From 8ef50f1df61b9767fdf60457fd52aaa28af90dd8 Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:15:55 +0300 Subject: [PATCH 01/13] =?UTF-8?q?fix:=20resolve=20docs=20audit=20residuals?= =?UTF-8?q?=20R.1=E2=80=93R.3=20and=20retire=20audit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Honor --state-dir for project recipes, populate function_params.owner_kind, register files/symbols MCP resource templates, and delete the May 2026 fact-check audit tombstone (R.10–R.12 remain in roadmap/plans). --- README.md | 4 +- docs/README.md | 2 +- .../2026-05-24-docs-fact-check-residuals.md | 56 ---------------- docs/glossary.md | 2 +- docs/roadmap.md | 8 +-- src/application/mcp-server.test.ts | 50 ++++++++++++++ src/application/mcp-server.ts | 67 +++++++++++++++++++ .../query-recipes.pre-bootstrap.test.ts | 9 +++ src/application/query-recipes.test.ts | 25 +++++++ src/application/query-recipes.ts | 49 +++++++++++--- src/cli/main.ts | 4 +- src/extractors/scopes.ts | 9 +++ src/extractors/symbols.ts | 1 + src/parser.test.ts | 28 ++++++++ templates/recipes/find-by-param-type.md | 2 +- 15 files changed, 240 insertions(+), 76 deletions(-) delete mode 100644 docs/audits/2026-05-24-docs-fact-check-residuals.md diff --git a/README.md b/README.md index 81eb1c59..ab626e7a 100644 --- a/README.md +++ b/README.md @@ -213,8 +213,8 @@ codemap mcp # JSON-RPC on st # Tools: query, query_batch (MCP-only — N statements in one round-trip), query_recipe, audit, # save_baseline, list_baselines, drop_baseline, context, validate, show, snippet, impact, apply # Resources: codemap://schema, codemap://skill, codemap://rule (lazy-cached); -# codemap://recipes, codemap://recipes/{id} (live read-per-call — recency fields stay fresh) -# HTTP-only resources (via `codemap serve` GET /resources/{uri}): codemap://files/{path}, codemap://symbols/{name} +# codemap://recipes, codemap://recipes/{id} (live read-per-call — recency fields stay fresh); +# codemap://files/{path}, codemap://symbols/{name} (live read-per-call) # Output shape verbatim from `--json` envelopes (no re-mapping). Snake_case throughout. # Another project diff --git a/docs/README.md b/docs/README.md index 5a5548fa..0d16f4bb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,7 +22,7 @@ Each topic has exactly one canonical file. Other files cross-reference by relati | [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`**). | | [roadmap.md](./roadmap.md) | Forward-looking [**Backlog**](./roadmap.md#backlog) and [**Non-goals**](./roadmap.md#non-goals-v1) (not a `src/` inventory). | | [plans/](./plans/) | One `.md` per in-flight plan. Created on demand — don't add the `-plan` suffix; the folder provides context. See folder contents for the current in-flight set; avoid maintaining a duplicate inline list. | -| [audits/](./audits/) | Targeted architecture / performance / lifecycle audits. Open: [`audits/2026-05-24-docs-fact-check-residuals.md`](./audits/2026-05-24-docs-fact-check-residuals.md). Closed audits indexed from [`roadmap.md` § Closed audits (pointers)](./roadmap.md#closed-audits-pointers). | +| [audits/](./audits/) | Targeted architecture / performance / lifecycle audits. None open; closed audits indexed from [`roadmap.md` § Closed audits (pointers)](./roadmap.md#closed-audits-pointers). | | [research/](./research/) | Dated, snapshot-style notes (e.g. competitive scans, non-goals reassessments). Each note links shipped items back to canonical homes — see [research/non-goals-reassessment-2026-05.md](./research/non-goals-reassessment-2026-05.md). | --- diff --git a/docs/audits/2026-05-24-docs-fact-check-residuals.md b/docs/audits/2026-05-24-docs-fact-check-residuals.md deleted file mode 100644 index 7eae43c5..00000000 --- a/docs/audits/2026-05-24-docs-fact-check-residuals.md +++ /dev/null @@ -1,56 +0,0 @@ -# Docs fact-check residuals — 2026-05-24 - -**Status:** Open — code items R.1–R.3 remain; doc-only R.4–R.9 fixed in follow-up commits on `main`. - -**Scope:** ~129 markdown files under `docs/`, `.agents/`, `templates/`, root, and `.github/`. Parent verification on `main` through `0f7e6fc`. - -**Method:** Five parallel fact-check passes against `src/`, `package.json`, CI workflows, and `templates/recipes/`; manual diff review for information loss. - -**Shipped in audit commits:** `e6ab158`, `3c65a65`, `0f7e6fc` — glob/batchInsert corrections, `--base` git-archive wording, MCP vs HTTP resource split, plan status headers, agent-content path fixes, recipe example SQL, rule frontmatter, synthesis Step 1 closure. - -This audit follows [docs/README.md Rule 6](../README.md) and [docs/README.md Rule 7](../README.md). - ---- - -## TL;DR - -The audit corrected stale/wrong prose without dropping load-bearing design. **Doc-only nits (R.4–R.9) are fixed.** **Three code/doc drift items (R.1–R.3)** remain — none block releases. - ---- - -## Residual findings - -### Code fixes (docs partially or fully updated) - -| ID | Finding | Evidence | Suggested fix | Effort | -| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | -| **R.1** | **Project recipes ignore custom `--state-dir`.** Docs now say `/.codemap/recipes/`; loader hardcodes that path. | `resolveProjectRecipesDir()` in `src/application/query-recipes.ts` — `join(projectRoot, ".codemap", "recipes")` only. | Honor `resolveStateDir()` when resolving project recipe dir, or document permanently that recipes are `.codemap`-scoped only. | S | -| **R.2** | **`function_params.owner_kind` never varies.** Recipe doc updated; extractor always passes `"function"`. | `pushParams()` default in `src/extractors/params.ts`; all call sites omit `ownerKind`. | Populate distinct values (`method`, `arrow`, `constructor`, …) at extraction sites, then restore filter docs on `find-by-param-type`. | M | -| **R.3** | **MCP stdio does not register `codemap://files/{path}` / `codemap://symbols/{name}`.** README and glossary correctly scope these to HTTP `GET /resources/{uri}`; `readResource()` supports them but `registerResources()` does not. | `src/application/mcp-server.ts` vs `src/application/resource-handlers.ts`. | Either register as MCP resource templates (parity with HTTP) or keep HTTP-only and ensure agent-content skill states the split (done in README). | S (if registering) | - -### Doc-only — fixed - -| ID | Finding | Fix | -| ------- | ------------------------------------------ | ------------------------------------------------------------------------ | -| **R.4** | Example timing tables will drift | `docs/benchmark.md` § Results — snapshot / regenerate callout | -| **R.5** | "Main tables" copy ambiguous | `templates/recipes/index-summary.md` — names five tables + points at SQL | -| **R.6** | Relative recipe link in MCP context | `templates/recipes/unused-type-members.md` — recipe id prose only | -| **R.7** | README env block omitted state-dir / watch | `README.md` Environment / flags — `CODEMAP_STATE_DIR`, `CODEMAP_WATCH` | -| **R.8** | Substrate plan ship dates mixed | `docs/plans/substrate-extraction.md` header — wave vs per-PR date note | -| **R.9** | Research note § numbering gap | `docs/research/non-goals-reassessment-2026-05.md` — §4/§6 lifted note | - -### Already tracked elsewhere (no new work) - -| ID | Item | Canonical home | -| -------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| **R.10** | GitHub Marketplace Slice 5 (tags, `MARKETPLACE.md`) | [`roadmap.md` Backlog](../roadmap.md#backlog) · [`plans/github-marketplace-action.md`](../plans/github-marketplace-action.md) | -| **R.11** | C.9 `files.is_entry`, plugin loader, reachability recipes | [`plans/c9-plugin-layer.md`](../plans/c9-plugin-layer.md) | -| **R.12** | Apply-engine synthesis Steps 2–12 | [`research/codemap-richer-index-synthesis-2026-05.md`](../research/codemap-richer-index-synthesis-2026-05.md) §6 | - ---- - -## Closing criteria - -Delete this file when **R.1–R.3** are resolved or explicitly rejected with a one-line decision in `roadmap.md`. Index closure in [`roadmap.md` § Closed audits (pointers)](../roadmap.md#closed-audits-pointers). - -Recover the full May 2026 audit agent reports via conversation / commit diffs at `f25f8a6..0f7e6fc` if needed. diff --git a/docs/glossary.md b/docs/glossary.md index dfc3a9da..c72544e7 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -353,7 +353,7 @@ Rust-based CSS parser (NAPI bindings). Codemap's `src/css-parser.ts` uses its vi ### `codemap mcp` / MCP server -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. v1 ships the query / audit / context / validate / show / snippet / impact / baseline / apply tool set, plus MCP resources for `codemap://schema`, `codemap://skill`, `codemap://rule`, `codemap://recipes`, and `codemap://recipes/{id}`. Resource freshness is split by contract: schema / skill / rule are lazy-cached per server process; recipes are live read-per-call so inline `last_run_at` / `run_count` recency fields don't freeze at first read. HTTP's `GET /resources/{encoded-uri}` uses the same resource handler and additionally serves direct `codemap://files/{path}` and `codemap://symbols/{name}` lookups. Tool input/output keys are snake_case — Codemap's convention, matching the patterns in MCP spec examples and reference servers (GitHub MCP, Cursor built-ins); the spec itself doesn't mandate it. CLI stays kebab — translation lives at the MCP-arg layer. Output shape is verbatim from the CLI's `--json` envelope (no re-mapping). 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}`. Implementation: `src/cli/cmd-mcp.ts` (CLI shell) + `src/application/mcp-server.ts` (engine). See [`architecture.md` § MCP wiring](./architecture.md#cli-usage). +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. v1 ships the query / audit / context / validate / show / snippet / impact / baseline / apply tool set, plus MCP resources for `codemap://schema`, `codemap://skill`, `codemap://rule`, `codemap://recipes`, `codemap://recipes/{id}`, `codemap://files/{path}`, and `codemap://symbols/{name}`. Resource freshness is split by contract: schema / skill / rule 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. Tool input/output keys are snake_case — Codemap's convention, matching the patterns in MCP spec examples and reference servers (GitHub MCP, Cursor built-ins); the spec itself doesn't mandate it. CLI stays kebab — translation lives at the MCP-arg layer. Output shape is verbatim from the CLI's `--json` envelope (no re-mapping). 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}`. Implementation: `src/cli/cmd-mcp.ts` (CLI shell) + `src/application/mcp-server.ts` (engine). See [`architecture.md` § MCP wiring](./architecture.md#cli-usage). ### `query_batch` (MCP-only tool) diff --git a/docs/roadmap.md b/docs/roadmap.md index 3aae88c3..29185fc5 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -52,7 +52,6 @@ Soft constraints — describe shipped reality. Decided-but-unshipped flips live ## Backlog -- [ ] **Docs fact-check residuals (2026-05-24)** — code drift from the full `.md` audit: project-recipes + `--state-dir` loader (R.1), `owner_kind` extraction (R.2), optional MCP `files`/`symbols` resource registration (R.3). Doc-only items fixed. Audit: [`audits/2026-05-24-docs-fact-check-residuals.md`](./audits/2026-05-24-docs-fact-check-residuals.md). Effort: S–M per item. - [ ] **`history` table** (deferred — revisit-triggered) — temporal queries: "when did symbol X get `@deprecated`?", "coverage trend over last 50 commits", "files that became dead this week". `audit --base ` covers the most-common temporal question (PR-scoped diff) without schema growth, so the table earns its place only when bigger questions emerge. Two shapes (per-commit snapshots ~N × DB size; append-only event log heavier CTE walks); both pay an N-reindexes backfill cost (~30s per reindex). **Revisit triggers:** two consumers ship `jq`-based "audit-runs-over-time" workflows, OR `query_baselines` evolution becomes a recurring agent need. - [ ] **`codemap audit` verdict + thresholds** (v1.x) — `verdict: "pass" | "warn" | "fail"` driven by an `audit.deltas[].{added_max, action}` field on the config object (`.codemap/config.{ts,js,json}`). Triggers: two consumers ship `jq`-based threshold scripts with similar shapes, OR one consumer asks with a concrete config sketch. Until then, raw deltas + consumer-side `jq` is the CI exit-code idiom. **Likely accelerant:** the Marketplace Action (next item) shipping is the most plausible path to firing the trigger — once `- uses: stainless-code/codemap@v1` is the dominant CI path, real `jq` threshold scripts will surface. - [ ] **GitHub Marketplace Action — publish + listing finish** — core Action implementation is in-tree: root `action.yml`, `query --ci`, `audit --format sarif` / `--ci`, package-manager detection, dogfood smoke, and opt-in `pr-comment` summary renderer have shipped. Remaining work is the release/listing slice: `MARKETPLACE.md`, `v1.0.0` / floating `v1` tags, Marketplace setup, sacrificial-repo smoke, and making `action-smoke` blocking once the Action tag exists. Action version stream is independent of CLI version (`package.json` currently drives CLI/npm version; Action publishes at its own `v1.0.0`). Plan: [`plans/github-marketplace-action.md`](./plans/github-marketplace-action.md). Effort: S. @@ -74,9 +73,10 @@ Soft constraints — describe shipped reality. Decided-but-unshipped flips live Closed audit content is consolidated into its canonical home (plan, reference doc, or `.agents/lessons.md`) per [docs/README.md Rule 8](./README.md). Recover full text via `git log --follow` on the deleted path. -| Topic | Canonical home | Closed | -| ---------------------------------------------------------- | -------------------------------------------------------------------------------------- | ---------------------- | -| Perf triangulation (per-model audits + triangulation file) | [`plans/perf-triangulation-rollout.md`](./plans/perf-triangulation-rollout.md) Phase 5 | 2026-05-18 (`cc28bce`) | +| Topic | Canonical home | Closed | +| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| Full `.md` fact-check (May 2026) | Shipped: audit fixes `e6ab158`–`14d3efc` on `main`, R.1–R.3 in this PR. R.10–R.12 always in [`roadmap.md` Backlog](./roadmap.md#backlog) / plans. Recover deleted audit: `git log --follow -- docs/audits/2026-05-24-docs-fact-check-residuals.md` | 2026-05-24 | +| Perf triangulation (per-model audits + triangulation file) | [`plans/perf-triangulation-rollout.md`](./plans/perf-triangulation-rollout.md) Phase 5 | 2026-05-18 (`cc28bce`) | - [ ] **Repo-structure conversion (codemap itself: flat → monorepo)** — tracked decision, not a backlog item to ship. Default bias: stay flat until a trigger fires (C.9 community plugins ship as separate packages, OR a user asks for `codemap-core` library export, OR a second distro emerges). Full analysis + three options + reference layouts (oxc / knip / biome / vitest) + revisit triggers in [`plans/lsp-diagnostic-push.md § Repo-structure tradeoffs`](./plans/lsp-diagnostic-push.md#repo-structure-tradeoffs-canonical-home-for-the-monorepo-vs-flat-decision). Don't convert preemptively. - [ ] **Monorepo / workspace awareness** — discover workspaces from `pnpm-workspace.yaml` / `package.json` and index per-workspace dependency graphs (separate from the codemap-itself repo-structure decision above; this is about indexing user repos) diff --git a/src/application/mcp-server.test.ts b/src/application/mcp-server.test.ts index 02a858e7..e11a5d0a 100644 --- a/src/application/mcp-server.test.ts +++ b/src/application/mcp-server.test.ts @@ -721,6 +721,13 @@ describe("MCP server — resources", () => { // callback as one entry per recipe id. const recipeUris = uris.filter((u) => u.startsWith("codemap://recipes/")); expect(recipeUris.length).toBeGreaterThan(0); + + const templates = await client.listResourceTemplates(); + const templateUris = templates.resourceTemplates.map( + (t) => t.uriTemplate, + ); + expect(templateUris).toContain("codemap://files/{+path}"); + expect(templateUris).toContain("codemap://symbols/{name}"); } finally { await server.close(); } @@ -807,6 +814,49 @@ describe("MCP server — resources", () => { await server.close(); } }); + + it("codemap://files/{path} returns a per-file roll-up", async () => { + const { client, server } = await makeClient(); + try { + const r = await client.readResource({ uri: "codemap://files/src/a.ts" }); + const parsed = JSON.parse(readResourceText(r)); + expect(parsed).toMatchObject({ + path: "src/a.ts", + language: "typescript", + line_count: 1, + }); + expect(Array.isArray(parsed.symbols)).toBe(true); + } finally { + await server.close(); + } + }); + + it("codemap://symbols/{name} resolves indexed symbols", async () => { + const db = openDb(); + try { + db.run( + `INSERT INTO symbols (file_path, name, kind, line_start, line_end, signature, is_exported, is_default_export) + VALUES ('src/a.ts', 'A', 'const', 1, 1, 'const A', 1, 0)`, + ); + } finally { + closeDb(db); + } + const { client, server } = await makeClient(); + try { + const r = await client.readResource({ uri: "codemap://symbols/A" }); + const parsed = JSON.parse(readResourceText(r)); + expect(parsed.matches).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: "A", + file_path: "src/a.ts", + }), + ]), + ); + } finally { + await server.close(); + } + }); }); describe("MCP server — show + snippet tools", () => { diff --git a/src/application/mcp-server.ts b/src/application/mcp-server.ts index 1d3addc8..baba4ee1 100644 --- a/src/application/mcp-server.ts +++ b/src/application/mcp-server.ts @@ -14,6 +14,7 @@ import { } from "../runtime"; import { listQueryRecipeCatalog } from "./query-recipes"; import { readResource } from "./resource-handlers"; +import type { ResourcePayload } from "./resource-handlers"; import { applyArgsSchema, auditArgsSchema, @@ -359,6 +360,72 @@ function registerResources(server: McpServer): void { }; }, ); + + server.registerResource( + "file", + new ResourceTemplate("codemap://files/{+path}", { list: undefined }), + { + description: + "Per-file roll-up: symbols, imports, exports, coverage. Encode `{path}` URI-style. Reads live (no caching).", + mimeType: "application/json", + }, + (uri, variables) => { + const path = decodeURIComponent( + typeof variables.path === "string" + ? variables.path + : String(variables.path), + ); + const payload = readResource( + `codemap://files/${encodeURIComponent(path)}`, + ); + return readTemplateResource(uri.toString(), payload, "file"); + }, + ); + + server.registerResource( + "symbol", + new ResourceTemplate("codemap://symbols/{name}", { list: undefined }), + { + description: + "Symbol lookup by exact name. Returns {matches, disambiguation?} envelope. Optional `?in=` filter (mirrors `show --in`). Reads live (no caching).", + mimeType: "application/json", + }, + (uri, variables) => { + const name = + typeof variables.name === "string" + ? variables.name + : String(variables.name); + const parsed = new URL(uri.toString()); + const resourceUri = + parsed.search.length > 0 + ? `codemap://symbols/${encodeURIComponent(name)}${parsed.search}` + : `codemap://symbols/${encodeURIComponent(name)}`; + return readTemplateResource( + uri.toString(), + readResource(resourceUri), + "symbol", + ); + }, + ); +} + +function readTemplateResource( + uri: string, + payload: ResourcePayload | undefined, + label: string, +): { contents: Array<{ uri: string; mimeType: string; text: string }> } { + if (payload === undefined) { + throw new Error(`codemap: unknown ${label} resource "${uri}".`); + } + return { + contents: [ + { + uri, + mimeType: payload.mimeType, + text: payload.text, + }, + ], + }; } function registerStaticResource( diff --git a/src/application/query-recipes.pre-bootstrap.test.ts b/src/application/query-recipes.pre-bootstrap.test.ts index 27c454a6..987d83b8 100644 --- a/src/application/query-recipes.pre-bootstrap.test.ts +++ b/src/application/query-recipes.pre-bootstrap.test.ts @@ -83,4 +83,13 @@ describe("setQueryRecipesProjectRoot — pre-bootstrap CLI parse-phase path", () rmSync(otherRoot, { recursive: true, force: true }); } }); + + it("loads project recipes from a custom --state-dir before bootstrap", () => { + const recipesDir = join(projectRoot, ".cm", "recipes"); + mkdirSync(recipesDir, { recursive: true }); + writeFileSync(join(recipesDir, `${primaryId}.sql`), "SELECT 3 AS ok\n"); + setQueryRecipesProjectRoot(projectRoot, ".cm"); + expect(listQueryRecipeIds()).toContain(primaryId); + expect(getQueryRecipeSql(primaryId)).toContain("SELECT 3"); + }); }); diff --git a/src/application/query-recipes.test.ts b/src/application/query-recipes.test.ts index 0a9d61ec..06b7d6bd 100644 --- a/src/application/query-recipes.test.ts +++ b/src/application/query-recipes.test.ts @@ -14,6 +14,7 @@ import { listQueryRecipeIds, resolveProjectRecipesDir, } from "./query-recipes"; +import { resolveStateDir } from "./state-dir"; let projectRoot: string; @@ -44,6 +45,13 @@ describe("resolveProjectRecipesDir", () => { writeFileSync(join(projectRoot, ".codemap", "recipes"), "not a dir"); expect(resolveProjectRecipesDir(projectRoot)).toBeUndefined(); }); + + it("honors a custom --state-dir for recipes discovery", () => { + const recipesDir = join(projectRoot, ".cm", "recipes"); + mkdirSync(recipesDir, { recursive: true }); + expect(resolveProjectRecipesDir(projectRoot)).toBeUndefined(); + expect(resolveProjectRecipesDir(projectRoot, ".cm")).toBe(recipesDir); + }); }); describe("query-recipes shim — project recipes via runtime root", () => { @@ -70,6 +78,23 @@ describe("query-recipes shim — project recipes via runtime root", () => { expect(getQueryRecipeSql("internal-flaky-tests")).toContain("WHERE 1=0"); }); + it("loads project recipes from a custom state-dir via initCodemap", () => { + const stateRoot = resolveStateDir({ root: projectRoot, cliFlag: ".cm" }); + const recipesDir = join(stateRoot, "recipes"); + mkdirSync(recipesDir, { recursive: true }); + writeFileSync( + join(recipesDir, "custom-state-recipe.sql"), + "SELECT 'cm' AS marker\n", + ); + initCodemap( + resolveCodemapConfig(projectRoot, undefined, { stateDir: stateRoot }), + ); + _resetRecipesCacheForTests(); + + expect(listQueryRecipeIds()).toContain("custom-state-recipe"); + expect(getQueryRecipeSql("custom-state-recipe")).toContain("'cm'"); + }); + it("project recipe shadows bundled — getQueryRecipeSql returns project version", () => { const recipesDir = join(projectRoot, ".codemap", "recipes"); mkdirSync(recipesDir, { recursive: true }); diff --git a/src/application/query-recipes.ts b/src/application/query-recipes.ts index 379a1ec5..dede4c9a 100644 --- a/src/application/query-recipes.ts +++ b/src/application/query-recipes.ts @@ -2,9 +2,10 @@ import { existsSync, statSync } from "node:fs"; import { join } from "node:path"; import { resolveAgentsTemplateDir } from "../agents-init"; -import { getProjectRoot } from "../runtime"; +import { getProjectRoot, getStateDir } from "../runtime"; import { loadAllRecipes } from "./recipes-loader"; import type { LoadedRecipe } from "./recipes-loader"; +import { resolveStateDir } from "./state-dir"; export type { RecipeAction, RecipeParam } from "./recipes-loader"; import type { RecipeAction, RecipeParam } from "./recipes-loader"; @@ -17,7 +18,7 @@ import type { RecipeAction, RecipeParam } from "./recipes-loader"; * - **`body`** — full Markdown body of the sibling `.md` (when present); * description is the first non-empty line of that body. * - **`source`** — `"bundled"` (ships with the npm package) or `"project"` - * (loaded from `/.codemap/recipes/`). + * (loaded from `/recipes/`). * - **`shadows`** — `true` when a project recipe overrides a bundled recipe * of the same id (per plan §9 Q-E — agents read this at session start to * know when a recipe behaves differently from the documented bundled @@ -51,14 +52,21 @@ export function resolveBundledRecipesDir(): string { } /** - * Returns `/.codemap/recipes/` if it exists as a directory, - * else `undefined`. Per plan §9 Q-C, root-only — no walk-up; same root - * the CLI's `--root` / `CODEMAP_ROOT` resolves to. + * Returns `/recipes/` if it exists as a directory, else + * `undefined`. Per plan §9 Q-C, root-only — no walk-up; same root the + * CLI's `--root` / `CODEMAP_ROOT` resolves to. Honors `--state-dir` / + * `CODEMAP_STATE_DIR` via {@link resolveStateDir} when `stateDir` is + * passed (relative flag) or when the runtime config is initialised. */ export function resolveProjectRecipesDir( projectRoot: string, + stateDir?: string | undefined, ): string | undefined { - const dir = join(projectRoot, ".codemap", "recipes"); + const stateRoot = + stateDir !== undefined + ? resolveStateDir({ root: projectRoot, cliFlag: stateDir }) + : resolveStateDir({ root: projectRoot }); + const dir = join(stateRoot, "recipes"); if (!existsSync(dir)) return undefined; if (!statSync(dir).isDirectory()) return undefined; return dir; @@ -77,6 +85,7 @@ export function resolveProjectRecipesDir( let cachedRegistry: LoadedRecipe[] | undefined; let cachedRegistryProjectDir: string | undefined; let projectRootOverride: string | undefined; +let stateDirOverride: string | undefined; /** * Resolve the project root for project-recipe discovery without requiring @@ -90,13 +99,34 @@ let projectRootOverride: string | undefined; * Single source of truth: same `root` value `bootstrapCodemap` resolves to * — no parallel walk-up heuristic. Cache invalidates when root changes. */ -export function setQueryRecipesProjectRoot(root: string | undefined): void { - if (projectRootOverride === root) return; +export function setQueryRecipesProjectRoot( + root: string | undefined, + stateDir?: string | undefined, +): void { + if (projectRootOverride === root && stateDirOverride === stateDir) return; projectRootOverride = root; + stateDirOverride = stateDir; cachedRegistry = undefined; cachedRegistryProjectDir = undefined; } +function resolveRecipesProjectDir(root: string): string | undefined { + if (stateDirOverride !== undefined) { + return resolveProjectRecipesDir(root, stateDirOverride); + } + try { + if (getProjectRoot() === root) { + const dir = join(getStateDir(), "recipes"); + if (!existsSync(dir)) return undefined; + if (!statSync(dir).isDirectory()) return undefined; + return dir; + } + } catch { + // Runtime not initialised — fall back to default `.codemap`. + } + return resolveProjectRecipesDir(root); +} + function resolveCurrentProjectRoot(): string | undefined { if (projectRootOverride !== undefined) return projectRootOverride; try { @@ -109,7 +139,7 @@ function resolveCurrentProjectRoot(): string | undefined { function getRegistry(): LoadedRecipe[] { const root = resolveCurrentProjectRoot(); const projectDir = - root !== undefined ? resolveProjectRecipesDir(root) : undefined; + root !== undefined ? resolveRecipesProjectDir(root) : undefined; if (cachedRegistry !== undefined && cachedRegistryProjectDir === projectDir) { return cachedRegistry; @@ -130,6 +160,7 @@ export function _resetRecipesCacheForTests(): void { cachedRegistry = undefined; cachedRegistryProjectDir = undefined; projectRootOverride = undefined; + stateDirOverride = undefined; } /** diff --git a/src/cli/main.ts b/src/cli/main.ts index 8681c87e..247364ad 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -30,14 +30,14 @@ export async function main(): Promise { return; } - // Project recipes live at `/.codemap/recipes/.sql`. Argv-parse-time + // Project recipes live at `/recipes/.sql`. Argv-parse-time // validation (`parseQueryRest` calls `getQueryRecipeSql` on `--recipe ` / // `--recipes-json` / `--print-sql`) runs BEFORE `bootstrapCodemap` and would // otherwise see `getProjectRoot()` throw → silent fallback to bundled-only. // Plumb the already-resolved root in so parser-side discovery works too. const { setQueryRecipesProjectRoot } = await import("../application/query-recipes.js"); - setQueryRecipesProjectRoot(root); + setQueryRecipesProjectRoot(root, stateDir); // Once-per-process stderr nag if the consumer's pointer files are out // of date relative to `EXPECTED_POINTER_VERSION`. Cure: `agents init diff --git a/src/extractors/scopes.ts b/src/extractors/scopes.ts index 3f8207d9..09cf2416 100644 --- a/src/extractors/scopes.ts +++ b/src/extractors/scopes.ts @@ -98,6 +98,12 @@ export const scopesExtractor: TierExtractor = { // Constructor params already emitted as class-scope symbols by // symbolsExtractor.ClassDeclaration — skip to avoid duplicates. if (node.kind !== "constructor" && node.value?.params?.length) { + const ownerKind = + node.kind === "get" + ? "getter" + : node.kind === "set" + ? "setter" + : "method"; pushTypeParams( node.value.typeParameters, scopes.currentLocalId(), @@ -111,6 +117,7 @@ export const scopesExtractor: TierExtractor = { ctx, jsDocComments, source, + ownerKind, ); } }, @@ -146,6 +153,7 @@ export const scopesExtractor: TierExtractor = { ctx, jsDocComments, source, + "arrow", ); return; } @@ -195,6 +203,7 @@ export const scopesExtractor: TierExtractor = { ctx, jsDocComments, source, + "function", ); }, "FunctionExpression:exit"(node: any) { diff --git a/src/extractors/symbols.ts b/src/extractors/symbols.ts index 0442975f..eaadff90 100644 --- a/src/extractors/symbols.ts +++ b/src/extractors/symbols.ts @@ -402,6 +402,7 @@ function registerSymbolHandlers( ctx, jsDocComments, source, + "constructor", ); } extractClassMembers( diff --git a/src/parser.test.ts b/src/parser.test.ts index f08a1717..3a4dcf21 100644 --- a/src/parser.test.ts +++ b/src/parser.test.ts @@ -736,4 +736,32 @@ describe("extractFileData", () => { ); }); }); + + describe("function_params.owner_kind", () => { + it("records distinct owner kinds for functions, methods, arrows, and constructors", () => { + const src = [ + "export function greet(name: string): string { return name; }", + "export class Svc {", + " constructor(host: string) { this.host = host; }", + " run(id: string): void {}", + " set label(value: string) { this._label = value; }", + "}", + "export const add = (a: number, b: number): number => a + b;", + ].join("\n"); + const d = extractFileData("/proj/x.ts", src, "x.ts"); + const byOwner = (owner: string, kind: string) => + d.functionParams.find( + (p) => + p.owner_name === owner && + p.name !== undefined && + p.owner_kind === kind, + ); + + expect(byOwner("greet", "function")?.name).toBe("name"); + expect(byOwner("Svc", "constructor")?.name).toBe("host"); + expect(byOwner("run", "method")?.name).toBe("id"); + expect(byOwner("label", "setter")?.name).toBe("value"); + expect(byOwner("add", "arrow")?.name).toBe("a"); + }); + }); }); diff --git a/templates/recipes/find-by-param-type.md b/templates/recipes/find-by-param-type.md index 7231c7ce..10128d56 100644 --- a/templates/recipes/find-by-param-type.md +++ b/templates/recipes/find-by-param-type.md @@ -26,7 +26,7 @@ SELECT * FROM function_params WHERE type_text LIKE '%User%'; Available filters on `function_params`: -- `owner_kind` — column exists; extractor currently sets `function` for all owners (distinct `method` / `arrow` / etc. values are planned) +- `owner_kind` — `function`, `method`, `getter`, `setter`, `arrow`, `constructor` - `is_rest = 1` — `...args` rest params only - `is_optional = 1` — params with `?` or default value - `default_text IS NOT NULL` — params that have an inline default From f30873df26d058f6bef4a32ab2149b47562ab621 Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:21:47 +0300 Subject: [PATCH 02/13] =?UTF-8?q?fix:=20PR=20#124=20sweep=20=E2=80=94=20do?= =?UTF-8?q?cs=20hydration,=20watcher=20state-dir,=20MCP=20=3Fin=3D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align canonical docs with /recipes/, extend watcher to honor custom state dirs, fix cmd-mcp resource cache buckets, add regression tests (stateDir cache invalidation, symbols ?in= via MCP template). --- README.md | 4 +- docs/agents.md | 20 ++--- docs/architecture.md | 4 +- docs/glossary.md | 4 +- docs/plans/github-marketplace-action.md | 4 +- docs/roadmap.md | 22 ++--- src/application/http-server.ts | 2 + src/application/mcp-server.test.ts | 23 ++--- src/application/mcp-server.ts | 23 +++-- .../query-recipes.pre-bootstrap.test.ts | 15 ++++ src/application/query-recipes.ts | 3 +- src/application/recipes-loader.ts | 4 +- src/application/watcher.test.ts | 6 ++ src/application/watcher.ts | 90 +++++++++++++------ src/cli/cmd-mcp.ts | 8 +- src/cli/cmd-watch.ts | 13 ++- src/parser.test.ts | 2 + .../agent-content/skill/10-recipes-context.md | 4 +- templates/recipes/high-complexity-untested.md | 2 +- templates/recipes/refactor-risk-ranking.md | 2 +- .../recipes/text-in-deprecated-functions.md | 2 +- 21 files changed, 166 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index ab626e7a..46798e68 100644 --- a/README.md +++ b/README.md @@ -173,10 +173,10 @@ codemap query --recipes-json codemap query --print-sql fan-out # `components-by-hooks` ranks by hook count without SQLite JSON1 (comma-based count on the stored JSON array). -# Project-local recipes — drop SQL files into .codemap/recipes/ to make them discoverable across the team +# Project-local recipes — drop SQL files into `/recipes/` (default `.codemap/recipes/`) to make them discoverable across the team # Bundled recipes live in templates/recipes/ in the npm package; project recipes win on id collision # (shadowing is signalled via a `shadows: true` field in --recipes-json so agents notice the override) -mkdir -p .codemap/recipes +mkdir -p .codemap/recipes # or: codemap --state-dir .cm --full && mkdir -p .cm/recipes echo "SELECT path FROM files WHERE language IN ('ts', 'tsx') AND line_count > 500" \ > .codemap/recipes/big-ts-files.sql codemap query --recipe big-ts-files # auto-discovered alongside bundled diff --git a/docs/agents.md b/docs/agents.md index 0b93f400..ab99f73a 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -78,7 +78,7 @@ Once `agents init` has written the pointer templates, the consumer's disk holds | MCP | resource `codemap://skill` | resource `codemap://rule` | | HTTP (`codemap serve`) | `GET /resources/{encoded uri}` against `codemap://skill` | `GET /resources/{encoded uri}` against `codemap://rule` | -All three transports resolve to the same `assembleAgentContent(kind)` function in `src/application/agent-content.ts` — there is no MCP-only or HTTP-only path. The MCP and HTTP paths share a lazy per-process cache via `readResource()` in `src/application/resource-handlers.ts`; the CLI re-assembles every call (cheap — markdown read + concat). +All three transports resolve to the same `assembleAgentContent(kind)` function in `src/application/agent-content.ts` — there is no MCP-only or HTTP-only path for skill/rule content. The MCP and HTTP paths share a lazy per-process cache via `readResource()` in `src/application/resource-handlers.ts` for schema/skill/rule; recipes, files, and symbols read live every call. The CLI re-assembles every call (cheap — markdown read + concat). ## Section assembler and `*.gen.md` @@ -88,15 +88,15 @@ All three transports resolve to the same `assembleAgentContent(kind)` function i Current skill section layout: -| File | Source | Updates when | -| ----------------------- | ----------------------------------------- | -------------------------------------------------------------------- | -| `00-overview.md` | Hand-written | Rare (intro / CLI usage / output contract) | -| `10-recipes-context.md` | Hand-written | Rare (flags, MCP/HTTP/Apply, tools, resources) | -| `20-recipes.gen.md` | Generated from `listQueryRecipeCatalog()` | Every recipe added under `templates/recipes/` or `.codemap/recipes/` | -| `30-schema.gen.md` | Generated from `createTables()` DDL | Every column / table added in `src/db.ts` | -| `40-query-patterns.md` | Hand-written | Rare (basic / dep / component / CSS / agg examples) | -| `50-maintenance.md` | Hand-written | Rare (re-indexing guidance) | -| `90-troubleshooting.md` | Hand-written | Rare (FAQ) | +| File | Source | Updates when | +| ----------------------- | ----------------------------------------- | ----------------------------------------------------------------------- | +| `00-overview.md` | Hand-written | Rare (intro / CLI usage / output contract) | +| `10-recipes-context.md` | Hand-written | Rare (flags, MCP/HTTP/Apply, tools, resources) | +| `20-recipes.gen.md` | Generated from `listQueryRecipeCatalog()` | Every recipe added under `templates/recipes/` or `/recipes/` | +| `30-schema.gen.md` | Generated from `createTables()` DDL | Every column / table added in `src/db.ts` | +| `40-query-patterns.md` | Hand-written | Rare (basic / dep / component / CSS / agg examples) | +| `50-maintenance.md` | Hand-written | Rare (re-indexing guidance) | +| `90-troubleshooting.md` | Hand-written | Rare (FAQ) | Adding a new generated section: write a renderer, register it in `RENDERERS`, drop a placeholder `XX-name.gen.md` in the kind directory (the placeholder body is the offline-fallback prose). No assembler change. diff --git a/docs/architecture.md b/docs/architecture.md index 634b7d44..eef4d7d3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -137,11 +137,11 @@ A local SQLite database (`.codemap/index.db`) indexes the project tree and store **Show / snippet wiring:** **`src/cli/cmd-show.ts`** + **`src/cli/cmd-snippet.ts`** — sibling CLI verbs sharing the same parser shape (`` + `--kind` + `--in ` + `--json`) and the pure engine **`src/application/show-engine.ts`** (`findSymbolsByName({db, name, kind?, inPath?})` for the lookup; `readSymbolSource({match, projectRoot, indexedContentHash?})` + `getIndexedContentHash(db, filePath)` for the snippet-side FS read; **`buildShowResult`** + **`buildSnippetResult`** envelope builders — same engine the MCP show/snippet tools call). Both verbs return the same `{matches, disambiguation?}` envelope per plan § 4 uniformity — single match → `{matches: [{...}]}`; multi-match adds `{n, by_kind, files, hint}`. Snippet matches add `source` / `stale` / `missing` fields (additive — no shape divergence). **`--in `** is normalized through `toProjectRelative(projectRoot, p)` (from **`src/application/validate-engine.ts`**) so `--in ./src/cli/`, `--in src/cli`, and `--in src/cli/cmd-show.ts` all resolve identically. Stale-file behavior on `snippet`: `hashContent` (from **`src/hash.ts`** — same primitive `cmd-validate.ts` uses) compares the on-disk content_hash against `files.content_hash`; mismatch sets `stale: true` but the source IS still returned (read tool, no auto-reindex side-effects). MCP tools `show` and `snippet` register parallel to the CLI surface (see [§ MCP wiring](#cli-usage)). -**Recipes wiring:** **`src/application/recipes-loader.ts`** (pure transport-agnostic loader) + **`src/application/query-recipes.ts`** (cache + public API — `getQueryRecipeSql` / `getQueryRecipeActions` / `getQueryRecipeParams` / `listQueryRecipeIds` / `listQueryRecipeCatalog` / `getQueryRecipeCatalogEntry`, shared by CLI + MCP). Recipes live as file pairs: **`.sql`** + optional **`.md`**. The loader reads `templates/recipes/` (bundled, ships in npm package next to `templates/agents/`) and `/.codemap/recipes/` (project-local — root-only resolution per the registry plan, no walk-up). Project recipes win on id collision; entries that override a bundled id carry **`shadows: true`** in the catalog so agents reading `codemap://recipes` at session start see when a recipe behaves differently from the documented bundled version. Per-row **`actions`** templates and recipe **`params`** declarations live in YAML frontmatter on each `.md` — uniform shape across bundled + project. Param types are `string | number | boolean`; CLI passes values via repeatable `--params key=value[,key=value]`, MCP / HTTP pass nested `params: {key: value}` to `query_recipe`. Validation runs before SQL binding; missing / unknown / malformed params return the same `{error}` envelope as query failures. Hand-rolled YAML parser is scoped to block-list `actions:` and `params:` only (no `js-yaml` dep). Load-time validation rejects empty SQL and DML / DDL keywords (`INSERT` / `UPDATE` / `DELETE` / `DROP` / `CREATE` / `ALTER` / `ATTACH` / `DETACH` / `REPLACE` / `TRUNCATE` / `VACUUM` / `PRAGMA`) with recipe-aware error messages — defence in depth alongside the runtime `PRAGMA query_only=1` backstop in `query-engine.ts` (PR #35). `.codemap/index.db` is gitignored; `.codemap/recipes/` is NOT (verified via `git check-ignore`) — recipes are git-tracked source code authored for human review. +**Recipes wiring:** **`src/application/recipes-loader.ts`** (pure transport-agnostic loader) + **`src/application/query-recipes.ts`** (cache + public API — `getQueryRecipeSql` / `getQueryRecipeActions` / `getQueryRecipeParams` / `listQueryRecipeIds` / `listQueryRecipeCatalog` / `getQueryRecipeCatalogEntry`, shared by CLI + MCP). Recipes live as file pairs: **`.sql`** + optional **`.md`**. The loader reads `templates/recipes/` (bundled, ships in npm package next to `templates/agents/`) and `/recipes/` (project-local — default `.codemap/recipes/`; honors `--state-dir` / `CODEMAP_STATE_DIR`; root-only resolution per the registry plan, no walk-up). Project recipes win on id collision; entries that override a bundled id carry **`shadows: true`** in the catalog so agents reading `codemap://recipes` at session start see when a recipe behaves differently from the documented bundled version. Per-row **`actions`** templates and recipe **`params`** declarations live in YAML frontmatter on each `.md` — uniform shape across bundled + project. Param types are `string | number | boolean`; CLI passes values via repeatable `--params key=value[,key=value]`, MCP / HTTP pass nested `params: {key: value}` to `query_recipe`. Validation runs before SQL binding; missing / unknown / malformed params return the same `{error}` envelope as query failures. Hand-rolled YAML parser is scoped to block-list `actions:` and `params:` only (no `js-yaml` dep). Load-time validation rejects empty SQL and DML / DDL keywords (`INSERT` / `UPDATE` / `DELETE` / `DROP` / `CREATE` / `ALTER` / `ATTACH` / `DETACH` / `REPLACE` / `TRUNCATE` / `VACUUM` / `PRAGMA`) with recipe-aware error messages — defence in depth alongside the runtime `PRAGMA query_only=1` backstop in `query-engine.ts` (PR #35). `/index.db` is gitignored; `/recipes/` is NOT (verified via `git check-ignore`) — recipes are git-tracked source code authored for human review. **Tool / resource handlers (transport-agnostic):** **`src/application/tool-handlers.ts`** + **`src/application/resource-handlers.ts`** — pure functions that take the args object an MCP tool / resource URI accepts and return a discriminated **`ToolResult`** (`{ok: true, format: 'json'|'sarif'|'annotations', payload}` / `{ok: false, error}`) or a **`ResourcePayload`** (`{mimeType, text}`). MCP and HTTP both wrap the same handlers — MCP translates to `{content: [{type: "text", text}]}`, HTTP translates to `(status, body)` with the right `Content-Type`. Engine layer untouched; transport changes don't ripple into the SQL. -**MCP wiring:** **`src/cli/cmd-mcp.ts`** (argv — `--help` only; bootstrap absorbs `--root`/`--config`) + **`src/application/mcp-server.ts`** (transport — tool / resource registry, SDK glue). Mirrors the `cmd-audit.ts ↔ audit-engine.ts` seam — CLI parses + lifecycle; engine owns the SDK. **`runMcpServer`** bootstraps codemap once at server boot (config + resolver + DB access become module-level state), instantiates `McpServer` from **`@modelcontextprotocol/sdk`**, attaches a **`StdioServerTransport`**, and resolves when stdin closes (clean shutdown). Tool handlers reuse the existing engine entry-points: **`query`** + **`query_recipe`** call **`executeQuery`** in **`src/application/query-engine.ts`** (a pure transport-agnostic engine extracted from `printQueryResult`'s JSON branch — same `[...rows]` / `{count}` / `{group_by, groups}` envelope `--json` would print); **`query_batch`** loops via **`executeQueryBatch`** with batch-wide-defaults + per-statement-overrides (items are `string | {sql, summary?, changed_since?, group_by?}`); **`audit`** runs `resolveAuditBaselines` + `runAudit` from PR #33 unchanged; **`context`** / **`validate`** call `buildContextEnvelope` / `computeValidateRows` from **`src/application/context-engine.ts`** + **`src/application/validate-engine.ts`** (lifted out of `src/cli/cmd-*.ts` in PR #41 — see § Tool / resource handlers above). **`save_baseline`** is one polymorphic tool (`{name, sql? | recipe?}`) with a runtime exclusivity check — mirrors the CLI's single `--save-baseline=` verb. **Tool naming**: snake_case throughout — Codemap convention matching the patterns in MCP spec examples and reference servers (GitHub MCP, Cursor built-ins); the spec itself doesn't mandate it. CLI stays kebab — translation lives at the MCP-arg layer. **Resources** split by freshness contract: `codemap://schema`, `codemap://skill`, and `codemap://rule` use **lazy memoisation** — first `read_resource` populates a per-server-instance cache; constant for the server-process lifetime so eager-vs-lazy produce identical observable behavior. `codemap://recipes` and `codemap://recipes/{id}` are **live read-per-call** (no cache) so the inline `last_run_at` / `run_count` recency fields reflect mutations during the server lifetime — a cached snapshot would freeze them at first-read. `codemap://schema` queries `sqlite_schema` live (on first read, then cached); `codemap://skill` / `codemap://rule` call `assembleAgentContent(kind)` from `application/agent-content.ts`, which concatenates section files under `templates/agent-content//` and dispatches `*.gen.md` files through `RENDERERS` (live recipe catalog, live `createTables()` DDL) — see [agents.md § Section assembler](./agents.md#section-assembler-and-genmd). Output shape uniformity (plan § 4): every tool returns the JSON envelope its CLI counterpart's `--json` flag prints, surfaced via `content: [{type: "text", text: JSON.stringify(payload)}]`. `--changed-since` git lookups are memoised per `(root, ref)` pair across batch items so a `query_batch` of N items sharing the same ref does one git invocation, not N. Per-statement errors in `query_batch` are isolated — failed statements return `{error}` in their slot while siblings still execute. +**MCP wiring:** **`src/cli/cmd-mcp.ts`** (argv — `--help` only; bootstrap absorbs `--root`/`--config`) + **`src/application/mcp-server.ts`** (transport — tool / resource registry, SDK glue). Mirrors the `cmd-audit.ts ↔ audit-engine.ts` seam — CLI parses + lifecycle; engine owns the SDK. **`runMcpServer`** bootstraps codemap once at server boot (config + resolver + DB access become module-level state), instantiates `McpServer` from **`@modelcontextprotocol/sdk`**, attaches a **`StdioServerTransport`**, and resolves when stdin closes (clean shutdown). Tool handlers reuse the existing engine entry-points: **`query`** + **`query_recipe`** call **`executeQuery`** in **`src/application/query-engine.ts`** (a pure transport-agnostic engine extracted from `printQueryResult`'s JSON branch — same `[...rows]` / `{count}` / `{group_by, groups}` envelope `--json` would print); **`query_batch`** loops via **`executeQueryBatch`** with batch-wide-defaults + per-statement-overrides (items are `string | {sql, summary?, changed_since?, group_by?}`); **`audit`** runs `resolveAuditBaselines` + `runAudit` from PR #33 unchanged; **`context`** / **`validate`** call `buildContextEnvelope` / `computeValidateRows` from **`src/application/context-engine.ts`** + **`src/application/validate-engine.ts`** (lifted out of `src/cli/cmd-*.ts` in PR #41 — see § Tool / resource handlers above). **`save_baseline`** is one polymorphic tool (`{name, sql? | recipe?}`) with a runtime exclusivity check — mirrors the CLI's single `--save-baseline=` verb. **Tool naming**: snake_case throughout — Codemap convention matching the patterns in MCP spec examples and reference servers (GitHub MCP, Cursor built-ins); the spec itself doesn't mandate it. CLI stays kebab — translation lives at the MCP-arg layer. **Resources** split by freshness contract: `codemap://schema`, `codemap://skill`, and `codemap://rule` use **lazy memoisation** — first `read_resource` populates a per-server-instance cache; constant for the server-process lifetime so eager-vs-lazy produce identical observable behavior. `codemap://recipes`, `codemap://recipes/{id}`, `codemap://files/{+path}`, and `codemap://symbols/{name}` are **live read-per-call** (no cache) so inline recency fields and index mutations under `--watch` don't freeze at first-read. `codemap://schema` queries `sqlite_schema` live (on first read, then cached); `codemap://skill` / `codemap://rule` call `assembleAgentContent(kind)` from `application/agent-content.ts`, which concatenates section files under `templates/agent-content//` and dispatches `*.gen.md` files through `RENDERERS` (live recipe catalog, live `createTables()` DDL) — see [agents.md § Section assembler](./agents.md#section-assembler-and-genmd). Output shape uniformity (plan § 4): every tool returns the JSON envelope its CLI counterpart's `--json` flag prints, surfaced via `content: [{type: "text", text: JSON.stringify(payload)}]`. `--changed-since` git lookups are memoised per `(root, ref)` pair across batch items so a `query_batch` of N items sharing the same ref does one git invocation, not N. Per-statement errors in `query_batch` are isolated — failed statements return `{error}` in their slot while siblings still execute. **HTTP wiring:** **`src/cli/cmd-serve.ts`** (argv — `--host` / `--port` / `--token`; bootstrap absorbs `--root`/`--config`) + **`src/application/http-server.ts`** (transport — bare `node:http`; routes `POST /tool/{name}` to `tool-handlers`, `GET /resources/{encoded-uri}` to `resource-handlers`, plus `GET /health` / `GET /tools` / `GET /resources`). Default bind **`127.0.0.1:7878`** (loopback only — refuse `0.0.0.0` unless explicitly opted in via `--host 0.0.0.0`). Optional **`--token `** requires `Authorization: Bearer ` on every request; `GET /health` is auth-exempt so liveness probes work without leaking the token. **CSRF + DNS-rebinding guard** (`csrfCheck`) runs before every route — rejects `Sec-Fetch-Site: cross-site` / `same-site` (modern-browser CSRF), any `Origin` header that isn't `null` (older-browser CSRF), and `Host` header mismatch on loopback bind (DNS rebinding). Non-browser clients (curl, fetch from Node, MCP hosts, CI scripts) don't send those headers and pass through. The guard runs even on `/health` so a malicious local webpage can't probe for liveness. Output shape uniformity (plan § D5): every tool returns the same `codemap query --json` envelope (NOT MCP's `{content: [...]}` wrapper — HTTP doesn't need that transport artifact); `format: "sarif"` payloads ship as `application/sarif+json`, `format: "annotations"` / `"mermaid"` / `"diff"` as `text/plain; charset=utf-8`, `format: "diff-json"` as `application/json; charset=utf-8`, JSON otherwise. Per-request DB lifecycle: open / `PRAGMA query_only = 1` / close per call (SQLite reader concurrency); 1 MiB request-body cap rejects trivial DoS. SIGINT / SIGTERM → graceful drain via `server.close()`. Every response carries **`X-Codemap-Version: `** so consumers can pin / detect upgrades. diff --git a/docs/glossary.md b/docs/glossary.md index c72544e7..e3e1c5cf 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -12,7 +12,7 @@ Alphabetical, lowercase. Disambiguation pairs link to each other. - **TS shape** = a TypeScript interface or type alias. - **SQLite table** = an actual on-disk table in `.codemap/index.db`. -- **Recipe** = a cataloged SQL recipe loaded by `src/application/recipes-loader.ts` from `templates/recipes/.{sql,md}` (bundled) or `/.codemap/recipes/.{sql,md}` (project-local). Exposed via `codemap query --recipe ` and the `codemap://recipes` MCP resource. See [§ R recipe](#recipe). +- **Recipe** = a cataloged SQL recipe loaded by `src/application/recipes-loader.ts` from `templates/recipes/.{sql,md}` (bundled) or `/recipes/.{sql,md}` (project-local; default `.codemap/recipes/`). Exposed via `codemap query --recipe ` and the `codemap://recipes` MCP resource. See [§ R recipe](#recipe). - **Query** = any SQL run against the index (recipe or ad-hoc). --- @@ -460,7 +460,7 @@ See **recipe**. A SQL file (plus optional sibling `.md` description) loaded into the catalog by `src/application/recipes-loader.ts`. Two sources, same shape: - **Bundled** — ships in the npm package as `templates/recipes/.{sql,md}`. Examples: `fan-in`, `deprecated-symbols`, `files-hashes`. -- **Project-local** — loaded from `/.codemap/recipes/.{sql,md}` (root-only resolution; not gitignored — meant to be checked in for team review). +- **Project-local** — loaded from `/recipes/.{sql,md}` (default `.codemap/recipes/`; honors `--state-dir` / `CODEMAP_STATE_DIR`; root-only resolution; not gitignored — meant to be checked in for team review). Run via `codemap query --recipe ` (alias `-r`). Project recipes win on id collision with bundled ones (entries carry `shadows: true` in the catalog so agents reading `codemap://recipes` at session start see when a recipe behaves differently from the documented bundled version). Per-row `actions` templates (kebab-case verb + description) live in YAML frontmatter on each `.md` — uniform between bundled and project. Load-time validation rejects empty SQL and DML / DDL keywords; runtime `PRAGMA query_only=1` on `queryRows`, `executeQuery`, and `printQueryResult` (ad-hoc CLI SQL) is the parser-proof backstop. Distinct from an ad-hoc **query** (any SQL string the agent composes itself; ad-hoc SQL never carries actions). diff --git a/docs/plans/github-marketplace-action.md b/docs/plans/github-marketplace-action.md index 0c41c899..9a36d78c 100644 --- a/docs/plans/github-marketplace-action.md +++ b/docs/plans/github-marketplace-action.md @@ -112,7 +112,7 @@ These are the design questions the plan-PR resolves before impl starts. Each get cli = resolveCommand(agent, 'execute', ['@stainless-code/codemap@latest']) ``` - Reasoning: matches consumer's pinned version by default (no surprise drift between local dev and CI); project-local recipes (`/.codemap/recipes/`) work for free; faster on cached runners (`node_modules/.bin/codemap` already there post-`npm ci`). `version:` input forces a pinned `'execute'` when set — explicit override stays clean. Rejected execute-only (ignores consumer's pinned version), project-only (friction for trial-run), pinned-first inversion (the strongest signal is "what runs locally", which is `package.json#devDependencies`). + Reasoning: matches consumer's pinned version by default (no surprise drift between local dev and CI); project-local recipes (`/recipes/`; default `.codemap/recipes/`) work for free; faster on cached runners (`node_modules/.bin/codemap` already there post-`npm ci`). `version:` input forces a pinned `'execute'` when set — explicit override stays clean. Rejected execute-only (ignores consumer's pinned version), project-only (friction for trial-run), pinned-first inversion (the strongest signal is "what runs locally", which is `package.json#devDependencies`). **Edge cases:** - **Version mismatch warning.** If `version:` input is set AND `codemap` is in `devDependencies` AND they disagree → log a warning ("Action input version=X differs from project devDependency Y; using input"). Don't error — consumer may be deliberately overriding. @@ -205,7 +205,7 @@ No schema changes. No new transports. The Action consumes existing engines. - **Agent UX directly** — agents call MCP / CLI, not Actions. Indirect lift only: the Action seeds CodeRabbit / Copilot / Cursor-bot reviews with codemap's structural facts, which they then cite — but that's downstream of the Action's primary value. - **Audit verdict semantics** — Action ships raw deltas + SARIF; pass/warn/fail thresholds remain backlog (per L.7). Shipping the Action is itself the most likely accelerant for the trigger fire (real consumers writing `jq` threshold scripts). -- **Recipe authoring** — Action consumes recipes, doesn't grow them. Project-local recipes (`/.codemap/recipes/`) work in CI exactly as locally; no Action-specific surface. +- **Recipe authoring** — Action consumes recipes, doesn't grow them. Project-local recipes (`/recipes/`; default `.codemap/recipes/`) work in CI exactly as locally; no Action-specific surface. - **IDE integration** — that's [`(d) LSP plan`](./lsp-diagnostic-push.md)'s scope; sibling plan, see relationship below. ## Relationship to `(d) LSP diagnostic-push` plan diff --git a/docs/roadmap.md b/docs/roadmap.md index 29185fc5..e9e1386c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -67,17 +67,6 @@ Soft constraints — describe shipped reality. Decided-but-unshipped flips live Architectural follow-ups (plan-PR-first): parse → insert pipeline overlap and AST cache — see [`plans/perf-triangulation-rollout.md`](./plans/perf-triangulation-rollout.md) Phase 3. ---- - -## Closed audits (pointers) - -Closed audit content is consolidated into its canonical home (plan, reference doc, or `.agents/lessons.md`) per [docs/README.md Rule 8](./README.md). Recover full text via `git log --follow` on the deleted path. - -| Topic | Canonical home | Closed | -| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | -| Full `.md` fact-check (May 2026) | Shipped: audit fixes `e6ab158`–`14d3efc` on `main`, R.1–R.3 in this PR. R.10–R.12 always in [`roadmap.md` Backlog](./roadmap.md#backlog) / plans. Recover deleted audit: `git log --follow -- docs/audits/2026-05-24-docs-fact-check-residuals.md` | 2026-05-24 | -| Perf triangulation (per-model audits + triangulation file) | [`plans/perf-triangulation-rollout.md`](./plans/perf-triangulation-rollout.md) Phase 5 | 2026-05-18 (`cc28bce`) | - - [ ] **Repo-structure conversion (codemap itself: flat → monorepo)** — tracked decision, not a backlog item to ship. Default bias: stay flat until a trigger fires (C.9 community plugins ship as separate packages, OR a user asks for `codemap-core` library export, OR a second distro emerges). Full analysis + three options + reference layouts (oxc / knip / biome / vitest) + revisit triggers in [`plans/lsp-diagnostic-push.md § Repo-structure tradeoffs`](./plans/lsp-diagnostic-push.md#repo-structure-tradeoffs-canonical-home-for-the-monorepo-vs-flat-decision). Don't convert preemptively. - [ ] **Monorepo / workspace awareness** — discover workspaces from `pnpm-workspace.yaml` / `package.json` and index per-workspace dependency graphs (separate from the codemap-itself repo-structure decision above; this is about indexing user repos) - [ ] **Cross-agent handoff artifact** — _speculative_; layered prefix/delta JSON written on session-stop, read on session-start. Complementary to indexing rather than core to it; revisit if user demand emerges @@ -86,3 +75,14 @@ Closed audit content is consolidated into its canonical home (plan, reference do - [ ] Optional **GitHub Actions** `workflow_dispatch` — run golden/benchmark against a **public** corpus only (never private app code) - [ ] **Sass / Less / SCSS:** [Lightning CSS](https://lightningcss.dev/) is CSS-only; preprocessors need a compile step before CSS parsing — see [architecture.md § CSS](./architecture.md#css--css-parserts-lightningcss) - [ ] **[UnJS](https://unjs.io) adoption** — candidates: [`citty`](https://unjs.io/packages/citty) (CLI builder), [`pathe`](https://unjs.io/packages/pathe) (cross-platform paths), [`consola`](https://unjs.io/packages/consola) (structured logging), [`pkg-types`](https://unjs.io/packages/pkg-types) (typed `package.json`/`tsconfig.json`), [`c12`](https://unjs.io/packages/c12) (config loader — see config loader item above) + +--- + +## Closed audits (pointers) + +Closed audit content is consolidated into its canonical home (plan, reference doc, or `.agents/lessons.md`) per the [Audit lifecycle](./README.md#file-ownership) row in [docs/README.md](./README.md). Recover full text via `git log --follow` on the deleted path. + +| Topic | Canonical home | Closed | +| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| Full `.md` fact-check (May 2026) | Shipped `e6ab158`–`14d3efc` + [#124](https://github.com/stainless-code/codemap/pull/124). Recover: `git log --follow -- docs/audits/2026-05-24-docs-fact-check-residuals.md` | 2026-05-24 | +| Perf triangulation | [`plans/perf-triangulation-rollout.md`](./plans/perf-triangulation-rollout.md) Phase 5 | 2026-05-18 | diff --git a/src/application/http-server.ts b/src/application/http-server.ts index 25211654..699cc196 100644 --- a/src/application/http-server.ts +++ b/src/application/http-server.ts @@ -46,6 +46,7 @@ import { createPrimeIndex, createReindexOnChange, DEFAULT_DEBOUNCE_MS, + resolveRecipesWatchPrefix, runWatchLoop, } from "./watcher"; @@ -131,6 +132,7 @@ export async function runHttpServer(opts: HttpServerOpts): Promise { const handle = runWatchLoop({ root: getProjectRoot(), excludeDirNames: getExcludeDirNames(), + recipesWatchPrefix: resolveRecipesWatchPrefix(getProjectRoot()), debounceMs: opts.debounceMs ?? DEFAULT_DEBOUNCE_MS, onPrime: createPrimeIndex({ quiet: false, label: "codemap serve" }), onChange: createReindexOnChange({ diff --git a/src/application/mcp-server.test.ts b/src/application/mcp-server.test.ts index e11a5d0a..a0f0b304 100644 --- a/src/application/mcp-server.test.ts +++ b/src/application/mcp-server.test.ts @@ -727,7 +727,7 @@ describe("MCP server — resources", () => { (t) => t.uriTemplate, ); expect(templateUris).toContain("codemap://files/{+path}"); - expect(templateUris).toContain("codemap://symbols/{name}"); + expect(templateUris).toContain("codemap://symbols/{name}{?in}"); } finally { await server.close(); } @@ -836,23 +836,24 @@ describe("MCP server — resources", () => { try { db.run( `INSERT INTO symbols (file_path, name, kind, line_start, line_end, signature, is_exported, is_default_export) - VALUES ('src/a.ts', 'A', 'const', 1, 1, 'const A', 1, 0)`, + VALUES ('src/a.ts', 'A', 'const', 1, 1, 'const A', 1, 0), + ('src/b.ts', 'A', 'const', 1, 1, 'const A', 1, 0)`, ); } finally { closeDb(db); } const { client, server } = await makeClient(); try { - const r = await client.readResource({ uri: "codemap://symbols/A" }); + const r = await client.readResource({ + uri: "codemap://symbols/A?in=src/a.ts", + }); const parsed = JSON.parse(readResourceText(r)); - expect(parsed.matches).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - name: "A", - file_path: "src/a.ts", - }), - ]), - ); + expect(parsed.matches).toEqual([ + expect.objectContaining({ + name: "A", + file_path: "src/a.ts", + }), + ]); } finally { await server.close(); } diff --git a/src/application/mcp-server.ts b/src/application/mcp-server.ts index baba4ee1..009a5a50 100644 --- a/src/application/mcp-server.ts +++ b/src/application/mcp-server.ts @@ -48,6 +48,7 @@ import { createPrimeIndex, createReindexOnChange, DEFAULT_DEBOUNCE_MS, + resolveRecipesWatchPrefix, runWatchLoop, } from "./watcher"; @@ -55,8 +56,8 @@ import { * MCP server engine — owns the tool / resource registry. CLI shell * (`src/cli/cmd-mcp.ts`) handles argv + lifecycle only; this module is * the thin wrapper around `@modelcontextprotocol/sdk` that registers - * one tool per CLI verb (plus MCP-only `query_batch`) and the four - * `codemap://` resources. Tool bodies are pure handlers in + * one tool per CLI verb (plus MCP-only `query_batch`) and MCP resources + * (static + templates). Tool bodies are pure handlers in * `application/tool-handlers.ts` — same handlers `codemap serve` (HTTP) * dispatches. See [`docs/architecture.md` § MCP wiring]. */ @@ -286,7 +287,8 @@ function registerApplyTool(server: McpServer, opts: ServerOpts): void { } /** - * Register codemap's four MCP resources. Payloads come from the shared + * Register codemap MCP resources (static URIs + file/symbol/recipe + * templates). Same payloads as HTTP `GET /resources/{encoded-uri}`. Payloads come from the shared * `application/resource-handlers.ts` module — same lazy-cache used by the * HTTP transport (`GET /resources/{uri}` in `http-server.ts`). Resources * are constant for the server-process lifetime so eager-vs-lazy produce @@ -384,7 +386,7 @@ function registerResources(server: McpServer): void { server.registerResource( "symbol", - new ResourceTemplate("codemap://symbols/{name}", { list: undefined }), + new ResourceTemplate("codemap://symbols/{name}{?in}", { list: undefined }), { description: "Symbol lookup by exact name. Returns {matches, disambiguation?} envelope. Optional `?in=` filter (mirrors `show --in`). Reads live (no caching).", @@ -395,10 +397,16 @@ function registerResources(server: McpServer): void { typeof variables.name === "string" ? variables.name : String(variables.name); - const parsed = new URL(uri.toString()); + const inRaw = variables.in; + const inPath = + inRaw === undefined + ? undefined + : typeof inRaw === "string" + ? inRaw + : String(inRaw); const resourceUri = - parsed.search.length > 0 - ? `codemap://symbols/${encodeURIComponent(name)}${parsed.search}` + inPath !== undefined && inPath.length > 0 + ? `codemap://symbols/${encodeURIComponent(name)}?in=${encodeURIComponent(inPath)}` : `codemap://symbols/${encodeURIComponent(name)}`; return readTemplateResource( uri.toString(), @@ -480,6 +488,7 @@ export async function runMcpServer(opts: ServerOpts): Promise { const handle = runWatchLoop({ root: getProjectRoot(), excludeDirNames: getExcludeDirNames(), + recipesWatchPrefix: resolveRecipesWatchPrefix(getProjectRoot()), debounceMs: opts.debounceMs ?? DEFAULT_DEBOUNCE_MS, onPrime: createPrimeIndex({ quiet: false, label: "codemap mcp" }), onChange: createReindexOnChange({ diff --git a/src/application/query-recipes.pre-bootstrap.test.ts b/src/application/query-recipes.pre-bootstrap.test.ts index 987d83b8..5bf2dfa2 100644 --- a/src/application/query-recipes.pre-bootstrap.test.ts +++ b/src/application/query-recipes.pre-bootstrap.test.ts @@ -92,4 +92,19 @@ describe("setQueryRecipesProjectRoot — pre-bootstrap CLI parse-phase path", () expect(listQueryRecipeIds()).toContain(primaryId); expect(getQueryRecipeSql(primaryId)).toContain("SELECT 3"); }); + + it("invalidates the cache when --state-dir changes for the same root", () => { + const cmDir = join(projectRoot, ".cm", "recipes"); + const otherDir = join(projectRoot, ".other", "recipes"); + mkdirSync(cmDir, { recursive: true }); + mkdirSync(otherDir, { recursive: true }); + writeFileSync(join(cmDir, `${primaryId}.sql`), "SELECT 3\n"); + writeFileSync(join(otherDir, `${otherId}.sql`), "SELECT 4\n"); + setQueryRecipesProjectRoot(projectRoot, ".cm"); + expect(listQueryRecipeIds()).toContain(primaryId); + setQueryRecipesProjectRoot(projectRoot, ".other"); + const ids = listQueryRecipeIds(); + expect(ids).toContain(otherId); + expect(ids).not.toContain(primaryId); + }); }); diff --git a/src/application/query-recipes.ts b/src/application/query-recipes.ts index dede4c9a..40aaf4ea 100644 --- a/src/application/query-recipes.ts +++ b/src/application/query-recipes.ts @@ -97,7 +97,8 @@ let stateDirOverride: string | undefined; * argv parse, giving the loader a project root before bootstrap. * * Single source of truth: same `root` value `bootstrapCodemap` resolves to - * — no parallel walk-up heuristic. Cache invalidates when root changes. + * — no parallel walk-up heuristic. Cache invalidates when root or + * `stateDir` changes. */ export function setQueryRecipesProjectRoot( root: string | undefined, diff --git a/src/application/recipes-loader.ts b/src/application/recipes-loader.ts index 672423dd..1f9195ec 100644 --- a/src/application/recipes-loader.ts +++ b/src/application/recipes-loader.ts @@ -62,7 +62,7 @@ export interface LoadRecipesOpts { */ bundledDir: string; /** - * Absolute path to the project's `.codemap/recipes/` directory, or + * Absolute path to the project's `/recipes/` directory, or * `undefined` if it doesn't exist. Tracer 3 wires this; Tracer 1 * accepts but doesn't read it. */ @@ -113,7 +113,7 @@ export function mergeRecipes( /** * Read every `.sql` from `dir`, pair with optional `.md`. Returns * `[]` if the directory doesn't exist (project-recipes case — absence of - * `.codemap/recipes/` is not an error). Throws with recipe-aware error + * `/recipes/` is not an error). Throws with recipe-aware error * messages if a `.sql` fails load-time validation (empty after * comment-stripping, or starts with a DML / DDL keyword). * diff --git a/src/application/watcher.test.ts b/src/application/watcher.test.ts index 37343f7d..35119675 100644 --- a/src/application/watcher.test.ts +++ b/src/application/watcher.test.ts @@ -45,6 +45,12 @@ describe("shouldIndexPath", () => { expect(shouldIndexPath(".codemap/recipes/foo.md", exclude)).toBe(true); }); + it("accepts custom state-dir recipe paths", () => { + expect( + shouldIndexPath(".cm/recipes/team.sql", exclude, ".cm/recipes/"), + ).toBe(true); + }); + it("rejects .codemap.db and other dot-prefixed files", () => { // The DB itself + WAL / SHM live under .codemap/ (or the root // depending on config) — never reindex on those. diff --git a/src/application/watcher.ts b/src/application/watcher.ts index 46809b54..928d01a2 100644 --- a/src/application/watcher.ts +++ b/src/application/watcher.ts @@ -1,10 +1,12 @@ -import { extname, relative, sep } from "node:path"; +import { extname, relative, resolve, sep } from "node:path"; import chokidar from "chokidar"; import type { FSWatcher } from "chokidar"; import { closeDb, openDb } from "../db"; +import { getProjectRoot, getStateDir } from "../runtime"; import { runCodemapIndex } from "./run-index"; +import { STATE_DIR_DEFAULT } from "./state-dir"; /** * `codemap watch` engine — keeps `.codemap.db` fresh on file edits so @@ -31,19 +33,43 @@ const INDEXED_EXTENSIONS = new Set([ ".css", ]); +/** Default project-recipes prefix when runtime is not initialised. */ +export const DEFAULT_RECIPES_WATCH_PREFIX = `${STATE_DIR_DEFAULT}/recipes/`; + +/** + * Project-root-relative POSIX prefix for `/recipes/` edits + * the watcher should react to. Uses `getStateDir()` when the runtime + * root matches `projectRoot`; otherwise falls back to `.codemap/recipes/`. + */ +export function resolveRecipesWatchPrefix(projectRoot: string): string { + try { + if (resolve(getProjectRoot()) !== resolve(projectRoot)) { + return DEFAULT_RECIPES_WATCH_PREFIX; + } + const rel = relative(projectRoot, getStateDir()); + if (rel.startsWith("..")) return DEFAULT_RECIPES_WATCH_PREFIX; + const base = rel === "" ? STATE_DIR_DEFAULT : rel.split(sep).join("/"); + return `${base}/recipes/`; + } catch { + return DEFAULT_RECIPES_WATCH_PREFIX; + } +} + /** * True if `relPath` (project-root-relative, POSIX-separated) is something * the indexer cares about: matches an indexed extension AND no path * segment is in the exclude set (`node_modules`, `.git`, `dist`, etc.). * * Pure — same predicate the watcher applies to every chokidar event - * before queueing a reindex. Recipe paths - * (`/.codemap/recipes/.{sql,md}`) are also returned (caller - * uses `runCodemapIndex` which handles them out-of-band). + * before queueing a reindex. Recipe paths under + * `/recipes/.{sql,md}` (default `.codemap/recipes/`) are + * also returned (caller uses `runCodemapIndex` which handles them + * out-of-band). */ export function shouldIndexPath( relPath: string, excludeDirNames: ReadonlySet, + recipesPrefix: string = DEFAULT_RECIPES_WATCH_PREFIX, ): boolean { if (relPath === "" || relPath === ".") return false; // Path-segment scan: bail if any segment is excluded. Hand-rolled (no @@ -62,17 +88,8 @@ export function shouldIndexPath( // Check extension last (cheaper bail above). const ext = extname(relPath); if (INDEXED_EXTENSIONS.has(ext)) return true; - // Project-local recipes: /.codemap/recipes/.{sql,md}. - // The path-segment scan above doesn't exclude `.codemap` since it's - // not in excludeDirNames (the whole .codemap.db dir lives under it). - // `relPath` is always POSIX (toRelativePosix normalizes Windows - // backslashes); compare with a literal `/` prefix so Windows recipe - // changes match too. (Earlier sep-built prefix mismatched on Windows - // — caught by CodeRabbit on PR #47.) - if ( - (ext === ".sql" || ext === ".md") && - relPath.startsWith(".codemap/recipes/") - ) { + // Project-local recipes under `/recipes/`. + if ((ext === ".sql" || ext === ".md") && relPath.startsWith(recipesPrefix)) { return true; } return false; @@ -251,6 +268,11 @@ export interface WatchLoopOpts { excludeDirNames: ReadonlySet; /** Coalesced reindex callback. Path set is project-relative POSIX. */ onChange: (paths: ReadonlySet) => void | Promise; + /** + * Project-root-relative POSIX prefix for `/recipes/` file + * events. Defaults to `.codemap/recipes/` when omitted. + */ + recipesWatchPrefix?: string; /** * Optional priming callback — runs after `backend.start()` but BEFORE * `isWatchActive()` flips to `true`. Embedders that want @@ -298,6 +320,8 @@ export function runWatchLoop(opts: WatchLoopOpts): { stop: () => Promise; } { const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS; + const recipesPrefix = opts.recipesWatchPrefix ?? DEFAULT_RECIPES_WATCH_PREFIX; + const stateDirRel = recipesPrefix.replace(/\/recipes\/$/, ""); // Track in-flight onChange so stop() can drain. Serialise rather than // overlap — concurrent reindexes against the same DB are pointless and @@ -315,13 +339,14 @@ export function runWatchLoop(opts: WatchLoopOpts): { }); }, debounceMs); - const backend: WatchBackend = opts.backend ?? createChokidarBackend(); + const backend: WatchBackend = + opts.backend ?? createChokidarBackend({ recipesPrefix, stateDirRel }); backend.start({ root: opts.root, onEvent: (_kind, absPath) => { const rel = toRelativePosix(opts.root, absPath); - if (!shouldIndexPath(rel, opts.excludeDirNames)) return; + if (!shouldIndexPath(rel, opts.excludeDirNames, recipesPrefix)) return; debouncer.trigger(rel); }, onError: (err) => { @@ -400,7 +425,10 @@ function toRelativePosix(root: string, absPath: string): string { * write detection — handles editors that write large files in chunks) * and `atomic` (mv-replace editors don't trigger spurious unlink+add). */ -function createChokidarBackend(): WatchBackend { +function createChokidarBackend(opts: { + recipesPrefix: string; + stateDirRel: string; +}): WatchBackend { let watcher: FSWatcher | undefined; return { start({ root, onEvent, onError }) { @@ -419,17 +447,23 @@ function createChokidarBackend(): WatchBackend { ignored: (path, stats) => { if (stats === undefined) return false; // dir not yet stat'd if (!stats.isFile()) return false; - // Cheapest filter: skip anything inside a dot-dir other than - // .codemap/recipes (which we want to watch for project recipes). - // chokidar passes absolute paths; convert to root-relative. const rel = toRelativePosix(root, path); - return !rel.startsWith(".codemap/recipes/") - ? rel.includes("/node_modules/") || - rel.includes("/.git/") || - rel.startsWith("node_modules/") || - rel.startsWith(".git/") || - rel.startsWith(".codemap/") - : false; + if (rel.startsWith(opts.recipesPrefix)) return false; + if ( + rel.includes("/node_modules/") || + rel.includes("/.git/") || + rel.startsWith("node_modules/") || + rel.startsWith(".git/") + ) { + return true; + } + if ( + rel.startsWith(`${opts.stateDirRel}/`) || + rel === opts.stateDirRel + ) { + return true; + } + return false; }, }); watcher.on("add", (p) => onEvent("add", p)); diff --git a/src/cli/cmd-mcp.ts b/src/cli/cmd-mcp.ts index 0ea52418..339d523c 100644 --- a/src/cli/cmd-mcp.ts +++ b/src/cli/cmd-mcp.ts @@ -100,13 +100,13 @@ Tools (one per CLI verb plus the MCP-only batch helper; snake_case): dependents, dependencies). Resources: - Lazy-cached catalog (reads once per server lifetime): - codemap://recipes Full recipe catalog. - codemap://recipes/{id} Single recipe (id, description, sql). - codemap://schema Live DDL of every table. + Lazy-cached (constant for the server-process lifetime): + codemap://schema Live DDL of every table (cached after first read). codemap://skill Bundled SKILL.md. codemap://rule Bundled codemap rule markdown. Live read-per-call (no caching — see latest indexed state every read): + codemap://recipes Full recipe catalog (recency fields stay fresh). + codemap://recipes/{id} Single recipe (id, description, sql). codemap://files/{path} Per-file roll-up (symbols, imports, exports, coverage). URI-encode the path. codemap://symbols/{name} Symbol lookup; \`?in=\` diff --git a/src/cli/cmd-watch.ts b/src/cli/cmd-watch.ts index f6169dad..9b60a1bd 100644 --- a/src/cli/cmd-watch.ts +++ b/src/cli/cmd-watch.ts @@ -2,6 +2,7 @@ import { createPrimeIndex, createReindexOnChange, DEFAULT_DEBOUNCE_MS, + resolveRecipesWatchPrefix, runWatchLoop, } from "../application/watcher"; import { getExcludeDirNames, getProjectRoot } from "../runtime"; @@ -80,7 +81,8 @@ export function parseWatchRest(rest: string[]): * stdout. Safe to call before bootstrap. */ export function printWatchCmdHelp(): void { - console.log(`Usage: codemap watch [--debounce ] [--quiet] + console.log( + `Usage: codemap watch [--debounce ] [--quiet] Long-running process that re-indexes changed files in real time so every \`codemap query\` (CLI / MCP / HTTP) reads live data without per-query @@ -103,13 +105,15 @@ Examples: codemap watch --quiet # for IDE-launched background use What gets watched: same files the indexer cares about (TS / TSX / JS / -JSX / CSS + project-local recipes under .codemap/recipes/). node_modules -/ .git / dist / build (and the configured excludeDirNames) are skipped. +JSX / CSS + project-local recipes under \`/recipes/\`, default +\`.codemap/recipes/\`). node_modules / .git / dist / build (and the +configured excludeDirNames) are skipped. The process runs until SIGINT/SIGTERM (drains pending edits + closes the file watcher). Tracer 4 lands an optimization: when watcher is active, \`codemap mcp audit\` skips its incremental-index prelude. -`); +`, + ); } /** @@ -131,6 +135,7 @@ export async function runWatchCmd(opts: WatchOpts): Promise { const handle = runWatchLoop({ root, excludeDirNames: getExcludeDirNames(), + recipesWatchPrefix: resolveRecipesWatchPrefix(root), debounceMs: opts.debounceMs, onPrime: createPrimeIndex({ quiet: opts.quiet }), onChange: createReindexOnChange({ quiet: opts.quiet }), diff --git a/src/parser.test.ts b/src/parser.test.ts index 3a4dcf21..6ed67c49 100644 --- a/src/parser.test.ts +++ b/src/parser.test.ts @@ -747,6 +747,7 @@ describe("extractFileData", () => { " set label(value: string) { this._label = value; }", "}", "export const add = (a: number, b: number): number => a + b;", + "export const bind = function (x: number): number { return x; }", ].join("\n"); const d = extractFileData("/proj/x.ts", src, "x.ts"); const byOwner = (owner: string, kind: string) => @@ -762,6 +763,7 @@ describe("extractFileData", () => { expect(byOwner("run", "method")?.name).toBe("id"); expect(byOwner("label", "setter")?.name).toBe("value"); expect(byOwner("add", "arrow")?.name).toBe("a"); + expect(byOwner("bind", "function")?.name).toBe("x"); }); }); }); diff --git a/templates/agent-content/skill/10-recipes-context.md b/templates/agent-content/skill/10-recipes-context.md index 4b72c2a7..b58d6939 100644 --- a/templates/agent-content/skill/10-recipes-context.md +++ b/templates/agent-content/skill/10-recipes-context.md @@ -18,7 +18,7 @@ Replace placeholders (`'...'`) with your module path, file glob, or symbol name. - **`--baselines`** lists saved baselines (no `rows_json` payload); **`--drop-baseline `** deletes one. Both reject every other flag — they're list-only / drop-only operations. - **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`**. - **Boundary violations (config-driven)** — declare `boundaries: [{name, from_glob, to_glob, action?}]` in `.codemap/config.ts` and run `codemap query --recipe boundary-violations [--format sarif]`. The `action` field defaults to `"deny"` (the only shape v1 surfaces); rules are reconciled into the `boundary_rules` table on every index pass and joined against `dependencies` via SQLite `GLOB`. -- **Project-local recipes** — drop **`.sql`** (and optional **`.md`** for description body, params, and actions) into **`/.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. `.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: `codemap 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. +- **Project-local recipes** — drop **`.sql`** (and optional **`.md`** for description body, params, and actions) into **`/recipes/`** (default `.codemap/recipes/`; honors `--state-dir` / `CODEMAP_STATE_DIR`) 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. `.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: `codemap 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. `/index.db` is gitignored; **`/recipes/` is NOT** — recipes are git-tracked source code authored for human review. **Audit (`codemap audit`)** — separate top-level command for structural-drift verdicts. Composes baselines into a per-delta `{head, deltas}` envelope; v1 ships `files` / `dependencies` / `deprecated`. Two snapshot-source shapes: @@ -55,7 +55,7 @@ Each emitted delta carries its own `base` metadata so mixed-baseline audits are - **`codemap://recipes/{id}`** — one recipe `{id, description, body?, sql, actions?, source, shadows?, last_run_at, run_count}` (replaces `--print-sql `). - **`codemap://schema`** — live DDL of every table in `.codemap/index.db` (also embedded inline below). - **`codemap://skill`** / **`codemap://rule`** — full text of this skill / the codemap rule. Same content `codemap skill` / `codemap rule` print. -- **`codemap://files/{path}`** — per-file roll-up `{path, language, line_count, symbols, imports, exports, coverage}`; URI-encode `{path}`. Live. +- **`codemap://files/{path}`** — per-file roll-up `{path, language, line_count, symbols, imports, exports, coverage}`; URI-encode path segments (MCP template uses `{+path}`). Live. - **`codemap://symbols/{name}`** — exact-name lookup → `{matches, disambiguation?}` (same as `show`); optional `?in=` filter. Live. **Launching:** point your agent host at `codemap mcp` as the stdio command. Most hosts (Claude Code, Cursor, Codex) accept `{command: "codemap", args: ["mcp"], cwd: "/path/to/project"}`. The server inherits `cwd` as the project root unless `--root` overrides it. diff --git a/templates/recipes/high-complexity-untested.md b/templates/recipes/high-complexity-untested.md index 5c477abc..0e4736f9 100644 --- a/templates/recipes/high-complexity-untested.md +++ b/templates/recipes/high-complexity-untested.md @@ -26,7 +26,7 @@ McCabe formula: `1 + (decision points)`. Branching nodes counted by Codemap's pa ## Tuning axes for project-local overrides -`/.codemap/recipes/high-complexity-untested.sql`: +`/recipes/high-complexity-untested.sql` (default `.codemap/recipes/`): - **Complexity threshold**: change `>= 10` to project's risk-appetite (5 for strict; 15 for tolerant). - **Coverage threshold**: change `< 50` to project's risk-appetite (`< 80` for strict). diff --git a/templates/recipes/refactor-risk-ranking.md b/templates/recipes/refactor-risk-ranking.md index 7bb7209f..d0026caf 100644 --- a/templates/recipes/refactor-risk-ranking.md +++ b/templates/recipes/refactor-risk-ranking.md @@ -19,7 +19,7 @@ Three correctness fixes baked into the formula: The output columns `exported_count`, `fan_in`, `avg_coverage_pct`, `measured_symbols`, `risk_score` give agents enough context to triage without re-running queries: count of exports + how many files import this one + average coverage of measured symbols + how many symbols had measurements. -**v1 trade-off (linear-in-fan_in, accepted):** `fan_in = 100, avg_coverage = 99%` and `fan_in = 1, avg_coverage = 0%` both score `100` — equivalent by formula but obviously not equivalent in practice. v1 ships the simple formula; tune via project-local recipe override at `/.codemap/recipes/refactor-risk-ranking.sql`. +**v1 trade-off (linear-in-fan_in, accepted):** `fan_in = 100, avg_coverage = 99%` and `fan_in = 1, avg_coverage = 0%` both score `100` — equivalent by formula but obviously not equivalent in practice. v1 ships the simple formula; tune via project-local recipe override at `/recipes/refactor-risk-ranking.sql` (default `.codemap/recipes/`). Suggested tuning axes for project-local overrides: diff --git a/templates/recipes/text-in-deprecated-functions.md b/templates/recipes/text-in-deprecated-functions.md index c5101ecc..1087886e 100644 --- a/templates/recipes/text-in-deprecated-functions.md +++ b/templates/recipes/text-in-deprecated-functions.md @@ -16,7 +16,7 @@ actions: The toggle-change auto-detect upgrades incremental → full when flipping `fts5: false → true`, so a fresh `--full` is automatic. -**Tuning axes** for project-local overrides at `/.codemap/recipes/text-in-deprecated-functions.sql`: +**Tuning axes** for project-local overrides at `/recipes/text-in-deprecated-functions.sql` (default `.codemap/recipes/`): - **Different markers**: replace `'TODO OR FIXME OR HACK'` with any FTS5 query (e.g. `'NOTE OR REVIEW'`). - **Different coverage threshold**: change `< 50` to project's risk-appetite (e.g. `< 80` for stricter projects). From d0ceeda972894d758510d89e6f7e4f2b5a579bae Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:23:24 +0300 Subject: [PATCH 03/13] docs: align schema resource path with state-dir convention --- templates/agent-content/skill/10-recipes-context.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/agent-content/skill/10-recipes-context.md b/templates/agent-content/skill/10-recipes-context.md index b58d6939..e7ae966e 100644 --- a/templates/agent-content/skill/10-recipes-context.md +++ b/templates/agent-content/skill/10-recipes-context.md @@ -53,7 +53,7 @@ Each emitted delta carries its own `base` metadata so mixed-baseline audits are - **`codemap://recipes`** — full catalog (same as `--recipes-json`). Each row carries `source: "bundled" | "project"`, optional `shadows: true`, plus `last_run_at` / `run_count` recency fields. - **`codemap://recipes/{id}`** — one recipe `{id, description, body?, sql, actions?, source, shadows?, last_run_at, run_count}` (replaces `--print-sql `). -- **`codemap://schema`** — live DDL of every table in `.codemap/index.db` (also embedded inline below). +- **`codemap://schema`** — live DDL of every table in `/index.db` (default `.codemap/index.db`; also embedded inline below). - **`codemap://skill`** / **`codemap://rule`** — full text of this skill / the codemap rule. Same content `codemap skill` / `codemap rule` print. - **`codemap://files/{path}`** — per-file roll-up `{path, language, line_count, symbols, imports, exports, coverage}`; URI-encode path segments (MCP template uses `{+path}`). Live. - **`codemap://symbols/{name}`** — exact-name lookup → `{matches, disambiguation?}` (same as `show`); optional `?in=` filter. Live. From 22edef06865f54135a15ec464b5ce7c517d9a398 Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:24:14 +0300 Subject: [PATCH 04/13] chore: bump deps and override qs to clear audit advisory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bun update --latest plus qs>=6.15.2 override resolves GHSA-q8mj-m7cp-5q26 (transitive via @modelcontextprotocol/sdk → express → body-parser). --- bun.lock | 21 +++++++++++---------- package.json | 3 ++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/bun.lock b/bun.lock index 9c95a4d4..03f46ff2 100644 --- a/bun.lock +++ b/bun.lock @@ -22,7 +22,7 @@ "@types/better-sqlite3": "^7.6.13", "@types/bun": "^1.3.14", "@types/node": "^25.9.1", - "@typescript/native-preview": "^7.0.0-dev.20260519.1", + "@typescript/native-preview": "^7.0.0-dev.20260524.1", "husky": "^9.1.7", "lint-staged": "^17.0.5", "oxfmt": "^0.51.0", @@ -34,6 +34,7 @@ }, }, "overrides": { + "qs": ">=6.15.2", "zod": "^4.4.3", }, "packages": { @@ -327,21 +328,21 @@ "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], - "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260519.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260519.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260519.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260519.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260519.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260519.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260519.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260519.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-VVER7vFUDdfm5k3jbH5765tVEJa7+0rTUkFeXyGYrXPxpw9BIjA0QDxdtdlRyaU8MCZV9IKZUo6doxeAQRAjPg=="], + "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260524.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260524.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260524.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260524.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260524.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260524.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260524.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260524.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-L4YviXl4FVYt4V9vkUKRCZW1DcsUbLRaKC4gxsYmBJQqB262mtUo8eqjKqElgZNIpKwYEAAYsBDrVmNhy6ze2w=="], - "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260519.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c9zdG6sGJf25Jpz04JgE23zhYeprqFypDGuqiX94yMTvR8IWXjq3R2oMnim66YLBDon/V1nCEy6cFixeSd/4fg=="], + "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260524.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dIaYmijy9/tdrxHTxlnfwzP+AC6iAycUYGChOowLR3bVSbKhz4DUJZGMaodzGCtBEWSRUwx0MK1Sa1zBpFbM5g=="], - "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260519.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-N16V3wiM0tsNmSSA7nZrxqXXt5OCJxBwiCVn35rnA7fr4WzJw6rJmwf9heNNhZ6Gh4ne3+Pexajf5akzuHR75Q=="], + "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260524.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-HjfPFOSQCymn5Iu07xoySqfK5lwXthEWN1vltO5SDaFYVRasK6P3DBRe4QL0AiSGr8tRfA3x7BdRPjPVu94Epg=="], - "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260519.1", "", { "os": "linux", "cpu": "arm" }, "sha512-8v4BExeeuCTrhaSGfeIJqm3qQkTzlZix/Qd/FkPlWoz9f7d7COvXb3Z4qhbaVolL0MMnUvQ7m005Z4kYsZ645A=="], + "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260524.1", "", { "os": "linux", "cpu": "arm" }, "sha512-gXzD1BNpPUSAv12a9UvxLTX/+WdoC34skLw6hDLk1kv3VYbd5LecQzEIb7u+WHCsNuXVXffk79+BZP7IQNS1aw=="], - "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260519.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-ltf91vAwKdbu0SlRQbFgi1h5ZrLLrBn6a4qIeN2VILGbtYrCXnARHRznLBv81yUETQ7aVr/LSQcmsWo1ejCK0w=="], + "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260524.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-dH+zYjBs2ajcIRMmb8YkapY7TXzU/yX6HITrXhuoDov+mun7nCNfiRRmRBeqbGxl5JM8mUugBl/yyyhFWjIWdQ=="], - "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260519.1", "", { "os": "linux", "cpu": "x64" }, "sha512-AVD0tczTtFCHNa4RQRVPvu8Hnw4P3hQ+OlUAjnz/lHowvc6o1pYB46elMqfDuaoWqIpv+EAkAPP4ipFCofJ5IA=="], + "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260524.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Fr0A8RPBMXco9IIXel598hC25LE1A5wpB33mBhwimlIQe58MMCAeW+wDHXsmLmF2NiFVT1vr1tjMGmEMMdYdsg=="], - "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260519.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-TM+qatljyejqjHevCta3WIH53i0oGC7K8SoJ6t+mf4cGMTpZTyd7NhC1ts7e6/aydZnG53Bsta2iQi1SMIlQEw=="], + "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260524.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-AhFD6bct2RWeZxONLEvaKPlu+c+/TNejj7ntztHWREaq+lrjz8Qg3tXryah2K66EEmYY0TooapfWUWXWe8i+VQ=="], - "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260519.1", "", { "os": "win32", "cpu": "x64" }, "sha512-r9LEsoY7JC/82gXo8hlOmpQaUXcqmngCVOv+mUx1UeMt9f+1S6oNO0W48o75mlBqqC7jfcMHqw8YS4LfVxPRGw=="], + "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260524.1", "", { "os": "win32", "cpu": "x64" }, "sha512-3VE5kBH2y7vYpj83X9iqGbNR8gwnlQlTjzLKJBzuyfTJPZ9R/vM4txvpuTDRJo18JGzEq4FUjKg7yqpf9ajb2g=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -721,7 +722,7 @@ "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], - "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], + "qs": ["qs@6.15.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="], "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], diff --git a/package.json b/package.json index c14bfb17..c735115f 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@types/better-sqlite3": "^7.6.13", "@types/bun": "^1.3.14", "@types/node": "^25.9.1", - "@typescript/native-preview": "^7.0.0-dev.20260519.1", + "@typescript/native-preview": "^7.0.0-dev.20260524.1", "husky": "^9.1.7", "lint-staged": "^17.0.5", "oxfmt": "^0.51.0", @@ -100,6 +100,7 @@ "unrun": "^0.3.0" }, "overrides": { + "qs": ">=6.15.2", "zod": "$zod" }, "engines": { From fce4e0d173916fd9a977d2133b72dac7657438cc Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:28:38 +0300 Subject: [PATCH 05/13] docs: move closed-audit index out of roadmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relocate deleted-audit pointers to docs/README § Closing audits; drop perf-triangulation row (plan self-documents); update skills. --- .agents/skills/audit-pr-architecture/SKILL.md | 2 +- .agents/skills/docs-governance/SKILL.md | 8 ++++---- .agents/skills/docs-lifecycle-sweep/SKILL.md | 4 ++-- docs/README.md | 12 +++++++++++- docs/roadmap.md | 11 ----------- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/.agents/skills/audit-pr-architecture/SKILL.md b/.agents/skills/audit-pr-architecture/SKILL.md index 2e1672ec..8c5794cf 100644 --- a/.agents/skills/audit-pr-architecture/SKILL.md +++ b/.agents/skills/audit-pr-architecture/SKILL.md @@ -197,7 +197,7 @@ This audit follows [docs/README.md Rule 6](../../../docs/README.md) (no inventor Once findings are shipped (or deferred to `roadmap.md`): 1. **Update Status header**: `Status: Closed (YYYY-MM-DD) — N findings shipped on commits , , ; M deferred to roadmap.md §
.` -2. **Add to `roadmap.md` § Closed audits (pointers)** with a one-line summary. +2. **If the audit file is deleted**, add one row to [`docs/README.md` § Closing audits (pointers)](../../../docs/README.md#closing-audits-pointers) (skip when the plan or slimmed audit self-documents provenance). 3. **Apply [`docs-governance`](../docs-governance/SKILL.md) § Closing an audit re-derivable test.** If the audit has no source-cites, no unique policy, no rejected-alternatives rationale → digest deferred items into `roadmap.md`, then **delete the audit file** (no tombstones). Otherwise slim per the keep-criteria. 4. **Run [`docs-lifecycle-sweep`](../docs-lifecycle-sweep/SKILL.md)** if the closure changes the audit substrate (new topic file, retired old topic) so the rest of the audits/ folder stays evaluated. diff --git a/.agents/skills/docs-governance/SKILL.md b/.agents/skills/docs-governance/SKILL.md index 4de6dea1..1ce0155f 100644 --- a/.agents/skills/docs-governance/SKILL.md +++ b/.agents/skills/docs-governance/SKILL.md @@ -128,10 +128,10 @@ Today, codemap has only the repo-root `docs/README.md` (Tier B). It doesn't stri Codemap doesn't ship an `audits/` folder today. When the first audit lands, choose substrate per: -| Substrate | When to use | Closing | -| ---------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Single-file `audit.md`** | One rolling audit doc | When all open items resolve: lift deferred items to `roadmap.md`; **slim** closed findings to durable policy + cited-from-source bits; keep "Last verified" header current | -| **Multi-file `audits/.md`** | Multiple targeted audit passes worth keeping side-by-side | Per audit: **No source cites AND no unique policy** → digest deferred items into `roadmap.md` (or absorb into a reference doc), then **delete** the audit file (don't leave a tombstone). **Has source cites OR unique policy** → **slim** to the cited findings + the durable policy; add a `Status: Closed` header; file stays in `audits/`. Index closed audits from `roadmap.md § Closed audits (pointers)` | +| Substrate | When to use | Closing | +| ---------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Single-file `audit.md`** | One rolling audit doc | When all open items resolve: lift deferred items to `roadmap.md`; **slim** closed findings to durable policy + cited-from-source bits; keep "Last verified" header current | +| **Multi-file `audits/.md`** | Multiple targeted audit passes worth keeping side-by-side | Per audit: **No source cites AND no unique policy** → digest deferred items into `roadmap.md` (or absorb into a reference doc), then **delete** the audit file (don't leave a tombstone). **Has source cites OR unique policy** → **slim** to the cited findings + the durable policy; add a `Status: Closed` header; file stays in `audits/`. If a deleted audit's recovery would be non-obvious, add one row to [`docs/README.md` § Closing audits (pointers)](../../../docs/README.md#closing-audits-pointers) | **The re-derivable test (positive framing of "no source cites AND no unique policy").** Before keeping a closed audit, ask: _would a fresh audit run today re-derive every finding from current code (codemap query, static-analysis tooling, grep, schema)?_ If yes and nothing in source points back to this file by name, the audit's only remaining job is historical archaeology — `git log --follow` does that better than a stale snapshot. **Delete it.** Three things an audit can carry that the codebase cannot infer, and that earn a slim+keep: diff --git a/.agents/skills/docs-lifecycle-sweep/SKILL.md b/.agents/skills/docs-lifecycle-sweep/SKILL.md index 2a383111..81cfc7d1 100644 --- a/.agents/skills/docs-lifecycle-sweep/SKILL.md +++ b/.agents/skills/docs-lifecycle-sweep/SKILL.md @@ -91,7 +91,7 @@ In dependency order (delete + lift before slimming so cross-refs are correct): 1. **Lift** orphan-able knowledge to its destination. 2. **Update** every inbound cross-reference (in-place edits). 3. **Delete** the source file (Tier C) or apply the slim diff (Tier B). -4. **Update pointer index** — `roadmap.md § Closed audits (pointers)` for audits; `architecture.md` for newly-promoted reference content; `docs/README.md § File Ownership` table for added/removed top-level docs (per [`docs/README.md` Rule 4](../../../docs/README.md)). +4. **Update pointer index** — [`docs/README.md` § Closing audits (pointers)](../../../docs/README.md#closing-audits-pointers) when an audit file is deleted; `architecture.md` for newly-promoted reference content; `docs/README.md § File Ownership` table for added/removed top-level docs (per [`docs/README.md` Rule 4](../../../docs/README.md)). 5. **Re-grep** to confirm zero broken cross-references: `rg ""` returns 0 hits outside the deletion commit message. After execution, the surface is **clean** by definition. @@ -100,7 +100,7 @@ After execution, the surface is **clean** by definition. A sweep report is **transient** by design — it lives on the PR / chat where the sweep ran, not in `docs/`. The findings + chosen actions land as commit messages + cross-link updates; the report itself is not a doc to keep. -If the user wants a durable record, promote it to a one-time entry in `roadmap.md § Closed audits (pointers)` or to a slim `audits/-lifecycle-sweep.md` — but only if the rationale would be hard to reconstruct from `git log --follow`. Default is: don't write a meta-doc about the cleanup. +If the user wants a durable record, promote it to a one-time entry in [`docs/README.md` § Closing audits (pointers)](../../../docs/README.md#closing-audits-pointers) — but only if the rationale would be hard to reconstruct from `git log --follow`. Default is: don't write a meta-doc about the cleanup. ## Anti-patterns diff --git a/docs/README.md b/docs/README.md index 0d16f4bb..0b4ad382 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,7 +22,7 @@ Each topic has exactly one canonical file. Other files cross-reference by relati | [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`**). | | [roadmap.md](./roadmap.md) | Forward-looking [**Backlog**](./roadmap.md#backlog) and [**Non-goals**](./roadmap.md#non-goals-v1) (not a `src/` inventory). | | [plans/](./plans/) | One `.md` per in-flight plan. Created on demand — don't add the `-plan` suffix; the folder provides context. See folder contents for the current in-flight set; avoid maintaining a duplicate inline list. | -| [audits/](./audits/) | Targeted architecture / performance / lifecycle audits. None open; closed audits indexed from [`roadmap.md` § Closed audits (pointers)](./roadmap.md#closed-audits-pointers). | +| [audits/](./audits/) | Targeted architecture / performance / lifecycle audits. None open; deleted audits indexed under [Closing audits (pointers)](#closing-audits-pointers) below. | | [research/](./research/) | Dated, snapshot-style notes (e.g. competitive scans, non-goals reassessments). Each note links shipped items back to canonical homes — see [research/non-goals-reassessment-2026-05.md](./research/non-goals-reassessment-2026-05.md). | --- @@ -117,6 +117,16 @@ Adding a new top-level doc requires: When in doubt, default to absorbing into the closest existing root-level file (usually `roadmap.md` for forward-looking work, `architecture.md` for shipped behavior, `glossary.md` for terminology, `research/` for snapshot notes). +### Closing audits (pointers) + +**Deleted audit files only.** Slimmed files that stay in `audits/` with a `Status: Closed` header, and plans that absorbed audit content, are self-indexing — no row here. + +Add a row when an audit file is **deleted** after closure and recovery would be non-obvious. Recover full text: `git log --follow -- `. + +| Topic | Canonical home | Deleted path | Closed | +| -------------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------- | ---------- | +| Full `.md` fact-check (May 2026) | [#124](https://github.com/stainless-code/codemap/pull/124) (R.1–R.9 shipped) | `docs/audits/2026-05-24-docs-fact-check-residuals.md` | 2026-05-24 | + --- ## Naming Conventions diff --git a/docs/roadmap.md b/docs/roadmap.md index e9e1386c..c1a03e63 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -75,14 +75,3 @@ Soft constraints — describe shipped reality. Decided-but-unshipped flips live - [ ] Optional **GitHub Actions** `workflow_dispatch` — run golden/benchmark against a **public** corpus only (never private app code) - [ ] **Sass / Less / SCSS:** [Lightning CSS](https://lightningcss.dev/) is CSS-only; preprocessors need a compile step before CSS parsing — see [architecture.md § CSS](./architecture.md#css--css-parserts-lightningcss) - [ ] **[UnJS](https://unjs.io) adoption** — candidates: [`citty`](https://unjs.io/packages/citty) (CLI builder), [`pathe`](https://unjs.io/packages/pathe) (cross-platform paths), [`consola`](https://unjs.io/packages/consola) (structured logging), [`pkg-types`](https://unjs.io/packages/pkg-types) (typed `package.json`/`tsconfig.json`), [`c12`](https://unjs.io/packages/c12) (config loader — see config loader item above) - ---- - -## Closed audits (pointers) - -Closed audit content is consolidated into its canonical home (plan, reference doc, or `.agents/lessons.md`) per the [Audit lifecycle](./README.md#file-ownership) row in [docs/README.md](./README.md). Recover full text via `git log --follow` on the deleted path. - -| Topic | Canonical home | Closed | -| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| Full `.md` fact-check (May 2026) | Shipped `e6ab158`–`14d3efc` + [#124](https://github.com/stainless-code/codemap/pull/124). Recover: `git log --follow -- docs/audits/2026-05-24-docs-fact-check-residuals.md` | 2026-05-24 | -| Perf triangulation | [`plans/perf-triangulation-rollout.md`](./plans/perf-triangulation-rollout.md) Phase 5 | 2026-05-18 | From 5d34ba0a91310f680dd59889d8ef92f9ecf60ad5 Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:29:26 +0300 Subject: [PATCH 06/13] docs: drop deleted-audit tombstone references Living docs should not cite removed audit paths; closure anchors are the shipping PR/commit and git history only. --- .agents/skills/audit-pr-architecture/SKILL.md | 2 +- .agents/skills/docs-governance/SKILL.md | 8 ++++---- .agents/skills/docs-lifecycle-sweep/SKILL.md | 4 ++-- docs/README.md | 14 ++++++-------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.agents/skills/audit-pr-architecture/SKILL.md b/.agents/skills/audit-pr-architecture/SKILL.md index 8c5794cf..8860dffa 100644 --- a/.agents/skills/audit-pr-architecture/SKILL.md +++ b/.agents/skills/audit-pr-architecture/SKILL.md @@ -197,7 +197,7 @@ This audit follows [docs/README.md Rule 6](../../../docs/README.md) (no inventor Once findings are shipped (or deferred to `roadmap.md`): 1. **Update Status header**: `Status: Closed (YYYY-MM-DD) — N findings shipped on commits , , ; M deferred to roadmap.md §
.` -2. **If the audit file is deleted**, add one row to [`docs/README.md` § Closing audits (pointers)](../../../docs/README.md#closing-audits-pointers) (skip when the plan or slimmed audit self-documents provenance). +2. **If the audit file is deleted**, cite the shipping PR or commit in the closure PR description — do not add tombstone rows for deleted paths in living docs (see [`docs/README.md` § Closing audits](../../../docs/README.md#closing-audits)). 3. **Apply [`docs-governance`](../docs-governance/SKILL.md) § Closing an audit re-derivable test.** If the audit has no source-cites, no unique policy, no rejected-alternatives rationale → digest deferred items into `roadmap.md`, then **delete the audit file** (no tombstones). Otherwise slim per the keep-criteria. 4. **Run [`docs-lifecycle-sweep`](../docs-lifecycle-sweep/SKILL.md)** if the closure changes the audit substrate (new topic file, retired old topic) so the rest of the audits/ folder stays evaluated. diff --git a/.agents/skills/docs-governance/SKILL.md b/.agents/skills/docs-governance/SKILL.md index 1ce0155f..4a062b8f 100644 --- a/.agents/skills/docs-governance/SKILL.md +++ b/.agents/skills/docs-governance/SKILL.md @@ -128,10 +128,10 @@ Today, codemap has only the repo-root `docs/README.md` (Tier B). It doesn't stri Codemap doesn't ship an `audits/` folder today. When the first audit lands, choose substrate per: -| Substrate | When to use | Closing | -| ---------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Single-file `audit.md`** | One rolling audit doc | When all open items resolve: lift deferred items to `roadmap.md`; **slim** closed findings to durable policy + cited-from-source bits; keep "Last verified" header current | -| **Multi-file `audits/.md`** | Multiple targeted audit passes worth keeping side-by-side | Per audit: **No source cites AND no unique policy** → digest deferred items into `roadmap.md` (or absorb into a reference doc), then **delete** the audit file (don't leave a tombstone). **Has source cites OR unique policy** → **slim** to the cited findings + the durable policy; add a `Status: Closed` header; file stays in `audits/`. If a deleted audit's recovery would be non-obvious, add one row to [`docs/README.md` § Closing audits (pointers)](../../../docs/README.md#closing-audits-pointers) | +| Substrate | When to use | Closing | +| ---------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Single-file `audit.md`** | One rolling audit doc | When all open items resolve: lift deferred items to `roadmap.md`; **slim** closed findings to durable policy + cited-from-source bits; keep "Last verified" header current | +| **Multi-file `audits/.md`** | Multiple targeted audit passes worth keeping side-by-side | Per audit: **No source cites AND no unique policy** → digest deferred items into `roadmap.md` (or absorb into a reference doc), then **delete** the audit file (no tombstones in living docs). **Has source cites OR unique policy** → **slim** to the cited findings + the durable policy; add a `Status: Closed` header; file stays in `audits/`. Recovery pointers belong in the absorbing plan's provenance block or git history — not a maintained index | **The re-derivable test (positive framing of "no source cites AND no unique policy").** Before keeping a closed audit, ask: _would a fresh audit run today re-derive every finding from current code (codemap query, static-analysis tooling, grep, schema)?_ If yes and nothing in source points back to this file by name, the audit's only remaining job is historical archaeology — `git log --follow` does that better than a stale snapshot. **Delete it.** Three things an audit can carry that the codebase cannot infer, and that earn a slim+keep: diff --git a/.agents/skills/docs-lifecycle-sweep/SKILL.md b/.agents/skills/docs-lifecycle-sweep/SKILL.md index 81cfc7d1..ea9b4bbf 100644 --- a/.agents/skills/docs-lifecycle-sweep/SKILL.md +++ b/.agents/skills/docs-lifecycle-sweep/SKILL.md @@ -91,7 +91,7 @@ In dependency order (delete + lift before slimming so cross-refs are correct): 1. **Lift** orphan-able knowledge to its destination. 2. **Update** every inbound cross-reference (in-place edits). 3. **Delete** the source file (Tier C) or apply the slim diff (Tier B). -4. **Update pointer index** — [`docs/README.md` § Closing audits (pointers)](../../../docs/README.md#closing-audits-pointers) when an audit file is deleted; `architecture.md` for newly-promoted reference content; `docs/README.md § File Ownership` table for added/removed top-level docs (per [`docs/README.md` Rule 4](../../../docs/README.md)). +4. **Update cross-references** — fix inbound links to deleted paths; `architecture.md` for newly-promoted reference content; `docs/README.md § File Ownership` table for added/removed top-level docs (per [`docs/README.md` Rule 4](../../../docs/README.md)). Do not add tombstone rows for deleted audit paths. 5. **Re-grep** to confirm zero broken cross-references: `rg ""` returns 0 hits outside the deletion commit message. After execution, the surface is **clean** by definition. @@ -100,7 +100,7 @@ After execution, the surface is **clean** by definition. A sweep report is **transient** by design — it lives on the PR / chat where the sweep ran, not in `docs/`. The findings + chosen actions land as commit messages + cross-link updates; the report itself is not a doc to keep. -If the user wants a durable record, promote it to a one-time entry in [`docs/README.md` § Closing audits (pointers)](../../../docs/README.md#closing-audits-pointers) — but only if the rationale would be hard to reconstruct from `git log --follow`. Default is: don't write a meta-doc about the cleanup. +Default: don't write a meta-doc about the cleanup. Durable closure anchors are the shipping PR / commit — not a maintained list of deleted paths. ## Anti-patterns diff --git a/docs/README.md b/docs/README.md index 0b4ad382..95646bea 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,7 +22,7 @@ Each topic has exactly one canonical file. Other files cross-reference by relati | [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`**). | | [roadmap.md](./roadmap.md) | Forward-looking [**Backlog**](./roadmap.md#backlog) and [**Non-goals**](./roadmap.md#non-goals-v1) (not a `src/` inventory). | | [plans/](./plans/) | One `.md` per in-flight plan. Created on demand — don't add the `-plan` suffix; the folder provides context. See folder contents for the current in-flight set; avoid maintaining a duplicate inline list. | -| [audits/](./audits/) | Targeted architecture / performance / lifecycle audits. None open; deleted audits indexed under [Closing audits (pointers)](#closing-audits-pointers) below. | +| [audits/](./audits/) | Targeted architecture / performance / lifecycle audits. None open. | | [research/](./research/) | Dated, snapshot-style notes (e.g. competitive scans, non-goals reassessments). Each note links shipped items back to canonical homes — see [research/non-goals-reassessment-2026-05.md](./research/non-goals-reassessment-2026-05.md). | --- @@ -117,15 +117,13 @@ Adding a new top-level doc requires: When in doubt, default to absorbing into the closest existing root-level file (usually `roadmap.md` for forward-looking work, `architecture.md` for shipped behavior, `glossary.md` for terminology, `research/` for snapshot notes). -### Closing audits (pointers) +### Closing audits -**Deleted audit files only.** Slimmed files that stay in `audits/` with a `Status: Closed` header, and plans that absorbed audit content, are self-indexing — no row here. +When an audit closes, lift shipped work into canonical homes (`architecture.md`, a plan, `.agents/lessons.md`, `roadmap.md` backlog). **Do not leave tombstones** — no pointer table, no "recover via `git log --follow -- `" rows in living docs. Deleted audit text lives in git history only; cite the shipping PR or commit when closure needs a durable anchor. -Add a row when an audit file is **deleted** after closure and recovery would be non-obvious. Recover full text: `git log --follow -- `. - -| Topic | Canonical home | Deleted path | Closed | -| -------------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------- | ---------- | -| Full `.md` fact-check (May 2026) | [#124](https://github.com/stainless-code/codemap/pull/124) (R.1–R.9 shipped) | `docs/audits/2026-05-24-docs-fact-check-residuals.md` | 2026-05-24 | +- **Delete** when the re-derivable test passes (findings visible in source / no source-cites / no unique policy) — see [docs-governance § Closing an audit](../../.agents/skills/docs-governance/SKILL.md#closing-an-audit). +- **Slim + keep** in `audits/` when the file carries decisions-of-record, source back-references, or methodology not captured elsewhere — add a `Status: Closed` header. +- **Absorb into a plan** when the audit is the synthesis substrate for in-flight work — the plan's provenance block owns recovery (`git show -- docs/audits/…` belongs there, not in `docs/README.md`). --- From 8731b597ecedade077e0aff37d6f32ff51a2252d Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:30:58 +0300 Subject: [PATCH 07/13] docs: tighten lifecycle guidance against historical bloat Default research/audit closure to lift+delete; restrict existence test to inbound cites; stop citing slim-appendix precedent; lessons are staging not archive. --- .agents/lessons.md | 10 +++++++--- .agents/rules/agents-tier-system.md | 2 +- .agents/rules/docs-governance.md | 18 +++++++++--------- .agents/rules/lessons.md | 13 +++++++------ .agents/skills/audit-pr-architecture/SKILL.md | 8 ++++---- .agents/skills/diagnose/SKILL.md | 2 +- .agents/skills/docs-governance/SKILL.md | 8 ++++---- .agents/skills/docs-lifecycle-sweep/SKILL.md | 10 +++++----- docs/README.md | 8 ++++---- 9 files changed, 42 insertions(+), 37 deletions(-) diff --git a/.agents/lessons.md b/.agents/lessons.md index 89b0d320..639e0d6c 100644 --- a/.agents/lessons.md +++ b/.agents/lessons.md @@ -1,10 +1,14 @@ # Lessons -Persistent log of corrections and insights from past sessions. Agents **must** check this file at the start of every session and append new lessons after corrections. +Persistent log of corrections and insights from past sessions. Read when relevant; **encode durable lessons in rules/skills** — this file is a staging area, not an archive. -## Format +## Rules -Each entry is a single bullet: `- **** — `. Newest entries at the bottom. +1. **Append only durable, non-obvious corrections** — not session trivia already captured in a rule, skill, or reference doc. +2. **Prefer lifting** — when a lesson becomes policy, move it to `.agents/rules/` or the relevant skill and **delete** the bullet here (or supersede with one line pointing at the rule). +3. **Keep entries atomic** — one lesson per bullet. One sentence. +4. **No duplicates** — check before appending; merge or supersede instead. +5. **Supersede, don't accumulate** — outdated lesson → replace with a new bullet citing what changed; don't leave both. ## Lessons diff --git a/.agents/rules/agents-tier-system.md b/.agents/rules/agents-tier-system.md index cd597543..e6b98fe0 100644 --- a/.agents/rules/agents-tier-system.md +++ b/.agents/rules/agents-tier-system.md @@ -28,7 +28,7 @@ Genuinely cross-cutting. Apply to every turn regardless of file: - `codemap` — STOP-before-grep - `concise-comments` — sweep your own new comments before reporting - `concise-reporting` — extreme concision in agent reports -- `lessons` — read at session start, append after corrections +- `lessons` — read when relevant; lift durable ones into rules/skills - `no-bypass-hooks` — never `--no-verify` on `git commit` - `pr-comment-fact-check` — fires the fact-check skill on PR-comment intent triggers - `preserve-comments` — never silently delete TODOs / commented-out code diff --git a/.agents/rules/docs-governance.md b/.agents/rules/docs-governance.md index bd18b883..93cf5337 100644 --- a/.agents/rules/docs-governance.md +++ b/.agents/rules/docs-governance.md @@ -21,13 +21,13 @@ The canonical Rules (1–10) live in [`docs/README.md`](../../docs/README.md) ## Five lifecycle types (universal) -| Type | Folder | Closing | -| ------------- | ----------------------------------------------------- | ------------------------------------------------------------------- | -| **Reference** | root (`architecture.md`, `glossary.md`, etc.) | Lives forever; kept current | -| **Roadmap** | root (`roadmap.md`, single file) | Lives forever | -| **Plan** | `plans/.md` | **Delete + lift** when work ships (no "Slim & keep in plans/") | -| **Audit** | `audit.md` (single) OR `audits/.md` (multi) | Substrate variants — see skill | -| **Research** | `research/.md` OR `research/-YYYY-MM.md` | Adopted (lift + delete) / Rejected (keep with status header) / Open | +| Type | Folder | Closing | +| ------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| **Reference** | root (`architecture.md`, `glossary.md`, etc.) | Lives forever; kept current | +| **Roadmap** | root (`roadmap.md`, single file) | Lives forever | +| **Plan** | `plans/.md` | **Delete + lift** when work ships (no "Slim & keep in plans/") | +| **Audit** | `audit.md` (single) OR `audits/.md` (multi) | Substrate variants — see skill | +| **Research** | `research/.md` OR `research/-YYYY-MM.md` | Adopted → lift + **delete** (unless inbound cites require slim stub) / Rejected → status header only / Open | ## Top three disciplines @@ -41,8 +41,8 @@ A file earns its place if it meets at least one of: 1. Source code cites it (JSDoc, error message, comment grep-anchor) 2. It documents durable policy unavailable elsewhere -3. It tracks open work (audit findings, plan, roadmap items, evaluation) -4. It carries unique historical context that `git log` + reference docs can't reconstruct +3. It tracks open work (audit findings, in-flight plan, roadmap items, evaluation with unresolved items) +4. Inbound source cites require a slim stub — deletion would orphan a live citation (not "interesting history") If none → fold + delete. diff --git a/.agents/rules/lessons.md b/.agents/rules/lessons.md index 99a2f68a..7bff68f9 100644 --- a/.agents/rules/lessons.md +++ b/.agents/rules/lessons.md @@ -1,5 +1,5 @@ --- -description: Read lessons at session start; append after corrections +description: Read lessons when relevant; lift durable ones into rules/skills alwaysApply: true --- @@ -7,8 +7,9 @@ alwaysApply: true ## Rules -1. **Read at session start** — At the beginning of every conversation, read `.agents/lessons.md` to avoid repeating past mistakes. -2. **Append after corrections** — When the user corrects you or you discover a non-obvious mistake, append a bullet to the `## Lessons` section: `- **** — `. -3. **Keep entries atomic** — One lesson per bullet. Be concise (one sentence). -4. **No duplicates** — Before appending, check if an equivalent lesson already exists. -5. **Never delete lessons** — Only add. If a lesson becomes outdated, append a new one that supersedes it. +1. **Read when relevant** — skim `.agents/lessons.md` when the task touches an area with past corrections; not a mandatory full read every session. +2. **Append only durable, non-obvious corrections** — not session trivia already in a rule, skill, or reference doc. +3. **Prefer lifting** — when a lesson becomes policy, move it to `.agents/rules/` or the relevant skill and remove the bullet here (or supersede with one line pointing at the rule). +4. **Keep entries atomic** — one lesson per bullet. One sentence. +5. **No duplicates** — merge or supersede instead of appending near-duplicates. +6. **Supersede, don't accumulate** — outdated lesson → one replacement bullet; don't leave both. diff --git a/.agents/skills/audit-pr-architecture/SKILL.md b/.agents/skills/audit-pr-architecture/SKILL.md index 8860dffa..884546c0 100644 --- a/.agents/skills/audit-pr-architecture/SKILL.md +++ b/.agents/skills/audit-pr-architecture/SKILL.md @@ -196,10 +196,10 @@ This audit follows [docs/README.md Rule 6](../../../docs/README.md) (no inventor Once findings are shipped (or deferred to `roadmap.md`): -1. **Update Status header**: `Status: Closed (YYYY-MM-DD) — N findings shipped on commits , , ; M deferred to roadmap.md §
.` -2. **If the audit file is deleted**, cite the shipping PR or commit in the closure PR description — do not add tombstone rows for deleted paths in living docs (see [`docs/README.md` § Closing audits](../../../docs/README.md#closing-audits)). -3. **Apply [`docs-governance`](../docs-governance/SKILL.md) § Closing an audit re-derivable test.** If the audit has no source-cites, no unique policy, no rejected-alternatives rationale → digest deferred items into `roadmap.md`, then **delete the audit file** (no tombstones). Otherwise slim per the keep-criteria. -4. **Run [`docs-lifecycle-sweep`](../docs-lifecycle-sweep/SKILL.md)** if the closure changes the audit substrate (new topic file, retired old topic) so the rest of the audits/ folder stays evaluated. +1. **Update Status header** (only if the file stays): `Status: Closed (YYYY-MM-DD) — N findings shipped; M deferred to roadmap.md §
.` Prefer the closure PR as the durable anchor — not a growing commit-hash list in the doc body. +2. **If the audit file is deleted**, cite the shipping PR in the closure PR description — do not add tombstone rows or deleted-path recovery instructions in living docs (see [`docs/README.md` § Closing audits](../../../docs/README.md#closing-audits)). +3. **Apply [`docs-governance`](../docs-governance/SKILL.md) § Closing an audit re-derivable test.** If the audit has no source-cites, no unique policy, no rejected-alternatives rationale → digest deferred items into `roadmap.md`, then **delete the audit file** (no tombstones). Otherwise slim to cited sections only — not full findings prose. +4. **Run [`docs-lifecycle-sweep`](../docs-lifecycle-sweep/SKILL.md)** only when closure leaves other files in `docs/audits/` or `docs/research/` that fail the existence test — not as a mandatory post-step on every closure. ## Anti-patterns diff --git a/.agents/skills/diagnose/SKILL.md b/.agents/skills/diagnose/SKILL.md index 57b463a6..04c62bf0 100644 --- a/.agents/skills/diagnose/SKILL.md +++ b/.agents/skills/diagnose/SKILL.md @@ -111,6 +111,6 @@ Required before declaring done: - [ ] All `[DEBUG-…]` instrumentation removed (`grep` the prefix) - [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location) - [ ] The hypothesis that turned out correct is stated in the commit / PR message — so the next debugger learns -- [ ] If the post-mortem yields a permanent insight, append a one-line entry to [`.agents/lessons.md`](../../lessons.md) per the lessons-rule discipline +- [ ] If the post-mortem yields a permanent insight, **lift** it into `.agents/rules/` or the relevant skill; append to [`.agents/lessons.md`](../../lessons.md) only when it is not yet encoded elsewhere **Then ask: what would have prevented this bug?** If the answer involves architectural change (no good test seam, tangled callers, hidden coupling) hand off to [`improve-codebase-architecture`](../improve-codebase-architecture/SKILL.md) with the specifics. Make the recommendation **after** the fix is in, not before — you have more information now than when you started. diff --git a/.agents/skills/docs-governance/SKILL.md b/.agents/skills/docs-governance/SKILL.md index 4a062b8f..b6b5695b 100644 --- a/.agents/skills/docs-governance/SKILL.md +++ b/.agents/skills/docs-governance/SKILL.md @@ -51,7 +51,7 @@ A file earns its place if it meets at least one of: 1. **Source code cites it** (JSDoc, error message, comment grep-anchor, cited rule number, file path reference) 2. **It documents durable policy or framework** unavailable elsewhere 3. **It tracks open work** (open audit findings, in-flight plan, roadmap items, ongoing evaluation) -4. **It carries unique historical context** that `git log` + the relevant reference doc cannot reconstruct +4. **Inbound source cites require a slim stub** — live citations by path/anchor would orphan on delete. (Not "interesting history" — if only git could reconstruct it, delete.) If none → fold any salvageable content into `roadmap.md` / `architecture.md` / the relevant reference doc, fix the cross-refs, delete the file. @@ -130,7 +130,7 @@ Codemap doesn't ship an `audits/` folder today. When the first audit lands, choo | Substrate | When to use | Closing | | ---------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Single-file `audit.md`** | One rolling audit doc | When all open items resolve: lift deferred items to `roadmap.md`; **slim** closed findings to durable policy + cited-from-source bits; keep "Last verified" header current | +| **Single-file `audit.md`** | One rolling audit doc | When all open items resolve: lift deferred items to `roadmap.md`; **delete** if re-derivable test passes; else **slim** to cited findings + durable policy only. `"Last verified"` headers apply to **open** audits only — not closed stubs | | **Multi-file `audits/.md`** | Multiple targeted audit passes worth keeping side-by-side | Per audit: **No source cites AND no unique policy** → digest deferred items into `roadmap.md` (or absorb into a reference doc), then **delete** the audit file (no tombstones in living docs). **Has source cites OR unique policy** → **slim** to the cited findings + the durable policy; add a `Status: Closed` header; file stays in `audits/`. Recovery pointers belong in the absorbing plan's provenance block or git history — not a maintained index | **The re-derivable test (positive framing of "no source cites AND no unique policy").** Before keeping a closed audit, ask: _would a fresh audit run today re-derive every finding from current code (codemap query, static-analysis tooling, grep, schema)?_ If yes and nothing in source points back to this file by name, the audit's only remaining job is historical archaeology — `git log --follow` does that better than a stale snapshot. **Delete it.** Three things an audit can carry that the codebase cannot infer, and that earn a slim+keep: @@ -150,7 +150,7 @@ The _one_ lifecycle, no "Slim & keep in plans/" option: - **Default — delete + lift.** When work ships, the plan's durable bits move to where they earn a permanent home; the plan file dies. Lift destinations: - **Caller-facing convention** → `architecture.md` (or a topic-split sibling under `architecture/.md` if `architecture.md` ever needs splitting) - **New domain term** → `glossary.md` entry - - **Slim-but-coherent durable policy with source cites** → `audits/.md` with `Status: Closed` + - **Slim-but-coherent durable policy with source cites** → lift into `.agents/rules/` / `.agents/skills/` or a **live** reference section — not a closed stub in `audits/` unless source already cites that audit path - **Project-wide policy** → `.agents/rules/` / `.agents/skills/` - **Decision-of-record from external evaluation** → already covered by § Closing research - **In-flight or deferred** → stays in `plans/` with no status header (open is the default). @@ -160,7 +160,7 @@ The _one_ lifecycle, no "Slim & keep in plans/" option: A research file's job is the evaluation. When the evaluation concludes, follow the canonical [`docs/README.md` Rule 8](../../../docs/README.md) lifecycle: -- **Adopted** → lift the decision-of-record into the relevant reference doc (`architecture.md`, `glossary.md`, etc.) or — for repo-level tools — into `.agents/rules/` + `.agents/skills/`. **Slim the note to a "What shipped" appendix** linking to canonical homes (precedent: [`research/non-goals-reassessment-2026-05.md`](../../../docs/research/non-goals-reassessment-2026-05.md) — its § 8 errata + § Closed-out items pattern). **Exception:** § 6 anti-pattern files (per-tool trackers; peer-tool framing) get **deleted**, not slimmed — the framing was off-mission, not just stale, and a "What shipped" appendix would re-anchor the wrong mental model. +- **Adopted** → lift the decision-of-record into the relevant reference doc (`architecture.md`, `glossary.md`, etc.) or — for repo-level tools — into `.agents/rules/` + `.agents/skills/`. **Delete** the research file once nothing cites it by path. **Do not** leave "What shipped" inventory tables, analytical-history sections, or `git log` / `git show` recovery instructions in living docs — git history is the archive. **Slim + keep** only when inbound source cites would orphan (same bar as audits). **Exception:** § 6 anti-pattern files (per-tool trackers; peer-tool framing) get **deleted**, not slimmed — the framing was off-mission, not just stale, and a slim appendix would re-anchor the wrong mental model. - **Rejected** → add a `Status: Rejected (date) — ` header at the top. Keep the file. The rejection rationale is exactly what saves the next agent from re-litigating it. - **Open / Ongoing** → stays in `research/` with no status header (open is the default). Ongoing tool trackers (e.g. `research/.md`) are explicitly long-lived but **only when they aren't peer-tool trackers** — see § 6. diff --git a/.agents/skills/docs-lifecycle-sweep/SKILL.md b/.agents/skills/docs-lifecycle-sweep/SKILL.md index ea9b4bbf..f791cac4 100644 --- a/.agents/skills/docs-lifecycle-sweep/SKILL.md +++ b/.agents/skills/docs-lifecycle-sweep/SKILL.md @@ -72,11 +72,11 @@ If the file is an audit, also check the [docs-governance § Closing an audit re- ### 3. Classify each file -| Tier | Verdict | Action | -| --------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **A — Keep verbatim** | Cited from source by rule number / section anchor; OR Reference / Roadmap that lives forever per its lifecycle | Update "Last verified" header (audits) or no-op | -| **B — Slim + keep** | Closed but ≥1 audit keep-criteria applies; OR has cited content that's stable | Slim to cited / durable bits + verification recipe + status header; preserve cited rule numbers per [§ 7](../docs-governance/SKILL.md#7-cross-reference-preservation-discipline) | -| **C — Delete + lift** | Closed AND no source cites AND all findings shipped/lifted; OR superseded; OR fails the existence test outright | Lift any orphan-able knowledge into the natural reference doc / skill; update the pointer index; **delete the file** (no tombstones) | +| Tier | Verdict | Action | +| --------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **A — Keep verbatim** | Cited from source by rule number / section anchor; OR Reference / Roadmap that lives forever per its lifecycle | No-op (open audits may carry a "Last verified" date — refresh only while findings are open) | +| **B — Slim + keep** | Closed but ≥1 audit keep-criteria applies; OR has cited content that's stable | Slim to cited / durable bits + verification recipe + status header; preserve cited rule numbers per [§ 7](../docs-governance/SKILL.md#7-cross-reference-preservation-discipline) | +| **C — Delete + lift** | Closed AND no source cites AND all findings shipped/lifted; OR superseded; OR fails the existence test outright | Lift any orphan-able knowledge into the natural reference doc / skill; fix inbound cross-refs; **delete the file** (no tombstones, no pointer rows, no recovery instructions in living docs) | ### 4. Surface the classification report (BEFORE any edits) diff --git a/docs/README.md b/docs/README.md index 95646bea..a689c851 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ Each topic has exactly one canonical file. Other files cross-reference by relati | [roadmap.md](./roadmap.md) | Forward-looking [**Backlog**](./roadmap.md#backlog) and [**Non-goals**](./roadmap.md#non-goals-v1) (not a `src/` inventory). | | [plans/](./plans/) | One `.md` per in-flight plan. Created on demand — don't add the `-plan` suffix; the folder provides context. See folder contents for the current in-flight set; avoid maintaining a duplicate inline list. | | [audits/](./audits/) | Targeted architecture / performance / lifecycle audits. None open. | -| [research/](./research/) | Dated, snapshot-style notes (e.g. competitive scans, non-goals reassessments). Each note links shipped items back to canonical homes — see [research/non-goals-reassessment-2026-05.md](./research/non-goals-reassessment-2026-05.md). | +| [research/](./research/) | Dated snapshot notes for **open** evaluations. Closed adopted notes delete after lift; rejected notes keep a one-line status header only. | --- @@ -38,7 +38,7 @@ These rules are normative — cite them by number in PR review. Ordered by how o 5. **Cross-references use relative paths** — `[architecture.md § Section](./architecture.md#section)` or `[plans/foo.md](./plans/foo.md)`. Prefer section-deep links over file-only links. 6. **No inventory counts in narrative** — don't hardcode counts of files, symbols, recipes, or other code-derived quantities. Use qualitative descriptors or a `codemap query --json` example. Decision values (cache TTLs, batch sizes, schema version) are fine — those are decisions, not inventory. 7. **No line-number references** — line numbers (e.g. `parser.ts:241`) rot on every edit. Reference by function name, section heading, or symbol from `codemap query` instead. Methodology tables in [benchmark.md](./benchmark.md) are exempt. -8. **Research notes get closed** — when a research scan's adopt items ship, slim the note to a "What shipped" appendix linking to canonical homes (see [research/non-goals-reassessment-2026-05.md](./research/non-goals-reassessment-2026-05.md) as the precedent — its § 8 errata + § Closed-out items pattern). Rejected items keep a `Status: Rejected (date) — ` header. +8. **Research notes get closed** — **default: lift + delete.** When adopt items ship, move decisions-of-record into canonical homes (`architecture.md`, `glossary.md`, `roadmap.md`, a plan, `.agents/rules/`). **Delete** the research file once nothing in source cites it by path. **Rejected-only keep:** add `Status: Rejected (YYYY-MM-DD) — ` and stop — no "analytical history" appendices. **Slim + keep** only when inbound source cites (rule numbers, `NOTE(...)`, tests) would orphan — then keep cited sections + status header, not the full evaluation prose. Do **not** retain "What shipped" inventory tables or `git log` / `git show` recovery rows in living docs. 9. **New term ⇒ update [glossary.md](./glossary.md) in the same PR** — when a PR introduces a new domain noun / verb / acronym (table name, recipe id, parser name, schema column), add or update its entry. Disambiguations (e.g. `FileRow` TS shape vs `files` SQLite table) take priority over single defs. 10. **Core surface change ⇒ check which agent-content layer it belongs to** — the v1 pointer pattern split the agent surface in two: - **Auto-flows (no template edit needed)** — recipe additions (`templates/recipes/*.{sql,md}`), schema additions (`src/db.ts` `createTables()`). Both surfaces via `*.gen.md` renderers in `src/application/agent-content.ts` and the served skill regenerates on every fetch. @@ -95,7 +95,7 @@ A file earns its place if it meets at least one of: 1. **Source code or another doc cites it** (grep finds the path). 2. **It documents durable policy or framework** unavailable elsewhere. 3. **It tracks open work** (open audit findings, in-flight plan, roadmap items). -4. **It carries unique historical context** that `git log` + `architecture.md` cannot reconstruct. +4. **Inbound source cites require a slim stub** — JSDoc, rules, tests, or plans link to this file by path/anchor; deletion would orphan them. (Not "interesting history" — if only git could reconstruct it, delete.). If none → fold any salvageable content into roadmap / architecture / glossary, fix the cross-refs, delete the file. @@ -103,7 +103,7 @@ If none → fold any salvageable content into roadmap / architecture / glossary, A research note's job is the evaluation. When it concludes: -- **Adopted** → lift the decisions-of-record into the relevant reference doc; slim the note to a "What shipped" appendix linking to canonical homes (precedent: [research/non-goals-reassessment-2026-05.md](./research/non-goals-reassessment-2026-05.md)). +- **Adopted** → lift the decision-of-record into the relevant reference doc; **delete** the research file when nothing cites it by path. Rejected-only keep per Rule 8 above. - **Rejected** → add `Status: Rejected (YYYY-MM-DD) — ` at the top. Keep the file. Don't delete; the rejection rationale saves the next agent from re-litigating. - **Open** → stays in `research/` with no status header (open is the default). From 8b351636cb5d2ffaafa86f91a9f1d996a3319ccc Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:31:10 +0300 Subject: [PATCH 08/13] docs: align glossary research lifecycle with lift+delete default --- docs/glossary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/glossary.md b/docs/glossary.md index e3e1c5cf..fbeb8584 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -474,7 +474,7 @@ Per-recipe `last_run_at` (epoch ms) + `run_count` for agent-host ranking — sur ### research -A snapshot-style note under `docs/research/` capturing a competitive scan or evaluation. Closed per [README § Closing research](./README.md#closing-research) — adopted items are slimmed to a "What shipped" appendix; rejected items keep a status header. +A snapshot-style note under `docs/research/` capturing a competitive scan or evaluation. Closed per [README § Closing research](./README.md#closing-research) — **default lift + delete**; rejected-only keep with a status header. ### resolver From dbce23760c546b2e35b0910a8897c0587c38d4db Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:35:31 +0300 Subject: [PATCH 09/13] docs: dogfood lifecycle sweep without losing decisions-of-record Lift apply-engine open steps to plans/apply-engine-direction.md; embed LSP/C.9 cadence + shape history in open plans; keep research archives (Tier B) with explicit plan pointers; rewire governance cross-refs. --- .agents/rules/agents-tier-system.md | 2 +- .agents/rules/docs-governance.md | 2 +- .agents/skills/docs-governance/SKILL.md | 2 +- .agents/skills/docs-lifecycle-sweep/SKILL.md | 2 +- docs/README.md | 2 +- docs/plans/apply-engine-direction.md | 103 ++++++++++++++++++ docs/plans/c9-plugin-layer.md | 37 ++++--- docs/plans/github-marketplace-action.md | 1 - docs/plans/lsp-diagnostic-push.md | 53 ++++++--- docs/plans/substrate-extraction.md | 2 +- .../codemap-richer-index-synthesis-2026-05.md | 14 +-- .../non-goals-reassessment-2026-05.md | 3 +- 12 files changed, 180 insertions(+), 43 deletions(-) create mode 100644 docs/plans/apply-engine-direction.md diff --git a/.agents/rules/agents-tier-system.md b/.agents/rules/agents-tier-system.md index e6b98fe0..cb36edc4 100644 --- a/.agents/rules/agents-tier-system.md +++ b/.agents/rules/agents-tier-system.md @@ -45,7 +45,7 @@ Today's Tier-2 rules: - `agents-tier-system` (this rule) — auto-attaches when authoring under `.agents/**` or `.cursor/**`. - `docs-governance` — primes the docs framework when authoring under `docs/**` or `.agents/**` (paired with [`docs-governance` skill](../skills/docs-governance/SKILL.md)). -- `plan-pr-inspiration-discipline` — primes plan-PR / recipe authoring with the open-spec inspection list when authoring under `docs/plans/**` or `templates/recipes/**`. The canonical inspection-sources table lives in the rule body itself (lifted from `research/non-goals-reassessment-2026-05.md § 4` in 2026-05; see [§ 7 Lifted to](../../docs/research/non-goals-reassessment-2026-05.md#7-lifted-to)). +- `plan-pr-inspiration-discipline` — primes plan-PR / recipe authoring with the open-spec inspection list when authoring under `docs/plans/**` or `templates/recipes/**`. The canonical inspection-sources table lives in the rule body itself. ### Tier 3 — Discoverable skills (no rule) diff --git a/.agents/rules/docs-governance.md b/.agents/rules/docs-governance.md index 93cf5337..556b1bf0 100644 --- a/.agents/rules/docs-governance.md +++ b/.agents/rules/docs-governance.md @@ -33,7 +33,7 @@ The canonical Rules (1–10) live in [`docs/README.md`](../../docs/README.md) 1. **Anchor preservation** — slim READMEs keep cited rule numbers and section anchors. Grep before any slim: `rg "Rule [0-9]+" docs/` and `rg "(#[a-z-]+)?"`. 2. **Anti-bloat meta-rule** — don't add a rule until there's content that needs it. Same for ownership-table rows. -3. **Repo-level vs in-source** — repo-wide tool evaluations + adoption (oxlint, future plugins) live in `.agents/`, not as permanent `docs/research/` files. A `docs/research/` file may motivate the adoption, but the rule + skill earn the permanent home. Per-tool tracker notes (peer-tool comparisons, adoption-candidate logs) are an anti-pattern — peer-tool framing goes off-mission fast; positioning lives in [`docs/why-codemap.md`](../../docs/why-codemap.md) and [`research/non-goals-reassessment-2026-05.md`](../../docs/research/non-goals-reassessment-2026-05.md), not in tracker files. +3. **Repo-level vs in-source** — repo-wide tool evaluations + adoption (oxlint, future plugins) live in `.agents/`, not as permanent `docs/research/` files. A `docs/research/` file may motivate the adoption, but the rule + skill earn the permanent home. Per-tool tracker notes (peer-tool comparisons, adoption-candidate logs) are an anti-pattern — peer-tool framing goes off-mission fast; positioning lives in [`docs/why-codemap.md`](../../docs/why-codemap.md), not in tracker files. ## Existence test (apply on every doc-touching PR) diff --git a/.agents/skills/docs-governance/SKILL.md b/.agents/skills/docs-governance/SKILL.md index b6b5695b..2bda695b 100644 --- a/.agents/skills/docs-governance/SKILL.md +++ b/.agents/skills/docs-governance/SKILL.md @@ -85,7 +85,7 @@ Same applies to ownership-table rows — a row exists when the file or folder it **Codemap-wide tool evaluations + adoption** (e.g. oxlint, future plugins) belong directly in `.agents/rules/` + `.agents/skills/` — not in `docs/research/`. The artifact that earns a permanent home isn't the evaluation; it's the rule + skill. -A `docs/research/` file may **motivate** adoption of a repo-level tool, but the _adoption itself_ is repo-level — the rule lands under `.agents/rules/`, not as a permanent doc under `docs/research/`. The research note then slims to "what shipped" (per [`docs/README.md` Rule 8](../../../docs/README.md)). **Per-tool tracker notes are an anti-pattern** — peer-tool framing goes off-mission fast; positioning lives in [`docs/why-codemap.md`](../../../docs/why-codemap.md) and [`research/non-goals-reassessment-2026-05.md`](../../../docs/research/non-goals-reassessment-2026-05.md), not in tracker files. +A `docs/research/` file may **motivate** adoption of a repo-level tool, but the _adoption itself_ is repo-level — the rule lands under `.agents/rules/`, not as a permanent doc under `docs/research/`. The research note is **deleted** after lift (per [`docs/README.md` Rule 8](../../../docs/README.md)). **Per-tool tracker notes are an anti-pattern** — peer-tool framing goes off-mission fast; positioning lives in [`docs/why-codemap.md`](../../../docs/why-codemap.md), not in tracker files. ### 7. Cross-reference preservation discipline diff --git a/.agents/skills/docs-lifecycle-sweep/SKILL.md b/.agents/skills/docs-lifecycle-sweep/SKILL.md index f791cac4..64b278fc 100644 --- a/.agents/skills/docs-lifecycle-sweep/SKILL.md +++ b/.agents/skills/docs-lifecycle-sweep/SKILL.md @@ -56,7 +56,7 @@ Map each file to one of the 5 lifecycle types per [docs-governance § 1](../docs ### 2. Apply the existence test -Per [docs-governance § 2](../docs-governance/SKILL.md#2-existence-test-apply-on-every-doc-touching-pr), each file earns its place if it meets ≥1 of: source cite / durable policy / open work / unique historical context. +Per [docs-governance § 2](../docs-governance/SKILL.md#2-existence-test-apply-on-every-doc-touching-pr), each file earns its place if it meets ≥1 of: source cite / durable policy / open work / inbound cites require slim stub. For each file, run the cite-check evidence command: diff --git a/docs/README.md b/docs/README.md index a689c851..0507d609 100644 --- a/docs/README.md +++ b/docs/README.md @@ -130,7 +130,7 @@ When an audit closes, lift shipped work into canonical homes (`architecture.md`, ## Naming Conventions - **`plans/` files**: `.md` — the folder provides "plan" context, don't add a `-plan` suffix. -- **`research/` files**: `-YYYY-MM.md` for dated snapshots (e.g. `non-goals-reassessment-2026-05.md`); `.md` for ongoing tool evaluations. +- **`research/` files**: `-YYYY-MM.md` for dated snapshots; `.md` for ongoing tool evaluations. **Delete after lift** when adopted (Rule 8). - **Top-level files**: descriptive domain noun (`architecture.md`, `glossary.md`, `roadmap.md`) — no prefix or suffix. --- diff --git a/docs/plans/apply-engine-direction.md b/docs/plans/apply-engine-direction.md new file mode 100644 index 00000000..0021c03e --- /dev/null +++ b/docs/plans/apply-engine-direction.md @@ -0,0 +1,103 @@ +# Apply-engine direction — diff-shape recipes + agent-in-the-loop substrate + +> **Status:** open · substrate growth (Steps 5–7 partial) lives in [`substrate-extraction.md`](./substrate-extraction.md) (tiers 1–6 shipped). This plan owns the **apply-engine / diff-shape recipe** half of the richer-index synthesis. +> +> **Motivator:** extend `codemap apply` from recipe-driven single-file hunks toward agent-in-the-loop row contracts (`apply --rows -`, diff-input, fixpoint loops) without crossing [Moat A](../roadmap.md#moats-load-bearing) (SQL/recipe API) or [Moat B](../roadmap.md#moats-load-bearing) (no re-extraction in apply). +> +> **Source:** consolidated from [`research/codemap-richer-index-synthesis-2026-05.md`](../research/codemap-richer-index-synthesis-2026-05.md) § 6–8 (lifted 2026-05). Full triangulation matrices remain in that research note until all steps close. + +--- + +## Shipped (do not re-litigate) + +| Step | Work | Canonical home | +| ------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| 1 | Doc reframe (Floors split; apply vs verdict lints) | [`roadmap.md § Floors`](../roadmap.md#floors-v1-product-shape), [`why-codemap.md`](../why-codemap.md) | +| 5 | `calls.{line_start, column_start, column_end}` + call-shape metadata | [`substrate-extraction.md` Tier 1.A](./substrate-extraction.md), [`architecture.md § Schema`](../architecture.md#schema) | +| 7 (substrate) | `exports` position columns + `re_export_chains` | [`substrate-extraction.md` Tiers 1.B / 2.2](./substrate-extraction.md) | + +--- + +## Open steps (implementation sequence) + +### Step 2 — Three diff-shape recipes (S × 3) + +Ship `replace-marker-kind`, `migrate-import-source`, `add-jsdoc-deprecated` — pure SQL + frontmatter; no engine/schema change. + +Open: sequential PRs per [tracer-bullets](../../.agents/rules/tracer-bullets.md); hold `organize-imports` / `missing-exports` for wave 2. + +### Step 3 — Per-row `actions[].command` template (S) + +Extend `recipes-loader.ts` / `query-recipes.ts` so `actions[]` carries a rendered `command` template (reuse `recipe-params.ts`; block-list shape only). + +### Step 4 — `auto_fixable` gating (S) + +Enforce `actions[].auto_fixable: true` (or `--force`) before write. `--force` bypasses gate only, not phase-1 conflict detection. + +### Step 6 — App-wide rename recipe (S; depends Step 5 substrate ✓) + +Extend `rename-preview.sql` with `call_rows` CTE. Bias: extend existing recipe id, not a second rename recipe. Preserve honest v1 gap list (re-exports, JSX, default-import binds). + +### Step 7 (recipe) — `rename-preview` `re_export_rows` CTE (S) + +Substrate shipped; **recipe extension open**. Single-hop alias chains in v1; recursive CTE if gap appears. + +### Step 8 — `apply --rows -` + `apply_rows` MCP/HTTP tool (M) + +JSON array of `{file_path, line_start, before_pattern, after_pattern}` from stdin/args; existing phase-1/2 validation. Separate MCP tool (not polymorphic with recipe id). + +### Step 9 — `apply --diff-input ` (S) + +Unified diff → row contract → same engine. Hand-roll parser; per-file atomicity; cross-file rollback deferred. + +### Step 10 — `apply --commit ""` (S) + +`git add` only files apply touched, then commit. Message templating deferred. + +### Step 11 — `--until-empty` + `--max-passes N` (S) + +Apply → reindex → re-run recipe loop. Abort whole loop on phase-1 conflicts. Extend result envelope with `passes` + `terminated_by`. + +### Step 12 — `apply.autoApplyRecipes` allowlist (S) + +Config list of recipe ids allowed with `--yes` without interactive confirm. `--force` bypasses allowlist. + +--- + +## Preserved moats (apply path) + +- No `severity` on apply rows — recipes propose; codemap executes. +- No suppression-by-default — existing `codemap-ignore-*` substrate only. +- No JS execution at apply time — rows are static data; never `eval`. +- No codemod-tool ambition — SQL API + substrate executor; AST work stays Path B / external tools. +- No telemetry upload — reliability metrics stay local/opt-in per [`roadmap.md § Floors`](../roadmap.md#floors-v1-product-shape). + +The "no fix engine" floor was about **product class** (no ESLint-style verdict engine), not forbidding a **substrate-shaped** apply executor. + +--- + +## Rejected items (grep-able; revisit only on trigger) + +| Item | Why rejected | Revisit when | +| ------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------- | +| Curated CLI write verbs (`codemap rename`, …) | Moat A — premature verb sprawl | ≥3 diff-shape recipes + clear agent-host demand beyond `actions[].command` | +| Parallel AST apply engine (Path A) | Competes with ts-morph/jscodeshift; maintenance burden | ≥2 external teams hit substring wall + concrete AST-shape demand | +| Trust tiers (`safe`/`review`/`risky`) | Taxonomy debt; `auto_fixable` + allowlist suffice | Allowlist insufficient + ≥2 consumers ship trust filters in CI | +| Per-row confidence scores | No consensus on computation | Recipe needs per-site ranking when `before_pattern` is ambiguous | +| Verifier as product surface (typecheck/lint gate) | Consumer CI owns orchestration | Consumer plan with concrete examples | +| Reliability loop telemetry | No upload floor | Self-hosted observability request | +| `--branch` / `--output-patch` flags | `--commit` priority | `--commit` insufficient in practice | +| Multi-line kind-tagged row contract | After single-line path stable | Multi-line edits required and workarounds fail | +| Cross-file moves in one apply | Higher risk | Alternative two-step ops insufficient | +| Cross-file atomic apply (50+ files) | Per-file atomicity sufficient today | Real 50+ file apply + partial failure leak | + +Full trigger wording: [`research/codemap-richer-index-synthesis-2026-05.md` § 7](../research/codemap-richer-index-synthesis-2026-05.md#7-rejected-items-with-trigger-conditions). + +--- + +## Cross-references + +- [`substrate-extraction.md`](./substrate-extraction.md) — shipped + open AST→SQLite tiers +- [`research/codemap-richer-index-synthesis-2026-05.md`](../research/codemap-richer-index-synthesis-2026-05.md) — full consensus / disagreement matrices (archive until all steps close) +- `src/application/apply-engine.ts` — current apply substrate +- [`docs/README.md` Rule 3](../README.md) — plan-file convention diff --git a/docs/plans/c9-plugin-layer.md b/docs/plans/c9-plugin-layer.md index 8a3f73c3..6719aa7f 100644 --- a/docs/plans/c9-plugin-layer.md +++ b/docs/plans/c9-plugin-layer.md @@ -1,24 +1,36 @@ # C.9 Framework plugin layer — plan -> **Status:** open · ships last in the cadence after § 1.5 / § 1.10 / § 1.9 / § 1.6 — per [`research/non-goals-reassessment-2026-05.md § 5`](../research/non-goals-reassessment-2026-05.md#5-pick-order-rationale-historical) Rationale 4 (orthogonality of small picks). Closed-dead-subgraph caveat motivating this plan: [`research note § 2.3`](../research/non-goals-reassessment-2026-05.md#23-no-static-analysis). +> **Status:** open · ships last in the impact-vs-cadence sequence after boundary / parametrised-recipe / recency / type-member picks — orthogonal to those items (none query reachability). **Closed-dead-subgraph caveat:** N-file packs with sibling imports but no real entry point; `fan-in` / `fan-out` rank hotspots, they do not detect unreachable files. > > **Motivator:** **closed-dead-subgraph case** — N-file packs where every file imports a sibling (non-zero `dependencies` fan-in for all) but none is reachable from a real entry point. Today's `untested-and-dead` recipe false-positives Next.js `app/**/page.tsx` files for the same reason: framework entry points aren't recognized as live without per-framework awareness. This plan proposes the smallest plugin contract that closes the gap. > -> **Tier:** XL effort (per the research note's § 5 (b) row). Shipping cadence is sequential after (a) + (c); this plan iterates in parallel so impl is unblocked when its slot arrives. +> **Tier:** XL effort. Ships last in the impact-vs-cadence sequence — parallel iteration unblocks impl before the slot opens. --- -## Pre-locked decisions (from non-goals-reassessment grill) +## Shipping cadence (decisions of record) + +C.9 ships **last** because it only sharpens **reachability-predicate** recipes: `untested-and-dead`, `unimported-exports`, and (Slice 2) `dead-files-by-reachability`. Boundary, parametrised-recipe, recency, type-member, complexity, hotspot, call, marker, and CSS recipes do not ask "is this file/symbol live?" — they do not inherit C.9's false-positive class. + +**Orthogonal picks ship first:** boundary violations (own `boundary_rules` table), rename-preview / parametrised recipes (query `calls`, not reachability), recipe-recency (own table), unused type members (`type_members` × import specifiers). + +**C.9 before LSP diagnostic-push:** entry-point hints reduce false-positive squigglies on framework files for the two live-predicate recipes. Not a hard block — [`lsp-diagnostic-push.md`](./lsp-diagnostic-push.md) can ship without C.9; diagnostics carry the same caveats those recipes document today. + +**If this plan is abandoned:** close as `Status: Rejected (YYYY-MM-DD) — `. Design surface captured either way. The two live-predicate recipes keep framework-file caveats permanently. + +--- + +## Pre-locked decisions These are committed to v1. Questions opened against them must justify against the linked decisions. -| # | Decision | Source | -| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | -| L.1 | **Entry-point hints only** (Shape A — `glob → is_entry: true` annotations on `files`). No arbitrary `dependencies` edge injection. | Q4 resolved (lifted); [§ 2.3 caveat](../research/non-goals-reassessment-2026-05.md#23-no-static-analysis) | -| L.2 | **Static config only** — plugins describe rules in static config (globs, glob → annotation mappings). No JS evaluation at index time. | [Floor "No JS execution at index time"](../roadmap.md#floors-v1-product-shape) | -| L.3 | **Moat-A clean** — recipes consume the new substrate via SQL; no new verdict-shaped CLI verbs. | [Moat A](../roadmap.md#moats-load-bearing) | -| L.4 | **Moat-B aligned** — `is_entry` annotation IS substrate growth (richer extracted structure on `files`). New schema column or table. | [Moat B](../roadmap.md#moats-load-bearing) | -| L.5 | **No edge injection in v1** — defer to v2 if a real recipe demands it; backwards-compat preserved (additive). Mirrors `query_baselines` deferral discipline. | Q4 resolved (lifted) | +| # | Decision | Source | +| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | +| L.1 | **Entry-point hints only** (Shape A — `glob → is_entry: true` annotations on `files`). No arbitrary `dependencies` edge injection. | This plan § Motivator; § What C.9 sharpens | +| L.2 | **Static config only** — plugins describe rules in static config (globs, glob → annotation mappings). No JS evaluation at index time. | [Floor "No JS execution at index time"](../roadmap.md#floors-v1-product-shape) | +| L.3 | **Moat-A clean** — recipes consume the new substrate via SQL; no new verdict-shaped CLI verbs. | [Moat A](../roadmap.md#moats-load-bearing) | +| L.4 | **Moat-B aligned** — `is_entry` annotation IS substrate growth (richer extracted structure on `files`). New schema column or table. | [Moat B](../roadmap.md#moats-load-bearing) | +| L.5 | **No edge injection in v1** — defer to v2 if a real recipe demands it; backwards-compat preserved (additive). Mirrors `query_baselines` deferral discipline. | Q4 resolved (lifted) | --- @@ -52,7 +64,7 @@ No CLI changes. No new verb. Recipes consume the new substrate. ## What C.9 sharpens — and what it doesn't -Per the [research note § 5 errata (2026-05)](../research/non-goals-reassessment-2026-05.md#8-triangulation-errata-2026-05): the original framing claimed C.9 "sharpens every shipped recipe" and that the LSP shim (§ 2.5 / item (d)) blocks on C.9's entry-point awareness. Both were wrong. (d) was reframed across three revisions (v1 "thin shim, agent UX" → v2 "orthogonal, ship before (b)" → v2.5 "dropped" → v3 "diagnostic-push server + VSCode extension, ships after (b)"); see same errata for the full evolution. Accurate scope: +Per the 2026-05 errata pass: the original framing claimed C.9 "sharpens every shipped recipe" and that the LSP plan blocked on C.9's entry-point awareness. Both were wrong. (d) was reframed across three revisions (v1 "thin shim, agent UX" → v2 "orthogonal, ship before (b)" → v2.5 "dropped" → v3 "diagnostic-push server + VSCode extension, ships after (b)"). Accurate scope: **C.9 sharpens (recipe layer):** @@ -69,7 +81,7 @@ Per the [research note § 5 errata (2026-05)](../research/non-goals-reassessment - Call recipes (`calls`) — query who-calls-what; orthogonal. - Marker / CSS recipes — orthogonal substrates. -This narrowing is why the research-note ship sequence pushes (b) C.9 last: every other planned item ships against substrate that doesn't depend on C.9. +This narrowing is why C.9 ships last: every other planned item in the cadence does not depend on entry-point hints. --- @@ -111,7 +123,6 @@ Per [`tracer-bullets`](../../.agents/rules/tracer-bullets.md) — ship one verti ## Cross-references -- [`docs/research/non-goals-reassessment-2026-05.md § 2.3`](../research/non-goals-reassessment-2026-05.md#23-no-static-analysis) — closed-dead-subgraph caveat (analytical history). Moats lifted to [`roadmap.md § Non-goals (v1) → Moats`](../roadmap.md#moats-load-bearing); pick-order rationale (especially the (b) before (d) reasoning) at [`§ 5`](../research/non-goals-reassessment-2026-05.md#5-pick-order-rationale-historical). - [`docs/architecture.md`](../architecture.md) — schema reference (where `is_entry` lands) - [`docs/golden-queries.md`](../golden-queries.md) — golden-query test pattern - [`docs/roadmap.md § Strategy`](../roadmap.md#strategy) — community-adapter precedent (Q2 / Q8 use the same lever) diff --git a/docs/plans/github-marketplace-action.md b/docs/plans/github-marketplace-action.md index 9a36d78c..e267f6af 100644 --- a/docs/plans/github-marketplace-action.md +++ b/docs/plans/github-marketplace-action.md @@ -314,7 +314,6 @@ Slices 1-4 are in-tree; Slice 5 is a sequenced manual runbook that requires a me ## Cross-references -- [`docs/research/non-goals-reassessment-2026-05.md § 5`](../research/non-goals-reassessment-2026-05.md#5-pick-order-rationale-historical) — pick-order rationale + the 2026-05 impact-vs-cadence amendment that surfaced this pick. - [`docs/roadmap.md § Backlog`](../roadmap.md#backlog) — backlog entry + audit-verdict trigger that this Action's adoption is likely to fire. - [`docs/plans/lsp-diagnostic-push.md`](./lsp-diagnostic-push.md) — sibling plan rendering same recipe substrate to IDE / VSCode surface; complementary, not competitive (see "Relationship to (d) LSP plan" section above). - [`docs/README.md` Rule 3](../README.md) — plan-file convention (this file's location). diff --git a/docs/plans/lsp-diagnostic-push.md b/docs/plans/lsp-diagnostic-push.md index fd606def..e621a5a0 100644 --- a/docs/plans/lsp-diagnostic-push.md +++ b/docs/plans/lsp-diagnostic-push.md @@ -1,28 +1,54 @@ # (d) LSP diagnostic-push server + paired VSCode extension — plan -> **Status:** open · plan iterating in parallel with (b) C.9 + every shipping-cadence item before (d). Pick-order rationale (the (d) v1 → v2 → v3 three-revisions arc) at [`research/non-goals-reassessment-2026-05.md § 5`](../research/non-goals-reassessment-2026-05.md#5-pick-order-rationale-historical). +> **Status:** open · ships after [`c9-plugin-layer.md`](./c9-plugin-layer.md) (C.9) in the impact-vs-cadence sequence — not a hard block; diagnostics on existing recipes work without entry-point hints, but landing C.9 first reduces false positives on framework files. > > **Motivator:** humans-in-IDEs surface for codemap's recipe outputs. Diagnostics-as-squigglies for `untested-and-dead`, `components-touching-deprecated`, `boundary-violations`, `unimported-exports`, `high-complexity-untested`, `deprecated-symbols` callers. Hover provider over `symbols.signature` + `doc_comment` + `complexity` + caller-count fills a gap `tsserver` doesn't (codemap-unique metadata, not types). Code lens for fan-in / complexity / coverage. Code actions hooked to `recipe.actions` template. > -> **Tier:** XL effort (per the research note's § 5 (d) row). Two paired components: `codemap-lsp` server + `codemap-vscode` extension. Server alone is incomplete — extension is required to consume the custom `codemap/analysisComplete` notification + render status bar / tree views. +> **Tier:** XL effort. Two paired components: `codemap-lsp` server + `codemap-vscode` extension. Server alone is incomplete — extension is required to consume the custom `codemap/analysisComplete` notification + render status bar / tree views. > > **Implementation libraries:** [`vscode-languageserver`](https://github.com/microsoft/vscode-languageserver-node) for the server side; [`vscode-languageclient`](https://github.com/microsoft/vscode-languageserver-node) for the paired VSCode extension. Both Microsoft-official LSP libraries on top of which the codemap-specific diagnostic-push behavior is built. --- -## Pre-locked decisions (from non-goals-reassessment grill 2026-05) +## Pre-locked decisions These are committed to v1. Questions opened against them must justify against the linked decisions. -| # | Decision | Source | -| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| L.1 | **Diagnostic-push shape, NOT request-handler shape.** No `textDocument/definition` / `references` / `hover types` / `workspace/symbol`. `tsserver` dominates those for JS/TS users; competing is wasted surface. | [§ 2.5 v3 verdict](../research/non-goals-reassessment-2026-05.md#25-no-lsp-replacement); [§ 8 errata row](../research/non-goals-reassessment-2026-05.md#8-triangulation-errata-2026-05) | -| L.2 | **Moat-A discipline:** every diagnostic must be the `--format lsp-diagnostic` rendering of a bundled recipe. Reviewer test: "is this finding queryable via `query --recipe X`?" If no recipe drives the diagnostic, it's rejected. | [Moat A](../roadmap.md#moats-load-bearing) | -| L.3 | **Moat-B aligned:** server consumes shipped engines (`application/show-engine.ts`, `application/impact-engine.ts`, `application/watcher.ts`); does NOT re-extract structure inside the protocol layer. | [Moat B](../roadmap.md#moats-load-bearing); [§ 2.5 "no LSP engine"](../research/non-goals-reassessment-2026-05.md#25-no-lsp-replacement) | -| L.4 | **Bun/Node binary, not Rust.** Keep the toolchain consistent; reuse engines via direct imports. | Operational — repo is TS | -| L.5 | **Server + extension is the unit, not either alone.** Custom `codemap/analysisComplete` notification requires the paired extension to render status bar / tree views; LSP-only consumption (no extension) is reduced UX, not v1. | [§ 2.5 v3 verdict](../research/non-goals-reassessment-2026-05.md#25-no-lsp-replacement) | -| L.6 | **No JS execution at index time** survives — server speaks LSP, doesn't `eval` recipe SQL or extension-injected code. | [Floors "No JS execution at index time"](../roadmap.md#floors-v1-product-shape) | -| L.7 | **Ships AFTER (b) C.9** per cadence. Not a hard block — (d) ships valid diagnostics on existing recipe outputs without (b); but landing (b) first means `untested-and-dead` / `unimported-exports` diagnostics inherit cleaner inputs from day one. | [§ 5 Rationale 5](../research/non-goals-reassessment-2026-05.md#5-pick-order-rationale-historical) | +| # | Decision | Source | +| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| L.1 | **Diagnostic-push shape, NOT request-handler shape.** No `textDocument/definition` / `references` / `hover types` / `workspace/symbol`. `tsserver` dominates those for JS/TS users; competing is wasted surface. | This plan § Shape; [`roadmap.md § Floors`](../roadmap.md#floors-v1-product-shape) (no LSP engine) | +| L.2 | **Moat-A discipline:** every diagnostic must be the `--format lsp-diagnostic` rendering of a bundled recipe. Reviewer test: "is this finding queryable via `query --recipe X`?" If no recipe drives the diagnostic, it's rejected. | [Moat A](../roadmap.md#moats-load-bearing) | +| L.3 | **Moat-B aligned:** server consumes shipped engines (`application/show-engine.ts`, `application/impact-engine.ts`, `application/watcher.ts`); does NOT re-extract structure inside the protocol layer. | [Moat B](../roadmap.md#moats-load-bearing) | +| L.4 | **Bun/Node binary, not Rust.** Keep the toolchain consistent; reuse engines via direct imports. | Operational — repo is TS | +| L.5 | **Server + extension is the unit, not either alone.** Custom `codemap/analysisComplete` notification requires the paired extension to render status bar / tree views; LSP-only consumption (no extension) is reduced UX, not v1. | This plan § Motivator | +| L.6 | **No JS execution at index time** survives — server speaks LSP, doesn't `eval` recipe SQL or extension-injected code. | [Floors "No JS execution at index time"](../roadmap.md#floors-v1-product-shape) | +| L.7 | **Ships AFTER C.9** per cadence. Not a hard block — (d) ships valid diagnostics on existing recipe outputs without (b); but landing (b) first means `untested-and-dead` / `unimported-exports` diagnostics inherit cleaner inputs from day one. | [`c9-plugin-layer.md`](./c9-plugin-layer.md) § Shipping cadence; § What C.9 sharpens | + +--- + +## Consumer-shape audit (decisions of record) + +- **Agents use MCP, not LSP** — a request-handler shim wrapping `show` / `impact` for agent UX was a dead end. +- **`tsserver` dominates** standard pull requests (`textDocument/definition`, `references`, `hover types`, `workspace/symbol`) for JS/TS. +- **Valuable shape:** diagnostic-push — `diagnostic` (LSP 3.17 pull), `code_action`, `code_lens`, `hover`, custom `codemap/analysisComplete` — plus a paired VSCode extension. Recipes render as `Diagnostic[]` (squigglies), not go-to-def. +- **Moat-A discipline:** diagnostics are `--format lsp-diagnostic` renderings of bundled recipes; reviewer test: "is this finding queryable via `query --recipe X`?" + +## Shape evolution (v1 → v3) + +| Rev | Proposal | Outcome | +| ------ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| v1 | Thin shim over show/impact engines; ship before C.9 | Rejected — assumed C.9 `is_entry` rides on LSP `Location[]` responses | +| v2 | Orthogonal to C.9; ship before C.9 | Correctly rejected request-handler shape; wrongly dropped diagnostic-push entirely | +| v2.5 | Defer until concrete consumer | Incomplete — conflated rejected shape with high-value diagnostic-push | +| **v3** | Diagnostic-push server + VSCode extension; ships after C.9 | **Current plan** — server + extension is one unit (L.5) | + +## v1 protocol surface + +**Implement:** `initialize`, `did_open`, `did_change`, `did_save`, `diagnostic` (pull), `code_action`, `code_lens`, `hover`, custom `codemap/analysisComplete`. + +**Explicitly skip:** `definition`, `references`, `documentSymbol`, `workspace/symbol` — `tsserver` covers these. + +Diagnostic codes map 1:1 to recipe ids from `--recipes-json`. Paired extension: tree views, status bar via custom notification, settings for issue toggles + git ref scope. --- @@ -338,8 +364,7 @@ Bias: **locked**. Relevant to Q1 — monorepo workspaces makes locked versioning ## Cross-references -- [`docs/research/non-goals-reassessment-2026-05.md`](../research/non-goals-reassessment-2026-05.md) — research foundation: [§ 2.5 v3 verdict](../research/non-goals-reassessment-2026-05.md#25-no-lsp-replacement), [§ 5 pick-order rationale (the (d) v1 → v2 → v3 arc)](../research/non-goals-reassessment-2026-05.md#5-pick-order-rationale-historical), [§ 8 errata](../research/non-goals-reassessment-2026-05.md#8-triangulation-errata-2026-05). Moats lifted to [`roadmap.md § Non-goals (v1)`](../roadmap.md#non-goals-v1). -- [`docs/plans/c9-plugin-layer.md`](./c9-plugin-layer.md) — C.9 plan (lands before (d); sharpens (d)'s diagnostic precision via entry-point awareness) +- [`docs/plans/c9-plugin-layer.md`](./c9-plugin-layer.md) — C.9 plan (lands before this plan; sharpens diagnostic precision via entry-point awareness) - [`docs/architecture.md`](../architecture.md) — engine reference (`application/show-engine.ts`, `application/impact-engine.ts`, `application/watcher.ts`) - [`docs/golden-queries.md`](../golden-queries.md) — golden-query test pattern (LSP fixture follows the same shape) - [`docs/README.md` Rule 3](../README.md) — plan-file convention (this file's location) diff --git a/docs/plans/substrate-extraction.md b/docs/plans/substrate-extraction.md index cbccf869..ce4588d4 100644 --- a/docs/plans/substrate-extraction.md +++ b/docs/plans/substrate-extraction.md @@ -1,6 +1,6 @@ # Substrate extraction — maximal AST → SQLite enrichment plan -> **Status:** open (tiers **7–13**) · plan iterating in parallel with the broader [`research/codemap-richer-index-synthesis-2026-05.md`](../research/codemap-richer-index-synthesis-2026-05.md) write-engine direction. +> **Status:** open (tiers **7–13**) · tiers **1–6** shipped (`SCHEMA_VERSION` **34**); live schema in [`architecture.md § Schema`](../architecture.md#schema). Apply-engine direction in [`apply-engine-direction.md`](./apply-engine-direction.md). > > **Per-tier ship status (fact-checked 2026-05-19):** Tiers **1–6** remainder shipped (`SCHEMA_VERSION` **34**). Tier headings carry the PR landing date for that slice; the SCHEMA 34 remainder wave closed **2026-05-19** (tiers 1–6 foundation landed **2026-05-14**–**15**). Tier **1**: call-shape columns, side-effect `import_specifiers` + `import_id`. Tier **2**: `bindings.resolution_kind='re-exported'`. Tier **3**: `jsx_elements` / `jsx_attributes`. Tier **5**: `async_calls`, `try_catch`, `decorators`, `jsdoc_tags`. Tier **4** partial: `symbols.{return_type,is_async,is_generator}`; `generic_params` / `type_predicates` deferred. Tier **6** partial: `dynamic_imports`, `files.{is_barrel,has_side_effects}`; `files.is_entry` deferred to [`c9-plugin-layer.md`](./c9-plugin-layer.md). Tiers **7–13** open. > diff --git a/docs/research/codemap-richer-index-synthesis-2026-05.md b/docs/research/codemap-richer-index-synthesis-2026-05.md index 09a57b83..6a1df383 100644 --- a/docs/research/codemap-richer-index-synthesis-2026-05.md +++ b/docs/research/codemap-richer-index-synthesis-2026-05.md @@ -1,10 +1,8 @@ # Codemap richer-index — consolidated triangulation across 5 model perspectives — 2026-05 -> **Status:** Open research note · consolidated 2026-05-06 by `claude-opus-4.7` from five independent strategic positions on the same prompt: "fully unlock codemap's potential and bypass the 'no fix engine' premise." The five sources have been merged into this single document; their unique substrate proposals, recipe candidates, engine extensions, argumentation chains, and reference views are inlined below with author attribution preserved. +> **Status:** Open research archive — **canonical open work** for apply-engine steps lives in [`plans/apply-engine-direction.md`](../plans/apply-engine-direction.md); substrate tiers in [`plans/substrate-extraction.md`](../plans/substrate-extraction.md). This note retains consensus / disagreement matrices and per-source argumentation until all synthesis steps close. > -> **Purpose:** Single canonical strategy doc for the codemap-richer-index direction. Captures (a) the consensus all sources reached, (b) the disagreements with per-axis verdicts and full argumentation, (c) the consolidated substrate / recipe / engine / workflow catalogs, (d) the synthesised path with open implementation questions per step, (e) the reference views (leverage-ranked, agent-angle, safety-loop, architecture diagram) that serve as design context, (f) the rejected items with trigger conditions, (g) preserved moats. Designed to stand alone without the source notes. -> -> **Lifecycle:** Open. Per [`docs-governance § Closing research`](../../.agents/skills/docs-governance/SKILL.md#closing-research). When any item lifts to a plan PR, slim the corresponding section here to a one-line "What shipped" appendix per [`docs/README.md` Rule 8](../README.md). When all items resolve, retire per the same rule. +> **Purpose:** Triangulation record (five model perspectives, May 2026). Do not treat this file as the implementation checklist — use the plans above. > > **Authoring discipline:** Per [`agents-tier-system`](../../.agents/rules/agents-tier-system.md) durability rules — no source-line citations, no specific commit hashes; symbol references and design intent only. Per [`docs-governance § 7`](../../.agents/skills/docs-governance/SKILL.md#7-cross-reference-preservation-discipline) all cross-references in this doc point at durable reference docs (`architecture.md`, `glossary.md`, `roadmap.md`) and rule numbers, not at mortal source notes (which were merged into this doc). @@ -12,7 +10,7 @@ ## What shipped (fact-checked 2026-05-19) — appendix per § 9 lifecycle -The substrate-growth half of this synthesis lifted to a dedicated plan PR — [`plans/substrate-extraction.md`](../plans/substrate-extraction.md) — which generalised § 4.1 / § 4.2 / § 5.3 items 1, 3 into a 13-tier sequenced plan. Per § 9's discipline ("when the synthesis path ships any step, add a 'What shipped' appendix; slim duplicated prose"), the canonical live status is **the substrate plan's per-tier headings**, not this note. +The substrate-growth half lifted to [`plans/substrate-extraction.md`](../plans/substrate-extraction.md). The apply-engine half lifted to [`plans/apply-engine-direction.md`](../plans/apply-engine-direction.md). **Live status = those plan headings**, not this note. **Shipped via the substrate plan:** @@ -703,8 +701,8 @@ The "no fix engine" line was right about the **product class**; it was wrong abo Per [`docs-governance § Closing research`](../../.agents/skills/docs-governance/SKILL.md#closing-research): -- **When the synthesis path ships any step:** add a one-line "What shipped" appendix under that step in [§ 6](#6-synthesised-12-step-path-with-open-implementation-questions) linking to the lift destination (`architecture.md` / `glossary.md` / `roadmap.md`); slim duplicated prose. -- **When all 12 steps ship or close:** retire this note per [`docs/README.md` Rule 8](../README.md). Lift any durable strategic claim (Path A vs B framing; agent-in-the-loop substrate positioning) into [`why-codemap.md`](../why-codemap.md) or [`research/non-goals-reassessment-2026-05.md`](./non-goals-reassessment-2026-05.md) before deletion. +- **Open implementation** → [`plans/apply-engine-direction.md`](../plans/apply-engine-direction.md) + [`plans/substrate-extraction.md`](../plans/substrate-extraction.md). Update those plans on ship; do not add inventory appendices here. +- **When all 12 steps ship or close:** retire this note per [`docs/README.md` Rule 8](../README.md). Lift any remaining durable strategic claim into [`why-codemap.md`](../why-codemap.md) before deletion. - **When a deferred / rejected item triggers** (per § 7 trigger conditions): open a plan PR with this note as the rationale anchor; this synthesis is the disagreement record that justifies the trigger. - **Annual re-evaluation** — re-run the triangulation against the substrate / recipe count of the day; update verdicts with current data. @@ -734,7 +732,7 @@ Per [`docs-governance § Closing research`](../../.agents/skills/docs-governance - [`architecture.md` Apply wiring](../architecture.md#cli-usage) — the engine the synthesis path grows the substrate around - [`glossary.md` Substrate-shaped fix executor](../glossary.md) — current canonical definition (preserved) - [`why-codemap.md` § When to reach for something else](../why-codemap.md#when-to-reach-for-something-else) — verdict/autofix vs predicate-as-API (Step 1 shipped) -- [`research/non-goals-reassessment-2026-05.md`](./non-goals-reassessment-2026-05.md) — precedent for floor-flips after architectural reality outpaces docs +- [`roadmap.md § Moats`](../roadmap.md#moats-load-bearing) — floor-flip decisions of record (non-goals reassessment lifted here) - [`docs/plans/c9-plugin-layer.md`](../plans/c9-plugin-layer.md) — C.9 entry-point work; orthogonal to synthesis path - [`docs/plans/lsp-diagnostic-push.md`](../plans/lsp-diagnostic-push.md) — sibling plan; Steps 8–9 (agent-in-the-loop) and the LSP `code_action` shape converge naturally diff --git a/docs/research/non-goals-reassessment-2026-05.md b/docs/research/non-goals-reassessment-2026-05.md index 510d93ce..c36ff2f1 100644 --- a/docs/research/non-goals-reassessment-2026-05.md +++ b/docs/research/non-goals-reassessment-2026-05.md @@ -1,6 +1,6 @@ # Non-goals reassessment — what _this_ codebase actually unlocks (2026-05) -> **Status:** lifted (2026-05-04) — durable decisions in [`roadmap.md § Non-goals (v1)`](../roadmap.md#non-goals-v1); pending picks tracked in [`roadmap.md § Backlog`](../roadmap.md#backlog) or dedicated plan PRs ([`c9-plugin-layer.md`](../plans/c9-plugin-layer.md), [`lsp-diagnostic-push.md`](../plans/lsp-diagnostic-push.md)). This note retains analytical history (§ 2 reasoning, § 5 pick-order rationale, § 8 errata). +> **Status:** decisions-of-record archive (Tier B). Shipped work → canonical homes in [§ 7](#7-lifted-to). Open implementation → [`../plans/c9-plugin-layer.md`](../plans/c9-plugin-layer.md), [`../plans/lsp-diagnostic-push.md`](../plans/lsp-diagnostic-push.md), [`../plans/apply-engine-direction.md`](../plans/apply-engine-direction.md). **§ 5 pick-order + § 8 errata** retain rejected-alternative rationale; cited by those plans. > > **Original positioning:** codemap occupies a specific niche in the **SQLite-backed-code-index cohort** for AI agents (peers: `srclight`, `Sverklo`, `ctxpp`, `KotaDB`, `codemogger`, `@squirrelsoft/code-index`, `QuickAST`, etc. — most use tree-sitter + SQLite + embeddings). Codemap's differentiation: **(1) predicate-as-API** (raw SQL + recipes) — peers ship pre-baked verbs; **(2) pure structural** — no embeddings, no LLM in box; **(3) JS/TS/CSS-ecosystem-deep extraction** via `oxc` + `lightningcss`. Mission: extract maximum value from the predicate-as-API thesis on this niche; grow the ecosystem. > @@ -152,6 +152,7 @@ The lift trail — for future archaeologists asking "where did this idea / decis | § 2.1–2.4 verdicts | [`roadmap.md § Non-goals (v1) → Floors`](../roadmap.md#floors-v1-product-shape) | docs-capability-sync Slice 3 | | § 2.5 / § 5 (b) C.9 framework plugin layer | [`docs/plans/c9-plugin-layer.md`](../plans/c9-plugin-layer.md) | already open | | § 2.5 / § 5 (d) LSP diagnostic-push + VSCode extension | [`docs/plans/lsp-diagnostic-push.md`](../plans/lsp-diagnostic-push.md) | already open | +| Apply-engine synthesis Steps 2–12 | [`docs/plans/apply-engine-direction.md`](../plans/apply-engine-direction.md) | lifted 2026-05 | | § 3 moats A + B + ergonomic floors | [`roadmap.md § Non-goals (v1) → Moats`](../roadmap.md#moats-load-bearing) + [`Floors`](../roadmap.md#floors-v1-product-shape) | docs-capability-sync Slice 3 | | § 4 inspiration sources catalogue | [`.agents/rules/plan-pr-inspiration-discipline.md`](../../.agents/rules/plan-pr-inspiration-discipline.md) Top inspiration sources table | docs-capability-sync Slice 5a | | § 6 Q1 daemon-default flip (shipped) | `mcp` / `serve` watcher default-ON; `--no-watch` / `CODEMAP_WATCH=0` opt-outs; `architecture.md` § Watch wiring + `glossary.md` | commit `31479a5` | From eb2f39f4c8ce8a8747580b7b1aec9ed9a67ba082 Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:37:08 +0300 Subject: [PATCH 10/13] =?UTF-8?q?docs:=20slim=20non-goals=20=C2=A71,=20add?= =?UTF-8?q?=20cohort=20positioning=20to=20why-codemap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collapse redundant shipped inventory into §7 lift table; add explicit backlog rows for open plans + trigger-gated ideas; embed cohort positioning in why-codemap (no tracker file). --- .../non-goals-reassessment-2026-05.md | 33 +++++++------------ docs/roadmap.md | 5 +++ docs/why-codemap.md | 23 +++++++++++++ 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/docs/research/non-goals-reassessment-2026-05.md b/docs/research/non-goals-reassessment-2026-05.md index c36ff2f1..8346b257 100644 --- a/docs/research/non-goals-reassessment-2026-05.md +++ b/docs/research/non-goals-reassessment-2026-05.md @@ -20,24 +20,11 @@ The right framing now: **what does the SQL-index-with-three-transports + worker- --- -## 1. Shipped since this inventory (appendix) +## 1. Shipped since this inventory -The original capability inventory contained 10 rows. Items that have shipped since are listed below; pending picks moved to canonical homes per [§ 7](#7-lifted-to). +The original capability inventory had 10 rows. Shipped picks — lift trail, PR/commit anchors, and canonical homes — are in [§ 7](#7-lifted-to). Implementation detail lives in those canonical homes (not duplicated here). -| Item | What shipped | Canonical home | -| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1.3 Cyclomatic complexity per symbol | `complexity` column on `symbols`; `high-complexity-untested.sql` recipe | [`architecture.md § Schema`](../architecture.md#schema), `templates/recipes/high-complexity-untested.{sql,md}` | -| 1.7 Mermaid output | `--format mermaid` on edge-shaped recipes (`impact`, `dependencies`); bounded-input contract (≤ 50 edges) | [`README.md § CLI`](../../README.md#cli) | -| 1.8 More MCP resources | `codemap://files/{path}` + `codemap://symbols/{name}` (live read-per-call); reuses `{matches, disambiguation?}` envelope | [`templates/agents/skills/codemap/SKILL.md`](../../templates/agents/skills/codemap/SKILL.md), [`docs/glossary.md § MCP server`](../glossary.md) | -| 2.1 Opt-in FTS5 | `--with-fts` CLI flag / `fts5: true` config; `source_fts` virtual table at index time; `text-in-deprecated-functions.sql` JOIN demo | [`roadmap.md § Non-goals (v1) → Floors`](../roadmap.md#floors-v1-product-shape), [`README.md § CLI`](../../README.md#cli) | -| 2.2 Visualisation flip → output formatter | `--format mermaid` (above); SARIF + annotations were the precedent | Same | -| 1.10 rename-preview + parametrised recipes | `params:` frontmatter + `--params key=value` CLI / `query_recipe.params` MCP/HTTP; `find-symbol-by-kind.sql` exemplar; `rename-preview.sql` recipe; `--format diff` / `diff-json` formatters | [`README.md § CLI`](../../README.md#cli), `templates/recipes/find-symbol-by-kind.{sql,md}`, `templates/recipes/rename-preview.{sql,md}` (PR #71) | -| § 6 Q1 daemon-default flip | `mcp` / `serve` watcher default-ON; `--no-watch` and `CODEMAP_WATCH=0` opt-outs | [`architecture.md § Watch wiring`](../architecture.md#cli-usage), [`docs/glossary.md`](../glossary.md) | -| 1.5 Boundary violations | `boundaries` config + `boundary_rules` table + bundled `boundary-violations` recipe; SARIF auto-detects via `from_path` location column | [`architecture.md § Schema`](../architecture.md#schema), `templates/recipes/boundary-violations.{sql,md}` (PR #72) | -| 1.9 Recipe-recency tracking | `recipe_recency` table; `last_run_at` + `run_count` inline on `--recipes-json` + `codemap://recipes` resources; opt-out via `.codemap/config` `recipeRecency: false`; 90-day eager prune on write | [`architecture.md § recipe_recency`](../architecture.md#recipe_recency--per-recipe-last-run--run-count-user-data-strict-without-rowid), [`glossary.md`](../glossary.md) | -| 1.6 Unused type members | `type_members` table + bundled `unused-type-members.sql` advisory recipe | `templates/recipes/unused-type-members.{sql,md}` | - -Pending picks (§ 1.1, 1.2, 1.4, plus § 5 (b) and (d)) moved to canonical homes — see [§ 7](#7-lifted-to). +**Still open:** C.9 plugin layer, LSP diagnostic-push, apply-engine steps — [§ 7](#7-lifted-to) open rows + linked plans. --- @@ -142,13 +129,17 @@ The lift trail — for future archaeologists asking "where did this idea / decis | Original (this note's section) | Lifted to | Plan PR / commit | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | -| § 1.7 Mermaid output (shipped) | `templates/recipes/` + `README.md § CLI` | PR #59 (FTS5 + Mermaid) | -| § 1.3 cyclomatic complexity (shipped) | `architecture.md § Schema` + `templates/recipes/high-complexity-untested.{sql,md}` | PR #70 | +| § 1.1 components-touching-deprecated (shipped) | `templates/recipes/components-touching-deprecated.{sql,md}` — UNION hook + call paths for `@deprecated` touch | shipped | +| § 1.2 unimported-exports (shipped; S not XS) | `templates/recipes/unimported-exports.{sql,md}` — JOIN through `exports.re_export_source` for barrel false-positive avoidance | shipped | +| § 1.3 / § 1.4 cyclomatic complexity (shipped) | `complexity` on `symbols` + `templates/recipes/high-complexity-untested.{sql,md}` | PR #70 | +| § 1.5 boundary violations (shipped) | `templates/recipes/boundary-violations.{sql,md}` + `boundaries` config + `boundary_rules` table | PR #72 | +| § 1.6 unused type members (advisory; shipped) | `type_members` table + `templates/recipes/unused-type-members.{sql,md}` | shipped after docs-capability-sync | +| § 1.7 Mermaid output (shipped) | `--format mermaid` on edge-shaped recipes; ≤ 50 edges | PR #59 (FTS5 + Mermaid) | | § 1.8 MCP resources (shipped) | `templates/agents/skills/codemap/SKILL.md` + `docs/glossary.md` | post-PR #35 follow-ups | -| § 1.5 boundary violations (shipped) | `templates/recipes/boundary-violations.{sql,md}` + `boundaries` config field + `boundary_rules` table | PR #72 | -| § 1.10 rename-preview + parametrised recipes (shipped) | `templates/recipes/find-symbol-by-kind.{sql,md}` + `templates/recipes/rename-preview.{sql,md}` + `--format diff` / `diff-json` formatters | PR #71 | | § 1.9 recipe-recency (shipped) | [`architecture.md § recipe_recency`](../architecture.md#recipe_recency--per-recipe-last-run--run-count-user-data-strict-without-rowid) + [`glossary.md`](../glossary.md) | shipped | -| § 1.6 unused type members (advisory; shipped) | `type_members` table + `templates/recipes/unused-type-members.{sql,md}` | shipped after docs-capability-sync | +| § 1.10 rename-preview + parametrised recipes (shipped) | `templates/recipes/find-symbol-by-kind.{sql,md}` + `templates/recipes/rename-preview.{sql,md}` + `--format diff` / `diff-json` formatters | PR #71 | +| § 2.1 opt-in FTS5 (shipped) | `--with-fts` / `fts5: true` → `source_fts`; demo `text-in-deprecated-functions.sql` | PR #59 | +| § 2.2 visualisation → formatter (shipped) | Same as § 1.7; SARIF/annotations precedent | PR #59 | | § 2.1–2.4 verdicts | [`roadmap.md § Non-goals (v1) → Floors`](../roadmap.md#floors-v1-product-shape) | docs-capability-sync Slice 3 | | § 2.5 / § 5 (b) C.9 framework plugin layer | [`docs/plans/c9-plugin-layer.md`](../plans/c9-plugin-layer.md) | already open | | § 2.5 / § 5 (d) LSP diagnostic-push + VSCode extension | [`docs/plans/lsp-diagnostic-push.md`](../plans/lsp-diagnostic-push.md) | already open | diff --git a/docs/roadmap.md b/docs/roadmap.md index c1a03e63..fe3a37cf 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -52,6 +52,11 @@ Soft constraints — describe shipped reality. Decided-but-unshipped flips live ## Backlog +- [ ] **C.9 framework plugin layer** — static entry-point hints on `files` to sharpen reachability-predicate recipes (`untested-and-dead`, `unimported-exports`, future `dead-files-by-reachability`). Plan: [`plans/c9-plugin-layer.md`](./plans/c9-plugin-layer.md). Effort: XL; ships last in the impact-vs-cadence sequence (see plan § Shipping cadence). +- [ ] **LSP diagnostic-push + VSCode extension** — recipes-as-`Diagnostic[]` server + paired extension; explicitly **not** a go-to-def / references shim (`tsserver` covers those). Plan: [`plans/lsp-diagnostic-push.md`](./plans/lsp-diagnostic-push.md). Effort: XL; soft ordering after C.9 for cleaner squigglies on framework files. +- [ ] **Apply-engine direction** — diff-shape recipes, per-row `actions[].command`, `apply --rows` / `--diff-input` / fixpoint loop. Substrate tiers 1–6 shipped; open steps 2–12. Plan: [`plans/apply-engine-direction.md`](./plans/apply-engine-direction.md). +- [ ] **Test-impact / `affected` recipe** (trigger-gated) — codemap has the dep graph substrate. **Revisit when:** a consumer asks for CI test-selection from indexed deps + a documented test-file convention. Effort: M. +- [ ] **Framework route extraction** (trigger-gated) — Express / Next / React Router route nodes (peer tools extract these; codemap is TS/JS-deep but route-aware). **Revisit when:** C.9 plugin contract exists OR ≥2 consumers ask with concrete framework targets. Effort: High; likely extends C.9 plugin shape, not a standalone engine. - [ ] **`history` table** (deferred — revisit-triggered) — temporal queries: "when did symbol X get `@deprecated`?", "coverage trend over last 50 commits", "files that became dead this week". `audit --base ` covers the most-common temporal question (PR-scoped diff) without schema growth, so the table earns its place only when bigger questions emerge. Two shapes (per-commit snapshots ~N × DB size; append-only event log heavier CTE walks); both pay an N-reindexes backfill cost (~30s per reindex). **Revisit triggers:** two consumers ship `jq`-based "audit-runs-over-time" workflows, OR `query_baselines` evolution becomes a recurring agent need. - [ ] **`codemap audit` verdict + thresholds** (v1.x) — `verdict: "pass" | "warn" | "fail"` driven by an `audit.deltas[].{added_max, action}` field on the config object (`.codemap/config.{ts,js,json}`). Triggers: two consumers ship `jq`-based threshold scripts with similar shapes, OR one consumer asks with a concrete config sketch. Until then, raw deltas + consumer-side `jq` is the CI exit-code idiom. **Likely accelerant:** the Marketplace Action (next item) shipping is the most plausible path to firing the trigger — once `- uses: stainless-code/codemap@v1` is the dominant CI path, real `jq` threshold scripts will surface. - [ ] **GitHub Marketplace Action — publish + listing finish** — core Action implementation is in-tree: root `action.yml`, `query --ci`, `audit --format sarif` / `--ci`, package-manager detection, dogfood smoke, and opt-in `pr-comment` summary renderer have shipped. Remaining work is the release/listing slice: `MARKETPLACE.md`, `v1.0.0` / floating `v1` tags, Marketplace setup, sacrificial-repo smoke, and making `action-smoke` blocking once the Action tag exists. Action version stream is independent of CLI version (`package.json` currently drives CLI/npm version; Action publishes at its own `v1.0.0`). Plan: [`plans/github-marketplace-action.md`](./plans/github-marketplace-action.md). Effort: S. diff --git a/docs/why-codemap.md b/docs/why-codemap.md index 4c600e90..aa9e6c77 100644 --- a/docs/why-codemap.md +++ b/docs/why-codemap.md @@ -122,6 +122,29 @@ Other "AI-friendly code intelligence" tools occupy different points in the desig **Why this matters:** Codemap deliberately **doesn't try to be smart**. Other tools predict what context an agent will need; Codemap lets the agent decide and just makes each decision cheap. The same agent can use Codemap **and** a verdict-shaped linter **and** an LSP — they don't compete for the same slot. +### SQLite-backed code-index cohort + +Several tools pre-index repos for AI agents (tree-sitter or AST parsers → SQLite or graph DB → MCP/CLI). The cohort includes srclight, ctxpp, KotaDB, codemogger, `@squirrelsoft/code-index`, QuickAST, and others. None share all three codemap axes at once: + +| Axis | Codemap | Typical cohort peer | +| ---------------- | ----------------------------------------------- | -------------------------------------------------------------- | +| Query API | **SQL + recipes** (predicate-as-API) | Pre-baked MCP graph verbs or GraphRAG → Cypher | +| Semantic layer | **Pure structural** — no LLM in the box | Task-context bundling or NL Q&A over the index | +| Extraction depth | **JS/TS/CSS-ecosystem** via oxc + Lightning CSS | Broader language count; often shallower per-language substrate | +| CI substrate | SARIF, audit, baselines, GitHub Action | Usually absent | + +| Scenario | Reach for | +| --------------------------------------------------- | ------------------------------------------------------- | +| Compose a custom structural lint as SQL | **Codemap** | +| CI boundary enforcement + SARIF | **Codemap** | +| Coverage-aware export analysis | **Codemap** | +| Polyglot "who calls X" across many languages | Multi-language index tools (outside codemap's TS focus) | +| Express/Next route inventory from framework plugins | Framework-aware index tools; codemap backlog (C.9) | +| Explore codebase in a 3D graph UI | Graph visualization products (different lane) | +| Ask NL questions over the whole repo | Agent host + embeddings (not codemap) | + +Backlog items for test-impact selection and framework route extraction are trigger-gated in [roadmap § Backlog](./roadmap.md#backlog) — not committed work. + For more on what Codemap deliberately does **not** do, see [When to reach for something else](#when-to-reach-for-something-else) above and [docs/roadmap.md § Non-goals](./roadmap.md#non-goals-v1). ## Cost Summary From 337c2459b1a4441cf247d925fdca07394b2be8bb Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 09:40:49 +0300 Subject: [PATCH 11/13] =?UTF-8?q?chore:=20add=20patch=20changeset=20for=20?= =?UTF-8?q?R.1=E2=80=93R.3=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/fix-residuals-r1-r3.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-residuals-r1-r3.md diff --git a/.changeset/fix-residuals-r1-r3.md b/.changeset/fix-residuals-r1-r3.md new file mode 100644 index 00000000..1cdc2044 --- /dev/null +++ b/.changeset/fix-residuals-r1-r3.md @@ -0,0 +1,5 @@ +--- +"@stainless-code/codemap": patch +--- + +Honor `--state-dir` for project recipes; populate `function_params.owner_kind`; register MCP `files`/`symbols` resource templates with optional `?in=` filter; watcher fixes for custom state dirs. From 807d20f9381fd27cf2f01a205c856bb49048cffb Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 11:15:04 +0300 Subject: [PATCH 12/13] docs(plans): add agent surface and indexing ops work plans. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capture P0–P2 implementation plans for MCP ergonomics, indexing reliability, and TS/JS graph substrate gaps with a hub index linking priority tiers and dependencies. --- docs/plans/affected-tests-recipe.md | 70 +++++++++++++++ docs/plans/agent-eval-harness.md | 44 ++++++++++ docs/plans/agent-surface-and-ops.md | 87 +++++++++++++++++++ docs/plans/agents-init-mcp-wiring.md | 58 +++++++++++++ .../plans/call-path-type-hierarchy-recipes.md | 62 +++++++++++++ docs/plans/callback-dispatch-synthesis.md | 62 +++++++++++++ docs/plans/cross-project-mcp-root.md | 50 +++++++++++ docs/plans/field-qualified-search.md | 54 ++++++++++++ docs/plans/framework-route-extraction.md | 79 +++++++++++++++++ docs/plans/fts-default-on-evaluation.md | 56 ++++++++++++ docs/plans/git-hook-auto-sync.md | 47 ++++++++++ docs/plans/index-lock-and-error-log.md | 46 ++++++++++ docs/plans/mcp-server-instructions.md | 45 ++++++++++ docs/plans/mcp-tool-allowlist.md | 41 +++++++++ docs/plans/mcp-trace-explore-tools.md | 72 +++++++++++++++ docs/plans/parse-worker-hardening.md | 45 ++++++++++ docs/plans/unresolved-calls-staging.md | 62 +++++++++++++ docs/plans/wsl-watch-policy.md | 44 ++++++++++ 18 files changed, 1024 insertions(+) create mode 100644 docs/plans/affected-tests-recipe.md create mode 100644 docs/plans/agent-eval-harness.md create mode 100644 docs/plans/agent-surface-and-ops.md create mode 100644 docs/plans/agents-init-mcp-wiring.md create mode 100644 docs/plans/call-path-type-hierarchy-recipes.md create mode 100644 docs/plans/callback-dispatch-synthesis.md create mode 100644 docs/plans/cross-project-mcp-root.md create mode 100644 docs/plans/field-qualified-search.md create mode 100644 docs/plans/framework-route-extraction.md create mode 100644 docs/plans/fts-default-on-evaluation.md create mode 100644 docs/plans/git-hook-auto-sync.md create mode 100644 docs/plans/index-lock-and-error-log.md create mode 100644 docs/plans/mcp-server-instructions.md create mode 100644 docs/plans/mcp-tool-allowlist.md create mode 100644 docs/plans/mcp-trace-explore-tools.md create mode 100644 docs/plans/parse-worker-hardening.md create mode 100644 docs/plans/unresolved-calls-staging.md create mode 100644 docs/plans/wsl-watch-policy.md diff --git a/docs/plans/affected-tests-recipe.md b/docs/plans/affected-tests-recipe.md new file mode 100644 index 00000000..8e6080a0 --- /dev/null +++ b/docs/plans/affected-tests-recipe.md @@ -0,0 +1,70 @@ +# Affected tests recipe — plan + +> **Status:** open · **Priority:** P1 · **Effort:** M (~1–2 weeks) +> +> **Motivator:** CI can skip full test suites if only a subgraph changed. Codemap already has `dependencies` and `test_suites` — missing a recipe + CLI alias to list test files transitively impacted by changed sources. +> +> **Roadmap:** [§ Backlog](../roadmap.md#backlog) (test-impact item) · [agent-surface-and-ops § P1](./agent-surface-and-ops.md#p1) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| L.1 | **Moat-A clean** — `affected-tests` recipe + optional CLI alias `codemap affected` (outcome alias cap: 5 total — audit alias budget). | [Moat A](../roadmap.md#moats-load-bearing) | +| L.2 | Algorithm: reverse BFS on `dependencies` from changed files → filter test paths via `test_suites.file_path` and configurable globs. | Uses existing substrate | +| L.3 | **Stdin support** — accept changed paths from `git diff --name-only` (same ergonomics as CI scripts). | CLI ergonomics | +| L.4 | Not a verdict — output is file paths only; CI composes exit policy. | Moat A | + +--- + +## Recipe spec + +**Id:** `affected-tests` + +**Params (frontmatter):** + +- `changed_files` — multiline or repeated (from `--params` or stdin preprocessor) +- `test_glob` — default `**/*.{test,spec}.{ts,tsx,js,jsx}` +- `max_depth` — optional cap on BFS + +**SQL shape:** + +1. Seed temp/changed file list (CLI pre-processes stdin into params or temp table) +2. Recursive CTE walking `dependencies` inverted (`to_path` → `from_path`) +3. JOIN `test_suites` OR glob-match `files.path` + +--- + +## CLI alias + +```bash +codemap affected --json # git diff vs HEAD +git diff --name-only origin/main | codemap affected --stdin --json +``` + +Implement in `src/cli/aliases.ts` if alias budget allows; else recipe-only with documented shell wrapper. + +--- + +## Implementation steps + +1. Recipe SQL + frontmatter + golden query fixture +2. CLI stdin handling in `cmd-query` or dedicated thin `cmd-affected.ts` +3. Document test-file conventions in recipe `.md` +4. Optional GitHub Action input `mode: affected` in [github-marketplace-action](./github-marketplace-action.md) (follow-up) + +--- + +## Acceptance + +- [ ] Recipe returns test file paths for a known fixture delta +- [ ] Stdin mode works in shell pipeline +- [ ] Documented in README + skill + +--- + +## Dependencies + +None on schema changes. Benefits from accurate `dependencies` graph (existing). diff --git a/docs/plans/agent-eval-harness.md b/docs/plans/agent-eval-harness.md new file mode 100644 index 00000000..0abe8b84 --- /dev/null +++ b/docs/plans/agent-eval-harness.md @@ -0,0 +1,44 @@ +# Agent eval harness — plan + +> **Status:** open · **Priority:** P1 · **Effort:** M (~2 weeks) +> +> **Motivator:** Codemap claims agent-discovery wins ([why-codemap.md](../why-codemap.md), [benchmark.md](../benchmark.md)) but CI only gates query latency and golden SQL. Need falsifiable A/B: agent with MCP vs without, measuring tool-call count and tokens on fixed tasks. +> +> **Roadmap:** [§ Backlog](../roadmap.md#backlog) (falsifiable benchmark item) · [agent-surface-and-ops § P1](./agent-surface-and-ops.md#p1) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| L.1 | Harness lives in **`scripts/agent-eval/`** — not shipped in npm package. | Dev/CI only | +| L.2 | Fixtures reuse **golden-query scenarios** + optional external public repos (zod, fastify) per roadmap benchmark item. | Don't duplicate fixtures | +| L.3 | Arms: **codemap MCP on** vs **off** (or [mcp-tool-allowlist](./mcp-tool-allowlist.md) subset). | Clean ablation | +| L.4 | Metrics: tool-call sequence, wall time, estimated tokens (chars/4), success bit. | Publishable table | +| L.5 | **No telemetry upload** — results written to local JSON + optional CI artifact. | [Floor](../roadmap.md#floors-v1-product-shape) | + +--- + +## Implementation steps + +1. **`scripts/agent-eval/run-arms.sh`** — orchestrate N runs per arm +2. **Probe scripts** — structured tasks mirroring golden queries ("find symbol X", "who imports Y", "fan-in top 10") +3. **Parser for agent logs** — extract tool names from Claude/Cursor export format (start with one agent) +4. **Summary reporter** — markdown table for docs/benchmark.md +5. **CI job** — optional nightly or manual `workflow_dispatch` (public fixtures only) +6. **Link to [mcp-tool-allowlist](./mcp-tool-allowlist.md)** for minimal-tool arms + +--- + +## Acceptance + +- [ ] One-command local run produces comparison JSON +- [ ] At least 3 scenarios covered +- [ ] Documented methodology section in benchmark.md + +--- + +## Dependencies + +- [mcp-server-instructions](./mcp-server-instructions.md) and [agents-init-mcp-wiring](./agents-init-mcp-wiring.md) improve arm fairness (agents actually use tools) diff --git a/docs/plans/agent-surface-and-ops.md b/docs/plans/agent-surface-and-ops.md new file mode 100644 index 00000000..f6851cad --- /dev/null +++ b/docs/plans/agent-surface-and-ops.md @@ -0,0 +1,87 @@ +# Agent surface & indexing ops — plan index + +> **Status:** open · **Created:** 2026-05-24 +> +> **Purpose:** Prioritized work queue for agent UX, MCP ergonomics, indexing reliability, and TS/JS graph substrate gaps. Each item has its own plan file; this index links priority tiers and recommended ship order. +> +> **Roadmap home:** [§ Backlog — Agent & indexing ops](../roadmap.md#agent--indexing-ops) + +--- + +## Recommended ship order + +``` +P0 (ops polish, ~2 weeks total) + mcp-server-instructions → wsl-watch-policy → git-hook-auto-sync → mcp-tool-allowlist + +P1 (agent + reliability, parallel tracks) + Track A: mcp-trace-explore-tools (+ call-path recipes) + Track B: agents-init-mcp-wiring + Track C: index-lock-and-error-log → parse-worker-hardening + Track D: affected-tests-recipe + Track E: field-qualified-search + Track F: agent-eval-harness (after MCP instructions + allowlist) + +P2 (substrate + trigger-gated) + c9-plugin-layer (existing XL plan) → framework-route-extraction + unresolved-calls-staging → callback-dispatch-synthesis + call-path-type-hierarchy-recipes (extends P1 recipes) + fts-default-on-evaluation (measurement gate) + cross-project-mcp-root (on demand) +``` + +--- + +## P0 — Quick wins + +| Plan | Effort | Summary | +| ------------------------------------------------------- | ------ | ------------------------------------------------- | +| [mcp-server-instructions](./mcp-server-instructions.md) | S | MCP initialize playbook for tool selection | +| [wsl-watch-policy](./wsl-watch-policy.md) | S | Disable broken watcher on WSL `/mnt` mounts | +| [git-hook-auto-sync](./git-hook-auto-sync.md) | S | Opt-in git hooks for background incremental index | +| [mcp-tool-allowlist](./mcp-tool-allowlist.md) | S | `CODEMAP_MCP_TOOLS` env subset registration | + +--- + +## P1 — Medium effort + +| Plan | Effort | Summary | +| --------------------------------------------------------- | ------ | -------------------------------------------------- | +| [mcp-trace-explore-tools](./mcp-trace-explore-tools.md) | M | MCP trace/explore/node + recipe twins | +| [agents-init-mcp-wiring](./agents-init-mcp-wiring.md) | M | `agents init --mcp` config + permissions | +| [affected-tests-recipe](./affected-tests-recipe.md) | M | Test selection from dep graph + stdin | +| [index-lock-and-error-log](./index-lock-and-error-log.md) | M | Cross-process lock + `codemap unlock` + errors.log | +| [parse-worker-hardening](./parse-worker-hardening.md) | M | Per-file timeout + worker recycle | +| [field-qualified-search](./field-qualified-search.md) | M | `kind:` / `path:` / `name:` search → SQL | +| [agent-eval-harness](./agent-eval-harness.md) | M | A/B agent eval for tool-call + token metrics | + +--- + +## P2 — Strategic bets + +| Plan | Effort | Summary | +| ------------------------------------------------------------------------- | ------ | ----------------------------------------------------- | +| [framework-route-extraction](./framework-route-extraction.md) | L | Express / React Router / NestJS → `http_routes` table | +| [callback-dispatch-synthesis](./callback-dispatch-synthesis.md) | L | Heuristic call edges with `provenance` column | +| [unresolved-calls-staging](./unresolved-calls-staging.md) | L | Two-phase call resolution queue | +| [cross-project-mcp-root](./cross-project-mcp-root.md) | M | Optional `root` on MCP tools + DB cache | +| [fts-default-on-evaluation](./fts-default-on-evaluation.md) | S–M | Measure size tax; maybe flip default | +| [call-path-type-hierarchy-recipes](./call-path-type-hierarchy-recipes.md) | M | `type-ancestors` / descendants recipes | + +--- + +## Related existing plans + +| Plan | Relationship | +| ------------------------------------------------------------- | --------------------------------------------------------------------------------- | +| [c9-plugin-layer](./c9-plugin-layer.md) | Prerequisite for [framework-route-extraction](./framework-route-extraction.md) | +| [github-marketplace-action](./github-marketplace-action.md) | May add `affected` mode after [affected-tests-recipe](./affected-tests-recipe.md) | +| [perf-triangulation-rollout](./perf-triangulation-rollout.md) | [parse-worker-hardening](./parse-worker-hardening.md) related Phase 3 items | + +--- + +## Moat checklist (all items) + +- **Moat A:** Every new CLI/MCP verb has a recipe or SQL equivalent (or is pure transport wiring). +- **Moat B:** Substrate additions (`http_routes`, `unresolved_calls`, `calls.provenance`) enable new JOINs — not verdict primitives. +- **Floors:** No LLM-in-box, no telemetry, no opaque graph-only APIs. diff --git a/docs/plans/agents-init-mcp-wiring.md b/docs/plans/agents-init-mcp-wiring.md new file mode 100644 index 00000000..dd8b41cf --- /dev/null +++ b/docs/plans/agents-init-mcp-wiring.md @@ -0,0 +1,58 @@ +# Agents init MCP wiring — plan + +> **Status:** open · **Priority:** P1 · **Effort:** M (~1–2 weeks) +> +> **Motivator:** `codemap agents init` wires rules/skills into 9 IDE targets but leaves MCP config manual. Agents won't use the index if MCP isn't configured and permission-gated (Claude Code blocks tools by default). +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p1) · extends [agents.md](../agents.md) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | -------------------------------------------------------------------------------------------------------------- | ------------------------------- | +| L.1 | **Keep pointer protocol** for skill/rule bodies — MCP wiring writes config JSON only, not duplicated markdown. | [agents.md](../agents.md) | +| L.2 | New flag **`--mcp`** on `agents init` (interactive step + non-interactive opt-in). | Opt-in | +| L.3 | MCP command shape: `codemap mcp --watch` with transport-specific args (Cursor: `--root ${workspaceFolder}`). | Work around MCP cwd ≠ workspace | +| L.4 | Claude Code: append **`permissions.allow`** entries for `mcp__codemap__*` tools when `--mcp` set. | Reduce prompt friction | + +--- + +## Target matrix (v1) + +| Target | Config path | Notes | +| --------------------- | -------------------------------------------------- | ---------------------------------- | +| Cursor | `~/.cursor/mcp.json` or project `.cursor/mcp.json` | Inject `--root ${workspaceFolder}` | +| Claude Code | `~/.claude.json` MCP + settings permissions | Auto-allow codemap tools | +| VS Code / Copilot | `.vscode/mcp.json` if supported | Detect capability | +| Continue / Cline | existing init paths | Same stdio command | +| AGENTS.md / GEMINI.md | Usage section only (no MCP file) | Document manual MCP | + +Reuse patterns from `src/agents-init-interactive.ts`; add `src/agents-init-mcp.ts`. + +--- + +## Implementation steps + +1. **Detect + write MCP entries** per target (read-merge, don't clobber unrelated servers) +2. **Marker blocks** for idempotent uninstall (``) +3. **`--mcp` flag** on `agents init` and interactive prompt +4. **Cursor workaround** — document in generated rule pointer: "always pass workspace root" +5. **Tests** — fixture configs; merge preserves foreign entries +6. **Docs** — agents.md § MCP wiring; README quickstart + +--- + +## Acceptance + +- [ ] `codemap agents init --mcp -i` writes working MCP config for Cursor + Claude +- [ ] Re-run is idempotent +- [ ] Pointer skill/rule unchanged in content shape + +--- + +## Dependencies + +- [mcp-server-instructions](./mcp-server-instructions.md) improves first-run agent behavior +- [mcp-tool-allowlist](./mcp-tool-allowlist.md) optional for minimal installs diff --git a/docs/plans/call-path-type-hierarchy-recipes.md b/docs/plans/call-path-type-hierarchy-recipes.md new file mode 100644 index 00000000..578f9e11 --- /dev/null +++ b/docs/plans/call-path-type-hierarchy-recipes.md @@ -0,0 +1,62 @@ +# Call path and type hierarchy recipes — plan + +> **Status:** open · **Priority:** P2 · **Effort:** M (~1–2 weeks) +> +> **Motivator:** Shortest-path and type-ancestor queries are common agent tasks. `impact` walks radius but doesn't find minimal paths. `type_members` exists but no bundled recipe for extends/implements chains. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p2) · overlaps [mcp-trace-explore-tools](./mcp-trace-explore-tools.md) (ship recipes there first for call-path) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | +| L.1 | **Recipes only** in this plan — MCP wrappers live in [mcp-trace-explore-tools](./mcp-trace-explore-tools.md). | Split concerns | +| L.2 | Call path uses **recursive CTE** on `calls` table; optional `via=dependencies` mode. | Moat A | +| L.3 | Type hierarchy uses **`type_members`** + class/interface `symbols` relationships where extracted; document gaps for incomplete extends data. | Honest limits | +| L.4 | No new library export required for v1 — optional `findCallPath()` in `api.ts` later. | YAGNI | + +--- + +## Recipe specs + +### `call-path` (may ship in P1 trace plan) + +- Params: `from_name`, `to_name`, `max_depth`, `via` +- Output: ordered hops + +### `type-ancestors` + +- Params: `symbol_name`, `kind` (class|interface), `max_depth` +- SQL: walk `type_members` / parent_name / extends relationships available in schema +- Output: ancestor symbol rows + +### `type-descendants` + +- Inverse of ancestors for interface implementation queries + +--- + +## Implementation steps + +1. Audit schema for extends/implements facts — gap list in plan PR if parser additions needed +2. Implement `type-ancestors.sql` + golden queries +3. Implement `type-descendants.sql` if substrate supports +4. If `call-path` not already shipped, add here +5. Document SQL patterns in skill; link from MCP instructions + +--- + +## Acceptance + +- [ ] `type-ancestors` returns expected chain on fixture with extends +- [ ] Documented limitations when extends not extracted +- [ ] Golden-query CI covers new recipes + +--- + +## Dependencies + +- [mcp-trace-explore-tools](./mcp-trace-explore-tools.md) for `call-path` MCP wrapper +- [callback-dispatch-synthesis](./callback-dispatch-synthesis.md) improves call-path completeness diff --git a/docs/plans/callback-dispatch-synthesis.md b/docs/plans/callback-dispatch-synthesis.md new file mode 100644 index 00000000..7720a028 --- /dev/null +++ b/docs/plans/callback-dispatch-synthesis.md @@ -0,0 +1,62 @@ +# Callback dispatch synthesis — plan + +> **Status:** open · **Priority:** P2 · **Effort:** L (~2–3 weeks) · **Trigger-gated** +> +> **Motivator:** Static AST `calls` edges miss EventEmitter wiring, React `setState`→render, and JSX parent→child composition. Call-path and impact queries stop early on real TS/React codebases without heuristic edges — but heuristics must be tagged so agents don't treat them as type-checked facts. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p2) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | --------------------------------------------------------- | ------------------------------------------ | +| L.1 | Add **`calls.provenance`** column: `NULL | 'ast' | 'heuristic'`. Default NULL = ast-era rows treated as ast. | [Moat B](../roadmap.md#moats-load-bearing) | +| L.2 | Synthesis runs **post-index pass** after bindings — additive only; failures ignored. | Optional enrichment | +| L.3 | **Moat-A filters** — recipes default `WHERE provenance IS NULL OR provenance = 'ast'`; opt-in recipe `calls-including-heuristic`. | Honesty | +| L.4 | **TS/React scope v1:** EventEmitter `on`/`emit`, JSX child component edges, `setState`→render heuristic. Skip Flutter/C++/Java patterns. | TS/JS focus | + +--- + +## Heuristics (v1) + +| Pattern | Edge | +| ------------------------------------------ | -------------------------------- | +| `.on('event', handler)` / `.emit('event')` | emitter → handler | +| JSX `` in component body | parent component → Child symbol | +| `setState` in class component | method → render (low confidence) | + +Cap fan-out per file to limit false positives. + +--- + +## Implementation steps + +1. SCHEMA_VERSION bump — `ALTER`/rebuild adds `calls.provenance` +2. **`src/application/callback-synthesis.ts`** — scan files (or use jsx_elements table); insert synthetic `calls` rows +3. Run from `run-index.ts` finally block (after bindings) +4. Recipe: `calls-including-heuristic`, update `call-path` docs +5. Tests — fixture with EventEmitter + JSX; assert provenance tags +6. Document limits in skill + MCP instructions + +--- + +## Acceptance + +- [ ] Heuristic edges visible with `provenance='heuristic'` +- [ ] Default call-path recipe excludes heuristics unless param set +- [ ] No change to existing ast call row counts when synthesis disabled via config flag + +--- + +## Dependencies + +- [unresolved-calls-staging](./unresolved-calls-staging.md) may run before synthesis +- Improves [mcp-trace-explore-tools](./mcp-trace-explore-tools.md) usefulness + +--- + +## Config (optional) + +`config.synthesis.heuristicCalls: boolean` default `true` after bake-in period. diff --git a/docs/plans/cross-project-mcp-root.md b/docs/plans/cross-project-mcp-root.md new file mode 100644 index 00000000..b27c1b86 --- /dev/null +++ b/docs/plans/cross-project-mcp-root.md @@ -0,0 +1,50 @@ +# Cross-project MCP root — plan + +> **Status:** open · **Priority:** P2 · **Effort:** M (~1–2 weeks) · **Trigger-gated** +> +> **Motivator:** Some agent setups index multiple repos (monorepo + packages, microservices). A single MCP server could query another indexed tree if given an explicit root path — without restarting the server. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p2) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | +| L.1 | Optional **`root` argument** on MCP tools (and HTTP) — absolute or workspace-relative path. | Explicit opt-in | +| L.2 | **LRU cache** of open DB handles per root (cap e.g. 5); close oldest on eviction. | Resource bound | +| L.3 | **Path sandbox** — reject paths outside allowed list or user home without `CODEMAP_ALLOW_ANY_ROOT=1`. | Safety | +| L.4 | Default root remains server startup `opts.root` — backward compatible. | No breaking change | +| L.5 | **One process, multiple read-only query contexts** — indexing still one active write root per process unless lock coordinates. | Align with global runtime | + +--- + +## Implementation steps + +1. **`src/application/project-cache.ts`** — `getDbForRoot(root): Database` +2. Extend tool handler schemas with optional `root?: string` +3. Validate path exists and contains `.codemap/index.db` (or run validate message) +4. Reuse same tool handlers with per-call db handle +5. Tests — two fixture roots; cache hit/eviction +6. Document in agents.md + MCP instructions + +--- + +## Acceptance + +- [ ] MCP `query` with `root` pointing at second fixture returns correct rows +- [ ] Invalid root → structured error +- [ ] Cache eviction closes DB connections + +--- + +## Revisit trigger + +Ship when a consumer runs multi-root MCP or opens an issue with concrete layout. + +--- + +## Dependencies + +- [index-lock-and-error-log](./index-lock-and-error-log.md) — clarify lock is per state-dir, not global singleton confusion diff --git a/docs/plans/field-qualified-search.md b/docs/plans/field-qualified-search.md new file mode 100644 index 00000000..75e447f2 --- /dev/null +++ b/docs/plans/field-qualified-search.md @@ -0,0 +1,54 @@ +# Field-qualified search — plan + +> **Status:** open · **Priority:** P1 · **Effort:** M (~1 week) +> +> **Motivator:** Agents often search with partial constraints (`kind:function`, `path:src/api`, `name:Auth`). Today they must write SQL or use exact `show` — higher friction for discovery queries. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p1) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | +| L.1 | **Moat-A clean** — parser translates to SQL WHERE clauses; always log/return equivalent SQL in `--print-sql` mode. | [Moat A](../roadmap.md#moats-load-bearing) | +| L.2 | Fields v1: `kind:`, `name:`, `path:`, `in:` (file glob). Optional FTS join when `--with-fts`. | Minimal surface | +| L.3 | Surface on **`show` convenience** and MCP `show` / new `search` tool — not a separate verdict engine. | Thin layer | +| L.4 | Document SQL equivalents in bundled skill. | Transparency | + +--- + +## Syntax (v1) + +``` +kind:function name:Auth path:src/ +name:"useQuery" kind:hook # hook → kind filter via components.hooks_used optional v2 +``` + +Free text without `field:` prefix → `name LIKE` or FTS if enabled. + +--- + +## Implementation steps + +1. **`src/application/search-query-parser.ts`** — tokenize `field:value` pairs (reuse recipe-params escaping patterns) +2. **`src/application/search-engine.ts`** — build parameterized SQL against `symbols` (+ optional `source_fts`) +3. **CLI** — `codemap show --query 'kind:function name:foo'` or extend `show` flags +4. **MCP** — extend `show` input schema with `query` string +5. **Tests** — parser unit tests + golden SQL snapshots +6. **Skill update** — "field search ≡ this SQL" + +--- + +## Acceptance + +- [ ] `kind:function name:auth` returns same rows as documented SQL +- [ ] `--print-sql` shows generated statement +- [ ] Invalid field names → clear error + +--- + +## Dependencies + +Optional synergy with [fts-default-on-evaluation](./fts-default-on-evaluation.md) for body-aware search. diff --git a/docs/plans/framework-route-extraction.md b/docs/plans/framework-route-extraction.md new file mode 100644 index 00000000..dca1caaa --- /dev/null +++ b/docs/plans/framework-route-extraction.md @@ -0,0 +1,79 @@ +# Framework route extraction — plan + +> **Status:** open · **Priority:** P2 · **Effort:** L (~3–4 weeks) · **Trigger-gated** +> +> **Motivator:** Agents ask "what handles `/api/users`?" and reachability recipes false-positive framework entry files without route awareness. Static HTTP route extraction closes the gap for TS/JS web stacks. +> +> **Roadmap:** [§ Backlog](../roadmap.md#backlog) · extends [c9-plugin-layer](./c9-plugin-layer.md) · ships after C.9 contract exists + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| L.1 | **Extends C.9 plugin contract** — plugins may emit route rows, not just `is_entry` hints. | [c9-plugin-layer](./c9-plugin-layer.md) | +| L.2 | **Moat-B substrate** — new table `routes` (or `http_routes`): `method`, `path_pattern`, `handler_symbol`, `file_path`, `line_start`, `framework`. | [Moat B](../roadmap.md#moats-load-bearing) | +| L.3 | **Moat-A exposure** — bundled recipe `http-routes` + parametrised `find-route-by-path`. | Moat A | +| L.4 | **Static extraction only** — regex/AST patterns on oxc-parsed files; no runtime route registration. | [Floor — No JS execution](../roadmap.md#floors-v1-product-shape) | +| L.5 | **v1 frameworks:** Express, React Router v6, NestJS decorators. Next.js App Router entry hints stay on C.9 `is_entry` until route patterns proven. | TS/JS depth first | + +--- + +## Schema sketch + +```sql +CREATE TABLE http_routes ( + id INTEGER PRIMARY KEY, + file_path TEXT NOT NULL, + framework TEXT NOT NULL, + method TEXT, -- GET|POST|*|USE + path_pattern TEXT NOT NULL, + handler_name TEXT, + handler_symbol_id INTEGER, + line_start INTEGER NOT NULL +); +``` + +--- + +## Bundled plugins (v1) + +| Plugin id | Detect | Extract patterns | +| -------------- | ------------------------------------- | ------------------------------------- | +| `express` | `express` import / `app.get/post/...` | CallExpression on `app`/`router` | +| `react-router` | `createBrowserRouter`, ` **Status:** open · **Priority:** P2 · **Effort:** S–M (~1 week measure + decision) +> +> **Motivator:** Body search requires `--with-fts` / `fts5: true` today. Agents miss content matches unless configured. Default-on improves discoverability but may inflate `.codemap/index.db` size and index time. +> +> **Roadmap:** [§ Floors — Full-text search default-on](../roadmap.md#floors-v1-product-shape) · [agent-surface-and-ops § P2](./agent-surface-and-ops.md#p2) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ----------------------------------------------------------------------------------------------------------- | ------------------------- | +| L.1 | **Measure before flipping default** — benchmark 3 fixture sizes for index time + DB bytes with/without FTS. | Empirical gate | +| L.2 | If adopted: change **`config.ts` default** `fts5: true` for new projects only; existing configs unchanged. | No silent behavior change | +| L.3 | **`--with-fts` / `--no-fts`** CLI flags continue to override. | Explicit escape hatch | +| L.4 | Document size tax in README + packaging.md. | Transparency | + +--- + +## Measurement protocol + +1. Fixtures: codemap self-index, medium TS app (~500 files), large OSS snapshot (public, cached in CI) +2. Metrics: `index.db` bytes, full index wall ms, FTS populate phase ms +3. Threshold proposal: if median size increase <40% and index increase <15%, recommend default-on +4. Record results in `docs/benchmark.md` § FTS size tax + +--- + +## Implementation steps (if approved) + +1. Update `codemapUserConfigSchema` default `fts5: true` +2. Update README quickstart to mention opt-out +3. Ensure `ensureStateConfig` doesn't overwrite user false +4. Golden tests unchanged (FTS-specific recipes already gated) + +--- + +## Acceptance + +- [ ] Benchmark table published with decision rationale +- [ ] If default-on: new init gets FTS without extra flags +- [ ] If stay opt-in: document why with numbers + +--- + +## Dependencies + +- Synergy with [field-qualified-search](./field-qualified-search.md) when FTS joined to symbol filters + +--- + +## Outcome + +Plan closes with **Adopt** or **Defer** header update — no perpetual open state. diff --git a/docs/plans/git-hook-auto-sync.md b/docs/plans/git-hook-auto-sync.md new file mode 100644 index 00000000..7cb9b9e1 --- /dev/null +++ b/docs/plans/git-hook-auto-sync.md @@ -0,0 +1,47 @@ +# Git hook auto-sync — plan + +> **Status:** open · **Priority:** P0 · **Effort:** S (~1 week) +> +> **Motivator:** When the file watcher is off (WSL mounts, CI, user preference), the index goes stale until manual `codemap`. Git hooks can run a background incremental index after commit/merge/checkout without blocking git. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p0) · complements [wsl-watch-policy](./wsl-watch-policy.md) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | +| L.1 | **Opt-in only** — `codemap agents init --git-hooks`; never auto-modify `.git/hooks` without consent. | Safety | +| L.2 | Hook body runs `( codemap >/dev/null 2>&1 & )` — non-blocking background incremental index. | Don't slow git | +| L.3 | **Marker-delimited blocks** in hook files for idempotent install/uninstall (`` … ``). | Same pattern as agents init sections | +| L.4 | Hooks: `post-commit`, `post-merge`, `post-checkout` (optional subset documented). | Cover common freshness gaps | + +--- + +## Implementation steps + +1. **Add `src/application/git-hooks.ts`** + - `installGitHooks(root, hooks: ('post-commit' | ...)[])` + - `uninstallGitHooks(root)` + - `isCodemapHookInstalled(path)` +2. **CLI flag** — `codemap agents init --git-hooks` (+ offer in interactive flow when [watch-policy](./wsl-watch-policy.md) returns disabled) +3. **Uninstall path** — `agents init --force` does not remove hooks; document `codemap agents init --no-git-hooks` or separate uninstall +4. **Tests** — temp git repo; verify marker injection/removal; hook invokes codemap when binary on PATH +5. **Docs** — [agents.md](../agents.md), troubleshooting for hook + lock interaction ([index-lock-and-error-log](./index-lock-and-error-log.md)) + +--- + +## Acceptance + +- [ ] Install adds non-blocking sync to selected hooks +- [ ] Re-run install is idempotent +- [ ] Uninstall removes only codemap-marked blocks +- [ ] Offered automatically when watcher disabled per watch-policy + +--- + +## Dependencies + +- Recommended after [wsl-watch-policy](./wsl-watch-policy.md) +- Coordinate with [index-lock-and-error-log](./index-lock-and-error-log.md) for concurrent hook + MCP writes diff --git a/docs/plans/index-lock-and-error-log.md b/docs/plans/index-lock-and-error-log.md new file mode 100644 index 00000000..9a4b5b6c --- /dev/null +++ b/docs/plans/index-lock-and-error-log.md @@ -0,0 +1,46 @@ +# Index lock and error log — plan + +> **Status:** open · **Priority:** P1 · **Effort:** M (~1 week) +> +> **Motivator:** MCP server, file watcher, git hooks, and CLI can index concurrently. SQLite `busy_timeout` alone causes opaque hangs. Per-file parse failures are easy to miss without a persistent log. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p1) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ----------------------------------------------------------------------------------------------------------------- | ------------------- | +| L.1 | Lock file at **`/index.lock`** — JSON `{ pid, started_at }`. | Cross-process guard | +| L.2 | **Stale lock detection** — if PID dead or lock older than threshold, `codemap unlock` or auto-steal with warning. | Recovery path | +| L.3 | **In-process mutex** still required for same-process MCP + watcher races. | Layer both | +| L.4 | **`errors.log`** append-only in state dir; one line per failed file with path + reason. | Ops visibility | +| L.5 | Fail-fast on lock acquire — return actionable message, not spin. | UX | + +--- + +## Implementation steps + +1. **`src/application/index-lock.ts`** + - `acquireLock(stateDir)`, `releaseLock()`, `isStale(lockPath)` +2. **Integrate in `run-index.ts` / `index-engine.ts`** — try/finally release +3. **CLI `codemap unlock`** — remove stale lock (`src/cli/cmd-unlock.ts` or subcommand) +4. **`src/application/error-log.ts`** — append on parse/resolver failures +5. **Ensure `.codemap/.gitignore`** includes `errors.log` optional (or keep for local debug — decide: gitignore `errors.log`) +6. **Tests** — concurrent process simulation; stale PID; unlock CLI +7. **Docs** — troubleshooting in README; link from [git-hook-auto-sync](./git-hook-auto-sync.md) + +--- + +## Acceptance + +- [ ] Second concurrent index fails fast with "run codemap unlock" +- [ ] Stale lock recoverable +- [ ] Parse errors visible in `errors.log` after index with bad fixture + +--- + +## Dependencies + +- Coordinate with [git-hook-auto-sync](./git-hook-auto-sync.md) and [parse-worker-hardening](./parse-worker-hardening.md) for logged timeout failures diff --git a/docs/plans/mcp-server-instructions.md b/docs/plans/mcp-server-instructions.md new file mode 100644 index 00000000..d8fa59ec --- /dev/null +++ b/docs/plans/mcp-server-instructions.md @@ -0,0 +1,45 @@ +# MCP server instructions — plan + +> **Status:** open · **Priority:** P0 · **Effort:** S (~1 week) +> +> **Motivator:** MCP clients inject server `instructions` from the `initialize` response into the agent system prompt. Codemap today exposes tools and resources but no tool-selection playbook — agents must discover `query_recipe` vs `impact` vs `context` from the pointer skill alone. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p0) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| L.1 | Instructions are **operational guidance only** — which tool when, common chains, anti-patterns. Full schema/recipe catalog stays on `codemap://skill` / `codemap://rule` resources. | [Moat A](../roadmap.md#moats-load-bearing) | +| L.2 | Content lives in **`templates/agent-content/`** and is assembled by `agent-content.ts` — same pointer/version discipline as skill/rule. | [agents.md](../agents.md) | +| L.3 | No NL routing or embedded intent classification in instructions. | [Floor — No LLM in the box](../roadmap.md#floors-v1-product-shape) | + +--- + +## Implementation steps + +1. **Author `templates/agent-content/mcp-instructions.md`** (~60 lines): + - Session start → `context` + fetch `codemap://rule` + - Symbol lookup → `show` / `query_recipe find-symbol-by-kind` + - Blast radius → `impact` or `query_recipe fan-in` + - CI findings → `query_recipe` + `--format sarif` via CLI; MCP `query_recipe` with JSON + - Every MCP convenience tool must name its recipe twin (when wrappers ship) +2. **Wire into `createMcpServer()`** — pass `instructions` field on `McpServer` init (`src/application/mcp-server.ts`). +3. **Expose via pointer protocol** — optional `codemap://mcp-instructions` resource or section in live rule. +4. **Tests** — snapshot instructions length; assert no stale recipe ids (grep against `templates/recipes/`). + +--- + +## Acceptance + +- [ ] `codemap mcp` startup includes instructions in MCP initialize handshake +- [ ] Instructions cite only shipped recipe ids and tool names +- [ ] Documented in [agents.md](../agents.md) § MCP + +--- + +## Dependencies + +None. Ships first in the agent-ops sequence. diff --git a/docs/plans/mcp-tool-allowlist.md b/docs/plans/mcp-tool-allowlist.md new file mode 100644 index 00000000..9c8825da --- /dev/null +++ b/docs/plans/mcp-tool-allowlist.md @@ -0,0 +1,41 @@ +# MCP tool allowlist — plan + +> **Status:** open · **Priority:** P0 · **Effort:** S (~2 days) +> +> **Motivator:** Minimal MCP installs and eval A/B runs need to register a subset of tools (e.g. `query,context` only). Today all 14 tools always register. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p0) · supports [agent-eval-harness](./agent-eval-harness.md) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ------------------------------------------------------------------------------------------- | ------------------------------ | +| L.1 | Env var **`CODEMAP_MCP_TOOLS`** — comma-separated snake_case tool names; unset = all tools. | Consistent with MCP tool names | +| L.2 | **`query_batch` always optional** — include only when listed or when unset. | Eval ablation | +| L.3 | Invalid tool names → stderr warning, ignore unknown (don't fail startup). | Fail-soft for typos | + +--- + +## Implementation steps + +1. **Parse env in `createMcpServer()`** before register calls +2. **Filter `register*Tool` invocations** — skip unlisted tools +3. **Log registered set** on stderr at debug level or when allowlist active +4. **Tests** — `CODEMAP_MCP_TOOLS=query,show` registers exactly two tools +5. **Docs** — README env table; [agent-eval-harness](./agent-eval-harness.md) uses allowlist for arms + +--- + +## Acceptance + +- [ ] Subset registration works +- [ ] Default behavior unchanged when env unset +- [ ] Documented in agents.md / packaging.md + +--- + +## Dependencies + +None. diff --git a/docs/plans/mcp-trace-explore-tools.md b/docs/plans/mcp-trace-explore-tools.md new file mode 100644 index 00000000..6c503805 --- /dev/null +++ b/docs/plans/mcp-trace-explore-tools.md @@ -0,0 +1,72 @@ +# MCP trace & explore tools — plan + +> **Status:** open · **Priority:** P1 · **Effort:** M (~2 weeks) +> +> **Motivator:** Agents often need call-path and multi-symbol survey answers in one round-trip. Codemap has `impact` (radius walk) and `snippet` but no shortest-path or budget-capped multi-file survey. MCP wrappers must not erode Moat A — every wrapper ships with a recipe twin. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p1) · related [call-path-type-hierarchy-recipes](./call-path-type-hierarchy-recipes.md) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| L.1 | **Recipe twins required** before MCP tools: `call-path`, `symbol-neighborhood` (bundled SQL). | [Moat A](../roadmap.md#moats-load-bearing) | +| L.2 | MCP tools **`trace`**, **`explore`**, **`node`** are thin composers over recipes + `snippet` + existing engines — not opaque graph APIs. | Moat A | +| L.3 | **Output budgets** — cap total response chars (e.g. 15k); truncate with explicit `truncated: true` in JSON. | Agent context economics | +| L.4 | No NL task parsing — `trace` takes `from` / `to` symbol names; `explore` takes symbol list or recipe result rows. | [Floor — No LLM in the box](../roadmap.md#floors-v1-product-shape) | + +--- + +## Recipe specs (ship first) + +### `call-path` + +- Params: `from_name`, `to_name`, optional `max_depth`, `via` (`calls` | `dependencies` | `all`) +- SQL: recursive CTE on `calls` (+ optional `dependencies` UNION) +- Output: ordered path rows `{file_path, caller_name, callee_name, line_start}` + +### `symbol-neighborhood` + +- Params: `name`, `depth` (default 1), optional `kind` +- SQL: UNION callers/callees from `calls` + `dependencies` fan-in/out +- Output: symbol rows suitable for `snippet` batch + +--- + +## MCP tool specs (ship second) + +| Tool | Composes | +| --------- | ---------------------------------------------------------------------------- | +| `trace` | `query_recipe call-path` + `snippet` for each hop | +| `explore` | `query_recipe symbol-neighborhood` (multi-name) + `snippet` with char budget | +| `node` | `show` + one-hop `symbol-neighborhood` + optional inline snippets | + +Register in `mcp-server.ts`; document chains in [mcp-server-instructions](./mcp-server-instructions.md). + +--- + +## Implementation steps + +1. Add `templates/recipes/call-path.sql` + `.md` frontmatter +2. Add `templates/recipes/symbol-neighborhood.sql` + `.md` +3. Golden-query tests for both recipes +4. Implement MCP handlers in `tool-handlers.ts` (or dedicated module) +5. Output budget helper shared by explore/trace +6. Update agent-content skill with SQL equivalents + +--- + +## Acceptance + +- [ ] `codemap query --recipe call-path --params from=foo,to=bar` works +- [ ] MCP `trace` returns same path + snippets, respects budget +- [ ] Instructions document recipe-first fallback + +--- + +## Dependencies + +- [mcp-server-instructions](./mcp-server-instructions.md) should land first or in same PR +- [call-path-type-hierarchy-recipes](./call-path-type-hierarchy-recipes.md) may extend CTE patterns later diff --git a/docs/plans/parse-worker-hardening.md b/docs/plans/parse-worker-hardening.md new file mode 100644 index 00000000..3f7b7697 --- /dev/null +++ b/docs/plans/parse-worker-hardening.md @@ -0,0 +1,45 @@ +# Parse worker hardening — plan + +> **Status:** open · **Priority:** P1 · **Effort:** M (~1 week) +> +> **Motivator:** Full rebuild uses a worker pool (`src/worker-pool.ts`) but workers run until chunk completes with no per-file timeout or mid-batch recycle. Pathological files (minified bundles, huge generated TS) can hang or balloon memory. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p1) · related [perf-triangulation-rollout](./perf-triangulation-rollout.md) Phase 3 deferrals + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ------------------------------------------------------------------------------------------ | -------------------------- | +| L.1 | **Per-file parse timeout** — default 10s, scaled by file size (cap e.g. 30s). | Bound blast radius | +| L.2 | **Worker recycle** — restart worker after N files (default 250) within a full rebuild. | Contain native heap growth | +| L.3 | On timeout: log to [errors.log](./index-lock-and-error-log.md), skip file, continue index. | Partial index > hung index | +| L.4 | Incremental sequential parse gets same timeout wrapper (main thread). | Parity | + +--- + +## Implementation steps + +1. **Extend `worker-pool.ts`** + - `Promise.race` around worker message with timeout + - Track files-per-worker; terminate + respawn at interval +2. **Timeout helper in `parse-worker-core.ts`** — cancel/ignore late responses +3. **Wire error log** on timeout/OOM +4. **Config knobs** (optional v1): env `CODEMAP_PARSE_TIMEOUT_MS`, `CODEMAP_WORKER_RECYCLE_EVERY` +5. **Tests** — fixture that sleeps in mock worker; verify skip + log +6. **Benchmark** — ensure recycle doesn't regress perf baseline on codemap self-index + +--- + +## Acceptance + +- [ ] Hung parse terminates within timeout +- [ ] Full rebuild completes with skip count in summary +- [ ] errors.log records skipped paths + +--- + +## Dependencies + +- [index-lock-and-error-log](./index-lock-and-error-log.md) for failure persistence diff --git a/docs/plans/unresolved-calls-staging.md b/docs/plans/unresolved-calls-staging.md new file mode 100644 index 00000000..19a991fe --- /dev/null +++ b/docs/plans/unresolved-calls-staging.md @@ -0,0 +1,62 @@ +# Unresolved calls staging — plan + +> **Status:** open · **Priority:** P2 · **Effort:** L (~2–3 weeks) +> +> **Motivator:** Call sites are inserted during parse; cross-file resolution to callee symbols happens in bindings post-pass without a durable staging queue. Re-indexing a subset of files can leave stale call edges until full rebuild. A two-phase extract→resolve pipeline enables scoped re-resolution on incremental sync. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p2) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| L.1 | New table **`unresolved_calls`** — staging queue analogous to pending refs: caller location, callee name, file, kind. | Substrate | +| L.2 | **Phase 1 (parse):** insert calls + queue unresolved targets. **Phase 2 (resolve):** batch bind → update `bindings` / mark resolved / delete queue rows. | Two-phase | +| L.3 | **Scoped resolve on `--files`:** only re-resolve queue entries touching changed files. | Incremental correctness | +| L.4 | Expose **`unresolved-call-sites`** and **`call-resolution-stats`** recipes. | Moat A | + +--- + +## Schema sketch + +```sql +CREATE TABLE unresolved_calls ( + id INTEGER PRIMARY KEY, + file_path TEXT NOT NULL, + caller_scope TEXT, + callee_name TEXT NOT NULL, + line_start INTEGER NOT NULL, + column_start INTEGER, + reference_kind TEXT, + created_at TEXT +); +``` + +--- + +## Implementation steps + +1. SCHEMA_VERSION bump + DDL +2. Parser emits unresolved entries when callee not locally defined +3. **`src/application/call-resolver.ts`** — batch pass using imports + bindings + name match +4. Wire into `index-engine.ts` — full rebuild resolves all; incremental resolves scoped set +5. Delete or archive unresolved rows after pass (retain failed count in meta) +6. Recipes + golden tests +7. Fix any `indexFiles`-without-resolve gap (parity with full index) + +--- + +## Acceptance + +- [ ] Incremental `--files` re-resolves calls for touched files only +- [ ] Recipe lists remaining unresolved sites +- [ ] Full rebuild clears queue (or reports residual count) + +--- + +## Dependencies + +- Pairs with [callback-dispatch-synthesis](./callback-dispatch-synthesis.md) (runs after base resolve) +- [index-lock-and-error-log](./index-lock-and-error-log.md) for concurrent index safety diff --git a/docs/plans/wsl-watch-policy.md b/docs/plans/wsl-watch-policy.md new file mode 100644 index 00000000..560e5d93 --- /dev/null +++ b/docs/plans/wsl-watch-policy.md @@ -0,0 +1,44 @@ +# WSL watch policy — plan + +> **Status:** open · **Priority:** P0 · **Effort:** S (~3 days) +> +> **Motivator:** Recursive file watchers on WSL2 Windows mounts (`/mnt/c/...`) are unreliable and slow. Codemap starts chokidar unconditionally on `mcp` / `serve` / `watch`, which can hang or miss events on those paths. +> +> **Roadmap:** [§ Backlog — Agent surface & ops](./agent-surface-and-ops.md#p0) + +--- + +## Pre-locked decisions + +| # | Decision | Source | +| --- | ----------------------------------------------------------------------------------------------------------- | ------------------------ | +| L.1 | **Single module** `watchDisabledReason(root)` shared by watcher, MCP boot, and `agents init` diagnostics. | DRY; one source of truth | +| L.2 | Env precedence: `CODEMAP_NO_WATCH=1` → off; `CODEMAP_FORCE_WATCH=1` → on; WSL `/mnt/*` → off unless forced. | Explicit override wins | +| L.3 | When disabled, **stderr explains why** and points to [git-hook-auto-sync](./git-hook-auto-sync.md). | Layered freshness stack | + +--- + +## Implementation steps + +1. **Add `src/application/watch-policy.ts`** + - `detectWsl()` — read `/proc/version` or `WSL_DISTRO_NAME` + - `isWindowsDriveMount(root)` — path starts with `/mnt/` + single letter + - `watchDisabledReason(root, env?)` → `string | null` +2. **Integrate in `src/application/watcher.ts` and `cmd-mcp.ts` / `cmd-serve.ts`** + - Skip watcher start when reason non-null; log reason once +3. **Tests** — unit tests with injected probe (no real WSL required) +4. **Docs** — [architecture.md](../architecture.md) freshness section; env vars in README + +--- + +## Acceptance + +- [ ] Project on `/mnt/c/...` under WSL2 skips watcher by default with actionable message +- [ ] `CODEMAP_FORCE_WATCH=1` overrides detection +- [ ] Existing `CODEMAP_WATCH=0` behavior unchanged + +--- + +## Dependencies + +Pairs with [git-hook-auto-sync](./git-hook-auto-sync.md) as fallback. From e2ce2d670d252e26e56587ffde3ca89aaf25cede Mon Sep 17 00:00:00 2001 From: Sutu Sebastian Date: Mon, 25 May 2026 11:15:19 +0300 Subject: [PATCH 13/13] docs(roadmap): link agent surface and indexing ops plans. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add P0–P2 backlog section pointing at the new plan files, consolidate duplicate affected/route items, and tie FTS default-on and falsifiable benchmark work to their plans. --- docs/roadmap.md | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index fe3a37cf..3f331567 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -35,7 +35,7 @@ Every PR reviewer defends these. The reviewer tests embedded below are the canon Soft constraints — describe shipped reality. Decided-but-unshipped flips live in [§ Backlog](#backlog), not here. -- **Full-text search default-on** — opt-in FTS5 ships per the `--with-fts` CLI flag / `fts5: true` config field (default OFF; populates `source_fts` virtual table at index time). Default-on revisits a v2 size-tax measurement. +- **Full-text search default-on** — opt-in FTS5 ships per the `--with-fts` CLI flag / `fts5: true` config field (default OFF; populates `source_fts` virtual table at index time). Default-on decision gated on measurement — plan: [`plans/fts-default-on-evaluation.md`](./plans/fts-default-on-evaluation.md). - **No LSP engine** — no rename / go-to-definition / hover types. Read-side LSP-adjacent primitives (`show` / `snippet` / `impact`) ship as CLI / MCP / HTTP verbs (see [README § CLI](../README.md#cli)). LSP **diagnostic-push** server (recipes-as-`Diagnostic[]`) is a separate roadmap item tracked at [`plans/lsp-diagnostic-push.md`](./plans/lsp-diagnostic-push.md). - **No opinionated rule engine / fix engine / severity levels** — verdict-shaped lints (`knip`, `jscpd`, `eslint`) are a different product class. Predicate-as-API recipes (`untested-and-dead`, `worst-covered-exports`, `visibility-tags`, `barrel-files`, `deprecated-symbols`, …) are in scope and shipping; they're upstream of [Moat A](#moats-load-bearing). **Suppression comments** ship as opt-in substrate (`// codemap-ignore-{next-line,file} ` → `suppressions` table; recipes JOIN to honor) — no severity, no suppression-by-default, no universal-honor; consumer-chosen, not policy. - **No renderer runtime** — skyline / ASCII art / animated diagrams; the index emits structured rows. Shape-only output formatters (`--format mermaid` shipped; `--format sarif` / `annotations` for CI; D2 / Graphviz on demand) are in scope. @@ -52,16 +52,48 @@ Soft constraints — describe shipped reality. Decided-but-unshipped flips live ## Backlog +### Agent & indexing ops + +Prioritized agent & indexing ops queue (2026-05). Index: [`plans/agent-surface-and-ops.md`](./plans/agent-surface-and-ops.md). + +**P0 — quick wins** + +- [ ] **MCP server instructions** — tool-selection playbook in MCP `initialize`. Plan: [`plans/mcp-server-instructions.md`](./plans/mcp-server-instructions.md). Effort: S. +- [ ] **WSL watch policy** — disable unreliable watcher on `/mnt/*`; env overrides. Plan: [`plans/wsl-watch-policy.md`](./plans/wsl-watch-policy.md). Effort: S. +- [ ] **Git hook auto-sync** — opt-in background incremental index when watcher off. Plan: [`plans/git-hook-auto-sync.md`](./plans/git-hook-auto-sync.md). Effort: S. +- [ ] **MCP tool allowlist** — `CODEMAP_MCP_TOOLS` env for subset registration. Plan: [`plans/mcp-tool-allowlist.md`](./plans/mcp-tool-allowlist.md). Effort: S. + +**P1 — medium** + +- [ ] **MCP trace / explore / node** — recipe twins + thin MCP composers. Plan: [`plans/mcp-trace-explore-tools.md`](./plans/mcp-trace-explore-tools.md). Effort: M. +- [ ] **Agents init MCP wiring** — `agents init --mcp` + permissions. Plan: [`plans/agents-init-mcp-wiring.md`](./plans/agents-init-mcp-wiring.md). Effort: M. +- [ ] **Affected tests recipe** — dep-graph test selection + stdin. Plan: [`plans/affected-tests-recipe.md`](./plans/affected-tests-recipe.md). Effort: M. +- [ ] **Index lock + error log** — cross-process lock, `unlock`, `errors.log`. Plan: [`plans/index-lock-and-error-log.md`](./plans/index-lock-and-error-log.md). Effort: M. +- [ ] **Parse worker hardening** — per-file timeout + worker recycle. Plan: [`plans/parse-worker-hardening.md`](./plans/parse-worker-hardening.md). Effort: M. +- [ ] **Field-qualified search** — `kind:` / `path:` / `name:` → SQL. Plan: [`plans/field-qualified-search.md`](./plans/field-qualified-search.md). Effort: M. +- [ ] **Agent eval harness** — A/B MCP tool-call + token metrics. Plan: [`plans/agent-eval-harness.md`](./plans/agent-eval-harness.md). Effort: M. + +**P2 — strategic (trigger-gated where noted)** + +- [ ] **Framework route extraction** — Express / React Router / NestJS `http_routes` substrate. Plan: [`plans/framework-route-extraction.md`](./plans/framework-route-extraction.md). Blocked on C.9 contract. Effort: L. +- [ ] **Callback dispatch synthesis** — heuristic `calls` with `provenance`. Plan: [`plans/callback-dispatch-synthesis.md`](./plans/callback-dispatch-synthesis.md). Effort: L. +- [ ] **Unresolved calls staging** — two-phase call resolution queue. Plan: [`plans/unresolved-calls-staging.md`](./plans/unresolved-calls-staging.md). Effort: L. +- [ ] **Cross-project MCP root** — optional `root` on tools + DB cache. Plan: [`plans/cross-project-mcp-root.md`](./plans/cross-project-mcp-root.md). Effort: M. +- [ ] **FTS default-on evaluation** — measure DB size tax; maybe flip default. Plan: [`plans/fts-default-on-evaluation.md`](./plans/fts-default-on-evaluation.md). Effort: S–M. +- [ ] **Call path + type hierarchy recipes** — `type-ancestors` / descendants. Plan: [`plans/call-path-type-hierarchy-recipes.md`](./plans/call-path-type-hierarchy-recipes.md). Effort: M. + +--- + +### Core substrate & platform + - [ ] **C.9 framework plugin layer** — static entry-point hints on `files` to sharpen reachability-predicate recipes (`untested-and-dead`, `unimported-exports`, future `dead-files-by-reachability`). Plan: [`plans/c9-plugin-layer.md`](./plans/c9-plugin-layer.md). Effort: XL; ships last in the impact-vs-cadence sequence (see plan § Shipping cadence). - [ ] **LSP diagnostic-push + VSCode extension** — recipes-as-`Diagnostic[]` server + paired extension; explicitly **not** a go-to-def / references shim (`tsserver` covers those). Plan: [`plans/lsp-diagnostic-push.md`](./plans/lsp-diagnostic-push.md). Effort: XL; soft ordering after C.9 for cleaner squigglies on framework files. - [ ] **Apply-engine direction** — diff-shape recipes, per-row `actions[].command`, `apply --rows` / `--diff-input` / fixpoint loop. Substrate tiers 1–6 shipped; open steps 2–12. Plan: [`plans/apply-engine-direction.md`](./plans/apply-engine-direction.md). -- [ ] **Test-impact / `affected` recipe** (trigger-gated) — codemap has the dep graph substrate. **Revisit when:** a consumer asks for CI test-selection from indexed deps + a documented test-file convention. Effort: M. -- [ ] **Framework route extraction** (trigger-gated) — Express / Next / React Router route nodes (peer tools extract these; codemap is TS/JS-deep but route-aware). **Revisit when:** C.9 plugin contract exists OR ≥2 consumers ask with concrete framework targets. Effort: High; likely extends C.9 plugin shape, not a standalone engine. - [ ] **`history` table** (deferred — revisit-triggered) — temporal queries: "when did symbol X get `@deprecated`?", "coverage trend over last 50 commits", "files that became dead this week". `audit --base ` covers the most-common temporal question (PR-scoped diff) without schema growth, so the table earns its place only when bigger questions emerge. Two shapes (per-commit snapshots ~N × DB size; append-only event log heavier CTE walks); both pay an N-reindexes backfill cost (~30s per reindex). **Revisit triggers:** two consumers ship `jq`-based "audit-runs-over-time" workflows, OR `query_baselines` evolution becomes a recurring agent need. - [ ] **`codemap audit` verdict + thresholds** (v1.x) — `verdict: "pass" | "warn" | "fail"` driven by an `audit.deltas[].{added_max, action}` field on the config object (`.codemap/config.{ts,js,json}`). Triggers: two consumers ship `jq`-based threshold scripts with similar shapes, OR one consumer asks with a concrete config sketch. Until then, raw deltas + consumer-side `jq` is the CI exit-code idiom. **Likely accelerant:** the Marketplace Action (next item) shipping is the most plausible path to firing the trigger — once `- uses: stainless-code/codemap@v1` is the dominant CI path, real `jq` threshold scripts will surface. - [ ] **GitHub Marketplace Action — publish + listing finish** — core Action implementation is in-tree: root `action.yml`, `query --ci`, `audit --format sarif` / `--ci`, package-manager detection, dogfood smoke, and opt-in `pr-comment` summary renderer have shipped. Remaining work is the release/listing slice: `MARKETPLACE.md`, `v1.0.0` / floating `v1` tags, Marketplace setup, sacrificial-repo smoke, and making `action-smoke` blocking once the Action tag exists. Action version stream is independent of CLI version (`package.json` currently drives CLI/npm version; Action publishes at its own `v1.0.0`). Plan: [`plans/github-marketplace-action.md`](./plans/github-marketplace-action.md). Effort: S. - [ ] **AST-hash duplication** — `symbols.body_hash` column (normalized AST hash via oxc, computed at parse time — Rust-native, fast) + bundled `duplicates.sql` recipe joining on `body_hash` (`SELECT * FROM symbols GROUP BY body_hash HAVING COUNT(*) > 1`). **Different shape from token-level suffix-array dupes** (catches structurally-identical functions, not copy-paste with renamed variables). Substrate addition — consumer writes the JOIN that decides "this is a problem"; no severity, no suppression-by-default. Effort: ~2 weeks (M). **Needs a plan PR before impl** — design questions: which oxc visitor scope (function bodies only? expressions? include comments?), what counts as "structurally identical" (rename-aware? whitespace-tolerant?), schema delta. -- [ ] **Falsifiable benchmark CI on named external fixtures** — codemap vs `find` + `grep` + `Read`-loop agent-discovery on zod, fastify, vue-core, next.js. Numbers land in [`docs/benchmark.md`](./benchmark.md); ~3 surface in `MARKETPLACE.md`. Replaces the unfalsifiable "sub-millisecond" claim with named-fixture comparisons any consumer can re-run. Effort: M. **Self-index regression guardrail already shipped** (#96 + #99 + #100): `bun run check:perf-baseline` + the `📈 Perf baseline (self-index)` CI hard gate on per-phase walls vs `fixtures/benchmark/perf-baseline.json`. This roadmap item is the external-fixture extension. +- [ ] **Falsifiable benchmark CI on named external fixtures** — codemap vs `find` + `grep` + `Read`-loop agent-discovery on zod, fastify, vue-core, next.js. Numbers land in [`docs/benchmark.md`](./benchmark.md); ~3 surface in `MARKETPLACE.md`. Plan: [`plans/agent-eval-harness.md`](./plans/agent-eval-harness.md) (harness) + external fixture extension. Effort: M. **Self-index regression guardrail already shipped** (#96 + #99 + #100): `bun run check:perf-baseline` + the `📈 Perf baseline (self-index)` CI hard gate on per-phase walls vs `fixtures/benchmark/perf-baseline.json`. - [ ] **Perf-triangulation deferrals (trigger-gated)** — Tier 5.2 / 5.4 / 5.6 / 5.7 / 6.1 / 6.2 from [`plans/perf-triangulation-rollout.md`](./plans/perf-triangulation-rollout.md) (Phases 0-2 + Phase 5 shipped; per-model audit + triangulation source content consolidated into the rollout plan 2026-05-18). Each ships when its trigger fires: - **5.2 IPC encoding (CBOR / transferables)** — after a `parse_ms_pure_worker` instrumentation split shows IPC > ~30% of `parse_ms`. - **5.4 `extractMarkers` lineMap reuse on TS/JS** — if marker extraction becomes hot on >10k-file trees (~1ms on this repo today).