---
name: query-code
description: |
  Query the codemap structural index — central, coupled, deps, rdeps, import path, symbol-level source extraction, and function-level call graph (fn-deps, fn-rdeps, fn-central, fn-blast).
  TRIGGER when: user asks about module relationships, dependency graph, callers/callees, or blast radius; phrases: "what depends on", "who calls", "imports of", "dependency graph", "blast radius of".
  SKIP: codemap index not built (skill self-checks and no-ops gracefully); simple grep would suffice; non-Python repo.
when_to_use: |
  TRIGGER when: user asks about module relationships, dependency graph, callers/callees, blast radius, or central/coupled modules; phrases: "what depends on", "who calls", "imports of", "dependency graph", "blast radius of", "list central modules".
  SKIP: codemap index not built (run `/codemap:scan-codebase` first); user wants to rename a symbol (use `/codemap:rename-refs`); simple grep would suffice; non-Python repository.
argument-hint: "<central [--top N] [--exclude-tests] | coupled [--top N] [--exclude-tests] | deps <module> | rdeps <module> [--exclude-tests] | path <from> <to> | symbol <name> [--limit N] [--exclude-tests] [--with-imports] | symbols <module> | find-symbol <pattern> [--limit N] [--exclude-tests] | list | fn-deps <qname> | fn-rdeps <qname> [--exclude-tests] | fn-central [--top N] [--exclude-tests] | fn-blast <qname> [--index <path>]> [--exhaustive]"
allowed-tools: Bash, Write, AskUserQuestion
model: haiku
effort: low
---

<objective>

Query codemap structural index for import-graph analysis, symbol-level source extraction, function-level call graph traversal. **Python projects only** — index covers `.py` files; queries on non-Python projects return empty or error. `scan-query` on PATH (installed by codemap plugin).

**Module-level queries** (import graph):
- `central [--top N]` — most-imported modules (highest blast radius, default top 10)
- `coupled [--top N]` — modules with most imports (highest coupling, default top 10)
- `deps <module>` — what module imports
- `rdeps <module>` — what imports module
- `path <from> <to>` — shortest import path between two modules

**Symbol-level queries** (use instead of reading full files — ~70–94% token reduction (single known-name lookup on large file)):
- `symbol <name> [--with-imports]` — get source of function/class/method by name; returns `stale: bool` per result; add `--with-imports` to include module-level import block alongside source
- `symbols <module>` — list all symbols in module (no file I/O)
- `find-symbol <pattern>` — regex search across all symbol names in index

**Function-level call graph queries** (v3 index — requires `/codemap:scan-codebase` with call graph):
- `fn-deps <qname>` — what does function/method call? (outgoing edges)
- `fn-rdeps <qname>` — what functions call this one? (incoming edges)
- `fn-central [--top N]` — most-called functions globally (default top 10)
- `fn-blast <qname>` — transitive reverse-call BFS with depth levels

Use `module::function` format for qname, e.g. `mypackage.auth::validate_token`. Requires v3 index — v2 returns clear upgrade prompt.

NOT for: building or rebuilding index (use `/codemap:scan-codebase`); writing symbol output to project files (Write is in allowed-tools for output routing only — never use it to modify project source). All query subcommands are **read-only** — any request that modifies the index or project files belongs to `/codemap:scan-codebase` (index writes) or `/codemap:integration` (file injection). Ambiguous prompts like "show me the call graph" that imply read → query-code is correct; "update the call graph" → scan-codebase. If subcommand roster expands significantly, run `/foundry:calibrate routing` (requires `foundry` plugin) to verify no routing collisions.

</objective>

<workflow>

## Step 1: Run the query

**deps vs rdeps — choose before calling:**

| Task asks for... | Use | Why |
| --- | --- | --- |
| "which modules import X?" | `rdeps X` | callers, blast radius |
| "what imports X?" | `rdeps X` | callers |
| "modules affected if X changes?" | `rdeps X` | blast radius = reverse deps |
| "blast radius of X" | `rdeps X` | reverse deps |
| "what does X import?" | `deps X` | forward deps |
| "dependencies of X" | `deps X` | forward deps |

**Common mistake — direction matters**: "which modules need updating if X changes?" = `rdeps` (callers), NOT `deps`. `deps` returns wrong direction — 0% recall.

