# M0.7 — `mem ingest --dry-run` | Field | Value | |---|---| | Phase | M0 — Read-only spine | | Size | S — under 1 day | | Status | ⬜ Not started | | Flags | — | | Spec | inlined below | | Blocks | M0.4, M0.6 | ## Goal See the chunk plan for a real project before spending a single model call on it. ## Facts (inlined — no spec read needed) `--dry-run` makes **zero network calls**. That is the property, not a side effect: this is the last checkpoint before inference, and its value is telling you the corpus is sane while feedback is still free. Output shape: ``` project /Users/rockliang/workplace/Poimen/agent-rust sources pi:4 files claude:2 files records 3118 (assistant 1445 toolResult 1261 user 196 system 216) chunks 412 over-budget 3 tokens min 84 p50 4870 p95 5000 max 11204 ``` `over-budget` counts single records exceeding the chunk budget (M0.3). A nonzero count is expected — large tool results — and is worth surfacing because it predicts requests the gateway may reject. Numbers to sanity-check against: a real pi session in this project has 2902 messages with the role split above, and toolResult being ~43% is the signal that the update gate has something to discriminate. ## Steps 1. `mem ingest --project --dry-run`. 2. Resolve sources: scan both source roots for directories whose `cwd` matches the project. Report which files matched. 3. Stream records through `mem-chunk` with the real tokenizer; accumulate stats without retaining chunk bodies. 4. Print the table above. Machine-readable variant behind `--format json`. 5. `--limit ` to stop after n chunks, for iterating on a large project. 6. Exit non-zero if zero sources matched — a silent empty plan reads like success. ## Acceptance - No network syscall occurs during `--dry-run`. - Stats are computed streaming; memory does not scale with corpus size. - Zero matched sources exits non-zero with a message naming the project key. ## Verify **Harness:** the fixtures from M0.5/M0.6, plus a network guard. **Integration test** — `tests/it_dry_run.rs`: 1. `a1_no_network` — run the command with outbound TCP blocked (inject a `reqwest` client that panics on use, or set an unroutable proxy); assert exit 0. 2. `a2_counts_match_sources` — record and role counts equal the sum of what the two adapters yield independently. 3. `a3_chunk_count_matches_chunker` — the reported chunk count equals `chunks(...).count()` computed separately. 4. `a4_over_budget_reported` — fixture with one oversized record; assert `over-budget 1`. 5. `a5_empty_project_fails` — unknown project key exits non-zero, message names the key. 6. `a6_constant_memory` — run against a 50 MB fixture; assert peak RSS stays under a bound well below file size. **Command:** `cargo test -p mem-cli dry_run` **False pass:** - Asserting the command exits 0 and printing looks right. A dry run that matched no sources also exits 0 and prints a tidy table of zeros — assertion 5 is what separates them. - Computing stats by collecting chunks into a `Vec` first. Every assertion here passes and assertion 6 is the only one that fails, which is why it is present. ## Traps - Making `--dry-run` construct the LLM client "but not call it". Construction reads credentials and can fail; the guarantee is no network, and the cheapest way to keep it is to not build the client at all. - Reporting p50 chunk size only. The max is the interesting number — it predicts which requests will be rejected downstream. --- Background: [DESIGN.md](../DESIGN.md) — Verification, P1