# Subtraction Report — Roc compiler (Zig)

*Read-only audit per `agents/agent-subtract.md`, 2026-08-26. Seven parallel passes: postcheck, check/types, backend/lir, eval/builtins, front-end (parse/canonicalize/base/collections), cli/compile/tooling, and a whole-system architecture pass. No files were modified.*

**Confidence labels:** *verified* = the auditing pass read the cited lines and grepped the whole repo for usages; *probable* = structure confirmed, feasibility of the fix not proven; *speculative* = needs a design pass. Re-verify before acting — none of this was compiled or tested.

## Headline

`src/` is ~700k lines of Zig. The audit found:

- **~16,000 lines of verified-dead code** — deletable now, nothing references it (Part II).
- **~20,000 lines of mechanical duplication** — collapsible with low risk (Part III).
- **One dominant systemic lever:** the experimental `.boxy` lowering strategy costs **~75,000 lines (~9% of the compiler)** and taxes every LIR pass, backend, and the ARC soundness argument. Three independent audit passes converged on it as the single highest-value subtraction (S1).
- **Eight real bugs** surfaced as side effects of the duplication being audited (Part IV).
- A cluster of **actively misleading docs**, including a CI script that guards a directory deleted long ago (Part V).

---

## Part I — Systemic simplifications (ranked)

### S1. Retire `.boxy` — one lowering strategy, one LIR contract (~72,000–75,000 lines)

`design.md:5433-5437` claims backends can't tell `.lss` from `.boxy` output — but the shared LIR statement union carries **14 `boxy_*` variants** only `.boxy` can emit (`src/lir/LIR.zig:498,511,622,765-853,1122`), and because the repo bans `else =>` prongs, **30 files** must handle all of them: `arc.zig` (259 boxy lines), `arc_solve.zig` (102), `arc_certify.zig` (76), every backend, the interpreter, the image format. Total boxy-mentioning lines outside `postcheck/boxy/`: **8,244**. `postcheck/boxy/` itself is **62,294 lines** (35% of postcheck).

What it buys: `--specialize=no`, called "experimental" in `src/cli/cli_args.zig:419` and `design.md:5416`, documented in no user-facing doc, exercised by ~12 CLI test cases and one benchmark line (`ci/benchmarks_zig/run_fx_benchmarks.sh:618`). It is **not a semantic peer**: it never adopted the shared Maranget match compiler (`match_tree.zig:3`: "`.boxy` does not consume this yet" — directly contradicting `design.md:8744-8746`), and `projects/big/one-value-semantics-layer.md` records that `Inspect` has three implementations and structural eq/hash two *because* `.boxy` exists.

- **Deleted:** `postcheck/boxy/` (62.3k) + `eval/boxy_runtime.zig`/`boxy_abi.zig`/tests (11.9k) + `src/boxy_runtime/` + ~1,500 lines of boxy branches across LIR passes and backends + the `SpecializationStrategy` concept, a cache-key dimension, and 14 statement shapes from every ownership proof.
- **Who notices:** nobody outside the compiler team; `.lss` is the default at every `--opt` level and CTFE is hard-wired to `.lss` (`design.md:5417-5421`).
- **Why:** keeping it is a permanent 2× tax on every value-semantics feature and a 14-variant tax on every LIR pass. If the boxing idea matters, it belongs on a branch, not in the union ARC quantifies over.
- **Confidence:** verified (costs, doc quotes); the retirement itself is a strategy decision for the team.
- Retiring it also collapses findings S4 (half), D-boxy items in Part III, and shrinks three of the seven `projects/big/` specs.

### S2. Land `reunify.md` — or demote it. Three unification engines, zero progress on the declared fix (~8,000–15,000 lines)

The repo has three unifiers: `src/check/unify.zig` (4,171 lines), Monotype's `InstGraph` (`src/postcheck/monotype/solve.zig:409-5114`, a 4,705-line struct with its own `unify` at `:3445`), and Lambda Solved's (`src/postcheck/lambda_solved/solve.zig:1677`, 3,285 lines). `reunify.md:579-588` names the resulting failure class precisely (checking concludes X, postcheck re-derives X′≠X, backends compile X′) and concludes the middle engine should be replaced by substitution into a published, immutable pool.