**Unsupported flag check** — after all supported flags extracted, scan `$ARGUMENTS` for remaining `--<token>` tokens. If found: print `! Unknown flag(s): \`--<token>\`. Supported: \`--top\`, \`--exclude-tests\`, \`--limit\`, \`--index\`, \`--exhaustive\`; \`--with-imports\` applies to \`symbol\` subcommand only.` then invoke `AskUserQuestion` — (a) **Abort** (stop, re-invoke with correct flags) · (b) **Continue ignoring** (skip unknown flags, proceed). On Abort: stop.

**Symbol staleness contract**: every `symbol` result includes `"stale": bool`. When `stale: true`:
- Do NOT use `source` — it may be wrong (function moved since last scan)
- `stale_reason` explains why: `"file deleted"`, `"line range past EOF"`, `"symbol name not in slice header"`
- Fall back: `Read(<result["path"]>)` — path is still valid even when content is stale
- Fix: run `scan-index --incremental` then retry

**Symbol vs Read — access pattern decision:**

| Need | Use |
| --- | --- |
| Single known function/method body | `symbol Module.fn` |
| Body + module-level imports (type context) | `symbol --with-imports Module.fn` |
| >2 symbols in same file | `Read` on file |
| Module-level constants / `__all__` | `Read` on file |
| Discover functions matching a concept | `find-symbol <pattern>` → `symbol` (N≤2 hits only) |
| Non-Python file | `Read` |

**Qualified names reduce ambiguity**: `symbol MyClass.method` returns one result; bare `symbol authenticate` may return N matches across modules. Prefer qualified form when module path known.

**On-demand freshness** (when index may be stale after recent file edits):

```bash
scan-index --incremental  # timeout: 5000 — ~0.5s flat; refreshes any changed files
scan-query ...
```

Skip if incremental already ran earlier this turn. Full scan (`scan-index` without `--incremental`) only needed after large structural changes.

Run `scan-query` via Bash:

```bash
scan-query <QUERY_ARGS>  # timeout: 5000
```

Replace `<QUERY_ARGS>`:

| Goal | Command |
| --- | --- |
| reverse deps | `rdeps <module>` |
| forward deps | `deps <module>` |
| central modules | `central --top 10` |
| coupling rank | `coupled --top 10` |
| import path | `path <from> <to>` |
| symbol source (body only) | `symbol <name>` |
| symbol source + imports | `symbol <name> --with-imports` |
| module symbols | `symbols <module>` |
| symbol search | `find-symbol <pattern>` |
| list modules | `list` |
| outgoing calls | `fn-deps module::function` |
| incoming calls | `fn-rdeps module::function` |
| most-called functions | `fn-central --top 10` |
| transitive callers | `fn-blast module::function` |

`scan-query` on PATH, locates index via git root — no setup. **Missing binary**: if `scan-query` not found on PATH, print `! scan-query not found — install codemap plugin: claude plugin install codemap@borda-ai-rig` and stop. Do not fall through to grep/bash fallback.

Symbol names accept: bare name (`authenticate`), qualified name (`MyClass.authenticate`), or case-insensitive substring fallback. Function qnames use `module::function` format (e.g. `mypackage.auth::validate_token`). Index must be current — re-run `/codemap:scan-codebase` if stale warning appears.

`find-symbol` pattern is a **Python regex** applied against the full qualified name — `auth` matches any symbol containing "auth" as a substring; `^Auth.*Handler$` matches only symbols starting with "Auth" and ending with "Handler". For exact match use anchors: `^MyClass.method$`. Bare substring (no anchors) = broad match — prefer anchored patterns for precision.

## Budget and stop rules

**Query budget**:
- Default: max **3** calls per task. Stop after 3 even if not exhaustive — report what found.
- Exhaustive mode: declare exhaustive intent before first call → budget extends to **6** calls. Declaring after first call is invalid — treat as non-exhaustive (budget=3).
- `path` or `fn-blast` with `--exhaustive` flag: budget extends to **6** calls (same as exhaustive mode). These subcommands traverse the graph internally; `--exhaustive` is the explicit signal to allow deeper exploration.

**exhaustive: true — STOP ALL TOOL CALLS:** When `rdeps`, `deps`, or `fn-rdeps` result has `result["index"]["exhaustive"] == true`, list is complete and authoritative for the **unfiltered** index. Check the `index.exhaustive` field specifically (not any top-level field). Note: if `--exclude-tests` used, exhaustive reflects unfiltered coverage — filtered results may omit callers; state caveat if relevant. Write answer immediately. Do NOT call codemap again. Do NOT run grep, bash, or Glob passes to verify or extend. No exceptions. In the response, explicitly note: "Result is complete and authoritative for the unfiltered index." If `--exclude-tests` was used, add: "Note: filtered results may omit some callers — unfiltered list is complete."

