|
| 1 | +# Codebase map in bootstrap responses — plan |
| 2 | + |
| 3 | +> **Status:** open · **Priority:** P2 (agent warm-path) · **Effort:** S–M (~3–5 days) |
| 4 | +> |
| 5 | +> **Motivator:** Agents call `context` at session start today but lack a compact, hash-stable routing card: which codemap CLI/MCP verbs to reach for first, and whether the structural summary changed since the last session. Roadmap marks this **partial** — `hubs`, `start_here.index_summary`, `index_freshness`, and `codemap.schema_version` already ship on `context`; **`cli_entry_hints`** and **`map_id`** do not. |
| 6 | +> |
| 7 | +> **Roadmap:** [§ Agent session & warm-path economics — Codebase map](../roadmap.md#agent-session--warm-path-economics) |
| 8 | +> |
| 9 | +> **Not in scope:** Framework entry-point substrate (`files.is_entry`) — tracked separately at [`c9-plugin-layer.md`](./c9-plugin-layer.md). “CLI entry hints” here means **codemap command/tool routing**, not app runtime entry files. |
| 10 | +
|
| 11 | +--- |
| 12 | + |
| 13 | +## Agent start here |
| 14 | + |
| 15 | +Read **`ContextEnvelope`** and **`composeStartHere`** in [`context-engine.ts`](../../src/application/context-engine.ts) first. Do not re-implement `start_here` — add a sibling **`codebase_map`** object and **`map_id`** at the envelope root. |
| 16 | + |
| 17 | +### Shipped today (do not rebuild) |
| 18 | + |
| 19 | +| Surface | Ships | Does not ship | |
| 20 | +| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | |
| 21 | +| **`context` tool** / `codemap context` | `codemap.cli_version`, `codemap.schema_version`, `project.*`, `recipes[]`, `index_freshness`, optional `hubs`, `sample_markers`, `start_here` (`index_summary`, intent recipe cards, `hub_leaders` + signatures) | `map_id`, `codebase_map`, `cli_entry_hints` | |
| 22 | +| **MCP `initialize`** | Markdown `instructions` from [`assembleMcpInstructions()`](../../src/application/agent-content.ts) (`templates/agent-content/mcp-instructions.md`) | JSON structural map; no auto-`context` call at boot ([`runMcpServer`](../../src/application/mcp-server.ts) only bootstraps DB + optional watch) | |
| 23 | +| **Opt-out** | `--compact` / MCP `compact: true` drops `hubs`, `sample_markers`, `start_here` ([`context-engine.ts:297-323`](../../src/application/context-engine.ts)) | No dedicated codebase-map opt-out flag | |
| 24 | + |
| 25 | +### Key touchpoints |
| 26 | + |
| 27 | +| File | Role | |
| 28 | +| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | |
| 29 | +| [`src/application/context-engine.ts`](../../src/application/context-engine.ts) | `ContextEnvelope` type, `buildContextEnvelope`, `composeStartHere`, `resolveContextBudget` | |
| 30 | +| [`src/application/index-freshness.ts`](../../src/application/index-freshness.ts) | `computeIndexFreshness` (already merged into envelope) | |
| 31 | +| [`src/application/tool-handlers.ts`](../../src/application/tool-handlers.ts) | `contextArgsSchema`, `handleContext` | |
| 32 | +| [`src/cli/cmd-context.ts`](../../src/cli/cmd-context.ts) | `--compact`, `--for`, `--include-snippets` argv | |
| 33 | +| [`src/application/mcp-server.ts`](../../src/application/mcp-server.ts) | `registerContextTool`, `createMcpServer` initialize `instructions` | |
| 34 | +| [`src/application/agent-content.ts`](../../src/application/agent-content.ts) | `assembleMcpInstructions` (Slice 2 hook) | |
| 35 | +| [`src/cli/aliases.ts`](../../src/cli/aliases.ts) | `OUTCOME_ALIASES` — five outcome-shaped CLI aliases | |
| 36 | +| [`src/application/mcp-tool-allowlist.ts`](../../src/application/mcp-tool-allowlist.ts) | `MCP_TOOL_NAMES` (21 tools) | |
| 37 | +| [`src/hash.ts`](../../src/hash.ts) | `hashContent` (SHA-256) for `map_id` | |
| 38 | +| [`src/application/context-engine.test.ts`](../../src/application/context-engine.test.ts) | Envelope + `composeStartHere` tests | |
| 39 | +| [`src/application/mcp-server.test.ts`](../../src/application/mcp-server.test.ts) | MCP `context` + initialize instructions | |
| 40 | +| [`templates/agent-content/mcp-instructions.md`](../../templates/agent-content/mcp-instructions.md) | Session-start playbook (Slice 2 cross-ref) | |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Pre-locked decisions |
| 45 | + |
| 46 | +| # | Decision | Source | |
| 47 | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | |
| 48 | +| L.1 | Add **`codebase_map`** on `ContextEnvelope` — sibling to `start_here`, not a replacement. | Preserve shipped `start_here` consumers | |
| 49 | +| L.2 | **`map_id`** = first **16** hex chars of `hashContent(JSON.stringify(canonical))` where `canonical` uses **sorted** `hub_paths: string[]`, `index_summary` object, `schema_version`, `file_count`, `last_indexed_commit` (from existing envelope fields). Same inputs → same id across transports. | [`hash.ts`](../../src/hash.ts); stable agent cache key | |
| 50 | +| L.3 | **`cli_entry_hints`** = structured static routing rows sourced from code constants — **not** inferred from indexed repo files. Minimum rows: (a) five [`OUTCOME_ALIASES`](../../src/cli/aliases.ts) (`dead-code` → `untested-and-dead`, …); (b) session-start MCP tools: `context`, `show`, `query_recipe`, `trace`, `explore`, `node`, `validate` (matches [`mcp-instructions.md` Session start](../../templates/agent-content/mcp-instructions.md)). Shape: `{ surface: "cli" \| "mcp", id: string, maps_to: string, note?: string }`. | Roadmap “CLI entry hints”; distinct from C.9 `is_entry` | |
| 51 | +| L.4 | **Opt-out:** new `--no-codebase-map` CLI flag + MCP/HTTP `include_codebase_map?: boolean` (default **true** when not `compact`). When `compact: true`, omit `codebase_map` and `map_id` regardless of flag. | Roadmap “opt-out via flag”; keep `compact` semantics | |
| 52 | +| L.5 | **No `SCHEMA_VERSION` bump** — JSON-only envelope fields. | Moat B discipline; no DDL | |
| 53 | +| L.6 | **MCP initialize Slice 2 (optional):** append a short auto-generated block to `assembleMcpInstructions()` output: `map_id` + top 3 `hub_paths` + link to call `context` for full map. MCP SDK exposes no structured initialize JSON beyond `instructions` ([`createMcpServer`](../../src/application/mcp-server.ts:157-164)). | Factual transport constraint | |
| 54 | + |
| 55 | +--- |
| 56 | + |
| 57 | +## Target envelope shape (Slice 1) |
| 58 | + |
| 59 | +```typescript |
| 60 | +// Added to ContextEnvelope in context-engine.ts |
| 61 | +map_id?: string; // omitted when compact / --no-codebase-map |
| 62 | +codebase_map?: { |
| 63 | + hub_paths: string[]; // from start_here.hub_leaders[].file_path (same budget cap) |
| 64 | + cli_entry_hints: { |
| 65 | + surface: "cli" | "mcp"; |
| 66 | + id: string; |
| 67 | + maps_to: string; |
| 68 | + note?: string; |
| 69 | + }[]; |
| 70 | +}; |
| 71 | +``` |
| 72 | + |
| 73 | +`map_id` is computed **after** `hub_paths` are known so agents can compare ids without re-fetching full `start_here`. |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +## Implementation slices |
| 78 | + |
| 79 | +| Slice | Scope | Ship gate | |
| 80 | +| ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | |
| 81 | +| **1** | Types + `buildCodebaseMap()` + `computeMapId()` in `context-engine.ts`; wire `handleContext` / `cmd-context`; tests in `context-engine.test.ts` + `tool-handlers.test.ts` | Tracer bullet — land first | |
| 82 | +| **2** | MCP initialize instructions append (requires DB at `assembleMcpInstructions` time **or** lazy placeholder + “call `context`”) — pick one in PR; update `mcp-instructions.md` + `mcp-server.test.ts` | Only after Slice 1 green | |
| 83 | +| **3** | Docs: [`architecture.md` § Context wiring](../architecture.md), [`agents.md`](../agents.md) bootstrap table; roadmap item → check `[x]` + delete this plan when closed | Same PR as Slice 1–2 or follow-up | |
| 84 | + |
| 85 | +### Tracer bullet (Slice 1) |
| 86 | + |
| 87 | +1. Add `buildCodebaseMap({ hubLeaders, compact, include })` returning `undefined` when `compact || !include`. |
| 88 | +2. Add `computeMapId(canonical)` using `hashContent`. |
| 89 | +3. Extend `contextArgsSchema` + `parseContextRest` with opt-out flag. |
| 90 | +4. Test: stable `map_id` for fixed fixture DB; opt-out omits fields; `compact` omits fields. |
| 91 | + |
| 92 | +### Out of scope |
| 93 | + |
| 94 | +- `files.is_entry` / framework route substrate ([`c9-plugin-layer.md`](./c9-plugin-layer.md), [`framework-route-extraction.md`](./framework-route-extraction.md)) |
| 95 | +- Auto-invoking `context` inside `runMcpServer` (extra index work on every MCP boot) |
| 96 | +- New MCP resource `codemap://map` (defer unless a consumer requests it) |
| 97 | +- Verdict / pass-fail on map freshness (Moat A) |
| 98 | + |
| 99 | +--- |
| 100 | + |
| 101 | +## Acceptance |
| 102 | + |
| 103 | +- [ ] `codemap context --json` includes `map_id` + `codebase_map.cli_entry_hints` with all five outcome aliases and seven session-start MCP tools (L.3) |
| 104 | +- [ ] Re-running `context` on unchanged index returns identical `map_id` |
| 105 | +- [ ] `codemap context --compact` and `--no-codebase-map` omit `map_id` and `codebase_map` |
| 106 | +- [ ] MCP `context` JSON matches CLI envelope (parity via `handleContext`) |
| 107 | +- [ ] `bun test src/application/context-engine.test.ts src/application/tool-handlers.test.ts src/application/mcp-server.test.ts` |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +## Verification |
| 112 | + |
| 113 | +```bash |
| 114 | +bun test src/application/context-engine.test.ts src/application/tool-handlers.test.ts |
| 115 | +bun src/index.ts context --json | jq '.map_id, .codebase_map.cli_entry_hints | length' |
| 116 | +bun src/index.ts context --no-codebase-map --json | jq 'has("map_id")' # expect false after implementation |
| 117 | +bun src/index.ts context --compact --json | jq 'has("codebase_map")' # expect false after implementation |
| 118 | +``` |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +## Dependencies |
| 123 | + |
| 124 | +- Shipped: `start_here`, `index_freshness`, `OUTCOME_ALIASES`, 21-tool MCP allowlist |
| 125 | +- Independent of: C.9 plugin layer, FTS default-on, tiered lookup fast paths |
0 commit comments