**Implementation status: zero.** None of the plan's identifiers exist in `src/`. Its own Slice 0 ("amend `design.md`") never happened — `design.md` and `AGENTS.md` never mention reunify, so every agent working in the repo still treats the instantiation graph as the design. Meanwhile the bookkeeping reunify says "deletes with the graph" keeps growing (359 `Deferred`/`refill`/`snapshot` lines across `postcheck/monotype/`).

- **Minimum action regardless of scheduling:** land the design.md amendment or move `reunify.md` into `projects/big/`. A 2,328-line authoritative-sounding plan at repo root that the authoritative docs don't acknowledge is worse than no plan.
- **Confidence:** verified (status); the plan itself is the repo's own judgment.

### S3. Delete the materialized Lambda Mono tree — a fifth evaluator kept alive by one debug-only check (~5,500–8,300 lines + 2 CI jobs)

`design.md:62-67` says Lambda Mono "is decision tables … not a second stored expression tree" — yet the tree exists: `lambda_mono/eval.zig` (3,296 lines, a tree-walking evaluator whose only consumer is `src/eval/test/lambda_mono_differential_runner.zig`), `lower.zig` (1,500, reachable only via `verifyMaterializedDecisions`, which starts with `if (builtin.mode != .Debug) return;` at `solved_lir_lower.zig:2579`), `ast.zig`, `type.zig`, plus `TypeEquivalence` (952 lines in `solved_lir_lower.zig:9523`, verifier-only), 1,933 lines of test runner/corpus, a dedicated build target, 5 mutation patches, and CI + nightly jobs.

The cross-check is real (mutation-tested), but `src/eval/test/parallel_runner.zig` already runs every eval case on interpreter + dev + wasm + llvm with byte-identical output — a stronger differential over semantics that actually ship. `lambda_mono/specialize.zig` (68 lines) is dead outright. Best sequenced **after** S2, which is what makes the tree redundant rather than merely expensive. Note this argues against `projects/small/lambda-mono-oracle-fidelity.md` — raise as a decision, don't just apply.

### S4. Derive codec bodies once — parser/encoder state machines are hand-built as IR, twice (~12,000–14,000 lines)

The checker types derived Parse/Encode codecs but never produces bodies; each lowering strategy hand-writes the same parser state machine in its own IR: ~10,285 lines in `monotype/lower.zig` (103 `*Parse*` fns, 63 `*Encod*`), ~6,300 across `boxy/lower.zig` + `boxy/plan.zig`, with one-to-one name mirrors (`lowerParseShapeFromState` ↔ `lowerGeneratedParseShapeFromState`). Fix: emit the derived body once, upstream of the strategy split, as typed CIR. S1 deletes the boxy half for free; the `.lss` copy remains worth single-sourcing.

