Skip to content

Commit b73f0df

Browse files
feat(apply): ship apply-engine steps 2–12 (#165)
* feat(apply): ship apply-engine steps 2–12 in one slice Extend codemap apply from rename-preview-only to a full substrate-shaped fix executor: three diff-shape recipes, rename-preview call/re-export CTEs, auto_fixable gating with --force, agent row/diff inputs, fixpoint loop, optional git commit, and config allowlist. Adds apply_rows MCP/HTTP tool. * fix(apply): utilization hygiene — marker recipe, docs, tests Fix replace-marker-kind SQL (drop content LIKE filter) and golden row. Sync rename-preview recipe docs, MCP 18-tool count, synthesis shipped status, apply_rows in agent-content, config example allowlist, allowlist unit tests, and migrate-import-source CLI dry-run E2E. Add substrate-apply-utilization plan. * docs(plans): reconcile substrate-apply-utilization after hygiene slice Mark shipped items (marker recipe, partial A/B, roadmap link) and narrow open work to architecture/glossary apply depth, testing map, wave-2 recipes. * docs(apply): complete Phase A — architecture, glossary, agents, close direction plan Expand apply transport/policy in architecture and glossary; README and agent-content discover→apply workflow; lift rejected items into utilization plan and delete apply-engine-direction.md. * feat(apply): rename-preview barrel/reference rows; Phase B apply tests Extend rename-preview with barrel_import_rows and reference_rows (deduped vs imports/calls/definitions). Golden rename-preview-product-card; apply harness map; cmd-apply --rows and replace-marker-kind disk e2e. * feat(recipes): add stale-imports diff-shape; reject organize-imports in plan Sole-specifier unused import lines via import_specifiers × references; review-first apply (auto_fixable false). Golden on ProductCard unused now import. Defer organize-imports to formatter domain in utilization plan. * feat(recipes): add migrate-deprecated and deprecated-usages diff-shape recipes migrate-deprecated rewrites call/import sites for @deprecated symbols; deprecated-usages updates the first @deprecated JSDoc line. Goldens on minimal now() fixture; CLI apply e2e; plan C.4 shipped. * feat(apply): Phase D transport parity, JSX/stale recipes, and review fixes - MCP/HTTP apply_diff_input; apply until_empty, max_passes, commit_message - ambiguity_count on diff-json hunks; CLI e2e for diff-input, until-empty, commit - migrate-jsx-prop recipe; multi-specifier stale-imports; rename-preview JSX CTEs - C.6 actions on read recipes; docs/tool-count 19; apply-run boundary kit - Add scoped-rename-define-in plan; refresh utilization + synthesis shipped rows * fix(test): update deprecated-symbols MCP actions expectation for C.6 Align mcp-server.test with apply command hints on read recipes; clarify README force example and architecture auto_fixable recipe list. * fix(apply): guard --commit on fixpoint cap and merge touched paths Reject git commit unless until_empty terminates with empty rows; accumulate files across fixpoint passes before staging. README force example drops conflicting --yes on dry-run; docs and unit tests cover the guard. * fix(apply): set applied=true on until-empty success for --commit Terminal dry-run probe no longer leaves applied=false when prior passes wrote files; add e2e for until-empty + commit. README clarifies commit requires fixpoint empty. * fix(recipes): normalize C.6 apply command placeholders Replace angle-bracket row hints with SYMBOL/OLD_SOURCE sentinels; use {{param}} only for bound query params. Expand cmd-query C.6 tests and agent-content guidance. * fix(docs): README migrate-import-source params and C.6 test coverage Use old_source/new_source in README example; assert all three deprecated-symbols apply hints and find-symbol-definitions C.6 command. Align glossary/architecture conflict reason count with engine. * docs: clarify apply param names and commit workflow in consumer surfaces README stale-imports force note and concrete --commit example; skill documents per-recipe param keys and OLD_SOURCE vs old_source distinction. * fix(recipes): scoped C.6 commands for migrate-jsx-prop and stale-imports Include optional in_file/component_name and include_type_only in action templates; scrub empty --params pairs; preview hints use --dry-run --force. * fix(recipes): use --dry-run --force on deprecated apply action hints Align migrate-deprecated, deprecated-usages, and add-jsdoc-deprecated actions[].command with C.6 preview-first pattern; update stale-imports prose example. * fix(apply): address CodeRabbit PR #165 review findings Multi-line diff pairing, fixpoint mode contract, max-passes >= 1, migrate-deprecated in_file scoping, deprecated-usages @deprecated line extraction, C.6 in_file on add-jsdoc/replace-marker-kind, synthesis doc. * fix(apply): close CodeRabbit outside-diff and nitpick threads Reject --params outside recipe mode, align Q16 with R.17 (decision locked, registry not shipped), mark synthesis Steps 8–9 shipped, and add recipe doc examples for optional params. * fix(apply): review cycle 1 — migrate-deprecated imports, Q6, cap exit Skip import_specifier rows when replacement contains '.' (avoids invalid import { Date.now }). Prompt before --until-empty on TTY; exit 1 on fixpoint cap. Refresh synthesis shipped status; add policy/cap CLI tests. * feat(apply): close deferred review items — define_in, MCP/HTTP tests, docs - rename-preview: define_in param, binding-scoped call_rows, homonym bench + golden + CLI e2e - MCP/HTTP: apply, apply_rows, apply_diff_input integration tests - tool-handlers: yes write, auto_fixable rejection, until_empty dry_run envelope - Docs: slim synthesis §6 to shipped index; non-goals + scoped-rename plan status - Reindex goldens after homonym bench fixtures * fix(apply): review cycle 3 — homonym coverage, allowlist+TTY, docs Add homonym-consumer-b and unscoped golden; binding-scope JSX when define_in set; migrate-deprecated calls bind to deprecated_targets; ttyConfirmed bypasses allowlist after Proceed?; MCP/HTTP apply write tests; lift define_in to architecture/glossary/README; fix synthesis §6 anchors. * fix(recipes): scope find-symbol-references apply hint with define_in C.6 apply-rename-preview command now passes define_in={{file_path}} so agents following read→apply after bindings-precise lookup stay homonym-safe.
1 parent 8c7064a commit b73f0df

108 files changed

Lines changed: 5132 additions & 666 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,20 +211,30 @@ codemap affected --changed-since origin/main --json # committed de
211211
# Moat-A twin: `affected-tests` recipe. Output: [{test_path, impact_depth}] — CI composes the runner command.
212212

213213
# Apply — substrate-shaped fix executor (recipe SQL describes hunks; codemap validates + writes)
214+
# Row contract (same as --format diff-json): {file_path, line_start, before_pattern, after_pattern}
215+
# Preview first: codemap query --recipe rename-preview --params old=foo,new=bar --format diff-json
214216
codemap apply rename-preview --params old=usePermissions,new=useAccess,kind=function --dry-run
215217
codemap apply rename-preview --params old=usePermissions,new=useAccess,kind=function --yes # TTY prompts without --yes
216-
# Consumes the --format diff-json row contract ({file_path, line_start, before_pattern, after_pattern}).
217-
# All-or-nothing: any conflict aborts before any file is written. Pair with --format diff-json preview first.
218+
# Homonym-safe: add define_in=src/path/to/definition.ts (scopes target; in_file only filters output rows)
219+
codemap apply migrate-import-source --params old_source=legacy,new_source=@app/core --dry-run
220+
codemap apply stale-imports --params in_file=src/widget --dry-run # preview; writes need --force --yes
221+
codemap apply migrate-jsx-prop --params old_name=data-id,new_name=data-testid,component_name=ProductCard --dry-run --force
222+
# Agent/codemod rows (no recipe policy gates): echo '[{...}]' | codemap apply --rows - --yes
223+
# External unified diff: codemap apply --diff-input /tmp/patch.diff --dry-run
224+
# Fixpoint (recipe mode): codemap apply rename-preview --params old=a,new=b --until-empty --yes
225+
# Optional git commit (recipe id required): codemap apply rename-preview --params old=a,new=b --yes --commit "chore: rename a→b"
226+
# Bundled diff-shape recipes: rename-preview, migrate-import-source, replace-marker-kind, stale-imports, migrate-deprecated, deprecated-usages, migrate-jsx-prop, add-jsdoc-deprecated
227+
# All-or-nothing: any conflict aborts before any file is written. MCP: apply, apply_rows, apply_diff_input (yes: true for writes).
218228

219229
# Live agent content (pointer protocol — full body served from installed package version)
220230
codemap skill # full codemap SKILL markdown to stdout
221231
codemap rule # full codemap rule markdown to stdout
222232

223233
# MCP server (Model Context Protocol) — for agent hosts (Claude Code, Cursor, Codex, generic MCP clients)
224-
codemap mcp # JSON-RPC on stdio (17 tools; watcher default-ON)
225-
# Tools (17): query, query_batch, query_recipe, audit, save_baseline,
234+
codemap mcp # JSON-RPC on stdio (19 tools; watcher default-ON)
235+
# Tools (19): query, query_batch, query_recipe, audit, save_baseline,
226236
# list_baselines, drop_baseline, context, validate, show, snippet, impact,
227-
# affected, trace, explore, node, apply
237+
# affected, trace, explore, node, apply, apply_rows, apply_diff_input
228238
# CLI twins: query batch, trace, explore, node, file, schema, symbols, context --include-snippets (same JSON as MCP/HTTP).
229239
# Resources: codemap://schema, codemap://skill, codemap://rule, codemap://mcp-instructions (lazy-cached);
230240
# codemap://recipes, codemap://recipes/{id} (live read-per-call — recency fields stay fresh);

codemap.config.example.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
"synthesis": {
55
"heuristicCalls": false
66
},
7+
"apply": {
8+
"autoApplyRecipes": [
9+
"migrate-import-source",
10+
"replace-marker-kind",
11+
"rename-preview"
12+
]
13+
},
714
"boundaries": [
815
{
916
"name": "ui-cant-touch-server",

docs/architecture.md

Lines changed: 31 additions & 6 deletions
Large diffs are not rendered by default.

docs/glossary.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,17 @@ See **language adapter**.
3131

3232
A `.agents/rules/<name>.md` file with YAML frontmatter. Distinct from a **skill** (longer, scenario-specific). Distinct from a **bundled recipe** (which is SQL, not Markdown).
3333

34+
### `apply_rows` (MCP tool / CLI mode)
35+
36+
Second apply transport for **caller-supplied** hunks — same `{file_path, line_start, before_pattern, after_pattern}` contract as [`codemap apply`](#codemap-apply--apply-tool) but **no recipe policy gates** (`auto_fixable`, `apply.autoApplyRecipes`, or `--force`). CLI: `codemap apply --rows -` (stdin JSON array) or `codemap apply --rows path.json`. MCP / HTTP: `apply_rows` with `{rows, dry_run?, yes?}`. Use when an agent or external codemod already materialised rows; use `apply` + a recipe id when the index should synthesise rows from SQL. Unified diffs: [`apply_diff_input`](#apply_diff_input-mcp-tool--cli-mode). Writes still require non-TTY consent (`--yes` / `yes: true`). Distinct from `query --format diff-json` (read-only preview).
37+
38+
### `apply_diff_input` (MCP tool / CLI mode)
39+
40+
Third apply transport for **caller-supplied unified diff text** — parses git-style hunks into the row contract, same executor as `apply_rows` (no recipe policy gates). CLI: `codemap apply --diff-input <file>`. MCP / HTTP: `apply_diff_input` with `{diff_text, dry_run?, yes?, commit_message?}`. Optional `commit_message` runs `git add` + `git commit` on touched files after a clean apply (recipe `apply` supports the same).
41+
3442
### `codemap apply` / apply tool
3543

36-
Substrate-shaped fix executor — reads the same `{file_path, line_start, before_pattern, after_pattern}` row contract `--format diff-json` emits and applies the hunks to disk. Recipe SQL is the synthesis surface; codemap is the executor (Moat-A clean — verdict-shape "should we fix this?" stays on the recipe author). CLI: `codemap apply <recipe-id> [--params k=v[,k=v]] [--dry-run] [--yes] [--json]`. MCP: `apply` tool. HTTP: `POST /tool/apply`. **Phase 1** validates every row against current disk via `actual.includes(before_pattern)` (substring match — mirrors `buildDiffJson`'s contract); collects five conflict reasons (`file missing` / `line out of range` / `line content drifted` / `path escapes project root` / `duplicate edit on same line`). The path-containment guard rejects absolute `file_path` inputs and `../`-traversal that resolves outside `projectRoot`; the overlap guard rejects two-or-more rows targeting the same `(file_path, line_start)`. **Phase 2** (gated on `!dryRun && conflicts.length === 0`) writes each modified file via sibling temp + `renameSync` for POSIX-atomic per-file writes, with `$`-pre-escape on `after_pattern` per `String.prototype.replace` GetSubstitution rule. **Q2 (c) all-or-nothing** — any conflict in any file aborts phase 2 entirely; partial writes never ship. **Q6 gate** — TTY no `--yes` triggers a `Proceed? [y/N]` prompt (default-N) on stderr; non-TTY contexts (CI / agents / MCP / HTTP) require `--yes` (or `yes: true`) explicitly. Result envelope (Q5; identical across modes): `{mode, applied, files, conflicts, summary}`. Re-running on already-applied code reports a `line content drifted` conflict whose `actual_at_line` shows the post-rename content — user re-runs `codemap` to refresh the index, then re-runs apply for a vacuous clean pass (Q7). Engine: `application/apply-engine.ts` (pure; `applyDiffPayload`). Boundary: only `cli/cmd-apply.ts` + `application/tool-handlers.ts` may import the engine — re-runnable kit at [`docs/architecture.md` § Boundary verification — apply write path](./architecture.md#boundary-verification--apply-write-path). Floor "No fix engine" preserved — codemap doesn't synthesise edits, it only executes the hunks the recipe row described.
44+
Substrate-shaped fix executor — reads the same row contract `--format diff-json` emits and applies hunks to disk. Recipe SQL is the synthesis surface; codemap is the executor (Moat-A — verdict-shape "should we fix this?" stays on the recipe author). **Three CLI input modes** (mutually exclusive): (1) **recipe** — `codemap apply <recipe-id> [--params k=v[,k=v]]`; MCP/HTTP `apply` `{recipe, params?, dry_run?, yes?, force?, until_empty?, max_passes?, commit_message?}`. (2) **rows** — `codemap apply --rows -|<file.json>`; MCP/HTTP [`apply_rows`](#apply_rows-mcp-tool--cli-mode). (3) **diff** — `codemap apply --diff-input <unified-diff>`; MCP/HTTP [`apply_diff_input`](#apply_diff_input-mcp-tool--cli-mode). Shared flags: `--dry-run` (phase-1 only), `--yes` (skip TTY prompt; required for non-TTY writes), `--json`. **Recipe-only flags:** `--force` / MCP `force` (bypass `auto_fixable` + allowlist); `--until-empty` / `until_empty` + `--max-passes` / `max_passes` (fixpoint on recipe `apply` only). **Git commit:** `--commit` / `commit_message` on recipe `apply` and `apply_diff_input`. **diff-json preview:** each hunk includes `ambiguity_count` (extra `before_pattern` matches on the line; apply rewrites first match only). **Policy (recipe mode only):** recipes with `auto_fixable: false` in `<id>.md` frontmatter reject writes unless `--force` / MCP `force: true`; `apply.autoApplyRecipes` in [user config](./architecture.md#user-config) allowlists recipe ids for non-interactive CLI without `--yes` (MCP/HTTP writes still need `yes: true`). Bundled diff-shape recipe ids: `rename-preview`, `migrate-import-source`, `replace-marker-kind` (`auto_fixable: true`); `stale-imports`, `migrate-deprecated`, `deprecated-usages`, `add-jsdoc-deprecated`, `migrate-jsx-prop` (force-gated unless allowlisted). **`rename-preview` homonyms:** pass `define_in=<symbols.file_path>` to scope the rename to one definition; omit to union all homonyms (use `find-symbol-references` to pick the anchor first). **`--commit` / `commit_message`:** recipe `apply` and `apply_diff_input` only (not `apply_rows`); with `--until-empty`, commit only when `terminated_by` is `empty`. **Phase 1** validates every row via `actual.includes(before_pattern)` (substring match); seven conflict reasons (`file missing` / `line out of range` / `line content drifted` / `path escapes project root` / `path is a symlink` / `duplicate edit on same line`). **Phase 2** (gated on `!dryRun && zero conflicts`) writes via sibling temp + `renameSync` per file; **all-or-nothing** across files on conflicts (no cross-file rollback on crash mid-phase-2). **Q6 gate** — TTY without `--yes` prompts `Proceed? [y/N]`; MCP/HTTP have no prompt path. Result envelope: `{mode, applied, files, conflicts, summary}` (+ optional `passes`, `terminated_by`). Re-apply on stale disk → `line content drifted`; re-index then vacuous zero-row pass (Q7). Engine: `application/apply-engine.ts` (`applyDiffPayload`); orchestration: `application/apply-run.ts`. Full transport matrix: [`architecture.md` § Apply — input modes](./architecture.md#apply--input-modes-transport-and-policy). Boundary kit: [§ Boundary verification — apply write path](./architecture.md#boundary-verification--apply-write-path).
3745

3846
### audit
3947

@@ -353,7 +361,7 @@ Rust-based CSS parser (NAPI bindings). Codemap's `src/css-parser.ts` uses its vi
353361

354362
### `codemap mcp` / MCP server
355363

356-
Stdio MCP (Model Context Protocol) server exposing codemap's structural-query surface to agent hosts (Claude Code, Cursor, Codex, generic MCP clients) as JSON-RPC tools — eliminates the bash round-trip on every agent invocation. **17 tools:** `query`, `query_batch`, `query_recipe`, `audit`, `save_baseline`, `list_baselines`, `drop_baseline`, `context`, `validate`, `show`, `snippet`, `impact`, `affected`, `trace`, `explore`, `node`, `apply`. Each has a CLI twin with the same JSON payload except transport-only MCP resources (`codemap://mcp-instructions`, initialize `instructions`). Subset via **`CODEMAP_MCP_TOOLS`** ([agents.md § MCP tool allowlist](./agents.md#mcp-tool-allowlist)). **Resources:** `codemap://schema`, `codemap://skill`, `codemap://rule`, `codemap://mcp-instructions`, `codemap://recipes`, `codemap://recipes/{id}`, `codemap://files/{path}`, `codemap://symbols/{name}`. Resource freshness is split by contract: schema / skill / rule / mcp-instructions are lazy-cached per server process; recipes, files, and symbols are live read-per-call so inline recency fields and index mutations under `--watch` don't freeze at first read. HTTP's `GET /resources/{encoded-uri}` uses the same resource handler. **Baseline tools** (`save_baseline`, `list_baselines`, `drop_baseline`) mirror `query --save-baseline` / `--baselines` / `--drop-baseline`. **CLI twins:** `query batch`, `trace`, `explore`, `node`, `file`, `schema`, `symbols`, `context --include-snippets`. Tool input/output keys are snake_case on MCP/HTTP — Codemap's convention; CLI stays kebab. Output shape matches each tool's CLI JSON payload; MCP wraps payloads in `{content: [{type: "text", text: …}]}`. Bootstrap once at server boot; tool handlers (in `application/tool-handlers.ts`) and resource handlers (in `application/resource-handlers.ts`) are pure transport-agnostic — the same handlers serve `codemap serve` (HTTP) via `POST /tool/{name}` and `GET /resources/{encoded-uri}`. **Session lifecycle:** exits on client disconnect (stdin EOF, stdout broken pipe, parent process exit, SIGINT/SIGTERM) via `session-lifecycle.ts`; **no idle timeout** — the process stays up while the pipe is open even without tool calls (see [§ Session lifecycle](./architecture.md#cli-usage)). With `--watch`, the watcher starts before connect and drains on exit. Implementation: `src/cli/cmd-mcp.ts` (CLI shell) + `src/application/mcp-server.ts` (engine). See [`architecture.md` § MCP wiring](./architecture.md#cli-usage).
364+
Stdio MCP (Model Context Protocol) server exposing codemap's structural-query surface to agent hosts (Claude Code, Cursor, Codex, generic MCP clients) as JSON-RPC tools — eliminates the bash round-trip on every agent invocation. **19 tools:** `query`, `query_batch`, `query_recipe`, `audit`, `save_baseline`, `list_baselines`, `drop_baseline`, `context`, `validate`, `show`, `snippet`, `impact`, `affected`, `trace`, `explore`, `node`, `apply`, `apply_rows`, `apply_diff_input`. Each has a CLI twin with the same JSON payload except transport-only MCP resources (`codemap://mcp-instructions`, initialize `instructions`). Subset via **`CODEMAP_MCP_TOOLS`** ([agents.md § MCP tool allowlist](./agents.md#mcp-tool-allowlist)). **Resources:** `codemap://schema`, `codemap://skill`, `codemap://rule`, `codemap://mcp-instructions`, `codemap://recipes`, `codemap://recipes/{id}`, `codemap://files/{path}`, `codemap://symbols/{name}`. Resource freshness is split by contract: schema / skill / rule / mcp-instructions are lazy-cached per server process; recipes, files, and symbols are live read-per-call so inline recency fields and index mutations under `--watch` don't freeze at first read. HTTP's `GET /resources/{encoded-uri}` uses the same resource handler. **Baseline tools** (`save_baseline`, `list_baselines`, `drop_baseline`) mirror `query --save-baseline` / `--baselines` / `--drop-baseline`. **CLI twins:** `query batch`, `trace`, `explore`, `node`, `file`, `schema`, `symbols`, `context --include-snippets`. Tool input/output keys are snake_case on MCP/HTTP — Codemap's convention; CLI stays kebab. Output shape matches each tool's CLI JSON payload; MCP wraps payloads in `{content: [{type: "text", text: …}]}`. Bootstrap once at server boot; tool handlers (in `application/tool-handlers.ts`) and resource handlers (in `application/resource-handlers.ts`) are pure transport-agnostic — the same handlers serve `codemap serve` (HTTP) via `POST /tool/{name}` and `GET /resources/{encoded-uri}`. **Session lifecycle:** exits on client disconnect (stdin EOF, stdout broken pipe, parent process exit, SIGINT/SIGTERM) via `session-lifecycle.ts`; **no idle timeout** — the process stays up while the pipe is open even without tool calls (see [§ Session lifecycle](./architecture.md#cli-usage)). With `--watch`, the watcher starts before connect and drains on exit. Implementation: `src/cli/cmd-mcp.ts` (CLI shell) + `src/application/mcp-server.ts` (engine). See [`architecture.md` § MCP wiring](./architecture.md#cli-usage).
357365

358366
### `query_batch`
359367

0 commit comments

Comments
 (0)