docs(plan): static coverage ingestion (coverage-final.json → coverage table)#56
Conversation
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).
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR adds planning documentation for "static coverage ingestion": a design doc defining a new ChangesCoverage ingestion plan & roadmap
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~2 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 52 minutes and 21 seconds.Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
docs/plans/coverage-ingestion.md (1)
58-60: 💤 Low valueClarify meta value types.
The schema comments show
<unix-ms>forcoverage_last_ingested_at, which suggests a numeric timestamp, but themetatable likely stores values as TEXT. Consider clarifying whether these are stored as stringified numbers or if the meta table schema supports typed values.This is a minor documentation clarity point — the implementation will resolve it, but being explicit in the plan prevents ambiguity during tracer
#1(schema bump).Suggested clarification
-INSERT INTO meta (key, value) VALUES ('coverage_last_ingested_at', '<unix-ms>'); -INSERT INTO meta (key, value) VALUES ('coverage_last_ingested_path', '<abs-path>'); +-- Meta table stores TEXT values; numeric timestamp stored as string +INSERT INTO meta (key, value) VALUES ('coverage_last_ingested_at', '1714809600000'); -- unix-ms as TEXT +INSERT INTO meta (key, value) VALUES ('coverage_last_ingested_path', '/absolute/path/to/coverage-final.json');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/plans/coverage-ingestion.md` around lines 58 - 60, The INSERT examples for meta keys (coverage_last_ingested_at, coverage_last_ingested_path, coverage_last_ingested_source) are ambiguous about value types — clarify whether meta.value is TEXT and thus expects stringified numbers/paths/sources or if the schema uses typed columns; update the docs to state the meta table's value type explicitly (e.g., "meta.value is TEXT, store unix-ms as stringified integer" or "schema uses INTEGER/JSON for typed values"), and if you decide to change the schema, note the required schema change and migration step so implementers know to add/alter columns rather than rely on implicit typing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/plans/coverage-ingestion.md`:
- Around line 11-20: The SQL uses non-existent columns/tables: replace the
callee_id/symbol_id joins with text-name joins against calls.callee_name and
symbols.name (e.g., LEFT JOIN calls ON calls.callee_name = s.name) and remove or
replace the non-existent coverage table; either join to the actual coverage
source (use the real coverage table name/column that holds coverage_pct) or
compute coverage_pct from existing data (or add coverage_pct to the schema)
before using COALESCE(c.coverage_pct, 0). Ensure the final query references
symbols.name, calls.callee_name/caller_name, and the real coverage column/table
instead of callee_id/symbol_id or a missing coverage table.
---
Nitpick comments:
In `@docs/plans/coverage-ingestion.md`:
- Around line 58-60: The INSERT examples for meta keys
(coverage_last_ingested_at, coverage_last_ingested_path,
coverage_last_ingested_source) are ambiguous about value types — clarify whether
meta.value is TEXT and thus expects stringified numbers/paths/sources or if the
schema uses typed columns; update the docs to state the meta table's value type
explicitly (e.g., "meta.value is TEXT, store unix-ms as stringified integer" or
"schema uses INTEGER/JSON for typed values"), and if you decide to change the
schema, note the required schema change and migration step so implementers know
to add/alter columns rather than rely on implicit typing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 77028727-a2bc-497e-87f8-930409f6f689
📒 Files selected for processing (2)
docs/plans/coverage-ingestion.mddocs/roadmap.md
…r 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.
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).
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.
… 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.
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.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
… PR #56 plan (#57) * feat(coverage): add `coverage` table + SCHEMA_VERSION 5→6 (Tracer 1) First tracer of the static coverage ingestion plan (docs/plans/coverage-ingestion.md). Pure schema change, no engine yet. What lands: - New `coverage` table with natural-key PK (file_path, name, line_start) per D6. Deliberately NOT a FK to symbols.id: dropAll() drops symbols on every --full reindex and the recreated rows get fresh AUTOINCREMENT ids; CASCADE would wipe coverage on every full rebuild. Natural key sidesteps the entire FK/CASCADE hazard. Orphan cleanup (file deleted from project) lives at the end of every ingest in tracer 2a — exercised by the new test using a raw DELETE so the contract is locked in before the engine code lands. - `idx_coverage_file_name` mirrors the typical join shape (the killer recipe joins symbols ↔ coverage on file_path/name/line_start) and the (file_path, name) prefix also covers the GROUP BY file_path scan used by the bundled files-by-coverage recipe (D2 + D13). - `coverage` table is intentionally absent from `dropAll()` (joins the query_baselines precedent), so user rows survive --full and SCHEMA_VERSION-mismatch rebuilds. - SCHEMA_VERSION bumped 5→6. Existing installs auto-rebuild on next `codemap` run via the schema-mismatch path in createSchema(); the new table is empty until first `codemap ingest-coverage` invocation (tracer 3), which is the correct initial state per D12. - New db.test.ts case exercises the full contract: round-trip of a partial-coverage row set including the total_statements=0 → NULL edge (D5), survival across dropAll() + createTables(), and the orphan- cleanup DELETE that ships in tracer 2a's engine. * feat(coverage): shared upsert core + Istanbul parser (Tracer 2a) Pure engine + format-agnostic write path. No CLI / FS side effects beyond db.run on the caller-supplied SQLite handle. `upsertCoverageRows({db, projectRoot, rows, format, sourcePath})` — shared core consumed by both Istanbul (this commit) and LCOV (next): - Normalises absolute paths via toProjectRelative (D8); files outside the project root land in skipped.unmatched_files. - Loads symbols per-file once and projects each statement onto the innermost enclosing symbol via JS-side (line_end - line_start) ASC tie-break (D7) — avoids the per-statement SQL round-trip the plan flagged as the hot path. - Aggregates per (file_path, name, line_start) bucket; total = 0 → coverage_pct NULL (D5 edge — "untested" ≠ "no testable code"). - Single transaction: per-file DELETE + bulk INSERT for idempotent re-ingest, then orphan-cleanup DELETE (D6 — no FK / CASCADE means deleted-file rows accumulate without this sweep), then writes the three coverage_last_ingested_* meta keys. `ingestIstanbul({db, projectRoot, payload, sourcePath})` — Istanbul parser front-end: - Subset-typed `IstanbulPayload` reads only statementMap + s; ignores fnMap / branchMap / inputSourceMap so the format can grow without churning the signature. - Inner `path` field takes precedence over the absolute-path key (handles webpack-style symlinked paths). - Tolerates malformed file entries (missing statementMap or s) — skips silently rather than throwing, so one corrupt file doesn't poison the whole ingest. 10 unit tests cover both pieces: - Shared core: innermost-wins aggregation, statement-outside-symbol observability, zero-statement-symbol → no row, UPSERT idempotence, orphan cleanup after file deletion, project-root normalisation, unmatched-file skipping, meta-key writes. - Istanbul parser: real-shape payload end-to-end, malformed-entry tolerance. * feat(coverage): LCOV parser front-end (Tracer 2b) Closes the second format axis from the plan — LCOV (`lcov.info`) ingester that shares the entire write path with the Istanbul ingester from Tracer 2a. `ingestLcov({db, projectRoot, payload, sourcePath})` is a pure regex tokenizer over the LCOV record format. Recognised lines: - `SF:<path>` — start of a file record (sets the "current file") - `DA:<line>,<exec_count>[,<checksum>]` — one statement per record - `end_of_record` — closes the current file record - Comments (`# …`), blank lines, and CRLF endings tolerated Ignored lines (everything else: `TN:`, `FN:`, `FNDA:`, `FNF:`, `FNH:`, `BRDA:`, `BRF:`, `BRH:`, `LF:`, `LH:`) — D5 scope is statement coverage only; the unused records are skipped without churning the parser. Throws on `DA:` outside an `SF:` block (malformed LCOV — the file would have nowhere to attach to). Missing `end_of_record` is tolerated by construction (next `SF:` resets the current file). Five unit tests cover both the LCOV parser in isolation and the cross-format equivalence contract: - `ingestLcov`: well-formed LCOV with multiple SF records → identical-shape coverage rows; CRLF + comments + blank-line tolerance; DA-outside-SF throws; optional `DA:` checksum field parsed. - Cross-format: identical Istanbul + LCOV inputs produce byte-identical rows in the `coverage` table — the contract that lets Tracer 4 ship one set of golden snapshots that asserts both formats land on the same answer. 15 tests total in coverage-engine.test.ts (10 existing + 5 LCOV). Zero new write-side code — the LCOV parser is pure parse-and-normalise into the same `CoverageRow[]` the Istanbul parser produces, then both hand off to `upsertCoverageRows`. * feat(coverage): codemap ingest-coverage CLI verb (Tracer 3) Wires the engine (Tracer 2a/2b) into the CLI surface end-to-end. Smoke- tested against a real Istanbul payload pointed at src/db.ts: ingest writes 2 symbols (openDb 100%, closeDb 0%); query --json reads them back with the expected coverage_pct shape. cli/cmd-ingest-coverage.ts: - Single positional arg + --json. No --source flag (per plan: format is auto-detectable from extension; flag noise dropped). - resolveArtifact() probes the path: .json → istanbul, .info → lcov, directory → looks for both filenames, errors if both or neither present (no precedence guessing — explicit is better than implicit). - File reads use the canonical Node-vs-Bun split: Bun.file().json() / .text() on Bun (native parser perf for multi-MB Istanbul payloads), readFile + JSON.parse on Node. Mirrors config.ts. See packaging.md. - Plain-text terminal output by default; --json emits the IngestResult envelope. Errors emit {"error":"…"} on stdout under --json, plain message on stderr otherwise. Sets process.exitCode (no process.exit) so piped stdout isn't truncated. cli/main.ts: dispatch new verb between snippet and query (lazy import matches every other cmd). cli/bootstrap.ts: validateIndexModeArgs() recognises ingest-coverage as a known subcommand; printCliUsage() lists it in a new "Coverage ingest" section. 7 parser tests cover all paths: requires the subcommand position, help on --help/-h, single-path default, --json flag, missing-path error, unknown-option error, multiple-paths error. Engine + CLI smoke verified locally: $ bun src/index.ts ingest-coverage /tmp/cov.json --json {"ingested":{"symbols":2,"files":1},"skipped":...,"format":"istanbul"} * feat(coverage): bundled recipes + fixture coverage data + goldens (Tracer 4) Closes the agent-surface contract from D13: every common coverage question gets a `--recipe` verb. Three v1 recipes ship under templates/recipes/, auto-discovered by the existing recipes-loader (`bun src/index.ts query --recipes-json` lists 15 recipes total — was 12). Bundled recipes: - `untested-and-dead.{sql,md}` — the killer recipe. Exported functions with no callers AND zero coverage. Recipe MD documents the v1 name-collision lossiness (D11) and three concrete narrowing patterns agents can apply: scope by file_path prefix, exclude default exports (Next.js entry points), restrict to public visibility. - `files-by-coverage.{sql,md}` — files ranked ascending by statement coverage. Replaces the deferred `file_coverage` rollup table (D2): GROUP BY on the symbol-level table is index-bounded by `idx_coverage_file_name`. NULL coverage_pct (zero-statement files) sorted last to avoid drowning out actual zero-coverage files. - `worst-covered-exports.{sql,md}` — top-20 worst-covered exported functions (LIMIT in the SQL, not configurable in v1 — config-driven LIMIT defers until a real consumer asks). Each recipe ships a frontmatter `actions` block (per PR #26) so agents get a per-row follow-up hint in `--json` output. Fixture data: - fixtures/minimal/coverage/coverage-final.json (Istanbul) and fixtures/minimal/coverage/lcov.info (LCOV) — equivalent partial coverage shape covering 10 fixture symbols across 6 files. Both formats use project-relative paths so they work without sed. Golden runner extension (scripts/query-golden.ts + schema.ts): - New `setup` array in scenarios.json runs one-time setup steps after `cm.index()` and before scenarios. Today: `{kind: "ingest-coverage", path: "..."}` — engine auto-detects format by extension. - Schema is backward-compatible: parser accepts either the legacy flat array OR the new `{setup?, scenarios}` object via z.union. - Setup dispatch reuses the engine functions directly (ingestIstanbul, ingestLcov) — same write path the CLI verb uses. 5 new golden snapshots prove the surface end-to-end on the fixture corpus: - coverage-rows-after-ingest.json: raw `coverage` table contents - untested-and-dead.json: 6 dead+untested symbols (incl. legacyClient + epochMs, both @deprecated) - files-by-coverage.json: 6 files ranked from 0% (api/client.ts) to 100% (ProductCard, usePermissions) - worst-covered-exports.json: top exported functions with coverage_pct - (LCOV cross-format equivalence proven by engine unit test, not duplicated in goldens — single setup step per scenarios.json keeps the runner simple) fixtures/minimal/README.md: new "What's exercised" row + Use section with `bun src/index.ts ingest-coverage` worked examples. All 24 golden scenarios pass. Engine unit tests still 15 pass. * docs(coverage): architecture + glossary + agent rule/skill lockstep + changeset (Tracer 5) Closes the plan execution per docs/README.md Rule 10 (agent rule + skill lockstep) and Rule 9 (new domain nouns → glossary same-PR). docs/architecture.md: - application/ list: add coverage-engine.ts (upsertCoverageRows core + ingestIstanbul / ingestLcov parsers). - New § coverage table under Schema with the natural-key PK rationale cross-referencing query_baselines as the dropAll() exclusion precedent. docs/glossary.md: - New `coverage` table entry: PK shape, NULL semantics, orphan cleanup. - New `codemap ingest-coverage` / Istanbul JSON / LCOV / static coverage ingestion entry: format auto-detection, innermost-wins projection rationale (D7), three bundled recipes, no-half-way principle. .agents/rules/codemap.md + templates/agents/rules/codemap.md (lockstep): - Index intro mentions coverage as part of indexed structure. - New CLI table row for `ingest-coverage`. - Four new trigger-pattern rows: "Is symbol X tested?" → coverage table; "What's structurally dead AND untested?" → --recipe untested-and-dead; "Rank files by test coverage" → --recipe files-by-coverage; "Worst-covered exported functions" → --recipe worst-covered-exports. - Two new quick-reference query rows: symbol coverage + bundled recipe. - Drift between .agents/ and templates/agents/ stays CLI-prefix-only (`bun src/index.ts` vs `codemap`). .agents/skills/codemap/SKILL.md + templates/agents/skills/codemap/SKILL.md (lockstep): new `coverage` table block with full column schema + the three meta keys. docs/research/fallow.md: - C.11 row marked Shipped (link to plan PR + this commit). - Open question "column on symbols vs separate table?" resolved to "separate table" with D1 + D6 cross-reference. docs/roadmap.md: drop the "Static coverage ingestion" backlog row (per Rule 2 — when a backlog item ships, it leaves the roadmap). docs/plans/coverage-ingestion.md: deleted per Rule 3 (plan files have the lifetime of the work; absorbed into architecture / glossary / agent rule on ship). .changeset/coverage-ingestion.md: minor changeset (per .agents/lessons.md "changesets bump policy" — new tables + SCHEMA_VERSION bump = minor). All 24 golden scenarios pass (5 new — coverage-rows-after-ingest, untested-and-dead, files-by-coverage, worst-covered-exports — plus the existing 19). 737 tests across 43 files, 0 fail. * fix(db): drop semicolon inside `--` line comment in coverage index DDL CI Build (Node, better-sqlite3) failed with "RangeError: The supplied SQL string contains no statements" on `node dist/index.mjs --full`. Root cause is the .agents/lessons.md lesson on naive `;` splitting: the new coverage index comment contained `symbols.{file_path,name,line_start};`, and runSql() splits multi-statement strings on `;` for better-sqlite3 (one statement per prepare). The trailing semicolon inside the `--` comment created an empty fragment that better-sqlite3 rejects. Reworded the comment to use parentheses + a period — same intent, no semicolon. Verified locally via `bun run build && node dist/index.mjs --full` against /tmp project: full index now completes on Node. * fix(coverage): apply CodeRabbit review on PR #57 — 3 valid threads All three CodeRabbit comments fact-check as ✅ Correct. Apply each: 1. **architecture.md:25 — broken anchor** `#static-coverage-ingestion` doesn't exist (the section heading is `### coverage — Statement coverage…`, not "Static coverage ingestion"). Fix: drop the parenthetical anchor link, replace with "schema in § Schema → coverage" pointing at the existing § Schema anchor that does exist. 2. **fixtures/minimal/README.md:24 — overstated deprecation focus.** Said "exercise the join axis against `@deprecated` symbols" but only 1 of 6 golden rows is `@deprecated` (legacyClient); the others (`FormatPrice`, `run`, `_epochSeconds`, `nanoseconds`, `_hiResEpoch`) carry `@internal`/`@alpha`/`@private`/no tag. Reword to "across exported functions of every visibility tag" — accurate and tag-neutral. 3. **untested-and-dead.md:21 — SQL precedence bug in narrowing pattern.** `AND s.visibility IS NULL OR s.visibility = 'public'` parses as `(... AND s.visibility IS NULL) OR (s.visibility = 'public')` per SQL precedence (AND binds tighter than OR). Every row with `visibility = 'public'` would bypass every other WHERE predicate — the agent following this pattern would get a much larger result set than intended. Fix: wrap in parentheses + explicit comment that the parens are load-bearing so future edits don't drop them. files-hashes.json refreshed for the README touch. * fix(docs): bump architecture.md schema version mention 5 → 6 CodeRabbit outside-diff comment on PR #57 (architecture.md:182) — caught that I bumped SCHEMA_VERSION 5 → 6 in db.ts (Tracer 1) but left the human-readable callout in architecture.md unchanged. Now in sync. Per `docs/README.md` Rule 6, "schema version" is explicitly listed as a decision value (not inventory) so the hardcoded number is fine — it just needs to track the constant. * docs(lessons): always construct gh body args via temp file, never heredoc PR #57's body shipped with literal backslash-backtick artifacts everywhere because the heredoc-into-`gh pr create --body` path shell-escaped every backtick. Add it to .agents/lessons.md alongside the existing template- literal backtick lesson — same root cause family (backticks + nested quoting), different surface (gh CLI vs TS template strings). Pattern: Write body → temp file → `gh pr <verb> --body-file <path>` → delete. One extra tool call, zero rendering surprises.
User reframe: codemap is the only SQL-based code index in the market;
inspiration comes from the free and open internet (LSP spec, SQLite
docs, AST tooling), not code-by-code cloning of any peer tool. Drop
fallow as a yardstick throughout.
Vital information preserved (per "don't lose any vital information
that is used to execute the plan"):
- Closed-dead-subgraph motivator for C.9 — kept as an abstract pattern
description in § 2.3 caveat (N-file packs with self-imports, non-
zero fan-in, none reachable from real entry). Was previously cited
to fallow.md § 0; now stands on its own merit.
- LSP read-side capabilities (show / impact / watch) — kept; LSP spec
upstream is now the protocol authority instead of fallow's
crates/lsp/.
- Runtime-tracing scope distinction — § 3 floor reframed to anchor on
"different product class entirely" (live process data vs static
analysis) instead of "fallow's paid moat."
- Predicate-as-API moat (A) — kept; justification now anchors on
intrinsic merit (SQL is durable, agents compose any predicate)
rather than "fallow ships verdicts; we don't."
- Schema-breadth moat (B) — kept; justification now "codemap-specific
extractions; their richness directly determines what JOINs are
expressible" rather than "fallow has none of these."
Section-by-section changes:
- HEADER — "Companion docs / Source for deep-dives" replaced with
"Companion doc" (competitive-scan only) + "Positioning" paragraph
declaring structural uniqueness.
- § 2.3 original-framing quote — paraphrased to drop the "(e.g.
fallow, knip, jscpd)" parenthetical; pointers to roadmap.md for the
full original wording. (roadmap.md itself still has the parenthetical;
separate-PR scope.)
- § 2.3 caveat — closed-dead-subgraph case described abstractly; no
source citation needed.
- § 2.5 LSP shim — "fallow has crates/lsp/" → "LSP spec upstream is
the protocol authority."
- § 3 intro — mission framing rewritten; "equal/surpass fallow"
language replaced with "extract maximum value from the SQL-index
architecture; grow the ecosystem" + "only SQL-based code index in
the market" positioning.
- § 3 Moat A — anchored on intrinsic merit (SQL durable + agent
composability) instead of fallow comparison.
- § 3 Moat B — anchored on "substrate every recipe layers on; richness
determines JOIN expressivity" instead of "fallow has none of these."
- § 3 ergonomic floors — dropped all "fallow is also fast" /
"Convergent with fallow" annotations; reframed runtime-tracing as
"different product class entirely (live process data, not static
analysis)" + reframed telemetry-upload as standalone safety promise.
- § 4 — DELETED ENTIRELY ("What to inspect in the fallow source
tree"). Replaced with "Inspiration sources for plan-PR authoring"
table listing open specs / primitive sources only (LSP spec, SQLite
docs, oxc node reference, Lightning CSS, JSON-RPC + MCP spec, TC39
proposals, existing codemap surface, internal third-party graph
audits). Discipline statement preserved: every plan PR cites the
spec / primitive source it took inspiration from.
- § 5 (d) row + T-table T+5w → +7w cell — dropped fallow crates/lsp/
refs; LSP spec is now the named authority.
- § 6 Q1 — dropped fallow.md § 6 citation; stale-index frequency now
anchored on PR #46 + PR #56 internal evidence.
- § 6 Q4 — dropped fallow.md § 0 + § 6 citations; closed-dead-subgraph
case cross-refs § 2.3 caveat instead.
- § 7 cross-references — removed research/fallow.md and fallow
upstream entries. Added § 4 inspection list as a self-reference.
- § 8 errata § 2.3 row — dropped fallow.md citation; pattern described
inline.
Net effect: the doc stands on codemap's intrinsic structural
properties. No peer-tool framing remains. The mission is now
self-coherent: extract max value from the SQL-index architecture +
grow the ecosystem, anchored on the unique-in-market positioning.
…quence (2026-05) (#58) * docs(research): non-goals reassessment + fallow clone deep-dive map (2026-05) Companion to research/fallow.md (capability tracker — what to adopt FROM fallow). This new doc inventories what THIS codebase already unlocks that the current Non-goals (v1) list forbids, post-C.11. User observation: many non-goals were defensive choices made when the project was 1/10th its current size, then carried forward unchallenged as the surface grew (15+ recipes, 12+ tables, 3 engines, watch mode, coverage, audit, impact). The reframe: stop asking "what should we not do?" and start asking "what does the SQL-index-with-three-transports actually unlock that no other tool does?" Findings: §1 — 10 first-class agent capabilities sitting in unwritten JOINs / formatters / verbs (components-touching-deprecated, unimported-exports, complexity per symbol, refactor-risk-ranking, boundary violations, unused type members, Mermaid output, MCP file/symbol resources, recipe usage telemetry, rename --dry-run preview). §2 — Five non-goals worth challenging: - "No FTS5 / use ripgrep" — SQLite ships FTS5; ripgrep loses JOIN composition (TODOs inside @deprecated functions in <50% covered files is one query, vs three tools today). - "No visualisation" — conflates rendering pixels with shaping render- ready data; Mermaid / D2 are JSON-shaped formatters (sibling of SARIF). - "No static analysis" — we already ship deprecated-symbols, untested- and-dead, barrel-files, fan-in/out; the line was rhetorical. Real boundary is "no opinionated rule engine, no fix mutation". - "No persistent daemon" — we have one (mcp --watch, serve --watch, watch); non-goal preserves a constraint that no longer exists. - "No LSP replacement" — show + impact + watch is 80% of LSP read-side; ship a thin shim consuming existing engines, don't write an LSP. §3 — Real architectural limits worth keeping (sub-100ms cold-start CLI, no LLM in box, no fix engine, no runtime tracing, no JS exec at index time). §4 — Map of /Users/sutusebastian/Developer/OSS/fallow clone deep-dive points: which crates / docs / configs to inspect before each shipped feature so we adopt patterns rather than reinvent. Cite-the-source-path discipline mirrors the existing research/fallow.md cite-the-PR habit. §5 — Recommended sequence: (a) FTS5 + Mermaid one-PR non-goal flip → (c) complexity column → (b) C.9 plugin layer (multi-tracer big surface) → (d) LSP shim. (a) is the cheapest non-goal flip; ships a confidence move before the bigger surfaces. §6 — 5 open questions (daemon-by-default for MCP/HTTP, FTS5 opt-in, LSP shim vs standalone, plugin contract scope, history table shape). Doc-governance compliance: - Goes in docs/research/ per Rule 3 (research-class doc). - Cross-references roadmap, why-codemap, fallow.md, competitive-scan per Rule 5. - Doesn't duplicate non-goals (Rule 1) — proposes amendments to be applied when § 2 items ship, in lockstep with why-codemap per the Single source of truth table. - No inventory counts in narrative (Rule 6) — uses qualitative "15+ recipes / 12+ tables" only. * docs(research): triangulate non-goals reassessment vs descriptive baseline User cross-checked my prescriptive doc (non-goals-reassessment-2026-05.md) against composer-2-fast's descriptive baseline (codemap-capability- surface-2026-05.md) plus the codebase as source of truth. Found three factual errors in mine; baseline doc held up clean. Corrections applied: 1. § 1.2 (Exports never imported): codebase has `exports.re_export_source` column — original doc missed it. Re-exports require a JOIN through that column to avoid false positives on barrel-only exports. Effort bumped XS → S. 2. § 1.3 (Cyclomatic complexity): claimed "AST walker already counts nodes during parse" — false. `rg 'complexity|node_count|nodeCount' src/` returns zero matches. Node-counting is NOT in place; needs an extension to the AST walker in src/parser.ts. Effort bumped S → M. 3. § 2.3 ("no static analysis" non-goal): listed `fan-in` and `fan-out` as "static analysis we already ship" — too loose. Per `fan-in.sql` (`ORDER BY fan_in DESC LIMIT 15`) they're hotspot rankers, not orphan / dead-code detectors. They don't cover the closed-dead- subgraph case from research/fallow.md § 0 (8-file pack with non- zero fan-in via self-import). That gap motivates C.9 framework plugin layer, not the "no static analysis" flip. Caveat now spelled out in the doc. Header updated: this doc is the **prescriptive** lens; the **descriptive baseline** lives in codemap-capability-surface-2026-05.md (read first). Cross-references list and § 8 errata block document the diff between v1 and v2 so future reviewers can see what changed and why. Process lesson encoded in § 8: every prescriptive research note should triangulate against a descriptive baseline (own doc or peer model) before recommending a ship sequence. Caught all three errors before they propagated into a plan PR. * docs(research): scrub local user paths from non-goals doc + new lesson User caught absolute-path leaks in the research note pointing at the fallow clone on the maintainer's machine. Three references replaced with the public upstream URL (https://github.com/fallow-rs/fallow): - Header "Local clone for deep-dives" → "Source for deep-dives" - § 4 heading "What to inspect in the local fallow clone" → "...in the fallow source tree" - § 7 cross-references "Local fallow clone — /Users/..." → "fallow upstream" Also adds a new general-purpose lesson to .agents/lessons.md: Never commit absolute local user paths — no /Users/<name>/…, /home/<name>/…, ~/…, or file:/// URIs in any tracked doc, code, comment, or PR body. Pattern: cite https://github.com/<org>/<repo> for upstream sources; repo-relative paths for in-tree references. Sibling to the existing "PR bodies via temp file" lesson — same family (committed strings need to be portable + non-leaking), different surface. * docs(lessons): add 'never commit local user paths' lesson (PR #58 catch) * docs(research): delete codemap-capability-surface-2026-05.md (existence test) Per docs/README.md existence test, this doc fails 3 of 4 criteria: - ❌ Doesn't document durable policy unavailable elsewhere — every fact reproducible from db.ts / builtin.ts / audit-engine.ts / --recipes-json - ❌ Doesn't track open work — pure snapshot - ❌ No unique historical context git log + architecture.md can't reconstruct - ✅ Cited by another doc (only because non-goals-reassessment cited it) Plus Rule 1 violation (duplicates architecture.md § Schema) and Rule 6 violation (hardcodes "15 recipes" / "9 of 15 ship actions" inventory counts in narrative). The real value the doc delivered was the **triangulation discipline** — catching 3 errors in non-goals-reassessment v1. That discipline is the durable artifact, not the doc. Codified in two places: 1. non-goals-reassessment § 8 errata + process lesson (kept) 2. .agents/lessons.md — new lesson explicitly bans the "dual descriptive + prescriptive doc" pattern as a Rule 1 violation. Right discipline: pin every concrete claim in the prescriptive doc itself, or self-audit against the canonical home before committing. Don't ship a parallel descriptive doc. non-goals-reassessment header + § 7 + § 8 updated to drop the now-deleted companion-doc references and point at canonical sources directly (architecture.md § Schema, db.ts, builtin.ts, audit-engine.ts V1_DELTAS). * docs(research): align § 5 (c) effort with § 1.3 / § 8 (M, not S) CodeRabbit caught § 5 row (c) "Cyclomatic complexity column" listing effort S, while § 1.3 + § 8 errata both list M (the v1→v2 bump after `rg 'complexity|node_count|nodeCount' src/` returned zero — node- counting isn't already in place; the AST walker in src/parser.ts has to be extended). Effort propagation gap from the v2 errata pass. § 5 row (c) updated to M; "Why" cell now spells out the AST-walker dependency inline so future readers don't re-litigate the figure. * docs(research): split § 3 into moat (load-bearing) vs ergonomic limits Grill-me Q1 outcome (under "extract max from SQL-index + equal/surpass fallow" mission): the original § 3 list conflated ergonomic floors (sub-100ms cold-start, no LLM, no JS at index time) with the actual moats. Most of the original entries are floors fallow also follows; they're not differentiators. The two real moats that needed naming as load-bearing limits: A. SQL is the API — every capability is a recipe (saved query) or a primitive recipes can compose. Verdicts are an OUTPUT mode (--format sarif, audit deltas), never a primitive. Reviewer test: "is this verb also expressible as query --recipe <id>?" B. Extracted structure ≥ verdicts — schema breadth (CSS, markers, type_members, calls.caller_scope, components.hooks_used) is what equals/surpasses fallow on agent-facing capability per fallow.md § 5. Reviewer test for any "drop column X" PR: "what recipe (bundled or hypothetical) does this kill?" Both are now load-bearing rows above the ergonomic ones. The original five preferences are kept verbatim but annotated with their relation to the moat (floor / convergent / adjacent / rivalrous / safety). Eroding either A or B is the most likely path from "codemap" to "fallow with extra steps" — § 3 now equips a reviewer to spot it. * docs(research): § 5 ship sequence — parallel plan-PR for (b) at T+0 Grill-me Q2 outcome (under "equal/surpass fallow" mission): the "cheapest non-goal flip first" ordering was a small-team confidence move, but the § 3 moat rewrite already paid that confidence cost. The real risk under the actual mission is the deferral trap — XL items become "next quarter" while every new recipe inherits the noisy substrate (untested-and-dead's Next.js page.tsx false-positive class). Hybrid resolved: - Shipping cadence stays (a) → (c) → (b) impl → (d). - (b) plan PR opens at T+0, iterates in parallel during (a)+(c). - Plan opens with ~30% of decisions pre-locked: entry-point hints only per Grill Q4, static config only per § 3 "no JS exec at index time" ergonomic limit. Not a blank-slate plan — structured from day 1. Added a 5-row T-table in § 5 spelling out the parallel tracks. (b)'s "Why" cell now names the deferral trap explicitly; (d)'s "Why" pins its dep on (b) impl (not just (b)). Rationale list updated to flag that the moat rewrite paid the confidence move so (a) doesn't pay it again. Cost-if-abandoned escape hatch: plan PR can close as "Status: Rejected (YYYY-MM-DD)" per docs/README.md Rule 8. Design surface captured either way. * docs(research): § 2 reframed via § 3 moats (taxonomy + verdict cross-refs) Grill-me Q3 outcome: § 2's five flips inherited their shape from "original non-goals worth challenging" — but after § 3 locked in the moats, that shape conflated three different categories: - Moat-extending flips (2.1 FTS5, 2.3 static analysis) — substrate growth inside moat B - Moat-aligned flip (2.2 output formatters) — verdicts as output mode per moat A - Moat-orthogonal transport flips (2.4 daemon, 2.5 LSP shim) — neither moat is touched; flipping just re-exposes existing substrate Anchors preserved (2.1-2.5 stay) — anchor-preservation discipline per docs-governance § 3 / docs/README.md Rule 7. No cascading link updates needed in § 3 / § 4 / § 5 / § 8. Changes per section: - § 2 header — added a reading note naming the three categories and pointing each flip at the moat row it relates to. - § 2.3 — verdict no longer restates "no opinionated rule engine + no fix engine" (now canonical in § 3 moat A + ergonomic row); instead cross-references and names the static-analysis category as in-scope. Closed-dead-subgraph caveat preserved (it's the C.9 motivator). - § 2.4 — added "Moat relation: orthogonal" subsection naming the transport / process-model framing. AST-caching capability claim preserved + cross-linked to § 6 Q1. Verdict points the daemon-default question at § 6 Q1 explicitly (single canonical home). - § 2.5 — replaced the unmeasured "80% of LSP read-side" claim with a structural argument: shim wraps shipped engines (show / impact / watch) via stdio without re-extracting structure; an LSP *engine* would duplicate moat B substrate (the actual reason not to build one). Cited application/show-engine.ts + application/impact-engine.ts as the substrate the shim wraps. - § 6 Q1 — enriched with the AST-caching downstream measurement note lifted from § 2.4 (single canonical home for the daemon-default decision; § 2.4 cross-refs here). Vital-info preservation audit: - ✅ Closed-dead-subgraph caveat (8-file widget pack via fallow.md § 0) — kept verbatim in § 2.3 caveat block. - ✅ AST-caching capability claim — kept in § 2.4 "Capability unlocked" + cross-linked from § 6 Q1. - ✅ Watch-mode receipts (codemap watch / mcp --watch / serve --watch) — kept verbatim in § 2.4 "What's actually true". - ✅ Fan-in/fan-out hotspot-rankers framing — kept verbatim in § 2.3 caveat (with errata cross-ref to § 8). - ✅ Fallow `crates/lsp/` cross-ref — kept in § 2.5. Dropped (intentional): - "80% of LSP read-side" — unmeasured; replaced with structural argument that doesn't need a measurement. * docs(research): § 1.7 Mermaid — bounded-input contract (moat A) Grill-me Q4 outcome: § 1.7's "What's needed" cell was loose ("new --format mermaid formatter") — true but underspecified. Real-project edge counts on dependencies / calls are 1k-10k+; rendering them is either Mermaid-choking or a hairball, and silently auto-truncating (or "best-effort") would be a verdict-shaped affordance masquerading as an output mode — violates moat A. Locked in: - Allow on: impact engine output (depth-bounded), LIMIT N-shipped recipes (fan-in / fan-out), ad-hoc SQL with explicit LIMIT ≤ 50. - Reject (with scope-suggestion message) on unbounded inputs. - No auto-truncation — that's a verdict (recipe author's job to scope). Threshold (50 edges) is configurable; chosen as a default-readable upper bound for chat-client rendering. Calibrate during (a) impl PR against fixtures/golden / external corpus. DX framing: hairballed Mermaid in MCP / Cursor / Slack chat clients renders as garbage; a clear error naming knobs (LIMIT / --via / WHERE from_path LIKE) is the better consumer signal. This keeps Mermaid an output mode (moat A clean) and forces recipe authors to scope graphs — correct because they own the structural meaning of the result set. * docs(research): § 1.10 rename — recipe-shape (moat A) + parametrised recipes Grill-me Q5 outcome: § 1.10's verb-shape ("codemap rename <old> <new> --dry-run") was downstream of the OLD § 3 ("no fix engine" as a top- level non-goal). After the moat reframe, the actual test is moat A: verdict-shape vs recipe-shape. Verb hides every implicit rename choice (visibility filter, type-only re-exports, test files, aliases) inside argv parsing — not auditable. Recipe-shape puts those choices in reviewable SQL. Locked in: - Bundled recipe rename-preview.sql with --params key=value substitution (?-placeholder binding via db.ts prepared statements). - --format diff output mode (sibling of --format mermaid per item 1.7; same "rows in, renderable text out" pattern). - No new verb / engine / MCP tool / HTTP route. SQL stays the API. - Effort drops M → S. Cross-cutting infrastructure unlocked: parametrised recipes is net-new plumbing but pays for itself on the first downstream use. Already- visible follow-ons captured in the new "Cross-cutting infrastructure unlocked by item 1.10" paragraph at the end of § 1: - delete-symbol-preview, extract-function-preview, inline-symbol- preview — same recipe-shape pattern; all gated on the same plumbing. - Parametrising existing static recipes (untested-and-dead --params min_coverage=80 instead of hardcoded < 80) — cleanup opportunity the same plumbing enables. This is the second moat-A demonstration in two adjacent grill rounds (after § 1.7's bounded-input contract on Mermaid). Both prove the "verdicts are output mode, recipes are the API" framing on real capabilities — exactly what the (a) plan-PR will need to point at when reviewers ask "what changed?". * docs(research): § 6 — close Q1 (daemon-default), Q3 (LSP shape), Q4 (plugin scope) Grill-me Q6 outcome (and accounting cleanup): three of five § 6 open questions are now resolved by prior grill outcomes — § 6 needs to reflect that, not pretend they're still open. Resolutions captured: - Q1 (daemon-default for mcp/serve) — RESOLVED THIS GRILL TURN. Default --watch ON for both modes; opt-out via --no-watch / CODEMAP_WATCH=0. One-shot CLI defaults preserved (no watcher on query/show/snippet). Receipts: stale-index = #1 agent UX complaint (fallow.md § 6); chokidar lazy startup validated tiny by PR #46 6-watcher audit. Flip is a small follow-up PR (flag default + test + patch changeset + agent rule update per docs/README.md Rule 10). AST-caching measurement parked downstream of the flip. - Q3 (LSP shim vs standalone) — RESOLVED in § 2.5 reframe earlier this grill (commit 0b9d878). Thin shim wrapping shipped engines; no engine (would duplicate moat B substrate). Standalone deferred to "if VSCode-extension demand emerges." - Q4 (C.9 plugin contract scope) — RESOLVED via § 5 (b) plan-PR pre-locked decisions (commit 6f845ba). Entry-point hints only for v1; arbitrary edge injection deferred to v2. Static config only per § 3 ergonomic "no JS exec at index time" floor. § 6 restructured: "Resolved (2026-05)" subsection at top with full rationale + receipts; "Still open" subsection below with Q2 (FTS5 default) and Q5 (history table) — the only two genuinely-open questions left. § 2.4 verdict updated to point at the resolved § 6 Q1 anchor instead of the open-question wording. Anchor preservation: external links (#6-open-questions) still resolve to the section heading. New internal anchor (#resolved-2026-05) used by § 2.4 verdict — single inbound link, no external citations to break. * docs(research): § 6 Q2 closed — FTS5 default-OFF, both config + CLI Grill-me Q7 outcome: § 6 Q2 (FTS5 opt-in vs default-on) resolved. Locked in: - Toggle: BOTH codemap.config.ts `fts5: true` AND --with-fts CLI flag at index time. Config-only forces CI / ephemeral workflows to commit fts5: true to a config file; CLI-only forces long-term users to remember the flag on every --full. Cheap to support both. - Default: OFF. Backwards-compat — existing users wouldn't see .codemap/index.db grow ~30-50% silently on next --full. - Re-evaluate default in v2 once external-corpus size measurements land (bun run benchmark:query shape). Bug fix in § 2.1: the "off by default to keep cold-start sub-100ms" framing was a WRONG REASON. FTS5 is index-time cost only; cold-start reads existing DB and the virtual table doesn't slow startup. Real reason for default-OFF is index size growth. § 2.1 verdict updated to reflect this; § 6 Q2 resolution explicitly calls out the wrong-reason correction so future readers see the diff. Principle pinned: default-ON is reserved for capabilities without disk-size tax (Mermaid output, parametrised recipes, complexity column). FTS5 is the disk-tax exception. Tree state after this commit: - § 6 Q1 (daemon-default) — resolved - § 6 Q2 (FTS5 default) — resolved - § 6 Q3 (LSP shape) — resolved - § 6 Q4 (plugin scope) — resolved - § 6 Q5 (history table) — STILL OPEN (defer-bias confirmed by doc) * docs(research): § 6 Q5 closed — history table deferred + full grill findings Grill-me Q8 outcome: § 6 Q5 (history table) resolved as DEFERRED, with the full grill analysis preserved inline so the next reviewer doesn't have to re-derive why we said no. Findings captured: - WHAT it would do — point-in-time index gains a temporal dimension ("when did symbol X get @deprecated?", "coverage trend over 50 commits", "files that became dead this week"). - WHAT audit --base <ref> already covers — pairwise diff serves the most-common temporal question (PR-scoped delta) with no schema growth. Longitudinal "evolved over commits 1..N" is the unfilled gap. - TWO SHAPES table — per-commit snapshots (~25 GB on 500-commit retention; trivial query cost) vs append-only event log (~5-25 MB deltas; heavy recursive-CTE query cost). - BACKFILL COST — N reindexes (~30s each = ~4 hrs first-run for 500 commits) is the same for both shapes; deal-breaker today. - ARCHITECTURE IMPACT — schema bump (minor per pre-v1 lesson), db.ts + indexer hooks, retention policy config, deeper git integration. - WHY DEFER — anti-bloat meta-rule (no recipe demands it); audit --base covers common case; backfill prohibitive without paying use case; shape-decision wasted without empirical access patterns. - REVISIT TRIGGERS — TWO consumers shipping jq-based "audit runs over time" workflows (mirrors B.5 verdict-threshold deferral pattern), OR query_baselines evolution becoming a recurring agent need. The full analysis is now inline in § 6 Q5 (~30 lines + cost table). Per user request: don't lose vital information; document grilling findings for fuller context. Future reviewers see the full reasoning, not just "deferred" — same posture as § 8 errata's "future readers can see the diff between v1 and v2." § 6 status after this commit: ALL FIVE OPEN QUESTIONS RESOLVED. Q1 (daemon-default), Q2 (FTS5 default), Q3 (LSP shape), Q4 (plugin scope), Q5 (history table) — every decision the doc was authored to force is now pinned with rationale and revisit triggers (where applicable). * docs(research): § 1.9 reframe + § 3 "No telemetry upload" floor Grill-me Q9 outcome: § 1.9's "Recipe usage telemetry" framing was a gotcha. The word "telemetry" carries upload / aggregation / surveillance connotations that don't match the actual capability (purely local recency tracking) — and would either get the feature rejected sight-unseen by privacy-conscious users / corp installations OR silently set up substrate for a future "phone home" PR without an explicit non-goal saying we won't. Renamed + tightened § 1.9: - "Recipe usage telemetry" → "Local recipe-recency tracking". - Table renamed recipe_usage → recipe_recency (named after the value, not the act). - Added 90-day retention bound (caps unbounded growth via per-reindex pruning). - Added opt-out config (`recipe_recency: false` skips the reconciler). - --recipes-json surface spec'd: {recipe_id, last_run_at, run_count_90d}. - Naming-note paragraph explains why "telemetry" was rejected. New § 3 ergonomic floor row "No telemetry upload": - Locks in the privacy posture explicitly. No HTTP-out primitive in codebase today (grep-able), but the floor exists to resist accumulation pressure — a future "anonymous opt-in usage stats to help prioritize recipes" PR would look reasonable without an explicit floor. - Convergent with fallow (probably also doesn't upload) — floor, not moat. - Cross-references item 1.9 as the only usage-data feature; consumers can audit the .codemap/index.db location + retention bound. Lockstep update needed when item 1.9 ships: docs/why-codemap.md "What Codemap is not" gains "Codemap never uploads usage data" per docs/README.md Rule 10. Already cross-referenced in § 7 of this doc. * docs(research): drop all fallow framing — codemap is structurally unique User reframe: codemap is the only SQL-based code index in the market; inspiration comes from the free and open internet (LSP spec, SQLite docs, AST tooling), not code-by-code cloning of any peer tool. Drop fallow as a yardstick throughout. Vital information preserved (per "don't lose any vital information that is used to execute the plan"): - Closed-dead-subgraph motivator for C.9 — kept as an abstract pattern description in § 2.3 caveat (N-file packs with self-imports, non- zero fan-in, none reachable from real entry). Was previously cited to fallow.md § 0; now stands on its own merit. - LSP read-side capabilities (show / impact / watch) — kept; LSP spec upstream is now the protocol authority instead of fallow's crates/lsp/. - Runtime-tracing scope distinction — § 3 floor reframed to anchor on "different product class entirely" (live process data vs static analysis) instead of "fallow's paid moat." - Predicate-as-API moat (A) — kept; justification now anchors on intrinsic merit (SQL is durable, agents compose any predicate) rather than "fallow ships verdicts; we don't." - Schema-breadth moat (B) — kept; justification now "codemap-specific extractions; their richness directly determines what JOINs are expressible" rather than "fallow has none of these." Section-by-section changes: - HEADER — "Companion docs / Source for deep-dives" replaced with "Companion doc" (competitive-scan only) + "Positioning" paragraph declaring structural uniqueness. - § 2.3 original-framing quote — paraphrased to drop the "(e.g. fallow, knip, jscpd)" parenthetical; pointers to roadmap.md for the full original wording. (roadmap.md itself still has the parenthetical; separate-PR scope.) - § 2.3 caveat — closed-dead-subgraph case described abstractly; no source citation needed. - § 2.5 LSP shim — "fallow has crates/lsp/" → "LSP spec upstream is the protocol authority." - § 3 intro — mission framing rewritten; "equal/surpass fallow" language replaced with "extract maximum value from the SQL-index architecture; grow the ecosystem" + "only SQL-based code index in the market" positioning. - § 3 Moat A — anchored on intrinsic merit (SQL durable + agent composability) instead of fallow comparison. - § 3 Moat B — anchored on "substrate every recipe layers on; richness determines JOIN expressivity" instead of "fallow has none of these." - § 3 ergonomic floors — dropped all "fallow is also fast" / "Convergent with fallow" annotations; reframed runtime-tracing as "different product class entirely (live process data, not static analysis)" + reframed telemetry-upload as standalone safety promise. - § 4 — DELETED ENTIRELY ("What to inspect in the fallow source tree"). Replaced with "Inspiration sources for plan-PR authoring" table listing open specs / primitive sources only (LSP spec, SQLite docs, oxc node reference, Lightning CSS, JSON-RPC + MCP spec, TC39 proposals, existing codemap surface, internal third-party graph audits). Discipline statement preserved: every plan PR cites the spec / primitive source it took inspiration from. - § 5 (d) row + T-table T+5w → +7w cell — dropped fallow crates/lsp/ refs; LSP spec is now the named authority. - § 6 Q1 — dropped fallow.md § 6 citation; stale-index frequency now anchored on PR #46 + PR #56 internal evidence. - § 6 Q4 — dropped fallow.md § 0 + § 6 citations; closed-dead-subgraph case cross-refs § 2.3 caveat instead. - § 7 cross-references — removed research/fallow.md and fallow upstream entries. Added § 4 inspection list as a self-reference. - § 8 errata § 2.3 row — dropped fallow.md citation; pattern described inline. Net effect: the doc stands on codemap's intrinsic structural properties. No peer-tool framing remains. The mission is now self-coherent: extract max value from the SQL-index architecture + grow the ecosystem, anchored on the unique-in-market positioning. * docs(research): retract uniqueness claim — honest cohort positioning Fact-check finding: the "structurally unique — only SQL-based code index in the market" claim doesn't hold. Web search + verification surfaced a real cohort of SQLite-backed code indexers for AI agents: - srclight (29 stars) — SQLite FTS5 + tree-sitter + embeddings + MCP, 42 tools, 11 langs. Pitch identical to codemap's ("AI agents spend 40-60% tokens on orientation; we eliminate this"). - Sverklo (30 stars) — local-first MCP, symbol graph, blast-radius, open-source alternative to Greptile/Sourcegraph. - ctxpp / ctx++ (17 stars) — Go MCP, tree-sitter, SQLite + FTS + vector, blast-radius analysis (= codemap's impact). - KotaDB (99 stars) — TS + Bun + SQLite — IDENTICAL stack to codemap. - codemogger (2026) — MCP, tree-sitter, SQLite + FTS + vector, semantic search. - @squirrelsoft/code-index, QuickAST, code-scale-mcp, CodeAgent Indexing Engine, Polyglot Indexer MCP, Continue's CodeSnippetsIndex — all SQLite-backed code indexers with overlapping surface. Codemap is one of ~10+, NOT unique. Retracting the claim. Honest differentiation (after verification): 1. Predicate-as-API — peers ship pre-baked verbs / MCP tools; codemap exposes raw SQL + recipes. Genuinely rare in the cohort. 2. Pure structural — no embeddings, no LLM in box. Most peers add vector search by default. Genuine differentiation. 3. JS/TS/CSS-ecosystem-deep extraction — CSS variables/classes/ keyframes, React components.hooks_used, type_members, markers. Peers focus on cross-language symbol+call surface via tree-sitter. The depth axis (3) is structurally enabled by parser choice — oxc (JS/TS) and lightningcss (CSS) are Rust-based and ecosystem- specialized; peers using tree-sitter trade depth for breadth. Where codemap is BEHIND the cohort (not hidden): multi-language support (codemap = TS/JS/CSS only; peers = 10-15 langs), star count, embeddings/semantic search, market traction. Edits applied: - HEADER positioning paragraph — retracted "structurally unique"; named the cohort explicitly (srclight, Sverklo, ctxpp, KotaDB, codemogger, etc.); spelled out the three differentiation axes; added the parser-choice rationale (oxc + lightningcss as the structural enabler of axis 3). - § 3 moat-intro line — replaced "the only SQL-based code index in the market" with "specific niche in the SQLite-backed-code-index cohort" + the three axes. Reviewer test reframed: eroding either moat turns codemap into "yet-another-tool-in-the-cohort instead of the predicate-shaped specialist." Moats A and B themselves required no rewrite — their justifications (predicate-as-API durability + extracted-structure substrate) hold under the corrected positioning. The peer cohort discovery actually sharpens both moats: A is the specialty (raw SQL surface) and B is the depth axis (richer extraction than tree-sitter cohort). * docs(research): § 1.4 refactor-risk formula — orphan + NULL fixes + caveat Grill-me Q12 outcome: § 1.4's "fan_in × (100 - coverage_pct)" formula had two correctness bugs and one accepted modeling limitation: CORRECTNESS FIXES (must ship): - Orphans (fan_in=0) scored 0 → "no risk" → wrong (orphans are high-risk: dead code or hidden-import targets we don't track). Fix: `fan_in + 1` so orphans score on coverage alone. - NULL coverage_pct propagated through the formula → 100 - NULL = NULL → row dropped from ORDER BY → unmeasured-coverage symbols silently vanished from the ranking. Fix: COALESCE(coverage_pct, 0) treats unmeasured as 0% (high risk). ACCEPTED v1 TRADE-OFF: - Linear-in-fan_in (fan_in 100 with 99% coverage = fan_in 1 with 0% coverage in the score). Real, but not worth fixing in the bundled recipe — users tune via project-local override. Caveat block in refactor-risk-ranking.md (will accompany the recipe when (a) ships) names tuning axes for project-local overrides: - Log-scale fan_in (LOG(fan_in + 1) * 30) for hub-heavy codebases - Visibility weight (if @public / @internal / @beta JSDoc tags are used consistently) - LOC weight (if test-density varies across files) Why ship-with-caveat instead of multi-axis composite (Option B): - Moat A says recipes are saved queries (starting points), not authoritative verdicts. Bundled formula gets 80% right; users iterate. - Anti-bloat meta-rule — every additional axis encodes more opinions; shipping minimal forces explicit thought during tuning. - Ecosystem-specific axes (visibility weight, LOC weight) shouldn't be in the bundled default. Effort stays XS. The .md caveat block lands in the (a) plan PR / impl PR alongside the .sql; not part of THIS research-note PR's scope. * docs(research): § 1.5 boundary violations — Shape A directional rules Grill-me Q13 outcome: § 1.5 was underspecified ("--boundaries <config> flag on audit OR recipe consuming the config"). Three real questions needed answering: where the config lives, what shape, recipe-or-flag. Shape A (directional rules) locked in for v1: boundaries: [ { name: "no-cross-feature", from_glob: "src/features/*/**", to_glob: "src/features/*/**", action: "deny", except_self: true, }, ... ] Why A over B (element-types) over C (layers) — honest discriminator: A and B have IDENTICAL expressiveness (B compiles to A at index time). The real question is ergonomics-at-scale vs forward-compat / smallest- viable-config: - A wins 5 of 6 dimensions: smallest-viable-config (one entry); Zod schema simplest; mental-model load (one concept); forward-compat (B layers on top later as sugar); backwards-compat (never paint into a corner; primitives are durable). - B wins only "ergonomics at scale" (5+ rules with element reuse) — exactly the dimension that can be added later as a sugar layer without breaking A. - C (layer ordering) is most opinionated; only fits layered architectures. Not a v1 default. Decision rule (ship the smallest primitive that doesn't paint into a corner; layer ergonomics on top later) mirrors § 6 Q5 history-table defer logic. Implementation reuses every shipped or in-flight piece of plumbing: - Zod config slot (existing src/config.ts substrate) - Index-time reconciler (mirrors recipe_recency from item 1.9) - New boundary_rules table (moat-B-aligned schema growth) - Bundled recipe boundary-violations.sql via SQLite GLOB operator - SARIF output formatter (already shipped) for CI gate NO new CLI flag — moat-A clean. The verb is query --recipe boundary-violations --format sarif. Recipe consumes config-as-data; SARIF output mode handles verdict-shaped CI consumers. Effort stays S. Element-types / layer sugar deferred to v1.x with explicit "demand-driven" trigger (mirrors fallow.md B.5 verdict- threshold deferral pattern, kept in this doc as the recurring deferral idiom). * docs(research): § 1.1, 1.6, 1.8 sanity sharpening (gotchas + envelopes) Grill-me Q14 outcome: three remaining § 1 rows had implicit gotchas the recipe author would otherwise have to discover during impl. Each row gets a small clarification — substrate unchanged, effort unchanged. § 1.1 components-touching-deprecated: - Was: "One bundled recipe (components-touching-deprecated)" - Now: explicit two-path UNION - HOOK PATH: components.hooks_used JSON overlap with @deprecated symbols (catches deprecated hooks like useDeprecatedThing) - CALL PATH: calls.caller_name IN (SELECT name FROM components) × @deprecated symbols by callee_name (catches regular deprecated functions called inside components) - Hook-only variants would ship false-negatives — recipe author needs the explicit UNION to avoid the trap. § 1.6 unused-type-members: - Was: "Recipe (unused-type-members) — needs JSON-extraction predicate" - Now: ADVISORY recipe with explicit caveat block in .md. Output is "review these" candidates, NEVER "safe to delete" — TS has multiple indirect-usage classes codemap's substrate doesn't track: - Indexed access: T['fieldName'] - keyof T - Type spreads: type X = T & {...} - Mapped types: {[K in keyof T]: ...} These produce false-positives. Recipe is useful as a candidate surfacer; agents must verify before deletion. § 1.8 more MCP resources: - Was: hand-wave "add codemap://files/{path} and codemap://symbols/ {name}" - Now: spell out disambiguation envelope (reuses {matches, disambiguation?} pattern from PR #39 show/snippet) — symbols with duplicate names across files (Component, index, default, util-name collisions) return all matches with by_kind / files / hint metadata. Plus ?in=<path-prefix> query parameter mirroring show --in <path>. - Without spelling this out, the implementation would have to invent disambiguation OR ship a "first match wins" gotcha. Net: each row's What's-needed cell now contains enough detail that the recipe / resource author can implement without re-deriving the JOIN structure or envelope shape. Tactical clarity layered on top of the structural decisions made in earlier grills.
… on parser.ts Fact-checking against codebase post-PR-#69-and-#70 surfaced four stale spots; concise-comments rule re-applied to recently-authored parser.ts comments. DOCS LIFTED (post-FTS5 / Mermaid / complexity merge): - README.md (root) line 113 — --format enum was missing `mermaid`. Updated to <text|json|sarif|annotations|mermaid> + added the bounded-input contract one-liner + 50-edge ceiling note. Added --with-fts example block alongside (was missing entirely; README is the canonical CLI surface per docs/README.md Single source of truth table). - docs/architecture.md output-formatters paragraph — described only formatSarif + formatAnnotations; missing formatMermaid + bounded- input contract. Added formatMermaid description + MERMAID_MAX_EDGES reference + the no-auto-truncation reasoning (would be a verdict masquerading as output mode). Updated the --format CLI enum to include mermaid; same for the MCP tools format union. - .agents/skills/codemap/SKILL.md + templates/agents/skills/codemap/ SKILL.md — recipe-id list missed three coverage recipes (untested-and-dead, files-by-coverage, worst-covered-exports) shipped earlier in PR #65/#56 era. Lockstep update per Rule 10. Skill now lists 20 of 20 bundled recipe ids. CONCISE-COMMENTS SWEEP on parser.ts (recently authored): - Trimmed the 14-line complexityStack JSDoc block to 6 lines. Kept: the -1 sentinel rationale (non-obvious), the WeakMap rationale (the bug fix from PR #70 review). Cut: re-stating push/pop semantics obvious from method names + step-by-step "this then that" prose. - Removed the "Defer complexity push to..." comment in the VariableDeclaration handler. The 4-line block restated the design decision documented one screen up in the complexityStack jsdoc; cross-ref makes it redundant. Per concise-comments § "Cut" rule: "Cross-references that save grep time" — keep when they actually do; cut when they restate. Verification: - bun run check: format + lint + typecheck + 23/23 golden ✓ - Recipe count: SQL files = 20, skill mentions = 20 (1:1 match) ✓ - SCHEMA_VERSION = 8 in db.ts; docs/architecture.md says 8 ✓ - complexity column documented in architecture.md + glossary.md ✓ - --with-fts in README.md + architecture.md + glossary.md + roadmap.md (consumer-facing surfaces all aligned) ✓ - --format mermaid in README.md + architecture.md + glossary.md + agent rule/skill ✓
…sted recipe (research note § 1.4) (#70) * feat(complexity): cyclomatic complexity column + high-complexity-untested recipe Research note § 1.4 ship-pick (c) per § 5 cadence. Schema bump SCHEMA_VERSION 7 → 8. Schema: - symbols.complexity REAL column. NULL for non-function kinds and class methods (v1 limitation documented in recipe .md). Parser: - complexityStack maintained alongside scopeStack. Function entry pushes {symbolIndex, count: 1}; branching-node visitors increment top.count; function exit pops + writes count into the already- pushed symbol row's complexity field. - McCabe decision points counted: if, while, do-while, for, for-in, for-of, case X (not default:), &&/||/??, ?:, catch. Bundled recipe high-complexity-untested: - Joins symbols (complexity >= 10) with coverage (< 50%). - Combines structural + runtime evidence axes — surfaces refactor- priority candidates that untested-and-dead and worst-covered-exports miss (they catch dead-or-uncalled, this catches called-but-undertested- AND-branchy). Empirical sanity check on codemap's own index after reindex: - extractFileData (parser.ts main visitor) → complexity 108 ✓ - stringifyTypeNode → 42 ✓ - All non-function kinds have NULL complexity ✓ - high-complexity-untested recipe returns 7 functions all from src/parser.ts (which has 0% coverage; complexity ≥ 10) ✓ Lockstep updates per Rule 10 (templates/agents + .agents): - Trigger pattern row "What's high-complexity AND undertested?" - Quick reference row for SELECT name, complexity FROM symbols - Recipe-id list extended in SKILL.md Plus architecture.md (schema version 8, complexity column docs), glossary.md (cyclomatic complexity entry), patch changeset. Files changed: - src/db.ts (SCHEMA_VERSION + symbols.complexity column + insertSymbols bind + SymbolRow optional complexity field) - src/parser.ts (complexityStack + branching node visitors + push/pop in FunctionDeclaration / VariableDeclaration arrow-fn paths) - templates/recipes/high-complexity-untested.{sql,md} - docs/architecture.md (schema version + symbols column doc) - docs/glossary.md (new entry) - templates/agents/rules/codemap.md + .agents/rules/codemap.md (trigger + quick-ref rows) - templates/agents/skills/codemap/SKILL.md + .agents/skills/codemap/ SKILL.md (recipe-id list) - .changeset/cyclomatic-complexity.md (patch) Verification: - bun test: 754 pass - bun run check passes (format, lint, typecheck, 23/23 golden queries) - Live re-index against codemap source produces sensible complexity values (parser visitor itself is the highest at 108, which tracks) * docs(skill): add complexity column to symbols schema in skill files CodeRabbit catch on PR #70: the high-complexity-untested recipe row was added to .agents/skills/codemap/SKILL.md but the symbols table schema section (under "### `symbols` — Functions, types, ...") still listed columns through `visibility` only, missing the new `complexity REAL` column. Verified by reading the file — claim was correct. Both lockstep mirrors (.agents/ + templates/agents/) updated with the same row: | complexity | REAL | Cyclomatic complexity (`1 + decision points`) for function-shaped symbols. NULL for non-functions and class methods (v1). Powers --recipe high-complexity-untested. Decision points: if, while, do…while, for/for-in/for-of, case X: (not default:), &&/||/??/?:, catch | Per docs/README.md Rule 10 — agent rule + skill schema docs must stay in lockstep with code-side schema changes. The trigger-pattern row + recipe-id list were already updated; the schema-table row was the gap. * fix(complexity): per-function visitors fix multi-declarator misattribution + cleanups CodeRabbit raised three valid findings on PR #70. All fact-checked against the code; all correct. A) docs/architecture.md symbols schema table was malformed: - Markdown table separator row had extra `| --- | ---` segments because oxfmt mis-counted columns when the description contained `|` chars inside `&&`/`||`/`??` backtick spans. - The complexity row's description was split across THREE cells with broken backtick fences. - Fix: restored single-row layout (3 cells: Column | Type | Description) and rephrased the decision-point list to avoid `|` inside backticks ("short-circuit `&&` / `||` / `??`" instead of "`&&`/`||`/`??`"). B) src/parser.ts complexity misattribution on multi-declarator VariableDeclaration (e.g. `const a = () => {…}, b = () => {…};`): Pre-fix: VariableDeclaration enter pushed all declarators' complexity entries up front. Then visitor traversed `a`'s body — branches incremented top (= b's entry). Then `b`'s body. Exit pops in reverse → symbols[1].complexity = 3 (wrong), symbols[0].complexity = 1 (wrong). Real bug. Fix: push/pop complexity on the FUNCTION-shaped node visitors (ArrowFunctionExpression / FunctionExpression) — not on VariableDeclaration. The VariableDeclaration handler still creates the symbol row but only RECORDS the symbol → init-node mapping in a WeakMap. The ArrowFunctionExpression / FunctionExpression enter handler reads the WeakMap to know which symbol to write back to; anonymous arrow fns (callbacks, IIFEs) get -1 and just track count without persistence. Verified against fixture: const a = () => { if (1===1) {…} }, b = () => { if (2===2) {…} }, c = () => 5; → a=2, b=2, c=1 (correct; pre-fix was a=1, b=3, c=1) C) popComplexityInto guard was a no-op (callers passed top.symbolIndex, so the equality check was always true). Simplified to parameterless popComplexityTop() that always pops + writes back if symbolIndex >= 0. Folds naturally into the B refactor — every push/pop pair now lives in a function-shaped visitor. Also re-ran codemap query against codemap source post-fix: extractFileData=108, stringifyTypeNode=42, extractClassMembers=18, extractLiteralValue=15, extractObjectMembers=14 Same scores as pre-fix on these (no FunctionExpression / arrow nesting in those particular functions, so the bug didn't surface) — confirms the refactor is a strict improvement, not a regression. * docs: audit + lift remaining stale references; concise-comments sweep on parser.ts Fact-checking against codebase post-PR-#69-and-#70 surfaced four stale spots; concise-comments rule re-applied to recently-authored parser.ts comments. DOCS LIFTED (post-FTS5 / Mermaid / complexity merge): - README.md (root) line 113 — --format enum was missing `mermaid`. Updated to <text|json|sarif|annotations|mermaid> + added the bounded-input contract one-liner + 50-edge ceiling note. Added --with-fts example block alongside (was missing entirely; README is the canonical CLI surface per docs/README.md Single source of truth table). - docs/architecture.md output-formatters paragraph — described only formatSarif + formatAnnotations; missing formatMermaid + bounded- input contract. Added formatMermaid description + MERMAID_MAX_EDGES reference + the no-auto-truncation reasoning (would be a verdict masquerading as output mode). Updated the --format CLI enum to include mermaid; same for the MCP tools format union. - .agents/skills/codemap/SKILL.md + templates/agents/skills/codemap/ SKILL.md — recipe-id list missed three coverage recipes (untested-and-dead, files-by-coverage, worst-covered-exports) shipped earlier in PR #65/#56 era. Lockstep update per Rule 10. Skill now lists 20 of 20 bundled recipe ids. CONCISE-COMMENTS SWEEP on parser.ts (recently authored): - Trimmed the 14-line complexityStack JSDoc block to 6 lines. Kept: the -1 sentinel rationale (non-obvious), the WeakMap rationale (the bug fix from PR #70 review). Cut: re-stating push/pop semantics obvious from method names + step-by-step "this then that" prose. - Removed the "Defer complexity push to..." comment in the VariableDeclaration handler. The 4-line block restated the design decision documented one screen up in the complexityStack jsdoc; cross-ref makes it redundant. Per concise-comments § "Cut" rule: "Cross-references that save grep time" — keep when they actually do; cut when they restate. Verification: - bun run check: format + lint + typecheck + 23/23 golden ✓ - Recipe count: SQL files = 20, skill mentions = 20 (1:1 match) ✓ - SCHEMA_VERSION = 8 in db.ts; docs/architecture.md says 8 ✓ - complexity column documented in architecture.md + glossary.md ✓ - --with-fts in README.md + architecture.md + glossary.md + roadmap.md (consumer-facing surfaces all aligned) ✓ - --format mermaid in README.md + architecture.md + glossary.md + agent rule/skill ✓
Summary
Plans the C.11 candidate from
research/fallow.md—codemap ingest-coverage <path>reads Istanbulcoverage-final.jsoninto two new tables (coveragesymbol-level +file_coveragerollup), joinable tosymbolsfor 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 withON DELETE CASCADE(D1).Killer recipe this unlocks
Today an agent runs two tools and joins in JS. After this lands it's one `query` + one `JOIN`.
Key decisions (full list in plan)
Tracer-bullet plan
Workflow
Plan only — implementation follows after CodeRabbit review per the established workflow (PRs #46 / #47, #49 / #50, #51 / #52, #53 / #54).
Test plan
Summary by CodeRabbit