**Non-exhaustive result — convergence rule**: after budget calls still non-exhaustive, stop and report what found. Do NOT switch to grep/bash — index covers what it covers.

**`--exclude-tests` + exhaustive**: when `--exclude-tests` is used and `index.exhaustive == true`, the exhaustive flag reflects unfiltered coverage (the index was fully searched). The STOP rule still applies — do NOT make additional calls. The caveat is informational: filtered results may omit callers that are in test files. State the caveat in the response but do not make additional non-filtered calls to compensate.

## Step 2: Parse JSON output and format

`scan-query` always emits JSON object — parse before rendering. Stale-index detection has two channels: (1) stderr: if contains `[stale]` or `⚠ codemap index stale` — surface warning; (2) JSON field `index.stale` (boolean) — check `result.index.stale`; if `true`, warn user to re-run `/codemap:scan-codebase`. Check `index.degraded` in result; if `> 0`, caveat that some modules unparsable — for `path` queries, note that intermediate nodes may be missing and the path result may be incomplete.

| Command | JSON key to use | Render as |
| --- | --- | --- |
| `rdeps` / `deps` | `imported_by` / `direct_imports` | list modules, one per line |
| `central` | `central` array | `name — N importers (high blast radius)`, one per line |
| `coupled` | `coupled` array | `name — N imports (high coupling)`, one per line |
| `path` | `path` array (or `null`) | chain `A → B → C → D`; if `null` → "No import path found." (`--exclude-tests` not supported on `path`) |
| `symbol` | `symbols[].source` | fenced code block; caption = module + line range; if `source` is empty string → render `[source not available — re-run /codemap:scan-codebase]` instead of empty block |
| `symbols` | `symbols` array | `type name (lines start–end)`, one per line |
| `find-symbol` | `matches` array | `module:qualified_name (type)`, one per line |
| `list` | `modules` array | `module (path)`, one per line |
| `fn-deps` / `fn-rdeps` | `calls` / `called_by` | `module::function (resolution)`, one per line |
| `fn-central` | `fn_central` array | `count module::function`, one per line |
| `fn-blast` | `blast_radius` array | `depth module::function` (if depth key present), sorted by depth then name; if `depth` key absent (older index format) → render `module::function` without depth prefix, note "depth unavailable — re-run /codemap:scan-codebase to upgrade index" |
| stale check | `index.stale` (boolean) | if true → warn "index stale — run /codemap:scan-codebase" |

`{"error": "..."}`: surface error, suggest re-running `/codemap:scan-codebase`.

**Partial JSON handling**: if output is truncated (does not parse as complete JSON object — e.g., ends mid-value or missing closing `}`), log `⚠ partial JSON response — results may be incomplete`. Attempt recovery using `jq` with `--stream` mode if available; otherwise fall back to treating output as plain text and extracting visible module/function names by line matching (look for quoted strings resembling module paths or qnames). Surface whatever was recovered; do not silently discard partial results. If recovery produces zero items, report `⚠ could not recover partial results — re-run the query` and stop.

**Output routing** — if result count ≥ 5 items: write full rendered output to `.temp/output-codemap-query-<branch>-<YYYY-MM-DD>.md` via Write tool, then print terminal summary (YAML header + path + top-5 items). Skip file write for ≤ 4 items — terminal only. Applies to: `rdeps`, `deps`, `central`, `coupled`, `fn-rdeps`, `fn-central`, `fn-deps`, `fn-blast`, `list`. For `fn-blast` on widely-called functions (>10 entries), always route to file — print file path and top-5 entries only to terminal to avoid burying the follow-up gate.

**Flags available on multiple commands** (`--exclude-tests`, `--limit`, `--index`):
- `--exclude-tests` — drop test modules from results; applies to: `rdeps`, `central`, `coupled`, `symbol`, `find-symbol`, `fn-rdeps`, `fn-central`; **not supported on `path`** — if user passes `path ... --exclude-tests`, print `! --exclude-tests is not supported for path queries — flag ignored` and proceed without it
- `--limit N` (default 20, use `0` for all) — caps results on `symbol`, `find-symbol`, `rdeps`, `deps`, `fn-rdeps`, and other list-type commands; **always pass `--limit 0` when counting or ranking** to avoid silent truncation at 20 items; output-routing count check and exhaustive assertions should be made only after `--limit 0` or confirmed item count is below 20
- `--index <path>` — explicit index file path (bypasses auto-discovery; useful for monorepos or comparing two indexes)

</workflow>
