Skip to content

Commit 560390b

Browse files
feat(fts5+mermaid): full FTS5 + Mermaid plan implementation (slices 1-7) + docs audit (#69)
* feat(fts5): Slice 1 — substrate (schema + Zod + CLI flag + indexer write path) First slice of the FTS5+Mermaid plan implementation per docs/plans/fts5-mermaid.md. Ships the FTS5 substrate end-to-end so follow-up slices (demo recipe, Mermaid formatter, MCP/HTTP plumbing) have something to layer on. Schema (per Q1): - SCHEMA_VERSION 6 → 7 (forces dropAll on first upgrade). - New `source_fts` virtual table — columns (file_path UNINDEXED, content), tokeniser 'porter unicode61'. Always created; near-zero space when empty. - Helpers: upsertSourceFts (DELETE+INSERT — FTS5 doesn't accept INSERT OR REPLACE on virtual tables), deleteSourceFts (manual mirror of FK CASCADE since virtual tables can't be FK targets), clearSourceFts. Config (per Q6): - New `fts5: boolean` field in Zod schema (default false, optional). - `ResolvedCodemapConfig.fts5` resolved from config + CLI; CLI wins per `--root` / `--state-dir` precedent. Logs stderr override line when CLI overrides config. - New `getFts5Enabled()` runtime accessor. CLI: - `--with-fts` flag in parseBootstrapArgs; threaded through bootstrapCodemap → resolveCodemapConfig.fts5Cli; rest also pushed into validateIndexModeArgs allowlist. Worker plumbing (per Q2): - WorkerInput.fts5Enabled propagated from worker-pool via getFts5Enabled(). - ParsedFile.content optional; worker tees source into it only when fts5Enabled (zero serialization cost on default-OFF path). Indexer (per Q2): - insertParsedResults writes source_fts in same transaction as files row insert. - Single-threaded path (parse-on-main, used in incremental per-relPath loop) also calls upsertSourceFts gated on getFts5Enabled(). - deleteFilesFromIndex mirrors DELETE to source_fts. - indexFiles fullRebuild path re-seeds meta (fts5_enabled, schema_version) after dropAll wipes meta. Toggle-change auto-detect (per Q3): - run-index reads meta.fts5_enabled; mismatch with current resolved config upgrades incremental → full and logs stderr line. First-run (no value) seeds silently. Already-full mode skips the upgrade message but still syncs. Verification: - bun run typecheck passes. - bun test: 746 pass, 0 fail. - End-to-end smoke test (/tmp/fts5-smoke-test): - Index without --with-fts → source_fts empty ✓ - Re-index with --with-fts → toggle-change auto-detect upgraded incremental to full → MATCH 'TODO' returns the seeded file ✓ Slice 1 deliberately does NOT include: - Telemetry on first FTS5 populate (Q7) — defer. - Mermaid formatter (slices 3-5). - Demo recipe text-in-deprecated-functions (slice 2). - Docs/agents lockstep (slice 6) — defer until the demo recipe lands so the rule update names a real recipe. - Patch changeset (slice 7). Files changed: 9 (db.ts, config.ts, runtime.ts, bootstrap.ts, bootstrap-codemap.ts, main.ts, cmd-index.ts, parsed-types.ts, parse-worker-core.ts, worker-pool.ts, index-engine.ts, run-index.ts). * feat(fts5+mermaid): slices 2-7 — recipe + Mermaid + plumbing + telemetry + lockstep Completes the FTS5+Mermaid plan (docs/plans/fts5-mermaid.md). Builds on slice 1's substrate. Slice 2 — demo recipe text-in-deprecated-functions: Bundled recipe: @deprecated functions in files containing TODO/FIXME/ HACK markers AND coverage <50%. Demonstrates FTS5 ⨯ symbols ⨯ coverage JOIN composability that ripgrep can't match. Returns empty when FTS5 is off (source_fts empty). Action template: review-cleanup-priority. Slices 3+4 — Mermaid formatter with bounded-input contract: - formatMermaid(rows, opts) in output-formatters.ts. Renders {from, to, label?, kind?} as flowchart LR. Reuses existing formatter plumbing pattern from SARIF / annotations. - MERMAID_MAX_EDGES = 50 hard-coded const (Q4). Auto-truncation explicitly out of scope (would be a verdict masquerading as output mode, violating moat A). - Reject error names recipe + count + scoping knobs (LIMIT / --via / WHERE) so agent knows how to scope. Slice 5 — MCP + HTTP plumbing: - formatEnum gains "mermaid"; QueryArgs / QueryRecipeArgs format unions extended. - tool-handlers.runFormattedQuery branches on "mermaid" → wraps formatMermaid in try/catch (bounded-input rejection becomes a structured ToolResult error). - MCP wrapToolResult needs no change — generic non-JSON passthrough handles "mermaid" same as "sarif" / "annotations". - HTTP writeToolResult needs no change — fall-through "text/plain; charset=utf-8" applies to mermaid (not sarif+json). - Tool descriptions in mcp-server.ts updated to mention "mermaid" format. Slice 6 — agent rule + skill lockstep (Rule 10): Both templates/agents/ AND .agents/ codemap rule + skill updated: - --format mermaid example row in CLI table - --with-fts row in CLI table - text-in-deprecated-functions in trigger pattern + recipe id list Slice 7 — Q7 telemetry + patch changeset: - Stderr line on first FTS5 populate: [fts5] source_fts populated: <N> files / <X> KB (uncompressed content). Cheap (single SUM(length(content)) on source_fts at end of full reindex); only fires when fts5 just became populated (fts5WasEmpty pre-check). - Patch changeset (.changeset/fts5-mermaid.md) per pre-v1 lesson: additive feature, default-OFF, behaviour-preserving for existing users. Verification end-to-end: - bun test: 754 pass, 0 fail (8 new mermaid formatter tests) - bun run check passes (format, lint, typecheck, 23/23 golden queries) - Smoke test with @deprecated function + TODO comment: * codemap --with-fts --full → "[fts5] source_fts populated: 2 files / 138 B (uncompressed content)" ✓ * query --recipe text-in-deprecated-functions → returns the deprecated function ✓ * query --format mermaid 'SELECT from_path AS "from", to_path AS "to" FROM dependencies LIMIT 50' → renders flowchart LR ✓ * query --format mermaid 'SELECT from_path AS "from", to_path AS "to" FROM dependencies' (unbounded) → rejects with scope- suggestion error ✓ Plan slices all complete. PR #69 description gets refreshed in a follow-up commit (this commit covers the impl — the doc-audit pass on docs/ comes next per user request). * docs: audit and lift FTS5+Mermaid decisions to canonical homes Per docs-governance lifecycle (docs/README.md Rule 3 + Lifecycle table: "Plan: Deleted when work ships"): LIFTED (decisions promoted from docs/plans/fts5-mermaid.md to canonical homes): - docs/architecture.md - Schema version 6 → 7 - meta table description: added fts5_enabled key - New "source_fts" table section under § Schema, after meta — documents tokenizer, file_path UNINDEXED, opt-in toggle precedence, auto-detect, telemetry, and the bundled demo recipe - application/ engines list: output-formatters.ts now mentions Mermaid alongside SARIF + GH annotations - docs/glossary.md - New "source_fts (FTS5 virtual table) / --with-fts / opt-in full-text" entry — covers schema shape, toggle precedence, auto-detect, telemetry, default-OFF rationale, the JOIN composability the FTS5 / ripgrep distinction tracks - New "--format mermaid / formatMermaid / MERMAID_MAX_EDGES" entry — covers the {from, to, label?, kind?} input contract, bounded- input rejection (50 edges), why auto-truncation is out of scope - docs/roadmap.md - Backlog: "Optional FTS5 for opt-in full-text" line removed (work has shipped per Rule 2 — backlog items move OUT when shipped) - Non-goals: "Full-text search across all file bodies — use ripgrep / IDE / opt-in FTS5 (see backlog)" REWORDED to "Full-text search default-on" non-goal — FTS5 ships per --with-fts, but default-on is still out of scope until v2 size-tax measurements DELETED: - docs/plans/fts5-mermaid.md (per Rule 3 + Lifecycle: plans deleted when work ships; decisions live in canonical homes above) - docs/README.md File Ownership row updated to drop fts5-mermaid.md from in-flight plans list (still has c9-plugin-layer.md) Audit method: grep [Ff]allow / FTS5 / source_fts / --with-fts / --format mermaid across docs/ — categorised hits as documentation (lift to architecture.md / glossary.md), historical (research notes keep their content; closed records like fallow.md stay closed), or plan-residue (delete fts5-mermaid.md per lifecycle). Verification: - Schema version assertion in architecture.md matches SCHEMA_VERSION in db.ts (7). - meta.fts5_enabled key documented matches META_FTS5_ENABLED_KEY in db.ts. - source_fts column shape (file_path UNINDEXED, content) + tokenizer ('porter unicode61') match the actual CREATE VIRTUAL TABLE in db.ts createTables. - --with-fts CLI flag documented matches parseBootstrapArgs + validateIndexModeArgs allowlists. - --format mermaid documented matches OUTPUT_FORMATS + formatEnum in cmd-query.ts + tool-handlers.ts. - 50-edge ceiling matches MERMAID_MAX_EDGES in output-formatters.ts. - bun run check passes (format, lint, typecheck, 23/23 golden queries).
1 parent 73ffa86 commit 560390b

29 files changed

Lines changed: 634 additions & 230 deletions

.agents/rules/codemap.md

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ A local database (default **`.codemap/index.db`**) indexes structure: symbols, i
3434
| Impact (blast-radius walker) || `bun src/index.ts impact <target> [--direction up\|down\|both] [--depth N] [--via <b>] [--limit N] [--summary] [--json]` — replaces hand-composed `WITH RECURSIVE` queries |
3535
| Coverage ingest || `bun src/index.ts ingest-coverage <path> [--json]` — Istanbul (`coverage-final.json`) or LCOV (`lcov.info`); format auto-detected. Joinable to `symbols` for "untested AND dead" queries. |
3636
| SARIF / GH annotations || `bun src/index.ts query --recipe deprecated-symbols --format sarif` · `… --format annotations` |
37+
| Mermaid graph (≤50 edges) || `bun src/index.ts query --format mermaid 'SELECT from_path AS "from", to_path AS "to" FROM dependencies LIMIT 50'` — recipes / SQL must alias columns to `{from, to, label?, kind?}`; rejects unbounded inputs. |
38+
| FTS5 full-text (opt-in) | `--with-fts` | `bun src/index.ts --with-fts --full` enables `source_fts` virtual table; `query --recipe text-in-deprecated-functions` demos JOINs. |
3739

3840
**Recipe `actions`:** with **`--json`**, recipes that define an `actions` template append it to every row (kebab-case verb + description — e.g. `fan-out``review-coupling`). Under `--baseline`, actions attach to the **`added`** rows only. Inspect via **`--recipes-json`**. Ad-hoc SQL never carries actions.
3941

@@ -94,29 +96,30 @@ Violating this order is wrong even if you get the right answer — it wastes tim
9496

9597
If the question looks like any of these → use the index:
9698

97-
| Question shape | Table(s) |
98-
| ------------------------------------------------------------ | -------------------------------------------------------- |
99-
| "What/which files import X?" | `imports` (by `source`) or `dependencies` (by `to_path`) |
100-
| "Where is X defined?" | `symbols` |
101-
| "What does file X export?" | `exports` |
102-
| "What hooks does component X use?" / "List React components" | `components` |
103-
| "What are the CSS variables/tokens for X?" | `css_variables` |
104-
| "Find all TODOs/FIXMEs" | `markers` |
105-
| "Who depends on file X?" / "What does file X depend on?" | `dependencies` |
106-
| "How many files/symbols/components are there?" | any table with `COUNT(*)` |
107-
| "What are the CSS classes in X?" | `css_classes` |
108-
| "What keyframe animations exist?" | `css_keyframes` |
109-
| "What fields does interface/type X have?" | `type_members` |
110-
| "Is symbol X deprecated?" / "What does X do?" | `symbols` (`doc_comment`) |
111-
| "What's `@internal` / `@beta` / `@alpha` / `@private`?" | `symbols.visibility` (parsed JSDoc tag — not regex) |
112-
| "Who calls X?" / "What does X call?" | `calls` |
113-
| "Is symbol X tested?" / "What's the coverage of file Y?" | `coverage` (after `ingest-coverage`) |
114-
| "What's structurally dead AND untested?" | `--recipe untested-and-dead` |
115-
| "Rank files by test coverage" | `--recipe files-by-coverage` |
116-
| "Worst-covered exported functions" | `--recipe worst-covered-exports` |
117-
| "Which components touch deprecated APIs?" | `--recipe components-touching-deprecated` |
118-
| "What's risky to refactor right now?" | `--recipe refactor-risk-ranking` |
119-
| "Which exports has nobody imported?" | `--recipe unimported-exports` |
99+
| Question shape | Table(s) |
100+
| ------------------------------------------------------------- | -------------------------------------------------------- |
101+
| "What/which files import X?" | `imports` (by `source`) or `dependencies` (by `to_path`) |
102+
| "Where is X defined?" | `symbols` |
103+
| "What does file X export?" | `exports` |
104+
| "What hooks does component X use?" / "List React components" | `components` |
105+
| "What are the CSS variables/tokens for X?" | `css_variables` |
106+
| "Find all TODOs/FIXMEs" | `markers` |
107+
| "Who depends on file X?" / "What does file X depend on?" | `dependencies` |
108+
| "How many files/symbols/components are there?" | any table with `COUNT(*)` |
109+
| "What are the CSS classes in X?" | `css_classes` |
110+
| "What keyframe animations exist?" | `css_keyframes` |
111+
| "What fields does interface/type X have?" | `type_members` |
112+
| "Is symbol X deprecated?" / "What does X do?" | `symbols` (`doc_comment`) |
113+
| "What's `@internal` / `@beta` / `@alpha` / `@private`?" | `symbols.visibility` (parsed JSDoc tag — not regex) |
114+
| "Who calls X?" / "What does X call?" | `calls` |
115+
| "Is symbol X tested?" / "What's the coverage of file Y?" | `coverage` (after `ingest-coverage`) |
116+
| "What's structurally dead AND untested?" | `--recipe untested-and-dead` |
117+
| "Rank files by test coverage" | `--recipe files-by-coverage` |
118+
| "Worst-covered exported functions" | `--recipe worst-covered-exports` |
119+
| "Which components touch deprecated APIs?" | `--recipe components-touching-deprecated` |
120+
| "What's risky to refactor right now?" | `--recipe refactor-risk-ranking` |
121+
| "Which exports has nobody imported?" | `--recipe unimported-exports` |
122+
| "Find @deprecated functions with TODO/FIXME and low coverage" | `--recipe text-in-deprecated-functions` (needs FTS5 on) |
120123

121124
## When Grep / Read IS appropriate
122125

.agents/skills/codemap/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ After **`bun run build`**, **`node dist/index.mjs query …`** or a linked **`co
3434

3535
Replace placeholders (`'...'`) with your module path, file glob, or symbol name.
3636

37-
**CLI shortcuts:** **`bun src/index.ts query --json --recipe <id>`** runs bundled SQL (preferred for agents). **`bun src/index.ts query --recipe <id>`** without **`--json`** prints a table. **`bun src/index.ts query --recipes-json`** prints every bundled recipe (**`id`**, **`description`**, **`sql`**, optional **`actions`**) as JSON (no index / DB required). **`bun src/index.ts query --print-sql <id>`** prints one recipe’s SQL only. Ids include **`fan-out`**, **`fan-out-sample`** (**`GROUP_CONCAT`** samples), **`fan-out-sample-json`** (same, but **`json_group_array`** — needs SQLite JSON1), **`fan-in`**, **`index-summary`**, **`files-largest`**, **`components-by-hooks`**, **`components-touching-deprecated`** (UNION of hook + call paths to `@deprecated` symbols), **`markers-by-kind`**, **`deprecated-symbols`**, **`refactor-risk-ranking`** (per-file `(fan_in + 1) × (100 - avg_coverage_pct)`), **`unimported-exports`** (exports with no detectable importer; v1 doesn't follow re-export chains — see recipe `.md` for caveats), **`visibility-tags`**, **`barrel-files`**, **`files-hashes`** — see **`bun src/index.ts query --help`**.
37+
**CLI shortcuts:** **`bun src/index.ts query --json --recipe <id>`** runs bundled SQL (preferred for agents). **`bun src/index.ts query --recipe <id>`** without **`--json`** prints a table. **`bun src/index.ts query --recipes-json`** prints every bundled recipe (**`id`**, **`description`**, **`sql`**, optional **`actions`**) as JSON (no index / DB required). **`bun src/index.ts query --print-sql <id>`** prints one recipe’s SQL only. Ids include **`fan-out`**, **`fan-out-sample`** (**`GROUP_CONCAT`** samples), **`fan-out-sample-json`** (same, but **`json_group_array`** — needs SQLite JSON1), **`fan-in`**, **`index-summary`**, **`files-largest`**, **`components-by-hooks`**, **`components-touching-deprecated`** (UNION of hook + call paths to `@deprecated` symbols), **`markers-by-kind`**, **`deprecated-symbols`**, **`refactor-risk-ranking`** (per-file `(fan_in + 1) × (100 - avg_coverage_pct)`), **`text-in-deprecated-functions`** (FTS5 ⨯ symbols ⨯ coverage demo — needs `--with-fts` enabled), **`unimported-exports`** (exports with no detectable importer; v1 doesn't follow re-export chains — see recipe `.md` for caveats), **`visibility-tags`**, **`barrel-files`**, **`files-hashes`** — see **`bun src/index.ts query --help`**.
3838

3939
**Output flags** (compose with **`--recipe`** or ad-hoc SQL):
4040

.changeset/fts5-mermaid.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
"@stainless-code/codemap": patch
3+
---
4+
5+
feat(fts5+mermaid): opt-in FTS5 virtual table + Mermaid output formatter
6+
7+
Implements the FTS5+Mermaid plan ([`docs/plans/fts5-mermaid.md`](https://github.com/stainless-code/codemap/blob/main/docs/plans/fts5-mermaid.md)) — two non-goal flips in one PR.
8+
9+
**FTS5 (opt-in, default OFF):**
10+
11+
- New `source_fts` virtual table — `(file_path UNINDEXED, content)` columns, `tokenize='porter unicode61'`. Always created; populated only when toggle is on.
12+
- Toggle via `codemap.config.ts` `fts5: true` OR `--with-fts` CLI flag at index time. CLI overrides config (logs stderr line on override).
13+
- Indexer tees file content into `source_fts` in same transaction as `files` row insert (atomic). Worker → main serialization cost is zero on default-OFF path.
14+
- Toggle-change auto-detect via `meta.fts5_enabled` — flipping `fts5: false → true` auto-upgrades incremental → full rebuild so `source_fts` is consistently populated.
15+
- DB-size telemetry on first FTS5 populate: `[fts5] source_fts populated: <N> files / <X> KB`.
16+
- Bundled demo recipe `text-in-deprecated-functions``@deprecated` functions in files containing `TODO`/`FIXME`/`HACK` markers AND coverage `<50%`. Demonstrates FTS5 ⨯ `symbols``coverage` JOIN composability that ripgrep can't match.
17+
18+
**Mermaid output formatter:**
19+
20+
- New `--format mermaid` output mode. Renders `{from, to, label?, kind?}` row-shape as `flowchart LR`.
21+
- **Bounded-input contract** (50-edge ceiling, `MERMAID_MAX_EDGES`): unbounded inputs reject with a scope-suggestion error naming the recipe + count + scoping knobs (`LIMIT` / `--via` / `WHERE`). Auto-truncation deliberately out of scope (would be a verdict masquerading as an output mode).
22+
- Available across CLI, MCP `query` / `query_recipe` tools, HTTP `POST /tool/query` (text/plain content type).
23+
24+
Schema bump: `SCHEMA_VERSION` 6 → 7. First reindex after upgrade triggers a full rebuild via the existing version-mismatch path; existing `.codemap/index.db` is preserved (only schema-managed tables get dropped + recreated).
25+
26+
**Pre-v1 patch** per `.agents/lessons.md` "changesets bump policy" — additive feature, default-OFF for FTS5, behaviour-preserving for existing users (`--with-fts` is opt-in; Mermaid is a new output mode).
27+
28+
Agent rule + skill lockstep updated per `docs/README.md` Rule 10 — both `templates/agents/` and `.agents/` mention `--with-fts`, `--format mermaid`, the new bundled recipe, and the bounded-input contract.

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Each topic has exactly one canonical file. Other files cross-reference by relati
2121
| [fixtures/qa/](../fixtures/qa/) | [prompts.external.template.md](../fixtures/qa/prompts.external.template.md) — optional chat QA prompts for an external index (`*.local.md` gitignored). |
2222
| [packaging.md](./packaging.md) | **`CHANGELOG.md` / `dist/` / `templates/`** on npm, **engines**, [**Node vs Bun**](./packaging.md#node-vs-bun), [**Releases**](./packaging.md#releases) (Changesets; **`bun run version`** + oxfmt **`CHANGELOG.md`**). |
2323
| [roadmap.md](./roadmap.md) | Forward-looking [**Backlog**](./roadmap.md#backlog) and [**Non-goals**](./roadmap.md#non-goals-v1) (not a `src/` inventory). |
24-
| [plans/](./plans/) | One `<feature-name>.md` per in-flight plan. Created on demand — don't add the `-plan` suffix; the folder provides context. Currently in flight: [`fts5-mermaid.md`](./plans/fts5-mermaid.md), [`c9-plugin-layer.md`](./plans/c9-plugin-layer.md). |
24+
| [plans/](./plans/) | One `<feature-name>.md` per in-flight plan. Created on demand — don't add the `-plan` suffix; the folder provides context. Currently in flight: [`c9-plugin-layer.md`](./plans/c9-plugin-layer.md). |
2525
| [research/](./research/) | Dated, snapshot-style notes (e.g. competitive scans). Each note links shipped items back to canonical homes — see [research/competitive-scan-2026-04.md](./research/competitive-scan-2026-04.md). |
2626

2727
---

0 commit comments

Comments
 (0)