The check side mirrors this: three parallel Parse/Encode families in `Check.zig` (~2,150 lines) that **have already drifted** — `typeSupportsDerivedParse` handles `.nominal_type` at flat-type level (`Check.zig:28460`) while the Encode twin doesn't (`:28769`); `nominalSupportsDerivedParseShape` (`:28613`) unwraps only `nullTry` while its 78%-identical sibling (`:28650`) unwraps three Try forms. One direction-parameterized walk (~700–900 lines removed) makes the drift class unrepresentable. Relatedly, five of design.md's Rewrite Inventory entries are all "close a derived codec's inferred row" — they want to be **one** declared rule with a direction/codec parameter, shrinking the inventory itself. (`RedirectRule`/`dangerousSetVarRedirect` itself is fine: 2 members, 2 call sites, good discipline — don't touch.)

### S5. Dissolve the `Draft*` shadow IR in `monotype/lower.zig` (~4,000–5,500 lines)

`monotype/lower.zig:9437-12753` declares 60+ `Draft*` types — a strict mirror of `Ast` (all 43 `DraftExprData` variants are also `Ast.ExprData` variants) plus id-remap machinery (`buildDraftCommitMap` `:7362`, `sealCoreIntoProgramWithMap` `:11671`). The only real delta is `DraftTypeCell` (`:9437`) because types aren't final until the instantiation graph seals. Emitting `Ast` nodes directly with a late-bound type cell and one resolution sweep at seal deletes the mirror. `DraftTypeCell` itself proves per-node late binding works. Verified (mirroring) / probable (single-IR form). Blocked partly by `structural_test.zig` pinning draft internals — see S8.

### S6. Dev backend: parameterize on ABI class, not `RocTarget` — 16 → 5 monomorphizations of a 27k-line generic

`LirCodeGen(comptime target: RocTarget)` (`src/backend/dev/LirCodeGen.zig:659`) consults `target` only as `toCpuArch()` (178×), `isWindows()` (7×), `toOsTag()` (2×) — yet `ObjectFileCompiler.zig:734-759` instantiates it **16 times**. The needed classification already exists (`src/layout/abi/call.zig:146 Target`). `aarch64/Emit.zig` is a generic that never reads its parameter at all, which is why its "CC identical across targets" tests (`aarch64/Emit.zig:2206-2260`) are tautologies. ~120 source lines, but the real win is build time, binary size, and one concept (the dev backend cares about ABI class, not 30 targets). Verified.

### S7. One type-graph walk in `src/check` (~2,500–3,500 lines)

`Check.zig` contains **16 hand-rolled recursive predicate walks** over the same type graph (41 functions take the identical `visited: *AutoHashMap(Var, void)` parameter; two walks are 97% identical for one boolean — `Check.zig:10464` vs `:10533`), and **7 explicit-stack frame machines** duplicate the same child-enumeration (`types/instantiate.zig:116` and `check/copy_import.zig:135` are the same algorithm token-for-token). One `TypeFold(comptime Visitor)` in `src/types/` turns each walk into a ~30-line visitor and merges instantiate/copy_import behind a `Translator`. Sub-item with outsized value: a `BuiltinShape` classifier (list/box/set/dict/try/…) replacing the 7×-repeated if-chain of `nominalListPayloadVar`→…→`nominalIsBuiltinTryType` (`Check.zig:24613` et al.) — makes "this walk forgot `Set`" a compile error. Verified (duplication) / probable (win size).

### S8. Collapse four architecture-enforcement mechanisms into one

Invariants are enforced in prose (`design.md`, 12.3k lines), Perl over source text (`ci/*.pl`, ~1,070 lines — one of which is dead and would die on startup, see Part V), Zig tests that `@embedFile` their own neighbors and grep for substrings (`src/postcheck/structural_test.zig`, 1,684 lines; `src/backend/structural_test.zig`), and proper AST lints (`ci/zig_lints.zig`, `ci/tidy.zig`). Layer 3 is fragile — 33 tests assert *function ordering and exact call spellings* in a 52k-line file (`structural_test.zig:911-993` pins strings like `"locals: ?[]?DraftLocalId = null"`) — and **evadable**: `boxy/lower.zig:4071 singleChildRepForDesc` is a verbatim re-derivation of `plan.zig:11838 requiredSingleChildOf` under a different name, sailing past the anti-duplication lint at `structural_test.zig:1503`. Migrate real invariants to AST lints or type-level boundaries (the `dangerousSetVarRedirect` comptime-rule pattern is the model); delete the rest. ~1,800 lines plus removal of the main friction against S5. Also directly caused: dev-backend structural test is 37 lines of substring assertions that fail the build if anyone writes "ownership signature" in a comment.

### S9. Default-app handling: serve the synthetic root through the existing virtual-FS override (~750 lines in `main.zig` + leakage into 8 modules)

Headerless `main!` files are staged as a synthetic app on disk, compiled, then diagnostics are *remapped back* (`remapDefaultApp*`, `SyntheticDefaultAppMapping`, 17 dedicated functions in `src/cli/main.zig:3652-16814`; 73 refs in main.zig, 18 in `Can.zig`, 17 in `compile_build.zig`, …). `BuildEnv` already supports `CoreCtx.ReadFileOverride` (used by the LSP for unsaved buffers, `src/lsp/build_session.zig:63-68`) and `setSyntheticRootSourceMappingWithLineOffset` (`compile_build.zig:522`). Serving the synthetic root virtually with a recorded line offset makes regions correct at creation and dissolves the remap layer and the rocRun/rocBuild/rocCheck × DefaultApp matrix. Inventory verified; feasibility speculative — highest-risk item in this part, but well covered by CLI integration tests.

### S10. One compile-time-eval driver, one RocOps host (~400 lines, fixes two design.md violations)

`compile_time_finalization.zig` has two structurally identical per-root drivers (`lowerEvalAndFinishRoots:655` on `CompilerHost`, `lowerDevEvalAndFinishRoots:1176` on `CompileTimeHost`) that **diverge in behavior** — see bug B3. Four hand-rolled `RocOps` environments each carry their own allocation map and alignment switch (`compiler_host.zig:137`, `compile_time_host.zig:290`, `runtime_host.zig:428`, `interpreter.zig:103`), while `src/host_alloc/mod.zig` documents itself as "the one scheme every host uses" and none of the four use it. Delete `compiler_host.zig`, fold the drivers, back the survivors with `host_alloc`.

### S11. Config types that represent states the program never has

- **`TargetConfig`** (`src/lir/checked_pipeline.zig:66-122`): ~10 independent booleans whose doc comments repeat the same sentence three times ("enabled for optimized builds, off for dev and compile-time"); exactly one production site sets them, all derived from `opt` (`src/cli/main.zig:11789-11791`). Replace with a 3-value `LoweringProfile` enum + explicit test-override struct. ~200 lines, kills a representable-state class. Verified.
- **`RocTarget`** (`src/target/mod.zig:377-427`): a 39-variant flat cross-product of arch × cpu-level × os/abi; 286 lines across `src/` are nothing but bare variant prongs. Decompose to `{arch, cpu_level, os_abi}` preserving names. ~300–600 lines; wide blast radius. Probable.
- Four hand-rolled arch/OS classifiers duplicate `roc_target.classifyCpuArch/classifyOs` (one — `object/mod.zig:44-96 ObjectFormat` — is entirely dead). ~200 lines. Verified.
- `src/ipc/platform.zig` splits POSIX into "linux" (raw syscalls) and "posix" (libc) with otherwise-identical bodies (`:589-648` etc.). Collapse to `{windows, posix}` + one `memfd_create` branch — *after* checking whether raw syscalls were deliberate for no-libc builds. ~150 lines. Verified (duplication).

### S12. Merge the Lambda Solved / Lambda Mono type stores (~1,500 lines) — probable

Type shape is represented 7 times across the pipeline. Stores 3/4/5 (Monotype, Lambda Solved, Lambda Mono) each redeclare `TypeId`/`Span`/`Field`/`Tag`/`Store` with 14 identical method names; the comptime-mixin fix was tried and is impossible (Zig 0.16 removed `usingnamespace`, per `projects/README.md`). The unproposed move: resolve Lambda Solved's content in place (its `solve.zig` is the only writer), removing the Lambda Solved → Lambda Mono store translation from the 10,474-line `solved_lir_lower.zig`. Needs a read of that translation first.

---

## Part II — Verified dead code (delete now, ~16,000 lines)

Everything here was checked repo-wide for references (including `build.zig`, `ci/`, tests); "dead" excludes `refAllDecls`-only liveness. Zero behavior change unless noted.

| What | Where | Lines | Notes |
|---|---|---|---|
| Orphaned fluxsort/quadsort port | `src/builtins/sort.zig` + `fuzz_sort.zig` + build entry | **~4,075** | `List.sort_with` is implemented in Roc (`src/build/roc/Builtin.roc:3868`); nothing registers these builtins |
| Wasm "host import" builtin mechanism | `WasmCodeGen.zig:1928-2285,742-875` + `.host_imports` arm + ~123 host fns in `wasm_runner.zig` | **~3,000** | `configureBuiltinRelocs` (`:2574`) overwrites the mechanism before any code compiles, and `verifyNoBuiltinImports` (`WasmModule.zig:2546`) panics if a `roc_*` import survives — the arm is unreachable by construction |
| Dead front-end `pub` decls | 77 in canonicalize, 33 reporting, 32 layout, 12 parse, 12 base, 6 collections | **~2,050** | Incl. 26 kept alive only by their own tests (six `DeclIndex` query methods among them) |
| Dead diagnostic-report builders | `src/canonicalize/Diagnostic.zig` | **1,094** | 43 of 51 `build*Report` fns unreferenced — a migration to `ModuleEnv.diagnosticToReport` that was never finished (see bugs B1/B2) |
| Unreachable object reader | `src/backend/dev/object_reader.zig` | **1,016** | Only ref is a re-export; its header describes an LLVM JIT path that now uses `dlopen` |
| Whole dead build modules | `src/symbol/` (167) + `src/values/` (696) + 20+ wiring sites in `modules.zig` + **2 minici CI steps** | **~880** | Zero `@import("symbol"|"values")` anywhere; real symbols live in `LIR.Symbol`. Add a zero-importer build lint so the class can't recur |
| Dead files in base/collections/reporting | `PackedDataSpan.zig` (195), `safe_hash_map.zig` (152), `safe_memory.zig` (115), `ArrayListMap` (58), `base/mod.zig:74-148` literal types (~75), `DocumentBuilder` + cascade (~260) | **~855** | `DocumentBuilder` deletion makes 3 `DocumentElement` variants unconstructible → more deletion in `renderer.zig` |
| Dead cli/compile/lsp/docs `pub` API | `compile/dependency_sort.zig` (297, whole file), `lsp/module_lookup.zig` (~200), cache-stats 3-of-4 render targets, BuildEnv/channel/cache accessors, etc. | **~700** | Check `src/compile/README.md`'s documented embedding surface before deleting BuildEnv accessors |
| Dead check/types decls | `checkExprReplWithDefs` (103), 9 fns in `typed_cir.zig` (~15% of file), `checked_artifact.zig:3568,3608` (76), `SourceDecl` constructors, `store.zig` helpers, + ~30 small fns | **~600** | Incl. `hoist_roots.zig:10 selection_algorithm_version = 4` — read by nothing, looks like a cache key, isn't (actively misleading) |
| Dead eval files/exports | `eval/stack.zig`+test (303), `crash_context.zig` (58), `wasm_runner` dead entries + drifted freestanding stub, `value.zig` fns | **~450** | The stub even declares a function the real module doesn't have |
| Dev backend dead API | dead half of `dev/mod.zig` (incl. two *dangling* re-exports `backend/mod.zig:23-24`), `ValueStorage.zig`, write-only `CallingConvention` struct (`:32-175`), 2 unconstructed `Relocation` variants + branches, 19 never-emitted instruction encoders, `WasmModule` dead fns, `LirStore` dead accessors | **~1,000** | The `Relocation` deletion removes an error condition (`UnsupportedDevRunRelocation`) that can no longer occur |
| Wasm archive reader | `backend/wasm/ObjectArchive.zig` + `CliProblem.zig:26` | **219** | Residue of a removed feature per `design.md:11190`; leaves 5 impossible error variants in a CLI error set |
| `ExternalDecl` store | `canonicalize/ExternalDecl.zig` + field + accessors | ~90 | Never written to; **serialized format bump** required |
| 11 never-emitted CIR diagnostics | `Diagnostic` union + `Node.Tag` + 6 files of arms | ~330 (+120 test fixtures) | **Node.Tag ordinals shift → CIR format bump**; do together with `ExternalDecl` |
| Layout back-compat aliases | `layout/store.zig` ~20 aliases from the record/tuple→struct rename | ~110 | The aliases are the only thing suggesting records ≠ tuples at layout level |
| Fuzz scaffolding in production tokenizer | `parse/tokenize.zig:1975-2665` → move to `test/fuzzing/` | 690 moved | Ships in every `roc` binary today |
| Dead CI script | `ci/check_mir_cutover_contracts.pl` | 91 | Opens `src/mir/*` — a directory that doesn't exist; would `die` on first read; referenced by nothing |
| Tautological CC tests + formula-restating encoder tests | `aarch64/Emit.zig:2206-2260`, `x86_64/Emit.zig:1729-1861`, etc. | ~185 | Assert `X == X` because the generics never read their target parameter |

Also checked and **cleared** (not dead): all 75 `zig build` steps, all `src/cli/*.zig` files, `snapshot_tool`/`glue`/`echo_platform`/`bump`/`base58`, all five reporting render targets, `roc test`'s multiple execution paths (genuinely different mechanisms), the fmt regression tests (issue-numbered, keep).

---

## Part III — Duplication collapses (high confidence, mechanical)

| # | What | Where | Est. lines | Risk note |
|---|---|---|---|---|
| D1 | Exhaustive error-name prongs routed to one body: 165 runs, 4,551 lines repo-wide (`else =>` is lint-legal for error switches, `ci/zig_lints.zig:329`) | `src/cli` 2,045 · `snapshot_tool` 712 · `lsp` 315 · `ctx` 288 · others | **~3,970** | Keep enumeration only where prongs differ; 31 blocks just rethrow, where exhaustiveness proves nothing. Byte-identical 30-arm example: `main.zig:10089` ≡ `:10460` |
| D2 | Hand-written S-expression printers → one comptime-reflective dumper with per-type hooks | `parse/AST.zig` (1,706 = 47% of file), `canonicalize/*` (~2,300) | **~3,500–3,900 net** | Deliberate: all 394 snapshots regenerate once; removes the "did I add an S-expr arm?" step forever |
| D3 | LSP integration tests: 36 verbatim copies of one JSON-RPC handshake → `runSession()` helper | `lsp/test/handler_integration_tests.zig` (+6 in `server_test.zig`) | **~2,500** | Test-only |
| D4 | `spec_constr.zig`: 51 `clone*` fns in 3 families over one `ExprData` (near-full tag list spelled out 21×) → one `mapExpr(policy)` | `monotype_lifted/spec_constr.zig` | **~1,500–2,000** | The abort-path policy (`cloneExprFresh` returns null) must survive explicitly |
| D5 | Boxy unit tests: hand-built CIR fixtures, ≥25 verbatim 37–50-line repeats → extend `test_fixtures.zig` builder | `boxy/lower.zig` test section (8,664 lines) | **~3,000–4,000** | Moot if S1 lands |
| D6 | `instantiate.zig` ≡ `copy_import.zig` (token-for-token, same doc comment) → one graph-copy + `Translator` | `src/types` / `src/check` | ~800–1,000 | Part of S7 |
| D7 | `inspected.zig`: 4-deep default-argument ladders ×7 families, 8 one-line backend forwarders forced by a comptime backend param, 4 byte-identical helpers shared with `inspected_run.zig`, 4 dead fns | `src/eval/inspected.zig` (3,239 lines) | ~600 | Runtime `Backend` enum also deletes the parallel fn-pointer tables in `parallel_runner.zig` (one slot already dead: `:1246`) |
| D8 | `report.zig`: 42 open-coded copies of its own `addSourceHighlightRegion` (`:234`) + 97%-identical builder triplets | `src/check/report.zig` | ~550 | Must preserve exact diagnostic text (snapshot-pinned) |
| D9 | Playground reimplements the REPL that `cli/ReplSession.zig` provides — **with a live divergence** (`import` lines classify differently: `main.zig:850` vs `ReplSession.zig:2030`) | `playground_wasm/main.zig:249-1236` | ~700 | Deliberate protocol breakage; playground README already points embedders at `repl_wasm` |
| D10 | 28 builtin wrappers in `LirCodeGen.zig:438-650` re-implement `dev_wrappers.zig` (17 byte-identical) to feed a false "adapter address differs" concept; kill `callBuiltinWithAdapter` | `src/backend/dev` | ~300 | Only profiler symbol names change |
| D11 | `checked_artifact.zig`: 13-slice closure enumerated longhand ~13×; 11 `Artifact*Ref` structs → one `Qualified(T)`; two 92%-identical projectors; 97%-identical template-closure builders | `src/check/checked_artifact.zig` | ~680 | `Serialized` layout is fingerprint-asserted, so mistakes are compile errors |
| D12 | `boxy_abi` list wrappers ≡ `dev_wrappers` twins; `dev_wrappers` repeats one refcount-context block 14× | `src/eval`, `src/builtins` | ~430 | Boxy half moot if S1 lands |
| D13 | Five build-command prologues (cache config, dirs, BuildEnv, reporter) → one `prepareBuild()`; hot-reload allocator (21 pure fns, ~510 lines) moves from `main.zig` to `src/ipc/hot_reload.zig` (whose matching API sits unused) | `src/cli/main.zig` | ~400 + 510 moved | The two flag differences (no_cache/verbose) must become explicit parameters |
| D14 | Monotype store: `DurableView` ≡ `Store.View` (12/12 methods byte-identical) + a third copy on `Store`; `typeEqlAcrossStores` subsumes in-view `typeEql` (2 callers) | `monotype/type.zig` | ~450 | Unifying the equality walkers picks a winner on alias unwrapping — exactly the drift class reunify.md names |
| D15 | Register-file bookkeeping duplicated between arch CodeGens (117-line blocks, ~15 lines genuinely differ) — incl. **opposite stack-offset sign conventions** | `x86_64/CodeGen.zig:84-200` vs `aarch64/CodeGen.zig:78-192` | ~100 | Extract first; unify the sign convention as a separate step |
| D16 | Two shared-memory image formats + two shims with a duplicated skeleton → shared `image_format.zig` / `shim_runtime.zig` | `RunImage.zig` / `lir_image.zig` / both shims | ~250 | Safety-critical bounds-checking paths — review hard |
| D17 | Misc verified: 3× 43-line parse prologue (`monotype/lower.zig:22940,24255,24419`), `Lowerer.deinit`≡`finish` 39-line teardown ×2, 5 copies of `invariant` (one with a freestanding fix the others lack — B8), `checkUnaryMinusExpr`≡`checkUnaryNotExpr` (1 line differs), `str_split_first`≡`str_split_last` interpreter blocks, byte-identical bundle/unbundle path formatters (`main.zig:7673`/`7700`), 4 static-lib build roots = a 2×2 matrix, `sljmp` platform chain ×3, unify.zig's "legacy" second visited mechanism, telescoping `mk*` constructor chains | various | ~600 | — |

Also: `roc docs --serve` is a hand-rolled HTTP server in `main.zig:17017-17194` — untested, unreferenced by CI/docs, no visible path-traversal guard (B5), no shutdown path. Delete the flag (~185 lines); help text can suggest `python3 -m http.server`. `render_markdown.zig` (1,375 lines) is a full Markdown engine used only by `--with-lang-ref` website content — move to the site repo, or at minimum route doc-comment rendering through it instead of the second markup layer in `render_html.zig:1763-2060` (probable; check who builds the site first).

---

## Part IV — Bugs surfaced by the audit (worth fixing regardless)

1. **Reachable panic on user source.** `ModuleEnv.zig:3710-3715` panics on `.invalid_string_interpolation`, which is emitted at `Can.zig:12276,12304`. A (dead) report builder for exactly this case exists at `Diagnostic.zig:861`. *(probable — depends on canonicalization returning null for an interpolation body in practice)*
2. **Misspelling tips silently lost.** `common_misspellings.getIdentifierTip` is called only from dead code (`Diagnostic.zig:730`); the live out-of-scope-identifier path (`ModuleEnv.zig:1566`) no longer suggests corrections. *(verified)*
3. **CTFE diagnostic divergence — a `design.md:10526` violation.** The interpreter path collects `dbg`/`expect` output (`compiler_host.zig:17-18`) but only ever reads `crash_message` (`compile_time_finalization.zig:833,840`), and drops `had_problem` (`:1735` vs the dev path `:1502`). Compile-time `dbg` output disappears on wasm-hosted compiler builds. *(verified)*
4. **Cross-compiled macOS `roc --watch` hangs forever.** `target_is_native=false` (any cross build, `build.zig:3149`) selects FSEvents *stubs* that `markReady()` and never emit events; `waitForWatchChange` (`main.zig:13802`) spins. Fix doubles as a deletion: route macOS to the existing kqueue backend and delete the FSEvents backend + stubs + the `target_is_native` build option (~390 lines). Trade-off: kqueue is fd-per-file; the CLI already re-hashes on wake, so a poll fallback preserves behavior. *(verified)*
5. **`roc docs --serve` path traversal.** `resolveFilePath` doesn't reject `..`. Deleting the server (above) is the cheap fix. *(verified that no guard is visible)*
6. **Live `.boxy` divergence.** `descriptorStorageRep` exists twice in `boxy/lower.zig` (`:3963` vs `:35516`) with different duplicate-role handling and a wrong panic message; one copy re-derives a Plan query under a new name, evading the anti-duplication lint. *(verified)*
7. **Target-parameter lie in shim caching.** `ShimLibraries.forTarget` (`main.zig:414`) discards its target but the cache filename interpolates it — identical bytes cached under N names, and `shimLibraryDigest` reads as target-sensitive when it isn't. Also explains the four Windows `targets/` dirs that stay empty. *(verified)*
8. **Portability drift in a 5×-copied helper.** Only `boxyLowerInvariant` (`boxy/lower.zig:37385`) handles `os.tag == .freestanding`; the other four copies would misbehave on wasm builds. *(verified)*

---

## Part V — Stale testimony: docs to delete or fix

| File | Action | Why |
|---|---|---|
| `ci/check_mir_cutover_contracts.pl` | delete | Guards `src/mir/` (deleted); would die on startup; referenced by nothing |
| `docs/llvm-codegen-comparison.md` | delete | Compares two files that don't exist; claims the LLVM backend is 1,700 lines (it's 12,873) |
| `design/editor/` (6 files) | delete | Pre-rewrite editor design; contradicts the shipped LSP |
| `design/language/RocStr.md`, `design/lambda-set-specialization.md` | delete | RocStr.md's own line 1: "almost completely outdated … wrong in many places"; live equivalents in `design.md`. Having `design/` beside `design.md` with only the latter authoritative is a trap |
| `devtools/debug_tips.md` | delete or rewrite | 17 references to the deleted Rust/cargo build; `CONTRIBUTING/README.md:117` sends new contributors here |
| `src/README.md` | rewrite | Lists 5 directories that don't exist, omits the 6 largest that do; status table matches no design.md stage |
| `src/lsp/README.md:5-16` | rewrite | Claims the LSP "doesn't provide any features yet"; `handlers/` has 9 features |
| `src/compile/README.md:195` (+ `coordinator.zig:1464`) | fix | Names a function that doesn't exist; references removed `src/io/` |
| `src/cli/REORGANIZATION.md` | execute or delete | Says main.zig is ~5,500 lines; it's 18,268 |
| `reunify.md` | ratify or move to `projects/big/` | See S2 |
| `experiments.md` | move to `projects/` | Orphaned; its acceptance-criteria framing is exactly a `projects/small/` spec |
| `AGENT.md` (→`.rules`) vs `AGENTS.md` | merge | Two agent-instruction surfaces with disjoint, partially conflicting content; `.rules`' "every src/ dir has a README" is 72% unmet and unenforced |
| One-liners | fix | `Glossary.md:230` ("Symbol: not yet implemented" — three exist); `snapshot_tool/main.zig:1271` (references MIR); `values/mod.zig:1-5` and `object_reader.zig:1-7` headers describe consumers that don't exist |

