{"owner":"colbymchenry","repo":"codegraph","hasSkills":true,"totalSkillsCount":7,"totalTokensCount":21823,"categories":["claude-rule","anthropic-skill","root-instruction","cursor-rule"],"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md",".claude/skills/agent-eval/SKILL.md","docs/AGENTS.md","AGENTS.md",".cursor/rules/codegraph.mdc",".claude/skills/agent-eval/corpus.json",".claude/skills/add-lang/SKILL.md"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nClaude Code project guidance for this repository.\n\nPrimary instructions live in the canonical agent guide — import it:\n\n@AGENTS.md\n\n## Claude-only notes\n\n- Prefer the repo-root `AGENTS.md` as the source of truth for build/test/architecture/house rules. Edit that file (not this wrapper) when guidance changes.\n- Claude Code can `@`-import nested guides too (e.g. `@docs/AGENTS.md`) when working on design/eval docs; Codex loads nested `AGENTS.md` automatically when the session cwd is under that directory.\n- Do not reintroduce a duplicated `## CodeGraph` MCP tool-guidance block here — `src/mcp/server-instructions.ts` is the single source of truth (issue #529); the installer strips legacy marker blocks on upgrade.\n",".claude/skills/agent-eval/SKILL.md":"---\nname: agent-eval\ndescription: Benchmark CodeGraph retrieval quality on a real codebase by comparing agent behavior with vs without CodeGraph. Use when the user runs /agent-eval or asks to test, benchmark, audit, or validate a codegraph version (the local dev build or a published npm version) against a language's repo.\n---\n\n# CodeGraph Quality Audit\n\nMeasures how much CodeGraph helps an agent versus plain grep/read, for a chosen\ncodegraph version on a chosen real-world repo. Drives the harness in\n`scripts/agent-eval/`.\n\n## Prerequisites\n- `tmux` 3+, a logged-in `claude` CLI, `node`, `git` (macOS/Linux).\n- Run from the codegraph repo root.\n\n## Workflow\n\nCopy this checklist:\n```\n- [ ] 1. Pick version (local or npm)\n- [ ] 2. Pick language\n- [ ] 3. Pick repo by size\n- [ ] 4. Pick harness (headless / tmux / both)\n- [ ] 5. Run audit.sh in the background\n- [ ] 6. Report results\n```\n\n**Step 1 — version.** Ask with `AskUserQuestion`: which codegraph version to test.\nOffer \"Local dev build\" and \"Latest published\"; the free-text \"Other\" lets the\nuser type a specific version (e.g. `0.7.10`). Map the answer to a VERSION token:\n- \"Local dev build\" → `local`\n- \"Latest published\" → `latest`\n- a typed version → that string (e.g. `0.7.10`)\n\n**Step 2 — language.** Read `.claude/skills/agent-eval/corpus.json`. Ask with\n`AskUserQuestion` which language to test, listing the languages that have entries.\n\n**Step 3 — repo.** From the chosen language's entries, ask which repo. Label each\noption with its size and file count, e.g. `excalidraw — Medium (~600 files)`.\nEach entry carries the `repo` URL and a representative `question`.\n\n**Step 4 — harness.** Ask with `AskUserQuestion` which harness to run, and map\nthe answer to a MODE token:\n- \"Headless\" → `headless` — `claude -p` with stream-json: exact tokens/cost and a\n  clean tool sequence (2 runs, fast, no TTY).\n- \"Interactive (tmux)\" → `tmux` — drives the real Claude TUI in tmux: faithful\n  Explore-subagent behavior, metrics from session logs (2 runs, slower).\n- \"Both\" → `all` — headless + interactive (4 runs).\n\n**Step 5 — run.** Launch in the background (sets the version, clones if missing,\nwipes + re-indexes, runs the chosen arms — several minutes):\n```bash\nscripts/agent-eval/audit.sh <VERSION> <repo-name> <repo-url> \"<question>\" <MODE>\n```\n\n**Step 6 — report.** When the job finishes, read the log and report per arm:\n- Headless (`parse-run.mjs`): total tool calls, file `Read`s, Grep/Bash,\n  codegraph-tool calls, duration, **total cost**.\n- Interactive (`parse-session.mjs`): the `VERDICT: codegraph_explore used Nx |\n  Read N | Grep/Bash N` and `TOKENS:` lines.\n- Both paths also print the three feedback metrics — residual context occupancy,\n  explore sufficiency, allocation efficiency — and a headless A/B ends with a\n  side-by-side `ARM COMPARISON` table. Report that table, and check its\n  contamination row first: `CLI calls that RETURNED output` > 0 means the arm\n  reached codegraph through Bash and its numbers are void. How to read the rest:\n  `docs/benchmarks/agent-eval-feedback-metrics.md`.\n\nLead with cost + tool/Read counts — they are the reliable signals; raw token\nin/out are confounded by subagent delegation and prompt caching. State whether\ncodegraph reduced effort and whether both arms reached a correct answer.\n\n## Notes\n- The index is rebuilt every run (`audit.sh` wipes `.codegraph`) — different\n  versions extract differently, so an index must be served by the same binary\n  that built it.\n- `audit.sh` temporarily mutates the global `codegraph` install for the test,\n  then restores your dev link via `local-install.sh`.\n- Corpus repos are cloned to `/tmp/codegraph-corpus` (reused if already present).\n- Add or edit repos in `corpus.json` (fields: `name`, `repo`, `size`, `files`,\n  `question`).\n","docs/AGENTS.md":"# AGENTS.md (docs/)\n\nNested Codex guidance under `docs/`. Loaded with the repo-root `AGENTS.md` when cwd is under `docs/` (Codex walks root to cwd; shared `project_doc_max_bytes` budget).\n\nRoot `AGENTS.md` already carries the non-negotiable retrieval principles (adapt-the-tool, explore budgets, end-to-end synthesis). This file holds the longer validation methodology and Excalidraw worked example that were moved out of root to fit the budget.\n\n### Validation methodology (REQUIRED for every new language/framework)\n\nFor each **language × framework**, validate on **small, medium, and large** real repos with **≥3 different flow prompts** each:\n\n1. **Pick the canonical flow** for the framework (\"how does X reach Y\": state→render, request→handler→view, query→SQL, action→reducer→store…).\n2. **Deterministic probes** (`scripts/agent-eval/probe-{node,explore}.mjs` against the built `dist/`): `codegraph_explore` with the flow's symbol names connects from→to end-to-end with no break (its Flow section shows the path); **no node explosion** (`select count(*) from nodes` stable before/after re-index); synthesized-edge **precision** spot-check (`select … where provenance='heuristic'`).\n3. **Agent A/B** (`scripts/agent-eval/run-all.sh <repo> \"<Q>\"`): with vs without codegraph, **≥2 runs/arm** (run-to-run variance is large — never conclude from n=1). Record **duration, total tool calls, Read, Grep**. Optional forced-Read-0 sufficiency proof via the block-read hook (`scripts/agent-eval/hook-settings.json`).\n   - **Every run also reports three feedback metrics** — residual context occupancy, explore sufficiency (what the agent did NEXT after each explore), and allocation efficiency (share of returned bytes the answer cited) — under each run, plus a side-by-side arm table (`compare-arms.mjs`). Entry point: `docs/benchmarks/agent-eval-feedback-metrics.md`. Reading them: `Read a file we returned` is an allocation miss, `Read a file we did NOT return`/`Grep` is recall; allocation efficiency is **relative** (attribution is by citation) so it is only valid between builds on the same question; occupancy *shares* are Claude Code / 200k and don't transfer to another host — the arm ratio does.\n   - **The `codegraph` CLI is blocked in every arm** (`no-cli-shim.sh`: sanitized PATH + a PreToolUse hook, shared by both harnesses). Without it 14 of 15 without-arm runs in one 7-repo pass reached codegraph through Bash. Check the contamination row before believing any number: `CLI calls that RETURNED output` > 0 invalidates the run (in a new-vs-baseline A/B it silently drops calls from all three metrics, since a CLI explore is not a tool call).\n   - **Model policy — every A/B arm runs Claude with `--model sonnet --effort high`. Always. Never Opus/Fable.** All `scripts/agent-eval/*.sh` default to this (`MODEL`/`EFFORT` env override exists — don't raise it without an explicit reason from the maintainer). Two reasons, and the second matters more than cost: (a) Sonnet doesn't burn tokens; (b) **Sonnet is the deliberate floor model** — codegraph's real users attach it to whatever agent they already run (Cursor Composer, Gemini, etc.), so we validate on a \"dumber\" model on purpose: a stronger model's tool-use covers up the salience/sufficiency problems a weaker one exposes. An affordance that lands on Sonnet generalizes up to every host; one that only works on Opus/Fable doesn't generalize down to the agents most users actually have. Both arms always use the same model.\n   - **MCP attach is a startup-latency issue, not a hard block.** On a multi-step task the agent dives into Read/grep before codegraph finishes its ~2-3s startup (worse when the eval is itself run nested inside a Claude session, under CPU contention), so it runs with no codegraph. Fix: **pre-warm a persistent daemon** for the target (`CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS` high; spawn `serve --mcp --path <target> </dev/null &`; wait for `.codegraph/daemon.sock`) **and skip the startup re-exec** (`CODEGRAPH_WASM_RELAUNCHED=1`) so claude connects before the agent's first turn. Don't trust claude's `init` snapshot — it can read `status:\"pending\"` / 0 tools even when it then connects; judge by actual codegraph usage in `parse-run.mjs`'s `by type`. To isolate a change — **new-build vs baseline-build, both codegraph-on** (vs run-all.sh's with-vs-without) — use `scripts/agent-eval/ab-new-vs-baseline.sh <indexed-repo> \"<task>\" [baseline-ref]` (it bakes in the pre-warm).\n4. **Pass bar:** a normal flow question reaches **~0 Read/Grep within the repo's explore-call budget**, runs **faster** than without-codegraph, and shows **no regression on a control repo**. Record the numbers in `docs/design/dynamic-dispatch-coverage-playbook.md` (the coverage matrix).\n\nFull playbook + per-mechanism design: `docs/design/dynamic-dispatch-coverage-playbook.md` and `docs/design/callback-edge-synthesis.md`.\n\n### Worked example — Excalidraw (TS/React, medium, 643 files)\n\nThe template to replicate per language/framework. Question: *\"how does updating an element re-render the canvas on screen?\"* (the full flow crosses three React boundaries: observer callback, `setState`→`render`, and JSX child).\n\n| Stage | duration | Read | Grep | codegraph |\n|---|---|---|---|---|\n| Without codegraph | 115–139s | 9–10 | 10–11 | 0 |\n| Broken (explore-budget regression) | 131–139s | 5–10 | 3–5 | 6–14 |\n| Fixed (budget + msgs + synthesis) | 64–112s | 0–2 | 2–4 | 3–**10** |\n| + trace-first steering | **51–74s** | **0–2** | 0–4 | **3–4** |\n\nn=4 unhooked runs/stage, same prompt. After steering flow questions to `codegraph_trace` first: **best run 0 Read / 0 Grep / 3 codegraph / 51s**; **2 of 4 fully clean** (0 Read, 0 Grep). Steering eliminated the over-drill variance — call count tightened from 3–10 to 3–4, trace adoption went 3/4 → 4/4, and the `search`+`callers` path-reconstruction floundering dropped to 0. Run-to-run variance is still real; report the range, never a single run. **Residual reads/greps are all the nonce data-flow** (`canvasNonce` — a local prop with no graph edges); that's the def-use/data-flow frontier, left deliberately uncovered (tracking every local would explode the graph). Validated: `trace(mutateElement, renderStaticScene)` connects in **6 hops** across all three boundaries (`mutateElement → triggerUpdate → [callback] triggerRender → [react-render] render → [jsx] StaticCanvas → renderStaticScene`), each hop showing inline source + the wiring site; node count stable at 9,289; 1 callback + 46 react-render + 280 jsx-render synthesized edges (no explosion, precision-checked).\n\n\nAlso see: `docs/design/dynamic-dispatch-coverage-playbook.md`, `docs/design/callback-edge-synthesis.md`, `docs/benchmarks/call-sequence-analysis.md`, `docs/benchmarks/agent-eval-feedback-metrics.md`.\n","AGENTS.md":"# AGENTS.md\n\nCanonical project guidance for coding agents working in this repository (Codex/Astra, Claude Code via `@AGENTS.md`, Cursor, etc.).\n\n**Codex size note:** root `AGENTS.md` is intentionally kept near ~35 KiB (critical build/test/arch/retrieval rules retained). Longer validation methodology + the Excalidraw worked example live in `docs/AGENTS.md`. This environment sets `project_doc_max_bytes = 49152` so root (and root+`docs/` when cwd is under `docs/`) are not silently truncated at the 32 KiB default.\n\n## Project Overview\n\nCodeGraph is a local-first code intelligence library + CLI + MCP server. It parses any supported codebase with tree-sitter, stores symbols/edges/files in SQLite (FTS5), and exposes a knowledge graph to AI agents (Claude Code, Cursor, Codex CLI, opencode) over MCP. Per-project data lives in `.codegraph/`. Extraction is deterministic — derived from AST, not LLM-summarized.\n\nDistributed as `@colbymchenry/codegraph` on npm; same binary serves as installer, indexer, and MCP server.\n\n## Build, Test, Run\n\n```bash\nnpm run build           # tsc + copy schema.sql and *.wasm + build the viewer into dist/; chmods dist/bin/codegraph.js\nnpm run build:lib       # the viewer's components as @colbymchenry/codegraph-ui (ui/dist) — NOT part of `build`\nnpm run dev             # tsc --watch\nnpm run clean           # rm -rf dist\n\nnpm test                # vitest run (all)\nnpm run test:watch\nnpm run test:eval       # only __tests__/evaluation/\nnpm run eval            # build then run __tests__/evaluation/runner.ts via tsx\n\nnpm run cli             # build then run the local dist binary\n\n# Single test file / pattern\nnpx vitest run __tests__/installer-targets.test.ts\nnpx vitest run __tests__/extraction.test.ts -t \"TypeScript\"\n```\n\n`copy-assets` (called from `build`) copies `src/db/schema.sql` and all `src/extraction/wasm/*.wasm` files into `dist/`. **Any new SQL or grammar wasm must be copied or it won't ship.**\n\nOne other build step writes into `dist/` and is subject to the same rule: `build:ui` builds the\nbrowser viewer into `dist/viewer/` (never `dist/ui/` — that's the terminal ui).\n`scripts/check-ui-build.mjs` asserts both `dist/viewer/` and the copied grammars in\n`dist/extraction/wasm/` after every build and inside every release archive — the viewer's syntax\nhighlighting reads a file with the same grammar the engine indexed it with, so a missing wasm is an\nunhighlighted screen as well as an extraction gap.\n\n`npm run build:lib` is separate and does NOT run as part of `npm run build`: it compiles the same\n`ui/src` tree a second way, with `svelte-package`, into `ui/dist` — the `@colbymchenry/codegraph-ui`\ncomponent library the Pro app imports (task CG-61). `scripts/check-ui-package.mjs` then prunes the\nstandalone app's shell out of it, resolves the extensionless import specifiers `svelte-package`\nleaves behind, and asserts the seam: nothing outside `lib/adapter.js` may reach the network. The\npackage is **prepared, not published** — `ui/package.json` carries `\"private\": true` deliberately,\nand `scripts/pack-npm.sh` only packs a tarball when `CODEGRAPH_PACK_UI=1`.\n\nTests run as **two vitest projects** (`vitest.workspace.mts`): `engine` (node) and `ui` (jsdom, the\nSvelte plugin, `resolve.conditions: ['browser']`) for the single `__tests__/ui-package.test.ts`.\n`npm test` still runs both. The split is not cosmetic — `browser` is a package-resolution\ncondition, and applied globally it hands the engine's suites the browser builds of\n`web-tree-sitter` and friends. The root config (`vitest.config.mts`, `.mts` because the plugin is\nESM-only and the repo is CJS) is the shared base; note that a workspace project **concatenates**\nthe base's `include` with its own, which is why the `ui` project does not `extends` it.\n\nNode engines: `>=20.0.0 <25.0.0`. There is a hard exit on Node 25.x and below 20 (see `src/bin/node-version-check.ts`).\n\n## Architecture\n\n### Layered pipeline\n\n```\nfiles → ExtractionOrchestrator (tree-sitter) → DB (nodes/edges/files)\n              ↓\n       ReferenceResolver (imports, name-matching, framework patterns)\n              ↓\n       GraphQueryManager / GraphTraverser (callers, callees, impact)\n              ↓\n       ContextBuilder (markdown/JSON for AI consumption)\n```\n\nThe public API surface is `src/index.ts` — the `CodeGraph` class wires all the layers and re-exports types. Library users only touch this file; the MCP server and CLI also drive it.\n\n### Module layout\n\n- `src/index.ts` — `CodeGraph` class: `init`/`open`/`close`, `indexAll`, `sync`, `searchNodes`, `getCallers`/`getCallees`, `getImpactRadius`, `buildContext`, `watch`/`unwatch`.\n- `src/db/` — `DatabaseConnection`, `QueryBuilder` (prepared statements), `schema.sql`, `sqlite-adapter.ts`. Backed by Node's built-in **`node:sqlite`** (`DatabaseSync`) — real SQLite with WAL + FTS5, exposed through a thin better-sqlite3-shaped adapter. The bundled runtime always ships Node ≥22.5, so `node:sqlite` is always available: **no native build step and no wasm fallback**. (Running from source needs Node ≥22.5.) `codegraph status` reports the live backend (`node-sqlite`, the sole backend).\n- `src/extraction/` — `ExtractionOrchestrator`, tree-sitter wrappers, per-language extractors under `languages/` (one file per language), plus standalone extractors for non-tree-sitter formats (`svelte-extractor.ts`, `vue-extractor.ts`, `liquid-extractor.ts`, `dfm-extractor.ts` for Delphi). `parse-worker.ts` runs heavy parsing off the main thread.\n- `src/resolution/` — `ReferenceResolver` orchestrates `import-resolver.ts` (with `path-aliases.ts` for tsconfig path aliases + cargo workspace member globs), `name-matcher.ts`, and `frameworks/` (Express, Laravel, Rails, FastAPI, Django, Flask, Spring, Gin, Axum, ASP.NET, Vapor, React Router, Next.js — `nextjs.ts`: pages and `route.ts` handlers from files, `router.push` / `redirect` / `NextResponse.redirect` as `navigates` edges, with `next-router-synthesizer.ts` for `<Link href>` — Expo Router, SvelteKit, Vue/Nuxt, Cargo workspaces). Frameworks emit `route` nodes and `references` edges. `callback-synthesizer.ts` holds the whole-graph synthesis passes (`SYNTH_PASSES`, merged in registry order — first-seen wins a duplicate pair) with the language gates; `tier-synthesizer.ts` is the cross-tier pass (a client's literal `fetch`/`axios` path onto its own route, a queue job onto its consumer, a bus / socket event onto its handler — `channel`, `tier`, `registeredAt` on every edge; registered before the in-process emitter pass so its more specific edge wins); `synth-utils.ts` has the helpers they share (`enclosingFn`, `enclosingValue`, `makeLineAt`). Express's `postExtract` composes `app.use('/prefix', router)` mounts onto a mounted file's route names, idempotently (the original path stays in `qualifiedName`).\n- `src/graph/` — `GraphTraverser` (BFS/DFS, impact radius, path finding) and `GraphQueryManager` (high-level queries), plus the shared query-time derivations more than one surface renders: `named-symbol-flow.ts` (the one path finder, behind `codegraph_explore`'s Flow section and the viewer's Flow strip), `dynamic-boundary-report.ts` (where the graph stops), `type-hierarchy.ts` (ancestors/subtypes and the implementation count explore prints and the viewer draws),\n  `dead-code.ts` (unreferenced symbols, and every reason a candidate is NOT claimed), `synthesized-hop.ts` (how a synthesized hop reads, in explore's Flow, the `codegraph_node` trail and ContextBuilder's call paths — a new synthesizer's wording goes here). A derivation that two callers render must live here, not in `ToolHandler` — two derivations eventually disagree.\n- `src/context/` — `ContextBuilder` + formatter for markdown/JSON output.\n- `src/search/` — full-text query parser and helpers for FTS5.\n- `src/sync/` — `FileWatcher` (native FSEvents/inotify/RDCW) with debounce + filter, and git-hook helpers.\n- `src/mcp/` — MCP server (`MCPServer`, `tools.ts`, `transport.ts`). `server-instructions.ts` is what the server returns in the MCP `initialize` response — keep it in sync with the user-facing tool guidance.\n- `src/installer/` — see below.\n- `src/bin/codegraph.ts` — CLI (commander). Subcommands: `install`, `init`, `uninit`, `index`, `sync`, `status`, `query`, `files`, `context`, `affected`, `serve --mcp`.\n- `src/ui/` — terminal UI (shimmer progress, worker).\n- `src/ui-server/` -- read-only JSON API for the `codegraph ui` browser viewer (`api/`: `node`, `flow`, `map`, `screens`, `steps`, `deadcode`, `trails`, `program`, ...) plus static server; Svelte viewer lives in `ui/` (see `docs/design/codegraph-ui-design-spec.md`). `screens`/`steps`/`program` share one fold (`via`/`when` via `graph/branch-guards.ts`); `api/effects.ts` curates calls that leave the index; `api/route-roots.ts` names where a route's code starts. Derivations rendered by more than one surface belong in `src/graph/`, not `ToolHandler`.\n  **Not released yet:** `codegraph ui` / `web` are refused (and hidden from `--help`) unless `CODEGRAPH_UI=1` is set — `src/bin/viewer-gate.ts`. Viewer changelog entries wait in `docs/viewer-launch-changelog.md`, not under `[Unreleased]`. At launch: delete the gate, move those entries back, drop the \"not released yet\" notes from `site/`.\n\n### NodeKind / EdgeKind\n\nDefined in `src/types.ts`. Both extractors and resolvers must use these exact strings.\n\n- **NodeKind**: `file`, `module`, `class`, `struct`, `interface`, `trait`, `protocol`, `function`, `method`, `property`, `field`, `variable`, `constant`, `enum`, `enum_member`, `type_alias`, `namespace`, `parameter`, `import`, `export`, `route`, `component`, `union`.\n- **EdgeKind**: `contains`, `calls`, `imports`, `exports`, `extends`, `implements`, `references`, `type_of`, `returns`, `instantiates`, `overrides`, `decorates`.\n\n### Multi-agent installer\n\n`src/installer/` is the entry point for `codegraph install` (and the bare `codegraph`/`npx @colbymchenry/codegraph` invocation). Architecture:\n\n- `targets/registry.ts` lists every supported agent.\n- `targets/types.ts` defines the `AgentTarget` interface — adding a 5th agent (Continue, Zed, Windsurf…) is **one new file in `targets/` + one entry in `registry.ts`**. Each target owns its config-file location and MCP-server JSON/TOML/JSONC writing. (Claude, Codex, Gemini and opencode also write a short instructions-file block — see below.)\n- Current targets: `claude.ts`, `cursor.ts`, `codex.ts`, `opencode.ts`.\n- `targets/toml.ts` is a hand-rolled TOML serializer scoped to `[mcp_servers.codegraph]` (used by Codex). Sibling tables and `[[array_of_tables]]` are preserved verbatim. No new dependency.\n- opencode reads `opencode.jsonc` by default; the installer prefers existing `.jsonc`, falls back to `.json`, and creates `.jsonc` for greenfield installs. Edits are surgical via `jsonc-parser` so user comments and formatting survive install/re-install/uninstall round-trips. The MCP entry is OpenCode 2's native `mcp.servers.codegraph` with `disabled: false` and `codemode: false` (so `codegraph_explore` stays on the native tool list); a pre-#1698 `mcp.codegraph` + `enabled` entry is migrated on re-install and removed by uninstall.\n- `instructions-template.ts` holds `CODEGRAPH_INSTRUCTIONS_BLOCK`, a deliberately short marker-fenced (`<!-- CODEGRAPH_START -->`/`<!-- CODEGRAPH_END -->`) `## CodeGraph` block. The Claude, Codex, Gemini and opencode targets write it into their instructions file (`CLAUDE.md` / `~/.codex/AGENTS.md` / `~/.gemini/GEMINI.md` / `~/.config/opencode/AGENTS.md`) via `upsertInstructionsEntry`, and `uninstall` removes it. History: #529 removed the old full playbook because it duplicated the MCP `initialize` instructions; #704 brought back this small block because the instructions file reaches two audiences `initialize` cannot — Task-tool subagents and non-MCP harnesses (the `codegraph explore` CLI). Cursor and Kiro write no block; their `install` only strips one a previous install left behind. Keep the block to command names and the two surfaces; tool guidance itself still belongs in `server-instructions.ts`.\n- All installer changes need matching coverage in `__tests__/installer-targets.test.ts` — there are ~47 parameterized contract tests covering install idempotency, sibling preservation, uninstall reverses install, byte-equal re-runs returning `unchanged`, and partial-state recovery for Codex.\n\n### Cursor MCP working-directory quirk\n\nCursor launches MCP subprocesses with the wrong cwd and doesn't pass `rootUri` in `initialize`. The installer injects `--path` into Cursor's MCP args — absolute path for local installs, `${workspaceFolder}` for global installs. If you touch Cursor wiring, preserve this.\n\n### MCP server instructions\n\n`src/mcp/server-instructions.ts` is sent back to the agent in the MCP `initialize` response. This is the *first* thing every agent sees about how to use the tools, and as of issue #529 it is the **single source of truth** for agent-facing tool guidance. The installer's instructions-file block (`instructions-template.ts`, see above) only names the tool and the CLI for subagents and non-MCP agents; it does not repeat this guidance. Edit tool guidance here and nowhere else.\n\n## Retrieval performance & dynamic-dispatch coverage (do not regress)\n\nCodeGraph's core value is letting an agent answer **structural/flow** questions (\"how does X reach Y\", trace, impact, callers) with a few **fast** codegraph calls and **zero Read/Grep**. The optimization target is **wall-clock latency + tool-call count** — *don't optimize for token cost*. (Cost is **lower**, not \"flat\" as earlier framing claimed: a current-build with-vs-without A/B across the 7 README repos, median of 4, saved on average **35% cost · 57% tokens · 46% time · 71% tool calls** — reproducing the published README. The mechanism is **far fewer turns over a much smaller accumulated context** — NOT cache-ability: the without-arm's huge token volume is *mostly* cheap cache-reads, which is why token-count savings (57%) look bigger than cost savings (35%). Measure tokens by **summing per-turn assistant usage**, not `result.usage` (last-turn only in current Claude Code). See `docs/benchmarks/call-sequence-analysis.md`.) The mechanism that drives everything here: **an agent falls back to Read/Grep the instant a codegraph answer is insufficient.** So every change is judged by one question — is codegraph's answer sufficient enough to *stop* the agent from reading?\n\n**Target behavior:** a flow question resolves in **1 codegraph call on small repos, scaling to 3–5 on large**, with **Read/Grep = 0**. When reviewing a PR or trying something new, do not regress this.\n\n### Adapt the tool to the agent — don't try to change the agent\n\nThe lever that decides whether a retrieval change lands. **Test before building anything here: does this make a tool the agent _already calls_ do more with the input it _already gives_? If it instead needs the agent to behave differently — pick a different tool, query differently, learn from examples — it hits the low-salience wall and won't land.**\n\nCodeGraph's only channels to influence the agent are low-salience: the MCP `initialize` instructions (`server-instructions.ts`) and the tool descriptions. Changing them does **not** reliably move the agent's tool _choice_ or query style — validated: trace-first steering ported into the server-instructions + tool descriptions (3 wording variants) never reproduced what a CLI `--append-system-prompt` achieved, and **regressed** wall-clock vs baseline. New tools fare worse (rarely chosen — the agent under-picks even `trace`); \"better examples\" is the same steering. The agent's tool-choice does improve on its own as host models get better at tool use — but that is not ours to force.\n\nWhat works is meeting the agent where it already is:\n- **explore-flow** — `codegraph_explore` is the PRIMARY tool the agent reliably calls; its query is a precise bag of symbol names (incl. qualified `Class.method`) spanning the flow the agent is after; explore finds the call path _among those named symbols_ (riding synthesized edges) and leads its output with it. (`buildFlowFromNamedSymbols`: segment/co-naming disambiguation; ≤1 unnamed bridge so it never wanders a god-function's fan-out. Overload-aware: a PascalCase type token in the query biases an overloaded name to that type's own def — `DataRequest task` → DataRequest's `task`, not the abstract base; named-symbol files sort first.)\n- **Sufficiency** — make the tool's output complete enough that the agent stops. `codegraph_node` returns the full body + the caller/callee trail, and for an AMBIGUOUS name returns **every overload's body in one call** (so the agent never Reads a file to find the right overload — validated on Alamofire/gin). This is the after-explore depth tool (labeled SECONDARY).\n- **Errors teach abandonment** — one or two `isError: true` responses early in a session and the agent stops calling codegraph entirely (maintainer-observed, repeatedly). `isError` is reserved for genuine \"stop trying\" cases: security refusals (`PathRefusalError`) and real malfunctions (which carry a retry-once note). Every expected/recoverable condition — project not indexed, symbol not found, file not in the index — returns a **SUCCESS-shaped response carrying the guidance** (`NotIndexedError` → `textResult`, see `ToolHandler.execute`'s catch). The same principle is why the tool surface is **always exposed, even at an un-indexed root** (the old empty-`tools/list` gate was removed in #964 — it broke monorepos where only sub-projects carry a `.codegraph/`, and hid the tools from a session that started before `codegraph init`): safety comes from the response SHAPE (success-shaped guidance, never `isError`), not from hiding tools. An un-indexed root's `initialize` sends a per-project variant (`SERVER_INSTRUCTIONS_NO_ROOT_INDEX` — \"pass `projectPath` to a project that has a `.codegraph/`\"), not an \"inactive\" note; indexing is still deliberately the user's call, never the agent's.\n\nWhat fails is the inverse — folding a precise answer into a **fuzzy-input** tool: the now-removed `codegraph_context` took a description, not symbols, so it couldn't disambiguate a flow's endpoints and surfaced the _wrong feature_ (which is why it was cut). Precise output needs precise input — explore takes a symbol bag for exactly this reason. (`codegraph_trace` was likewise removed: explore-flow does its job and the agent under-picked it.)\n\nThe remaining lever under this axis is **coverage**: every flow made to connect statically (a new dynamic-dispatch synthesizer, or extracting symbols static parsing skipped — e.g. object-literal store actions in `create((set,get)=>({...}))`) is then surfaced automatically by explore-flow, no agent change needed. Reactive/reconciler runtimes (Halo's `ReactiveExtensionClient`, MediatR, Vue Proxy) are the frontier — flows there have no static edges, so nothing surfaces (correctly — silent beats wrong). Full investigation + A/B record: `docs/benchmarks/call-sequence-analysis.md` + auto-memory `project_codegraph_read_displacement`.\n\n### Explore budget — keep BOTH budgets monotonic with repo size\n\nTwo functions in `src/mcp/tools.ts` scale explore with indexed file count. This is the expected resolution (a regression here silently forces agents back to Read):\n\n| Repo | files | explore calls | chars/call | per-file |\n|---|---|---|---|---|\n| express (small) | 147 | 1 | 18K | 3800 |\n| excalidraw/django (medium) | 643–3043 | 2 | 28K | 6500 |\n| vscode (large) | 10446 | 3 | 35K | 7000 |\n| ~20k / ~40k | — | 4 / 5 | 38K | 7000 |\n\n- `getExploreBudget(fileCount)` → **call** budget: `<500→1, <5000→2, <15000→3, <25000→4, ≥25000→5` (max 5).\n- `getExploreOutputBudget(fileCount)` → **per-call** output (chars / files / per-file). **Invariant: a larger tier must never get a smaller `maxCharsPerFile` than a smaller tier.** (Regression that motivated this doc: the `<5000` tier's 2500 was *below* the `<500` tier's 3800, so on a god-file repo — excalidraw's 415 KB `App.tsx` — one explore returned <1% of the file and forced a Read.)\n- Explore output must **never tell the agent to \"use Read\"** — steer to another `codegraph_explore` and \"treat returned source as already Read.\"\n\n### Dynamic-dispatch coverage — the flow must EXIST in the graph end-to-end\n\nStatic tree-sitter extraction misses computed/indirect calls, so flows break at dynamic dispatch and the agent reads to reconstruct them. Synthesizers/resolvers bridge these so `codegraph_explore` connects them end-to-end (`src/resolution/callback-synthesizer.ts`, `src/resolution/frameworks/`). Channels today: callback/observer, EventEmitter, **React re-render** (`setState`→`render`), **JSX child** (`render`→child component), **React Native native→JS events** (`sendEvent(withName:)` / JVM `emit` → the `addListener` handler, named or inline, `rn-event-channel`), django ORM descriptor. The JS→native direction is a *resolver* (`frameworks/react-native.ts`: `RCT_EXPORT_METHOD`, `RCT_EXTERN_MODULE` Swift shims, TurboModules), which trusts receiver evidence — an alias bound to `NativeModules.X` — over the import resolver. All synthesized edges are `provenance:'heuristic'` with `metadata.synthesizedBy` + `registeredAt` (the wiring site), surfaced inline in `codegraph_explore`'s Flow section and the `codegraph_node` trail.\n\n**Principle: partial coverage is WORSE than none.** Bridging one boundary but not the next reveals a hop the agent then drills + reads to finish. Measured on excalidraw: react-render alone *raised* reads to 5–7; only completing the flow (adding the jsx-child hop) dropped it to 0–1. **Always close the flow end-to-end and re-measure** — never ship a half-bridged flow.\n\n\n### Validation methodology & worked examples\n\n**Required** for every new language/framework: validate on small/medium/large real repos with >=3 flow prompts; deterministic probes (`scripts/agent-eval/probe-*.mjs`) then agent A/B (`scripts/agent-eval/run-all.sh` / `ab-new-vs-baseline.sh`). Pass bar: ~0 Read/Grep within the explore-call budget, faster than without-codegraph, no control-repo regression.\n\nFull methodology (feedback metrics, CLI contamination guard, Sonnet/`--effort high` model policy, daemon pre-warm), the Excalidraw worked example, and coverage matrix live in:\n- `docs/AGENTS.md` (nested; also loaded when cwd is under `docs/`)\n- `docs/design/dynamic-dispatch-coverage-playbook.md`\n- `docs/design/callback-edge-synthesis.md`\n- `docs/benchmarks/call-sequence-analysis.md` / `docs/benchmarks/agent-eval-feedback-metrics.md`\n\n\nTests live in `__tests__/` and mirror the module they cover. Notable ones beyond the obvious:\n\n- `installer-targets.test.ts` — parameterized contract suite across all 4 agent targets (see installer notes above).\n- `evaluation/` — `runner.ts` + `test-cases.ts` exercise codegraph against synthetic projects and score the results; run via `npm run eval` (builds first). Not part of `npm test`.\n- `sqlite-backend.test.ts` / `node-sqlite-backend.test.ts` — pin that `node:sqlite` is the sole backend: `getBackend()` reports `node-sqlite` and the DB comes up in WAL.\n- `pr19-improvements.test.ts`, `frameworks-integration.test.ts` — regression coverage for specific past PRs/incidents; don't rename these, the names anchor to git history.\n\nTests create temp dirs with `fs.mkdtempSync` and clean up in `afterEach`. They write real files and exercise real SQLite — there is no DB mocking.\n\nEvery engine test file runs in a throwaway home dir (`__tests__/setup-home-sandbox.ts`, a `setupFiles` entry): `HOME`/`USERPROFILE` (+ Windows vars), `XDG_CONFIG_HOME` and `GIT_CONFIG_GLOBAL` point into it, and `CLAUDE_CONFIG_DIR`/`CODEX_HOME`/… are cleared; spawned children inherit it. It's a backstop — still inject writes to global state (e.g. `UpgradeDeps.wirePromptHook`, #2275).\n\n### Windows-gated tests\n\nBehavior that differs by platform (path resolution, drive letters, `SENSITIVE_PATHS`, `%APPDATA%` config dirs, CRLF) must be gated, not assumed. Use `it.runIf(process.platform === 'win32')(...)` for Windows-only assertions and `it.runIf(process.platform !== 'win32')(...)` for POSIX-only ones — e.g. `/etc` is sensitive on POSIX but resolves to `C:\\etc` (non-existent) on Windows, so an ungated `/etc` assertion fails on Windows. Validate the Windows side for real (see below); don't merge a Windows-gated test you haven't seen run.\n\n## Cross-platform validation\n\nThe dev machine — and the default `npm test` target — is **macOS**, so local runs cover the macOS path. The other two platforms aren't here; when a change is platform-sensitive (file watching, sockets / named pipes, path & symlink handling, process lifecycle, inotify budget) validate them for real rather than guessing.\n\n### Linux (Docker)\n\nWhen asked to test or validate on Linux, use **Docker** — there's no Linux box, but Docker runs on the macOS host. Build a throwaway image from the repo and run the suite inside it:\n\n- `FROM node:22-bookworm`; `COPY` the repo with a `.dockerignore` excluding `node_modules`/`dist`/`.git`/`.codegraph`; `RUN npm ci && npm run build`. Don't reuse the Mac `node_modules` — `esbuild`/`rollup` ship platform-specific binaries.\n- Run with **`docker run --rm --init`**. The `--init` is load-bearing for any process-lifecycle test (daemon reaping, the #277 PPID watchdog, idle-timeout): without a zombie-reaping PID 1, a SIGKILL'd/exited process lingers as a zombie and `process.kill(pid, 0)` still reports it *alive*, so exit-detection assertions false-fail even though the process did exit.\n- Linux is where the inotify watch budget actually bites: count a process's watches via `/proc/<pid>/fdinfo/*` (sum `^inotify ` lines on the fd whose `readlink` is `anon_inode:inotify`).\n\n### Windows (Parallels VM + SSH)\n\nFor any Windows-specific PR, bug, or implementation, validate it on the real Windows VM rather than guessing. Connection details live in the gitignored **`.parallels`** file at the repo root (VM name, guest IP, SSH user/key). `prlctl exec` needs Parallels Pro and is unavailable, so SSH is the bridge.\n\n- Connect / run from the Mac host: `ssh <user>@<guest_ip> \"...\"`. For multi-line work, pipe PowerShell over stdin and **refresh PATH from the registry** first (sshd's session has a stale PATH after winget installs):\n  ```\n  ssh colby@10.211.55.3 \"powershell -NoProfile -ExecutionPolicy Bypass -Command -\" <<'PS'\n  $env:Path = [Environment]::GetEnvironmentVariable(\"Path\",\"Machine\") + \";\" + [Environment]::GetEnvironmentVariable(\"Path\",\"User\")\n  Set-Location C:\\dev\\codegraph\n  PS\n  ```\n- Clone fresh into a **Windows-local** path (`C:\\dev\\codegraph`) and `npm ci` there — never run npm against the shared Mac repo, since `esbuild`/`rollup` ship platform-specific binaries.\n- Guest toolchain (winget): Node LTS, Git, and the **VC++ ARM64 redistributable** (required by `@rollup/rollup-win32-arm64-msvc`, which vitest pulls in).\n- Fetch a contributor PR head straight from their fork to dodge `pull/<n>/head` lag: `git fetch <fork-url> <branch>` then `git checkout -f FETCH_HEAD`.\n- Windows baseline: as of #2053 the full suite passes on the Windows 11 (ARM64) VM. The only expected exception is `security.test.ts > Session marker symlink resistance > does not follow a pre-planted symlink`, which needs symlink privileges (Developer Mode) — confirm any other failure against `origin/main` before blaming your PR. The former `mcp-initialize.test.ts` / `mcp-roots.test.ts` `EPERM` teardown failures came from tests spawning `serve --mcp` without the runtime flags (its `--liftoff-only` re-exec grandchild kept the cwd / SQLite file open); spawn it with `WASM_RUNTIME_FLAGS` and await the child's exit before removing the temp dir. Windows checkouts may be CRLF — split source lines on `/\\r?\\n/` in tests.\n- Windows worker crashes: a worker thread that ends (`terminate()`, its own `process.exit()`, or the process exiting) while V8's concurrent marker is marking its heap kills the process with exit 3221225477 (0xC0000005) and leaves no error or dump. It's worst while the worker is still loading its modules. Owners must not terminate a worker before its first message (`workerStarted` / `terminateOnceStarted`, or the pool's own handshake), and a worker that exits itself calls `collectBeforeExit()` first — both in `src/worker-teardown.ts`. `--no-concurrent-marking` also stops it, but measured about a third slower indexing on the Windows VM, so it is not used.\n\n## Releases\n\nReleased to npm and mirrored as [GitHub Releases](https://github.com/colbymchenry/codegraph/releases). `CHANGELOG.md` is the source of truth; GitHub Release notes are extracted from it.\n\n### Writing changelog entries\n\n**Default: write entries under `## [Unreleased]`** — that's the section reserved for work landing between releases. **Don't pre-create a `## [X.Y.Z]` block** for the next release: the Release workflow's first step is `scripts/prepare-release.mjs`, which automatically promotes everything under `[Unreleased]` into a new `## [X.Y.Z] - <YYYY-MM-DD>` block at release time (or merges into a pre-existing `[X.Y.Z]` block if one exists — but you don't need one). Pre-staging is what caused the v0.9.5 sparse-release-notes incident: a sparse `[0.9.5]` block hand-added before the rest of the work landed got picked by the extractor over the much-larger `[Unreleased]` section above it. Don't do that.\n\nFormatting rules for any entry (anywhere — `[Unreleased]` or otherwise):\n\n1. **Write friendly, user-facing notes — not engineer-facing ones.** Group under `### New Features` and `### Fixes` (sentence-case). Surface `### Breaking Changes` and `### Security` as their own sections **only when the release has them**; fold improvement-flavored changes into New Features. Omit empty sections. (This replaces the old Keep-a-Changelog `Added/Changed/Fixed/Removed/Deprecated` grouping: the GitHub Release page extracts each version block **verbatim** via `scripts/extract-release-notes.mjs`, and the old dense, implementation-focused entries rendered as an unreadable wall of text — so the whole CHANGELOG was rewritten to this format and every published release re-noted to match.)\n2. **One plain-language sentence per bullet:** what changed and why it matters to a user. Lead with the capability, or with the symptom that's now fixed.\n3. **Strip the internals.** No internal file paths (`src/...`), no internal symbol / function / class names, no benchmark numbers / percentages / node-or-edge counts. **Keep:** language & framework names (Go, Spring, NestJS, …), things a user types or sets (`codegraph install`, `codegraph_explore`, the `CODEGRAPH_*` env vars), agent / IDE names (Claude Code, Cursor, opencode, Kiro, …), and a brief `Thanks @user` when a contributor is credited.\n4. Issue / PR references in entries are by number (`(#403)` etc.); the GitHub renderer auto-links them in the published release notes.\n5. **Don't add a `[X.Y.Z]: https://...` link reference yourself** — `prepare-release.mjs` appends it automatically when it promotes the version (idempotent: a re-run is a no-op if it already exists).\n6. **Every release opens with a `### Highlights` block — the only part most people read.** At most ~8 one-line bullets, in plain language for someone who doesn't read code, ordered by what a typical user notices first (new agent/IDE support and setup changes, then answer quality, then reliability), plus a one-sentence upgrade note when a re-index is needed. Write or refresh it in `[Unreleased]` when a release is being prepared — not per PR — and keep the detailed `### New Features` / `### Fixes` entries below it. When `### Fixes` grows past ~15 entries, group them under `####` sub-headings (`Better answers from codegraph_explore`, `Finding your project, live updates, and the CLI`, `Indexing reliability and disk usage`, `Language and framework accuracy`) so a skimmer can find their area.\n\nMulti-word headings like `### New Features` are safe on the normal release path: `prepare-release.mjs` **Case A** moves the whole `[Unreleased]` body verbatim into `[X.Y.Z]`. (Only its rarely-used **Case B** *merge* splits sub-sections with a single-word `^### (\\w+)$` regex that wouldn't match them — and Case B fires only if a `[X.Y.Z]` block was pre-created, which rule above already forbids.)\n\n### Release flow (the user runs these)\n\nReleases are built and published by the **GitHub Actions \"Release\" workflow**\n(`.github/workflows/release.yml`). It runs `scripts/prepare-release.mjs` to\npromote `[Unreleased]` into `[<version>]` (and auto-commit + push that\nCHANGELOG change back to `main` so on-disk truth matches the published\nnotes), then bundles a Node runtime per platform (`scripts/build-bundle.sh`)\nand publishes both the GitHub Release and the npm thin-installer\n(`scripts/pack-npm.sh`: a shim package + per-platform packages).\nPublishing manually is **wrong** now — a plain `npm publish` ships the root\npackage (non-bundled), which breaks anyone on Node < 22.5.\n\n**Claude does NOT bump the version unless explicitly asked.** The maintainer\ntypically does it themselves — often by editing `package.json` directly via\nthe GitHub web UI. Don't proactively commit a version bump as part of\nunrelated work, and don't propose one when summarizing a PR.\n\nWhen the maintainer DOES bump the version, the only edit strictly required is\nto `package.json` — the workflow's \"Sync package-lock.json\" step detects a\nmismatch between `package.json` and `package-lock.json`, runs\n`npm install --package-lock-only --ignore-scripts` to rewrite the lock file's\nversion fields (top-level + `packages.\"\"`), and auto-commits + pushes the\nresult back to `main` with `[skip ci]`. So a GitHub-web-UI single-file edit to\n`package.json` is enough to kick off a clean release. (If they edit both files\nlocally, that's fine too — the sync step no-ops.) Bump `ui/package.json` to the\nsame version in that change: the component library is versioned with the\nengine, `ui-package.test.ts` pins it, and the workflow does not sync it — the\n1.6.1 bump left `main` red on exactly that.\n\nOnce `package.json` is at the target version on `main`, trigger\n**Actions → Release → Run workflow** (on `main`). The workflow:\n\n1. Syncs `package-lock.json` to `package.json`'s version if they've drifted; commits + pushes that change.\n2. Runs `prepare-release.mjs <X.Y.Z>` → promotes `[Unreleased]` → `[X.Y.Z] - <today>` in `CHANGELOG.md`, appends the link reference, commits + pushes the move with `[skip ci]`.\n3. Builds every platform bundle on one runner, generates `SHA256SUMS`.\n4. Creates the GitHub Release with notes from the freshly-promoted `[X.Y.Z]` block.\n5. Publishes the npm shim + per-platform packages. Requires the `NPM_TOKEN` repo secret.\n\n**Do not run `npm publish`, `git push`, or `git tag` yourself** — these are\npublish actions on shared state. Write the files, hand the user the commands.\n\n## House rules\n\n- The `0.7.x` line is in active multi-agent rollout. Any change to `src/installer/` (especially `targets/`) needs corresponding test coverage and a CHANGELOG entry — installer regressions break every new install silently.\n- When changing what the MCP tools do or how agents should use them, edit `src/mcp/server-instructions.ts` — it is the **single source of truth** for agent-facing tool guidance (issue #529). The installer's short instructions-file block (`instructions-template.ts`) only names `codegraph_explore` and the `codegraph explore` CLI; update it only when a tool or CLI command is renamed or removed. (The repo's own checked-in `.cursor/rules/codegraph.mdc` is dogfooding config — update it too if you use Cursor on this repo, but it ships nowhere.)\n- **Before adding or extending a router, a web framework, or a language's `WHEN` rules, read `docs/design/framework-coverage.md`.** It is the standing answer to \"what is supported and what is left\" across the three axes (route nodes → Entry points, `navigates` edges → Screens, branch-guard rules → the `WHEN` labels), with what each remaining item needs, the traps that have already cost debugging time, and the queries to re-verify it. Update it in the same change that moves a row.\n- CodeGraph provides **code context**, not product requirements. For new features, ask the user about UX, edge cases, and acceptance criteria — the graph won't tell you.\n- **When the user references issues, PR comments, or external reports, anchor them to a date and version before drawing conclusions.** Check the comment's `createdAt` against:\n  - The **last released version** — `grep -m1 '^## \\[' CHANGELOG.md` shows the top-of-file version (older releases follow). A comment dated before the latest `## [X.Y.Z] - YYYY-MM-DD` is reacting to *released* state — work that's only on `main` or on an unmerged branch doesn't apply.\n  - The **last main commit** — `git log --first-parent main -1 --format='%ai %h %s'`. A comment after the last release but before a fix on main may already be addressed there but unreleased.\n  - The **current branch's tip** — your own unmerged work obviously can't be what the comment is reacting to.\n  Always disambiguate \"released,\" \"merged-but-unreleased,\" and \"in-progress\" before agreeing that a user-reported problem is unfixed (or that a fix is incomplete). A user saying \"your fix only covers X\" about a recent PR is usually pointing at the *released* shortcomings — your in-flight branch may already address them but they have no way to know that.\n- **Version-tag every image referenced in `README.md`.** GitHub caches README images (`raw.githubusercontent.com` with a 5-minute TTL; third-party hosts sit behind the long-lived camo proxy), so updating an asset in place can keep showing the stale version. Give each README image URL a `?v=N` query tag and **bump `N` in the same commit whenever the asset bytes change** — e.g. `assets/waitlist.svg?v=2`. The changed URL sidesteps every cache so the new image shows immediately instead of waiting on a TTL to expire.\n",".cursor/rules/codegraph.mdc":"---\ndescription: CodeGraph MCP usage guide — one tool, codegraph_explore\nalwaysApply: true\n---\n<!-- CODEGRAPH_START -->\n## CodeGraph\n\nThis project has a CodeGraph MCP server configured, exposing a single tool: `codegraph_explore`. CodeGraph is a tree-sitter-parsed knowledge graph of every symbol, edge, and file. Reads are sub-millisecond and return structural information grep cannot.\n\n### Use codegraph_explore instead of reading files\n\nReach for `codegraph_explore` before grep/find or Read for any **structural** question — how does X work, how does X reach Y, what calls what, where is X defined, or surveying an area. It takes a natural-language question or a bag of symbol/file names and returns the relevant symbols' **verbatim, line-numbered source** grouped by file (the same `<n>\\t<line>` shape Read gives you, safe to Edit from), plus the call paths between them — including dynamic-dispatch hops (callbacks, React re-render, JSX children) grep can't follow — and a blast-radius summary of what depends on them. Name a file or symbol in the query to read its current source.\n\n### Rules of thumb\n\n- **Answer directly — don't delegate exploration.** ONE `codegraph_explore` usually answers the whole question; follow up with another `codegraph_explore` naming more specific symbols if you need more. Codegraph IS the pre-built index, so spawning a separate file-reading sub-task/agent — or running a grep + read loop — repeats work codegraph already did and costs more for the same answer.\n- **Trust codegraph results.** They come from a full AST parse. Do NOT re-verify them with grep — that's slower, less accurate, and wastes context.\n- **Don't grep or Read first** to find or understand indexed code — one `codegraph_explore` returns the relevant source in a single round-trip. Reach for raw Read/Grep only to confirm a specific detail codegraph didn't cover, or for what it doesn't index (configs, docs).\n- **Index lag — check the staleness banner, don't guess a wait.** When a codegraph response starts with \"⚠️ Some files referenced below were edited since the last index sync…\", the listed files are pending re-index — Read those specific files for accurate content. Files NOT in that banner are fresh and codegraph is authoritative for them.\n\n### If `.codegraph/` doesn't exist\n\nThe MCP server returns \"not initialized.\" Ask the user: *\"I notice this project doesn't have CodeGraph initialized. Want me to run `codegraph init -i` to build the index?\"*\n<!-- CODEGRAPH_END -->\n",".claude/skills/agent-eval/corpus.json":"{\n  \"_comment\": \"Test corpus for /agent-eval. Add entries freely. size: Small (<~150 files), Medium (~150-1500), Large (>~1500). 'question' is a representative architectural question that exercises cross-file understanding.\",\n  \"TypeScript\": [\n    {\n      \"name\": \"ky\",\n      \"repo\": \"https://github.com/sindresorhus/ky\",\n      \"size\": \"Small\",\n      \"files\": \"~25\",\n      \"question\": \"How does ky implement request retries and timeouts?\"\n    },\n    {\n      \"name\": \"excalidraw\",\n      \"repo\": \"https://github.com/excalidraw/excalidraw\",\n      \"size\": \"Medium\",\n      \"files\": \"~600\",\n      \"question\": \"How does Excalidraw render and update canvas elements?\"\n    },\n    {\n      \"name\": \"vscode\",\n      \"repo\": \"https://github.com/microsoft/vscode\",\n      \"size\": \"Large\",\n      \"files\": \"~10000\",\n      \"question\": \"How does the extension host communicate with the main process?\"\n    }\n  ],\n  \"JavaScript\": [\n    {\n      \"name\": \"express\",\n      \"repo\": \"https://github.com/expressjs/express\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does Express route a request through its middleware stack?\"\n    }\n  ],\n  \"Go\": [\n    {\n      \"name\": \"cobra\",\n      \"repo\": \"https://github.com/spf13/cobra\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does cobra parse commands and flags?\"\n    },\n    {\n      \"name\": \"gin\",\n      \"repo\": \"https://github.com/gin-gonic/gin\",\n      \"size\": \"Medium\",\n      \"files\": \"~150\",\n      \"question\": \"How does gin route requests through its middleware chain?\"\n    },\n    {\n      \"name\": \"terraform\",\n      \"repo\": \"https://github.com/hashicorp/terraform\",\n      \"size\": \"Large\",\n      \"files\": \"~4000\",\n      \"question\": \"How does Terraform build and walk the resource dependency graph?\"\n    },\n    {\n      \"name\": \"cosmos-sdk\",\n      \"repo\": \"https://github.com/cosmos/cosmos-sdk\",\n      \"size\": \"Large\",\n      \"files\": \"~5000\",\n      \"question\": \"How does a bank module MsgSend message reach the account balance update? Trace the cross-module call path from the bank keeper's Send handler through to the account/balance store update.\"\n    }\n  ],\n  \"Python\": [\n    {\n      \"name\": \"click\",\n      \"repo\": \"https://github.com/pallets/click\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does click parse command-line arguments into commands?\"\n    },\n    {\n      \"name\": \"flask\",\n      \"repo\": \"https://github.com/pallets/flask\",\n      \"size\": \"Medium\",\n      \"files\": \"~90\",\n      \"question\": \"How does Flask dispatch a request to a view function?\"\n    },\n    {\n      \"name\": \"django\",\n      \"repo\": \"https://github.com/django/django\",\n      \"size\": \"Large\",\n      \"files\": \"~2700\",\n      \"question\": \"How does Django's ORM build and execute a query from a QuerySet?\"\n    }\n  ],\n  \"Rust\": [\n    {\n      \"name\": \"clap\",\n      \"repo\": \"https://github.com/clap-rs/clap\",\n      \"size\": \"Medium\",\n      \"files\": \"~200\",\n      \"question\": \"How does clap parse arguments against a derived command definition?\"\n    },\n    {\n      \"name\": \"tokio\",\n      \"repo\": \"https://github.com/tokio-rs/tokio\",\n      \"size\": \"Large\",\n      \"files\": \"~700\",\n      \"question\": \"How does tokio schedule and run async tasks on its runtime?\"\n    },\n    {\n      \"name\": \"deno\",\n      \"repo\": \"https://github.com/denoland/deno\",\n      \"size\": \"Large\",\n      \"files\": \"~1500\",\n      \"question\": \"How does Deno load and execute a TypeScript module?\"\n    }\n  ],\n  \"Java\": [\n    {\n      \"name\": \"gson\",\n      \"repo\": \"https://github.com/google/gson\",\n      \"size\": \"Medium\",\n      \"files\": \"~200\",\n      \"question\": \"How does Gson serialize an object to JSON?\"\n    },\n    {\n      \"name\": \"okhttp\",\n      \"repo\": \"https://github.com/square/okhttp\",\n      \"size\": \"Medium\",\n      \"files\": \"~640\",\n      \"question\": \"How does OkHttp process a request through its interceptor chain?\"\n    },\n    {\n      \"name\": \"guava\",\n      \"repo\": \"https://github.com/google/guava\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Guava's CacheBuilder build and configure a cache?\"\n    }\n  ],\n  \"Kotlin\": [\n    {\n      \"name\": \"koin\",\n      \"repo\": \"https://github.com/InsertKoinIO/koin\",\n      \"size\": \"Medium\",\n      \"files\": \"~300\",\n      \"question\": \"How does Koin resolve and inject dependencies?\"\n    },\n    {\n      \"name\": \"leakcanary\",\n      \"repo\": \"https://github.com/square/leakcanary\",\n      \"size\": \"Medium\",\n      \"files\": \"~250\",\n      \"question\": \"How does LeakCanary detect and analyze a memory leak?\"\n    }\n  ],\n  \"Swift\": [\n    {\n      \"name\": \"alamofire\",\n      \"repo\": \"https://github.com/Alamofire/Alamofire\",\n      \"size\": \"Small\",\n      \"files\": \"~100\",\n      \"question\": \"How does Alamofire build, send, and validate a request?\"\n    }\n  ],\n  \"C#\": [\n    {\n      \"name\": \"serilog\",\n      \"repo\": \"https://github.com/serilog/serilog\",\n      \"size\": \"Medium\",\n      \"files\": \"~250\",\n      \"question\": \"How does Serilog route a log event to its sinks?\"\n    },\n    {\n      \"name\": \"jellyfin\",\n      \"repo\": \"https://github.com/jellyfin/jellyfin\",\n      \"size\": \"Large\",\n      \"files\": \"~2500\",\n      \"question\": \"How does Jellyfin scan and identify items in a media library?\"\n    }\n  ],\n  \"Ruby\": [\n    {\n      \"name\": \"sinatra\",\n      \"repo\": \"https://github.com/sinatra/sinatra\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does Sinatra match a request to a route handler?\"\n    },\n    {\n      \"name\": \"discourse\",\n      \"repo\": \"https://github.com/discourse/discourse\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Discourse create and render a new post?\"\n    }\n  ],\n  \"PHP\": [\n    {\n      \"name\": \"slim\",\n      \"repo\": \"https://github.com/slimphp/Slim\",\n      \"size\": \"Small\",\n      \"files\": \"~80\",\n      \"question\": \"How does Slim handle a request through its middleware?\"\n    },\n    {\n      \"name\": \"laravel\",\n      \"repo\": \"https://github.com/laravel/framework\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Laravel resolve and dispatch a route to a controller?\"\n    }\n  ],\n  \"C\": [\n    {\n      \"name\": \"redis\",\n      \"repo\": \"https://github.com/redis/redis\",\n      \"size\": \"Large\",\n      \"files\": \"~600\",\n      \"question\": \"How does Redis parse and dispatch a client command?\"\n    }\n  ],\n  \"C++\": [\n    {\n      \"name\": \"json\",\n      \"repo\": \"https://github.com/nlohmann/json\",\n      \"size\": \"Small\",\n      \"files\": \"~100\",\n      \"question\": \"How does nlohmann::json parse a JSON string into a value?\"\n    },\n    {\n      \"name\": \"grpc\",\n      \"repo\": \"https://github.com/grpc/grpc\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does gRPC dispatch an incoming RPC to its handler?\"\n    }\n  ],\n  \"Dart\": [\n    {\n      \"name\": \"flutter\",\n      \"repo\": \"https://github.com/flutter/flutter\",\n      \"size\": \"Large\",\n      \"files\": \"~6000\",\n      \"question\": \"How does Flutter build and lay out a widget tree?\"\n    }\n  ],\n  \"Svelte\": [\n    {\n      \"name\": \"shadcn-svelte\",\n      \"repo\": \"https://github.com/huntabyte/shadcn-svelte\",\n      \"size\": \"Medium\",\n      \"files\": \"~600\",\n      \"question\": \"How do shadcn-svelte components compose and apply their styling?\"\n    }\n  ],\n  \"Lua\": [\n    {\n      \"name\": \"lualine.nvim\",\n      \"repo\": \"https://github.com/nvim-lualine/lualine.nvim\",\n      \"size\": \"Small\",\n      \"files\": \"~120\",\n      \"question\": \"How does lualine assemble and render its statusline sections and components?\"\n    },\n    {\n      \"name\": \"telescope.nvim\",\n      \"repo\": \"https://github.com/nvim-telescope/telescope.nvim\",\n      \"size\": \"Medium\",\n      \"files\": \"~80\",\n      \"question\": \"How does Telescope wire a picker to its finder, sorter, and previewer?\"\n    },\n    {\n      \"name\": \"kong\",\n      \"repo\": \"https://github.com/Kong/kong\",\n      \"size\": \"Large\",\n      \"files\": \"~1330\",\n      \"question\": \"How does Kong execute plugins across a request's lifecycle phases?\"\n    }\n  ],\n  \"Luau\": [\n    {\n      \"name\": \"Knit\",\n      \"repo\": \"https://github.com/Sleitnick/Knit\",\n      \"size\": \"Small\",\n      \"files\": \"~10\",\n      \"question\": \"How does Knit register services and expose them to clients?\"\n    },\n    {\n      \"name\": \"vide\",\n      \"repo\": \"https://github.com/centau/vide\",\n      \"size\": \"Small\",\n      \"files\": \"~40\",\n      \"question\": \"How does vide track reactive sources and re-run effects when state changes?\"\n    },\n    {\n      \"name\": \"Fusion\",\n      \"repo\": \"https://github.com/dphfox/Fusion\",\n      \"size\": \"Medium\",\n      \"files\": \"~115\",\n      \"question\": \"How does Fusion build and update its reactive UI graph from state objects?\"\n    }\n  ],\n  \"Objective-C\": [\n    {\n      \"name\": \"Masonry\",\n      \"repo\": \"https://github.com/SnapKit/Masonry\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does Masonry build and activate Auto Layout constraints from its block DSL?\"\n    },\n    {\n      \"name\": \"FMDB\",\n      \"repo\": \"https://github.com/ccgus/fmdb\",\n      \"size\": \"Medium\",\n      \"files\": \"~80\",\n      \"question\": \"How does FMDB execute a prepared SQL statement and bind parameters?\"\n    },\n    {\n      \"name\": \"SDWebImage\",\n      \"repo\": \"https://github.com/SDWebImage/SDWebImage\",\n      \"size\": \"Large\",\n      \"files\": \"~400\",\n      \"question\": \"How does SDWebImage download, cache, and decode an image for a UIImageView?\"\n    }\n  ],\n  \"Mixed iOS (Swift+ObjC)\": [\n    {\n      \"name\": \"Charts\",\n      \"repo\": \"https://github.com/danielgindi/Charts\",\n      \"size\": \"Small\",\n      \"files\": \"~270\",\n      \"question\": \"How does the ChartsDemo ObjC demo controller drive the Swift Charts library to animate and notify a data update?\"\n    },\n    {\n      \"name\": \"realm-swift\",\n      \"repo\": \"https://github.com/realm/realm-swift\",\n      \"size\": \"Medium\",\n      \"files\": \"~370\",\n      \"question\": \"How does a Swift `Realm.write { realm.add(obj) }` reach the Objective-C persistence layer?\"\n    },\n    {\n      \"name\": \"wikipedia-ios\",\n      \"repo\": \"https://github.com/wikimedia/wikipedia-ios\",\n      \"size\": \"Large\",\n      \"files\": \"~1700\",\n      \"question\": \"How does tapping a search result reach the article-fetch network call across the Swift / ObjC boundary?\"\n    }\n  ],\n  \"React Native (legacy bridge + TurboModule)\": [\n    {\n      \"name\": \"@react-native-async-storage\",\n      \"repo\": \"https://github.com/react-native-async-storage/async-storage\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does `setItem` in JS reach the native `legacy_multiSet` implementation?\"\n    },\n    {\n      \"name\": \"react-native-svg\",\n      \"repo\": \"https://github.com/software-mansion/react-native-svg\",\n      \"size\": \"Medium\",\n      \"files\": \"~700\",\n      \"question\": \"How does a JS `Svg.getTotalLength(...)` reach the iOS / Android native implementation via TurboModule?\"\n    },\n    {\n      \"name\": \"react-native-firebase\",\n      \"repo\": \"https://github.com/invertase/react-native-firebase\",\n      \"size\": \"Large\",\n      \"files\": \"~1100\",\n      \"question\": \"How does a native iOS push notification reach the JS `messaging().onMessage(...)` listener?\"\n    }\n  ],\n  \"Expo Modules\": [\n    {\n      \"name\": \"expo-haptics\",\n      \"repo\": \"https://github.com/expo/expo/tree/main/packages/expo-haptics\",\n      \"size\": \"Small\",\n      \"files\": \"~15\",\n      \"question\": \"How does `Haptics.notificationAsync(...)` in JS reach `UINotificationFeedbackGenerator` in the Swift Module?\"\n    },\n    {\n      \"name\": \"expo-camera\",\n      \"repo\": \"https://github.com/expo/expo/tree/main/packages/expo-camera\",\n      \"size\": \"Medium\",\n      \"files\": \"~70\",\n      \"question\": \"How does a JS `CameraView.takePictureAsync(options)` reach the native AVCaptureSession / CameraDevice call?\"\n    }\n  ],\n  \"React Native Fabric (view components)\": [\n    {\n      \"name\": \"react-native-segmented-control\",\n      \"repo\": \"https://github.com/react-native-segmented-control/segmented-control\",\n      \"size\": \"Small\",\n      \"files\": \"~25\",\n      \"question\": \"How does JSX `<SegmentedControl onChange={cb}/>` reach the native onChange handler on iOS/Android?\"\n    },\n    {\n      \"name\": \"react-native-screens\",\n      \"repo\": \"https://github.com/software-mansion/react-native-screens\",\n      \"size\": \"Medium\",\n      \"files\": \"~1200\",\n      \"question\": \"How does JSX `<ScreenStack>` reach the native RNSScreenStackView component?\"\n    },\n    {\n      \"name\": \"react-native-skia\",\n      \"repo\": \"https://github.com/Shopify/react-native-skia\",\n      \"size\": \"Large\",\n      \"files\": \"~1000\",\n      \"question\": \"How does a `<SkiaPictureView/>` JSX usage reach the iOS / Android native renderer?\"\n    }\n  ],\n  \"R\": [\n    {\n      \"name\": \"AnomalyDetection\",\n      \"repo\": \"https://github.com/twitter/AnomalyDetection\",\n      \"size\": \"Small\",\n      \"files\": \"~24\",\n      \"question\": \"How does AnomalyDetectionTs go from the exported entry function to the underlying S-H-ESD statistical test? Name the functions on the path in order.\"\n    },\n    {\n      \"name\": \"dplyr\",\n      \"repo\": \"https://github.com/tidyverse/dplyr\",\n      \"size\": \"Medium\",\n      \"files\": \"~450\",\n      \"question\": \"When mutate() is called on a grouped data frame, which functions handle the grouping and expression evaluation, in order, from mutate() down?\"\n    },\n    {\n      \"name\": \"ggplot2\",\n      \"repo\": \"https://github.com/tidyverse/ggplot2\",\n      \"size\": \"Large\",\n      \"files\": \"~1150\",\n      \"question\": \"When a ggplot object is printed, how does the plot actually get built and drawn — trace the path from print/plot to where geoms render. Name the key functions in order.\"\n    }\n  ],\n  \"COBOL\": [\n    {\n      \"name\": \"cics-genapp\",\n      \"repo\": \"https://github.com/cicsdev/cics-genapp\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"When a new insurance policy is added through the LGTESTP1 CICS test harness, which programs handle the request on the way to the DB2 insert? Trace the path and name the programs in order.\"\n    },\n    {\n      \"name\": \"carddemo\",\n      \"repo\": \"https://github.com/aws-samples/aws-mainframe-modernization-carddemo\",\n      \"size\": \"Medium\",\n      \"files\": \"~75\",\n      \"question\": \"How does the online bill-payment flow work — from the COBIL00C screen program to the transaction record being written? Name the paragraphs performed in order and the copybooks that define the records involved.\"\n    },\n    {\n      \"name\": \"CobolCraft\",\n      \"repo\": \"https://github.com/meyfa/CobolCraft\",\n      \"size\": \"Medium\",\n      \"files\": \"~270\",\n      \"question\": \"How does an incoming player chat message travel from packet handling to being broadcast to the other connected players? Name the programs on the path in order.\"\n    }\n  ],\n  \"VB.NET\": [\n    {\n      \"name\": \"policyplus\",\n      \"repo\": \"https://github.com/Fleex255/PolicyPlus\",\n      \"size\": \"Small\",\n      \"files\": \"~94\",\n      \"question\": \"When the user toggles a policy to Enabled in the policy-setting editor and clicks OK, how does the new state end up written into the loaded policy source (POL file or registry)? Trace the path from the EditSetting dialog to the concrete write.\"\n    },\n    {\n      \"name\": \"scrawler\",\n      \"repo\": \"https://github.com/AAndyProgram/SCrawler\",\n      \"size\": \"Medium\",\n      \"files\": \"~320\",\n      \"question\": \"When a user download is started for a Reddit user, how does the request flow from the user-level download entry point through the shared downloader base into the Reddit site plugin, and where do downloaded media items get appended to the user's content list?\"\n    },\n    {\n      \"name\": \"staxrip\",\n      \"repo\": \"https://github.com/staxrip/staxrip\",\n      \"size\": \"Medium\",\n      \"files\": \"~145\",\n      \"question\": \"When a job finishes video encoding, how does staxrip decide which muxer runs and how does the muxer command line get built and executed? Trace from job processing to the mkvmerge invocation.\"\n    }\n  ],\n  \"Erlang\": [\n    {\n      \"name\": \"cowboy\",\n      \"repo\": \"https://github.com/ninenines/cowboy\",\n      \"size\": \"Small\",\n      \"files\": \"~190\",\n      \"question\": \"How does an incoming HTTP request travel from cowboy's connection process to a user-defined handler's init/2 callback? Trace the path through the stream handler and middleware chain.\"\n    },\n    {\n      \"name\": \"ejabberd\",\n      \"repo\": \"https://github.com/processone/ejabberd\",\n      \"size\": \"Medium\",\n      \"files\": \"~410\",\n      \"question\": \"When a client sends a chat message, how does the stanza get from the receiving C2S process to the recipient's session on the same node? Trace the path through the router and session manager.\"\n    },\n    {\n      \"name\": \"emqx\",\n      \"repo\": \"https://github.com/emqx/emqx\",\n      \"size\": \"Large\",\n      \"files\": \"~2450\",\n      \"question\": \"How does a PUBLISH packet from an MQTT client reach the sessions of matching subscribers? Trace the flow from the connection/channel layer through the broker's routing to session delivery.\"\n    }\n  ],\n  \"Solidity\": [\n    {\n      \"name\": \"solmate\",\n      \"repo\": \"https://github.com/transmissions11/solmate\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does solmate's ERC20 transferFrom enforce allowance and update balances? Trace the flow including the permit() signature path.\"\n    },\n    {\n      \"name\": \"solady\",\n      \"repo\": \"https://github.com/Vectorized/solady\",\n      \"size\": \"Medium\",\n      \"files\": \"~270\",\n      \"question\": \"How does solady's ERC20 implementation handle a permit() call — from signature recovery through nonce update to allowance write?\"\n    },\n    {\n      \"name\": \"openzeppelin-contracts\",\n      \"repo\": \"https://github.com/OpenZeppelin/openzeppelin-contracts\",\n      \"size\": \"Large\",\n      \"files\": \"~400\",\n      \"question\": \"How does an OpenZeppelin AccessControl-protected function check the caller's role? Trace from the onlyRole modifier through hasRole to the role storage.\"\n    }\n  ],\n  \"CUDA\": [\n    {\n      \"name\": \"llm.c\",\n      \"repo\": \"https://github.com/karpathy/llm.c\",\n      \"size\": \"Small\",\n      \"files\": \"~76\",\n      \"question\": \"How does the attention forward pass reach the GPU in the CUDA training path? Trace from attention_forward to the kernels it launches, and explain where softmax happens.\"\n    },\n    {\n      \"name\": \"flash-attention\",\n      \"repo\": \"https://github.com/Dao-AILab/flash-attention\",\n      \"size\": \"Medium\",\n      \"files\": \"~900\",\n      \"question\": \"How does a Python call to flash_attn_func reach the CUDA kernel that computes forward attention? Trace the dispatch path from the Python API through the C++ binding to the kernel launch.\"\n    },\n    {\n      \"name\": \"cutlass\",\n      \"repo\": \"https://github.com/NVIDIA/cutlass\",\n      \"size\": \"Large\",\n      \"files\": \"~2700\",\n      \"question\": \"When a cutlass device-level GEMM (cutlass::gemm::device::Gemm) is invoked, how does it reach the GPU kernel? Trace from the operator() call to the kernel entry point and its launch site.\"\n    }\n  ],\n  \"Terraform\": [\n    {\n      \"name\": \"terraform-aws-vpc\",\n      \"repo\": \"https://github.com/terraform-aws-modules/terraform-aws-vpc\",\n      \"size\": \"Small\",\n      \"files\": \"~77\",\n      \"question\": \"How does the private_subnets variable shape the NAT gateway setup? Trace from the variable through the subnet and NAT gateway resources to the outputs that expose the private subnets.\"\n    },\n    {\n      \"name\": \"cloud-foundation-fabric\",\n      \"repo\": \"https://github.com/GoogleCloudPlatform/cloud-foundation-fabric\",\n      \"size\": \"Medium\",\n      \"files\": \"~990\",\n      \"question\": \"In the project module (modules/project), how does the iam variable turn into actual IAM bindings on the project, and what depends on the module's project_id output elsewhere in the repo?\"\n    },\n    {\n      \"name\": \"terraform-aws-components\",\n      \"repo\": \"https://github.com/cloudposse/terraform-aws-components\",\n      \"size\": \"Large\",\n      \"files\": \"~1800\",\n      \"question\": \"In the eks/cluster component, how does the cluster IAM role get created and reach the EKS cluster resource, and which outputs expose cluster identity to other components?\"\n    }\n  ],\n  \"ArkTS\": [\n    {\n      \"name\": \"HarmoneyOpenEye\",\n      \"repo\": \"https://github.com/WinWang/HarmoneyOpenEye\",\n      \"size\": \"Small\",\n      \"files\": \"~82\",\n      \"question\": \"How does the home page get its feed data from the network layer, and how does that data end up rendered as the list on screen? Trace the flow from the HTTP request through the view model into the home page UI.\"\n    },\n    {\n      \"name\": \"CoolMallArkTS\",\n      \"repo\": \"https://github.com/Joker-x-dev/CoolMallArkTS\",\n      \"size\": \"Medium\",\n      \"files\": \"~528\",\n      \"question\": \"When the user adds a product to the cart from the goods detail page, how does the item travel from the UI action to persistent storage? Trace the flow across the feature and core modules.\"\n    },\n    {\n      \"name\": \"applications_app_samples\",\n      \"repo\": \"https://github.com/openharmony/applications_app_samples\",\n      \"size\": \"Large\",\n      \"files\": \"~9500\",\n      \"question\": \"In the OrangeShopping sample app, how does the product detail page's bottom bar (add to cart / buy) lead to the order placement flow? Trace from the bottom navigation component to where the order is created.\"\n    }\n  ],\n  \"Nix\": [\n    {\n      \"name\": \"agenix\",\n      \"repo\": \"https://github.com/ryantm/agenix\",\n      \"size\": \"Small\",\n      \"files\": \"~13\",\n      \"question\": \"When a NixOS system activates, how does a secret declared under age.secrets get decrypted and installed at its runtime path? Trace the flow from the age.secrets option definition to the activation machinery that performs the decryption.\"\n    },\n    {\n      \"name\": \"nix-darwin\",\n      \"repo\": \"https://github.com/nix-darwin/nix-darwin\",\n      \"size\": \"Medium\",\n      \"files\": \"~207\",\n      \"question\": \"How does setting services.yabai.enable = true become a running launchd service? Trace the flow from the yabai module's options to the launchd daemon definition that generates the plist.\"\n    },\n    {\n      \"name\": \"home-manager\",\n      \"repo\": \"https://github.com/nix-community/home-manager\",\n      \"size\": \"Large\",\n      \"files\": \"~3390\",\n      \"question\": \"How does programs.git.enable produce the final git config file in the user's home directory? Trace the flow from the git program module to the home-files machinery that links generated files into place.\"\n    }\n  ]\n}\n",".claude/skills/add-lang/SKILL.md":"---\nname: add-lang\ndescription: Add tree-sitter language support to codegraph end-to-end — wire the grammar + extractor, write tests, then benchmark extraction quality and retrieval value on 3 popular real-world repos. Use when the user runs /add-lang <language> or asks to add/support a new language (e.g. Lua, Elixir, Zig, OCaml) in codegraph.\n---\n\n# Add a language to CodeGraph\n\nWire a new tree-sitter language into codegraph's extraction pipeline, prove it\nextracts real symbols on popular repos, and prove it beats no-codegraph for an\nagent. Runs **fully autonomously** — pick repos, benchmark, update docs, then\nreport. **Never commit, push, publish, or tag** (house rule); leave all changes\nfor the user to review.\n\nThe argument is the language token used throughout the `Language` union, e.g.\n`lua`, `elixir`, `zig`. If none was given, ask which language. Use the lowercase\nsingle-token form everywhere (`csharp`, not `c#`).\n\n## Prerequisites\n- Run from the codegraph repo root. `node`, `git`, `gh`, and a logged-in\n  `claude` CLI (the benchmark spawns real `claude -p` runs).\n- The benchmark uses the local dev build — Step 8 builds + links it on PATH.\n\n## Workflow\n\nCopy this checklist and work through it in order:\n```\n- [ ] 1. Resolve language; bail early if already supported (just benchmark)\n- [ ] 2. Find a grammar + health-check it (ABI / heap corruption)\n- [ ] 3. Discover the grammar's AST node types (dump-ast.mjs)\n- [ ] 4. Wire the language (4 files; sometimes a 5th core touch)\n- [ ] 5. Build + verify-extraction loop until PASS\n- [ ] 6. Add extraction tests; make them green\n- [ ] 7. Auto-pick 3 popular repos by size tier; add to corpus.json\n- [ ] 8. Benchmark all 3: extraction + with/without A/B\n- [ ] 9. Update README + CHANGELOG\n- [ ] 10. Report; do NOT commit\n```\n\n### Step 1 — Resolve + short-circuit\n\nCheck whether the language is already wired: look for the token in the\n`LANGUAGES` const (`src/types.ts`) and the `EXTRACTORS` map\n(`src/extraction/languages/index.ts`). If it is already supported (e.g.\n`typescript`, `rust`), **skip Steps 2–6** and go straight to benchmarking\n(Steps 7–8) to validate/measure it — note in the report that no code changed.\n\n### Step 2 — Find a grammar, then health-check it\n\n```bash\nls node_modules/tree-sitter-wasms/out/ | grep -i <lang>   # csharp -> c_sharp\n```\n- **Present** → likely off-the-shelf; `grammars.ts` resolves it from\n  `tree-sitter-wasms` automatically. (Many languages: elixir, zig, ocaml,\n  solidity, toml, yaml, …)\n- **Absent** → vendor a `.wasm` into `src/extraction/wasm/` (like `pascal` /\n  `scala` / `lua`) and add the token to the vendored branch in Step 4.\n\n**Always health-check before writing an extractor — a *present* grammar can\nstill be unusable:**\n```bash\nnode scripts/add-lang/check-grammar.mjs <lang> path/to/valid-sample.<ext>\n```\nIt prints the grammar's ABI version and runs it the way indexing does: in a\nchild process with indexing's V8 flags, beside the vendored grammars, parsing a\nvalid sample many times and once at the 1 MiB file-size limit. If it **FAILs**\n(ERROR trees, or later parses giving a different tree, on valid code — an old ABI\ncorrupting the shared WASM heap, which silently drops nested calls/imports on\nevery file after the first; e.g. the tree-sitter-wasms **Lua** grammar is ABI 13\nand fails — or the process running it crashing or hanging, even after every\nparse finished), do NOT use that wasm. **Vendor a newer (ABI 14/15) build instead:**\n```bash\nnpm pack @tree-sitter-grammars/tree-sitter-<lang>   # often ships a prebuilt *.wasm\n# or build one: npx tree-sitter build --wasm   (needs Docker/emscripten)\ncp <the>.wasm src/extraction/wasm/tree-sitter-<lang>.wasm\n```\nthen add the token to the vendored branch in Step 4 and re-run check-grammar on\nthe vendored path until it PASSes. **If you cannot obtain a healthy wasm, STOP\nand tell the user.**\n\n### Step 3 — Discover AST node types\n\nGet a representative source file (write a small sample covering functions,\nclasses/structs, imports, enums; or `curl` a raw file from a known repo), then:\n```bash\nnode scripts/add-lang/dump-ast.mjs <lang> path/to/sample.<ext>\n# vendored grammar: pass the wasm path instead of the token\nnode scripts/add-lang/dump-ast.mjs src/extraction/wasm/tree-sitter-<lang>.wasm sample.<ext>\n```\nThe frequency table + field names (`name:`, `parameters:`, `body:`,\n`return_type:`) tell you what to map. Open the existing extractor closest to the\nlanguage's paradigm as a model: `rust.ts`/`scala.ts` (functional, traits),\n`java.ts`/`csharp.ts` (OO), `python.ts`/`ruby.ts` (scripting), `go.ts`\n(top-level methods + receivers).\n\n### Step 4 — Wire the language (4 files)\n\nThese are exact, fragile wiring — match the existing style precisely:\n\n1. **`src/types.ts`** — TWO edits:\n   - add `'<lang>',` to the `LANGUAGES` const (before `'unknown'`);\n   - add `'**/*.<ext>',` to `DEFAULT_CONFIG.include`. **Don't skip this** — it's\n     the file-scan allowlist; without the glob, `codegraph init` finds **0\n     files** even though detection/extraction are wired.\n2. **`src/extraction/grammars.ts`** — three maps:\n   - `WASM_GRAMMAR_FILES`: `<lang>: 'tree-sitter-<lang>.wasm',`\n   - `EXTENSION_MAP`: each file extension → `'<lang>'` (e.g. `'.lua': 'lua',`)\n   - `getLanguageDisplayName`: `<lang>: '<Display Name>',`\n   - **vendored only**: add `<lang>` to the\n     `(lang === 'pascal' || lang === 'scala' || …)` wasm-path branch.\n3. **`src/extraction/languages/<lang>.ts`** — new file exporting\n   `export const <lang>Extractor: LanguageExtractor = { … }`. Map the node types\n   from Step 3. Required fields: `functionTypes`, `classTypes`, `methodTypes`,\n   `interfaceTypes`, `structTypes`, `enumTypes`, `typeAliasTypes`,\n   `importTypes`, `callTypes`, `variableTypes`, `nameField`, `bodyField`,\n   `paramsField`. Add hooks as the grammar needs them (`getSignature`,\n   `getVisibility`, `isExported`, `extractImport`, `visitNode`, `getReceiverType`,\n   `interfaceKind`, `enumMemberTypes`, etc. — see\n   `src/extraction/tree-sitter-types.ts`).\n4. **`src/extraction/languages/index.ts`** — `import { <lang>Extractor } from\n   './<lang>';` and add `<lang>: <lang>Extractor,` to `EXTRACTORS`.\n\n**Sometimes a 5th, core touch in `src/extraction/tree-sitter.ts`** — variable\nextraction has per-language branches in `extractVariable` (the generic fallback\nonly finds direct `identifier`/`variable_declarator` children). If the grammar\nnests declared names (e.g. Lua's `variable_declaration → variable_list`), add a\n`} else if (this.language === '<lang>')` branch there, mirroring the existing\nts/python/go ones. Import forms that aren't a distinct node (Lua/Ruby `require`\nis a *call*) are handled in the extractor's `visitNode` hook instead.\n\n### Step 5 — Build + verify loop\n\n```bash\nnpm run build            # tsc + copy-assets (copies any vendored *.wasm into dist/)\n```\nIndex a small sample repo and check extraction:\n```bash\n( cd <sample-repo> && codegraph init -i )\nnode scripts/add-lang/verify-extraction.mjs <sample-repo> <lang>\n```\n`verify-extraction.mjs` fails (exit 1) if the language isn't detected or only\n`file`/`import` nodes were produced — the classic symptom of wrong node-type\nnames. On FAIL or a thin WARN: re-run `dump-ast.mjs` on a richer file, fix the\nmappings in `<lang>.ts`, `npm run build`, re-index, re-verify. **Repeat until\nPASS.**\n\n### Step 6 — Tests\n\nAdd to `__tests__/extraction.test.ts`, modeled on the `Rust Extraction` block:\n- a `detectLanguage` assertion in `describe('Language Detection')`\n- a `describe('<Lang> Extraction')` block asserting functions/classes/imports\n  are extracted from an inline source string.\n```bash\nnpx vitest run __tests__/extraction.test.ts\n```\nGreen before continuing.\n\n### Step 7 — Auto-pick 3 repos + corpus\n\nPick **without asking**. Find candidates, then curate 3 that are genuinely\n`<lang>`-dominant, one per size tier:\n```bash\ngh search repos --language=<lang> --sort=stars --limit 40 \\\n  --json fullName,stargazerCount,description\n```\nTiers (match `corpus.json`): **Small** <~150 files · **Medium** ~150–1500 ·\n**Large** >~1500. Skip repos that are tagged `<lang>` but mostly another\nlanguage. Write one cross-file architecture **question** per repo (the kind that\nneeds tracing across files). Add a `\"<Language>\"` block to\n`.claude/skills/agent-eval/corpus.json` (fields: `name`, `repo`, `size`,\n`files`, `question`) so `/agent-eval` can reuse them.\n\n### Step 8 — Benchmark all 3 (extraction + A/B)\n\nMake the dev build the codegraph on PATH **once**, then loop:\n```bash\nnpm run build && ./scripts/local-install.sh\nscripts/add-lang/bench.sh <lang> <name> <url> \"<question>\" headless   # ×3\n```\n`bench.sh` clones (shared `/tmp/codegraph-corpus`), wipes + indexes, runs\n`verify-extraction.mjs`, then the with/without retrieval A/B via\n`scripts/agent-eval/run-all.sh` (skips the paid A/B if extraction is broken).\nRead each `parse-run.mjs` summary printed by `run-all.sh`: tool calls, file\n`Read`s, Grep/Bash, codegraph-tool calls, duration, and **cost** — for both the\n`with` and `without` arms. After the loop, restore the dev link if needed:\n`./scripts/local-install.sh`.\n\n### Step 9 — Docs + CHANGELOG\n\n- **README.md**: add `<Lang>` to the \"19+ Languages\" feature bullet, and add a\n  row to the **Supported Languages** table:\n  `| <Lang> | \\`.ext\\` | Full support (classes, methods, …) |`.\n- **CHANGELOG.md**: add an `## [Unreleased]` section at the top (above the\n  latest version) with `### Added` → a user-perspective bullet, e.g.\n  *\"CodeGraph now indexes **<Lang>** (`.ext`) — functions, classes, imports, and\n  call edges.\"* If `## [Unreleased]` already exists, append under it. (It's\n  folded into the next versioned block at release time.)\n\n### Step 10 — Report (do NOT commit)\n\nSummarize for review:\n- **Files changed**: the 4 wiring edits + new extractor + tests + README +\n  CHANGELOG + corpus.json (+ any vendored `.wasm`).\n- **Extraction** per repo: files / nodes / edges / `verify-extraction` result.\n- **A/B** per repo: `with` vs `without` (tool calls, file Reads, cost) and a\n  one-line verdict — did codegraph reduce effort, and did both arms reach a\n  correct answer?\n- **Gaps / follow-ups** (node types not yet mapped, resolution edges missing,\n  framework routes, etc.).\n\nHand the changes to the user. **Do not** run `git commit`/`push` or publish —\nreleases go through the GitHub Actions Release workflow.\n\n## Notes\n- The A/B spawns real **paid** `claude -p` runs (opus, `--max-budget-usd`),\n  2 arms × 3 repos. The corpus dir `/tmp/codegraph-corpus` is shared with\n  `/agent-eval`, so clones are reused across runs.\n- Any new `*.wasm` must live in `src/extraction/wasm/` — `copy-assets` (run by\n  `npm run build`) ships it; otherwise it won't be in `dist/`.\n- An index must be served by the **same** binary that built it. Step 8 builds +\n  links the dev build first, so this holds.\n- If a grammar can't be obtained, or extraction can't reach PASS, **STOP and\n  report** — don't ship a half-wired language.\n"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nClaude Code project guidance for this repository.\n\nPrimary instructions live in the canonical agent guide — import it:\n\n@AGENTS.md\n\n## Claude-only notes\n\n- Prefer the repo-root `AGENTS.md` as the source of truth for build/test/architecture/house rules. Edit that file (not this wrapper) when guidance changes.\n- Claude Code can `@`-import nested guides too (e.g. `@docs/AGENTS.md`) when working on design/eval docs; Codex loads nested `AGENTS.md` automatically when the session cwd is under that directory.\n- Do not reintroduce a duplicated `## CodeGraph` MCP tool-guidance block here — `src/mcp/server-instructions.ts` is the single source of truth (issue #529); the installer strips legacy marker blocks on upgrade.\n",".claude/skills/agent-eval/SKILL.md":"---\nname: agent-eval\ndescription: Benchmark CodeGraph retrieval quality on a real codebase by comparing agent behavior with vs without CodeGraph. Use when the user runs /agent-eval or asks to test, benchmark, audit, or validate a codegraph version (the local dev build or a published npm version) against a language's repo.\n---\n\n# CodeGraph Quality Audit\n\nMeasures how much CodeGraph helps an agent versus plain grep/read, for a chosen\ncodegraph version on a chosen real-world repo. Drives the harness in\n`scripts/agent-eval/`.\n\n## Prerequisites\n- `tmux` 3+, a logged-in `claude` CLI, `node`, `git` (macOS/Linux).\n- Run from the codegraph repo root.\n\n## Workflow\n\nCopy this checklist:\n```\n- [ ] 1. Pick version (local or npm)\n- [ ] 2. Pick language\n- [ ] 3. Pick repo by size\n- [ ] 4. Pick harness (headless / tmux / both)\n- [ ] 5. Run audit.sh in the background\n- [ ] 6. Report results\n```\n\n**Step 1 — version.** Ask with `AskUserQuestion`: which codegraph version to test.\nOffer \"Local dev build\" and \"Latest published\"; the free-text \"Other\" lets the\nuser type a specific version (e.g. `0.7.10`). Map the answer to a VERSION token:\n- \"Local dev build\" → `local`\n- \"Latest published\" → `latest`\n- a typed version → that string (e.g. `0.7.10`)\n\n**Step 2 — language.** Read `.claude/skills/agent-eval/corpus.json`. Ask with\n`AskUserQuestion` which language to test, listing the languages that have entries.\n\n**Step 3 — repo.** From the chosen language's entries, ask which repo. Label each\noption with its size and file count, e.g. `excalidraw — Medium (~600 files)`.\nEach entry carries the `repo` URL and a representative `question`.\n\n**Step 4 — harness.** Ask with `AskUserQuestion` which harness to run, and map\nthe answer to a MODE token:\n- \"Headless\" → `headless` — `claude -p` with stream-json: exact tokens/cost and a\n  clean tool sequence (2 runs, fast, no TTY).\n- \"Interactive (tmux)\" → `tmux` — drives the real Claude TUI in tmux: faithful\n  Explore-subagent behavior, metrics from session logs (2 runs, slower).\n- \"Both\" → `all` — headless + interactive (4 runs).\n\n**Step 5 — run.** Launch in the background (sets the version, clones if missing,\nwipes + re-indexes, runs the chosen arms — several minutes):\n```bash\nscripts/agent-eval/audit.sh <VERSION> <repo-name> <repo-url> \"<question>\" <MODE>\n```\n\n**Step 6 — report.** When the job finishes, read the log and report per arm:\n- Headless (`parse-run.mjs`): total tool calls, file `Read`s, Grep/Bash,\n  codegraph-tool calls, duration, **total cost**.\n- Interactive (`parse-session.mjs`): the `VERDICT: codegraph_explore used Nx |\n  Read N | Grep/Bash N` and `TOKENS:` lines.\n- Both paths also print the three feedback metrics — residual context occupancy,\n  explore sufficiency, allocation efficiency — and a headless A/B ends with a\n  side-by-side `ARM COMPARISON` table. Report that table, and check its\n  contamination row first: `CLI calls that RETURNED output` > 0 means the arm\n  reached codegraph through Bash and its numbers are void. How to read the rest:\n  `docs/benchmarks/agent-eval-feedback-metrics.md`.\n\nLead with cost + tool/Read counts — they are the reliable signals; raw token\nin/out are confounded by subagent delegation and prompt caching. State whether\ncodegraph reduced effort and whether both arms reached a correct answer.\n\n## Notes\n- The index is rebuilt every run (`audit.sh` wipes `.codegraph`) — different\n  versions extract differently, so an index must be served by the same binary\n  that built it.\n- `audit.sh` temporarily mutates the global `codegraph` install for the test,\n  then restores your dev link via `local-install.sh`.\n- Corpus repos are cloned to `/tmp/codegraph-corpus` (reused if already present).\n- Add or edit repos in `corpus.json` (fields: `name`, `repo`, `size`, `files`,\n  `question`).\n","docs/AGENTS.md":"# AGENTS.md (docs/)\n\nNested Codex guidance under `docs/`. Loaded with the repo-root `AGENTS.md` when cwd is under `docs/` (Codex walks root to cwd; shared `project_doc_max_bytes` budget).\n\nRoot `AGENTS.md` already carries the non-negotiable retrieval principles (adapt-the-tool, explore budgets, end-to-end synthesis). This file holds the longer validation methodology and Excalidraw worked example that were moved out of root to fit the budget.\n\n### Validation methodology (REQUIRED for every new language/framework)\n\nFor each **language × framework**, validate on **small, medium, and large** real repos with **≥3 different flow prompts** each:\n\n1. **Pick the canonical flow** for the framework (\"how does X reach Y\": state→render, request→handler→view, query→SQL, action→reducer→store…).\n2. **Deterministic probes** (`scripts/agent-eval/probe-{node,explore}.mjs` against the built `dist/`): `codegraph_explore` with the flow's symbol names connects from→to end-to-end with no break (its Flow section shows the path); **no node explosion** (`select count(*) from nodes` stable before/after re-index); synthesized-edge **precision** spot-check (`select … where provenance='heuristic'`).\n3. **Agent A/B** (`scripts/agent-eval/run-all.sh <repo> \"<Q>\"`): with vs without codegraph, **≥2 runs/arm** (run-to-run variance is large — never conclude from n=1). Record **duration, total tool calls, Read, Grep**. Optional forced-Read-0 sufficiency proof via the block-read hook (`scripts/agent-eval/hook-settings.json`).\n   - **Every run also reports three feedback metrics** — residual context occupancy, explore sufficiency (what the agent did NEXT after each explore), and allocation efficiency (share of returned bytes the answer cited) — under each run, plus a side-by-side arm table (`compare-arms.mjs`). Entry point: `docs/benchmarks/agent-eval-feedback-metrics.md`. Reading them: `Read a file we returned` is an allocation miss, `Read a file we did NOT return`/`Grep` is recall; allocation efficiency is **relative** (attribution is by citation) so it is only valid between builds on the same question; occupancy *shares* are Claude Code / 200k and don't transfer to another host — the arm ratio does.\n   - **The `codegraph` CLI is blocked in every arm** (`no-cli-shim.sh`: sanitized PATH + a PreToolUse hook, shared by both harnesses). Without it 14 of 15 without-arm runs in one 7-repo pass reached codegraph through Bash. Check the contamination row before believing any number: `CLI calls that RETURNED output` > 0 invalidates the run (in a new-vs-baseline A/B it silently drops calls from all three metrics, since a CLI explore is not a tool call).\n   - **Model policy — every A/B arm runs Claude with `--model sonnet --effort high`. Always. Never Opus/Fable.** All `scripts/agent-eval/*.sh` default to this (`MODEL`/`EFFORT` env override exists — don't raise it without an explicit reason from the maintainer). Two reasons, and the second matters more than cost: (a) Sonnet doesn't burn tokens; (b) **Sonnet is the deliberate floor model** — codegraph's real users attach it to whatever agent they already run (Cursor Composer, Gemini, etc.), so we validate on a \"dumber\" model on purpose: a stronger model's tool-use covers up the salience/sufficiency problems a weaker one exposes. An affordance that lands on Sonnet generalizes up to every host; one that only works on Opus/Fable doesn't generalize down to the agents most users actually have. Both arms always use the same model.\n   - **MCP attach is a startup-latency issue, not a hard block.** On a multi-step task the agent dives into Read/grep before codegraph finishes its ~2-3s startup (worse when the eval is itself run nested inside a Claude session, under CPU contention), so it runs with no codegraph. Fix: **pre-warm a persistent daemon** for the target (`CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS` high; spawn `serve --mcp --path <target> </dev/null &`; wait for `.codegraph/daemon.sock`) **and skip the startup re-exec** (`CODEGRAPH_WASM_RELAUNCHED=1`) so claude connects before the agent's first turn. Don't trust claude's `init` snapshot — it can read `status:\"pending\"` / 0 tools even when it then connects; judge by actual codegraph usage in `parse-run.mjs`'s `by type`. To isolate a change — **new-build vs baseline-build, both codegraph-on** (vs run-all.sh's with-vs-without) — use `scripts/agent-eval/ab-new-vs-baseline.sh <indexed-repo> \"<task>\" [baseline-ref]` (it bakes in the pre-warm).\n4. **Pass bar:** a normal flow question reaches **~0 Read/Grep within the repo's explore-call budget**, runs **faster** than without-codegraph, and shows **no regression on a control repo**. Record the numbers in `docs/design/dynamic-dispatch-coverage-playbook.md` (the coverage matrix).\n\nFull playbook + per-mechanism design: `docs/design/dynamic-dispatch-coverage-playbook.md` and `docs/design/callback-edge-synthesis.md`.\n\n### Worked example — Excalidraw (TS/React, medium, 643 files)\n\nThe template to replicate per language/framework. Question: *\"how does updating an element re-render the canvas on screen?\"* (the full flow crosses three React boundaries: observer callback, `setState`→`render`, and JSX child).\n\n| Stage | duration | Read | Grep | codegraph |\n|---|---|---|---|---|\n| Without codegraph | 115–139s | 9–10 | 10–11 | 0 |\n| Broken (explore-budget regression) | 131–139s | 5–10 | 3–5 | 6–14 |\n| Fixed (budget + msgs + synthesis) | 64–112s | 0–2 | 2–4 | 3–**10** |\n| + trace-first steering | **51–74s** | **0–2** | 0–4 | **3–4** |\n\nn=4 unhooked runs/stage, same prompt. After steering flow questions to `codegraph_trace` first: **best run 0 Read / 0 Grep / 3 codegraph / 51s**; **2 of 4 fully clean** (0 Read, 0 Grep). Steering eliminated the over-drill variance — call count tightened from 3–10 to 3–4, trace adoption went 3/4 → 4/4, and the `search`+`callers` path-reconstruction floundering dropped to 0. Run-to-run variance is still real; report the range, never a single run. **Residual reads/greps are all the nonce data-flow** (`canvasNonce` — a local prop with no graph edges); that's the def-use/data-flow frontier, left deliberately uncovered (tracking every local would explode the graph). Validated: `trace(mutateElement, renderStaticScene)` connects in **6 hops** across all three boundaries (`mutateElement → triggerUpdate → [callback] triggerRender → [react-render] render → [jsx] StaticCanvas → renderStaticScene`), each hop showing inline source + the wiring site; node count stable at 9,289; 1 callback + 46 react-render + 280 jsx-render synthesized edges (no explosion, precision-checked).\n\n\nAlso see: `docs/design/dynamic-dispatch-coverage-playbook.md`, `docs/design/callback-edge-synthesis.md`, `docs/benchmarks/call-sequence-analysis.md`, `docs/benchmarks/agent-eval-feedback-metrics.md`.\n","AGENTS.md":"# AGENTS.md\n\nCanonical project guidance for coding agents working in this repository (Codex/Astra, Claude Code via `@AGENTS.md`, Cursor, etc.).\n\n**Codex size note:** root `AGENTS.md` is intentionally kept near ~35 KiB (critical build/test/arch/retrieval rules retained). Longer validation methodology + the Excalidraw worked example live in `docs/AGENTS.md`. This environment sets `project_doc_max_bytes = 49152` so root (and root+`docs/` when cwd is under `docs/`) are not silently truncated at the 32 KiB default.\n\n## Project Overview\n\nCodeGraph is a local-first code intelligence library + CLI + MCP server. It parses any supported codebase with tree-sitter, stores symbols/edges/files in SQLite (FTS5), and exposes a knowledge graph to AI agents (Claude Code, Cursor, Codex CLI, opencode) over MCP. Per-project data lives in `.codegraph/`. Extraction is deterministic — derived from AST, not LLM-summarized.\n\nDistributed as `@colbymchenry/codegraph` on npm; same binary serves as installer, indexer, and MCP server.\n\n## Build, Test, Run\n\n```bash\nnpm run build           # tsc + copy schema.sql and *.wasm + build the viewer into dist/; chmods dist/bin/codegraph.js\nnpm run build:lib       # the viewer's components as @colbymchenry/codegraph-ui (ui/dist) — NOT part of `build`\nnpm run dev             # tsc --watch\nnpm run clean           # rm -rf dist\n\nnpm test                # vitest run (all)\nnpm run test:watch\nnpm run test:eval       # only __tests__/evaluation/\nnpm run eval            # build then run __tests__/evaluation/runner.ts via tsx\n\nnpm run cli             # build then run the local dist binary\n\n# Single test file / pattern\nnpx vitest run __tests__/installer-targets.test.ts\nnpx vitest run __tests__/extraction.test.ts -t \"TypeScript\"\n```\n\n`copy-assets` (called from `build`) copies `src/db/schema.sql` and all `src/extraction/wasm/*.wasm` files into `dist/`. **Any new SQL or grammar wasm must be copied or it won't ship.**\n\nOne other build step writes into `dist/` and is subject to the same rule: `build:ui` builds the\nbrowser viewer into `dist/viewer/` (never `dist/ui/` — that's the terminal ui).\n`scripts/check-ui-build.mjs` asserts both `dist/viewer/` and the copied grammars in\n`dist/extraction/wasm/` after every build and inside every release archive — the viewer's syntax\nhighlighting reads a file with the same grammar the engine indexed it with, so a missing wasm is an\nunhighlighted screen as well as an extraction gap.\n\n`npm run build:lib` is separate and does NOT run as part of `npm run build`: it compiles the same\n`ui/src` tree a second way, with `svelte-package`, into `ui/dist` — the `@colbymchenry/codegraph-ui`\ncomponent library the Pro app imports (task CG-61). `scripts/check-ui-package.mjs` then prunes the\nstandalone app's shell out of it, resolves the extensionless import specifiers `svelte-package`\nleaves behind, and asserts the seam: nothing outside `lib/adapter.js` may reach the network. The\npackage is **prepared, not published** — `ui/package.json` carries `\"private\": true` deliberately,\nand `scripts/pack-npm.sh` only packs a tarball when `CODEGRAPH_PACK_UI=1`.\n\nTests run as **two vitest projects** (`vitest.workspace.mts`): `engine` (node) and `ui` (jsdom, the\nSvelte plugin, `resolve.conditions: ['browser']`) for the single `__tests__/ui-package.test.ts`.\n`npm test` still runs both. The split is not cosmetic — `browser` is a package-resolution\ncondition, and applied globally it hands the engine's suites the browser builds of\n`web-tree-sitter` and friends. The root config (`vitest.config.mts`, `.mts` because the plugin is\nESM-only and the repo is CJS) is the shared base; note that a workspace project **concatenates**\nthe base's `include` with its own, which is why the `ui` project does not `extends` it.\n\nNode engines: `>=20.0.0 <25.0.0`. There is a hard exit on Node 25.x and below 20 (see `src/bin/node-version-check.ts`).\n\n## Architecture\n\n### Layered pipeline\n\n```\nfiles → ExtractionOrchestrator (tree-sitter) → DB (nodes/edges/files)\n              ↓\n       ReferenceResolver (imports, name-matching, framework patterns)\n              ↓\n       GraphQueryManager / GraphTraverser (callers, callees, impact)\n              ↓\n       ContextBuilder (markdown/JSON for AI consumption)\n```\n\nThe public API surface is `src/index.ts` — the `CodeGraph` class wires all the layers and re-exports types. Library users only touch this file; the MCP server and CLI also drive it.\n\n### Module layout\n\n- `src/index.ts` — `CodeGraph` class: `init`/`open`/`close`, `indexAll`, `sync`, `searchNodes`, `getCallers`/`getCallees`, `getImpactRadius`, `buildContext`, `watch`/`unwatch`.\n- `src/db/` — `DatabaseConnection`, `QueryBuilder` (prepared statements), `schema.sql`, `sqlite-adapter.ts`. Backed by Node's built-in **`node:sqlite`** (`DatabaseSync`) — real SQLite with WAL + FTS5, exposed through a thin better-sqlite3-shaped adapter. The bundled runtime always ships Node ≥22.5, so `node:sqlite` is always available: **no native build step and no wasm fallback**. (Running from source needs Node ≥22.5.) `codegraph status` reports the live backend (`node-sqlite`, the sole backend).\n- `src/extraction/` — `ExtractionOrchestrator`, tree-sitter wrappers, per-language extractors under `languages/` (one file per language), plus standalone extractors for non-tree-sitter formats (`svelte-extractor.ts`, `vue-extractor.ts`, `liquid-extractor.ts`, `dfm-extractor.ts` for Delphi). `parse-worker.ts` runs heavy parsing off the main thread.\n- `src/resolution/` — `ReferenceResolver` orchestrates `import-resolver.ts` (with `path-aliases.ts` for tsconfig path aliases + cargo workspace member globs), `name-matcher.ts`, and `frameworks/` (Express, Laravel, Rails, FastAPI, Django, Flask, Spring, Gin, Axum, ASP.NET, Vapor, React Router, Next.js — `nextjs.ts`: pages and `route.ts` handlers from files, `router.push` / `redirect` / `NextResponse.redirect` as `navigates` edges, with `next-router-synthesizer.ts` for `<Link href>` — Expo Router, SvelteKit, Vue/Nuxt, Cargo workspaces). Frameworks emit `route` nodes and `references` edges. `callback-synthesizer.ts` holds the whole-graph synthesis passes (`SYNTH_PASSES`, merged in registry order — first-seen wins a duplicate pair) with the language gates; `tier-synthesizer.ts` is the cross-tier pass (a client's literal `fetch`/`axios` path onto its own route, a queue job onto its consumer, a bus / socket event onto its handler — `channel`, `tier`, `registeredAt` on every edge; registered before the in-process emitter pass so its more specific edge wins); `synth-utils.ts` has the helpers they share (`enclosingFn`, `enclosingValue`, `makeLineAt`). Express's `postExtract` composes `app.use('/prefix', router)` mounts onto a mounted file's route names, idempotently (the original path stays in `qualifiedName`).\n- `src/graph/` — `GraphTraverser` (BFS/DFS, impact radius, path finding) and `GraphQueryManager` (high-level queries), plus the shared query-time derivations more than one surface renders: `named-symbol-flow.ts` (the one path finder, behind `codegraph_explore`'s Flow section and the viewer's Flow strip), `dynamic-boundary-report.ts` (where the graph stops), `type-hierarchy.ts` (ancestors/subtypes and the implementation count explore prints and the viewer draws),\n  `dead-code.ts` (unreferenced symbols, and every reason a candidate is NOT claimed), `synthesized-hop.ts` (how a synthesized hop reads, in explore's Flow, the `codegraph_node` trail and ContextBuilder's call paths — a new synthesizer's wording goes here). A derivation that two callers render must live here, not in `ToolHandler` — two derivations eventually disagree.\n- `src/context/` — `ContextBuilder` + formatter for markdown/JSON output.\n- `src/search/` — full-text query parser and helpers for FTS5.\n- `src/sync/` — `FileWatcher` (native FSEvents/inotify/RDCW) with debounce + filter, and git-hook helpers.\n- `src/mcp/` — MCP server (`MCPServer`, `tools.ts`, `transport.ts`). `server-instructions.ts` is what the server returns in the MCP `initialize` response — keep it in sync with the user-facing tool guidance.\n- `src/installer/` — see below.\n- `src/bin/codegraph.ts` — CLI (commander). Subcommands: `install`, `init`, `uninit`, `index`, `sync`, `status`, `query`, `files`, `context`, `affected`, `serve --mcp`.\n- `src/ui/` — terminal UI (shimmer progress, worker).\n- `src/ui-server/` -- read-only JSON API for the `codegraph ui` browser viewer (`api/`: `node`, `flow`, `map`, `screens`, `steps`, `deadcode`, `trails`, `program`, ...) plus static server; Svelte viewer lives in `ui/` (see `docs/design/codegraph-ui-design-spec.md`). `screens`/`steps`/`program` share one fold (`via`/`when` via `graph/branch-guards.ts`); `api/effects.ts` curates calls that leave the index; `api/route-roots.ts` names where a route's code starts. Derivations rendered by more than one surface belong in `src/graph/`, not `ToolHandler`.\n  **Not released yet:** `codegraph ui` / `web` are refused (and hidden from `--help`) unless `CODEGRAPH_UI=1` is set — `src/bin/viewer-gate.ts`. Viewer changelog entries wait in `docs/viewer-launch-changelog.md`, not under `[Unreleased]`. At launch: delete the gate, move those entries back, drop the \"not released yet\" notes from `site/`.\n\n### NodeKind / EdgeKind\n\nDefined in `src/types.ts`. Both extractors and resolvers must use these exact strings.\n\n- **NodeKind**: `file`, `module`, `class`, `struct`, `interface`, `trait`, `protocol`, `function`, `method`, `property`, `field`, `variable`, `constant`, `enum`, `enum_member`, `type_alias`, `namespace`, `parameter`, `import`, `export`, `route`, `component`, `union`.\n- **EdgeKind**: `contains`, `calls`, `imports`, `exports`, `extends`, `implements`, `references`, `type_of`, `returns`, `instantiates`, `overrides`, `decorates`.\n\n### Multi-agent installer\n\n`src/installer/` is the entry point for `codegraph install` (and the bare `codegraph`/`npx @colbymchenry/codegraph` invocation). Architecture:\n\n- `targets/registry.ts` lists every supported agent.\n- `targets/types.ts` defines the `AgentTarget` interface — adding a 5th agent (Continue, Zed, Windsurf…) is **one new file in `targets/` + one entry in `registry.ts`**. Each target owns its config-file location and MCP-server JSON/TOML/JSONC writing. (Claude, Codex, Gemini and opencode also write a short instructions-file block — see below.)\n- Current targets: `claude.ts`, `cursor.ts`, `codex.ts`, `opencode.ts`.\n- `targets/toml.ts` is a hand-rolled TOML serializer scoped to `[mcp_servers.codegraph]` (used by Codex). Sibling tables and `[[array_of_tables]]` are preserved verbatim. No new dependency.\n- opencode reads `opencode.jsonc` by default; the installer prefers existing `.jsonc`, falls back to `.json`, and creates `.jsonc` for greenfield installs. Edits are surgical via `jsonc-parser` so user comments and formatting survive install/re-install/uninstall round-trips. The MCP entry is OpenCode 2's native `mcp.servers.codegraph` with `disabled: false` and `codemode: false` (so `codegraph_explore` stays on the native tool list); a pre-#1698 `mcp.codegraph` + `enabled` entry is migrated on re-install and removed by uninstall.\n- `instructions-template.ts` holds `CODEGRAPH_INSTRUCTIONS_BLOCK`, a deliberately short marker-fenced (`<!-- CODEGRAPH_START -->`/`<!-- CODEGRAPH_END -->`) `## CodeGraph` block. The Claude, Codex, Gemini and opencode targets write it into their instructions file (`CLAUDE.md` / `~/.codex/AGENTS.md` / `~/.gemini/GEMINI.md` / `~/.config/opencode/AGENTS.md`) via `upsertInstructionsEntry`, and `uninstall` removes it. History: #529 removed the old full playbook because it duplicated the MCP `initialize` instructions; #704 brought back this small block because the instructions file reaches two audiences `initialize` cannot — Task-tool subagents and non-MCP harnesses (the `codegraph explore` CLI). Cursor and Kiro write no block; their `install` only strips one a previous install left behind. Keep the block to command names and the two surfaces; tool guidance itself still belongs in `server-instructions.ts`.\n- All installer changes need matching coverage in `__tests__/installer-targets.test.ts` — there are ~47 parameterized contract tests covering install idempotency, sibling preservation, uninstall reverses install, byte-equal re-runs returning `unchanged`, and partial-state recovery for Codex.\n\n### Cursor MCP working-directory quirk\n\nCursor launches MCP subprocesses with the wrong cwd and doesn't pass `rootUri` in `initialize`. The installer injects `--path` into Cursor's MCP args — absolute path for local installs, `${workspaceFolder}` for global installs. If you touch Cursor wiring, preserve this.\n\n### MCP server instructions\n\n`src/mcp/server-instructions.ts` is sent back to the agent in the MCP `initialize` response. This is the *first* thing every agent sees about how to use the tools, and as of issue #529 it is the **single source of truth** for agent-facing tool guidance. The installer's instructions-file block (`instructions-template.ts`, see above) only names the tool and the CLI for subagents and non-MCP agents; it does not repeat this guidance. Edit tool guidance here and nowhere else.\n\n## Retrieval performance & dynamic-dispatch coverage (do not regress)\n\nCodeGraph's core value is letting an agent answer **structural/flow** questions (\"how does X reach Y\", trace, impact, callers) with a few **fast** codegraph calls and **zero Read/Grep**. The optimization target is **wall-clock latency + tool-call count** — *don't optimize for token cost*. (Cost is **lower**, not \"flat\" as earlier framing claimed: a current-build with-vs-without A/B across the 7 README repos, median of 4, saved on average **35% cost · 57% tokens · 46% time · 71% tool calls** — reproducing the published README. The mechanism is **far fewer turns over a much smaller accumulated context** — NOT cache-ability: the without-arm's huge token volume is *mostly* cheap cache-reads, which is why token-count savings (57%) look bigger than cost savings (35%). Measure tokens by **summing per-turn assistant usage**, not `result.usage` (last-turn only in current Claude Code). See `docs/benchmarks/call-sequence-analysis.md`.) The mechanism that drives everything here: **an agent falls back to Read/Grep the instant a codegraph answer is insufficient.** So every change is judged by one question — is codegraph's answer sufficient enough to *stop* the agent from reading?\n\n**Target behavior:** a flow question resolves in **1 codegraph call on small repos, scaling to 3–5 on large**, with **Read/Grep = 0**. When reviewing a PR or trying something new, do not regress this.\n\n### Adapt the tool to the agent — don't try to change the agent\n\nThe lever that decides whether a retrieval change lands. **Test before building anything here: does this make a tool the agent _already calls_ do more with the input it _already gives_? If it instead needs the agent to behave differently — pick a different tool, query differently, learn from examples — it hits the low-salience wall and won't land.**\n\nCodeGraph's only channels to influence the agent are low-salience: the MCP `initialize` instructions (`server-instructions.ts`) and the tool descriptions. Changing them does **not** reliably move the agent's tool _choice_ or query style — validated: trace-first steering ported into the server-instructions + tool descriptions (3 wording variants) never reproduced what a CLI `--append-system-prompt` achieved, and **regressed** wall-clock vs baseline. New tools fare worse (rarely chosen — the agent under-picks even `trace`); \"better examples\" is the same steering. The agent's tool-choice does improve on its own as host models get better at tool use — but that is not ours to force.\n\nWhat works is meeting the agent where it already is:\n- **explore-flow** — `codegraph_explore` is the PRIMARY tool the agent reliably calls; its query is a precise bag of symbol names (incl. qualified `Class.method`) spanning the flow the agent is after; explore finds the call path _among those named symbols_ (riding synthesized edges) and leads its output with it. (`buildFlowFromNamedSymbols`: segment/co-naming disambiguation; ≤1 unnamed bridge so it never wanders a god-function's fan-out. Overload-aware: a PascalCase type token in the query biases an overloaded name to that type's own def — `DataRequest task` → DataRequest's `task`, not the abstract base; named-symbol files sort first.)\n- **Sufficiency** — make the tool's output complete enough that the agent stops. `codegraph_node` returns the full body + the caller/callee trail, and for an AMBIGUOUS name returns **every overload's body in one call** (so the agent never Reads a file to find the right overload — validated on Alamofire/gin). This is the after-explore depth tool (labeled SECONDARY).\n- **Errors teach abandonment** — one or two `isError: true` responses early in a session and the agent stops calling codegraph entirely (maintainer-observed, repeatedly). `isError` is reserved for genuine \"stop trying\" cases: security refusals (`PathRefusalError`) and real malfunctions (which carry a retry-once note). Every expected/recoverable condition — project not indexed, symbol not found, file not in the index — returns a **SUCCESS-shaped response carrying the guidance** (`NotIndexedError` → `textResult`, see `ToolHandler.execute`'s catch). The same principle is why the tool surface is **always exposed, even at an un-indexed root** (the old empty-`tools/list` gate was removed in #964 — it broke monorepos where only sub-projects carry a `.codegraph/`, and hid the tools from a session that started before `codegraph init`): safety comes from the response SHAPE (success-shaped guidance, never `isError`), not from hiding tools. An un-indexed root's `initialize` sends a per-project variant (`SERVER_INSTRUCTIONS_NO_ROOT_INDEX` — \"pass `projectPath` to a project that has a `.codegraph/`\"), not an \"inactive\" note; indexing is still deliberately the user's call, never the agent's.\n\nWhat fails is the inverse — folding a precise answer into a **fuzzy-input** tool: the now-removed `codegraph_context` took a description, not symbols, so it couldn't disambiguate a flow's endpoints and surfaced the _wrong feature_ (which is why it was cut). Precise output needs precise input — explore takes a symbol bag for exactly this reason. (`codegraph_trace` was likewise removed: explore-flow does its job and the agent under-picked it.)\n\nThe remaining lever under this axis is **coverage**: every flow made to connect statically (a new dynamic-dispatch synthesizer, or extracting symbols static parsing skipped — e.g. object-literal store actions in `create((set,get)=>({...}))`) is then surfaced automatically by explore-flow, no agent change needed. Reactive/reconciler runtimes (Halo's `ReactiveExtensionClient`, MediatR, Vue Proxy) are the frontier — flows there have no static edges, so nothing surfaces (correctly — silent beats wrong). Full investigation + A/B record: `docs/benchmarks/call-sequence-analysis.md` + auto-memory `project_codegraph_read_displacement`.\n\n### Explore budget — keep BOTH budgets monotonic with repo size\n\nTwo functions in `src/mcp/tools.ts` scale explore with indexed file count. This is the expected resolution (a regression here silently forces agents back to Read):\n\n| Repo | files | explore calls | chars/call | per-file |\n|---|---|---|---|---|\n| express (small) | 147 | 1 | 18K | 3800 |\n| excalidraw/django (medium) | 643–3043 | 2 | 28K | 6500 |\n| vscode (large) | 10446 | 3 | 35K | 7000 |\n| ~20k / ~40k | — | 4 / 5 | 38K | 7000 |\n\n- `getExploreBudget(fileCount)` → **call** budget: `<500→1, <5000→2, <15000→3, <25000→4, ≥25000→5` (max 5).\n- `getExploreOutputBudget(fileCount)` → **per-call** output (chars / files / per-file). **Invariant: a larger tier must never get a smaller `maxCharsPerFile` than a smaller tier.** (Regression that motivated this doc: the `<5000` tier's 2500 was *below* the `<500` tier's 3800, so on a god-file repo — excalidraw's 415 KB `App.tsx` — one explore returned <1% of the file and forced a Read.)\n- Explore output must **never tell the agent to \"use Read\"** — steer to another `codegraph_explore` and \"treat returned source as already Read.\"\n\n### Dynamic-dispatch coverage — the flow must EXIST in the graph end-to-end\n\nStatic tree-sitter extraction misses computed/indirect calls, so flows break at dynamic dispatch and the agent reads to reconstruct them. Synthesizers/resolvers bridge these so `codegraph_explore` connects them end-to-end (`src/resolution/callback-synthesizer.ts`, `src/resolution/frameworks/`). Channels today: callback/observer, EventEmitter, **React re-render** (`setState`→`render`), **JSX child** (`render`→child component), **React Native native→JS events** (`sendEvent(withName:)` / JVM `emit` → the `addListener` handler, named or inline, `rn-event-channel`), django ORM descriptor. The JS→native direction is a *resolver* (`frameworks/react-native.ts`: `RCT_EXPORT_METHOD`, `RCT_EXTERN_MODULE` Swift shims, TurboModules), which trusts receiver evidence — an alias bound to `NativeModules.X` — over the import resolver. All synthesized edges are `provenance:'heuristic'` with `metadata.synthesizedBy` + `registeredAt` (the wiring site), surfaced inline in `codegraph_explore`'s Flow section and the `codegraph_node` trail.\n\n**Principle: partial coverage is WORSE than none.** Bridging one boundary but not the next reveals a hop the agent then drills + reads to finish. Measured on excalidraw: react-render alone *raised* reads to 5–7; only completing the flow (adding the jsx-child hop) dropped it to 0–1. **Always close the flow end-to-end and re-measure** — never ship a half-bridged flow.\n\n\n### Validation methodology & worked examples\n\n**Required** for every new language/framework: validate on small/medium/large real repos with >=3 flow prompts; deterministic probes (`scripts/agent-eval/probe-*.mjs`) then agent A/B (`scripts/agent-eval/run-all.sh` / `ab-new-vs-baseline.sh`). Pass bar: ~0 Read/Grep within the explore-call budget, faster than without-codegraph, no control-repo regression.\n\nFull methodology (feedback metrics, CLI contamination guard, Sonnet/`--effort high` model policy, daemon pre-warm), the Excalidraw worked example, and coverage matrix live in:\n- `docs/AGENTS.md` (nested; also loaded when cwd is under `docs/`)\n- `docs/design/dynamic-dispatch-coverage-playbook.md`\n- `docs/design/callback-edge-synthesis.md`\n- `docs/benchmarks/call-sequence-analysis.md` / `docs/benchmarks/agent-eval-feedback-metrics.md`\n\n\nTests live in `__tests__/` and mirror the module they cover. Notable ones beyond the obvious:\n\n- `installer-targets.test.ts` — parameterized contract suite across all 4 agent targets (see installer notes above).\n- `evaluation/` — `runner.ts` + `test-cases.ts` exercise codegraph against synthetic projects and score the results; run via `npm run eval` (builds first). Not part of `npm test`.\n- `sqlite-backend.test.ts` / `node-sqlite-backend.test.ts` — pin that `node:sqlite` is the sole backend: `getBackend()` reports `node-sqlite` and the DB comes up in WAL.\n- `pr19-improvements.test.ts`, `frameworks-integration.test.ts` — regression coverage for specific past PRs/incidents; don't rename these, the names anchor to git history.\n\nTests create temp dirs with `fs.mkdtempSync` and clean up in `afterEach`. They write real files and exercise real SQLite — there is no DB mocking.\n\nEvery engine test file runs in a throwaway home dir (`__tests__/setup-home-sandbox.ts`, a `setupFiles` entry): `HOME`/`USERPROFILE` (+ Windows vars), `XDG_CONFIG_HOME` and `GIT_CONFIG_GLOBAL` point into it, and `CLAUDE_CONFIG_DIR`/`CODEX_HOME`/… are cleared; spawned children inherit it. It's a backstop — still inject writes to global state (e.g. `UpgradeDeps.wirePromptHook`, #2275).\n\n### Windows-gated tests\n\nBehavior that differs by platform (path resolution, drive letters, `SENSITIVE_PATHS`, `%APPDATA%` config dirs, CRLF) must be gated, not assumed. Use `it.runIf(process.platform === 'win32')(...)` for Windows-only assertions and `it.runIf(process.platform !== 'win32')(...)` for POSIX-only ones — e.g. `/etc` is sensitive on POSIX but resolves to `C:\\etc` (non-existent) on Windows, so an ungated `/etc` assertion fails on Windows. Validate the Windows side for real (see below); don't merge a Windows-gated test you haven't seen run.\n\n## Cross-platform validation\n\nThe dev machine — and the default `npm test` target — is **macOS**, so local runs cover the macOS path. The other two platforms aren't here; when a change is platform-sensitive (file watching, sockets / named pipes, path & symlink handling, process lifecycle, inotify budget) validate them for real rather than guessing.\n\n### Linux (Docker)\n\nWhen asked to test or validate on Linux, use **Docker** — there's no Linux box, but Docker runs on the macOS host. Build a throwaway image from the repo and run the suite inside it:\n\n- `FROM node:22-bookworm`; `COPY` the repo with a `.dockerignore` excluding `node_modules`/`dist`/`.git`/`.codegraph`; `RUN npm ci && npm run build`. Don't reuse the Mac `node_modules` — `esbuild`/`rollup` ship platform-specific binaries.\n- Run with **`docker run --rm --init`**. The `--init` is load-bearing for any process-lifecycle test (daemon reaping, the #277 PPID watchdog, idle-timeout): without a zombie-reaping PID 1, a SIGKILL'd/exited process lingers as a zombie and `process.kill(pid, 0)` still reports it *alive*, so exit-detection assertions false-fail even though the process did exit.\n- Linux is where the inotify watch budget actually bites: count a process's watches via `/proc/<pid>/fdinfo/*` (sum `^inotify ` lines on the fd whose `readlink` is `anon_inode:inotify`).\n\n### Windows (Parallels VM + SSH)\n\nFor any Windows-specific PR, bug, or implementation, validate it on the real Windows VM rather than guessing. Connection details live in the gitignored **`.parallels`** file at the repo root (VM name, guest IP, SSH user/key). `prlctl exec` needs Parallels Pro and is unavailable, so SSH is the bridge.\n\n- Connect / run from the Mac host: `ssh <user>@<guest_ip> \"...\"`. For multi-line work, pipe PowerShell over stdin and **refresh PATH from the registry** first (sshd's session has a stale PATH after winget installs):\n  ```\n  ssh colby@10.211.55.3 \"powershell -NoProfile -ExecutionPolicy Bypass -Command -\" <<'PS'\n  $env:Path = [Environment]::GetEnvironmentVariable(\"Path\",\"Machine\") + \";\" + [Environment]::GetEnvironmentVariable(\"Path\",\"User\")\n  Set-Location C:\\dev\\codegraph\n  PS\n  ```\n- Clone fresh into a **Windows-local** path (`C:\\dev\\codegraph`) and `npm ci` there — never run npm against the shared Mac repo, since `esbuild`/`rollup` ship platform-specific binaries.\n- Guest toolchain (winget): Node LTS, Git, and the **VC++ ARM64 redistributable** (required by `@rollup/rollup-win32-arm64-msvc`, which vitest pulls in).\n- Fetch a contributor PR head straight from their fork to dodge `pull/<n>/head` lag: `git fetch <fork-url> <branch>` then `git checkout -f FETCH_HEAD`.\n- Windows baseline: as of #2053 the full suite passes on the Windows 11 (ARM64) VM. The only expected exception is `security.test.ts > Session marker symlink resistance > does not follow a pre-planted symlink`, which needs symlink privileges (Developer Mode) — confirm any other failure against `origin/main` before blaming your PR. The former `mcp-initialize.test.ts` / `mcp-roots.test.ts` `EPERM` teardown failures came from tests spawning `serve --mcp` without the runtime flags (its `--liftoff-only` re-exec grandchild kept the cwd / SQLite file open); spawn it with `WASM_RUNTIME_FLAGS` and await the child's exit before removing the temp dir. Windows checkouts may be CRLF — split source lines on `/\\r?\\n/` in tests.\n- Windows worker crashes: a worker thread that ends (`terminate()`, its own `process.exit()`, or the process exiting) while V8's concurrent marker is marking its heap kills the process with exit 3221225477 (0xC0000005) and leaves no error or dump. It's worst while the worker is still loading its modules. Owners must not terminate a worker before its first message (`workerStarted` / `terminateOnceStarted`, or the pool's own handshake), and a worker that exits itself calls `collectBeforeExit()` first — both in `src/worker-teardown.ts`. `--no-concurrent-marking` also stops it, but measured about a third slower indexing on the Windows VM, so it is not used.\n\n## Releases\n\nReleased to npm and mirrored as [GitHub Releases](https://github.com/colbymchenry/codegraph/releases). `CHANGELOG.md` is the source of truth; GitHub Release notes are extracted from it.\n\n### Writing changelog entries\n\n**Default: write entries under `## [Unreleased]`** — that's the section reserved for work landing between releases. **Don't pre-create a `## [X.Y.Z]` block** for the next release: the Release workflow's first step is `scripts/prepare-release.mjs`, which automatically promotes everything under `[Unreleased]` into a new `## [X.Y.Z] - <YYYY-MM-DD>` block at release time (or merges into a pre-existing `[X.Y.Z]` block if one exists — but you don't need one). Pre-staging is what caused the v0.9.5 sparse-release-notes incident: a sparse `[0.9.5]` block hand-added before the rest of the work landed got picked by the extractor over the much-larger `[Unreleased]` section above it. Don't do that.\n\nFormatting rules for any entry (anywhere — `[Unreleased]` or otherwise):\n\n1. **Write friendly, user-facing notes — not engineer-facing ones.** Group under `### New Features` and `### Fixes` (sentence-case). Surface `### Breaking Changes` and `### Security` as their own sections **only when the release has them**; fold improvement-flavored changes into New Features. Omit empty sections. (This replaces the old Keep-a-Changelog `Added/Changed/Fixed/Removed/Deprecated` grouping: the GitHub Release page extracts each version block **verbatim** via `scripts/extract-release-notes.mjs`, and the old dense, implementation-focused entries rendered as an unreadable wall of text — so the whole CHANGELOG was rewritten to this format and every published release re-noted to match.)\n2. **One plain-language sentence per bullet:** what changed and why it matters to a user. Lead with the capability, or with the symptom that's now fixed.\n3. **Strip the internals.** No internal file paths (`src/...`), no internal symbol / function / class names, no benchmark numbers / percentages / node-or-edge counts. **Keep:** language & framework names (Go, Spring, NestJS, …), things a user types or sets (`codegraph install`, `codegraph_explore`, the `CODEGRAPH_*` env vars), agent / IDE names (Claude Code, Cursor, opencode, Kiro, …), and a brief `Thanks @user` when a contributor is credited.\n4. Issue / PR references in entries are by number (`(#403)` etc.); the GitHub renderer auto-links them in the published release notes.\n5. **Don't add a `[X.Y.Z]: https://...` link reference yourself** — `prepare-release.mjs` appends it automatically when it promotes the version (idempotent: a re-run is a no-op if it already exists).\n6. **Every release opens with a `### Highlights` block — the only part most people read.** At most ~8 one-line bullets, in plain language for someone who doesn't read code, ordered by what a typical user notices first (new agent/IDE support and setup changes, then answer quality, then reliability), plus a one-sentence upgrade note when a re-index is needed. Write or refresh it in `[Unreleased]` when a release is being prepared — not per PR — and keep the detailed `### New Features` / `### Fixes` entries below it. When `### Fixes` grows past ~15 entries, group them under `####` sub-headings (`Better answers from codegraph_explore`, `Finding your project, live updates, and the CLI`, `Indexing reliability and disk usage`, `Language and framework accuracy`) so a skimmer can find their area.\n\nMulti-word headings like `### New Features` are safe on the normal release path: `prepare-release.mjs` **Case A** moves the whole `[Unreleased]` body verbatim into `[X.Y.Z]`. (Only its rarely-used **Case B** *merge* splits sub-sections with a single-word `^### (\\w+)$` regex that wouldn't match them — and Case B fires only if a `[X.Y.Z]` block was pre-created, which rule above already forbids.)\n\n### Release flow (the user runs these)\n\nReleases are built and published by the **GitHub Actions \"Release\" workflow**\n(`.github/workflows/release.yml`). It runs `scripts/prepare-release.mjs` to\npromote `[Unreleased]` into `[<version>]` (and auto-commit + push that\nCHANGELOG change back to `main` so on-disk truth matches the published\nnotes), then bundles a Node runtime per platform (`scripts/build-bundle.sh`)\nand publishes both the GitHub Release and the npm thin-installer\n(`scripts/pack-npm.sh`: a shim package + per-platform packages).\nPublishing manually is **wrong** now — a plain `npm publish` ships the root\npackage (non-bundled), which breaks anyone on Node < 22.5.\n\n**Claude does NOT bump the version unless explicitly asked.** The maintainer\ntypically does it themselves — often by editing `package.json` directly via\nthe GitHub web UI. Don't proactively commit a version bump as part of\nunrelated work, and don't propose one when summarizing a PR.\n\nWhen the maintainer DOES bump the version, the only edit strictly required is\nto `package.json` — the workflow's \"Sync package-lock.json\" step detects a\nmismatch between `package.json` and `package-lock.json`, runs\n`npm install --package-lock-only --ignore-scripts` to rewrite the lock file's\nversion fields (top-level + `packages.\"\"`), and auto-commits + pushes the\nresult back to `main` with `[skip ci]`. So a GitHub-web-UI single-file edit to\n`package.json` is enough to kick off a clean release. (If they edit both files\nlocally, that's fine too — the sync step no-ops.) Bump `ui/package.json` to the\nsame version in that change: the component library is versioned with the\nengine, `ui-package.test.ts` pins it, and the workflow does not sync it — the\n1.6.1 bump left `main` red on exactly that.\n\nOnce `package.json` is at the target version on `main`, trigger\n**Actions → Release → Run workflow** (on `main`). The workflow:\n\n1. Syncs `package-lock.json` to `package.json`'s version if they've drifted; commits + pushes that change.\n2. Runs `prepare-release.mjs <X.Y.Z>` → promotes `[Unreleased]` → `[X.Y.Z] - <today>` in `CHANGELOG.md`, appends the link reference, commits + pushes the move with `[skip ci]`.\n3. Builds every platform bundle on one runner, generates `SHA256SUMS`.\n4. Creates the GitHub Release with notes from the freshly-promoted `[X.Y.Z]` block.\n5. Publishes the npm shim + per-platform packages. Requires the `NPM_TOKEN` repo secret.\n\n**Do not run `npm publish`, `git push`, or `git tag` yourself** — these are\npublish actions on shared state. Write the files, hand the user the commands.\n\n## House rules\n\n- The `0.7.x` line is in active multi-agent rollout. Any change to `src/installer/` (especially `targets/`) needs corresponding test coverage and a CHANGELOG entry — installer regressions break every new install silently.\n- When changing what the MCP tools do or how agents should use them, edit `src/mcp/server-instructions.ts` — it is the **single source of truth** for agent-facing tool guidance (issue #529). The installer's short instructions-file block (`instructions-template.ts`) only names `codegraph_explore` and the `codegraph explore` CLI; update it only when a tool or CLI command is renamed or removed. (The repo's own checked-in `.cursor/rules/codegraph.mdc` is dogfooding config — update it too if you use Cursor on this repo, but it ships nowhere.)\n- **Before adding or extending a router, a web framework, or a language's `WHEN` rules, read `docs/design/framework-coverage.md`.** It is the standing answer to \"what is supported and what is left\" across the three axes (route nodes → Entry points, `navigates` edges → Screens, branch-guard rules → the `WHEN` labels), with what each remaining item needs, the traps that have already cost debugging time, and the queries to re-verify it. Update it in the same change that moves a row.\n- CodeGraph provides **code context**, not product requirements. For new features, ask the user about UX, edge cases, and acceptance criteria — the graph won't tell you.\n- **When the user references issues, PR comments, or external reports, anchor them to a date and version before drawing conclusions.** Check the comment's `createdAt` against:\n  - The **last released version** — `grep -m1 '^## \\[' CHANGELOG.md` shows the top-of-file version (older releases follow). A comment dated before the latest `## [X.Y.Z] - YYYY-MM-DD` is reacting to *released* state — work that's only on `main` or on an unmerged branch doesn't apply.\n  - The **last main commit** — `git log --first-parent main -1 --format='%ai %h %s'`. A comment after the last release but before a fix on main may already be addressed there but unreleased.\n  - The **current branch's tip** — your own unmerged work obviously can't be what the comment is reacting to.\n  Always disambiguate \"released,\" \"merged-but-unreleased,\" and \"in-progress\" before agreeing that a user-reported problem is unfixed (or that a fix is incomplete). A user saying \"your fix only covers X\" about a recent PR is usually pointing at the *released* shortcomings — your in-flight branch may already address them but they have no way to know that.\n- **Version-tag every image referenced in `README.md`.** GitHub caches README images (`raw.githubusercontent.com` with a 5-minute TTL; third-party hosts sit behind the long-lived camo proxy), so updating an asset in place can keep showing the stale version. Give each README image URL a `?v=N` query tag and **bump `N` in the same commit whenever the asset bytes change** — e.g. `assets/waitlist.svg?v=2`. The changed URL sidesteps every cache so the new image shows immediately instead of waiting on a TTL to expire.\n",".cursor/rules/codegraph.mdc":"---\ndescription: CodeGraph MCP usage guide — one tool, codegraph_explore\nalwaysApply: true\n---\n<!-- CODEGRAPH_START -->\n## CodeGraph\n\nThis project has a CodeGraph MCP server configured, exposing a single tool: `codegraph_explore`. CodeGraph is a tree-sitter-parsed knowledge graph of every symbol, edge, and file. Reads are sub-millisecond and return structural information grep cannot.\n\n### Use codegraph_explore instead of reading files\n\nReach for `codegraph_explore` before grep/find or Read for any **structural** question — how does X work, how does X reach Y, what calls what, where is X defined, or surveying an area. It takes a natural-language question or a bag of symbol/file names and returns the relevant symbols' **verbatim, line-numbered source** grouped by file (the same `<n>\\t<line>` shape Read gives you, safe to Edit from), plus the call paths between them — including dynamic-dispatch hops (callbacks, React re-render, JSX children) grep can't follow — and a blast-radius summary of what depends on them. Name a file or symbol in the query to read its current source.\n\n### Rules of thumb\n\n- **Answer directly — don't delegate exploration.** ONE `codegraph_explore` usually answers the whole question; follow up with another `codegraph_explore` naming more specific symbols if you need more. Codegraph IS the pre-built index, so spawning a separate file-reading sub-task/agent — or running a grep + read loop — repeats work codegraph already did and costs more for the same answer.\n- **Trust codegraph results.** They come from a full AST parse. Do NOT re-verify them with grep — that's slower, less accurate, and wastes context.\n- **Don't grep or Read first** to find or understand indexed code — one `codegraph_explore` returns the relevant source in a single round-trip. Reach for raw Read/Grep only to confirm a specific detail codegraph didn't cover, or for what it doesn't index (configs, docs).\n- **Index lag — check the staleness banner, don't guess a wait.** When a codegraph response starts with \"⚠️ Some files referenced below were edited since the last index sync…\", the listed files are pending re-index — Read those specific files for accurate content. Files NOT in that banner are fresh and codegraph is authoritative for them.\n\n### If `.codegraph/` doesn't exist\n\nThe MCP server returns \"not initialized.\" Ask the user: *\"I notice this project doesn't have CodeGraph initialized. Want me to run `codegraph init -i` to build the index?\"*\n<!-- CODEGRAPH_END -->\n",".claude/skills/agent-eval/corpus.json":"{\n  \"_comment\": \"Test corpus for /agent-eval. Add entries freely. size: Small (<~150 files), Medium (~150-1500), Large (>~1500). 'question' is a representative architectural question that exercises cross-file understanding.\",\n  \"TypeScript\": [\n    {\n      \"name\": \"ky\",\n      \"repo\": \"https://github.com/sindresorhus/ky\",\n      \"size\": \"Small\",\n      \"files\": \"~25\",\n      \"question\": \"How does ky implement request retries and timeouts?\"\n    },\n    {\n      \"name\": \"excalidraw\",\n      \"repo\": \"https://github.com/excalidraw/excalidraw\",\n      \"size\": \"Medium\",\n      \"files\": \"~600\",\n      \"question\": \"How does Excalidraw render and update canvas elements?\"\n    },\n    {\n      \"name\": \"vscode\",\n      \"repo\": \"https://github.com/microsoft/vscode\",\n      \"size\": \"Large\",\n      \"files\": \"~10000\",\n      \"question\": \"How does the extension host communicate with the main process?\"\n    }\n  ],\n  \"JavaScript\": [\n    {\n      \"name\": \"express\",\n      \"repo\": \"https://github.com/expressjs/express\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does Express route a request through its middleware stack?\"\n    }\n  ],\n  \"Go\": [\n    {\n      \"name\": \"cobra\",\n      \"repo\": \"https://github.com/spf13/cobra\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does cobra parse commands and flags?\"\n    },\n    {\n      \"name\": \"gin\",\n      \"repo\": \"https://github.com/gin-gonic/gin\",\n      \"size\": \"Medium\",\n      \"files\": \"~150\",\n      \"question\": \"How does gin route requests through its middleware chain?\"\n    },\n    {\n      \"name\": \"terraform\",\n      \"repo\": \"https://github.com/hashicorp/terraform\",\n      \"size\": \"Large\",\n      \"files\": \"~4000\",\n      \"question\": \"How does Terraform build and walk the resource dependency graph?\"\n    },\n    {\n      \"name\": \"cosmos-sdk\",\n      \"repo\": \"https://github.com/cosmos/cosmos-sdk\",\n      \"size\": \"Large\",\n      \"files\": \"~5000\",\n      \"question\": \"How does a bank module MsgSend message reach the account balance update? Trace the cross-module call path from the bank keeper's Send handler through to the account/balance store update.\"\n    }\n  ],\n  \"Python\": [\n    {\n      \"name\": \"click\",\n      \"repo\": \"https://github.com/pallets/click\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does click parse command-line arguments into commands?\"\n    },\n    {\n      \"name\": \"flask\",\n      \"repo\": \"https://github.com/pallets/flask\",\n      \"size\": \"Medium\",\n      \"files\": \"~90\",\n      \"question\": \"How does Flask dispatch a request to a view function?\"\n    },\n    {\n      \"name\": \"django\",\n      \"repo\": \"https://github.com/django/django\",\n      \"size\": \"Large\",\n      \"files\": \"~2700\",\n      \"question\": \"How does Django's ORM build and execute a query from a QuerySet?\"\n    }\n  ],\n  \"Rust\": [\n    {\n      \"name\": \"clap\",\n      \"repo\": \"https://github.com/clap-rs/clap\",\n      \"size\": \"Medium\",\n      \"files\": \"~200\",\n      \"question\": \"How does clap parse arguments against a derived command definition?\"\n    },\n    {\n      \"name\": \"tokio\",\n      \"repo\": \"https://github.com/tokio-rs/tokio\",\n      \"size\": \"Large\",\n      \"files\": \"~700\",\n      \"question\": \"How does tokio schedule and run async tasks on its runtime?\"\n    },\n    {\n      \"name\": \"deno\",\n      \"repo\": \"https://github.com/denoland/deno\",\n      \"size\": \"Large\",\n      \"files\": \"~1500\",\n      \"question\": \"How does Deno load and execute a TypeScript module?\"\n    }\n  ],\n  \"Java\": [\n    {\n      \"name\": \"gson\",\n      \"repo\": \"https://github.com/google/gson\",\n      \"size\": \"Medium\",\n      \"files\": \"~200\",\n      \"question\": \"How does Gson serialize an object to JSON?\"\n    },\n    {\n      \"name\": \"okhttp\",\n      \"repo\": \"https://github.com/square/okhttp\",\n      \"size\": \"Medium\",\n      \"files\": \"~640\",\n      \"question\": \"How does OkHttp process a request through its interceptor chain?\"\n    },\n    {\n      \"name\": \"guava\",\n      \"repo\": \"https://github.com/google/guava\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Guava's CacheBuilder build and configure a cache?\"\n    }\n  ],\n  \"Kotlin\": [\n    {\n      \"name\": \"koin\",\n      \"repo\": \"https://github.com/InsertKoinIO/koin\",\n      \"size\": \"Medium\",\n      \"files\": \"~300\",\n      \"question\": \"How does Koin resolve and inject dependencies?\"\n    },\n    {\n      \"name\": \"leakcanary\",\n      \"repo\": \"https://github.com/square/leakcanary\",\n      \"size\": \"Medium\",\n      \"files\": \"~250\",\n      \"question\": \"How does LeakCanary detect and analyze a memory leak?\"\n    }\n  ],\n  \"Swift\": [\n    {\n      \"name\": \"alamofire\",\n      \"repo\": \"https://github.com/Alamofire/Alamofire\",\n      \"size\": \"Small\",\n      \"files\": \"~100\",\n      \"question\": \"How does Alamofire build, send, and validate a request?\"\n    }\n  ],\n  \"C#\": [\n    {\n      \"name\": \"serilog\",\n      \"repo\": \"https://github.com/serilog/serilog\",\n      \"size\": \"Medium\",\n      \"files\": \"~250\",\n      \"question\": \"How does Serilog route a log event to its sinks?\"\n    },\n    {\n      \"name\": \"jellyfin\",\n      \"repo\": \"https://github.com/jellyfin/jellyfin\",\n      \"size\": \"Large\",\n      \"files\": \"~2500\",\n      \"question\": \"How does Jellyfin scan and identify items in a media library?\"\n    }\n  ],\n  \"Ruby\": [\n    {\n      \"name\": \"sinatra\",\n      \"repo\": \"https://github.com/sinatra/sinatra\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does Sinatra match a request to a route handler?\"\n    },\n    {\n      \"name\": \"discourse\",\n      \"repo\": \"https://github.com/discourse/discourse\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Discourse create and render a new post?\"\n    }\n  ],\n  \"PHP\": [\n    {\n      \"name\": \"slim\",\n      \"repo\": \"https://github.com/slimphp/Slim\",\n      \"size\": \"Small\",\n      \"files\": \"~80\",\n      \"question\": \"How does Slim handle a request through its middleware?\"\n    },\n    {\n      \"name\": \"laravel\",\n      \"repo\": \"https://github.com/laravel/framework\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Laravel resolve and dispatch a route to a controller?\"\n    }\n  ],\n  \"C\": [\n    {\n      \"name\": \"redis\",\n      \"repo\": \"https://github.com/redis/redis\",\n      \"size\": \"Large\",\n      \"files\": \"~600\",\n      \"question\": \"How does Redis parse and dispatch a client command?\"\n    }\n  ],\n  \"C++\": [\n    {\n      \"name\": \"json\",\n      \"repo\": \"https://github.com/nlohmann/json\",\n      \"size\": \"Small\",\n      \"files\": \"~100\",\n      \"question\": \"How does nlohmann::json parse a JSON string into a value?\"\n    },\n    {\n      \"name\": \"grpc\",\n      \"repo\": \"https://github.com/grpc/grpc\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does gRPC dispatch an incoming RPC to its handler?\"\n    }\n  ],\n  \"Dart\": [\n    {\n      \"name\": \"flutter\",\n      \"repo\": \"https://github.com/flutter/flutter\",\n      \"size\": \"Large\",\n      \"files\": \"~6000\",\n      \"question\": \"How does Flutter build and lay out a widget tree?\"\n    }\n  ],\n  \"Svelte\": [\n    {\n      \"name\": \"shadcn-svelte\",\n      \"repo\": \"https://github.com/huntabyte/shadcn-svelte\",\n      \"size\": \"Medium\",\n      \"files\": \"~600\",\n      \"question\": \"How do shadcn-svelte components compose and apply their styling?\"\n    }\n  ],\n  \"Lua\": [\n    {\n      \"name\": \"lualine.nvim\",\n      \"repo\": \"https://github.com/nvim-lualine/lualine.nvim\",\n      \"size\": \"Small\",\n      \"files\": \"~120\",\n      \"question\": \"How does lualine assemble and render its statusline sections and components?\"\n    },\n    {\n      \"name\": \"telescope.nvim\",\n      \"repo\": \"https://github.com/nvim-telescope/telescope.nvim\",\n      \"size\": \"Medium\",\n      \"files\": \"~80\",\n      \"question\": \"How does Telescope wire a picker to its finder, sorter, and previewer?\"\n    },\n    {\n      \"name\": \"kong\",\n      \"repo\": \"https://github.com/Kong/kong\",\n      \"size\": \"Large\",\n      \"files\": \"~1330\",\n      \"question\": \"How does Kong execute plugins across a request's lifecycle phases?\"\n    }\n  ],\n  \"Luau\": [\n    {\n      \"name\": \"Knit\",\n      \"repo\": \"https://github.com/Sleitnick/Knit\",\n      \"size\": \"Small\",\n      \"files\": \"~10\",\n      \"question\": \"How does Knit register services and expose them to clients?\"\n    },\n    {\n      \"name\": \"vide\",\n      \"repo\": \"https://github.com/centau/vide\",\n      \"size\": \"Small\",\n      \"files\": \"~40\",\n      \"question\": \"How does vide track reactive sources and re-run effects when state changes?\"\n    },\n    {\n      \"name\": \"Fusion\",\n      \"repo\": \"https://github.com/dphfox/Fusion\",\n      \"size\": \"Medium\",\n      \"files\": \"~115\",\n      \"question\": \"How does Fusion build and update its reactive UI graph from state objects?\"\n    }\n  ],\n  \"Objective-C\": [\n    {\n      \"name\": \"Masonry\",\n      \"repo\": \"https://github.com/SnapKit/Masonry\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does Masonry build and activate Auto Layout constraints from its block DSL?\"\n    },\n    {\n      \"name\": \"FMDB\",\n      \"repo\": \"https://github.com/ccgus/fmdb\",\n      \"size\": \"Medium\",\n      \"files\": \"~80\",\n      \"question\": \"How does FMDB execute a prepared SQL statement and bind parameters?\"\n    },\n    {\n      \"name\": \"SDWebImage\",\n      \"repo\": \"https://github.com/SDWebImage/SDWebImage\",\n      \"size\": \"Large\",\n      \"files\": \"~400\",\n      \"question\": \"How does SDWebImage download, cache, and decode an image for a UIImageView?\"\n    }\n  ],\n  \"Mixed iOS (Swift+ObjC)\": [\n    {\n      \"name\": \"Charts\",\n      \"repo\": \"https://github.com/danielgindi/Charts\",\n      \"size\": \"Small\",\n      \"files\": \"~270\",\n      \"question\": \"How does the ChartsDemo ObjC demo controller drive the Swift Charts library to animate and notify a data update?\"\n    },\n    {\n      \"name\": \"realm-swift\",\n      \"repo\": \"https://github.com/realm/realm-swift\",\n      \"size\": \"Medium\",\n      \"files\": \"~370\",\n      \"question\": \"How does a Swift `Realm.write { realm.add(obj) }` reach the Objective-C persistence layer?\"\n    },\n    {\n      \"name\": \"wikipedia-ios\",\n      \"repo\": \"https://github.com/wikimedia/wikipedia-ios\",\n      \"size\": \"Large\",\n      \"files\": \"~1700\",\n      \"question\": \"How does tapping a search result reach the article-fetch network call across the Swift / ObjC boundary?\"\n    }\n  ],\n  \"React Native (legacy bridge + TurboModule)\": [\n    {\n      \"name\": \"@react-native-async-storage\",\n      \"repo\": \"https://github.com/react-native-async-storage/async-storage\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does `setItem` in JS reach the native `legacy_multiSet` implementation?\"\n    },\n    {\n      \"name\": \"react-native-svg\",\n      \"repo\": \"https://github.com/software-mansion/react-native-svg\",\n      \"size\": \"Medium\",\n      \"files\": \"~700\",\n      \"question\": \"How does a JS `Svg.getTotalLength(...)` reach the iOS / Android native implementation via TurboModule?\"\n    },\n    {\n      \"name\": \"react-native-firebase\",\n      \"repo\": \"https://github.com/invertase/react-native-firebase\",\n      \"size\": \"Large\",\n      \"files\": \"~1100\",\n      \"question\": \"How does a native iOS push notification reach the JS `messaging().onMessage(...)` listener?\"\n    }\n  ],\n  \"Expo Modules\": [\n    {\n      \"name\": \"expo-haptics\",\n      \"repo\": \"https://github.com/expo/expo/tree/main/packages/expo-haptics\",\n      \"size\": \"Small\",\n      \"files\": \"~15\",\n      \"question\": \"How does `Haptics.notificationAsync(...)` in JS reach `UINotificationFeedbackGenerator` in the Swift Module?\"\n    },\n    {\n      \"name\": \"expo-camera\",\n      \"repo\": \"https://github.com/expo/expo/tree/main/packages/expo-camera\",\n      \"size\": \"Medium\",\n      \"files\": \"~70\",\n      \"question\": \"How does a JS `CameraView.takePictureAsync(options)` reach the native AVCaptureSession / CameraDevice call?\"\n    }\n  ],\n  \"React Native Fabric (view components)\": [\n    {\n      \"name\": \"react-native-segmented-control\",\n      \"repo\": \"https://github.com/react-native-segmented-control/segmented-control\",\n      \"size\": \"Small\",\n      \"files\": \"~25\",\n      \"question\": \"How does JSX `<SegmentedControl onChange={cb}/>` reach the native onChange handler on iOS/Android?\"\n    },\n    {\n      \"name\": \"react-native-screens\",\n      \"repo\": \"https://github.com/software-mansion/react-native-screens\",\n      \"size\": \"Medium\",\n      \"files\": \"~1200\",\n      \"question\": \"How does JSX `<ScreenStack>` reach the native RNSScreenStackView component?\"\n    },\n    {\n      \"name\": \"react-native-skia\",\n      \"repo\": \"https://github.com/Shopify/react-native-skia\",\n      \"size\": \"Large\",\n      \"files\": \"~1000\",\n      \"question\": \"How does a `<SkiaPictureView/>` JSX usage reach the iOS / Android native renderer?\"\n    }\n  ],\n  \"R\": [\n    {\n      \"name\": \"AnomalyDetection\",\n      \"repo\": \"https://github.com/twitter/AnomalyDetection\",\n      \"size\": \"Small\",\n      \"files\": \"~24\",\n      \"question\": \"How does AnomalyDetectionTs go from the exported entry function to the underlying S-H-ESD statistical test? Name the functions on the path in order.\"\n    },\n    {\n      \"name\": \"dplyr\",\n      \"repo\": \"https://github.com/tidyverse/dplyr\",\n      \"size\": \"Medium\",\n      \"files\": \"~450\",\n      \"question\": \"When mutate() is called on a grouped data frame, which functions handle the grouping and expression evaluation, in order, from mutate() down?\"\n    },\n    {\n      \"name\": \"ggplot2\",\n      \"repo\": \"https://github.com/tidyverse/ggplot2\",\n      \"size\": \"Large\",\n      \"files\": \"~1150\",\n      \"question\": \"When a ggplot object is printed, how does the plot actually get built and drawn — trace the path from print/plot to where geoms render. Name the key functions in order.\"\n    }\n  ],\n  \"COBOL\": [\n    {\n      \"name\": \"cics-genapp\",\n      \"repo\": \"https://github.com/cicsdev/cics-genapp\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"When a new insurance policy is added through the LGTESTP1 CICS test harness, which programs handle the request on the way to the DB2 insert? Trace the path and name the programs in order.\"\n    },\n    {\n      \"name\": \"carddemo\",\n      \"repo\": \"https://github.com/aws-samples/aws-mainframe-modernization-carddemo\",\n      \"size\": \"Medium\",\n      \"files\": \"~75\",\n      \"question\": \"How does the online bill-payment flow work — from the COBIL00C screen program to the transaction record being written? Name the paragraphs performed in order and the copybooks that define the records involved.\"\n    },\n    {\n      \"name\": \"CobolCraft\",\n      \"repo\": \"https://github.com/meyfa/CobolCraft\",\n      \"size\": \"Medium\",\n      \"files\": \"~270\",\n      \"question\": \"How does an incoming player chat message travel from packet handling to being broadcast to the other connected players? Name the programs on the path in order.\"\n    }\n  ],\n  \"VB.NET\": [\n    {\n      \"name\": \"policyplus\",\n      \"repo\": \"https://github.com/Fleex255/PolicyPlus\",\n      \"size\": \"Small\",\n      \"files\": \"~94\",\n      \"question\": \"When the user toggles a policy to Enabled in the policy-setting editor and clicks OK, how does the new state end up written into the loaded policy source (POL file or registry)? Trace the path from the EditSetting dialog to the concrete write.\"\n    },\n    {\n      \"name\": \"scrawler\",\n      \"repo\": \"https://github.com/AAndyProgram/SCrawler\",\n      \"size\": \"Medium\",\n      \"files\": \"~320\",\n      \"question\": \"When a user download is started for a Reddit user, how does the request flow from the user-level download entry point through the shared downloader base into the Reddit site plugin, and where do downloaded media items get appended to the user's content list?\"\n    },\n    {\n      \"name\": \"staxrip\",\n      \"repo\": \"https://github.com/staxrip/staxrip\",\n      \"size\": \"Medium\",\n      \"files\": \"~145\",\n      \"question\": \"When a job finishes video encoding, how does staxrip decide which muxer runs and how does the muxer command line get built and executed? Trace from job processing to the mkvmerge invocation.\"\n    }\n  ],\n  \"Erlang\": [\n    {\n      \"name\": \"cowboy\",\n      \"repo\": \"https://github.com/ninenines/cowboy\",\n      \"size\": \"Small\",\n      \"files\": \"~190\",\n      \"question\": \"How does an incoming HTTP request travel from cowboy's connection process to a user-defined handler's init/2 callback? Trace the path through the stream handler and middleware chain.\"\n    },\n    {\n      \"name\": \"ejabberd\",\n      \"repo\": \"https://github.com/processone/ejabberd\",\n      \"size\": \"Medium\",\n      \"files\": \"~410\",\n      \"question\": \"When a client sends a chat message, how does the stanza get from the receiving C2S process to the recipient's session on the same node? Trace the path through the router and session manager.\"\n    },\n    {\n      \"name\": \"emqx\",\n      \"repo\": \"https://github.com/emqx/emqx\",\n      \"size\": \"Large\",\n      \"files\": \"~2450\",\n      \"question\": \"How does a PUBLISH packet from an MQTT client reach the sessions of matching subscribers? Trace the flow from the connection/channel layer through the broker's routing to session delivery.\"\n    }\n  ],\n  \"Solidity\": [\n    {\n      \"name\": \"solmate\",\n      \"repo\": \"https://github.com/transmissions11/solmate\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does solmate's ERC20 transferFrom enforce allowance and update balances? Trace the flow including the permit() signature path.\"\n    },\n    {\n      \"name\": \"solady\",\n      \"repo\": \"https://github.com/Vectorized/solady\",\n      \"size\": \"Medium\",\n      \"files\": \"~270\",\n      \"question\": \"How does solady's ERC20 implementation handle a permit() call — from signature recovery through nonce update to allowance write?\"\n    },\n    {\n      \"name\": \"openzeppelin-contracts\",\n      \"repo\": \"https://github.com/OpenZeppelin/openzeppelin-contracts\",\n      \"size\": \"Large\",\n      \"files\": \"~400\",\n      \"question\": \"How does an OpenZeppelin AccessControl-protected function check the caller's role? Trace from the onlyRole modifier through hasRole to the role storage.\"\n    }\n  ],\n  \"CUDA\": [\n    {\n      \"name\": \"llm.c\",\n      \"repo\": \"https://github.com/karpathy/llm.c\",\n      \"size\": \"Small\",\n      \"files\": \"~76\",\n      \"question\": \"How does the attention forward pass reach the GPU in the CUDA training path? Trace from attention_forward to the kernels it launches, and explain where softmax happens.\"\n    },\n    {\n      \"name\": \"flash-attention\",\n      \"repo\": \"https://github.com/Dao-AILab/flash-attention\",\n      \"size\": \"Medium\",\n      \"files\": \"~900\",\n      \"question\": \"How does a Python call to flash_attn_func reach the CUDA kernel that computes forward attention? Trace the dispatch path from the Python API through the C++ binding to the kernel launch.\"\n    },\n    {\n      \"name\": \"cutlass\",\n      \"repo\": \"https://github.com/NVIDIA/cutlass\",\n      \"size\": \"Large\",\n      \"files\": \"~2700\",\n      \"question\": \"When a cutlass device-level GEMM (cutlass::gemm::device::Gemm) is invoked, how does it reach the GPU kernel? Trace from the operator() call to the kernel entry point and its launch site.\"\n    }\n  ],\n  \"Terraform\": [\n    {\n      \"name\": \"terraform-aws-vpc\",\n      \"repo\": \"https://github.com/terraform-aws-modules/terraform-aws-vpc\",\n      \"size\": \"Small\",\n      \"files\": \"~77\",\n      \"question\": \"How does the private_subnets variable shape the NAT gateway setup? Trace from the variable through the subnet and NAT gateway resources to the outputs that expose the private subnets.\"\n    },\n    {\n      \"name\": \"cloud-foundation-fabric\",\n      \"repo\": \"https://github.com/GoogleCloudPlatform/cloud-foundation-fabric\",\n      \"size\": \"Medium\",\n      \"files\": \"~990\",\n      \"question\": \"In the project module (modules/project), how does the iam variable turn into actual IAM bindings on the project, and what depends on the module's project_id output elsewhere in the repo?\"\n    },\n    {\n      \"name\": \"terraform-aws-components\",\n      \"repo\": \"https://github.com/cloudposse/terraform-aws-components\",\n      \"size\": \"Large\",\n      \"files\": \"~1800\",\n      \"question\": \"In the eks/cluster component, how does the cluster IAM role get created and reach the EKS cluster resource, and which outputs expose cluster identity to other components?\"\n    }\n  ],\n  \"ArkTS\": [\n    {\n      \"name\": \"HarmoneyOpenEye\",\n      \"repo\": \"https://github.com/WinWang/HarmoneyOpenEye\",\n      \"size\": \"Small\",\n      \"files\": \"~82\",\n      \"question\": \"How does the home page get its feed data from the network layer, and how does that data end up rendered as the list on screen? Trace the flow from the HTTP request through the view model into the home page UI.\"\n    },\n    {\n      \"name\": \"CoolMallArkTS\",\n      \"repo\": \"https://github.com/Joker-x-dev/CoolMallArkTS\",\n      \"size\": \"Medium\",\n      \"files\": \"~528\",\n      \"question\": \"When the user adds a product to the cart from the goods detail page, how does the item travel from the UI action to persistent storage? Trace the flow across the feature and core modules.\"\n    },\n    {\n      \"name\": \"applications_app_samples\",\n      \"repo\": \"https://github.com/openharmony/applications_app_samples\",\n      \"size\": \"Large\",\n      \"files\": \"~9500\",\n      \"question\": \"In the OrangeShopping sample app, how does the product detail page's bottom bar (add to cart / buy) lead to the order placement flow? Trace from the bottom navigation component to where the order is created.\"\n    }\n  ],\n  \"Nix\": [\n    {\n      \"name\": \"agenix\",\n      \"repo\": \"https://github.com/ryantm/agenix\",\n      \"size\": \"Small\",\n      \"files\": \"~13\",\n      \"question\": \"When a NixOS system activates, how does a secret declared under age.secrets get decrypted and installed at its runtime path? Trace the flow from the age.secrets option definition to the activation machinery that performs the decryption.\"\n    },\n    {\n      \"name\": \"nix-darwin\",\n      \"repo\": \"https://github.com/nix-darwin/nix-darwin\",\n      \"size\": \"Medium\",\n      \"files\": \"~207\",\n      \"question\": \"How does setting services.yabai.enable = true become a running launchd service? Trace the flow from the yabai module's options to the launchd daemon definition that generates the plist.\"\n    },\n    {\n      \"name\": \"home-manager\",\n      \"repo\": \"https://github.com/nix-community/home-manager\",\n      \"size\": \"Large\",\n      \"files\": \"~3390\",\n      \"question\": \"How does programs.git.enable produce the final git config file in the user's home directory? Trace the flow from the git program module to the home-files machinery that links generated files into place.\"\n    }\n  ]\n}\n",".claude/skills/add-lang/SKILL.md":"---\nname: add-lang\ndescription: Add tree-sitter language support to codegraph end-to-end — wire the grammar + extractor, write tests, then benchmark extraction quality and retrieval value on 3 popular real-world repos. Use when the user runs /add-lang <language> or asks to add/support a new language (e.g. Lua, Elixir, Zig, OCaml) in codegraph.\n---\n\n# Add a language to CodeGraph\n\nWire a new tree-sitter language into codegraph's extraction pipeline, prove it\nextracts real symbols on popular repos, and prove it beats no-codegraph for an\nagent. Runs **fully autonomously** — pick repos, benchmark, update docs, then\nreport. **Never commit, push, publish, or tag** (house rule); leave all changes\nfor the user to review.\n\nThe argument is the language token used throughout the `Language` union, e.g.\n`lua`, `elixir`, `zig`. If none was given, ask which language. Use the lowercase\nsingle-token form everywhere (`csharp`, not `c#`).\n\n## Prerequisites\n- Run from the codegraph repo root. `node`, `git`, `gh`, and a logged-in\n  `claude` CLI (the benchmark spawns real `claude -p` runs).\n- The benchmark uses the local dev build — Step 8 builds + links it on PATH.\n\n## Workflow\n\nCopy this checklist and work through it in order:\n```\n- [ ] 1. Resolve language; bail early if already supported (just benchmark)\n- [ ] 2. Find a grammar + health-check it (ABI / heap corruption)\n- [ ] 3. Discover the grammar's AST node types (dump-ast.mjs)\n- [ ] 4. Wire the language (4 files; sometimes a 5th core touch)\n- [ ] 5. Build + verify-extraction loop until PASS\n- [ ] 6. Add extraction tests; make them green\n- [ ] 7. Auto-pick 3 popular repos by size tier; add to corpus.json\n- [ ] 8. Benchmark all 3: extraction + with/without A/B\n- [ ] 9. Update README + CHANGELOG\n- [ ] 10. Report; do NOT commit\n```\n\n### Step 1 — Resolve + short-circuit\n\nCheck whether the language is already wired: look for the token in the\n`LANGUAGES` const (`src/types.ts`) and the `EXTRACTORS` map\n(`src/extraction/languages/index.ts`). If it is already supported (e.g.\n`typescript`, `rust`), **skip Steps 2–6** and go straight to benchmarking\n(Steps 7–8) to validate/measure it — note in the report that no code changed.\n\n### Step 2 — Find a grammar, then health-check it\n\n```bash\nls node_modules/tree-sitter-wasms/out/ | grep -i <lang>   # csharp -> c_sharp\n```\n- **Present** → likely off-the-shelf; `grammars.ts` resolves it from\n  `tree-sitter-wasms` automatically. (Many languages: elixir, zig, ocaml,\n  solidity, toml, yaml, …)\n- **Absent** → vendor a `.wasm` into `src/extraction/wasm/` (like `pascal` /\n  `scala` / `lua`) and add the token to the vendored branch in Step 4.\n\n**Always health-check before writing an extractor — a *present* grammar can\nstill be unusable:**\n```bash\nnode scripts/add-lang/check-grammar.mjs <lang> path/to/valid-sample.<ext>\n```\nIt prints the grammar's ABI version and runs it the way indexing does: in a\nchild process with indexing's V8 flags, beside the vendored grammars, parsing a\nvalid sample many times and once at the 1 MiB file-size limit. If it **FAILs**\n(ERROR trees, or later parses giving a different tree, on valid code — an old ABI\ncorrupting the shared WASM heap, which silently drops nested calls/imports on\nevery file after the first; e.g. the tree-sitter-wasms **Lua** grammar is ABI 13\nand fails — or the process running it crashing or hanging, even after every\nparse finished), do NOT use that wasm. **Vendor a newer (ABI 14/15) build instead:**\n```bash\nnpm pack @tree-sitter-grammars/tree-sitter-<lang>   # often ships a prebuilt *.wasm\n# or build one: npx tree-sitter build --wasm   (needs Docker/emscripten)\ncp <the>.wasm src/extraction/wasm/tree-sitter-<lang>.wasm\n```\nthen add the token to the vendored branch in Step 4 and re-run check-grammar on\nthe vendored path until it PASSes. **If you cannot obtain a healthy wasm, STOP\nand tell the user.**\n\n### Step 3 — Discover AST node types\n\nGet a representative source file (write a small sample covering functions,\nclasses/structs, imports, enums; or `curl` a raw file from a known repo), then:\n```bash\nnode scripts/add-lang/dump-ast.mjs <lang> path/to/sample.<ext>\n# vendored grammar: pass the wasm path instead of the token\nnode scripts/add-lang/dump-ast.mjs src/extraction/wasm/tree-sitter-<lang>.wasm sample.<ext>\n```\nThe frequency table + field names (`name:`, `parameters:`, `body:`,\n`return_type:`) tell you what to map. Open the existing extractor closest to the\nlanguage's paradigm as a model: `rust.ts`/`scala.ts` (functional, traits),\n`java.ts`/`csharp.ts` (OO), `python.ts`/`ruby.ts` (scripting), `go.ts`\n(top-level methods + receivers).\n\n### Step 4 — Wire the language (4 files)\n\nThese are exact, fragile wiring — match the existing style precisely:\n\n1. **`src/types.ts`** — TWO edits:\n   - add `'<lang>',` to the `LANGUAGES` const (before `'unknown'`);\n   - add `'**/*.<ext>',` to `DEFAULT_CONFIG.include`. **Don't skip this** — it's\n     the file-scan allowlist; without the glob, `codegraph init` finds **0\n     files** even though detection/extraction are wired.\n2. **`src/extraction/grammars.ts`** — three maps:\n   - `WASM_GRAMMAR_FILES`: `<lang>: 'tree-sitter-<lang>.wasm',`\n   - `EXTENSION_MAP`: each file extension → `'<lang>'` (e.g. `'.lua': 'lua',`)\n   - `getLanguageDisplayName`: `<lang>: '<Display Name>',`\n   - **vendored only**: add `<lang>` to the\n     `(lang === 'pascal' || lang === 'scala' || …)` wasm-path branch.\n3. **`src/extraction/languages/<lang>.ts`** — new file exporting\n   `export const <lang>Extractor: LanguageExtractor = { … }`. Map the node types\n   from Step 3. Required fields: `functionTypes`, `classTypes`, `methodTypes`,\n   `interfaceTypes`, `structTypes`, `enumTypes`, `typeAliasTypes`,\n   `importTypes`, `callTypes`, `variableTypes`, `nameField`, `bodyField`,\n   `paramsField`. Add hooks as the grammar needs them (`getSignature`,\n   `getVisibility`, `isExported`, `extractImport`, `visitNode`, `getReceiverType`,\n   `interfaceKind`, `enumMemberTypes`, etc. — see\n   `src/extraction/tree-sitter-types.ts`).\n4. **`src/extraction/languages/index.ts`** — `import { <lang>Extractor } from\n   './<lang>';` and add `<lang>: <lang>Extractor,` to `EXTRACTORS`.\n\n**Sometimes a 5th, core touch in `src/extraction/tree-sitter.ts`** — variable\nextraction has per-language branches in `extractVariable` (the generic fallback\nonly finds direct `identifier`/`variable_declarator` children). If the grammar\nnests declared names (e.g. Lua's `variable_declaration → variable_list`), add a\n`} else if (this.language === '<lang>')` branch there, mirroring the existing\nts/python/go ones. Import forms that aren't a distinct node (Lua/Ruby `require`\nis a *call*) are handled in the extractor's `visitNode` hook instead.\n\n### Step 5 — Build + verify loop\n\n```bash\nnpm run build            # tsc + copy-assets (copies any vendored *.wasm into dist/)\n```\nIndex a small sample repo and check extraction:\n```bash\n( cd <sample-repo> && codegraph init -i )\nnode scripts/add-lang/verify-extraction.mjs <sample-repo> <lang>\n```\n`verify-extraction.mjs` fails (exit 1) if the language isn't detected or only\n`file`/`import` nodes were produced — the classic symptom of wrong node-type\nnames. On FAIL or a thin WARN: re-run `dump-ast.mjs` on a richer file, fix the\nmappings in `<lang>.ts`, `npm run build`, re-index, re-verify. **Repeat until\nPASS.**\n\n### Step 6 — Tests\n\nAdd to `__tests__/extraction.test.ts`, modeled on the `Rust Extraction` block:\n- a `detectLanguage` assertion in `describe('Language Detection')`\n- a `describe('<Lang> Extraction')` block asserting functions/classes/imports\n  are extracted from an inline source string.\n```bash\nnpx vitest run __tests__/extraction.test.ts\n```\nGreen before continuing.\n\n### Step 7 — Auto-pick 3 repos + corpus\n\nPick **without asking**. Find candidates, then curate 3 that are genuinely\n`<lang>`-dominant, one per size tier:\n```bash\ngh search repos --language=<lang> --sort=stars --limit 40 \\\n  --json fullName,stargazerCount,description\n```\nTiers (match `corpus.json`): **Small** <~150 files · **Medium** ~150–1500 ·\n**Large** >~1500. Skip repos that are tagged `<lang>` but mostly another\nlanguage. Write one cross-file architecture **question** per repo (the kind that\nneeds tracing across files). Add a `\"<Language>\"` block to\n`.claude/skills/agent-eval/corpus.json` (fields: `name`, `repo`, `size`,\n`files`, `question`) so `/agent-eval` can reuse them.\n\n### Step 8 — Benchmark all 3 (extraction + A/B)\n\nMake the dev build the codegraph on PATH **once**, then loop:\n```bash\nnpm run build && ./scripts/local-install.sh\nscripts/add-lang/bench.sh <lang> <name> <url> \"<question>\" headless   # ×3\n```\n`bench.sh` clones (shared `/tmp/codegraph-corpus`), wipes + indexes, runs\n`verify-extraction.mjs`, then the with/without retrieval A/B via\n`scripts/agent-eval/run-all.sh` (skips the paid A/B if extraction is broken).\nRead each `parse-run.mjs` summary printed by `run-all.sh`: tool calls, file\n`Read`s, Grep/Bash, codegraph-tool calls, duration, and **cost** — for both the\n`with` and `without` arms. After the loop, restore the dev link if needed:\n`./scripts/local-install.sh`.\n\n### Step 9 — Docs + CHANGELOG\n\n- **README.md**: add `<Lang>` to the \"19+ Languages\" feature bullet, and add a\n  row to the **Supported Languages** table:\n  `| <Lang> | \\`.ext\\` | Full support (classes, methods, …) |`.\n- **CHANGELOG.md**: add an `## [Unreleased]` section at the top (above the\n  latest version) with `### Added` → a user-perspective bullet, e.g.\n  *\"CodeGraph now indexes **<Lang>** (`.ext`) — functions, classes, imports, and\n  call edges.\"* If `## [Unreleased]` already exists, append under it. (It's\n  folded into the next versioned block at release time.)\n\n### Step 10 — Report (do NOT commit)\n\nSummarize for review:\n- **Files changed**: the 4 wiring edits + new extractor + tests + README +\n  CHANGELOG + corpus.json (+ any vendored `.wasm`).\n- **Extraction** per repo: files / nodes / edges / `verify-extraction` result.\n- **A/B** per repo: `with` vs `without` (tool calls, file Reads, cost) and a\n  one-line verdict — did codegraph reduce effort, and did both arms reach a\n  correct answer?\n- **Gaps / follow-ups** (node types not yet mapped, resolution edges missing,\n  framework routes, etc.).\n\nHand the changes to the user. **Do not** run `git commit`/`push` or publish —\nreleases go through the GitHub Actions Release workflow.\n\n## Notes\n- The A/B spawns real **paid** `claude -p` runs (opus, `--max-budget-usd`),\n  2 arms × 3 repos. The corpus dir `/tmp/codegraph-corpus` is shared with\n  `/agent-eval`, so clones are reused across runs.\n- Any new `*.wasm` must live in `src/extraction/wasm/` — `copy-assets` (run by\n  `npm run build`) ships it; otherwise it won't be in `dist/`.\n- An index must be served by the **same** binary that built it. Step 8 builds +\n  links the dev build first, so this holds.\n- If a grammar can't be obtained, or extraction can't reach PASS, **STOP and\n  report** — don't ship a half-wired language.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/colbymchenry/codegraph/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nCanonical project guidance for coding agents working in this repository (Codex/Astra, Claude Code via `@AGENTS.md`, Cursor, etc.).\n\n**Codex size note:** root `AGENTS.md` is intentionally kept near ~35 KiB (critical build/test/arch/retrieval rules retained). Longer validation methodology + the Excalidraw worked example live in `docs/AGENTS.md`. This environment sets `project_doc_max_bytes = 49152` so root (and root+`docs/` when cwd is under `docs/`) are not silently truncated at the 32 KiB default.\n\n## Project Overview\n\nCodeGraph is a local-first code intelligence library + CLI + MCP server. It parses any supported codebase with tree-sitter, stores symbols/edges/files in SQLite (FTS5), and exposes a knowledge graph to AI agents (Claude Code, Cursor, Codex CLI, opencode) over MCP. Per-project data lives in `.codegraph/`. Extraction is deterministic — derived from AST, not LLM-summarized.\n\nDistributed as `@colbymchenry/codegraph` on npm; same binary serves as installer, indexer, and MCP server.\n\n## Build, Test, Run\n\n```bash\nnpm run build           # tsc + copy schema.sql and *.wasm + build the viewer into dist/; chmods dist/bin/codegraph.js\nnpm run build:lib       # the viewer's components as @colbymchenry/codegraph-ui (ui/dist) — NOT part of `build`\nnpm run dev             # tsc --watch\nnpm run clean           # rm -rf dist\n\nnpm test                # vitest run (all)\nnpm run test:watch\nnpm run test:eval       # only __tests__/evaluation/\nnpm run eval            # build then run __tests__/evaluation/runner.ts via tsx\n\nnpm run cli             # build then run the local dist binary\n\n# Single test file / pattern\nnpx vitest run __tests__/installer-targets.test.ts\nnpx vitest run __tests__/extraction.test.ts -t \"TypeScript\"\n```\n\n`copy-assets` (called from `build`) copies `src/db/schema.sql` and all `src/extraction/wasm/*.wasm` files into `dist/`. **Any new SQL or grammar wasm must be copied or it won't ship.**\n\nOne other build step writes into `dist/` and is subject to the same rule: `build:ui` builds the\nbrowser viewer into `dist/viewer/` (never `dist/ui/` — that's the terminal ui).\n`scripts/check-ui-build.mjs` asserts both `dist/viewer/` and the copied grammars in\n`dist/extraction/wasm/` after every build and inside every release archive — the viewer's syntax\nhighlighting reads a file with the same grammar the engine indexed it with, so a missing wasm is an\nunhighlighted screen as well as an extraction gap.\n\n`npm run build:lib` is separate and does NOT run as part of `npm run build`: it compiles the same\n`ui/src` tree a second way, with `svelte-package`, into `ui/dist` — the `@colbymchenry/codegraph-ui`\ncomponent library the Pro app imports (task CG-61). `scripts/check-ui-package.mjs` then prunes the\nstandalone app's shell out of it, resolves the extensionless import specifiers `svelte-package`\nleaves behind, and asserts the seam: nothing outside `lib/adapter.js` may reach the network. The\npackage is **prepared, not published** — `ui/package.json` carries `\"private\": true` deliberately,\nand `scripts/pack-npm.sh` only packs a tarball when `CODEGRAPH_PACK_UI=1`.\n\nTests run as **two vitest projects** (`vitest.workspace.mts`): `engine` (node) and `ui` (jsdom, the\nSvelte plugin, `resolve.conditions: ['browser']`) for the single `__tests__/ui-package.test.ts`.\n`npm test` still runs both. The split is not cosmetic — `browser` is a package-resolution\ncondition, and applied globally it hands the engine's suites the browser builds of\n`web-tree-sitter` and friends. The root config (`vitest.config.mts`, `.mts` because the plugin is\nESM-only and the repo is CJS) is the shared base; note that a workspace project **concatenates**\nthe base's `include` with its own, which is why the `ui` project does not `extends` it.\n\nNode engines: `>=20.0.0 <25.0.0`. There is a hard exit on Node 25.x and below 20 (see `src/bin/node-version-check.ts`).\n\n## Architecture\n\n### Layered pipeline\n\n```\nfiles → ExtractionOrchestrator (tree-sitter) → DB (nodes/edges/files)\n              ↓\n       ReferenceResolver (imports, name-matching, framework patterns)\n              ↓\n       GraphQueryManager / GraphTraverser (callers, callees, impact)\n              ↓\n       ContextBuilder (markdown/JSON for AI consumption)\n```\n\nThe public API surface is `src/index.ts` — the `CodeGraph` class wires all the layers and re-exports types. Library users only touch this file; the MCP server and CLI also drive it.\n\n### Module layout\n\n- `src/index.ts` — `CodeGraph` class: `init`/`open`/`close`, `indexAll`, `sync`, `searchNodes`, `getCallers`/`getCallees`, `getImpactRadius`, `buildContext`, `watch`/`unwatch`.\n- `src/db/` — `DatabaseConnection`, `QueryBuilder` (prepared statements), `schema.sql`, `sqlite-adapter.ts`. Backed by Node's built-in **`node:sqlite`** (`DatabaseSync`) — real SQLite with WAL + FTS5, exposed through a thin better-sqlite3-shaped adapter. The bundled runtime always ships Node ≥22.5, so `node:sqlite` is always available: **no native build step and no wasm fallback**. (Running from source needs Node ≥22.5.) `codegraph status` reports the live backend (`node-sqlite`, the sole backend).\n- `src/extraction/` — `ExtractionOrchestrator`, tree-sitter wrappers, per-language extractors under `languages/` (one file per language), plus standalone extractors for non-tree-sitter formats (`svelte-extractor.ts`, `vue-extractor.ts`, `liquid-extractor.ts`, `dfm-extractor.ts` for Delphi). `parse-worker.ts` runs heavy parsing off the main thread.\n- `src/resolution/` — `ReferenceResolver` orchestrates `import-resolver.ts` (with `path-aliases.ts` for tsconfig path aliases + cargo workspace member globs), `name-matcher.ts`, and `frameworks/` (Express, Laravel, Rails, FastAPI, Django, Flask, Spring, Gin, Axum, ASP.NET, Vapor, React Router, Next.js — `nextjs.ts`: pages and `route.ts` handlers from files, `router.push` / `redirect` / `NextResponse.redirect` as `navigates` edges, with `next-router-synthesizer.ts` for `<Link href>` — Expo Router, SvelteKit, Vue/Nuxt, Cargo workspaces). Frameworks emit `route` nodes and `references` edges. `callback-synthesizer.ts` holds the whole-graph synthesis passes (`SYNTH_PASSES`, merged in registry order — first-seen wins a duplicate pair) with the language gates; `tier-synthesizer.ts` is the cross-tier pass (a client's literal `fetch`/`axios` path onto its own route, a queue job onto its consumer, a bus / socket event onto its handler — `channel`, `tier`, `registeredAt` on every edge; registered before the in-process emitter pass so its more specific edge wins); `synth-utils.ts` has the helpers they share (`enclosingFn`, `enclosingValue`, `makeLineAt`). Express's `postExtract` composes `app.use('/prefix', router)` mounts onto a mounted file's route names, idempotently (the original path stays in `qualifiedName`).\n- `src/graph/` — `GraphTraverser` (BFS/DFS, impact radius, path finding) and `GraphQueryManager` (high-level queries), plus the shared query-time derivations more than one surface renders: `named-symbol-flow.ts` (the one path finder, behind `codegraph_explore`'s Flow section and the viewer's Flow strip), `dynamic-boundary-report.ts` (where the graph stops), `type-hierarchy.ts` (ancestors/subtypes and the implementation count explore prints and the viewer draws),\n  `dead-code.ts` (unreferenced symbols, and every reason a candidate is NOT claimed), `synthesized-hop.ts` (how a synthesized hop reads, in explore's Flow, the `codegraph_node` trail and ContextBuilder's call paths — a new synthesizer's wording goes here). A derivation that two callers render must live here, not in `ToolHandler` — two derivations eventually disagree.\n- `src/context/` — `ContextBuilder` + formatter for markdown/JSON output.\n- `src/search/` — full-text query parser and helpers for FTS5.\n- `src/sync/` — `FileWatcher` (native FSEvents/inotify/RDCW) with debounce + filter, and git-hook helpers.\n- `src/mcp/` — MCP server (`MCPServer`, `tools.ts`, `transport.ts`). `server-instructions.ts` is what the server returns in the MCP `initialize` response — keep it in sync with the user-facing tool guidance.\n- `src/installer/` — see below.\n- `src/bin/codegraph.ts` — CLI (commander). Subcommands: `install`, `init`, `uninit`, `index`, `sync`, `status`, `query`, `files`, `context`, `affected`, `serve --mcp`.\n- `src/ui/` — terminal UI (shimmer progress, worker).\n- `src/ui-server/` -- read-only JSON API for the `codegraph ui` browser viewer (`api/`: `node`, `flow`, `map`, `screens`, `steps`, `deadcode`, `trails`, `program`, ...) plus static server; Svelte viewer lives in `ui/` (see `docs/design/codegraph-ui-design-spec.md`). `screens`/`steps`/`program` share one fold (`via`/`when` via `graph/branch-guards.ts`); `api/effects.ts` curates calls that leave the index; `api/route-roots.ts` names where a route's code starts. Derivations rendered by more than one surface belong in `src/graph/`, not `ToolHandler`.\n  **Not released yet:** `codegraph ui` / `web` are refused (and hidden from `--help`) unless `CODEGRAPH_UI=1` is set — `src/bin/viewer-gate.ts`. Viewer changelog entries wait in `docs/viewer-launch-changelog.md`, not under `[Unreleased]`. At launch: delete the gate, move those entries back, drop the \"not released yet\" notes from `site/`.\n\n### NodeKind / EdgeKind\n\nDefined in `src/types.ts`. Both extractors and resolvers must use these exact strings.\n\n- **NodeKind**: `file`, `module`, `class`, `struct`, `interface`, `trait`, `protocol`, `function`, `method`, `property`, `field`, `variable`, `constant`, `enum`, `enum_member`, `type_alias`, `namespace`, `parameter`, `import`, `export`, `route`, `component`, `union`.\n- **EdgeKind**: `contains`, `calls`, `imports`, `exports`, `extends`, `implements`, `references`, `type_of`, `returns`, `instantiates`, `overrides`, `decorates`.\n\n### Multi-agent installer\n\n`src/installer/` is the entry point for `codegraph install` (and the bare `codegraph`/`npx @colbymchenry/codegraph` invocation). Architecture:\n\n- `targets/registry.ts` lists every supported agent.\n- `targets/types.ts` defines the `AgentTarget` interface — adding a 5th agent (Continue, Zed, Windsurf…) is **one new file in `targets/` + one entry in `registry.ts`**. Each target owns its config-file location and MCP-server JSON/TOML/JSONC writing. (Claude, Codex, Gemini and opencode also write a short instructions-file block — see below.)\n- Current targets: `claude.ts`, `cursor.ts`, `codex.ts`, `opencode.ts`.\n- `targets/toml.ts` is a hand-rolled TOML serializer scoped to `[mcp_servers.codegraph]` (used by Codex). Sibling tables and `[[array_of_tables]]` are preserved verbatim. No new dependency.\n- opencode reads `opencode.jsonc` by default; the installer prefers existing `.jsonc`, falls back to `.json`, and creates `.jsonc` for greenfield installs. Edits are surgical via `jsonc-parser` so user comments and formatting survive install/re-install/uninstall round-trips. The MCP entry is OpenCode 2's native `mcp.servers.codegraph` with `disabled: false` and `codemode: false` (so `codegraph_explore` stays on the native tool list); a pre-#1698 `mcp.codegraph` + `enabled` entry is migrated on re-install and removed by uninstall.\n- `instructions-template.ts` holds `CODEGRAPH_INSTRUCTIONS_BLOCK`, a deliberately short marker-fenced (`<!-- CODEGRAPH_START -->`/`<!-- CODEGRAPH_END -->`) `## CodeGraph` block. The Claude, Codex, Gemini and opencode targets write it into their instructions file (`CLAUDE.md` / `~/.codex/AGENTS.md` / `~/.gemini/GEMINI.md` / `~/.config/opencode/AGENTS.md`) via `upsertInstructionsEntry`, and `uninstall` removes it. History: #529 removed the old full playbook because it duplicated the MCP `initialize` instructions; #704 brought back this small block because the instructions file reaches two audiences `initialize` cannot — Task-tool subagents and non-MCP harnesses (the `codegraph explore` CLI). Cursor and Kiro write no block; their `install` only strips one a previous install left behind. Keep the block to command names and the two surfaces; tool guidance itself still belongs in `server-instructions.ts`.\n- All installer changes need matching coverage in `__tests__/installer-targets.test.ts` — there are ~47 parameterized contract tests covering install idempotency, sibling preservation, uninstall reverses install, byte-equal re-runs returning `unchanged`, and partial-state recovery for Codex.\n\n### Cursor MCP working-directory quirk\n\nCursor launches MCP subprocesses with the wrong cwd and doesn't pass `rootUri` in `initialize`. The installer injects `--path` into Cursor's MCP args — absolute path for local installs, `${workspaceFolder}` for global installs. If you touch Cursor wiring, preserve this.\n\n### MCP server instructions\n\n`src/mcp/server-instructions.ts` is sent back to the agent in the MCP `initialize` response. This is the *first* thing every agent sees about how to use the tools, and as of issue #529 it is the **single source of truth** for agent-facing tool guidance. The installer's instructions-file block (`instructions-template.ts`, see above) only names the tool and the CLI for subagents and non-MCP agents; it does not repeat this guidance. Edit tool guidance here and nowhere else.\n\n## Retrieval performance & dynamic-dispatch coverage (do not regress)\n\nCodeGraph's core value is letting an agent answer **structural/flow** questions (\"how does X reach Y\", trace, impact, callers) with a few **fast** codegraph calls and **zero Read/Grep**. The optimization target is **wall-clock latency + tool-call count** — *don't optimize for token cost*. (Cost is **lower**, not \"flat\" as earlier framing claimed: a current-build with-vs-without A/B across the 7 README repos, median of 4, saved on average **35% cost · 57% tokens · 46% time · 71% tool calls** — reproducing the published README. The mechanism is **far fewer turns over a much smaller accumulated context** — NOT cache-ability: the without-arm's huge token volume is *mostly* cheap cache-reads, which is why token-count savings (57%) look bigger than cost savings (35%). Measure tokens by **summing per-turn assistant usage**, not `result.usage` (last-turn only in current Claude Code). See `docs/benchmarks/call-sequence-analysis.md`.) The mechanism that drives everything here: **an agent falls back to Read/Grep the instant a codegraph answer is insufficient.** So every change is judged by one question — is codegraph's answer sufficient enough to *stop* the agent from reading?\n\n**Target behavior:** a flow question resolves in **1 codegraph call on small repos, scaling to 3–5 on large**, with **Read/Grep = 0**. When reviewing a PR or trying something new, do not regress this.\n\n### Adapt the tool to the agent — don't try to change the agent\n\nThe lever that decides whether a retrieval change lands. **Test before building anything here: does this make a tool the agent _already calls_ do more with the input it _already gives_? If it instead needs the agent to behave differently — pick a different tool, query differently, learn from examples — it hits the low-salience wall and won't land.**\n\nCodeGraph's only channels to influence the agent are low-salience: the MCP `initialize` instructions (`server-instructions.ts`) and the tool descriptions. Changing them does **not** reliably move the agent's tool _choice_ or query style — validated: trace-first steering ported into the server-instructions + tool descriptions (3 wording variants) never reproduced what a CLI `--append-system-prompt` achieved, and **regressed** wall-clock vs baseline. New tools fare worse (rarely chosen — the agent under-picks even `trace`); \"better examples\" is the same steering. The agent's tool-choice does improve on its own as host models get better at tool use — but that is not ours to force.\n\nWhat works is meeting the agent where it already is:\n- **explore-flow** — `codegraph_explore` is the PRIMARY tool the agent reliably calls; its query is a precise bag of symbol names (incl. qualified `Class.method`) spanning the flow the agent is after; explore finds the call path _among those named symbols_ (riding synthesized edges) and leads its output with it. (`buildFlowFromNamedSymbols`: segment/co-naming disambiguation; ≤1 unnamed bridge so it never wanders a god-function's fan-out. Overload-aware: a PascalCase type token in the query biases an overloaded name to that type's own def — `DataRequest task` → DataRequest's `task`, not the abstract base; named-symbol files sort first.)\n- **Sufficiency** — make the tool's output complete enough that the agent stops. `codegraph_node` returns the full body + the caller/callee trail, and for an AMBIGUOUS name returns **every overload's body in one call** (so the agent never Reads a file to find the right overload — validated on Alamofire/gin). This is the after-explore depth tool (labeled SECONDARY).\n- **Errors teach abandonment** — one or two `isError: true` responses early in a session and the agent stops calling codegraph entirely (maintainer-observed, repeatedly). `isError` is reserved for genuine \"stop trying\" cases: security refusals (`PathRefusalError`) and real malfunctions (which carry a retry-once note). Every expected/recoverable condition — project not indexed, symbol not found, file not in the index — returns a **SUCCESS-shaped response carrying the guidance** (`NotIndexedError` → `textResult`, see `ToolHandler.execute`'s catch). The same principle is why the tool surface is **always exposed, even at an un-indexed root** (the old empty-`tools/list` gate was removed in #964 — it broke monorepos where only sub-projects carry a `.codegraph/`, and hid the tools from a session that started before `codegraph init`): safety comes from the response SHAPE (success-shaped guidance, never `isError`), not from hiding tools. An un-indexed root's `initialize` sends a per-project variant (`SERVER_INSTRUCTIONS_NO_ROOT_INDEX` — \"pass `projectPath` to a project that has a `.codegraph/`\"), not an \"inactive\" note; indexing is still deliberately the user's call, never the agent's.\n\nWhat fails is the inverse — folding a precise answer into a **fuzzy-input** tool: the now-removed `codegraph_context` took a description, not symbols, so it couldn't disambiguate a flow's endpoints and surfaced the _wrong feature_ (which is why it was cut). Precise output needs precise input — explore takes a symbol bag for exactly this reason. (`codegraph_trace` was likewise removed: explore-flow does its job and the agent under-picked it.)\n\nThe remaining lever under this axis is **coverage**: every flow made to connect statically (a new dynamic-dispatch synthesizer, or extracting symbols static parsing skipped — e.g. object-literal store actions in `create((set,get)=>({...}))`) is then surfaced automatically by explore-flow, no agent change needed. Reactive/reconciler runtimes (Halo's `ReactiveExtensionClient`, MediatR, Vue Proxy) are the frontier — flows there have no static edges, so nothing surfaces (correctly — silent beats wrong). Full investigation + A/B record: `docs/benchmarks/call-sequence-analysis.md` + auto-memory `project_codegraph_read_displacement`.\n\n### Explore budget — keep BOTH budgets monotonic with repo size\n\nTwo functions in `src/mcp/tools.ts` scale explore with indexed file count. This is the expected resolution (a regression here silently forces agents back to Read):\n\n| Repo | files | explore calls | chars/call | per-file |\n|---|---|---|---|---|\n| express (small) | 147 | 1 | 18K | 3800 |\n| excalidraw/django (medium) | 643–3043 | 2 | 28K | 6500 |\n| vscode (large) | 10446 | 3 | 35K | 7000 |\n| ~20k / ~40k | — | 4 / 5 | 38K | 7000 |\n\n- `getExploreBudget(fileCount)` → **call** budget: `<500→1, <5000→2, <15000→3, <25000→4, ≥25000→5` (max 5).\n- `getExploreOutputBudget(fileCount)` → **per-call** output (chars / files / per-file). **Invariant: a larger tier must never get a smaller `maxCharsPerFile` than a smaller tier.** (Regression that motivated this doc: the `<5000` tier's 2500 was *below* the `<500` tier's 3800, so on a god-file repo — excalidraw's 415 KB `App.tsx` — one explore returned <1% of the file and forced a Read.)\n- Explore output must **never tell the agent to \"use Read\"** — steer to another `codegraph_explore` and \"treat returned source as already Read.\"\n\n### Dynamic-dispatch coverage — the flow must EXIST in the graph end-to-end\n\nStatic tree-sitter extraction misses computed/indirect calls, so flows break at dynamic dispatch and the agent reads to reconstruct them. Synthesizers/resolvers bridge these so `codegraph_explore` connects them end-to-end (`src/resolution/callback-synthesizer.ts`, `src/resolution/frameworks/`). Channels today: callback/observer, EventEmitter, **React re-render** (`setState`→`render`), **JSX child** (`render`→child component), **React Native native→JS events** (`sendEvent(withName:)` / JVM `emit` → the `addListener` handler, named or inline, `rn-event-channel`), django ORM descriptor. The JS→native direction is a *resolver* (`frameworks/react-native.ts`: `RCT_EXPORT_METHOD`, `RCT_EXTERN_MODULE` Swift shims, TurboModules), which trusts receiver evidence — an alias bound to `NativeModules.X` — over the import resolver. All synthesized edges are `provenance:'heuristic'` with `metadata.synthesizedBy` + `registeredAt` (the wiring site), surfaced inline in `codegraph_explore`'s Flow section and the `codegraph_node` trail.\n\n**Principle: partial coverage is WORSE than none.** Bridging one boundary but not the next reveals a hop the agent then drills + reads to finish. Measured on excalidraw: react-render alone *raised* reads to 5–7; only completing the flow (adding the jsx-child hop) dropped it to 0–1. **Always close the flow end-to-end and re-measure** — never ship a half-bridged flow.\n\n\n### Validation methodology & worked examples\n\n**Required** for every new language/framework: validate on small/medium/large real repos with >=3 flow prompts; deterministic probes (`scripts/agent-eval/probe-*.mjs`) then agent A/B (`scripts/agent-eval/run-all.sh` / `ab-new-vs-baseline.sh`). Pass bar: ~0 Read/Grep within the explore-call budget, faster than without-codegraph, no control-repo regression.\n\nFull methodology (feedback metrics, CLI contamination guard, Sonnet/`--effort high` model policy, daemon pre-warm), the Excalidraw worked example, and coverage matrix live in:\n- `docs/AGENTS.md` (nested; also loaded when cwd is under `docs/`)\n- `docs/design/dynamic-dispatch-coverage-playbook.md`\n- `docs/design/callback-edge-synthesis.md`\n- `docs/benchmarks/call-sequence-analysis.md` / `docs/benchmarks/agent-eval-feedback-metrics.md`\n\n\nTests live in `__tests__/` and mirror the module they cover. Notable ones beyond the obvious:\n\n- `installer-targets.test.ts` — parameterized contract suite across all 4 agent targets (see installer notes above).\n- `evaluation/` — `runner.ts` + `test-cases.ts` exercise codegraph against synthetic projects and score the results; run via `npm run eval` (builds first). Not part of `npm test`.\n- `sqlite-backend.test.ts` / `node-sqlite-backend.test.ts` — pin that `node:sqlite` is the sole backend: `getBackend()` reports `node-sqlite` and the DB comes up in WAL.\n- `pr19-improvements.test.ts`, `frameworks-integration.test.ts` — regression coverage for specific past PRs/incidents; don't rename these, the names anchor to git history.\n\nTests create temp dirs with `fs.mkdtempSync` and clean up in `afterEach`. They write real files and exercise real SQLite — there is no DB mocking.\n\nEvery engine test file runs in a throwaway home dir (`__tests__/setup-home-sandbox.ts`, a `setupFiles` entry): `HOME`/`USERPROFILE` (+ Windows vars), `XDG_CONFIG_HOME` and `GIT_CONFIG_GLOBAL` point into it, and `CLAUDE_CONFIG_DIR`/`CODEX_HOME`/… are cleared; spawned children inherit it. It's a backstop — still inject writes to global state (e.g. `UpgradeDeps.wirePromptHook`, #2275).\n\n### Windows-gated tests\n\nBehavior that differs by platform (path resolution, drive letters, `SENSITIVE_PATHS`, `%APPDATA%` config dirs, CRLF) must be gated, not assumed. Use `it.runIf(process.platform === 'win32')(...)` for Windows-only assertions and `it.runIf(process.platform !== 'win32')(...)` for POSIX-only ones — e.g. `/etc` is sensitive on POSIX but resolves to `C:\\etc` (non-existent) on Windows, so an ungated `/etc` assertion fails on Windows. Validate the Windows side for real (see below); don't merge a Windows-gated test you haven't seen run.\n\n## Cross-platform validation\n\nThe dev machine — and the default `npm test` target — is **macOS**, so local runs cover the macOS path. The other two platforms aren't here; when a change is platform-sensitive (file watching, sockets / named pipes, path & symlink handling, process lifecycle, inotify budget) validate them for real rather than guessing.\n\n### Linux (Docker)\n\nWhen asked to test or validate on Linux, use **Docker** — there's no Linux box, but Docker runs on the macOS host. Build a throwaway image from the repo and run the suite inside it:\n\n- `FROM node:22-bookworm`; `COPY` the repo with a `.dockerignore` excluding `node_modules`/`dist`/`.git`/`.codegraph`; `RUN npm ci && npm run build`. Don't reuse the Mac `node_modules` — `esbuild`/`rollup` ship platform-specific binaries.\n- Run with **`docker run --rm --init`**. The `--init` is load-bearing for any process-lifecycle test (daemon reaping, the #277 PPID watchdog, idle-timeout): without a zombie-reaping PID 1, a SIGKILL'd/exited process lingers as a zombie and `process.kill(pid, 0)` still reports it *alive*, so exit-detection assertions false-fail even though the process did exit.\n- Linux is where the inotify watch budget actually bites: count a process's watches via `/proc/<pid>/fdinfo/*` (sum `^inotify ` lines on the fd whose `readlink` is `anon_inode:inotify`).\n\n### Windows (Parallels VM + SSH)\n\nFor any Windows-specific PR, bug, or implementation, validate it on the real Windows VM rather than guessing. Connection details live in the gitignored **`.parallels`** file at the repo root (VM name, guest IP, SSH user/key). `prlctl exec` needs Parallels Pro and is unavailable, so SSH is the bridge.\n\n- Connect / run from the Mac host: `ssh <user>@<guest_ip> \"...\"`. For multi-line work, pipe PowerShell over stdin and **refresh PATH from the registry** first (sshd's session has a stale PATH after winget installs):\n  ```\n  ssh colby@10.211.55.3 \"powershell -NoProfile -ExecutionPolicy Bypass -Command -\" <<'PS'\n  $env:Path = [Environment]::GetEnvironmentVariable(\"Path\",\"Machine\") + \";\" + [Environment]::GetEnvironmentVariable(\"Path\",\"User\")\n  Set-Location C:\\dev\\codegraph\n  PS\n  ```\n- Clone fresh into a **Windows-local** path (`C:\\dev\\codegraph`) and `npm ci` there — never run npm against the shared Mac repo, since `esbuild`/`rollup` ship platform-specific binaries.\n- Guest toolchain (winget): Node LTS, Git, and the **VC++ ARM64 redistributable** (required by `@rollup/rollup-win32-arm64-msvc`, which vitest pulls in).\n- Fetch a contributor PR head straight from their fork to dodge `pull/<n>/head` lag: `git fetch <fork-url> <branch>` then `git checkout -f FETCH_HEAD`.\n- Windows baseline: as of #2053 the full suite passes on the Windows 11 (ARM64) VM. The only expected exception is `security.test.ts > Session marker symlink resistance > does not follow a pre-planted symlink`, which needs symlink privileges (Developer Mode) — confirm any other failure against `origin/main` before blaming your PR. The former `mcp-initialize.test.ts` / `mcp-roots.test.ts` `EPERM` teardown failures came from tests spawning `serve --mcp` without the runtime flags (its `--liftoff-only` re-exec grandchild kept the cwd / SQLite file open); spawn it with `WASM_RUNTIME_FLAGS` and await the child's exit before removing the temp dir. Windows checkouts may be CRLF — split source lines on `/\\r?\\n/` in tests.\n- Windows worker crashes: a worker thread that ends (`terminate()`, its own `process.exit()`, or the process exiting) while V8's concurrent marker is marking its heap kills the process with exit 3221225477 (0xC0000005) and leaves no error or dump. It's worst while the worker is still loading its modules. Owners must not terminate a worker before its first message (`workerStarted` / `terminateOnceStarted`, or the pool's own handshake), and a worker that exits itself calls `collectBeforeExit()` first — both in `src/worker-teardown.ts`. `--no-concurrent-marking` also stops it, but measured about a third slower indexing on the Windows VM, so it is not used.\n\n## Releases\n\nReleased to npm and mirrored as [GitHub Releases](https://github.com/colbymchenry/codegraph/releases). `CHANGELOG.md` is the source of truth; GitHub Release notes are extracted from it.\n\n### Writing changelog entries\n\n**Default: write entries under `## [Unreleased]`** — that's the section reserved for work landing between releases. **Don't pre-create a `## [X.Y.Z]` block** for the next release: the Release workflow's first step is `scripts/prepare-release.mjs`, which automatically promotes everything under `[Unreleased]` into a new `## [X.Y.Z] - <YYYY-MM-DD>` block at release time (or merges into a pre-existing `[X.Y.Z]` block if one exists — but you don't need one). Pre-staging is what caused the v0.9.5 sparse-release-notes incident: a sparse `[0.9.5]` block hand-added before the rest of the work landed got picked by the extractor over the much-larger `[Unreleased]` section above it. Don't do that.\n\nFormatting rules for any entry (anywhere — `[Unreleased]` or otherwise):\n\n1. **Write friendly, user-facing notes — not engineer-facing ones.** Group under `### New Features` and `### Fixes` (sentence-case). Surface `### Breaking Changes` and `### Security` as their own sections **only when the release has them**; fold improvement-flavored changes into New Features. Omit empty sections. (This replaces the old Keep-a-Changelog `Added/Changed/Fixed/Removed/Deprecated` grouping: the GitHub Release page extracts each version block **verbatim** via `scripts/extract-release-notes.mjs`, and the old dense, implementation-focused entries rendered as an unreadable wall of text — so the whole CHANGELOG was rewritten to this format and every published release re-noted to match.)\n2. **One plain-language sentence per bullet:** what changed and why it matters to a user. Lead with the capability, or with the symptom that's now fixed.\n3. **Strip the internals.** No internal file paths (`src/...`), no internal symbol / function / class names, no benchmark numbers / percentages / node-or-edge counts. **Keep:** language & framework names (Go, Spring, NestJS, …), things a user types or sets (`codegraph install`, `codegraph_explore`, the `CODEGRAPH_*` env vars), agent / IDE names (Claude Code, Cursor, opencode, Kiro, …), and a brief `Thanks @user` when a contributor is credited.\n4. Issue / PR references in entries are by number (`(#403)` etc.); the GitHub renderer auto-links them in the published release notes.\n5. **Don't add a `[X.Y.Z]: https://...` link reference yourself** — `prepare-release.mjs` appends it automatically when it promotes the version (idempotent: a re-run is a no-op if it already exists).\n6. **Every release opens with a `### Highlights` block — the only part most people read.** At most ~8 one-line bullets, in plain language for someone who doesn't read code, ordered by what a typical user notices first (new agent/IDE support and setup changes, then answer quality, then reliability), plus a one-sentence upgrade note when a re-index is needed. Write or refresh it in `[Unreleased]` when a release is being prepared — not per PR — and keep the detailed `### New Features` / `### Fixes` entries below it. When `### Fixes` grows past ~15 entries, group them under `####` sub-headings (`Better answers from codegraph_explore`, `Finding your project, live updates, and the CLI`, `Indexing reliability and disk usage`, `Language and framework accuracy`) so a skimmer can find their area.\n\nMulti-word headings like `### New Features` are safe on the normal release path: `prepare-release.mjs` **Case A** moves the whole `[Unreleased]` body verbatim into `[X.Y.Z]`. (Only its rarely-used **Case B** *merge* splits sub-sections with a single-word `^### (\\w+)$` regex that wouldn't match them — and Case B fires only if a `[X.Y.Z]` block was pre-created, which rule above already forbids.)\n\n### Release flow (the user runs these)\n\nReleases are built and published by the **GitHub Actions \"Release\" workflow**\n(`.github/workflows/release.yml`). It runs `scripts/prepare-release.mjs` to\npromote `[Unreleased]` into `[<version>]` (and auto-commit + push that\nCHANGELOG change back to `main` so on-disk truth matches the published\nnotes), then bundles a Node runtime per platform (`scripts/build-bundle.sh`)\nand publishes both the GitHub Release and the npm thin-installer\n(`scripts/pack-npm.sh`: a shim package + per-platform packages).\nPublishing manually is **wrong** now — a plain `npm publish` ships the root\npackage (non-bundled), which breaks anyone on Node < 22.5.\n\n**Claude does NOT bump the version unless explicitly asked.** The maintainer\ntypically does it themselves — often by editing `package.json` directly via\nthe GitHub web UI. Don't proactively commit a version bump as part of\nunrelated work, and don't propose one when summarizing a PR.\n\nWhen the maintainer DOES bump the version, the only edit strictly required is\nto `package.json` — the workflow's \"Sync package-lock.json\" step detects a\nmismatch between `package.json` and `package-lock.json`, runs\n`npm install --package-lock-only --ignore-scripts` to rewrite the lock file's\nversion fields (top-level + `packages.\"\"`), and auto-commits + pushes the\nresult back to `main` with `[skip ci]`. So a GitHub-web-UI single-file edit to\n`package.json` is enough to kick off a clean release. (If they edit both files\nlocally, that's fine too — the sync step no-ops.) Bump `ui/package.json` to the\nsame version in that change: the component library is versioned with the\nengine, `ui-package.test.ts` pins it, and the workflow does not sync it — the\n1.6.1 bump left `main` red on exactly that.\n\nOnce `package.json` is at the target version on `main`, trigger\n**Actions → Release → Run workflow** (on `main`). The workflow:\n\n1. Syncs `package-lock.json` to `package.json`'s version if they've drifted; commits + pushes that change.\n2. Runs `prepare-release.mjs <X.Y.Z>` → promotes `[Unreleased]` → `[X.Y.Z] - <today>` in `CHANGELOG.md`, appends the link reference, commits + pushes the move with `[skip ci]`.\n3. Builds every platform bundle on one runner, generates `SHA256SUMS`.\n4. Creates the GitHub Release with notes from the freshly-promoted `[X.Y.Z]` block.\n5. Publishes the npm shim + per-platform packages. Requires the `NPM_TOKEN` repo secret.\n\n**Do not run `npm publish`, `git push`, or `git tag` yourself** — these are\npublish actions on shared state. Write the files, hand the user the commands.\n\n## House rules\n\n- The `0.7.x` line is in active multi-agent rollout. Any change to `src/installer/` (especially `targets/`) needs corresponding test coverage and a CHANGELOG entry — installer regressions break every new install silently.\n- When changing what the MCP tools do or how agents should use them, edit `src/mcp/server-instructions.ts` — it is the **single source of truth** for agent-facing tool guidance (issue #529). The installer's short instructions-file block (`instructions-template.ts`) only names `codegraph_explore` and the `codegraph explore` CLI; update it only when a tool or CLI command is renamed or removed. (The repo's own checked-in `.cursor/rules/codegraph.mdc` is dogfooding config — update it too if you use Cursor on this repo, but it ships nowhere.)\n- **Before adding or extending a router, a web framework, or a language's `WHEN` rules, read `docs/design/framework-coverage.md`.** It is the standing answer to \"what is supported and what is left\" across the three axes (route nodes → Entry points, `navigates` edges → Screens, branch-guard rules → the `WHEN` labels), with what each remaining item needs, the traps that have already cost debugging time, and the queries to re-verify it. Update it in the same change that moves a row.\n- CodeGraph provides **code context**, not product requirements. For new features, ask the user about UX, edge cases, and acceptance criteria — the graph won't tell you.\n- **When the user references issues, PR comments, or external reports, anchor them to a date and version before drawing conclusions.** Check the comment's `createdAt` against:\n  - The **last released version** — `grep -m1 '^## \\[' CHANGELOG.md` shows the top-of-file version (older releases follow). A comment dated before the latest `## [X.Y.Z] - YYYY-MM-DD` is reacting to *released* state — work that's only on `main` or on an unmerged branch doesn't apply.\n  - The **last main commit** — `git log --first-parent main -1 --format='%ai %h %s'`. A comment after the last release but before a fix on main may already be addressed there but unreleased.\n  - The **current branch's tip** — your own unmerged work obviously can't be what the comment is reacting to.\n  Always disambiguate \"released,\" \"merged-but-unreleased,\" and \"in-progress\" before agreeing that a user-reported problem is unfixed (or that a fix is incomplete). A user saying \"your fix only covers X\" about a recent PR is usually pointing at the *released* shortcomings — your in-flight branch may already address them but they have no way to know that.\n- **Version-tag every image referenced in `README.md`.** GitHub caches README images (`raw.githubusercontent.com` with a 5-minute TTL; third-party hosts sit behind the long-lived camo proxy), so updating an asset in place can keep showing the stale version. Give each README image URL a `?v=N` query tag and **bump `N` in the same commit whenever the asset bytes change** — e.g. `assets/waitlist.svg?v=2`. The changed URL sidesteps every cache so the new image shows immediately instead of waiting on a TTL to expire.\n","isInternal":false,"tokens":9432,"sizeBytes":38228},{"name":"AGENTS.md","path":"docs/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/colbymchenry/codegraph/HEAD/docs/AGENTS.md","title":"docs - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md (docs/)\n\nNested Codex guidance under `docs/`. Loaded with the repo-root `AGENTS.md` when cwd is under `docs/` (Codex walks root to cwd; shared `project_doc_max_bytes` budget).\n\nRoot `AGENTS.md` already carries the non-negotiable retrieval principles (adapt-the-tool, explore budgets, end-to-end synthesis). This file holds the longer validation methodology and Excalidraw worked example that were moved out of root to fit the budget.\n\n### Validation methodology (REQUIRED for every new language/framework)\n\nFor each **language × framework**, validate on **small, medium, and large** real repos with **≥3 different flow prompts** each:\n\n1. **Pick the canonical flow** for the framework (\"how does X reach Y\": state→render, request→handler→view, query→SQL, action→reducer→store…).\n2. **Deterministic probes** (`scripts/agent-eval/probe-{node,explore}.mjs` against the built `dist/`): `codegraph_explore` with the flow's symbol names connects from→to end-to-end with no break (its Flow section shows the path); **no node explosion** (`select count(*) from nodes` stable before/after re-index); synthesized-edge **precision** spot-check (`select … where provenance='heuristic'`).\n3. **Agent A/B** (`scripts/agent-eval/run-all.sh <repo> \"<Q>\"`): with vs without codegraph, **≥2 runs/arm** (run-to-run variance is large — never conclude from n=1). Record **duration, total tool calls, Read, Grep**. Optional forced-Read-0 sufficiency proof via the block-read hook (`scripts/agent-eval/hook-settings.json`).\n   - **Every run also reports three feedback metrics** — residual context occupancy, explore sufficiency (what the agent did NEXT after each explore), and allocation efficiency (share of returned bytes the answer cited) — under each run, plus a side-by-side arm table (`compare-arms.mjs`). Entry point: `docs/benchmarks/agent-eval-feedback-metrics.md`. Reading them: `Read a file we returned` is an allocation miss, `Read a file we did NOT return`/`Grep` is recall; allocation efficiency is **relative** (attribution is by citation) so it is only valid between builds on the same question; occupancy *shares* are Claude Code / 200k and don't transfer to another host — the arm ratio does.\n   - **The `codegraph` CLI is blocked in every arm** (`no-cli-shim.sh`: sanitized PATH + a PreToolUse hook, shared by both harnesses). Without it 14 of 15 without-arm runs in one 7-repo pass reached codegraph through Bash. Check the contamination row before believing any number: `CLI calls that RETURNED output` > 0 invalidates the run (in a new-vs-baseline A/B it silently drops calls from all three metrics, since a CLI explore is not a tool call).\n   - **Model policy — every A/B arm runs Claude with `--model sonnet --effort high`. Always. Never Opus/Fable.** All `scripts/agent-eval/*.sh` default to this (`MODEL`/`EFFORT` env override exists — don't raise it without an explicit reason from the maintainer). Two reasons, and the second matters more than cost: (a) Sonnet doesn't burn tokens; (b) **Sonnet is the deliberate floor model** — codegraph's real users attach it to whatever agent they already run (Cursor Composer, Gemini, etc.), so we validate on a \"dumber\" model on purpose: a stronger model's tool-use covers up the salience/sufficiency problems a weaker one exposes. An affordance that lands on Sonnet generalizes up to every host; one that only works on Opus/Fable doesn't generalize down to the agents most users actually have. Both arms always use the same model.\n   - **MCP attach is a startup-latency issue, not a hard block.** On a multi-step task the agent dives into Read/grep before codegraph finishes its ~2-3s startup (worse when the eval is itself run nested inside a Claude session, under CPU contention), so it runs with no codegraph. Fix: **pre-warm a persistent daemon** for the target (`CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS` high; spawn `serve --mcp --path <target> </dev/null &`; wait for `.codegraph/daemon.sock`) **and skip the startup re-exec** (`CODEGRAPH_WASM_RELAUNCHED=1`) so claude connects before the agent's first turn. Don't trust claude's `init` snapshot — it can read `status:\"pending\"` / 0 tools even when it then connects; judge by actual codegraph usage in `parse-run.mjs`'s `by type`. To isolate a change — **new-build vs baseline-build, both codegraph-on** (vs run-all.sh's with-vs-without) — use `scripts/agent-eval/ab-new-vs-baseline.sh <indexed-repo> \"<task>\" [baseline-ref]` (it bakes in the pre-warm).\n4. **Pass bar:** a normal flow question reaches **~0 Read/Grep within the repo's explore-call budget**, runs **faster** than without-codegraph, and shows **no regression on a control repo**. Record the numbers in `docs/design/dynamic-dispatch-coverage-playbook.md` (the coverage matrix).\n\nFull playbook + per-mechanism design: `docs/design/dynamic-dispatch-coverage-playbook.md` and `docs/design/callback-edge-synthesis.md`.\n\n### Worked example — Excalidraw (TS/React, medium, 643 files)\n\nThe template to replicate per language/framework. Question: *\"how does updating an element re-render the canvas on screen?\"* (the full flow crosses three React boundaries: observer callback, `setState`→`render`, and JSX child).\n\n| Stage | duration | Read | Grep | codegraph |\n|---|---|---|---|---|\n| Without codegraph | 115–139s | 9–10 | 10–11 | 0 |\n| Broken (explore-budget regression) | 131–139s | 5–10 | 3–5 | 6–14 |\n| Fixed (budget + msgs + synthesis) | 64–112s | 0–2 | 2–4 | 3–**10** |\n| + trace-first steering | **51–74s** | **0–2** | 0–4 | **3–4** |\n\nn=4 unhooked runs/stage, same prompt. After steering flow questions to `codegraph_trace` first: **best run 0 Read / 0 Grep / 3 codegraph / 51s**; **2 of 4 fully clean** (0 Read, 0 Grep). Steering eliminated the over-drill variance — call count tightened from 3–10 to 3–4, trace adoption went 3/4 → 4/4, and the `search`+`callers` path-reconstruction floundering dropped to 0. Run-to-run variance is still real; report the range, never a single run. **Residual reads/greps are all the nonce data-flow** (`canvasNonce` — a local prop with no graph edges); that's the def-use/data-flow frontier, left deliberately uncovered (tracking every local would explode the graph). Validated: `trace(mutateElement, renderStaticScene)` connects in **6 hops** across all three boundaries (`mutateElement → triggerUpdate → [callback] triggerRender → [react-render] render → [jsx] StaticCanvas → renderStaticScene`), each hop showing inline source + the wiring site; node count stable at 9,289; 1 callback + 46 react-render + 280 jsx-render synthesized edges (no explosion, precision-checked).\n\n\nAlso see: `docs/design/dynamic-dispatch-coverage-playbook.md`, `docs/design/callback-edge-synthesis.md`, `docs/benchmarks/call-sequence-analysis.md`, `docs/benchmarks/agent-eval-feedback-metrics.md`.\n","isInternal":false,"tokens":1752,"sizeBytes":6885},{"name":"SKILL.md","path":".claude/skills/add-lang/SKILL.md","rawUrl":"https://raw.githubusercontent.com/colbymchenry/codegraph/HEAD/.claude/skills/add-lang/SKILL.md","title":"add-lang","category":"anthropic-skill","format":"markdown","content":"---\nname: add-lang\ndescription: Add tree-sitter language support to codegraph end-to-end — wire the grammar + extractor, write tests, then benchmark extraction quality and retrieval value on 3 popular real-world repos. Use when the user runs /add-lang <language> or asks to add/support a new language (e.g. Lua, Elixir, Zig, OCaml) in codegraph.\n---\n\n# Add a language to CodeGraph\n\nWire a new tree-sitter language into codegraph's extraction pipeline, prove it\nextracts real symbols on popular repos, and prove it beats no-codegraph for an\nagent. Runs **fully autonomously** — pick repos, benchmark, update docs, then\nreport. **Never commit, push, publish, or tag** (house rule); leave all changes\nfor the user to review.\n\nThe argument is the language token used throughout the `Language` union, e.g.\n`lua`, `elixir`, `zig`. If none was given, ask which language. Use the lowercase\nsingle-token form everywhere (`csharp`, not `c#`).\n\n## Prerequisites\n- Run from the codegraph repo root. `node`, `git`, `gh`, and a logged-in\n  `claude` CLI (the benchmark spawns real `claude -p` runs).\n- The benchmark uses the local dev build — Step 8 builds + links it on PATH.\n\n## Workflow\n\nCopy this checklist and work through it in order:\n```\n- [ ] 1. Resolve language; bail early if already supported (just benchmark)\n- [ ] 2. Find a grammar + health-check it (ABI / heap corruption)\n- [ ] 3. Discover the grammar's AST node types (dump-ast.mjs)\n- [ ] 4. Wire the language (4 files; sometimes a 5th core touch)\n- [ ] 5. Build + verify-extraction loop until PASS\n- [ ] 6. Add extraction tests; make them green\n- [ ] 7. Auto-pick 3 popular repos by size tier; add to corpus.json\n- [ ] 8. Benchmark all 3: extraction + with/without A/B\n- [ ] 9. Update README + CHANGELOG\n- [ ] 10. Report; do NOT commit\n```\n\n### Step 1 — Resolve + short-circuit\n\nCheck whether the language is already wired: look for the token in the\n`LANGUAGES` const (`src/types.ts`) and the `EXTRACTORS` map\n(`src/extraction/languages/index.ts`). If it is already supported (e.g.\n`typescript`, `rust`), **skip Steps 2–6** and go straight to benchmarking\n(Steps 7–8) to validate/measure it — note in the report that no code changed.\n\n### Step 2 — Find a grammar, then health-check it\n\n```bash\nls node_modules/tree-sitter-wasms/out/ | grep -i <lang>   # csharp -> c_sharp\n```\n- **Present** → likely off-the-shelf; `grammars.ts` resolves it from\n  `tree-sitter-wasms` automatically. (Many languages: elixir, zig, ocaml,\n  solidity, toml, yaml, …)\n- **Absent** → vendor a `.wasm` into `src/extraction/wasm/` (like `pascal` /\n  `scala` / `lua`) and add the token to the vendored branch in Step 4.\n\n**Always health-check before writing an extractor — a *present* grammar can\nstill be unusable:**\n```bash\nnode scripts/add-lang/check-grammar.mjs <lang> path/to/valid-sample.<ext>\n```\nIt prints the grammar's ABI version and runs it the way indexing does: in a\nchild process with indexing's V8 flags, beside the vendored grammars, parsing a\nvalid sample many times and once at the 1 MiB file-size limit. If it **FAILs**\n(ERROR trees, or later parses giving a different tree, on valid code — an old ABI\ncorrupting the shared WASM heap, which silently drops nested calls/imports on\nevery file after the first; e.g. the tree-sitter-wasms **Lua** grammar is ABI 13\nand fails — or the process running it crashing or hanging, even after every\nparse finished), do NOT use that wasm. **Vendor a newer (ABI 14/15) build instead:**\n```bash\nnpm pack @tree-sitter-grammars/tree-sitter-<lang>   # often ships a prebuilt *.wasm\n# or build one: npx tree-sitter build --wasm   (needs Docker/emscripten)\ncp <the>.wasm src/extraction/wasm/tree-sitter-<lang>.wasm\n```\nthen add the token to the vendored branch in Step 4 and re-run check-grammar on\nthe vendored path until it PASSes. **If you cannot obtain a healthy wasm, STOP\nand tell the user.**\n\n### Step 3 — Discover AST node types\n\nGet a representative source file (write a small sample covering functions,\nclasses/structs, imports, enums; or `curl` a raw file from a known repo), then:\n```bash\nnode scripts/add-lang/dump-ast.mjs <lang> path/to/sample.<ext>\n# vendored grammar: pass the wasm path instead of the token\nnode scripts/add-lang/dump-ast.mjs src/extraction/wasm/tree-sitter-<lang>.wasm sample.<ext>\n```\nThe frequency table + field names (`name:`, `parameters:`, `body:`,\n`return_type:`) tell you what to map. Open the existing extractor closest to the\nlanguage's paradigm as a model: `rust.ts`/`scala.ts` (functional, traits),\n`java.ts`/`csharp.ts` (OO), `python.ts`/`ruby.ts` (scripting), `go.ts`\n(top-level methods + receivers).\n\n### Step 4 — Wire the language (4 files)\n\nThese are exact, fragile wiring — match the existing style precisely:\n\n1. **`src/types.ts`** — TWO edits:\n   - add `'<lang>',` to the `LANGUAGES` const (before `'unknown'`);\n   - add `'**/*.<ext>',` to `DEFAULT_CONFIG.include`. **Don't skip this** — it's\n     the file-scan allowlist; without the glob, `codegraph init` finds **0\n     files** even though detection/extraction are wired.\n2. **`src/extraction/grammars.ts`** — three maps:\n   - `WASM_GRAMMAR_FILES`: `<lang>: 'tree-sitter-<lang>.wasm',`\n   - `EXTENSION_MAP`: each file extension → `'<lang>'` (e.g. `'.lua': 'lua',`)\n   - `getLanguageDisplayName`: `<lang>: '<Display Name>',`\n   - **vendored only**: add `<lang>` to the\n     `(lang === 'pascal' || lang === 'scala' || …)` wasm-path branch.\n3. **`src/extraction/languages/<lang>.ts`** — new file exporting\n   `export const <lang>Extractor: LanguageExtractor = { … }`. Map the node types\n   from Step 3. Required fields: `functionTypes`, `classTypes`, `methodTypes`,\n   `interfaceTypes`, `structTypes`, `enumTypes`, `typeAliasTypes`,\n   `importTypes`, `callTypes`, `variableTypes`, `nameField`, `bodyField`,\n   `paramsField`. Add hooks as the grammar needs them (`getSignature`,\n   `getVisibility`, `isExported`, `extractImport`, `visitNode`, `getReceiverType`,\n   `interfaceKind`, `enumMemberTypes`, etc. — see\n   `src/extraction/tree-sitter-types.ts`).\n4. **`src/extraction/languages/index.ts`** — `import { <lang>Extractor } from\n   './<lang>';` and add `<lang>: <lang>Extractor,` to `EXTRACTORS`.\n\n**Sometimes a 5th, core touch in `src/extraction/tree-sitter.ts`** — variable\nextraction has per-language branches in `extractVariable` (the generic fallback\nonly finds direct `identifier`/`variable_declarator` children). If the grammar\nnests declared names (e.g. Lua's `variable_declaration → variable_list`), add a\n`} else if (this.language === '<lang>')` branch there, mirroring the existing\nts/python/go ones. Import forms that aren't a distinct node (Lua/Ruby `require`\nis a *call*) are handled in the extractor's `visitNode` hook instead.\n\n### Step 5 — Build + verify loop\n\n```bash\nnpm run build            # tsc + copy-assets (copies any vendored *.wasm into dist/)\n```\nIndex a small sample repo and check extraction:\n```bash\n( cd <sample-repo> && codegraph init -i )\nnode scripts/add-lang/verify-extraction.mjs <sample-repo> <lang>\n```\n`verify-extraction.mjs` fails (exit 1) if the language isn't detected or only\n`file`/`import` nodes were produced — the classic symptom of wrong node-type\nnames. On FAIL or a thin WARN: re-run `dump-ast.mjs` on a richer file, fix the\nmappings in `<lang>.ts`, `npm run build`, re-index, re-verify. **Repeat until\nPASS.**\n\n### Step 6 — Tests\n\nAdd to `__tests__/extraction.test.ts`, modeled on the `Rust Extraction` block:\n- a `detectLanguage` assertion in `describe('Language Detection')`\n- a `describe('<Lang> Extraction')` block asserting functions/classes/imports\n  are extracted from an inline source string.\n```bash\nnpx vitest run __tests__/extraction.test.ts\n```\nGreen before continuing.\n\n### Step 7 — Auto-pick 3 repos + corpus\n\nPick **without asking**. Find candidates, then curate 3 that are genuinely\n`<lang>`-dominant, one per size tier:\n```bash\ngh search repos --language=<lang> --sort=stars --limit 40 \\\n  --json fullName,stargazerCount,description\n```\nTiers (match `corpus.json`): **Small** <~150 files · **Medium** ~150–1500 ·\n**Large** >~1500. Skip repos that are tagged `<lang>` but mostly another\nlanguage. Write one cross-file architecture **question** per repo (the kind that\nneeds tracing across files). Add a `\"<Language>\"` block to\n`.claude/skills/agent-eval/corpus.json` (fields: `name`, `repo`, `size`,\n`files`, `question`) so `/agent-eval` can reuse them.\n\n### Step 8 — Benchmark all 3 (extraction + A/B)\n\nMake the dev build the codegraph on PATH **once**, then loop:\n```bash\nnpm run build && ./scripts/local-install.sh\nscripts/add-lang/bench.sh <lang> <name> <url> \"<question>\" headless   # ×3\n```\n`bench.sh` clones (shared `/tmp/codegraph-corpus`), wipes + indexes, runs\n`verify-extraction.mjs`, then the with/without retrieval A/B via\n`scripts/agent-eval/run-all.sh` (skips the paid A/B if extraction is broken).\nRead each `parse-run.mjs` summary printed by `run-all.sh`: tool calls, file\n`Read`s, Grep/Bash, codegraph-tool calls, duration, and **cost** — for both the\n`with` and `without` arms. After the loop, restore the dev link if needed:\n`./scripts/local-install.sh`.\n\n### Step 9 — Docs + CHANGELOG\n\n- **README.md**: add `<Lang>` to the \"19+ Languages\" feature bullet, and add a\n  row to the **Supported Languages** table:\n  `| <Lang> | \\`.ext\\` | Full support (classes, methods, …) |`.\n- **CHANGELOG.md**: add an `## [Unreleased]` section at the top (above the\n  latest version) with `### Added` → a user-perspective bullet, e.g.\n  *\"CodeGraph now indexes **<Lang>** (`.ext`) — functions, classes, imports, and\n  call edges.\"* If `## [Unreleased]` already exists, append under it. (It's\n  folded into the next versioned block at release time.)\n\n### Step 10 — Report (do NOT commit)\n\nSummarize for review:\n- **Files changed**: the 4 wiring edits + new extractor + tests + README +\n  CHANGELOG + corpus.json (+ any vendored `.wasm`).\n- **Extraction** per repo: files / nodes / edges / `verify-extraction` result.\n- **A/B** per repo: `with` vs `without` (tool calls, file Reads, cost) and a\n  one-line verdict — did codegraph reduce effort, and did both arms reach a\n  correct answer?\n- **Gaps / follow-ups** (node types not yet mapped, resolution edges missing,\n  framework routes, etc.).\n\nHand the changes to the user. **Do not** run `git commit`/`push` or publish —\nreleases go through the GitHub Actions Release workflow.\n\n## Notes\n- The A/B spawns real **paid** `claude -p` runs (opus, `--max-budget-usd`),\n  2 arms × 3 repos. The corpus dir `/tmp/codegraph-corpus` is shared with\n  `/agent-eval`, so clones are reused across runs.\n- Any new `*.wasm` must live in `src/extraction/wasm/` — `copy-assets` (run by\n  `npm run build`) ships it; otherwise it won't be in `dist/`.\n- An index must be served by the **same** binary that built it. Step 8 builds +\n  links the dev build first, so this holds.\n- If a grammar can't be obtained, or extraction can't reach PASS, **STOP and\n  report** — don't ship a half-wired language.\n","frontmatter":{"name":"add-lang","description":"Add tree-sitter language support to codegraph end-to-end — wire the grammar + extractor, write tests, then benchmark extraction quality and retrieval value on 3 popular real-world repos. Use when the user runs /add-lang <language> or asks to add/support a new language (e.g. Lua, Elixir, Zig, OCaml) in codegraph."},"isInternal":false,"tokens":3058,"sizeBytes":11073},{"name":"corpus.json","path":".claude/skills/agent-eval/corpus.json","rawUrl":"https://raw.githubusercontent.com/colbymchenry/codegraph/HEAD/.claude/skills/agent-eval/corpus.json","title":"Agent-eval Skill","category":"anthropic-skill","format":"json","content":"{\n  \"_comment\": \"Test corpus for /agent-eval. Add entries freely. size: Small (<~150 files), Medium (~150-1500), Large (>~1500). 'question' is a representative architectural question that exercises cross-file understanding.\",\n  \"TypeScript\": [\n    {\n      \"name\": \"ky\",\n      \"repo\": \"https://github.com/sindresorhus/ky\",\n      \"size\": \"Small\",\n      \"files\": \"~25\",\n      \"question\": \"How does ky implement request retries and timeouts?\"\n    },\n    {\n      \"name\": \"excalidraw\",\n      \"repo\": \"https://github.com/excalidraw/excalidraw\",\n      \"size\": \"Medium\",\n      \"files\": \"~600\",\n      \"question\": \"How does Excalidraw render and update canvas elements?\"\n    },\n    {\n      \"name\": \"vscode\",\n      \"repo\": \"https://github.com/microsoft/vscode\",\n      \"size\": \"Large\",\n      \"files\": \"~10000\",\n      \"question\": \"How does the extension host communicate with the main process?\"\n    }\n  ],\n  \"JavaScript\": [\n    {\n      \"name\": \"express\",\n      \"repo\": \"https://github.com/expressjs/express\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does Express route a request through its middleware stack?\"\n    }\n  ],\n  \"Go\": [\n    {\n      \"name\": \"cobra\",\n      \"repo\": \"https://github.com/spf13/cobra\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does cobra parse commands and flags?\"\n    },\n    {\n      \"name\": \"gin\",\n      \"repo\": \"https://github.com/gin-gonic/gin\",\n      \"size\": \"Medium\",\n      \"files\": \"~150\",\n      \"question\": \"How does gin route requests through its middleware chain?\"\n    },\n    {\n      \"name\": \"terraform\",\n      \"repo\": \"https://github.com/hashicorp/terraform\",\n      \"size\": \"Large\",\n      \"files\": \"~4000\",\n      \"question\": \"How does Terraform build and walk the resource dependency graph?\"\n    },\n    {\n      \"name\": \"cosmos-sdk\",\n      \"repo\": \"https://github.com/cosmos/cosmos-sdk\",\n      \"size\": \"Large\",\n      \"files\": \"~5000\",\n      \"question\": \"How does a bank module MsgSend message reach the account balance update? Trace the cross-module call path from the bank keeper's Send handler through to the account/balance store update.\"\n    }\n  ],\n  \"Python\": [\n    {\n      \"name\": \"click\",\n      \"repo\": \"https://github.com/pallets/click\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does click parse command-line arguments into commands?\"\n    },\n    {\n      \"name\": \"flask\",\n      \"repo\": \"https://github.com/pallets/flask\",\n      \"size\": \"Medium\",\n      \"files\": \"~90\",\n      \"question\": \"How does Flask dispatch a request to a view function?\"\n    },\n    {\n      \"name\": \"django\",\n      \"repo\": \"https://github.com/django/django\",\n      \"size\": \"Large\",\n      \"files\": \"~2700\",\n      \"question\": \"How does Django's ORM build and execute a query from a QuerySet?\"\n    }\n  ],\n  \"Rust\": [\n    {\n      \"name\": \"clap\",\n      \"repo\": \"https://github.com/clap-rs/clap\",\n      \"size\": \"Medium\",\n      \"files\": \"~200\",\n      \"question\": \"How does clap parse arguments against a derived command definition?\"\n    },\n    {\n      \"name\": \"tokio\",\n      \"repo\": \"https://github.com/tokio-rs/tokio\",\n      \"size\": \"Large\",\n      \"files\": \"~700\",\n      \"question\": \"How does tokio schedule and run async tasks on its runtime?\"\n    },\n    {\n      \"name\": \"deno\",\n      \"repo\": \"https://github.com/denoland/deno\",\n      \"size\": \"Large\",\n      \"files\": \"~1500\",\n      \"question\": \"How does Deno load and execute a TypeScript module?\"\n    }\n  ],\n  \"Java\": [\n    {\n      \"name\": \"gson\",\n      \"repo\": \"https://github.com/google/gson\",\n      \"size\": \"Medium\",\n      \"files\": \"~200\",\n      \"question\": \"How does Gson serialize an object to JSON?\"\n    },\n    {\n      \"name\": \"okhttp\",\n      \"repo\": \"https://github.com/square/okhttp\",\n      \"size\": \"Medium\",\n      \"files\": \"~640\",\n      \"question\": \"How does OkHttp process a request through its interceptor chain?\"\n    },\n    {\n      \"name\": \"guava\",\n      \"repo\": \"https://github.com/google/guava\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Guava's CacheBuilder build and configure a cache?\"\n    }\n  ],\n  \"Kotlin\": [\n    {\n      \"name\": \"koin\",\n      \"repo\": \"https://github.com/InsertKoinIO/koin\",\n      \"size\": \"Medium\",\n      \"files\": \"~300\",\n      \"question\": \"How does Koin resolve and inject dependencies?\"\n    },\n    {\n      \"name\": \"leakcanary\",\n      \"repo\": \"https://github.com/square/leakcanary\",\n      \"size\": \"Medium\",\n      \"files\": \"~250\",\n      \"question\": \"How does LeakCanary detect and analyze a memory leak?\"\n    }\n  ],\n  \"Swift\": [\n    {\n      \"name\": \"alamofire\",\n      \"repo\": \"https://github.com/Alamofire/Alamofire\",\n      \"size\": \"Small\",\n      \"files\": \"~100\",\n      \"question\": \"How does Alamofire build, send, and validate a request?\"\n    }\n  ],\n  \"C#\": [\n    {\n      \"name\": \"serilog\",\n      \"repo\": \"https://github.com/serilog/serilog\",\n      \"size\": \"Medium\",\n      \"files\": \"~250\",\n      \"question\": \"How does Serilog route a log event to its sinks?\"\n    },\n    {\n      \"name\": \"jellyfin\",\n      \"repo\": \"https://github.com/jellyfin/jellyfin\",\n      \"size\": \"Large\",\n      \"files\": \"~2500\",\n      \"question\": \"How does Jellyfin scan and identify items in a media library?\"\n    }\n  ],\n  \"Ruby\": [\n    {\n      \"name\": \"sinatra\",\n      \"repo\": \"https://github.com/sinatra/sinatra\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does Sinatra match a request to a route handler?\"\n    },\n    {\n      \"name\": \"discourse\",\n      \"repo\": \"https://github.com/discourse/discourse\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Discourse create and render a new post?\"\n    }\n  ],\n  \"PHP\": [\n    {\n      \"name\": \"slim\",\n      \"repo\": \"https://github.com/slimphp/Slim\",\n      \"size\": \"Small\",\n      \"files\": \"~80\",\n      \"question\": \"How does Slim handle a request through its middleware?\"\n    },\n    {\n      \"name\": \"laravel\",\n      \"repo\": \"https://github.com/laravel/framework\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Laravel resolve and dispatch a route to a controller?\"\n    }\n  ],\n  \"C\": [\n    {\n      \"name\": \"redis\",\n      \"repo\": \"https://github.com/redis/redis\",\n      \"size\": \"Large\",\n      \"files\": \"~600\",\n      \"question\": \"How does Redis parse and dispatch a client command?\"\n    }\n  ],\n  \"C++\": [\n    {\n      \"name\": \"json\",\n      \"repo\": \"https://github.com/nlohmann/json\",\n      \"size\": \"Small\",\n      \"files\": \"~100\",\n      \"question\": \"How does nlohmann::json parse a JSON string into a value?\"\n    },\n    {\n      \"name\": \"grpc\",\n      \"repo\": \"https://github.com/grpc/grpc\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does gRPC dispatch an incoming RPC to its handler?\"\n    }\n  ],\n  \"Dart\": [\n    {\n      \"name\": \"flutter\",\n      \"repo\": \"https://github.com/flutter/flutter\",\n      \"size\": \"Large\",\n      \"files\": \"~6000\",\n      \"question\": \"How does Flutter build and lay out a widget tree?\"\n    }\n  ],\n  \"Svelte\": [\n    {\n      \"name\": \"shadcn-svelte\",\n      \"repo\": \"https://github.com/huntabyte/shadcn-svelte\",\n      \"size\": \"Medium\",\n      \"files\": \"~600\",\n      \"question\": \"How do shadcn-svelte components compose and apply their styling?\"\n    }\n  ],\n  \"Lua\": [\n    {\n      \"name\": \"lualine.nvim\",\n      \"repo\": \"https://github.com/nvim-lualine/lualine.nvim\",\n      \"size\": \"Small\",\n      \"files\": \"~120\",\n      \"question\": \"How does lualine assemble and render its statusline sections and components?\"\n    },\n    {\n      \"name\": \"telescope.nvim\",\n      \"repo\": \"https://github.com/nvim-telescope/telescope.nvim\",\n      \"size\": \"Medium\",\n      \"files\": \"~80\",\n      \"question\": \"How does Telescope wire a picker to its finder, sorter, and previewer?\"\n    },\n    {\n      \"name\": \"kong\",\n      \"repo\": \"https://github.com/Kong/kong\",\n      \"size\": \"Large\",\n      \"files\": \"~1330\",\n      \"question\": \"How does Kong execute plugins across a request's lifecycle phases?\"\n    }\n  ],\n  \"Luau\": [\n    {\n      \"name\": \"Knit\",\n      \"repo\": \"https://github.com/Sleitnick/Knit\",\n      \"size\": \"Small\",\n      \"files\": \"~10\",\n      \"question\": \"How does Knit register services and expose them to clients?\"\n    },\n    {\n      \"name\": \"vide\",\n      \"repo\": \"https://github.com/centau/vide\",\n      \"size\": \"Small\",\n      \"files\": \"~40\",\n      \"question\": \"How does vide track reactive sources and re-run effects when state changes?\"\n    },\n    {\n      \"name\": \"Fusion\",\n      \"repo\": \"https://github.com/dphfox/Fusion\",\n      \"size\": \"Medium\",\n      \"files\": \"~115\",\n      \"question\": \"How does Fusion build and update its reactive UI graph from state objects?\"\n    }\n  ],\n  \"Objective-C\": [\n    {\n      \"name\": \"Masonry\",\n      \"repo\": \"https://github.com/SnapKit/Masonry\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does Masonry build and activate Auto Layout constraints from its block DSL?\"\n    },\n    {\n      \"name\": \"FMDB\",\n      \"repo\": \"https://github.com/ccgus/fmdb\",\n      \"size\": \"Medium\",\n      \"files\": \"~80\",\n      \"question\": \"How does FMDB execute a prepared SQL statement and bind parameters?\"\n    },\n    {\n      \"name\": \"SDWebImage\",\n      \"repo\": \"https://github.com/SDWebImage/SDWebImage\",\n      \"size\": \"Large\",\n      \"files\": \"~400\",\n      \"question\": \"How does SDWebImage download, cache, and decode an image for a UIImageView?\"\n    }\n  ],\n  \"Mixed iOS (Swift+ObjC)\": [\n    {\n      \"name\": \"Charts\",\n      \"repo\": \"https://github.com/danielgindi/Charts\",\n      \"size\": \"Small\",\n      \"files\": \"~270\",\n      \"question\": \"How does the ChartsDemo ObjC demo controller drive the Swift Charts library to animate and notify a data update?\"\n    },\n    {\n      \"name\": \"realm-swift\",\n      \"repo\": \"https://github.com/realm/realm-swift\",\n      \"size\": \"Medium\",\n      \"files\": \"~370\",\n      \"question\": \"How does a Swift `Realm.write { realm.add(obj) }` reach the Objective-C persistence layer?\"\n    },\n    {\n      \"name\": \"wikipedia-ios\",\n      \"repo\": \"https://github.com/wikimedia/wikipedia-ios\",\n      \"size\": \"Large\",\n      \"files\": \"~1700\",\n      \"question\": \"How does tapping a search result reach the article-fetch network call across the Swift / ObjC boundary?\"\n    }\n  ],\n  \"React Native (legacy bridge + TurboModule)\": [\n    {\n      \"name\": \"@react-native-async-storage\",\n      \"repo\": \"https://github.com/react-native-async-storage/async-storage\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does `setItem` in JS reach the native `legacy_multiSet` implementation?\"\n    },\n    {\n      \"name\": \"react-native-svg\",\n      \"repo\": \"https://github.com/software-mansion/react-native-svg\",\n      \"size\": \"Medium\",\n      \"files\": \"~700\",\n      \"question\": \"How does a JS `Svg.getTotalLength(...)` reach the iOS / Android native implementation via TurboModule?\"\n    },\n    {\n      \"name\": \"react-native-firebase\",\n      \"repo\": \"https://github.com/invertase/react-native-firebase\",\n      \"size\": \"Large\",\n      \"files\": \"~1100\",\n      \"question\": \"How does a native iOS push notification reach the JS `messaging().onMessage(...)` listener?\"\n    }\n  ],\n  \"Expo Modules\": [\n    {\n      \"name\": \"expo-haptics\",\n      \"repo\": \"https://github.com/expo/expo/tree/main/packages/expo-haptics\",\n      \"size\": \"Small\",\n      \"files\": \"~15\",\n      \"question\": \"How does `Haptics.notificationAsync(...)` in JS reach `UINotificationFeedbackGenerator` in the Swift Module?\"\n    },\n    {\n      \"name\": \"expo-camera\",\n      \"repo\": \"https://github.com/expo/expo/tree/main/packages/expo-camera\",\n      \"size\": \"Medium\",\n      \"files\": \"~70\",\n      \"question\": \"How does a JS `CameraView.takePictureAsync(options)` reach the native AVCaptureSession / CameraDevice call?\"\n    }\n  ],\n  \"React Native Fabric (view components)\": [\n    {\n      \"name\": \"react-native-segmented-control\",\n      \"repo\": \"https://github.com/react-native-segmented-control/segmented-control\",\n      \"size\": \"Small\",\n      \"files\": \"~25\",\n      \"question\": \"How does JSX `<SegmentedControl onChange={cb}/>` reach the native onChange handler on iOS/Android?\"\n    },\n    {\n      \"name\": \"react-native-screens\",\n      \"repo\": \"https://github.com/software-mansion/react-native-screens\",\n      \"size\": \"Medium\",\n      \"files\": \"~1200\",\n      \"question\": \"How does JSX `<ScreenStack>` reach the native RNSScreenStackView component?\"\n    },\n    {\n      \"name\": \"react-native-skia\",\n      \"repo\": \"https://github.com/Shopify/react-native-skia\",\n      \"size\": \"Large\",\n      \"files\": \"~1000\",\n      \"question\": \"How does a `<SkiaPictureView/>` JSX usage reach the iOS / Android native renderer?\"\n    }\n  ],\n  \"R\": [\n    {\n      \"name\": \"AnomalyDetection\",\n      \"repo\": \"https://github.com/twitter/AnomalyDetection\",\n      \"size\": \"Small\",\n      \"files\": \"~24\",\n      \"question\": \"How does AnomalyDetectionTs go from the exported entry function to the underlying S-H-ESD statistical test? Name the functions on the path in order.\"\n    },\n    {\n      \"name\": \"dplyr\",\n      \"repo\": \"https://github.com/tidyverse/dplyr\",\n      \"size\": \"Medium\",\n      \"files\": \"~450\",\n      \"question\": \"When mutate() is called on a grouped data frame, which functions handle the grouping and expression evaluation, in order, from mutate() down?\"\n    },\n    {\n      \"name\": \"ggplot2\",\n      \"repo\": \"https://github.com/tidyverse/ggplot2\",\n      \"size\": \"Large\",\n      \"files\": \"~1150\",\n      \"question\": \"When a ggplot object is printed, how does the plot actually get built and drawn — trace the path from print/plot to where geoms render. Name the key functions in order.\"\n    }\n  ],\n  \"COBOL\": [\n    {\n      \"name\": \"cics-genapp\",\n      \"repo\": \"https://github.com/cicsdev/cics-genapp\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"When a new insurance policy is added through the LGTESTP1 CICS test harness, which programs handle the request on the way to the DB2 insert? Trace the path and name the programs in order.\"\n    },\n    {\n      \"name\": \"carddemo\",\n      \"repo\": \"https://github.com/aws-samples/aws-mainframe-modernization-carddemo\",\n      \"size\": \"Medium\",\n      \"files\": \"~75\",\n      \"question\": \"How does the online bill-payment flow work — from the COBIL00C screen program to the transaction record being written? Name the paragraphs performed in order and the copybooks that define the records involved.\"\n    },\n    {\n      \"name\": \"CobolCraft\",\n      \"repo\": \"https://github.com/meyfa/CobolCraft\",\n      \"size\": \"Medium\",\n      \"files\": \"~270\",\n      \"question\": \"How does an incoming player chat message travel from packet handling to being broadcast to the other connected players? Name the programs on the path in order.\"\n    }\n  ],\n  \"VB.NET\": [\n    {\n      \"name\": \"policyplus\",\n      \"repo\": \"https://github.com/Fleex255/PolicyPlus\",\n      \"size\": \"Small\",\n      \"files\": \"~94\",\n      \"question\": \"When the user toggles a policy to Enabled in the policy-setting editor and clicks OK, how does the new state end up written into the loaded policy source (POL file or registry)? Trace the path from the EditSetting dialog to the concrete write.\"\n    },\n    {\n      \"name\": \"scrawler\",\n      \"repo\": \"https://github.com/AAndyProgram/SCrawler\",\n      \"size\": \"Medium\",\n      \"files\": \"~320\",\n      \"question\": \"When a user download is started for a Reddit user, how does the request flow from the user-level download entry point through the shared downloader base into the Reddit site plugin, and where do downloaded media items get appended to the user's content list?\"\n    },\n    {\n      \"name\": \"staxrip\",\n      \"repo\": \"https://github.com/staxrip/staxrip\",\n      \"size\": \"Medium\",\n      \"files\": \"~145\",\n      \"question\": \"When a job finishes video encoding, how does staxrip decide which muxer runs and how does the muxer command line get built and executed? Trace from job processing to the mkvmerge invocation.\"\n    }\n  ],\n  \"Erlang\": [\n    {\n      \"name\": \"cowboy\",\n      \"repo\": \"https://github.com/ninenines/cowboy\",\n      \"size\": \"Small\",\n      \"files\": \"~190\",\n      \"question\": \"How does an incoming HTTP request travel from cowboy's connection process to a user-defined handler's init/2 callback? Trace the path through the stream handler and middleware chain.\"\n    },\n    {\n      \"name\": \"ejabberd\",\n      \"repo\": \"https://github.com/processone/ejabberd\",\n      \"size\": \"Medium\",\n      \"files\": \"~410\",\n      \"question\": \"When a client sends a chat message, how does the stanza get from the receiving C2S process to the recipient's session on the same node? Trace the path through the router and session manager.\"\n    },\n    {\n      \"name\": \"emqx\",\n      \"repo\": \"https://github.com/emqx/emqx\",\n      \"size\": \"Large\",\n      \"files\": \"~2450\",\n      \"question\": \"How does a PUBLISH packet from an MQTT client reach the sessions of matching subscribers? Trace the flow from the connection/channel layer through the broker's routing to session delivery.\"\n    }\n  ],\n  \"Solidity\": [\n    {\n      \"name\": \"solmate\",\n      \"repo\": \"https://github.com/transmissions11/solmate\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does solmate's ERC20 transferFrom enforce allowance and update balances? Trace the flow including the permit() signature path.\"\n    },\n    {\n      \"name\": \"solady\",\n      \"repo\": \"https://github.com/Vectorized/solady\",\n      \"size\": \"Medium\",\n      \"files\": \"~270\",\n      \"question\": \"How does solady's ERC20 implementation handle a permit() call — from signature recovery through nonce update to allowance write?\"\n    },\n    {\n      \"name\": \"openzeppelin-contracts\",\n      \"repo\": \"https://github.com/OpenZeppelin/openzeppelin-contracts\",\n      \"size\": \"Large\",\n      \"files\": \"~400\",\n      \"question\": \"How does an OpenZeppelin AccessControl-protected function check the caller's role? Trace from the onlyRole modifier through hasRole to the role storage.\"\n    }\n  ],\n  \"CUDA\": [\n    {\n      \"name\": \"llm.c\",\n      \"repo\": \"https://github.com/karpathy/llm.c\",\n      \"size\": \"Small\",\n      \"files\": \"~76\",\n      \"question\": \"How does the attention forward pass reach the GPU in the CUDA training path? Trace from attention_forward to the kernels it launches, and explain where softmax happens.\"\n    },\n    {\n      \"name\": \"flash-attention\",\n      \"repo\": \"https://github.com/Dao-AILab/flash-attention\",\n      \"size\": \"Medium\",\n      \"files\": \"~900\",\n      \"question\": \"How does a Python call to flash_attn_func reach the CUDA kernel that computes forward attention? Trace the dispatch path from the Python API through the C++ binding to the kernel launch.\"\n    },\n    {\n      \"name\": \"cutlass\",\n      \"repo\": \"https://github.com/NVIDIA/cutlass\",\n      \"size\": \"Large\",\n      \"files\": \"~2700\",\n      \"question\": \"When a cutlass device-level GEMM (cutlass::gemm::device::Gemm) is invoked, how does it reach the GPU kernel? Trace from the operator() call to the kernel entry point and its launch site.\"\n    }\n  ],\n  \"Terraform\": [\n    {\n      \"name\": \"terraform-aws-vpc\",\n      \"repo\": \"https://github.com/terraform-aws-modules/terraform-aws-vpc\",\n      \"size\": \"Small\",\n      \"files\": \"~77\",\n      \"question\": \"How does the private_subnets variable shape the NAT gateway setup? Trace from the variable through the subnet and NAT gateway resources to the outputs that expose the private subnets.\"\n    },\n    {\n      \"name\": \"cloud-foundation-fabric\",\n      \"repo\": \"https://github.com/GoogleCloudPlatform/cloud-foundation-fabric\",\n      \"size\": \"Medium\",\n      \"files\": \"~990\",\n      \"question\": \"In the project module (modules/project), how does the iam variable turn into actual IAM bindings on the project, and what depends on the module's project_id output elsewhere in the repo?\"\n    },\n    {\n      \"name\": \"terraform-aws-components\",\n      \"repo\": \"https://github.com/cloudposse/terraform-aws-components\",\n      \"size\": \"Large\",\n      \"files\": \"~1800\",\n      \"question\": \"In the eks/cluster component, how does the cluster IAM role get created and reach the EKS cluster resource, and which outputs expose cluster identity to other components?\"\n    }\n  ],\n  \"ArkTS\": [\n    {\n      \"name\": \"HarmoneyOpenEye\",\n      \"repo\": \"https://github.com/WinWang/HarmoneyOpenEye\",\n      \"size\": \"Small\",\n      \"files\": \"~82\",\n      \"question\": \"How does the home page get its feed data from the network layer, and how does that data end up rendered as the list on screen? Trace the flow from the HTTP request through the view model into the home page UI.\"\n    },\n    {\n      \"name\": \"CoolMallArkTS\",\n      \"repo\": \"https://github.com/Joker-x-dev/CoolMallArkTS\",\n      \"size\": \"Medium\",\n      \"files\": \"~528\",\n      \"question\": \"When the user adds a product to the cart from the goods detail page, how does the item travel from the UI action to persistent storage? Trace the flow across the feature and core modules.\"\n    },\n    {\n      \"name\": \"applications_app_samples\",\n      \"repo\": \"https://github.com/openharmony/applications_app_samples\",\n      \"size\": \"Large\",\n      \"files\": \"~9500\",\n      \"question\": \"In the OrangeShopping sample app, how does the product detail page's bottom bar (add to cart / buy) lead to the order placement flow? Trace from the bottom navigation component to where the order is created.\"\n    }\n  ],\n  \"Nix\": [\n    {\n      \"name\": \"agenix\",\n      \"repo\": \"https://github.com/ryantm/agenix\",\n      \"size\": \"Small\",\n      \"files\": \"~13\",\n      \"question\": \"When a NixOS system activates, how does a secret declared under age.secrets get decrypted and installed at its runtime path? Trace the flow from the age.secrets option definition to the activation machinery that performs the decryption.\"\n    },\n    {\n      \"name\": \"nix-darwin\",\n      \"repo\": \"https://github.com/nix-darwin/nix-darwin\",\n      \"size\": \"Medium\",\n      \"files\": \"~207\",\n      \"question\": \"How does setting services.yabai.enable = true become a running launchd service? Trace the flow from the yabai module's options to the launchd daemon definition that generates the plist.\"\n    },\n    {\n      \"name\": \"home-manager\",\n      \"repo\": \"https://github.com/nix-community/home-manager\",\n      \"size\": \"Large\",\n      \"files\": \"~3390\",\n      \"question\": \"How does programs.git.enable produce the final git config file in the user's home directory? Trace the flow from the git program module to the home-files machinery that links generated files into place.\"\n    }\n  ]\n}\n","frontmatter":{"_comment":"Test corpus for /agent-eval. Add entries freely. size: Small (<~150 files), Medium (~150-1500), Large (>~1500). 'question' is a representative architectural question that exercises cross-file understanding.","TypeScript":[{"name":"ky","repo":"https://github.com/sindresorhus/ky","size":"Small","files":"~25","question":"How does ky implement request retries and timeouts?"},{"name":"excalidraw","repo":"https://github.com/excalidraw/excalidraw","size":"Medium","files":"~600","question":"How does Excalidraw render and update canvas elements?"},{"name":"vscode","repo":"https://github.com/microsoft/vscode","size":"Large","files":"~10000","question":"How does the extension host communicate with the main process?"}],"JavaScript":[{"name":"express","repo":"https://github.com/expressjs/express","size":"Small","files":"~50","question":"How does Express route a request through its middleware stack?"}],"Go":[{"name":"cobra","repo":"https://github.com/spf13/cobra","size":"Small","files":"~50","question":"How does cobra parse commands and flags?"},{"name":"gin","repo":"https://github.com/gin-gonic/gin","size":"Medium","files":"~150","question":"How does gin route requests through its middleware chain?"},{"name":"terraform","repo":"https://github.com/hashicorp/terraform","size":"Large","files":"~4000","question":"How does Terraform build and walk the resource dependency graph?"},{"name":"cosmos-sdk","repo":"https://github.com/cosmos/cosmos-sdk","size":"Large","files":"~5000","question":"How does a bank module MsgSend message reach the account balance update? Trace the cross-module call path from the bank keeper's Send handler through to the account/balance store update."}],"Python":[{"name":"click","repo":"https://github.com/pallets/click","size":"Small","files":"~60","question":"How does click parse command-line arguments into commands?"},{"name":"flask","repo":"https://github.com/pallets/flask","size":"Medium","files":"~90","question":"How does Flask dispatch a request to a view function?"},{"name":"django","repo":"https://github.com/django/django","size":"Large","files":"~2700","question":"How does Django's ORM build and execute a query from a QuerySet?"}],"Rust":[{"name":"clap","repo":"https://github.com/clap-rs/clap","size":"Medium","files":"~200","question":"How does clap parse arguments against a derived command definition?"},{"name":"tokio","repo":"https://github.com/tokio-rs/tokio","size":"Large","files":"~700","question":"How does tokio schedule and run async tasks on its runtime?"},{"name":"deno","repo":"https://github.com/denoland/deno","size":"Large","files":"~1500","question":"How does Deno load and execute a TypeScript module?"}],"Java":[{"name":"gson","repo":"https://github.com/google/gson","size":"Medium","files":"~200","question":"How does Gson serialize an object to JSON?"},{"name":"okhttp","repo":"https://github.com/square/okhttp","size":"Medium","files":"~640","question":"How does OkHttp process a request through its interceptor chain?"},{"name":"guava","repo":"https://github.com/google/guava","size":"Large","files":"~3000","question":"How does Guava's CacheBuilder build and configure a cache?"}],"Kotlin":[{"name":"koin","repo":"https://github.com/InsertKoinIO/koin","size":"Medium","files":"~300","question":"How does Koin resolve and inject dependencies?"},{"name":"leakcanary","repo":"https://github.com/square/leakcanary","size":"Medium","files":"~250","question":"How does LeakCanary detect and analyze a memory leak?"}],"Swift":[{"name":"alamofire","repo":"https://github.com/Alamofire/Alamofire","size":"Small","files":"~100","question":"How does Alamofire build, send, and validate a request?"}],"C#":[{"name":"serilog","repo":"https://github.com/serilog/serilog","size":"Medium","files":"~250","question":"How does Serilog route a log event to its sinks?"},{"name":"jellyfin","repo":"https://github.com/jellyfin/jellyfin","size":"Large","files":"~2500","question":"How does Jellyfin scan and identify items in a media library?"}],"Ruby":[{"name":"sinatra","repo":"https://github.com/sinatra/sinatra","size":"Small","files":"~60","question":"How does Sinatra match a request to a route handler?"},{"name":"discourse","repo":"https://github.com/discourse/discourse","size":"Large","files":"~3000","question":"How does Discourse create and render a new post?"}],"PHP":[{"name":"slim","repo":"https://github.com/slimphp/Slim","size":"Small","files":"~80","question":"How does Slim handle a request through its middleware?"},{"name":"laravel","repo":"https://github.com/laravel/framework","size":"Large","files":"~3000","question":"How does Laravel resolve and dispatch a route to a controller?"}],"C":[{"name":"redis","repo":"https://github.com/redis/redis","size":"Large","files":"~600","question":"How does Redis parse and dispatch a client command?"}],"C++":[{"name":"json","repo":"https://github.com/nlohmann/json","size":"Small","files":"~100","question":"How does nlohmann::json parse a JSON string into a value?"},{"name":"grpc","repo":"https://github.com/grpc/grpc","size":"Large","files":"~3000","question":"How does gRPC dispatch an incoming RPC to its handler?"}],"Dart":[{"name":"flutter","repo":"https://github.com/flutter/flutter","size":"Large","files":"~6000","question":"How does Flutter build and lay out a widget tree?"}],"Svelte":[{"name":"shadcn-svelte","repo":"https://github.com/huntabyte/shadcn-svelte","size":"Medium","files":"~600","question":"How do shadcn-svelte components compose and apply their styling?"}],"Lua":[{"name":"lualine.nvim","repo":"https://github.com/nvim-lualine/lualine.nvim","size":"Small","files":"~120","question":"How does lualine assemble and render its statusline sections and components?"},{"name":"telescope.nvim","repo":"https://github.com/nvim-telescope/telescope.nvim","size":"Medium","files":"~80","question":"How does Telescope wire a picker to its finder, sorter, and previewer?"},{"name":"kong","repo":"https://github.com/Kong/kong","size":"Large","files":"~1330","question":"How does Kong execute plugins across a request's lifecycle phases?"}],"Luau":[{"name":"Knit","repo":"https://github.com/Sleitnick/Knit","size":"Small","files":"~10","question":"How does Knit register services and expose them to clients?"},{"name":"vide","repo":"https://github.com/centau/vide","size":"Small","files":"~40","question":"How does vide track reactive sources and re-run effects when state changes?"},{"name":"Fusion","repo":"https://github.com/dphfox/Fusion","size":"Medium","files":"~115","question":"How does Fusion build and update its reactive UI graph from state objects?"}],"Objective-C":[{"name":"Masonry","repo":"https://github.com/SnapKit/Masonry","size":"Small","files":"~50","question":"How does Masonry build and activate Auto Layout constraints from its block DSL?"},{"name":"FMDB","repo":"https://github.com/ccgus/fmdb","size":"Medium","files":"~80","question":"How does FMDB execute a prepared SQL statement and bind parameters?"},{"name":"SDWebImage","repo":"https://github.com/SDWebImage/SDWebImage","size":"Large","files":"~400","question":"How does SDWebImage download, cache, and decode an image for a UIImageView?"}],"Mixed iOS (Swift+ObjC)":[{"name":"Charts","repo":"https://github.com/danielgindi/Charts","size":"Small","files":"~270","question":"How does the ChartsDemo ObjC demo controller drive the Swift Charts library to animate and notify a data update?"},{"name":"realm-swift","repo":"https://github.com/realm/realm-swift","size":"Medium","files":"~370","question":"How does a Swift `Realm.write { realm.add(obj) }` reach the Objective-C persistence layer?"},{"name":"wikipedia-ios","repo":"https://github.com/wikimedia/wikipedia-ios","size":"Large","files":"~1700","question":"How does tapping a search result reach the article-fetch network call across the Swift / ObjC boundary?"}],"React Native (legacy bridge + TurboModule)":[{"name":"@react-native-async-storage","repo":"https://github.com/react-native-async-storage/async-storage","size":"Small","files":"~60","question":"How does `setItem` in JS reach the native `legacy_multiSet` implementation?"},{"name":"react-native-svg","repo":"https://github.com/software-mansion/react-native-svg","size":"Medium","files":"~700","question":"How does a JS `Svg.getTotalLength(...)` reach the iOS / Android native implementation via TurboModule?"},{"name":"react-native-firebase","repo":"https://github.com/invertase/react-native-firebase","size":"Large","files":"~1100","question":"How does a native iOS push notification reach the JS `messaging().onMessage(...)` listener?"}],"Expo Modules":[{"name":"expo-haptics","repo":"https://github.com/expo/expo/tree/main/packages/expo-haptics","size":"Small","files":"~15","question":"How does `Haptics.notificationAsync(...)` in JS reach `UINotificationFeedbackGenerator` in the Swift Module?"},{"name":"expo-camera","repo":"https://github.com/expo/expo/tree/main/packages/expo-camera","size":"Medium","files":"~70","question":"How does a JS `CameraView.takePictureAsync(options)` reach the native AVCaptureSession / CameraDevice call?"}],"React Native Fabric (view components)":[{"name":"react-native-segmented-control","repo":"https://github.com/react-native-segmented-control/segmented-control","size":"Small","files":"~25","question":"How does JSX `<SegmentedControl onChange={cb}/>` reach the native onChange handler on iOS/Android?"},{"name":"react-native-screens","repo":"https://github.com/software-mansion/react-native-screens","size":"Medium","files":"~1200","question":"How does JSX `<ScreenStack>` reach the native RNSScreenStackView component?"},{"name":"react-native-skia","repo":"https://github.com/Shopify/react-native-skia","size":"Large","files":"~1000","question":"How does a `<SkiaPictureView/>` JSX usage reach the iOS / Android native renderer?"}],"R":[{"name":"AnomalyDetection","repo":"https://github.com/twitter/AnomalyDetection","size":"Small","files":"~24","question":"How does AnomalyDetectionTs go from the exported entry function to the underlying S-H-ESD statistical test? Name the functions on the path in order."},{"name":"dplyr","repo":"https://github.com/tidyverse/dplyr","size":"Medium","files":"~450","question":"When mutate() is called on a grouped data frame, which functions handle the grouping and expression evaluation, in order, from mutate() down?"},{"name":"ggplot2","repo":"https://github.com/tidyverse/ggplot2","size":"Large","files":"~1150","question":"When a ggplot object is printed, how does the plot actually get built and drawn — trace the path from print/plot to where geoms render. Name the key functions in order."}],"COBOL":[{"name":"cics-genapp","repo":"https://github.com/cicsdev/cics-genapp","size":"Small","files":"~60","question":"When a new insurance policy is added through the LGTESTP1 CICS test harness, which programs handle the request on the way to the DB2 insert? Trace the path and name the programs in order."},{"name":"carddemo","repo":"https://github.com/aws-samples/aws-mainframe-modernization-carddemo","size":"Medium","files":"~75","question":"How does the online bill-payment flow work — from the COBIL00C screen program to the transaction record being written? Name the paragraphs performed in order and the copybooks that define the records involved."},{"name":"CobolCraft","repo":"https://github.com/meyfa/CobolCraft","size":"Medium","files":"~270","question":"How does an incoming player chat message travel from packet handling to being broadcast to the other connected players? Name the programs on the path in order."}],"VB.NET":[{"name":"policyplus","repo":"https://github.com/Fleex255/PolicyPlus","size":"Small","files":"~94","question":"When the user toggles a policy to Enabled in the policy-setting editor and clicks OK, how does the new state end up written into the loaded policy source (POL file or registry)? Trace the path from the EditSetting dialog to the concrete write."},{"name":"scrawler","repo":"https://github.com/AAndyProgram/SCrawler","size":"Medium","files":"~320","question":"When a user download is started for a Reddit user, how does the request flow from the user-level download entry point through the shared downloader base into the Reddit site plugin, and where do downloaded media items get appended to the user's content list?"},{"name":"staxrip","repo":"https://github.com/staxrip/staxrip","size":"Medium","files":"~145","question":"When a job finishes video encoding, how does staxrip decide which muxer runs and how does the muxer command line get built and executed? Trace from job processing to the mkvmerge invocation."}],"Erlang":[{"name":"cowboy","repo":"https://github.com/ninenines/cowboy","size":"Small","files":"~190","question":"How does an incoming HTTP request travel from cowboy's connection process to a user-defined handler's init/2 callback? Trace the path through the stream handler and middleware chain."},{"name":"ejabberd","repo":"https://github.com/processone/ejabberd","size":"Medium","files":"~410","question":"When a client sends a chat message, how does the stanza get from the receiving C2S process to the recipient's session on the same node? Trace the path through the router and session manager."},{"name":"emqx","repo":"https://github.com/emqx/emqx","size":"Large","files":"~2450","question":"How does a PUBLISH packet from an MQTT client reach the sessions of matching subscribers? Trace the flow from the connection/channel layer through the broker's routing to session delivery."}],"Solidity":[{"name":"solmate","repo":"https://github.com/transmissions11/solmate","size":"Small","files":"~60","question":"How does solmate's ERC20 transferFrom enforce allowance and update balances? Trace the flow including the permit() signature path."},{"name":"solady","repo":"https://github.com/Vectorized/solady","size":"Medium","files":"~270","question":"How does solady's ERC20 implementation handle a permit() call — from signature recovery through nonce update to allowance write?"},{"name":"openzeppelin-contracts","repo":"https://github.com/OpenZeppelin/openzeppelin-contracts","size":"Large","files":"~400","question":"How does an OpenZeppelin AccessControl-protected function check the caller's role? Trace from the onlyRole modifier through hasRole to the role storage."}],"CUDA":[{"name":"llm.c","repo":"https://github.com/karpathy/llm.c","size":"Small","files":"~76","question":"How does the attention forward pass reach the GPU in the CUDA training path? Trace from attention_forward to the kernels it launches, and explain where softmax happens."},{"name":"flash-attention","repo":"https://github.com/Dao-AILab/flash-attention","size":"Medium","files":"~900","question":"How does a Python call to flash_attn_func reach the CUDA kernel that computes forward attention? Trace the dispatch path from the Python API through the C++ binding to the kernel launch."},{"name":"cutlass","repo":"https://github.com/NVIDIA/cutlass","size":"Large","files":"~2700","question":"When a cutlass device-level GEMM (cutlass::gemm::device::Gemm) is invoked, how does it reach the GPU kernel? Trace from the operator() call to the kernel entry point and its launch site."}],"Terraform":[{"name":"terraform-aws-vpc","repo":"https://github.com/terraform-aws-modules/terraform-aws-vpc","size":"Small","files":"~77","question":"How does the private_subnets variable shape the NAT gateway setup? Trace from the variable through the subnet and NAT gateway resources to the outputs that expose the private subnets."},{"name":"cloud-foundation-fabric","repo":"https://github.com/GoogleCloudPlatform/cloud-foundation-fabric","size":"Medium","files":"~990","question":"In the project module (modules/project), how does the iam variable turn into actual IAM bindings on the project, and what depends on the module's project_id output elsewhere in the repo?"},{"name":"terraform-aws-components","repo":"https://github.com/cloudposse/terraform-aws-components","size":"Large","files":"~1800","question":"In the eks/cluster component, how does the cluster IAM role get created and reach the EKS cluster resource, and which outputs expose cluster identity to other components?"}],"ArkTS":[{"name":"HarmoneyOpenEye","repo":"https://github.com/WinWang/HarmoneyOpenEye","size":"Small","files":"~82","question":"How does the home page get its feed data from the network layer, and how does that data end up rendered as the list on screen? Trace the flow from the HTTP request through the view model into the home page UI."},{"name":"CoolMallArkTS","repo":"https://github.com/Joker-x-dev/CoolMallArkTS","size":"Medium","files":"~528","question":"When the user adds a product to the cart from the goods detail page, how does the item travel from the UI action to persistent storage? Trace the flow across the feature and core modules."},{"name":"applications_app_samples","repo":"https://github.com/openharmony/applications_app_samples","size":"Large","files":"~9500","question":"In the OrangeShopping sample app, how does the product detail page's bottom bar (add to cart / buy) lead to the order placement flow? Trace from the bottom navigation component to where the order is created."}],"Nix":[{"name":"agenix","repo":"https://github.com/ryantm/agenix","size":"Small","files":"~13","question":"When a NixOS system activates, how does a secret declared under age.secrets get decrypted and installed at its runtime path? Trace the flow from the age.secrets option definition to the activation machinery that performs the decryption."},{"name":"nix-darwin","repo":"https://github.com/nix-darwin/nix-darwin","size":"Medium","files":"~207","question":"How does setting services.yabai.enable = true become a running launchd service? Trace the flow from the yabai module's options to the launchd daemon definition that generates the plist."},{"name":"home-manager","repo":"https://github.com/nix-community/home-manager","size":"Large","files":"~3390","question":"How does programs.git.enable produce the final git config file in the user's home directory? Trace the flow from the git program module to the home-files machinery that links generated files into place."}]},"isInternal":false,"tokens":5825,"sizeBytes":22255},{"name":"SKILL.md","path":".claude/skills/agent-eval/SKILL.md","rawUrl":"https://raw.githubusercontent.com/colbymchenry/codegraph/HEAD/.claude/skills/agent-eval/SKILL.md","title":"agent-eval","category":"anthropic-skill","format":"markdown","content":"---\nname: agent-eval\ndescription: Benchmark CodeGraph retrieval quality on a real codebase by comparing agent behavior with vs without CodeGraph. Use when the user runs /agent-eval or asks to test, benchmark, audit, or validate a codegraph version (the local dev build or a published npm version) against a language's repo.\n---\n\n# CodeGraph Quality Audit\n\nMeasures how much CodeGraph helps an agent versus plain grep/read, for a chosen\ncodegraph version on a chosen real-world repo. Drives the harness in\n`scripts/agent-eval/`.\n\n## Prerequisites\n- `tmux` 3+, a logged-in `claude` CLI, `node`, `git` (macOS/Linux).\n- Run from the codegraph repo root.\n\n## Workflow\n\nCopy this checklist:\n```\n- [ ] 1. Pick version (local or npm)\n- [ ] 2. Pick language\n- [ ] 3. Pick repo by size\n- [ ] 4. Pick harness (headless / tmux / both)\n- [ ] 5. Run audit.sh in the background\n- [ ] 6. Report results\n```\n\n**Step 1 — version.** Ask with `AskUserQuestion`: which codegraph version to test.\nOffer \"Local dev build\" and \"Latest published\"; the free-text \"Other\" lets the\nuser type a specific version (e.g. `0.7.10`). Map the answer to a VERSION token:\n- \"Local dev build\" → `local`\n- \"Latest published\" → `latest`\n- a typed version → that string (e.g. `0.7.10`)\n\n**Step 2 — language.** Read `.claude/skills/agent-eval/corpus.json`. Ask with\n`AskUserQuestion` which language to test, listing the languages that have entries.\n\n**Step 3 — repo.** From the chosen language's entries, ask which repo. Label each\noption with its size and file count, e.g. `excalidraw — Medium (~600 files)`.\nEach entry carries the `repo` URL and a representative `question`.\n\n**Step 4 — harness.** Ask with `AskUserQuestion` which harness to run, and map\nthe answer to a MODE token:\n- \"Headless\" → `headless` — `claude -p` with stream-json: exact tokens/cost and a\n  clean tool sequence (2 runs, fast, no TTY).\n- \"Interactive (tmux)\" → `tmux` — drives the real Claude TUI in tmux: faithful\n  Explore-subagent behavior, metrics from session logs (2 runs, slower).\n- \"Both\" → `all` — headless + interactive (4 runs).\n\n**Step 5 — run.** Launch in the background (sets the version, clones if missing,\nwipes + re-indexes, runs the chosen arms — several minutes):\n```bash\nscripts/agent-eval/audit.sh <VERSION> <repo-name> <repo-url> \"<question>\" <MODE>\n```\n\n**Step 6 — report.** When the job finishes, read the log and report per arm:\n- Headless (`parse-run.mjs`): total tool calls, file `Read`s, Grep/Bash,\n  codegraph-tool calls, duration, **total cost**.\n- Interactive (`parse-session.mjs`): the `VERDICT: codegraph_explore used Nx |\n  Read N | Grep/Bash N` and `TOKENS:` lines.\n- Both paths also print the three feedback metrics — residual context occupancy,\n  explore sufficiency, allocation efficiency — and a headless A/B ends with a\n  side-by-side `ARM COMPARISON` table. Report that table, and check its\n  contamination row first: `CLI calls that RETURNED output` > 0 means the arm\n  reached codegraph through Bash and its numbers are void. How to read the rest:\n  `docs/benchmarks/agent-eval-feedback-metrics.md`.\n\nLead with cost + tool/Read counts — they are the reliable signals; raw token\nin/out are confounded by subagent delegation and prompt caching. State whether\ncodegraph reduced effort and whether both arms reached a correct answer.\n\n## Notes\n- The index is rebuilt every run (`audit.sh` wipes `.codegraph`) — different\n  versions extract differently, so an index must be served by the same binary\n  that built it.\n- `audit.sh` temporarily mutates the global `codegraph` install for the test,\n  then restores your dev link via `local-install.sh`.\n- Corpus repos are cloned to `/tmp/codegraph-corpus` (reused if already present).\n- Add or edit repos in `corpus.json` (fields: `name`, `repo`, `size`, `files`,\n  `question`).\n","frontmatter":{"name":"agent-eval","description":"Benchmark CodeGraph retrieval quality on a real codebase by comparing agent behavior with vs without CodeGraph. Use when the user runs /agent-eval or asks to test, benchmark, audit, or validate a codegraph version (the local dev build or a published npm version) against a language's repo."},"isInternal":false,"tokens":1019,"sizeBytes":3840},{"name":"codegraph.mdc","path":".cursor/rules/codegraph.mdc","rawUrl":"https://raw.githubusercontent.com/colbymchenry/codegraph/HEAD/.cursor/rules/codegraph.mdc","title":"Cursor Rule: codegraph","category":"cursor-rule","format":"mdc","content":"---\ndescription: CodeGraph MCP usage guide — one tool, codegraph_explore\nalwaysApply: true\n---\n<!-- CODEGRAPH_START -->\n## CodeGraph\n\nThis project has a CodeGraph MCP server configured, exposing a single tool: `codegraph_explore`. CodeGraph is a tree-sitter-parsed knowledge graph of every symbol, edge, and file. Reads are sub-millisecond and return structural information grep cannot.\n\n### Use codegraph_explore instead of reading files\n\nReach for `codegraph_explore` before grep/find or Read for any **structural** question — how does X work, how does X reach Y, what calls what, where is X defined, or surveying an area. It takes a natural-language question or a bag of symbol/file names and returns the relevant symbols' **verbatim, line-numbered source** grouped by file (the same `<n>\\t<line>` shape Read gives you, safe to Edit from), plus the call paths between them — including dynamic-dispatch hops (callbacks, React re-render, JSX children) grep can't follow — and a blast-radius summary of what depends on them. Name a file or symbol in the query to read its current source.\n\n### Rules of thumb\n\n- **Answer directly — don't delegate exploration.** ONE `codegraph_explore` usually answers the whole question; follow up with another `codegraph_explore` naming more specific symbols if you need more. Codegraph IS the pre-built index, so spawning a separate file-reading sub-task/agent — or running a grep + read loop — repeats work codegraph already did and costs more for the same answer.\n- **Trust codegraph results.** They come from a full AST parse. Do NOT re-verify them with grep — that's slower, less accurate, and wastes context.\n- **Don't grep or Read first** to find or understand indexed code — one `codegraph_explore` returns the relevant source in a single round-trip. Reach for raw Read/Grep only to confirm a specific detail codegraph didn't cover, or for what it doesn't index (configs, docs).\n- **Index lag — check the staleness banner, don't guess a wait.** When a codegraph response starts with \"⚠️ Some files referenced below were edited since the last index sync…\", the listed files are pending re-index — Read those specific files for accurate content. Files NOT in that banner are fresh and codegraph is authoritative for them.\n\n### If `.codegraph/` doesn't exist\n\nThe MCP server returns \"not initialized.\" Ask the user: *\"I notice this project doesn't have CodeGraph initialized. Want me to run `codegraph init -i` to build the index?\"*\n<!-- CODEGRAPH_END -->\n","frontmatter":{"description":"CodeGraph MCP usage guide — one tool, codegraph_explore","alwaysApply":true},"isInternal":false,"tokens":565,"sizeBytes":2523},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/colbymchenry/codegraph/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nClaude Code project guidance for this repository.\n\nPrimary instructions live in the canonical agent guide — import it:\n\n@AGENTS.md\n\n## Claude-only notes\n\n- Prefer the repo-root `AGENTS.md` as the source of truth for build/test/architecture/house rules. Edit that file (not this wrapper) when guidance changes.\n- Claude Code can `@`-import nested guides too (e.g. `@docs/AGENTS.md`) when working on design/eval docs; Codex loads nested `AGENTS.md` automatically when the session cwd is under that directory.\n- Do not reintroduce a duplicated `## CodeGraph` MCP tool-guidance block here — `src/mcp/server-instructions.ts` is the single source of truth (issue #529); the installer strips legacy marker blocks on upgrade.\n","isInternal":false,"tokens":172,"sizeBytes":735}],"systemPromptSnippet":"<yakaai_skills repo=\"colbymchenry/codegraph\">\n<!-- File: AGENTS.md (Tokens: ~9432 | Category: root-instruction) -->\n# AGENTS.md\n\nCanonical project guidance for coding agents working in this repository (Codex/Astra, Claude Code via `@AGENTS.md`, Cursor, etc.).\n\n**Codex size note:** root `AGENTS.md` is intentionally kept near ~35 KiB (critical build/test/arch/retrieval rules retained). Longer validation methodology + the Excalidraw worked example live in `docs/AGENTS.md`. This environment sets `project_doc_max_bytes = 49152` so root (and root+`docs/` when cwd is under `docs/`) are not silently truncated at the 32 KiB default.\n\n## Project Overview\n\nCodeGraph is a local-first code intelligence library + CLI + MCP server. It parses any supported codebase with tree-sitter, stores symbols/edges/files in SQLite (FTS5), and exposes a knowledge graph to AI agents (Claude Code, Cursor, Codex CLI, opencode) over MCP. Per-project data lives in `.codegraph/`. Extraction is deterministic — derived from AST, not LLM-summarized.\n\nDistributed as `@colbymchenry/codegraph` on npm; same binary serves as installer, indexer, and MCP server.\n\n## Build, Test, Run\n\n```bash\nnpm run build           # tsc + copy schema.sql and *.wasm + build the viewer into dist/; chmods dist/bin/codegraph.js\nnpm run build:lib       # the viewer's components as @colbymchenry/codegraph-ui (ui/dist) — NOT part of `build`\nnpm run dev             # tsc --watch\nnpm run clean           # rm -rf dist\n\nnpm test                # vitest run (all)\nnpm run test:watch\nnpm run test:eval       # only __tests__/evaluation/\nnpm run eval            # build then run __tests__/evaluation/runner.ts via tsx\n\nnpm run cli             # build then run the local dist binary\n\n# Single test file / pattern\nnpx vitest run __tests__/installer-targets.test.ts\nnpx vitest run __tests__/extraction.test.ts -t \"TypeScript\"\n```\n\n`copy-assets` (called from `build`) copies `src/db/schema.sql` and all `src/extraction/wasm/*.wasm` files into `dist/`. **Any new SQL or grammar wasm must be copied or it won't ship.**\n\nOne other build step writes into `dist/` and is subject to the same rule: `build:ui` builds the\nbrowser viewer into `dist/viewer/` (never `dist/ui/` — that's the terminal ui).\n`scripts/check-ui-build.mjs` asserts both `dist/viewer/` and the copied grammars in\n`dist/extraction/wasm/` after every build and inside every release archive — the viewer's syntax\nhighlighting reads a file with the same grammar the engine indexed it with, so a missing wasm is an\nunhighlighted screen as well as an extraction gap.\n\n`npm run build:lib` is separate and does NOT run as part of `npm run build`: it compiles the same\n`ui/src` tree a second way, with `svelte-package`, into `ui/dist` — the `@colbymchenry/codegraph-ui`\ncomponent library the Pro app imports (task CG-61). `scripts/check-ui-package.mjs` then prunes the\nstandalone app's shell out of it, resolves the extensionless import specifiers `svelte-package`\nleaves behind, and asserts the seam: nothing outside `lib/adapter.js` may reach the network. The\npackage is **prepared, not published** — `ui/package.json` carries `\"private\": true` deliberately,\nand `scripts/pack-npm.sh` only packs a tarball when `CODEGRAPH_PACK_UI=1`.\n\nTests run as **two vitest projects** (`vitest.workspace.mts`): `engine` (node) and `ui` (jsdom, the\nSvelte plugin, `resolve.conditions: ['browser']`) for the single `__tests__/ui-package.test.ts`.\n`npm test` still runs both. The split is not cosmetic — `browser` is a package-resolution\ncondition, and applied globally it hands the engine's suites the browser builds of\n`web-tree-sitter` and friends. The root config (`vitest.config.mts`, `.mts` because the plugin is\nESM-only and the repo is CJS) is the shared base; note that a workspace project **concatenates**\nthe base's `include` with its own, which is why the `ui` project does not `extends` it.\n\nNode engines: `>=20.0.0 <25.0.0`. There is a hard exit on Node 25.x and below 20 (see `src/bin/node-version-check.ts`).\n\n## Architecture\n\n### Layered pipeline\n\n```\nfiles → ExtractionOrchestrator (tree-sitter) → DB (nodes/edges/files)\n              ↓\n       ReferenceResolver (imports, name-matching, framework patterns)\n              ↓\n       GraphQueryManager / GraphTraverser (callers, callees, impact)\n              ↓\n       ContextBuilder (markdown/JSON for AI consumption)\n```\n\nThe public API surface is `src/index.ts` — the `CodeGraph` class wires all the layers and re-exports types. Library users only touch this file; the MCP server and CLI also drive it.\n\n### Module layout\n\n- `src/index.ts` — `CodeGraph` class: `init`/`open`/`close`, `indexAll`, `sync`, `searchNodes`, `getCallers`/`getCallees`, `getImpactRadius`, `buildContext`, `watch`/`unwatch`.\n- `src/db/` — `DatabaseConnection`, `QueryBuilder` (prepared statements), `schema.sql`, `sqlite-adapter.ts`. Backed by Node's built-in **`node:sqlite`** (`DatabaseSync`) — real SQLite with WAL + FTS5, exposed through a thin better-sqlite3-shaped adapter. The bundled runtime always ships Node ≥22.5, so `node:sqlite` is always available: **no native build step and no wasm fallback**. (Running from source needs Node ≥22.5.) `codegraph status` reports the live backend (`node-sqlite`, the sole backend).\n- `src/extraction/` — `ExtractionOrchestrator`, tree-sitter wrappers, per-language extractors under `languages/` (one file per language), plus standalone extractors for non-tree-sitter formats (`svelte-extractor.ts`, `vue-extractor.ts`, `liquid-extractor.ts`, `dfm-extractor.ts` for Delphi). `parse-worker.ts` runs heavy parsing off the main thread.\n- `src/resolution/` — `ReferenceResolver` orchestrates `import-resolver.ts` (with `path-aliases.ts` for tsconfig path aliases + cargo workspace member globs), `name-matcher.ts`, and `frameworks/` (Express, Laravel, Rails, FastAPI, Django, Flask, Spring, Gin, Axum, ASP.NET, Vapor, React Router, Next.js — `nextjs.ts`: pages and `route.ts` handlers from files, `router.push` / `redirect` / `NextResponse.redirect` as `navigates` edges, with `next-router-synthesizer.ts` for `<Link href>` — Expo Router, SvelteKit, Vue/Nuxt, Cargo workspaces). Frameworks emit `route` nodes and `references` edges. `callback-synthesizer.ts` holds the whole-graph synthesis passes (`SYNTH_PASSES`, merged in registry order — first-seen wins a duplicate pair) with the language gates; `tier-synthesizer.ts` is the cross-tier pass (a client's literal `fetch`/`axios` path onto its own route, a queue job onto its consumer, a bus / socket event onto its handler — `channel`, `tier`, `registeredAt` on every edge; registered before the in-process emitter pass so its more specific edge wins); `synth-utils.ts` has the helpers they share (`enclosingFn`, `enclosingValue`, `makeLineAt`). Express's `postExtract` composes `app.use('/prefix', router)` mounts onto a mounted file's route names, idempotently (the original path stays in `qualifiedName`).\n- `src/graph/` — `GraphTraverser` (BFS/DFS, impact radius, path finding) and `GraphQueryManager` (high-level queries), plus the shared query-time derivations more than one surface renders: `named-symbol-flow.ts` (the one path finder, behind `codegraph_explore`'s Flow section and the viewer's Flow strip), `dynamic-boundary-report.ts` (where the graph stops), `type-hierarchy.ts` (ancestors/subtypes and the implementation count explore prints and the viewer draws),\n  `dead-code.ts` (unreferenced symbols, and every reason a candidate is NOT claimed), `synthesized-hop.ts` (how a synthesized hop reads, in explore's Flow, the `codegraph_node` trail and ContextBuilder's call paths — a new synthesizer's wording goes here). A derivation that two callers render must live here, not in `ToolHandler` — two derivations eventually disagree.\n- `src/context/` — `ContextBuilder` + formatter for markdown/JSON output.\n- `src/search/` — full-text query parser and helpers for FTS5.\n- `src/sync/` — `FileWatcher` (native FSEvents/inotify/RDCW) with debounce + filter, and git-hook helpers.\n- `src/mcp/` — MCP server (`MCPServer`, `tools.ts`, `transport.ts`). `server-instructions.ts` is what the server returns in the MCP `initialize` response — keep it in sync with the user-facing tool guidance.\n- `src/installer/` — see below.\n- `src/bin/codegraph.ts` — CLI (commander). Subcommands: `install`, `init`, `uninit`, `index`, `sync`, `status`, `query`, `files`, `context`, `affected`, `serve --mcp`.\n- `src/ui/` — terminal UI (shimmer progress, worker).\n- `src/ui-server/` -- read-only JSON API for the `codegraph ui` browser viewer (`api/`: `node`, `flow`, `map`, `screens`, `steps`, `deadcode`, `trails`, `program`, ...) plus static server; Svelte viewer lives in `ui/` (see `docs/design/codegraph-ui-design-spec.md`). `screens`/`steps`/`program` share one fold (`via`/`when` via `graph/branch-guards.ts`); `api/effects.ts` curates calls that leave the index; `api/route-roots.ts` names where a route's code starts. Derivations rendered by more than one surface belong in `src/graph/`, not `ToolHandler`.\n  **Not released yet:** `codegraph ui` / `web` are refused (and hidden from `--help`) unless `CODEGRAPH_UI=1` is set — `src/bin/viewer-gate.ts`. Viewer changelog entries wait in `docs/viewer-launch-changelog.md`, not under `[Unreleased]`. At launch: delete the gate, move those entries back, drop the \"not released yet\" notes from `site/`.\n\n### NodeKind / EdgeKind\n\nDefined in `src/types.ts`. Both extractors and resolvers must use these exact strings.\n\n- **NodeKind**: `file`, `module`, `class`, `struct`, `interface`, `trait`, `protocol`, `function`, `method`, `property`, `field`, `variable`, `constant`, `enum`, `enum_member`, `type_alias`, `namespace`, `parameter`, `import`, `export`, `route`, `component`, `union`.\n- **EdgeKind**: `contains`, `calls`, `imports`, `exports`, `extends`, `implements`, `references`, `type_of`, `returns`, `instantiates`, `overrides`, `decorates`.\n\n### Multi-agent installer\n\n`src/installer/` is the entry point for `codegraph install` (and the bare `codegraph`/`npx @colbymchenry/codegraph` invocation). Architecture:\n\n- `targets/registry.ts` lists every supported agent.\n- `targets/types.ts` defines the `AgentTarget` interface — adding a 5th agent (Continue, Zed, Windsurf…) is **one new file in `targets/` + one entry in `registry.ts`**. Each target owns its config-file location and MCP-server JSON/TOML/JSONC writing. (Claude, Codex, Gemini and opencode also write a short instructions-file block — see below.)\n- Current targets: `claude.ts`, `cursor.ts`, `codex.ts`, `opencode.ts`.\n- `targets/toml.ts` is a hand-rolled TOML serializer scoped to `[mcp_servers.codegraph]` (used by Codex). Sibling tables and `[[array_of_tables]]` are preserved verbatim. No new dependency.\n- opencode reads `opencode.jsonc` by default; the installer prefers existing `.jsonc`, falls back to `.json`, and creates `.jsonc` for greenfield installs. Edits are surgical via `jsonc-parser` so user comments and formatting survive install/re-install/uninstall round-trips. The MCP entry is OpenCode 2's native `mcp.servers.codegraph` with `disabled: false` and `codemode: false` (so `codegraph_explore` stays on the native tool list); a pre-#1698 `mcp.codegraph` + `enabled` entry is migrated on re-install and removed by uninstall.\n- `instructions-template.ts` holds `CODEGRAPH_INSTRUCTIONS_BLOCK`, a deliberately short marker-fenced (`<!-- CODEGRAPH_START -->`/`<!-- CODEGRAPH_END -->`) `## CodeGraph` block. The Claude, Codex, Gemini and opencode targets write it into their instructions file (`CLAUDE.md` / `~/.codex/AGENTS.md` / `~/.gemini/GEMINI.md` / `~/.config/opencode/AGENTS.md`) via `upsertInstructionsEntry`, and `uninstall` removes it. History: #529 removed the old full playbook because it duplicated the MCP `initialize` instructions; #704 brought back this small block because the instructions file reaches two audiences `initialize` cannot — Task-tool subagents and non-MCP harnesses (the `codegraph explore` CLI). Cursor and Kiro write no block; their `install` only strips one a previous install left behind. Keep the block to command names and the two surfaces; tool guidance itself still belongs in `server-instructions.ts`.\n- All installer changes need matching coverage in `__tests__/installer-targets.test.ts` — there are ~47 parameterized contract tests covering install idempotency, sibling preservation, uninstall reverses install, byte-equal re-runs returning `unchanged`, and partial-state recovery for Codex.\n\n### Cursor MCP working-directory quirk\n\nCursor launches MCP subprocesses with the wrong cwd and doesn't pass `rootUri` in `initialize`. The installer injects `--path` into Cursor's MCP args — absolute path for local installs, `${workspaceFolder}` for global installs. If you touch Cursor wiring, preserve this.\n\n### MCP server instructions\n\n`src/mcp/server-instructions.ts` is sent back to the agent in the MCP `initialize` response. This is the *first* thing every agent sees about how to use the tools, and as of issue #529 it is the **single source of truth** for agent-facing tool guidance. The installer's instructions-file block (`instructions-template.ts`, see above) only names the tool and the CLI for subagents and non-MCP agents; it does not repeat this guidance. Edit tool guidance here and nowhere else.\n\n## Retrieval performance & dynamic-dispatch coverage (do not regress)\n\nCodeGraph's core value is letting an agent answer **structural/flow** questions (\"how does X reach Y\", trace, impact, callers) with a few **fast** codegraph calls and **zero Read/Grep**. The optimization target is **wall-clock latency + tool-call count** — *don't optimize for token cost*. (Cost is **lower**, not \"flat\" as earlier framing claimed: a current-build with-vs-without A/B across the 7 README repos, median of 4, saved on average **35% cost · 57% tokens · 46% time · 71% tool calls** — reproducing the published README. The mechanism is **far fewer turns over a much smaller accumulated context** — NOT cache-ability: the without-arm's huge token volume is *mostly* cheap cache-reads, which is why token-count savings (57%) look bigger than cost savings (35%). Measure tokens by **summing per-turn assistant usage**, not `result.usage` (last-turn only in current Claude Code). See `docs/benchmarks/call-sequence-analysis.md`.) The mechanism that drives everything here: **an agent falls back to Read/Grep the instant a codegraph answer is insufficient.** So every change is judged by one question — is codegraph's answer sufficient enough to *stop* the agent from reading?\n\n**Target behavior:** a flow question resolves in **1 codegraph call on small repos, scaling to 3–5 on large**, with **Read/Grep = 0**. When reviewing a PR or trying something new, do not regress this.\n\n### Adapt the tool to the agent — don't try to change the agent\n\nThe lever that decides whether a retrieval change lands. **Test before building anything here: does this make a tool the agent _already calls_ do more with the input it _already gives_? If it instead needs the agent to behave differently — pick a different tool, query differently, learn from examples — it hits the low-salience wall and won't land.**\n\nCodeGraph's only channels to influence the agent are low-salience: the MCP `initialize` instructions (`server-instructions.ts`) and the tool descriptions. Changing them does **not** reliably move the agent's tool _choice_ or query style — validated: trace-first steering ported into the server-instructions + tool descriptions (3 wording variants) never reproduced what a CLI `--append-system-prompt` achieved, and **regressed** wall-clock vs baseline. New tools fare worse (rarely chosen — the agent under-picks even `trace`); \"better examples\" is the same steering. The agent's tool-choice does improve on its own as host models get better at tool use — but that is not ours to force.\n\nWhat works is meeting the agent where it already is:\n- **explore-flow** — `codegraph_explore` is the PRIMARY tool the agent reliably calls; its query is a precise bag of symbol names (incl. qualified `Class.method`) spanning the flow the agent is after; explore finds the call path _among those named symbols_ (riding synthesized edges) and leads its output with it. (`buildFlowFromNamedSymbols`: segment/co-naming disambiguation; ≤1 unnamed bridge so it never wanders a god-function's fan-out. Overload-aware: a PascalCase type token in the query biases an overloaded name to that type's own def — `DataRequest task` → DataRequest's `task`, not the abstract base; named-symbol files sort first.)\n- **Sufficiency** — make the tool's output complete enough that the agent stops. `codegraph_node` returns the full body + the caller/callee trail, and for an AMBIGUOUS name returns **every overload's body in one call** (so the agent never Reads a file to find the right overload — validated on Alamofire/gin). This is the after-explore depth tool (labeled SECONDARY).\n- **Errors teach abandonment** — one or two `isError: true` responses early in a session and the agent stops calling codegraph entirely (maintainer-observed, repeatedly). `isError` is reserved for genuine \"stop trying\" cases: security refusals (`PathRefusalError`) and real malfunctions (which carry a retry-once note). Every expected/recoverable condition — project not indexed, symbol not found, file not in the index — returns a **SUCCESS-shaped response carrying the guidance** (`NotIndexedError` → `textResult`, see `ToolHandler.execute`'s catch). The same principle is why the tool surface is **always exposed, even at an un-indexed root** (the old empty-`tools/list` gate was removed in #964 — it broke monorepos where only sub-projects carry a `.codegraph/`, and hid the tools from a session that started before `codegraph init`): safety comes from the response SHAPE (success-shaped guidance, never `isError`), not from hiding tools. An un-indexed root's `initialize` sends a per-project variant (`SERVER_INSTRUCTIONS_NO_ROOT_INDEX` — \"pass `projectPath` to a project that has a `.codegraph/`\"), not an \"inactive\" note; indexing is still deliberately the user's call, never the agent's.\n\nWhat fails is the inverse — folding a precise answer into a **fuzzy-input** tool: the now-removed `codegraph_context` took a description, not symbols, so it couldn't disambiguate a flow's endpoints and surfaced the _wrong feature_ (which is why it was cut). Precise output needs precise input — explore takes a symbol bag for exactly this reason. (`codegraph_trace` was likewise removed: explore-flow does its job and the agent under-picked it.)\n\nThe remaining lever under this axis is **coverage**: every flow made to connect statically (a new dynamic-dispatch synthesizer, or extracting symbols static parsing skipped — e.g. object-literal store actions in `create((set,get)=>({...}))`) is then surfaced automatically by explore-flow, no agent change needed. Reactive/reconciler runtimes (Halo's `ReactiveExtensionClient`, MediatR, Vue Proxy) are the frontier — flows there have no static edges, so nothing surfaces (correctly — silent beats wrong). Full investigation + A/B record: `docs/benchmarks/call-sequence-analysis.md` + auto-memory `project_codegraph_read_displacement`.\n\n### Explore budget — keep BOTH budgets monotonic with repo size\n\nTwo functions in `src/mcp/tools.ts` scale explore with indexed file count. This is the expected resolution (a regression here silently forces agents back to Read):\n\n| Repo | files | explore calls | chars/call | per-file |\n|---|---|---|---|---|\n| express (small) | 147 | 1 | 18K | 3800 |\n| excalidraw/django (medium) | 643–3043 | 2 | 28K | 6500 |\n| vscode (large) | 10446 | 3 | 35K | 7000 |\n| ~20k / ~40k | — | 4 / 5 | 38K | 7000 |\n\n- `getExploreBudget(fileCount)` → **call** budget: `<500→1, <5000→2, <15000→3, <25000→4, ≥25000→5` (max 5).\n- `getExploreOutputBudget(fileCount)` → **per-call** output (chars / files / per-file). **Invariant: a larger tier must never get a smaller `maxCharsPerFile` than a smaller tier.** (Regression that motivated this doc: the `<5000` tier's 2500 was *below* the `<500` tier's 3800, so on a god-file repo — excalidraw's 415 KB `App.tsx` — one explore returned <1% of the file and forced a Read.)\n- Explore output must **never tell the agent to \"use Read\"** — steer to another `codegraph_explore` and \"treat returned source as already Read.\"\n\n### Dynamic-dispatch coverage — the flow must EXIST in the graph end-to-end\n\nStatic tree-sitter extraction misses computed/indirect calls, so flows break at dynamic dispatch and the agent reads to reconstruct them. Synthesizers/resolvers bridge these so `codegraph_explore` connects them end-to-end (`src/resolution/callback-synthesizer.ts`, `src/resolution/frameworks/`). Channels today: callback/observer, EventEmitter, **React re-render** (`setState`→`render`), **JSX child** (`render`→child component), **React Native native→JS events** (`sendEvent(withName:)` / JVM `emit` → the `addListener` handler, named or inline, `rn-event-channel`), django ORM descriptor. The JS→native direction is a *resolver* (`frameworks/react-native.ts`: `RCT_EXPORT_METHOD`, `RCT_EXTERN_MODULE` Swift shims, TurboModules), which trusts receiver evidence — an alias bound to `NativeModules.X` — over the import resolver. All synthesized edges are `provenance:'heuristic'` with `metadata.synthesizedBy` + `registeredAt` (the wiring site), surfaced inline in `codegraph_explore`'s Flow section and the `codegraph_node` trail.\n\n**Principle: partial coverage is WORSE than none.** Bridging one boundary but not the next reveals a hop the agent then drills + reads to finish. Measured on excalidraw: react-render alone *raised* reads to 5–7; only completing the flow (adding the jsx-child hop) dropped it to 0–1. **Always close the flow end-to-end and re-measure** — never ship a half-bridged flow.\n\n\n### Validation methodology & worked examples\n\n**Required** for every new language/framework: validate on small/medium/large real repos with >=3 flow prompts; deterministic probes (`scripts/agent-eval/probe-*.mjs`) then agent A/B (`scripts/agent-eval/run-all.sh` / `ab-new-vs-baseline.sh`). Pass bar: ~0 Read/Grep within the explore-call budget, faster than without-codegraph, no control-repo regression.\n\nFull methodology (feedback metrics, CLI contamination guard, Sonnet/`--effort high` model policy, daemon pre-warm), the Excalidraw worked example, and coverage matrix live in:\n- `docs/AGENTS.md` (nested; also loaded when cwd is under `docs/`)\n- `docs/design/dynamic-dispatch-coverage-playbook.md`\n- `docs/design/callback-edge-synthesis.md`\n- `docs/benchmarks/call-sequence-analysis.md` / `docs/benchmarks/agent-eval-feedback-metrics.md`\n\n\nTests live in `__tests__/` and mirror the module they cover. Notable ones beyond the obvious:\n\n- `installer-targets.test.ts` — parameterized contract suite across all 4 agent targets (see installer notes above).\n- `evaluation/` — `runner.ts` + `test-cases.ts` exercise codegraph against synthetic projects and score the results; run via `npm run eval` (builds first). Not part of `npm test`.\n- `sqlite-backend.test.ts` / `node-sqlite-backend.test.ts` — pin that `node:sqlite` is the sole backend: `getBackend()` reports `node-sqlite` and the DB comes up in WAL.\n- `pr19-improvements.test.ts`, `frameworks-integration.test.ts` — regression coverage for specific past PRs/incidents; don't rename these, the names anchor to git history.\n\nTests create temp dirs with `fs.mkdtempSync` and clean up in `afterEach`. They write real files and exercise real SQLite — there is no DB mocking.\n\nEvery engine test file runs in a throwaway home dir (`__tests__/setup-home-sandbox.ts`, a `setupFiles` entry): `HOME`/`USERPROFILE` (+ Windows vars), `XDG_CONFIG_HOME` and `GIT_CONFIG_GLOBAL` point into it, and `CLAUDE_CONFIG_DIR`/`CODEX_HOME`/… are cleared; spawned children inherit it. It's a backstop — still inject writes to global state (e.g. `UpgradeDeps.wirePromptHook`, #2275).\n\n### Windows-gated tests\n\nBehavior that differs by platform (path resolution, drive letters, `SENSITIVE_PATHS`, `%APPDATA%` config dirs, CRLF) must be gated, not assumed. Use `it.runIf(process.platform === 'win32')(...)` for Windows-only assertions and `it.runIf(process.platform !== 'win32')(...)` for POSIX-only ones — e.g. `/etc` is sensitive on POSIX but resolves to `C:\\etc` (non-existent) on Windows, so an ungated `/etc` assertion fails on Windows. Validate the Windows side for real (see below); don't merge a Windows-gated test you haven't seen run.\n\n## Cross-platform validation\n\nThe dev machine — and the default `npm test` target — is **macOS**, so local runs cover the macOS path. The other two platforms aren't here; when a change is platform-sensitive (file watching, sockets / named pipes, path & symlink handling, process lifecycle, inotify budget) validate them for real rather than guessing.\n\n### Linux (Docker)\n\nWhen asked to test or validate on Linux, use **Docker** — there's no Linux box, but Docker runs on the macOS host. Build a throwaway image from the repo and run the suite inside it:\n\n- `FROM node:22-bookworm`; `COPY` the repo with a `.dockerignore` excluding `node_modules`/`dist`/`.git`/`.codegraph`; `RUN npm ci && npm run build`. Don't reuse the Mac `node_modules` — `esbuild`/`rollup` ship platform-specific binaries.\n- Run with **`docker run --rm --init`**. The `--init` is load-bearing for any process-lifecycle test (daemon reaping, the #277 PPID watchdog, idle-timeout): without a zombie-reaping PID 1, a SIGKILL'd/exited process lingers as a zombie and `process.kill(pid, 0)` still reports it *alive*, so exit-detection assertions false-fail even though the process did exit.\n- Linux is where the inotify watch budget actually bites: count a process's watches via `/proc/<pid>/fdinfo/*` (sum `^inotify ` lines on the fd whose `readlink` is `anon_inode:inotify`).\n\n### Windows (Parallels VM + SSH)\n\nFor any Windows-specific PR, bug, or implementation, validate it on the real Windows VM rather than guessing. Connection details live in the gitignored **`.parallels`** file at the repo root (VM name, guest IP, SSH user/key). `prlctl exec` needs Parallels Pro and is unavailable, so SSH is the bridge.\n\n- Connect / run from the Mac host: `ssh <user>@<guest_ip> \"...\"`. For multi-line work, pipe PowerShell over stdin and **refresh PATH from the registry** first (sshd's session has a stale PATH after winget installs):\n  ```\n  ssh colby@10.211.55.3 \"powershell -NoProfile -ExecutionPolicy Bypass -Command -\" <<'PS'\n  $env:Path = [Environment]::GetEnvironmentVariable(\"Path\",\"Machine\") + \";\" + [Environment]::GetEnvironmentVariable(\"Path\",\"User\")\n  Set-Location C:\\dev\\codegraph\n  PS\n  ```\n- Clone fresh into a **Windows-local** path (`C:\\dev\\codegraph`) and `npm ci` there — never run npm against the shared Mac repo, since `esbuild`/`rollup` ship platform-specific binaries.\n- Guest toolchain (winget): Node LTS, Git, and the **VC++ ARM64 redistributable** (required by `@rollup/rollup-win32-arm64-msvc`, which vitest pulls in).\n- Fetch a contributor PR head straight from their fork to dodge `pull/<n>/head` lag: `git fetch <fork-url> <branch>` then `git checkout -f FETCH_HEAD`.\n- Windows baseline: as of #2053 the full suite passes on the Windows 11 (ARM64) VM. The only expected exception is `security.test.ts > Session marker symlink resistance > does not follow a pre-planted symlink`, which needs symlink privileges (Developer Mode) — confirm any other failure against `origin/main` before blaming your PR. The former `mcp-initialize.test.ts` / `mcp-roots.test.ts` `EPERM` teardown failures came from tests spawning `serve --mcp` without the runtime flags (its `--liftoff-only` re-exec grandchild kept the cwd / SQLite file open); spawn it with `WASM_RUNTIME_FLAGS` and await the child's exit before removing the temp dir. Windows checkouts may be CRLF — split source lines on `/\\r?\\n/` in tests.\n- Windows worker crashes: a worker thread that ends (`terminate()`, its own `process.exit()`, or the process exiting) while V8's concurrent marker is marking its heap kills the process with exit 3221225477 (0xC0000005) and leaves no error or dump. It's worst while the worker is still loading its modules. Owners must not terminate a worker before its first message (`workerStarted` / `terminateOnceStarted`, or the pool's own handshake), and a worker that exits itself calls `collectBeforeExit()` first — both in `src/worker-teardown.ts`. `--no-concurrent-marking` also stops it, but measured about a third slower indexing on the Windows VM, so it is not used.\n\n## Releases\n\nReleased to npm and mirrored as [GitHub Releases](https://github.com/colbymchenry/codegraph/releases). `CHANGELOG.md` is the source of truth; GitHub Release notes are extracted from it.\n\n### Writing changelog entries\n\n**Default: write entries under `## [Unreleased]`** — that's the section reserved for work landing between releases. **Don't pre-create a `## [X.Y.Z]` block** for the next release: the Release workflow's first step is `scripts/prepare-release.mjs`, which automatically promotes everything under `[Unreleased]` into a new `## [X.Y.Z] - <YYYY-MM-DD>` block at release time (or merges into a pre-existing `[X.Y.Z]` block if one exists — but you don't need one). Pre-staging is what caused the v0.9.5 sparse-release-notes incident: a sparse `[0.9.5]` block hand-added before the rest of the work landed got picked by the extractor over the much-larger `[Unreleased]` section above it. Don't do that.\n\nFormatting rules for any entry (anywhere — `[Unreleased]` or otherwise):\n\n1. **Write friendly, user-facing notes — not engineer-facing ones.** Group under `### New Features` and `### Fixes` (sentence-case). Surface `### Breaking Changes` and `### Security` as their own sections **only when the release has them**; fold improvement-flavored changes into New Features. Omit empty sections. (This replaces the old Keep-a-Changelog `Added/Changed/Fixed/Removed/Deprecated` grouping: the GitHub Release page extracts each version block **verbatim** via `scripts/extract-release-notes.mjs`, and the old dense, implementation-focused entries rendered as an unreadable wall of text — so the whole CHANGELOG was rewritten to this format and every published release re-noted to match.)\n2. **One plain-language sentence per bullet:** what changed and why it matters to a user. Lead with the capability, or with the symptom that's now fixed.\n3. **Strip the internals.** No internal file paths (`src/...`), no internal symbol / function / class names, no benchmark numbers / percentages / node-or-edge counts. **Keep:** language & framework names (Go, Spring, NestJS, …), things a user types or sets (`codegraph install`, `codegraph_explore`, the `CODEGRAPH_*` env vars), agent / IDE names (Claude Code, Cursor, opencode, Kiro, …), and a brief `Thanks @user` when a contributor is credited.\n4. Issue / PR references in entries are by number (`(#403)` etc.); the GitHub renderer auto-links them in the published release notes.\n5. **Don't add a `[X.Y.Z]: https://...` link reference yourself** — `prepare-release.mjs` appends it automatically when it promotes the version (idempotent: a re-run is a no-op if it already exists).\n6. **Every release opens with a `### Highlights` block — the only part most people read.** At most ~8 one-line bullets, in plain language for someone who doesn't read code, ordered by what a typical user notices first (new agent/IDE support and setup changes, then answer quality, then reliability), plus a one-sentence upgrade note when a re-index is needed. Write or refresh it in `[Unreleased]` when a release is being prepared — not per PR — and keep the detailed `### New Features` / `### Fixes` entries below it. When `### Fixes` grows past ~15 entries, group them under `####` sub-headings (`Better answers from codegraph_explore`, `Finding your project, live updates, and the CLI`, `Indexing reliability and disk usage`, `Language and framework accuracy`) so a skimmer can find their area.\n\nMulti-word headings like `### New Features` are safe on the normal release path: `prepare-release.mjs` **Case A** moves the whole `[Unreleased]` body verbatim into `[X.Y.Z]`. (Only its rarely-used **Case B** *merge* splits sub-sections with a single-word `^### (\\w+)$` regex that wouldn't match them — and Case B fires only if a `[X.Y.Z]` block was pre-created, which rule above already forbids.)\n\n### Release flow (the user runs these)\n\nReleases are built and published by the **GitHub Actions \"Release\" workflow**\n(`.github/workflows/release.yml`). It runs `scripts/prepare-release.mjs` to\npromote `[Unreleased]` into `[<version>]` (and auto-commit + push that\nCHANGELOG change back to `main` so on-disk truth matches the published\nnotes), then bundles a Node runtime per platform (`scripts/build-bundle.sh`)\nand publishes both the GitHub Release and the npm thin-installer\n(`scripts/pack-npm.sh`: a shim package + per-platform packages).\nPublishing manually is **wrong** now — a plain `npm publish` ships the root\npackage (non-bundled), which breaks anyone on Node < 22.5.\n\n**Claude does NOT bump the version unless explicitly asked.** The maintainer\ntypically does it themselves — often by editing `package.json` directly via\nthe GitHub web UI. Don't proactively commit a version bump as part of\nunrelated work, and don't propose one when summarizing a PR.\n\nWhen the maintainer DOES bump the version, the only edit strictly required is\nto `package.json` — the workflow's \"Sync package-lock.json\" step detects a\nmismatch between `package.json` and `package-lock.json`, runs\n`npm install --package-lock-only --ignore-scripts` to rewrite the lock file's\nversion fields (top-level + `packages.\"\"`), and auto-commits + pushes the\nresult back to `main` with `[skip ci]`. So a GitHub-web-UI single-file edit to\n`package.json` is enough to kick off a clean release. (If they edit both files\nlocally, that's fine too — the sync step no-ops.) Bump `ui/package.json` to the\nsame version in that change: the component library is versioned with the\nengine, `ui-package.test.ts` pins it, and the workflow does not sync it — the\n1.6.1 bump left `main` red on exactly that.\n\nOnce `package.json` is at the target version on `main`, trigger\n**Actions → Release → Run workflow** (on `main`). The workflow:\n\n1. Syncs `package-lock.json` to `package.json`'s version if they've drifted; commits + pushes that change.\n2. Runs `prepare-release.mjs <X.Y.Z>` → promotes `[Unreleased]` → `[X.Y.Z] - <today>` in `CHANGELOG.md`, appends the link reference, commits + pushes the move with `[skip ci]`.\n3. Builds every platform bundle on one runner, generates `SHA256SUMS`.\n4. Creates the GitHub Release with notes from the freshly-promoted `[X.Y.Z]` block.\n5. Publishes the npm shim + per-platform packages. Requires the `NPM_TOKEN` repo secret.\n\n**Do not run `npm publish`, `git push`, or `git tag` yourself** — these are\npublish actions on shared state. Write the files, hand the user the commands.\n\n## House rules\n\n- The `0.7.x` line is in active multi-agent rollout. Any change to `src/installer/` (especially `targets/`) needs corresponding test coverage and a CHANGELOG entry — installer regressions break every new install silently.\n- When changing what the MCP tools do or how agents should use them, edit `src/mcp/server-instructions.ts` — it is the **single source of truth** for agent-facing tool guidance (issue #529). The installer's short instructions-file block (`instructions-template.ts`) only names `codegraph_explore` and the `codegraph explore` CLI; update it only when a tool or CLI command is renamed or removed. (The repo's own checked-in `.cursor/rules/codegraph.mdc` is dogfooding config — update it too if you use Cursor on this repo, but it ships nowhere.)\n- **Before adding or extending a router, a web framework, or a language's `WHEN` rules, read `docs/design/framework-coverage.md`.** It is the standing answer to \"what is supported and what is left\" across the three axes (route nodes → Entry points, `navigates` edges → Screens, branch-guard rules → the `WHEN` labels), with what each remaining item needs, the traps that have already cost debugging time, and the queries to re-verify it. Update it in the same change that moves a row.\n- CodeGraph provides **code context**, not product requirements. For new features, ask the user about UX, edge cases, and acceptance criteria — the graph won't tell you.\n- **When the user references issues, PR comments, or external reports, anchor them to a date and version before drawing conclusions.** Check the comment's `createdAt` against:\n  - The **last released version** — `grep -m1 '^## \\[' CHANGELOG.md` shows the top-of-file version (older releases follow). A comment dated before the latest `## [X.Y.Z] - YYYY-MM-DD` is reacting to *released* state — work that's only on `main` or on an unmerged branch doesn't apply.\n  - The **last main commit** — `git log --first-parent main -1 --format='%ai %h %s'`. A comment after the last release but before a fix on main may already be addressed there but unreleased.\n  - The **current branch's tip** — your own unmerged work obviously can't be what the comment is reacting to.\n  Always disambiguate \"released,\" \"merged-but-unreleased,\" and \"in-progress\" before agreeing that a user-reported problem is unfixed (or that a fix is incomplete). A user saying \"your fix only covers X\" about a recent PR is usually pointing at the *released* shortcomings — your in-flight branch may already address them but they have no way to know that.\n- **Version-tag every image referenced in `README.md`.** GitHub caches README images (`raw.githubusercontent.com` with a 5-minute TTL; third-party hosts sit behind the long-lived camo proxy), so updating an asset in place can keep showing the stale version. Give each README image URL a `?v=N` query tag and **bump `N` in the same commit whenever the asset bytes change** — e.g. `assets/waitlist.svg?v=2`. The changed URL sidesteps every cache so the new image shows immediately instead of waiting on a TTL to expire.\n\n<!-- File: docs/AGENTS.md (Tokens: ~1752 | Category: root-instruction) -->\n# AGENTS.md (docs/)\n\nNested Codex guidance under `docs/`. Loaded with the repo-root `AGENTS.md` when cwd is under `docs/` (Codex walks root to cwd; shared `project_doc_max_bytes` budget).\n\nRoot `AGENTS.md` already carries the non-negotiable retrieval principles (adapt-the-tool, explore budgets, end-to-end synthesis). This file holds the longer validation methodology and Excalidraw worked example that were moved out of root to fit the budget.\n\n### Validation methodology (REQUIRED for every new language/framework)\n\nFor each **language × framework**, validate on **small, medium, and large** real repos with **≥3 different flow prompts** each:\n\n1. **Pick the canonical flow** for the framework (\"how does X reach Y\": state→render, request→handler→view, query→SQL, action→reducer→store…).\n2. **Deterministic probes** (`scripts/agent-eval/probe-{node,explore}.mjs` against the built `dist/`): `codegraph_explore` with the flow's symbol names connects from→to end-to-end with no break (its Flow section shows the path); **no node explosion** (`select count(*) from nodes` stable before/after re-index); synthesized-edge **precision** spot-check (`select … where provenance='heuristic'`).\n3. **Agent A/B** (`scripts/agent-eval/run-all.sh <repo> \"<Q>\"`): with vs without codegraph, **≥2 runs/arm** (run-to-run variance is large — never conclude from n=1). Record **duration, total tool calls, Read, Grep**. Optional forced-Read-0 sufficiency proof via the block-read hook (`scripts/agent-eval/hook-settings.json`).\n   - **Every run also reports three feedback metrics** — residual context occupancy, explore sufficiency (what the agent did NEXT after each explore), and allocation efficiency (share of returned bytes the answer cited) — under each run, plus a side-by-side arm table (`compare-arms.mjs`). Entry point: `docs/benchmarks/agent-eval-feedback-metrics.md`. Reading them: `Read a file we returned` is an allocation miss, `Read a file we did NOT return`/`Grep` is recall; allocation efficiency is **relative** (attribution is by citation) so it is only valid between builds on the same question; occupancy *shares* are Claude Code / 200k and don't transfer to another host — the arm ratio does.\n   - **The `codegraph` CLI is blocked in every arm** (`no-cli-shim.sh`: sanitized PATH + a PreToolUse hook, shared by both harnesses). Without it 14 of 15 without-arm runs in one 7-repo pass reached codegraph through Bash. Check the contamination row before believing any number: `CLI calls that RETURNED output` > 0 invalidates the run (in a new-vs-baseline A/B it silently drops calls from all three metrics, since a CLI explore is not a tool call).\n   - **Model policy — every A/B arm runs Claude with `--model sonnet --effort high`. Always. Never Opus/Fable.** All `scripts/agent-eval/*.sh` default to this (`MODEL`/`EFFORT` env override exists — don't raise it without an explicit reason from the maintainer). Two reasons, and the second matters more than cost: (a) Sonnet doesn't burn tokens; (b) **Sonnet is the deliberate floor model** — codegraph's real users attach it to whatever agent they already run (Cursor Composer, Gemini, etc.), so we validate on a \"dumber\" model on purpose: a stronger model's tool-use covers up the salience/sufficiency problems a weaker one exposes. An affordance that lands on Sonnet generalizes up to every host; one that only works on Opus/Fable doesn't generalize down to the agents most users actually have. Both arms always use the same model.\n   - **MCP attach is a startup-latency issue, not a hard block.** On a multi-step task the agent dives into Read/grep before codegraph finishes its ~2-3s startup (worse when the eval is itself run nested inside a Claude session, under CPU contention), so it runs with no codegraph. Fix: **pre-warm a persistent daemon** for the target (`CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS` high; spawn `serve --mcp --path <target> </dev/null &`; wait for `.codegraph/daemon.sock`) **and skip the startup re-exec** (`CODEGRAPH_WASM_RELAUNCHED=1`) so claude connects before the agent's first turn. Don't trust claude's `init` snapshot — it can read `status:\"pending\"` / 0 tools even when it then connects; judge by actual codegraph usage in `parse-run.mjs`'s `by type`. To isolate a change — **new-build vs baseline-build, both codegraph-on** (vs run-all.sh's with-vs-without) — use `scripts/agent-eval/ab-new-vs-baseline.sh <indexed-repo> \"<task>\" [baseline-ref]` (it bakes in the pre-warm).\n4. **Pass bar:** a normal flow question reaches **~0 Read/Grep within the repo's explore-call budget**, runs **faster** than without-codegraph, and shows **no regression on a control repo**. Record the numbers in `docs/design/dynamic-dispatch-coverage-playbook.md` (the coverage matrix).\n\nFull playbook + per-mechanism design: `docs/design/dynamic-dispatch-coverage-playbook.md` and `docs/design/callback-edge-synthesis.md`.\n\n### Worked example — Excalidraw (TS/React, medium, 643 files)\n\nThe template to replicate per language/framework. Question: *\"how does updating an element re-render the canvas on screen?\"* (the full flow crosses three React boundaries: observer callback, `setState`→`render`, and JSX child).\n\n| Stage | duration | Read | Grep | codegraph |\n|---|---|---|---|---|\n| Without codegraph | 115–139s | 9–10 | 10–11 | 0 |\n| Broken (explore-budget regression) | 131–139s | 5–10 | 3–5 | 6–14 |\n| Fixed (budget + msgs + synthesis) | 64–112s | 0–2 | 2–4 | 3–**10** |\n| + trace-first steering | **51–74s** | **0–2** | 0–4 | **3–4** |\n\nn=4 unhooked runs/stage, same prompt. After steering flow questions to `codegraph_trace` first: **best run 0 Read / 0 Grep / 3 codegraph / 51s**; **2 of 4 fully clean** (0 Read, 0 Grep). Steering eliminated the over-drill variance — call count tightened from 3–10 to 3–4, trace adoption went 3/4 → 4/4, and the `search`+`callers` path-reconstruction floundering dropped to 0. Run-to-run variance is still real; report the range, never a single run. **Residual reads/greps are all the nonce data-flow** (`canvasNonce` — a local prop with no graph edges); that's the def-use/data-flow frontier, left deliberately uncovered (tracking every local would explode the graph). Validated: `trace(mutateElement, renderStaticScene)` connects in **6 hops** across all three boundaries (`mutateElement → triggerUpdate → [callback] triggerRender → [react-render] render → [jsx] StaticCanvas → renderStaticScene`), each hop showing inline source + the wiring site; node count stable at 9,289; 1 callback + 46 react-render + 280 jsx-render synthesized edges (no explosion, precision-checked).\n\n\nAlso see: `docs/design/dynamic-dispatch-coverage-playbook.md`, `docs/design/callback-edge-synthesis.md`, `docs/benchmarks/call-sequence-analysis.md`, `docs/benchmarks/agent-eval-feedback-metrics.md`.\n\n<!-- File: .claude/skills/add-lang/SKILL.md (Tokens: ~3058 | Category: anthropic-skill) -->\n---\nname: add-lang\ndescription: Add tree-sitter language support to codegraph end-to-end — wire the grammar + extractor, write tests, then benchmark extraction quality and retrieval value on 3 popular real-world repos. Use when the user runs /add-lang <language> or asks to add/support a new language (e.g. Lua, Elixir, Zig, OCaml) in codegraph.\n---\n\n# Add a language to CodeGraph\n\nWire a new tree-sitter language into codegraph's extraction pipeline, prove it\nextracts real symbols on popular repos, and prove it beats no-codegraph for an\nagent. Runs **fully autonomously** — pick repos, benchmark, update docs, then\nreport. **Never commit, push, publish, or tag** (house rule); leave all changes\nfor the user to review.\n\nThe argument is the language token used throughout the `Language` union, e.g.\n`lua`, `elixir`, `zig`. If none was given, ask which language. Use the lowercase\nsingle-token form everywhere (`csharp`, not `c#`).\n\n## Prerequisites\n- Run from the codegraph repo root. `node`, `git`, `gh`, and a logged-in\n  `claude` CLI (the benchmark spawns real `claude -p` runs).\n- The benchmark uses the local dev build — Step 8 builds + links it on PATH.\n\n## Workflow\n\nCopy this checklist and work through it in order:\n```\n- [ ] 1. Resolve language; bail early if already supported (just benchmark)\n- [ ] 2. Find a grammar + health-check it (ABI / heap corruption)\n- [ ] 3. Discover the grammar's AST node types (dump-ast.mjs)\n- [ ] 4. Wire the language (4 files; sometimes a 5th core touch)\n- [ ] 5. Build + verify-extraction loop until PASS\n- [ ] 6. Add extraction tests; make them green\n- [ ] 7. Auto-pick 3 popular repos by size tier; add to corpus.json\n- [ ] 8. Benchmark all 3: extraction + with/without A/B\n- [ ] 9. Update README + CHANGELOG\n- [ ] 10. Report; do NOT commit\n```\n\n### Step 1 — Resolve + short-circuit\n\nCheck whether the language is already wired: look for the token in the\n`LANGUAGES` const (`src/types.ts`) and the `EXTRACTORS` map\n(`src/extraction/languages/index.ts`). If it is already supported (e.g.\n`typescript`, `rust`), **skip Steps 2–6** and go straight to benchmarking\n(Steps 7–8) to validate/measure it — note in the report that no code changed.\n\n### Step 2 — Find a grammar, then health-check it\n\n```bash\nls node_modules/tree-sitter-wasms/out/ | grep -i <lang>   # csharp -> c_sharp\n```\n- **Present** → likely off-the-shelf; `grammars.ts` resolves it from\n  `tree-sitter-wasms` automatically. (Many languages: elixir, zig, ocaml,\n  solidity, toml, yaml, …)\n- **Absent** → vendor a `.wasm` into `src/extraction/wasm/` (like `pascal` /\n  `scala` / `lua`) and add the token to the vendored branch in Step 4.\n\n**Always health-check before writing an extractor — a *present* grammar can\nstill be unusable:**\n```bash\nnode scripts/add-lang/check-grammar.mjs <lang> path/to/valid-sample.<ext>\n```\nIt prints the grammar's ABI version and runs it the way indexing does: in a\nchild process with indexing's V8 flags, beside the vendored grammars, parsing a\nvalid sample many times and once at the 1 MiB file-size limit. If it **FAILs**\n(ERROR trees, or later parses giving a different tree, on valid code — an old ABI\ncorrupting the shared WASM heap, which silently drops nested calls/imports on\nevery file after the first; e.g. the tree-sitter-wasms **Lua** grammar is ABI 13\nand fails — or the process running it crashing or hanging, even after every\nparse finished), do NOT use that wasm. **Vendor a newer (ABI 14/15) build instead:**\n```bash\nnpm pack @tree-sitter-grammars/tree-sitter-<lang>   # often ships a prebuilt *.wasm\n# or build one: npx tree-sitter build --wasm   (needs Docker/emscripten)\ncp <the>.wasm src/extraction/wasm/tree-sitter-<lang>.wasm\n```\nthen add the token to the vendored branch in Step 4 and re-run check-grammar on\nthe vendored path until it PASSes. **If you cannot obtain a healthy wasm, STOP\nand tell the user.**\n\n### Step 3 — Discover AST node types\n\nGet a representative source file (write a small sample covering functions,\nclasses/structs, imports, enums; or `curl` a raw file from a known repo), then:\n```bash\nnode scripts/add-lang/dump-ast.mjs <lang> path/to/sample.<ext>\n# vendored grammar: pass the wasm path instead of the token\nnode scripts/add-lang/dump-ast.mjs src/extraction/wasm/tree-sitter-<lang>.wasm sample.<ext>\n```\nThe frequency table + field names (`name:`, `parameters:`, `body:`,\n`return_type:`) tell you what to map. Open the existing extractor closest to the\nlanguage's paradigm as a model: `rust.ts`/`scala.ts` (functional, traits),\n`java.ts`/`csharp.ts` (OO), `python.ts`/`ruby.ts` (scripting), `go.ts`\n(top-level methods + receivers).\n\n### Step 4 — Wire the language (4 files)\n\nThese are exact, fragile wiring — match the existing style precisely:\n\n1. **`src/types.ts`** — TWO edits:\n   - add `'<lang>',` to the `LANGUAGES` const (before `'unknown'`);\n   - add `'**/*.<ext>',` to `DEFAULT_CONFIG.include`. **Don't skip this** — it's\n     the file-scan allowlist; without the glob, `codegraph init` finds **0\n     files** even though detection/extraction are wired.\n2. **`src/extraction/grammars.ts`** — three maps:\n   - `WASM_GRAMMAR_FILES`: `<lang>: 'tree-sitter-<lang>.wasm',`\n   - `EXTENSION_MAP`: each file extension → `'<lang>'` (e.g. `'.lua': 'lua',`)\n   - `getLanguageDisplayName`: `<lang>: '<Display Name>',`\n   - **vendored only**: add `<lang>` to the\n     `(lang === 'pascal' || lang === 'scala' || …)` wasm-path branch.\n3. **`src/extraction/languages/<lang>.ts`** — new file exporting\n   `export const <lang>Extractor: LanguageExtractor = { … }`. Map the node types\n   from Step 3. Required fields: `functionTypes`, `classTypes`, `methodTypes`,\n   `interfaceTypes`, `structTypes`, `enumTypes`, `typeAliasTypes`,\n   `importTypes`, `callTypes`, `variableTypes`, `nameField`, `bodyField`,\n   `paramsField`. Add hooks as the grammar needs them (`getSignature`,\n   `getVisibility`, `isExported`, `extractImport`, `visitNode`, `getReceiverType`,\n   `interfaceKind`, `enumMemberTypes`, etc. — see\n   `src/extraction/tree-sitter-types.ts`).\n4. **`src/extraction/languages/index.ts`** — `import { <lang>Extractor } from\n   './<lang>';` and add `<lang>: <lang>Extractor,` to `EXTRACTORS`.\n\n**Sometimes a 5th, core touch in `src/extraction/tree-sitter.ts`** — variable\nextraction has per-language branches in `extractVariable` (the generic fallback\nonly finds direct `identifier`/`variable_declarator` children). If the grammar\nnests declared names (e.g. Lua's `variable_declaration → variable_list`), add a\n`} else if (this.language === '<lang>')` branch there, mirroring the existing\nts/python/go ones. Import forms that aren't a distinct node (Lua/Ruby `require`\nis a *call*) are handled in the extractor's `visitNode` hook instead.\n\n### Step 5 — Build + verify loop\n\n```bash\nnpm run build            # tsc + copy-assets (copies any vendored *.wasm into dist/)\n```\nIndex a small sample repo and check extraction:\n```bash\n( cd <sample-repo> && codegraph init -i )\nnode scripts/add-lang/verify-extraction.mjs <sample-repo> <lang>\n```\n`verify-extraction.mjs` fails (exit 1) if the language isn't detected or only\n`file`/`import` nodes were produced — the classic symptom of wrong node-type\nnames. On FAIL or a thin WARN: re-run `dump-ast.mjs` on a richer file, fix the\nmappings in `<lang>.ts`, `npm run build`, re-index, re-verify. **Repeat until\nPASS.**\n\n### Step 6 — Tests\n\nAdd to `__tests__/extraction.test.ts`, modeled on the `Rust Extraction` block:\n- a `detectLanguage` assertion in `describe('Language Detection')`\n- a `describe('<Lang> Extraction')` block asserting functions/classes/imports\n  are extracted from an inline source string.\n```bash\nnpx vitest run __tests__/extraction.test.ts\n```\nGreen before continuing.\n\n### Step 7 — Auto-pick 3 repos + corpus\n\nPick **without asking**. Find candidates, then curate 3 that are genuinely\n`<lang>`-dominant, one per size tier:\n```bash\ngh search repos --language=<lang> --sort=stars --limit 40 \\\n  --json fullName,stargazerCount,description\n```\nTiers (match `corpus.json`): **Small** <~150 files · **Medium** ~150–1500 ·\n**Large** >~1500. Skip repos that are tagged `<lang>` but mostly another\nlanguage. Write one cross-file architecture **question** per repo (the kind that\nneeds tracing across files). Add a `\"<Language>\"` block to\n`.claude/skills/agent-eval/corpus.json` (fields: `name`, `repo`, `size`,\n`files`, `question`) so `/agent-eval` can reuse them.\n\n### Step 8 — Benchmark all 3 (extraction + A/B)\n\nMake the dev build the codegraph on PATH **once**, then loop:\n```bash\nnpm run build && ./scripts/local-install.sh\nscripts/add-lang/bench.sh <lang> <name> <url> \"<question>\" headless   # ×3\n```\n`bench.sh` clones (shared `/tmp/codegraph-corpus`), wipes + indexes, runs\n`verify-extraction.mjs`, then the with/without retrieval A/B via\n`scripts/agent-eval/run-all.sh` (skips the paid A/B if extraction is broken).\nRead each `parse-run.mjs` summary printed by `run-all.sh`: tool calls, file\n`Read`s, Grep/Bash, codegraph-tool calls, duration, and **cost** — for both the\n`with` and `without` arms. After the loop, restore the dev link if needed:\n`./scripts/local-install.sh`.\n\n### Step 9 — Docs + CHANGELOG\n\n- **README.md**: add `<Lang>` to the \"19+ Languages\" feature bullet, and add a\n  row to the **Supported Languages** table:\n  `| <Lang> | \\`.ext\\` | Full support (classes, methods, …) |`.\n- **CHANGELOG.md**: add an `## [Unreleased]` section at the top (above the\n  latest version) with `### Added` → a user-perspective bullet, e.g.\n  *\"CodeGraph now indexes **<Lang>** (`.ext`) — functions, classes, imports, and\n  call edges.\"* If `## [Unreleased]` already exists, append under it. (It's\n  folded into the next versioned block at release time.)\n\n### Step 10 — Report (do NOT commit)\n\nSummarize for review:\n- **Files changed**: the 4 wiring edits + new extractor + tests + README +\n  CHANGELOG + corpus.json (+ any vendored `.wasm`).\n- **Extraction** per repo: files / nodes / edges / `verify-extraction` result.\n- **A/B** per repo: `with` vs `without` (tool calls, file Reads, cost) and a\n  one-line verdict — did codegraph reduce effort, and did both arms reach a\n  correct answer?\n- **Gaps / follow-ups** (node types not yet mapped, resolution edges missing,\n  framework routes, etc.).\n\nHand the changes to the user. **Do not** run `git commit`/`push` or publish —\nreleases go through the GitHub Actions Release workflow.\n\n## Notes\n- The A/B spawns real **paid** `claude -p` runs (opus, `--max-budget-usd`),\n  2 arms × 3 repos. The corpus dir `/tmp/codegraph-corpus` is shared with\n  `/agent-eval`, so clones are reused across runs.\n- Any new `*.wasm` must live in `src/extraction/wasm/` — `copy-assets` (run by\n  `npm run build`) ships it; otherwise it won't be in `dist/`.\n- An index must be served by the **same** binary that built it. Step 8 builds +\n  links the dev build first, so this holds.\n- If a grammar can't be obtained, or extraction can't reach PASS, **STOP and\n  report** — don't ship a half-wired language.\n\n<!-- File: .claude/skills/agent-eval/corpus.json (Tokens: ~5825 | Category: anthropic-skill) -->\n{\n  \"_comment\": \"Test corpus for /agent-eval. Add entries freely. size: Small (<~150 files), Medium (~150-1500), Large (>~1500). 'question' is a representative architectural question that exercises cross-file understanding.\",\n  \"TypeScript\": [\n    {\n      \"name\": \"ky\",\n      \"repo\": \"https://github.com/sindresorhus/ky\",\n      \"size\": \"Small\",\n      \"files\": \"~25\",\n      \"question\": \"How does ky implement request retries and timeouts?\"\n    },\n    {\n      \"name\": \"excalidraw\",\n      \"repo\": \"https://github.com/excalidraw/excalidraw\",\n      \"size\": \"Medium\",\n      \"files\": \"~600\",\n      \"question\": \"How does Excalidraw render and update canvas elements?\"\n    },\n    {\n      \"name\": \"vscode\",\n      \"repo\": \"https://github.com/microsoft/vscode\",\n      \"size\": \"Large\",\n      \"files\": \"~10000\",\n      \"question\": \"How does the extension host communicate with the main process?\"\n    }\n  ],\n  \"JavaScript\": [\n    {\n      \"name\": \"express\",\n      \"repo\": \"https://github.com/expressjs/express\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does Express route a request through its middleware stack?\"\n    }\n  ],\n  \"Go\": [\n    {\n      \"name\": \"cobra\",\n      \"repo\": \"https://github.com/spf13/cobra\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does cobra parse commands and flags?\"\n    },\n    {\n      \"name\": \"gin\",\n      \"repo\": \"https://github.com/gin-gonic/gin\",\n      \"size\": \"Medium\",\n      \"files\": \"~150\",\n      \"question\": \"How does gin route requests through its middleware chain?\"\n    },\n    {\n      \"name\": \"terraform\",\n      \"repo\": \"https://github.com/hashicorp/terraform\",\n      \"size\": \"Large\",\n      \"files\": \"~4000\",\n      \"question\": \"How does Terraform build and walk the resource dependency graph?\"\n    },\n    {\n      \"name\": \"cosmos-sdk\",\n      \"repo\": \"https://github.com/cosmos/cosmos-sdk\",\n      \"size\": \"Large\",\n      \"files\": \"~5000\",\n      \"question\": \"How does a bank module MsgSend message reach the account balance update? Trace the cross-module call path from the bank keeper's Send handler through to the account/balance store update.\"\n    }\n  ],\n  \"Python\": [\n    {\n      \"name\": \"click\",\n      \"repo\": \"https://github.com/pallets/click\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does click parse command-line arguments into commands?\"\n    },\n    {\n      \"name\": \"flask\",\n      \"repo\": \"https://github.com/pallets/flask\",\n      \"size\": \"Medium\",\n      \"files\": \"~90\",\n      \"question\": \"How does Flask dispatch a request to a view function?\"\n    },\n    {\n      \"name\": \"django\",\n      \"repo\": \"https://github.com/django/django\",\n      \"size\": \"Large\",\n      \"files\": \"~2700\",\n      \"question\": \"How does Django's ORM build and execute a query from a QuerySet?\"\n    }\n  ],\n  \"Rust\": [\n    {\n      \"name\": \"clap\",\n      \"repo\": \"https://github.com/clap-rs/clap\",\n      \"size\": \"Medium\",\n      \"files\": \"~200\",\n      \"question\": \"How does clap parse arguments against a derived command definition?\"\n    },\n    {\n      \"name\": \"tokio\",\n      \"repo\": \"https://github.com/tokio-rs/tokio\",\n      \"size\": \"Large\",\n      \"files\": \"~700\",\n      \"question\": \"How does tokio schedule and run async tasks on its runtime?\"\n    },\n    {\n      \"name\": \"deno\",\n      \"repo\": \"https://github.com/denoland/deno\",\n      \"size\": \"Large\",\n      \"files\": \"~1500\",\n      \"question\": \"How does Deno load and execute a TypeScript module?\"\n    }\n  ],\n  \"Java\": [\n    {\n      \"name\": \"gson\",\n      \"repo\": \"https://github.com/google/gson\",\n      \"size\": \"Medium\",\n      \"files\": \"~200\",\n      \"question\": \"How does Gson serialize an object to JSON?\"\n    },\n    {\n      \"name\": \"okhttp\",\n      \"repo\": \"https://github.com/square/okhttp\",\n      \"size\": \"Medium\",\n      \"files\": \"~640\",\n      \"question\": \"How does OkHttp process a request through its interceptor chain?\"\n    },\n    {\n      \"name\": \"guava\",\n      \"repo\": \"https://github.com/google/guava\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Guava's CacheBuilder build and configure a cache?\"\n    }\n  ],\n  \"Kotlin\": [\n    {\n      \"name\": \"koin\",\n      \"repo\": \"https://github.com/InsertKoinIO/koin\",\n      \"size\": \"Medium\",\n      \"files\": \"~300\",\n      \"question\": \"How does Koin resolve and inject dependencies?\"\n    },\n    {\n      \"name\": \"leakcanary\",\n      \"repo\": \"https://github.com/square/leakcanary\",\n      \"size\": \"Medium\",\n      \"files\": \"~250\",\n      \"question\": \"How does LeakCanary detect and analyze a memory leak?\"\n    }\n  ],\n  \"Swift\": [\n    {\n      \"name\": \"alamofire\",\n      \"repo\": \"https://github.com/Alamofire/Alamofire\",\n      \"size\": \"Small\",\n      \"files\": \"~100\",\n      \"question\": \"How does Alamofire build, send, and validate a request?\"\n    }\n  ],\n  \"C#\": [\n    {\n      \"name\": \"serilog\",\n      \"repo\": \"https://github.com/serilog/serilog\",\n      \"size\": \"Medium\",\n      \"files\": \"~250\",\n      \"question\": \"How does Serilog route a log event to its sinks?\"\n    },\n    {\n      \"name\": \"jellyfin\",\n      \"repo\": \"https://github.com/jellyfin/jellyfin\",\n      \"size\": \"Large\",\n      \"files\": \"~2500\",\n      \"question\": \"How does Jellyfin scan and identify items in a media library?\"\n    }\n  ],\n  \"Ruby\": [\n    {\n      \"name\": \"sinatra\",\n      \"repo\": \"https://github.com/sinatra/sinatra\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does Sinatra match a request to a route handler?\"\n    },\n    {\n      \"name\": \"discourse\",\n      \"repo\": \"https://github.com/discourse/discourse\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Discourse create and render a new post?\"\n    }\n  ],\n  \"PHP\": [\n    {\n      \"name\": \"slim\",\n      \"repo\": \"https://github.com/slimphp/Slim\",\n      \"size\": \"Small\",\n      \"files\": \"~80\",\n      \"question\": \"How does Slim handle a request through its middleware?\"\n    },\n    {\n      \"name\": \"laravel\",\n      \"repo\": \"https://github.com/laravel/framework\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does Laravel resolve and dispatch a route to a controller?\"\n    }\n  ],\n  \"C\": [\n    {\n      \"name\": \"redis\",\n      \"repo\": \"https://github.com/redis/redis\",\n      \"size\": \"Large\",\n      \"files\": \"~600\",\n      \"question\": \"How does Redis parse and dispatch a client command?\"\n    }\n  ],\n  \"C++\": [\n    {\n      \"name\": \"json\",\n      \"repo\": \"https://github.com/nlohmann/json\",\n      \"size\": \"Small\",\n      \"files\": \"~100\",\n      \"question\": \"How does nlohmann::json parse a JSON string into a value?\"\n    },\n    {\n      \"name\": \"grpc\",\n      \"repo\": \"https://github.com/grpc/grpc\",\n      \"size\": \"Large\",\n      \"files\": \"~3000\",\n      \"question\": \"How does gRPC dispatch an incoming RPC to its handler?\"\n    }\n  ],\n  \"Dart\": [\n    {\n      \"name\": \"flutter\",\n      \"repo\": \"https://github.com/flutter/flutter\",\n      \"size\": \"Large\",\n      \"files\": \"~6000\",\n      \"question\": \"How does Flutter build and lay out a widget tree?\"\n    }\n  ],\n  \"Svelte\": [\n    {\n      \"name\": \"shadcn-svelte\",\n      \"repo\": \"https://github.com/huntabyte/shadcn-svelte\",\n      \"size\": \"Medium\",\n      \"files\": \"~600\",\n      \"question\": \"How do shadcn-svelte components compose and apply their styling?\"\n    }\n  ],\n  \"Lua\": [\n    {\n      \"name\": \"lualine.nvim\",\n      \"repo\": \"https://github.com/nvim-lualine/lualine.nvim\",\n      \"size\": \"Small\",\n      \"files\": \"~120\",\n      \"question\": \"How does lualine assemble and render its statusline sections and components?\"\n    },\n    {\n      \"name\": \"telescope.nvim\",\n      \"repo\": \"https://github.com/nvim-telescope/telescope.nvim\",\n      \"size\": \"Medium\",\n      \"files\": \"~80\",\n      \"question\": \"How does Telescope wire a picker to its finder, sorter, and previewer?\"\n    },\n    {\n      \"name\": \"kong\",\n      \"repo\": \"https://github.com/Kong/kong\",\n      \"size\": \"Large\",\n      \"files\": \"~1330\",\n      \"question\": \"How does Kong execute plugins across a request's lifecycle phases?\"\n    }\n  ],\n  \"Luau\": [\n    {\n      \"name\": \"Knit\",\n      \"repo\": \"https://github.com/Sleitnick/Knit\",\n      \"size\": \"Small\",\n      \"files\": \"~10\",\n      \"question\": \"How does Knit register services and expose them to clients?\"\n    },\n    {\n      \"name\": \"vide\",\n      \"repo\": \"https://github.com/centau/vide\",\n      \"size\": \"Small\",\n      \"files\": \"~40\",\n      \"question\": \"How does vide track reactive sources and re-run effects when state changes?\"\n    },\n    {\n      \"name\": \"Fusion\",\n      \"repo\": \"https://github.com/dphfox/Fusion\",\n      \"size\": \"Medium\",\n      \"files\": \"~115\",\n      \"question\": \"How does Fusion build and update its reactive UI graph from state objects?\"\n    }\n  ],\n  \"Objective-C\": [\n    {\n      \"name\": \"Masonry\",\n      \"repo\": \"https://github.com/SnapKit/Masonry\",\n      \"size\": \"Small\",\n      \"files\": \"~50\",\n      \"question\": \"How does Masonry build and activate Auto Layout constraints from its block DSL?\"\n    },\n    {\n      \"name\": \"FMDB\",\n      \"repo\": \"https://github.com/ccgus/fmdb\",\n      \"size\": \"Medium\",\n      \"files\": \"~80\",\n      \"question\": \"How does FMDB execute a prepared SQL statement and bind parameters?\"\n    },\n    {\n      \"name\": \"SDWebImage\",\n      \"repo\": \"https://github.com/SDWebImage/SDWebImage\",\n      \"size\": \"Large\",\n      \"files\": \"~400\",\n      \"question\": \"How does SDWebImage download, cache, and decode an image for a UIImageView?\"\n    }\n  ],\n  \"Mixed iOS (Swift+ObjC)\": [\n    {\n      \"name\": \"Charts\",\n      \"repo\": \"https://github.com/danielgindi/Charts\",\n      \"size\": \"Small\",\n      \"files\": \"~270\",\n      \"question\": \"How does the ChartsDemo ObjC demo controller drive the Swift Charts library to animate and notify a data update?\"\n    },\n    {\n      \"name\": \"realm-swift\",\n      \"repo\": \"https://github.com/realm/realm-swift\",\n      \"size\": \"Medium\",\n      \"files\": \"~370\",\n      \"question\": \"How does a Swift `Realm.write { realm.add(obj) }` reach the Objective-C persistence layer?\"\n    },\n    {\n      \"name\": \"wikipedia-ios\",\n      \"repo\": \"https://github.com/wikimedia/wikipedia-ios\",\n      \"size\": \"Large\",\n      \"files\": \"~1700\",\n      \"question\": \"How does tapping a search result reach the article-fetch network call across the Swift / ObjC boundary?\"\n    }\n  ],\n  \"React Native (legacy bridge + TurboModule)\": [\n    {\n      \"name\": \"@react-native-async-storage\",\n      \"repo\": \"https://github.com/react-native-async-storage/async-storage\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does `setItem` in JS reach the native `legacy_multiSet` implementation?\"\n    },\n    {\n      \"name\": \"react-native-svg\",\n      \"repo\": \"https://github.com/software-mansion/react-native-svg\",\n      \"size\": \"Medium\",\n      \"files\": \"~700\",\n      \"question\": \"How does a JS `Svg.getTotalLength(...)` reach the iOS / Android native implementation via TurboModule?\"\n    },\n    {\n      \"name\": \"react-native-firebase\",\n      \"repo\": \"https://github.com/invertase/react-native-firebase\",\n      \"size\": \"Large\",\n      \"files\": \"~1100\",\n      \"question\": \"How does a native iOS push notification reach the JS `messaging().onMessage(...)` listener?\"\n    }\n  ],\n  \"Expo Modules\": [\n    {\n      \"name\": \"expo-haptics\",\n      \"repo\": \"https://github.com/expo/expo/tree/main/packages/expo-haptics\",\n      \"size\": \"Small\",\n      \"files\": \"~15\",\n      \"question\": \"How does `Haptics.notificationAsync(...)` in JS reach `UINotificationFeedbackGenerator` in the Swift Module?\"\n    },\n    {\n      \"name\": \"expo-camera\",\n      \"repo\": \"https://github.com/expo/expo/tree/main/packages/expo-camera\",\n      \"size\": \"Medium\",\n      \"files\": \"~70\",\n      \"question\": \"How does a JS `CameraView.takePictureAsync(options)` reach the native AVCaptureSession / CameraDevice call?\"\n    }\n  ],\n  \"React Native Fabric (view components)\": [\n    {\n      \"name\": \"react-native-segmented-control\",\n      \"repo\": \"https://github.com/react-native-segmented-control/segmented-control\",\n      \"size\": \"Small\",\n      \"files\": \"~25\",\n      \"question\": \"How does JSX `<SegmentedControl onChange={cb}/>` reach the native onChange handler on iOS/Android?\"\n    },\n    {\n      \"name\": \"react-native-screens\",\n      \"repo\": \"https://github.com/software-mansion/react-native-screens\",\n      \"size\": \"Medium\",\n      \"files\": \"~1200\",\n      \"question\": \"How does JSX `<ScreenStack>` reach the native RNSScreenStackView component?\"\n    },\n    {\n      \"name\": \"react-native-skia\",\n      \"repo\": \"https://github.com/Shopify/react-native-skia\",\n      \"size\": \"Large\",\n      \"files\": \"~1000\",\n      \"question\": \"How does a `<SkiaPictureView/>` JSX usage reach the iOS / Android native renderer?\"\n    }\n  ],\n  \"R\": [\n    {\n      \"name\": \"AnomalyDetection\",\n      \"repo\": \"https://github.com/twitter/AnomalyDetection\",\n      \"size\": \"Small\",\n      \"files\": \"~24\",\n      \"question\": \"How does AnomalyDetectionTs go from the exported entry function to the underlying S-H-ESD statistical test? Name the functions on the path in order.\"\n    },\n    {\n      \"name\": \"dplyr\",\n      \"repo\": \"https://github.com/tidyverse/dplyr\",\n      \"size\": \"Medium\",\n      \"files\": \"~450\",\n      \"question\": \"When mutate() is called on a grouped data frame, which functions handle the grouping and expression evaluation, in order, from mutate() down?\"\n    },\n    {\n      \"name\": \"ggplot2\",\n      \"repo\": \"https://github.com/tidyverse/ggplot2\",\n      \"size\": \"Large\",\n      \"files\": \"~1150\",\n      \"question\": \"When a ggplot object is printed, how does the plot actually get built and drawn — trace the path from print/plot to where geoms render. Name the key functions in order.\"\n    }\n  ],\n  \"COBOL\": [\n    {\n      \"name\": \"cics-genapp\",\n      \"repo\": \"https://github.com/cicsdev/cics-genapp\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"When a new insurance policy is added through the LGTESTP1 CICS test harness, which programs handle the request on the way to the DB2 insert? Trace the path and name the programs in order.\"\n    },\n    {\n      \"name\": \"carddemo\",\n      \"repo\": \"https://github.com/aws-samples/aws-mainframe-modernization-carddemo\",\n      \"size\": \"Medium\",\n      \"files\": \"~75\",\n      \"question\": \"How does the online bill-payment flow work — from the COBIL00C screen program to the transaction record being written? Name the paragraphs performed in order and the copybooks that define the records involved.\"\n    },\n    {\n      \"name\": \"CobolCraft\",\n      \"repo\": \"https://github.com/meyfa/CobolCraft\",\n      \"size\": \"Medium\",\n      \"files\": \"~270\",\n      \"question\": \"How does an incoming player chat message travel from packet handling to being broadcast to the other connected players? Name the programs on the path in order.\"\n    }\n  ],\n  \"VB.NET\": [\n    {\n      \"name\": \"policyplus\",\n      \"repo\": \"https://github.com/Fleex255/PolicyPlus\",\n      \"size\": \"Small\",\n      \"files\": \"~94\",\n      \"question\": \"When the user toggles a policy to Enabled in the policy-setting editor and clicks OK, how does the new state end up written into the loaded policy source (POL file or registry)? Trace the path from the EditSetting dialog to the concrete write.\"\n    },\n    {\n      \"name\": \"scrawler\",\n      \"repo\": \"https://github.com/AAndyProgram/SCrawler\",\n      \"size\": \"Medium\",\n      \"files\": \"~320\",\n      \"question\": \"When a user download is started for a Reddit user, how does the request flow from the user-level download entry point through the shared downloader base into the Reddit site plugin, and where do downloaded media items get appended to the user's content list?\"\n    },\n    {\n      \"name\": \"staxrip\",\n      \"repo\": \"https://github.com/staxrip/staxrip\",\n      \"size\": \"Medium\",\n      \"files\": \"~145\",\n      \"question\": \"When a job finishes video encoding, how does staxrip decide which muxer runs and how does the muxer command line get built and executed? Trace from job processing to the mkvmerge invocation.\"\n    }\n  ],\n  \"Erlang\": [\n    {\n      \"name\": \"cowboy\",\n      \"repo\": \"https://github.com/ninenines/cowboy\",\n      \"size\": \"Small\",\n      \"files\": \"~190\",\n      \"question\": \"How does an incoming HTTP request travel from cowboy's connection process to a user-defined handler's init/2 callback? Trace the path through the stream handler and middleware chain.\"\n    },\n    {\n      \"name\": \"ejabberd\",\n      \"repo\": \"https://github.com/processone/ejabberd\",\n      \"size\": \"Medium\",\n      \"files\": \"~410\",\n      \"question\": \"When a client sends a chat message, how does the stanza get from the receiving C2S process to the recipient's session on the same node? Trace the path through the router and session manager.\"\n    },\n    {\n      \"name\": \"emqx\",\n      \"repo\": \"https://github.com/emqx/emqx\",\n      \"size\": \"Large\",\n      \"files\": \"~2450\",\n      \"question\": \"How does a PUBLISH packet from an MQTT client reach the sessions of matching subscribers? Trace the flow from the connection/channel layer through the broker's routing to session delivery.\"\n    }\n  ],\n  \"Solidity\": [\n    {\n      \"name\": \"solmate\",\n      \"repo\": \"https://github.com/transmissions11/solmate\",\n      \"size\": \"Small\",\n      \"files\": \"~60\",\n      \"question\": \"How does solmate's ERC20 transferFrom enforce allowance and update balances? Trace the flow including the permit() signature path.\"\n    },\n    {\n      \"name\": \"solady\",\n      \"repo\": \"https://github.com/Vectorized/solady\",\n      \"size\": \"Medium\",\n      \"files\": \"~270\",\n      \"question\": \"How does solady's ERC20 implementation handle a permit() call — from signature recovery through nonce update to allowance write?\"\n    },\n    {\n      \"name\": \"openzeppelin-contracts\",\n      \"repo\": \"https://github.com/OpenZeppelin/openzeppelin-contracts\",\n      \"size\": \"Large\",\n      \"files\": \"~400\",\n      \"question\": \"How does an OpenZeppelin AccessControl-protected function check the caller's role? Trace from the onlyRole modifier through hasRole to the role storage.\"\n    }\n  ],\n  \"CUDA\": [\n    {\n      \"name\": \"llm.c\",\n      \"repo\": \"https://github.com/karpathy/llm.c\",\n      \"size\": \"Small\",\n      \"files\": \"~76\",\n      \"question\": \"How does the attention forward pass reach the GPU in the CUDA training path? Trace from attention_forward to the kernels it launches, and explain where softmax happens.\"\n    },\n    {\n      \"name\": \"flash-attention\",\n      \"repo\": \"https://github.com/Dao-AILab/flash-attention\",\n      \"size\": \"Medium\",\n      \"files\": \"~900\",\n      \"question\": \"How does a Python call to flash_attn_func reach the CUDA kernel that computes forward attention? Trace the dispatch path from the Python API through the C++ binding to the kernel launch.\"\n    },\n    {\n      \"name\": \"cutlass\",\n      \"repo\": \"https://github.com/NVIDIA/cutlass\",\n      \"size\": \"Large\",\n      \"files\": \"~2700\",\n      \"question\": \"When a cutlass device-level GEMM (cutlass::gemm::device::Gemm) is invoked, how does it reach the GPU kernel? Trace from the operator() call to the kernel entry point and its launch site.\"\n    }\n  ],\n  \"Terraform\": [\n    {\n      \"name\": \"terraform-aws-vpc\",\n      \"repo\": \"https://github.com/terraform-aws-modules/terraform-aws-vpc\",\n      \"size\": \"Small\",\n      \"files\": \"~77\",\n      \"question\": \"How does the private_subnets variable shape the NAT gateway setup? Trace from the variable through the subnet and NAT gateway resources to the outputs that expose the private subnets.\"\n    },\n    {\n      \"name\": \"cloud-foundation-fabric\",\n      \"repo\": \"https://github.com/GoogleCloudPlatform/cloud-foundation-fabric\",\n      \"size\": \"Medium\",\n      \"files\": \"~990\",\n      \"question\": \"In the project module (modules/project), how does the iam variable turn into actual IAM bindings on the project, and what depends on the module's project_id output elsewhere in the repo?\"\n    },\n    {\n      \"name\": \"terraform-aws-components\",\n      \"repo\": \"https://github.com/cloudposse/terraform-aws-components\",\n      \"size\": \"Large\",\n      \"files\": \"~1800\",\n      \"question\": \"In the eks/cluster component, how does the cluster IAM role get created and reach the EKS cluster resource, and which outputs expose cluster identity to other components?\"\n    }\n  ],\n  \"ArkTS\": [\n    {\n      \"name\": \"HarmoneyOpenEye\",\n      \"repo\": \"https://github.com/WinWang/HarmoneyOpenEye\",\n      \"size\": \"Small\",\n      \"files\": \"~82\",\n      \"question\": \"How does the home page get its feed data from the network layer, and how does that data end up rendered as the list on screen? Trace the flow from the HTTP request through the view model into the home page UI.\"\n    },\n    {\n      \"name\": \"CoolMallArkTS\",\n      \"repo\": \"https://github.com/Joker-x-dev/CoolMallArkTS\",\n      \"size\": \"Medium\",\n      \"files\": \"~528\",\n      \"question\": \"When the user adds a product to the cart from the goods detail page, how does the item travel from the UI action to persistent storage? Trace the flow across the feature and core modules.\"\n    },\n    {\n      \"name\": \"applications_app_samples\",\n      \"repo\": \"https://github.com/openharmony/applications_app_samples\",\n      \"size\": \"Large\",\n      \"files\": \"~9500\",\n      \"question\": \"In the OrangeShopping sample app, how does the product detail page's bottom bar (add to cart / buy) lead to the order placement flow? Trace from the bottom navigation component to where the order is created.\"\n    }\n  ],\n  \"Nix\": [\n    {\n      \"name\": \"agenix\",\n      \"repo\": \"https://github.com/ryantm/agenix\",\n      \"size\": \"Small\",\n      \"files\": \"~13\",\n      \"question\": \"When a NixOS system activates, how does a secret declared under age.secrets get decrypted and installed at its runtime path? Trace the flow from the age.secrets option definition to the activation machinery that performs the decryption.\"\n    },\n    {\n      \"name\": \"nix-darwin\",\n      \"repo\": \"https://github.com/nix-darwin/nix-darwin\",\n      \"size\": \"Medium\",\n      \"files\": \"~207\",\n      \"question\": \"How does setting services.yabai.enable = true become a running launchd service? Trace the flow from the yabai module's options to the launchd daemon definition that generates the plist.\"\n    },\n    {\n      \"name\": \"home-manager\",\n      \"repo\": \"https://github.com/nix-community/home-manager\",\n      \"size\": \"Large\",\n      \"files\": \"~3390\",\n      \"question\": \"How does programs.git.enable produce the final git config file in the user's home directory? Trace the flow from the git program module to the home-files machinery that links generated files into place.\"\n    }\n  ]\n}\n\n<!-- File: .claude/skills/agent-eval/SKILL.md (Tokens: ~1019 | Category: anthropic-skill) -->\n---\nname: agent-eval\ndescription: Benchmark CodeGraph retrieval quality on a real codebase by comparing agent behavior with vs without CodeGraph. Use when the user runs /agent-eval or asks to test, benchmark, audit, or validate a codegraph version (the local dev build or a published npm version) against a language's repo.\n---\n\n# CodeGraph Quality Audit\n\nMeasures how much CodeGraph helps an agent versus plain grep/read, for a chosen\ncodegraph version on a chosen real-world repo. Drives the harness in\n`scripts/agent-eval/`.\n\n## Prerequisites\n- `tmux` 3+, a logged-in `claude` CLI, `node`, `git` (macOS/Linux).\n- Run from the codegraph repo root.\n\n## Workflow\n\nCopy this checklist:\n```\n- [ ] 1. Pick version (local or npm)\n- [ ] 2. Pick language\n- [ ] 3. Pick repo by size\n- [ ] 4. Pick harness (headless / tmux / both)\n- [ ] 5. Run audit.sh in the background\n- [ ] 6. Report results\n```\n\n**Step 1 — version.** Ask with `AskUserQuestion`: which codegraph version to test.\nOffer \"Local dev build\" and \"Latest published\"; the free-text \"Other\" lets the\nuser type a specific version (e.g. `0.7.10`). Map the answer to a VERSION token:\n- \"Local dev build\" → `local`\n- \"Latest published\" → `latest`\n- a typed version → that string (e.g. `0.7.10`)\n\n**Step 2 — language.** Read `.claude/skills/agent-eval/corpus.json`. Ask with\n`AskUserQuestion` which language to test, listing the languages that have entries.\n\n**Step 3 — repo.** From the chosen language's entries, ask which repo. Label each\noption with its size and file count, e.g. `excalidraw — Medium (~600 files)`.\nEach entry carries the `repo` URL and a representative `question`.\n\n**Step 4 — harness.** Ask with `AskUserQuestion` which harness to run, and map\nthe answer to a MODE token:\n- \"Headless\" → `headless` — `claude -p` with stream-json: exact tokens/cost and a\n  clean tool sequence (2 runs, fast, no TTY).\n- \"Interactive (tmux)\" → `tmux` — drives the real Claude TUI in tmux: faithful\n  Explore-subagent behavior, metrics from session logs (2 runs, slower).\n- \"Both\" → `all` — headless + interactive (4 runs).\n\n**Step 5 — run.** Launch in the background (sets the version, clones if missing,\nwipes + re-indexes, runs the chosen arms — several minutes):\n```bash\nscripts/agent-eval/audit.sh <VERSION> <repo-name> <repo-url> \"<question>\" <MODE>\n```\n\n**Step 6 — report.** When the job finishes, read the log and report per arm:\n- Headless (`parse-run.mjs`): total tool calls, file `Read`s, Grep/Bash,\n  codegraph-tool calls, duration, **total cost**.\n- Interactive (`parse-session.mjs`): the `VERDICT: codegraph_explore used Nx |\n  Read N | Grep/Bash N` and `TOKENS:` lines.\n- Both paths also print the three feedback metrics — residual context occupancy,\n  explore sufficiency, allocation efficiency — and a headless A/B ends with a\n  side-by-side `ARM COMPARISON` table. Report that table, and check its\n  contamination row first: `CLI calls that RETURNED output` > 0 means the arm\n  reached codegraph through Bash and its numbers are void. How to read the rest:\n  `docs/benchmarks/agent-eval-feedback-metrics.md`.\n\nLead with cost + tool/Read counts — they are the reliable signals; raw token\nin/out are confounded by subagent delegation and prompt caching. State whether\ncodegraph reduced effort and whether both arms reached a correct answer.\n\n## Notes\n- The index is rebuilt every run (`audit.sh` wipes `.codegraph`) — different\n  versions extract differently, so an index must be served by the same binary\n  that built it.\n- `audit.sh` temporarily mutates the global `codegraph` install for the test,\n  then restores your dev link via `local-install.sh`.\n- Corpus repos are cloned to `/tmp/codegraph-corpus` (reused if already present).\n- Add or edit repos in `corpus.json` (fields: `name`, `repo`, `size`, `files`,\n  `question`).\n\n<!-- File: .cursor/rules/codegraph.mdc (Tokens: ~565 | Category: cursor-rule) -->\n---\ndescription: CodeGraph MCP usage guide — one tool, codegraph_explore\nalwaysApply: true\n---\n<!-- CODEGRAPH_START -->\n## CodeGraph\n\nThis project has a CodeGraph MCP server configured, exposing a single tool: `codegraph_explore`. CodeGraph is a tree-sitter-parsed knowledge graph of every symbol, edge, and file. Reads are sub-millisecond and return structural information grep cannot.\n\n### Use codegraph_explore instead of reading files\n\nReach for `codegraph_explore` before grep/find or Read for any **structural** question — how does X work, how does X reach Y, what calls what, where is X defined, or surveying an area. It takes a natural-language question or a bag of symbol/file names and returns the relevant symbols' **verbatim, line-numbered source** grouped by file (the same `<n>\\t<line>` shape Read gives you, safe to Edit from), plus the call paths between them — including dynamic-dispatch hops (callbacks, React re-render, JSX children) grep can't follow — and a blast-radius summary of what depends on them. Name a file or symbol in the query to read its current source.\n\n### Rules of thumb\n\n- **Answer directly — don't delegate exploration.** ONE `codegraph_explore` usually answers the whole question; follow up with another `codegraph_explore` naming more specific symbols if you need more. Codegraph IS the pre-built index, so spawning a separate file-reading sub-task/agent — or running a grep + read loop — repeats work codegraph already did and costs more for the same answer.\n- **Trust codegraph results.** They come from a full AST parse. Do NOT re-verify them with grep — that's slower, less accurate, and wastes context.\n- **Don't grep or Read first** to find or understand indexed code — one `codegraph_explore` returns the relevant source in a single round-trip. Reach for raw Read/Grep only to confirm a specific detail codegraph didn't cover, or for what it doesn't index (configs, docs).\n- **Index lag — check the staleness banner, don't guess a wait.** When a codegraph response starts with \"⚠️ Some files referenced below were edited since the last index sync…\", the listed files are pending re-index — Read those specific files for accurate content. Files NOT in that banner are fresh and codegraph is authoritative for them.\n\n### If `.codegraph/` doesn't exist\n\nThe MCP server returns \"not initialized.\" Ask the user: *\"I notice this project doesn't have CodeGraph initialized. Want me to run `codegraph init -i` to build the index?\"*\n<!-- CODEGRAPH_END -->\n\n<!-- File: CLAUDE.md (Tokens: ~172 | Category: claude-rule) -->\n# CLAUDE.md\n\nClaude Code project guidance for this repository.\n\nPrimary instructions live in the canonical agent guide — import it:\n\n@AGENTS.md\n\n## Claude-only notes\n\n- Prefer the repo-root `AGENTS.md` as the source of truth for build/test/architecture/house rules. Edit that file (not this wrapper) when guidance changes.\n- Claude Code can `@`-import nested guides too (e.g. `@docs/AGENTS.md`) when working on design/eval docs; Codex loads nested `AGENTS.md` automatically when the session cwd is under that directory.\n- Do not reintroduce a duplicated `## CodeGraph` MCP tool-guidance block here — `src/mcp/server-instructions.ts` is the single source of truth (issue #529); the installer strips legacy marker blocks on upgrade.\n</yakaai_skills>"}