---
name: multi-agent-graph
language: en
description: "Build and query this repo's code graph: a deterministic, LLM-free map of symbols, imports and references used to narrow Phase 1's Explore scope and to keep the knowledge base current. Read-only on code, costs no API tokens. Use when a task needs to know where something lives or what depends on it before reading files."
user-invocable: true
argument-hint: "[build | refresh | ask \"<question>\" | affected \"<symbol>\" | report | status]"
---
# multi-agent-graph  -  code graph build and query

**Input**: $ARGUMENTS

A code graph is a map of what this repo declares and what refers to what, built by
regex over comment-stripped source. It exists so Phase 1 can narrow its Explore
fan-out and Phase 7 can refresh `~/.claude/knowledge/<project>/` without an LLM
pass. It answers "where does this live" and "what depends on this". It does not
answer "what calls this at runtime": call graphs and type resolution need a real
parser, which would be an npm runtime dependency, and ADR-0004 forbids one.

No worktree, no branch, no commit, no pipeline chaining.

## Sub-commands

| Input | Runs | Notes |
|---|---|---|
| `build` | `graph-build.mjs --root <repo> --stack <stack>` | Writes `~/.claude/knowledge/<project>/code-graph.json` |
| `refresh` | same as `build` | A full rebuild takes seconds, so there is no separate incremental path |
| `ask "<question>"` | `graph-query.mjs "<question>" --budget N` | Token-budgeted traversal; default budget 2000 |
| `affected "<symbol>"` | `graph-affected.mjs "<symbol>" --depth N` | Reverse traversal: the blast radius of a change |
| `report` | `graph-report.mjs` | Writes `GRAPH_REPORT.md` beside the graph |
| `status` | `graph-report.mjs --status` | One line: stack, scale, build time and whether `baseCommit` still matches HEAD. Never read the graph file yourself - it is 22MB on a large repo |

With no argument, run `status`, then offer `build` when no graph exists and
`refresh` when `baseCommit` differs from the current HEAD.

## Steps

1. **Resolve the repo.** `PROJECT_ROOT` is the current repo root unless the user
   named another. Derive the graph path once and pass it to every call below:

   ```bash
   GRAPH_PATH="$HOME/.claude/knowledge/$(basename "$PROJECT_ROOT")/code-graph.json"
   ```

   The query, affected and report scripts default that path from the CWD's
   basename, which is not the same thing when the shell sits in a worktree or a
   sub-package, so a defaulted path can point at a graph that was never built.

2. **Resolve the stack.** Read `state.detectedStack` when a run is in flight;
   otherwise detect from project markers the way Phase 1 Step 2 does. Only stacks
   with a file in `$HOME/.claude/scripts/code-graph-rules/` can be built. A stack with
   no rule file is reported as unsupported, never guessed at.

3. **Run the sub-command.** Every script is read-only on the repo and writes only
   under `~/.claude/knowledge/`:

   ```bash
   node $HOME/.claude/scripts/graph-build.mjs --root "$PROJECT_ROOT" --stack "$STACK" --out "$GRAPH_PATH"
   node $HOME/.claude/scripts/graph-query.mjs "<question>" --graph "$GRAPH_PATH" --budget 2000
   node $HOME/.claude/scripts/graph-affected.mjs "<symbol>" --graph "$GRAPH_PATH" --depth 2
   node $HOME/.claude/scripts/graph-report.mjs --graph "$GRAPH_PATH"
   node $HOME/.claude/scripts/graph-report.mjs --graph "$GRAPH_PATH" --status
   ```

4. **Validate after a build.** A graph that parses but whose edges point at
   missing nodes yields silently truncated traversals, so the build is not
   reported as successful until the validator agrees:

   ```bash
   node $HOME/.claude/scripts/validate-code-graph.mjs "$GRAPH_PATH"
   ```

   A non-zero exit fails CLOSED: report the validator's `errors[]` verbatim and
   do not record the graph as usable.

5. **Report.** One line for a build (`files / nodes / edges / elapsed`), the
   traversal output as-is for `ask` and `affected`, the report path for `report`.

## What the output is for

`ask` returns ranked nodes plus their neighbourhood within a token budget. Feed
it to an Explore agent as the starting file set rather than pasting it into a
final answer: it is a search result, not an explanation.

`affected` returns dependents, which is what `analysis.touchedAreas[]` wants.

## Limits worth stating when reporting

- References resolve only when a name maps to exactly one declaration. A type
  declared in two files is ambiguous and is deliberately dropped, so `affected`
  under-reports for duplicated names rather than fanning out to every candidate.
- Only type-like symbols are reference targets. Functions appear in the graph
  through their declaring file, not as targets, because a bare lowercase name
  matched across files is almost never a call to that exact declaration.
- Comments and string literals are stripped before extraction, so a name that
  appears only in prose or in a string produces no edge.
- A nested declaration is a node but never a reference target. A Kotlin sealed
  case or a Python inner class named `Icon` or `Color` is declared exactly once,
  so the ambiguity rule above does not catch it, and every file that merely
  mentions the framework type of that name would otherwise gain an edge to it.
- On stacks whose exported unit is a function (Node most of all) the symbol
  layer is thin by design and the import graph between files carries the value.
  Ask `affected "<file>.mjs"` there, not `affected "<functionName>"`.
- `affected` at `--depth 1` returns direct symbol references only. An import
  whose module name matches a declaring file's basename reaches the symbol
  through that file, so its importers appear at depth 2. Keep the default
  depth of 2 unless direct references are what you actually want.