---

## Part VI — Suggested sequencing

**Now (no behavior change, ~10k lines):** Part II items without format bumps; D8/D11/D17 small merges; dead-doc deletions; the zero-importer build lint (so the `symbol`/`values` class can't recur).

**Next (mechanical, review-heavy, ~10k lines):** D1 (error prongs — keep enumeration only where prongs differ), D3, D4, D7, D10, D13, D14; the format-bump pair (`ExternalDecl` + 11 dead diagnostics) in one change; bug fixes B1–B4, B7, B8.

**Decisions to put to the team (the real leverage):**
1. **S1 — retire `.boxy`.** ~9% of the compiler; three independent passes converged here; every other big item shrinks if it lands.
2. **S2 — schedule or demote `reunify.md`,** and land its design.md amendment either way.
3. **S3 — the Lambda Mono oracle** (a real verification net vs. 8k lines + 2 CI jobs; sequence after S2).
4. **D2 — S-expression printers** (one mass snapshot regeneration buys ~3.7k lines and a per-node maintenance step).
5. **S6, S11 — comptime parameters** that encode far more states than the program has.

**Design-pass-first (bold, don't rush):** S4, S5, S7, S9, S12; the check-side hoist-selection double-decision (`HoistSelectionTransaction`, 441 lines of rollback for a decision that `pruneSelectedHoistedRootsAfterSolving` re-makes authoritatively anyway — verified double, speculative removal); the `snapshot.zig` structural mirror of the type language (~1,300 lines whose consumers mostly read the cached rendered string).
