Skip to content

Commit fc6790b

Browse files
feat(recipes): parametrised recipes and rename preview (#71)
* feat(recipes): parametrised recipe infra + find-symbol-by-kind Ships Slice 1 from the parametrised-recipes plan: recipe frontmatter can now declare typed params, CLI/MCP/HTTP validate those params before SQL binding, and query execution binds values through SQLite placeholders instead of string interpolation. Highlights: - Add `params:` block-list frontmatter support alongside existing `actions:`. Param types are `string | number | boolean`; omitted optional params bind NULL internally so positional placeholders stay stable. - Add strict param validation with clear `{error}` envelopes for missing, unknown, and malformed params. CLI supports comma-separated and repeated `--params`; duplicate keys use last-write semantics. - Thread bind values through query execution, grouped results, baselines, formatted outputs, MCP `query_recipe`, HTTP `query_recipe`, and golden-query scenarios. - Add bundled exemplar recipe `find-symbol-by-kind` and expose params metadata in `--recipes-json` / recipe catalog entries. - Update README, architecture docs, bundled agent rule + skill, and dev-side mirrors per Rule 10. - Add focused tests for param parsing, metadata loading, execution binding, MCP/HTTP handler path, CLI parsing, and golden-query coverage. Verification: bun run check (782 tests + golden scenarios) passes. * feat(recipes): diff formatters + rename-preview recipe Completes Slices 2 and 3 from the parametrised-recipes plan in the same PR: - Add `--format diff` (plain unified diff) and `--format diff-json` (structured hunks) for rows shaped as `{file_path, line_start, before_pattern, after_pattern}`. - Diff formatters read source files at format time and surface stale/missing files instead of silently emitting partial output. - Wire diff / diff-json through CLI, MCP, and HTTP formatted-output paths. - Add conservative `rename-preview` bundled recipe using the new params + diff infrastructure. V1 covers symbol definition lines and direct named import specifiers; docs clearly call out call-site / re-export / string/comment caveats until those source locations are indexed. - Add golden scenario for rename-preview and docs / agent lockstep updates. - Delete the completed plan file and remove its docs-index entry per Rule 3. Verification: bun run check (786 tests + golden scenarios) passes. * fix(recipes): address PR #71 fact-checked review comments Verified each CodeRabbit thread against the code, applied real bugs and substantive nits, kept changes minimal. - scripts/query-golden.ts: reject params on raw-SQL scenarios so misconfigured fixtures fail fast instead of silently running unbound SQL. - output-formatters: add JSDoc to all new diff exports; treat \`after_pattern\` as a literal so identifiers with \`$\` (e.g. \`$inject\`, \`$$factory\`) round- trip through the diff; replace stale-vs-missing substring inference with an explicit kind argument and preserve every per-file warning instead of overwriting the last one. - recipe-params: add JSDoc on every public symbol; accept numeric 1/0 for boolean params so the MCP / HTTP path (where Zod permits z.number()) stops rejecting legitimate inputs. - recipes-loader: JSDoc on the new \`RecipeParamType\` / \`RecipeParam\` types; refresh the \`extractFrontmatterAndBody\` JSDoc so it documents the new \`params\` field. - templates/recipes/rename-preview.sql: stop applying the \`in_file\` prefix to \`target_symbols\` so import-only previews surface even when the symbol's defining file is outside the scoped path; tighten the \`.md\` docstring to describe the output-side filter explicitly. New regression tests: \`$\`-escape, path-substring stale/missing classification, multi-warning preservation, numeric boolean coercion. * fix(recipes): address PR #71 round-2 fact-checked review feedback Verified each CodeRabbit nit against the code, applied the substantive ones. - cli/cmd-query: introduce a `structuredErrors` flag (true for any non-text format) so bootstrap / param / `--changed-since` errors emit the same `{"error":"..."}` envelope `printFormattedQuery` already uses, instead of plain stderr only when `--format json`. - application/query-engine: stop importing `RecipeParamValue`. Define a local `QueryBindValue` union at the DB boundary so the engine layer no longer depends on the recipe layer; recipe coercion stays in `application/recipe-params.ts` and produces values assignable to it. - application/index-engine: switch `printQueryResult` / `queryRows` bind signatures to `QueryBindValue` for the same decoupling reason. - application/query-recipes: doc the new `params` surface on `QueryRecipeCatalogEntry` and `getQueryRecipeParams` so `--recipes-json` and MCP catalog consumers are self-describing. - application/recipes-loader: extract a shared `scanFrontmatterBlock` helper so `parseActionsFromFrontmatter` and `parseParamsFromFrontmatter` no longer duplicate the scanning skeleton. - scripts/query-golden/schema: add a Zod refine that rejects raw-SQL scenarios declaring `params` at parse time (matches the runtime check in `query-golden.ts`). - README: add a runnable `--format diff-json` example next to `--format diff`. No behaviour change beyond the structured-errors flip and the schema-level fail-fast on misconfigured fixtures.
1 parent f7bf884 commit fc6790b

39 files changed

Lines changed: 1626 additions & 302 deletions

.agents/rules/codemap.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ A local database (default **`.codemap/index.db`**) indexes structure: symbols, i
1818
| Same entry | `bun run dev` | (same as first row) |
1919
| Query (ASCII table — optional) || `bun src/index.ts query "<SQL>"` |
2020
| Recipe || `bun src/index.ts query --json --recipe fan-out` (see **`bun src/index.ts query --help`**) |
21+
| Parametrised recipe || `bun src/index.ts query --json --recipe find-symbol-by-kind --params kind=function,name_pattern=%Query%` — params declared in recipe `.md` frontmatter and validated before SQL binding. |
22+
| Rename preview || `bun src/index.ts query --recipe rename-preview --params old=usePermissions,new=useAccess,kind=function --format diff` — read-only unified diff; codemap never writes files. |
2123
| Recipe catalog / SQL || `bun src/index.ts query --recipes-json` · `bun src/index.ts query --print-sql fan-out` |
2224
| Counts only || `bun src/index.ts query --json --summary -r deprecated-symbols` |
2325
| PR-scoped rows || `bun src/index.ts query --json --changed-since origin/main -r fan-out` |
@@ -35,14 +37,20 @@ A local database (default **`.codemap/index.db`**) indexes structure: symbols, i
3537
| 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. |
3638
| SARIF / GH annotations || `bun src/index.ts query --recipe deprecated-symbols --format sarif` · `… --format annotations` |
3739
| 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. |
40+
| Diff preview || `bun src/index.ts query --format diff '<SQL returning file_path, line_start, before_pattern, after_pattern>'` — read-only unified diff; `--format diff-json` returns structured hunks for agents. |
3841
| 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. |
3942

40-
**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.
43+
**Recipe metadata:** 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. Parametrised recipes declare `params` in `.md` frontmatter; pass values with `--params key=value[,key=value]` (repeatable; last value wins). Inspect both via **`--recipes-json`**. Ad-hoc SQL never carries actions or params.
4144

4245
**Project-local recipes:** drop `<id>.sql` (and optional `<id>.md` for description + actions) into **`<projectRoot>/.codemap/recipes/`** — auto-discovered, runs via `--recipe <id>` like bundled. Project recipes win on id collision; check `--recipes-json` for **`shadows: true`** entries to know when a project recipe overrides the documented bundled version. `<id>.md` supports YAML frontmatter for the per-row action template — block-list shape only (the loader's hand-rolled parser doesn't accept inline-flow `[{...}]`):
4346

4447
```markdown
4548
---
49+
params:
50+
- name: kind
51+
type: string
52+
required: true
53+
description: "Symbol kind to match."
4654
actions:
4755
- type: review-coupling
4856
auto_fixable: false

.agents/skills/codemap/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Replace placeholders (`'...'`) with your module path, file glob, or symbol name.
4545
- **`--baseline[=<name>]`** — diff the current result against the saved baseline. Output `{baseline:{...}, current_row_count, added: [...], removed: [...]}` (with `--json`) or a two-section terminal dump. Identity = per-row multiset equality (canonical `JSON.stringify` keyed frequency map; duplicates preserved). Pair with `--summary` for `{baseline:{...}, current_row_count, added: N, removed: N}`. **Mutually exclusive with `--group-by`.**
4646
- **`--baselines`** lists saved baselines (no `rows_json` payload); **`--drop-baseline <name>`** deletes one. Both reject every other flag — they're list-only / drop-only operations.
4747
- **Per-row recipe `actions`** — recipes that define an **`actions: [{type, auto_fixable?, description?}]`** template append it to every row in **`--json`** output (recipe-only; ad-hoc SQL never carries actions). Under `--baseline`, actions attach to the **`added`** rows only (the rows the agent should act on). Inspect via **`--recipes-json`**.
48-
- **Project-local recipes** — drop **`<id>.sql`** (and optional **`<id>.md`** for description body + actions) into **`<state-dir>/recipes/`** (default `<projectRoot>/.codemap/recipes/`) to make team-internal SQL a first-class CLI verb. `--recipes-json` and the `codemap://recipes` MCP resource list project recipes alongside bundled ones with **`source: "bundled" | "project"`** discriminating them. Project recipes win on id collision; entries that override a bundled id carry **`shadows: true`** so agents reading the catalog at session start know when a recipe behaves differently from the documented bundled version. `<id>.md` supports YAML frontmatter for the per-row action template — **block-list shape only** (loader's hand-rolled parser; no inline-flow `[{...}]`): `---\nactions:\n - type: my-verb\n auto_fixable: false\n description: "..."\n---`. Validation: SQL is rejected at load time if it starts with DML/DDL (DELETE/DROP/UPDATE/etc.); the runtime `PRAGMA query_only=1` is the parser-proof backstop. `.codemap/index.db` is gitignored; **`.codemap/recipes/` is NOT** — recipes are git-tracked source code authored for human review.
48+
- **Project-local recipes** — drop **`<id>.sql`** (and optional **`<id>.md`** for description body, params, and actions) into **`<state-dir>/recipes/`** (default `<projectRoot>/.codemap/recipes/`) to make team-internal SQL a first-class CLI verb. `--recipes-json` and the `codemap://recipes` MCP resource list project recipes alongside bundled ones with **`source: "bundled" | "project"`** discriminating them. Project recipes win on id collision; entries that override a bundled id carry **`shadows: true`** so agents reading the catalog at session start know when a recipe behaves differently from the documented bundled version. `<id>.md` supports YAML frontmatter for `params:` and per-row `actions:` — **block-list shape only** (loader's hand-rolled parser; no inline-flow `[{...}]`). Param types: `string | number | boolean`; pass values with `--params key=value[,key=value]` (repeatable; last value wins). Example: `bun src/index.ts query --json --recipe find-symbol-by-kind --params kind=function,name_pattern=%Query%`. Validation: SQL is rejected at load time if it starts with DML/DDL (DELETE/DROP/UPDATE/etc.); params validate before SQL binding; runtime `PRAGMA query_only=1` is the parser-proof backstop. `.codemap/index.db` is gitignored; **`.codemap/recipes/` is NOT** — recipes are git-tracked source code authored for human review.
4949

5050
**Audit (`bun src/index.ts audit`)** — separate top-level command for structural-drift verdicts. Composes B.6 baselines into a per-delta `{head, deltas}` envelope; v1 ships `files` / `dependencies` / `deprecated`. Two snapshot-source shapes:
5151

@@ -62,9 +62,9 @@ Each emitted delta carries its own `base` metadata so mixed-baseline audits are
6262

6363
**Tools (snake_case keys — Codemap convention matching MCP spec examples + reference servers; spec is convention-agnostic. CLI stays kebab; translation lives at the MCP-arg layer.):**
6464

65-
- **`query`** — one SQL statement. Args: `{sql, summary?, changed_since?, group_by?, format?}`. Same envelope as `codemap query --json`. Pass `format: "sarif"` or `"annotations"` to receive a formatted text payload (SARIF 2.1.0 doc / `::notice` lines); ad-hoc SQL gets `rule.id = codemap.adhoc`. Format is incompatible with `summary` / `group_by` (parser rejects with a structured `{error}`).
65+
- **`query`** — one SQL statement. Args: `{sql, summary?, changed_since?, group_by?, format?}`. Same envelope as `codemap query --json`. Pass `format: "sarif" | "annotations" | "mermaid" | "diff" | "diff-json"` to receive a formatted payload; ad-hoc SQL gets `rule.id = codemap.adhoc` for SARIF. `diff` expects rows shaped as `{file_path, line_start, before_pattern, after_pattern}` and emits read-only unified diff text; `diff-json` emits structured hunks. Format is incompatible with `summary` / `group_by` (parser rejects with a structured `{error}`).
6666
- **`query_batch`** — MCP-only, no CLI counterpart. Args: `{statements: (string | {sql, summary?, changed_since?, group_by?})[], summary?, changed_since?, group_by?}`. Items are bare SQL strings (inherit batch-wide flag defaults) or objects (override on a per-key basis). Output is N-element array; per-element shape mirrors single-`query`'s output for that statement's effective flag set. Per-statement errors are isolated — failed statements return `{error}` in their slot; siblings still execute. SQL-only (no `recipe` polymorphism in items). `format` deferred to v1.x — annotation/sarif on a heterogeneous batch is awkward; call `query` per recipe instead.
67-
- **`query_recipe`**`{recipe, summary?, changed_since?, group_by?, format?}`. Resolves the recipe id to SQL + per-row actions, then executes like `query`. Unknown recipe id returns a structured `{error}` pointing at the `codemap://recipes` resource. With `format: "sarif"`, `rule.id = codemap.<recipe>`, `rule.shortDescription` = recipe description, `rule.fullDescription` = the recipe's `<id>.md` body.
67+
- **`query_recipe`**`{recipe, params?, summary?, changed_since?, group_by?, format?}`. Resolves the recipe id to SQL + params + per-row actions, then executes like `query`. `params` is a nested object, e.g. `{recipe: "find-symbol-by-kind", params: {kind: "function", name_pattern: "%Query%"}}`; values validate against the recipe's `.md` frontmatter before SQL binding. Unknown recipe id returns a structured `{error}` pointing at the `codemap://recipes` resource. With `format: "sarif"`, `rule.id = codemap.<recipe>`, `rule.shortDescription` = recipe description, `rule.fullDescription` = the recipe's `<id>.md` body. `format: "diff" | "diff-json"` is read-only preview output; codemap never writes files. Example: `bun src/index.ts query --recipe rename-preview --params old=usePermissions,new=useAccess,kind=function --format diff`.
6868
- **`audit`**`{base?, baseline_prefix?, baselines?: {files?, dependencies?, deprecated?}, summary?, no_index?}`. Composes per-delta snapshots into the `{head, deltas}` envelope. Two **primary** sources are mutually exclusive: `base: <ref>` (git committish — worktree+reindex against any committish; sha-keyed cache under `.codemap/audit-cache/`; sub-100ms second run; requires git, errors cleanly on non-git projects) OR `baseline_prefix: "<prefix>"` (auto-resolve `<prefix>-{files,dependencies,deprecated}` from `query_baselines`). Plus optional **per-delta overrides** via `baselines: {<key>: <name>}` that compose with either primary source. Per-delta `base.source` is `"ref"` (with `base.ref` + `base.sha`) or `"baseline"` (with `base.name` + `base.sha`). Auto-runs incremental index unless `no_index: true`; watch-active sessions skip the prelude automatically.
6969
- **`save_baseline`** — polymorphic `{name, sql? | recipe?}` with runtime exclusivity check (mirrors the CLI's single `--save-baseline=<name>` verb). Pass exactly one of `sql` or `recipe`.
7070
- **`list_baselines`** — no args; returns the array `codemap query --baselines --json` would print.

.changeset/diff-formatters.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@stainless-code/codemap": minor
3+
---
4+
5+
feat(query): add `--format diff` and `--format diff-json`
6+
7+
Adds transport-agnostic diff formatters for query row sets shaped as:
8+
9+
```sql
10+
SELECT
11+
'src/file.ts' AS file_path,
12+
42 AS line_start,
13+
'oldName' AS before_pattern,
14+
'newName' AS after_pattern
15+
```
16+
17+
- **`--format diff`** emits plain unified diff text, ready for `git apply --check`.
18+
- **`--format diff-json`** emits `{files, warnings, summary}` for agents that need structured hunks.
19+
- Source files are read at format time. If a file is missing or the indexed line no longer contains `before_pattern`, the formatter marks it `missing` / `stale` in `diff-json` and emits `# WARNING:` comments at the top of plain diff output.
20+
- Same formatter support is exposed through MCP / HTTP `format: "diff" | "diff-json"` on `query` and `query_recipe`.
21+
22+
This is read-only preview infrastructure — codemap never writes files.

.changeset/parametrised-recipes.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
"@stainless-code/codemap": minor
3+
---
4+
5+
feat(recipes): parametrised recipe support + `find-symbol-by-kind`
6+
7+
Recipes may now declare `params` in sibling `<id>.md` frontmatter and consume values through positional `?` placeholders in SQL. Values validate before SQL binding and support `string`, `number`, and `boolean` types.
8+
9+
**CLI**
10+
11+
- `codemap query --recipe <id> --params key=value[,key=value]`
12+
- `--params` may be repeated; duplicate keys use last-write semantics.
13+
- Values may contain `=` (split on first equals). Values containing literal commas should use repeated `--params`.
14+
- Param validation is strict: missing required, unknown, and malformed values return `{error}`.
15+
16+
**MCP / HTTP**
17+
18+
- `query_recipe` accepts `params: {key: value}`.
19+
- HTTP `POST /tool/query_recipe` uses the same shape.
20+
21+
**Catalog**
22+
23+
- `--recipes-json`, `codemap://recipes`, and `codemap://recipes/{id}` expose the `params` declaration for each parametrised recipe.
24+
25+
**Example bundled recipe**
26+
27+
- `find-symbol-by-kind` demonstrates the new path:
28+
`codemap query --json --recipe find-symbol-by-kind --params kind=function,name_pattern=%Query%`
29+
30+
No schema bump. Runtime remains read-only via `PRAGMA query_only=1`; params are bound through SQLite placeholders, not string interpolation.

.changeset/rename-preview.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
"@stainless-code/codemap": minor
3+
---
4+
5+
feat(recipes): add read-only `rename-preview` recipe
6+
7+
Adds a conservative `rename-preview` bundled recipe that composes the new parametrised recipe infrastructure with the new diff formatters:
8+
9+
```bash
10+
codemap query --recipe rename-preview \
11+
--params old=usePermissions,new=useAccess,kind=function \
12+
--format diff
13+
```
14+
15+
The v1 recipe emits rows shaped for `--format diff` / `diff-json` and covers:
16+
17+
- symbol definition lines from `symbols`
18+
- direct named import specifier lines from `imports.specifiers` when `imports.resolved_path` points at the target symbol file
19+
20+
It intentionally does **not** cover call sites, re-export alias chains, string literals, comments, dynamic dispatch, or template-literal property access yet. Those require more precise source-location substrate (for calls / exports) or non-structural search. The recipe `.md` documents the caveats clearly and repeats the key product-floor rule: codemap never writes files; this is a preview for review / `git apply --check`.
21+
22+
Parameters:
23+
24+
- `old` (required string)
25+
- `new` (required string)
26+
- `kind` (optional string)
27+
- `in_file` (optional string path prefix)
28+
- `include_tests` (optional boolean, default true)
29+
- `include_re_exports` (optional boolean, default true; reserved until export locations are indexed)

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ codemap query "SELECT name, file_path FROM symbols LIMIT 10"
7777
# Bundled SQL (same as skill examples): fan-out rankings
7878
codemap query --json --recipe fan-out
7979
codemap query --json --recipe fan-out-sample
80+
# Parametrised recipes validate params from <id>.md frontmatter before SQL binding.
81+
codemap query --json --recipe find-symbol-by-kind --params kind=function,name_pattern=%Query%
82+
codemap query --recipe rename-preview --params old=usePermissions,new=useAccess,kind=function --format diff
8083
# Counts only (skip the rows) — pairs well with --recipe for dashboards / agent context windows
8184
codemap query --json --summary -r deprecated-symbols
8285
# PR-scoped: filter result rows to those touching files changed since <ref>
@@ -111,16 +114,19 @@ codemap audit --base v1.0.0 --files-baseline pre-release-files # mix --base wit
111114
# non-git projects get a clean `--base requires a git repository` error.
112115
# Recipes that define per-row action templates append "actions" hints (kebab-case verb +
113116
# description) in --json output; ad-hoc SQL never carries actions. Inspect via --recipes-json.
114-
# --format <text|json|sarif|annotations|mermaid> — pipe results into GitHub Code Scanning
117+
# --format <text|json|sarif|annotations|mermaid|diff|diff-json> — pipe results into GitHub Code Scanning
115118
# (SARIF 2.1.0), surface findings inline on PRs (GH Actions ::notice file=…,line=…::msg), or
116-
# render edge-shaped recipes as Mermaid `flowchart LR`. All four require a flat row list
119+
# render edge-shaped recipes as Mermaid `flowchart LR`, or preview edits as unified diffs. All
120+
# formatted outputs require a flat row list
117121
# (no --summary / --group-by / baseline). SARIF / annotations auto-detect file_path /
118122
# path / to_path / from_path; rule.id is codemap.<recipe-id> (or codemap.adhoc). Mermaid
119123
# requires {from, to, label?, kind?} rows and rejects unbounded inputs (>50 edges) with a
120124
# scope-suggestion error — alias columns via SELECT col AS "from", col2 AS "to".
121125
codemap query --recipe deprecated-symbols --format sarif > findings.sarif
122126
codemap query --recipe deprecated-symbols --format annotations # one ::notice per row
123127
codemap query --format mermaid 'SELECT from_path AS "from", to_path AS "to" FROM dependencies LIMIT 50'
128+
codemap query --format diff 'SELECT "README.md" AS file_path, 1 AS line_start, "# Codemap" AS before_pattern, "# Codemap Preview" AS after_pattern'
129+
codemap query --format diff-json 'SELECT "README.md" AS file_path, 1 AS line_start, "# Codemap" AS before_pattern, "# Codemap Preview" AS after_pattern' | jq '.summary'
124130
# --with-fts — opt-in FTS5 virtual table populated at index time. Default OFF (preserves
125131
# .codemap/index.db size); CLI flag wins over codemap.config.ts `fts5` field. Toggle change
126132
# auto-detects and forces a full rebuild so `source_fts` stays consistent.

0 commit comments

Comments
 (0)