diff --git a/.changeset/mcp-instructions-allowlist.md b/.changeset/mcp-instructions-allowlist.md new file mode 100644 index 0000000..fccab34 --- /dev/null +++ b/.changeset/mcp-instructions-allowlist.md @@ -0,0 +1,5 @@ +--- +"@stainless-code/codemap": patch +--- + +Add MCP initialize server instructions (tool-selection playbook) and `CODEMAP_MCP_TOOLS` env for subset tool registration. diff --git a/README.md b/README.md index 46798e6..3c5331b 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,7 @@ codemap agents init --force codemap agents init --interactive # -i; IDE wiring + symlink vs copy ``` -**Environment / flags:** `--root` overrides **`CODEMAP_ROOT`** / **`CODEMAP_TEST_BENCH`**, then **`process.cwd()`**; **`--state-dir`** overrides **`CODEMAP_STATE_DIR`** (default `.codemap/`); **`CODEMAP_WATCH=0`** opts out of the default-ON watcher on `mcp` / `serve` (mirrors `--no-watch`). Indexing a project outside this clone: [docs/benchmark.md § Indexing another project](docs/benchmark.md#indexing-another-project). +**Environment / flags:** `--root` overrides **`CODEMAP_ROOT`** / **`CODEMAP_TEST_BENCH`**, then **`process.cwd()`**; **`--state-dir`** overrides **`CODEMAP_STATE_DIR`** (default `.codemap/`); **`CODEMAP_WATCH=0`** opts out of the default-ON watcher on `mcp` / `serve` (mirrors `--no-watch`); **`CODEMAP_MCP_TOOLS`** registers a subset of MCP tools (comma-separated snake_case names; see [agents.md § MCP tool allowlist](docs/agents.md#mcp-tool-allowlist)). Indexing a project outside this clone: [docs/benchmark.md § Indexing another project](docs/benchmark.md#indexing-another-project). **Configuration:** optional **`/config.{ts,js,json}`** (default `.codemap/config.*`; default export object or async factory). Shape: [codemap.config.example.json](codemap.config.example.json). Runtime validation (**Zod**, strict keys) and API surface: [docs/architecture.md § User config](docs/architecture.md#user-config). When developing inside this repo you can use `defineConfig` from `@stainless-code/codemap` or `./src/config`. If you set **`include`**, it **replaces** the default glob list entirely. **Self-healing files (D11):** `/.gitignore` is rewritten to canonical on every codemap boot; JSON config gets unknown-key pruning + key-sort drift; TS/JS configs are validate-only. diff --git a/docs/agents.md b/docs/agents.md index ab99f73..188d375 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -78,7 +78,25 @@ 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 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). +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/mcp-instructions; recipes, files, and symbols read live every call. The CLI re-assembles every call (cheap — markdown read + concat). + +## MCP server instructions + +`codemap mcp` passes a tool-selection playbook in the MCP **`initialize`** response **`instructions`** field. MCP clients (Cursor, Claude Code, etc.) inject this into the agent system prompt — operational guidance only (which tool when, common chains, anti-patterns). Full schema and recipe catalog stay on **`codemap://skill`** / **`codemap://rule`**. + +| Surface | URI / field | +| ------------------- | -------------------------------------------------------------------------------------------------------------- | +| MCP initialize | `instructions` on handshake | +| MCP / HTTP resource | `codemap://mcp-instructions` | +| Source file | `templates/agent-content/mcp-instructions.md` (assembled by `assembleMcpInstructions()` in `agent-content.ts`) | + +Recipe ids cited in the playbook are machine-validated in tests against the live catalog (`extractMcpInstructionRecipeIds`). + +## MCP tool allowlist + +**`CODEMAP_MCP_TOOLS`** — comma-separated snake_case MCP tool names. When set, only listed tools register (stderr lists the active set). Unknown names are ignored with a warning. Unset = all tools (default). **`query_batch`** registers only when listed or when unset (eval ablation). + +Example: `CODEMAP_MCP_TOOLS=query,context,show codemap mcp --no-watch` ## Section assembler and `*.gen.md` diff --git a/docs/packaging.md b/docs/packaging.md index adfdcf2..6e2f951 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -10,7 +10,7 @@ How **@stainless-code/codemap** is built and published. **Doc index:** [README.m The `templates/` directory ships two parallel subtrees: - **`templates/agents/`** — consumer-disk targets copied by `codemap agents init` (thin pointer files: ~16-line SKILL.md + ~22-line rule). -- **`templates/agent-content/`** — server-side source assembled live by `codemap skill` / `codemap rule` / `codemap://skill` / `codemap://rule`. Section files in `agent-content/skill/` concatenate in lexical order; `*.gen.md` files are replaced at fetch time by renderers in `src/application/agent-content.ts`. See [agents.md](./agents.md#section-assembler-and-genmd) for the split rationale. +- **`templates/agent-content/`** — server-side source assembled live by `codemap skill` / `codemap rule` / `codemap://skill` / `codemap://rule` / `codemap://mcp-instructions`. Section files in `agent-content/skill/` concatenate in lexical order; `*.gen.md` files are replaced at fetch time by renderers in `src/application/agent-content.ts`. Root-level `mcp-instructions.md` feeds MCP initialize `instructions`. See [agents.md](./agents.md#section-assembler-and-genmd) for the split rationale. - **`templates/recipes/`** — bundled SQL recipe `.sql` + `.md` pairs (every recipe in `--recipes-json`). ## Consuming locally diff --git a/src/application/agent-content.ts b/src/application/agent-content.ts index 6e2c746..1fb5732 100644 --- a/src/application/agent-content.ts +++ b/src/application/agent-content.ts @@ -39,6 +39,25 @@ export function resolveAgentContentDir(): string { return join(resolveAgentsTemplateDir(), "..", "agent-content"); } +const MCP_INSTRUCTIONS_FILE = "mcp-instructions.md"; +const MCP_RECIPE_REFS_RE = //; + +/** MCP initialize playbook — `templates/agent-content/mcp-instructions.md`. */ +export function assembleMcpInstructions(): string { + const path = join(resolveAgentContentDir(), MCP_INSTRUCTIONS_FILE); + return readFileSync(path, "utf8").trimEnd() + "\n"; +} + +/** Recipe ids declared in the MCP instructions machine-ref comment. */ +export function extractMcpInstructionRecipeIds(content: string): string[] { + const match = content.match(MCP_RECIPE_REFS_RE); + if (match === null) return []; + return match[1]! + .split(",") + .map((id) => id.trim()) + .filter(Boolean); +} + /** * Renderer registry — keyed by `/`. Files ending in * `.gen.md` are treated as generated content: if a renderer is diff --git a/src/application/mcp-server.test.ts b/src/application/mcp-server.test.ts index a0f0b30..7fdbc3c 100644 --- a/src/application/mcp-server.test.ts +++ b/src/application/mcp-server.test.ts @@ -33,8 +33,12 @@ afterEach(() => { rmSync(benchDir, { recursive: true, force: true }); }); -async function makeClient() { - const server = createMcpServer({ version: "0.0.0-test", root: benchDir }); +async function makeClient(env?: NodeJS.ProcessEnv) { + const server = createMcpServer({ + version: "0.0.0-test", + root: benchDir, + env, + }); const client = new Client({ name: "test-client", version: "0.0.0" }); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); @@ -54,6 +58,62 @@ function readJson(result: unknown): any { return JSON.parse(first.text) as unknown; } +describe("MCP server — initialize instructions", () => { + it("includes tool-selection playbook in initialize handshake", async () => { + const { client, server } = await makeClient(); + try { + const instructions = client.getInstructions(); + expect(instructions).toBeDefined(); + expect(instructions!.length).toBeGreaterThan(500); + expect(instructions).toContain("Session start"); + expect(instructions).toContain("codemap://rule"); + } finally { + await server.close(); + } + }); + + it("cites only shipped recipe ids", async () => { + const { assembleMcpInstructions, extractMcpInstructionRecipeIds } = + await import("./agent-content"); + const { listQueryRecipeCatalog } = await import("./query-recipes"); + const cited = extractMcpInstructionRecipeIds(assembleMcpInstructions()); + expect(cited.length).toBeGreaterThan(0); + const catalog = new Set(listQueryRecipeCatalog().map((e) => e.id)); + for (const id of cited) { + expect(catalog.has(id)).toBe(true); + } + }); +}); + +describe("MCP server — tool allowlist", () => { + it("registers only listed tools when CODEMAP_MCP_TOOLS is set", async () => { + const { client, server } = await makeClient({ + CODEMAP_MCP_TOOLS: "query,show", + }); + try { + const tools = await client.listTools(); + const names = tools.tools.map((t) => t.name).sort(); + expect(names).toEqual(["query", "show"]); + } finally { + await server.close(); + } + }); + + it("excludes query_batch unless explicitly listed", async () => { + const { client, server } = await makeClient({ + CODEMAP_MCP_TOOLS: "query", + }); + try { + const tools = await client.listTools(); + const names = tools.tools.map((t) => t.name); + expect(names).toContain("query"); + expect(names).not.toContain("query_batch"); + } finally { + await server.close(); + } + }); +}); + describe("MCP server — query tool", () => { it("lists query and query_batch in tools/list", async () => { const { client, server } = await makeClient(); @@ -717,6 +777,7 @@ describe("MCP server — resources", () => { expect(uris).toContain("codemap://schema"); expect(uris).toContain("codemap://skill"); expect(uris).toContain("codemap://rule"); + expect(uris).toContain("codemap://mcp-instructions"); // The recipe-by-id resource is a template — surfaced via list-template // callback as one entry per recipe id. const recipeUris = uris.filter((u) => u.startsWith("codemap://recipes/")); @@ -815,6 +876,22 @@ describe("MCP server — resources", () => { } }); + it("codemap://mcp-instructions returns the MCP playbook", async () => { + const { client, server } = await makeClient(); + try { + const r = await client.readResource({ + uri: "codemap://mcp-instructions", + }); + const first = r.contents[0] as { mimeType?: string }; + expect(first.mimeType).toBe("text/markdown"); + const text = readResourceText(r); + expect(text).toContain("tool selection"); + expect(text).toContain("Session start"); + } finally { + await server.close(); + } + }); + it("codemap://files/{path} returns a per-file roll-up", async () => { const { client, server } = await makeClient(); try { diff --git a/src/application/mcp-server.ts b/src/application/mcp-server.ts index 009a5a5..97b261a 100644 --- a/src/application/mcp-server.ts +++ b/src/application/mcp-server.ts @@ -12,6 +12,13 @@ import { getTsconfigPath, initCodemap, } from "../runtime"; +import { assembleMcpInstructions } from "./agent-content"; +import { + isMcpToolEnabled, + logMcpToolAllowlist, + resolveMcpToolAllowlist, +} from "./mcp-tool-allowlist"; +import type { McpToolName } from "./mcp-tool-allowlist"; import { listQueryRecipeCatalog } from "./query-recipes"; import { readResource } from "./resource-handlers"; import type { ResourcePayload } from "./resource-handlers"; @@ -67,6 +74,8 @@ interface ServerOpts { root: string; configFile?: string | undefined; stateDir?: string | undefined; + /** Test hook — defaults to `process.env`. */ + env?: NodeJS.ProcessEnv | undefined; /** * If true, boot a co-process file watcher (chokidar via * `runWatchLoop`) so the server's tools always read live data without @@ -107,25 +116,39 @@ function wrapToolResult(r: ToolResult) { * `InMemoryTransport.createLinkedPair()` for in-process driving). */ export function createMcpServer(opts: ServerOpts): McpServer { - const server = new McpServer({ - name: "codemap", - version: opts.version, - }); + const allowlistResolved = resolveMcpToolAllowlist(opts.env ?? process.env); + const server = new McpServer( + { + name: "codemap", + version: opts.version, + }, + { + instructions: assembleMcpInstructions(), + }, + ); + + const registered: McpToolName[] = []; + const maybeRegister = (name: McpToolName, register: () => void): void => { + if (!isMcpToolEnabled(name, allowlistResolved.allowlist)) return; + register(); + registered.push(name); + }; - registerQueryTool(server, opts); - registerQueryBatchTool(server, opts); - registerQueryRecipeTool(server, opts); - registerAuditTool(server); - registerContextTool(server); - registerValidateTool(server); - registerSaveBaselineTool(server, opts); - registerListBaselinesTool(server); - registerDropBaselineTool(server); - registerShowTool(server, opts); - registerSnippetTool(server, opts); - registerImpactTool(server); - registerApplyTool(server, opts); + maybeRegister("query", () => registerQueryTool(server, opts)); + maybeRegister("query_batch", () => registerQueryBatchTool(server, opts)); + maybeRegister("query_recipe", () => registerQueryRecipeTool(server, opts)); + maybeRegister("audit", () => registerAuditTool(server)); + maybeRegister("context", () => registerContextTool(server)); + maybeRegister("validate", () => registerValidateTool(server)); + maybeRegister("save_baseline", () => registerSaveBaselineTool(server, opts)); + maybeRegister("list_baselines", () => registerListBaselinesTool(server)); + maybeRegister("drop_baseline", () => registerDropBaselineTool(server)); + maybeRegister("show", () => registerShowTool(server, opts)); + maybeRegister("snippet", () => registerSnippetTool(server, opts)); + maybeRegister("impact", () => registerImpactTool(server)); + maybeRegister("apply", () => registerApplyTool(server, opts)); registerResources(server); + logMcpToolAllowlist(allowlistResolved, registered); return server; } @@ -320,6 +343,12 @@ function registerResources(server: McpServer): void { "codemap://rule", "Full text of the bundled `templates/agents/rules/codemap.md` (always-on priming for agents working in this repo).", ); + registerStaticResource( + server, + "mcp-instructions", + "codemap://mcp-instructions", + "MCP initialize tool-selection playbook (operational guidance only; full catalog in codemap://skill).", + ); // codemap://recipes/{id} — one recipe (template form). Payload includes // `body` / `source` / `shadows` from the catalog entry — session-start diff --git a/src/application/mcp-tool-allowlist.test.ts b/src/application/mcp-tool-allowlist.test.ts new file mode 100644 index 0000000..c98ff1b --- /dev/null +++ b/src/application/mcp-tool-allowlist.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from "bun:test"; + +import { + isMcpToolEnabled, + resolveMcpToolAllowlist, +} from "./mcp-tool-allowlist"; + +describe("mcp-tool-allowlist", () => { + it("returns null allowlist when env unset", () => { + expect(resolveMcpToolAllowlist({})).toEqual({ + allowlist: null, + unknown: [], + }); + }); + + it("returns null allowlist when env is whitespace", () => { + expect(resolveMcpToolAllowlist({ CODEMAP_MCP_TOOLS: " " })).toEqual({ + allowlist: null, + unknown: [], + }); + }); + + it("parses comma-separated tool names", () => { + const { allowlist, unknown } = resolveMcpToolAllowlist({ + CODEMAP_MCP_TOOLS: "query, show", + }); + expect(unknown).toEqual([]); + expect(allowlist).toEqual(new Set(["query", "show"])); + }); + + it("ignores unknown names without failing", () => { + const { allowlist, unknown } = resolveMcpToolAllowlist({ + CODEMAP_MCP_TOOLS: "query,not_a_tool,show", + }); + expect(unknown).toEqual(["not_a_tool"]); + expect(allowlist).toEqual(new Set(["query", "show"])); + }); + + it("query_batch is excluded unless explicitly listed", () => { + const { allowlist } = resolveMcpToolAllowlist({ + CODEMAP_MCP_TOOLS: "query,show", + }); + expect(isMcpToolEnabled("query_batch", allowlist)).toBe(false); + expect( + isMcpToolEnabled( + "query_batch", + resolveMcpToolAllowlist({ CODEMAP_MCP_TOOLS: "query_batch" }).allowlist, + ), + ).toBe(true); + }); + + it("isMcpToolEnabled allows all when allowlist is null", () => { + expect(isMcpToolEnabled("query_batch", null)).toBe(true); + }); +}); diff --git a/src/application/mcp-tool-allowlist.ts b/src/application/mcp-tool-allowlist.ts new file mode 100644 index 0000000..30c488f --- /dev/null +++ b/src/application/mcp-tool-allowlist.ts @@ -0,0 +1,77 @@ +/** + * Subset registration for MCP tools via `CODEMAP_MCP_TOOLS` (comma-separated + * snake_case names). Unset = all tools. Used by eval A/B arms and minimal installs. + */ + +export const MCP_TOOL_NAMES = [ + "query", + "query_batch", + "query_recipe", + "audit", + "context", + "validate", + "save_baseline", + "list_baselines", + "drop_baseline", + "show", + "snippet", + "impact", + "apply", +] as const; + +export type McpToolName = (typeof MCP_TOOL_NAMES)[number]; + +const KNOWN = new Set(MCP_TOOL_NAMES); + +export interface McpToolAllowlistResult { + /** `null` when env unset — register every tool. */ + allowlist: Set | null; + unknown: string[]; +} + +export function resolveMcpToolAllowlist( + env: NodeJS.ProcessEnv = process.env, +): McpToolAllowlistResult { + const raw = env.CODEMAP_MCP_TOOLS?.trim(); + if (raw === undefined || raw === "") { + return { allowlist: null, unknown: [] }; + } + const allowlist = new Set(); + const unknown: string[] = []; + for (const part of raw.split(",")) { + const name = part.trim(); + if (name === "") continue; + if (KNOWN.has(name)) { + allowlist.add(name as McpToolName); + } else { + unknown.push(name); + } + } + return { allowlist, unknown }; +} + +export function isMcpToolEnabled( + name: McpToolName, + allowlist: Set | null, +): boolean { + if (allowlist === null) return true; + return allowlist.has(name); +} + +export function logMcpToolAllowlist( + resolved: McpToolAllowlistResult, + registered: readonly string[], +): void { + for (const name of resolved.unknown) { + // eslint-disable-next-line no-console -- intentional MCP bootstrap log on stderr + console.error( + `codemap mcp: ignoring unknown CODEMAP_MCP_TOOLS entry "${name}"`, + ); + } + if (resolved.allowlist !== null) { + // eslint-disable-next-line no-console -- intentional MCP bootstrap log on stderr + console.error( + `codemap mcp: CODEMAP_MCP_TOOLS active — registered: ${registered.join(", ")}`, + ); + } +} diff --git a/src/application/resource-handlers.ts b/src/application/resource-handlers.ts index 04aa6af..b02c73e 100644 --- a/src/application/resource-handlers.ts +++ b/src/application/resource-handlers.ts @@ -2,9 +2,10 @@ * Pure transport-agnostic resource fetchers — every MCP resource the * codemap server exposes (`codemap://recipes`, `codemap://recipes/{id}`, * `codemap://schema`, `codemap://skill`, `codemap://rule`, - * `codemap://files/{path}`, `codemap://symbols/{name}`) is also reachable + * `codemap://mcp-instructions`, `codemap://files/{path}`, + * `codemap://symbols/{name}`) is also reachable * over HTTP via `GET /resources/{encoded-uri}`. Lazy-cached: - * `schema` / `skill` / `rule` (constant for the process lifetime). + * `schema` / `skill` / `rule` / `mcp-instructions` (constant for the process lifetime). * Live read-per-call: `recipes` / `recipes/{id}` (recipes carry inline * recency fields that must reflect mutations during the server lifetime), * `files/{path}` / `symbols/{name}` (the index can change between calls @@ -12,7 +13,7 @@ */ import { closeDb, openDb } from "../db"; -import { assembleAgentContent } from "./agent-content"; +import { assembleAgentContent, assembleMcpInstructions } from "./agent-content"; import { getQueryRecipeCatalogEntry, listQueryRecipeCatalog, @@ -33,6 +34,7 @@ export interface ResourcePayload { let schemaCache: ResourcePayload | undefined; let skillCache: ResourcePayload | undefined; let ruleCache: ResourcePayload | undefined; +let mcpInstructionsCache: ResourcePayload | undefined; /** * Test-only escape hatch — drops every cached payload so a temp-DB test @@ -42,6 +44,7 @@ export function _resetResourceCachesForTests(): void { schemaCache = undefined; skillCache = undefined; ruleCache = undefined; + mcpInstructionsCache = undefined; } /** @@ -62,6 +65,7 @@ export function readResource(uri: string): ResourcePayload | undefined { if (uri === "codemap://schema") return readSchema(); if (uri === "codemap://skill") return readSkill(); if (uri === "codemap://rule") return readRule(); + if (uri === "codemap://mcp-instructions") return readMcpInstructions(); if (uri.startsWith("codemap://files/")) { const path = decodeURIComponent(uri.slice("codemap://files/".length)); return readFileResource(path); @@ -98,6 +102,10 @@ export function listResources(): { uri: string; description: string }[] { uri: "codemap://rule", description: "Full text of the bundled codemap rule markdown.", }, + { + uri: "codemap://mcp-instructions", + description: "MCP initialize tool-selection playbook.", + }, { uri: "codemap://files/{path}", description: @@ -176,6 +184,15 @@ function readRule(): ResourcePayload { return ruleCache; } +function readMcpInstructions(): ResourcePayload { + if (mcpInstructionsCache !== undefined) return mcpInstructionsCache; + mcpInstructionsCache = { + mimeType: "text/markdown", + text: assembleMcpInstructions(), + }; + return mcpInstructionsCache; +} + /** * Per-file roll-up: every shape codemap extracts about one file * (symbols, imports, exports, coverage). Returns `undefined` when the diff --git a/templates/agent-content/mcp-instructions.md b/templates/agent-content/mcp-instructions.md new file mode 100644 index 0000000..b83530a --- /dev/null +++ b/templates/agent-content/mcp-instructions.md @@ -0,0 +1,43 @@ +# Codemap MCP — tool selection + +Operational playbook injected into the MCP initialize handshake. Full schema, recipe catalog, and query patterns live in **`codemap://skill`** and **`codemap://rule`** (same content as `codemap skill` / `codemap rule` on CLI). + +## Session start + +1. **`context`** — project root, schema version, file/symbol counts, recipe summary (one call replaces 4–5 queries). +2. **`codemap://rule`** — always-on priming: query the index for structure, don't grep. +3. When you need the catalog or DDL: **`codemap://recipes`**, **`codemap://schema`**. + +## Common tasks + +| Goal | MCP tool | Recipe twin (`query_recipe`) | +| ----------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------ | +| Exact symbol lookup | **`show`** (`name`, optional `in`) | `find-symbol-definitions` | +| Kind / pattern lookup | **`query_recipe`** | `find-symbol-by-kind` | +| Source at symbol | **`snippet`** | same rows as `show` + disk text | +| Blast radius | **`impact`** (`target`, `direction`, `via`, `depth`) | `fan-in` for file hubs; symbol call graph via SQL or `impact` | +| CI / SARIF | **`query_recipe`** + `format: "sarif"` | `deprecated-symbols`, `boundary-violations`, … | +| Ad-hoc SQL | **`query`** | — | +| N statements / one round-trip | **`query_batch`** (MCP-only) | N × `query` | +| Index freshness | **`validate`** | — | +| Drift vs baseline | **`audit`** | saved via `save_baseline` + `query_recipe` / `query` | +| Apply recipe diff rows | **`apply`** | recipe must emit `{file_path, line_start, before_pattern, after_pattern}` rows | + +## Chains + +- Rename: `find-symbol-definitions` → `find-symbol-references` (both via **`query_recipe`**). +- Refactor risk: `fan-in` + `refactor-risk-ranking`. +- Edit path: **`show`** → **`snippet`**; if `stale: true`, line range may have drifted. + +## Anti-patterns + +- Don't grep for "where is X defined" — **`show`** or **`query_recipe`**. +- Don't hand-roll `WITH RECURSIVE` for impact — **`impact`**. +- Convenience tools are thin composers — fall back to **`query_recipe`** / **`query`** when unsure. +- Don't skip **`context`** at session start. + +## Recipe ids cited here + +`find-symbol-definitions`, `find-symbol-by-kind`, `find-symbol-references`, `fan-in`, `deprecated-symbols`, `boundary-violations`, `refactor-risk-ranking`. Others: list via **`codemap://recipes`** before **`query_recipe`**. + + diff --git a/templates/agent-content/skill/10-recipes-context.md b/templates/agent-content/skill/10-recipes-context.md index e7ae966..6c9aa54 100644 --- a/templates/agent-content/skill/10-recipes-context.md +++ b/templates/agent-content/skill/10-recipes-context.md @@ -55,6 +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 `/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://mcp-instructions`** — MCP initialize tool-selection playbook (also injected as `instructions` on handshake). - **`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.