Commit e100d2c
authored
* docs(plan): static coverage ingestion (Istanbul JSON → `coverage` table)
Plans the C.11 candidate from `research/fallow.md` — `codemap ingest-coverage <path>`
reads Istanbul `coverage-final.json` into two new tables (`coverage` symbol-level +
`file_coverage` rollup), joinable to `symbols` for the killer "what's structurally
dead AND untested?" recipe in one query.
Resolves the open question from `fallow.md § 6` ("symbols column vs separate table?")
in favour of a separate table with `ON DELETE CASCADE` (D1) — coverage shape evolves
independently of structural columns; LEFT JOIN keeps NULL semantics explicit; rows
survive `--full` reindex via the `query_baselines` precedent (D6).
Key decisions:
- Istanbul JSON in v1; LCOV in v1.x; raw V8 traces never (D3, fallow's paid moat).
- One-shot `ingest-coverage` verb decoupled from `codemap` index runs (D4) — coverage
cadence (per `bun test --coverage`) ≠ index cadence (per file edit).
- Statement coverage only in v1 (D5); branch/function deferred until a consumer asks.
- MCP/HTTP exposure as a query column, not a separate `coverage` tool (D9) — composes
with every existing recipe + ad-hoc SQL.
- `codemap audit --delta coverage` deferred to v1.x (D10) — raw schema first.
Five-tracer plan: schema bump → engine → CLI verb → fixture + golden recipe → docs.
Plan only — implementation follows after CodeRabbit review per the established
workflow (PRs #46/47, #49/50, #51/52, #53/54).
* docs(plan): fact-check fixes — drop hallucinated SQL/projection/runner claims
Self-audit against the actual codebase surfaced four claims that didn't hold:
1. Killer recipe SQL referenced `callee_id` — `calls` is name-keyed
(`callee_name TEXT`, no symbol-id FK; see `db.ts` `CallRow`). Rewrote
the "no callers" predicate as `NOT EXISTS (… WHERE callee_name = s.name)`.
2. D7 claimed line-range projection is "the same `markers` already uses" —
`markers` is line-pinned (`line_number INTEGER`), no projection.
Reworded as "novel for this plan" with the actual mechanic spelled out.
3. D3 listed `bun test --coverage` as an Istanbul JSON emitter — `bun test
--help` shows only `text` / `lcov` reporters today. Removed bun from the
Istanbul-emitters list; left vitest/jest/c8/nyc with the explicit reporter
flags they need.
4. D12 contradicted D6 ("rows absent until re-ingest" vs "rows survive
`--full`"). Reconciled: empty is the correct initial state on first bump;
subsequent bumps preserve via the `dropAll()` exclusion. Quoted the
`lessons.md` policy verbatim instead of paraphrasing.
* docs(plan): v2 — fix CASCADE hazard + innermost-wins projection + nits
Self-grilling found two real schema design holes that would block execution:
1. **D6 CASCADE hazard.** Original draft keyed `coverage` on
`symbol_id REFERENCES symbols(id) ON DELETE CASCADE`. Every `--full`
reindex calls `dropAll()` → drops `symbols` → CASCADE wipes coverage,
regardless of whether `coverage` itself was excluded from `dropAll()`.
Recreated `symbols` get fresh auto-increment IDs anyway → coverage
permanently lost without re-ingest. Fix: natural-key PK
`(file_path, name, line_start)` — no FK to `symbols.id`. Survives the
`symbols` drop-recreate cycle. Trade-off: orphan rows when files are
deleted; cleaned by one explicit `DELETE FROM coverage WHERE file_path
NOT IN (SELECT path FROM files)` after every ingest.
2. **D7 overlapping symbols.** Original draft: `line_start ≤ stmt_line ≤
line_end` matches every enclosing scope. With nested symbols (class
methods inside classes, closures inside functions), one Istanbul
statement projects onto 3+ symbols, inflating `total_statements` 2-3×.
Fix: innermost-wins via `(line_end - line_start) ASC LIMIT 1`. New
`skipped.statements_no_symbol` counter for statements that fall outside
every symbol range (top-level expressions, side-effect imports).
Nits cleared in the same pass:
- D2: drop `file_coverage` rollup table from v1 (aggregateable via
GROUP BY on the symbol-level table; doubling sources of truth without
a benchmark is premature). Promote to v1.x with a real query.
- D11: spec the `total_statements = 0 → coverage_pct IS NULL` edge case
+ document the cross-file name-collision lossiness in the killer recipe.
- Drop `--prune` flag (orphan cleanup is unconditional, no flag needed).
- Drop per-row `source` column (single meta key sufficient; one ingest
at a time).
- Update killer recipe SQL to use the natural-key 3-column join.
- Drop made-up "~50 LoC LCOV ingester" estimate and "<50 ms / <1 ms /
~500 KB" performance numbers (no benchmark backed them).
- Tracer 1 / 2 / 3 acceptance criteria updated to match the new schema.
Plan is now ready for tracer-1 implementation. CodeRabbit pass deferred
(rate-limited 57m).
* docs(plan): tighten Bun-native API references (file read + perf note)
Plan correctly inherits the established Node vs Bun runtime split, but the
single tracer-3 reference understated it. Now:
- Tracer 3 cites `packaging.md § Node vs Bun` as the canonical pattern
source instead of pointing at config.ts in passing.
- Performance section calls out the actual lever — `Bun.file(path).json()`
uses Bun's native JSON parser, materially faster than V8 `JSON.parse`
on multi-MB Istanbul payloads (real coverage files for medium codebases
routinely hit several MB).
No new Bun-native API surfaces are added — the feature doesn't need
globbing, file writes, spawn, or hashing beyond what the existing engines
already use through their abstractions.
* docs(plan): v3 — ship LCOV in v1 + drop --source flag + bundle killer recipe
The "fully capable, no half-way APIs" principle reshapes three things:
1. **LCOV ingester ships in v1** alongside Istanbul. Original draft deferred
LCOV to v1.x, which would exclude `bun test --coverage` users — i.e.
codemap's own primary runtime. That's the textbook half-baked surface
the principle bans. Two parser front-ends share one `upsertCoverageRows`
core; LCOV is regex tokenizing over `SF:` / `DA:` / `end_of_record`.
Tracer 2 splits into 2a (shared core + Istanbul parser) and 2b (LCOV
parser), both writing identical normalised CoverageRow[] into the same
upsert path.
2. **`--source istanbul|lcov` flag dropped.** Auto-detection from extension
(`.json` → istanbul, `.info` → lcov, directory → probe both, error on
ambiguous) is unambiguous; a flag for "tell codemap what it can already
see" is API noise. Misnamed files can be renamed (one-liner) cheaper
than codemap can grow a flag.
3. **Killer recipe ships as bundled `untested-and-dead.{sql,md}`** in
`templates/recipes/`. Per the recipes-as-content registry (PR #37), the
high-value queries become first-class agent surface. A buried doc
snippet would be invisible to agents at session start; the bundled
recipe shows up in `--recipes-json` and gets a `codemap query --recipe
untested-and-dead` direct invocation.
Tracer 4 also fans out: Istanbul + LCOV fixtures cover the same partial
coverage shape; three golden recipes (`coverage-istanbul.json`,
`coverage-lcov.json`, `untested-and-dead.json`) prove format equivalence.
Out-of-scope, alternatives, performance section, title, and goal
statement all updated to match.
* docs(plan): v4 — agent-journey audit + bundled recipe shelf (D13)
Walked every D / OOS / tracer item against "fully capable + agent
first-class + no half-baked APIs". Found three half-baked surfaces:
1. **D2 deferral leaks "compose GROUP BY yourself" onto the agent.**
Deferring the `file_coverage` table is correct (no benchmark proves
it's needed) — but the agent-facing answer for "rank files by
coverage" was missing. Fix: keep table deferral, ship a bundled
`files-by-coverage.{sql,md}` recipe so the GROUP BY view IS
first-class.
2. **D11 name-collision lossiness was acknowledged but unmitigated.**
The killer recipe's `callee_name = s.name` cross-file lossiness
was documented in the recipe SQL comment, but the recipe `.md`
didn't give the agent any narrowing pattern. Now D11 ships three
concrete narrowing patterns in the `.md` (file_path scope, default-
export filter, exported-only restriction) so the agent has
workable mitigations on day one.
3. **Missing recipe shelf for common agent questions.** Walking the
journey: only "What's structurally dead AND untested?" had a recipe;
"Rank files by coverage" and "Worst-covered exported symbols" forced
ad-hoc SQL. Three recipes fully cover the agent journey end-to-end.
New D13 codifies the bundled-recipe principle: every common agent
question gets a `--recipe` verb. Three v1 recipes:
- `untested-and-dead.{sql,md}` (killer, with name-collision mitigations)
- `files-by-coverage.{sql,md}` (replaces D2's table deferral)
- `worst-covered-exports.{sql,md}` (top-N agent ask)
Each `.md` carries a frontmatter `actions` block (per PR #26) so agents
get per-row follow-up hints. All three appear in `--recipes-json`
automatically — agents discover them at session start.
New "Agent journey" section makes the principle visible: a table mapping
every common agent question to the v1 verb that answers it. If a row
ever shows "compose SQL yourself" without a recipe, the surface is
half-baked and needs a recipe before tracer 1 ships.
Tracer 4 expanded: ships all three recipes + five golden snapshots
(adds files-by-coverage.json + worst-covered-exports.json on top of the
three existing). Tracer 5 expanded: glossary + agent rule trigger
table gain three new rows.
Plan now passes the principle audit end-to-end.
1 parent 499f661 commit e100d2c
2 files changed
Lines changed: 170 additions & 0 deletions
0 commit comments