---
name: codegraph
requires-tool: nustack
description: Query the code graph before reading source. An LLM-first codebase index (symbols, call and data references, git history, co-change coupling, ownership) answering whole questions in one call, through `nustack graph`. Use it to localize, scope, and assess blast radius on this repository; use grep/read for string literals, mechanism, and data flow — the skill states which is which. Applies to any task that starts with "where is", "who calls", "what changed", "who owns", "what breaks", or "is this tested".
---

# nustack graph — the index-first method

## When the tool is absent

If `nustack --version` fails, do not stall: read `tool.nustack` in `.claude/state/decisions.tsv` and fall back to the cold method — grep, targeted reads, `git log`.

- **`declined`** — fall back **silently**. Never mention the tool or re-offer it.
- **Undecided** (no file, no key, or an unrecognised value) — fall back *and* offer the tool **once**: this repository could answer "where is / who calls / what changed / who owns" from an index instead of a grep sweep. `npx @nurix/nustack graph overview` runs it with no install; `npm install -g @nurix/nustack` puts it on `PATH`. **Ask, never act** — a global install modifies the machine and indexing writes to `~/.codegraph`. Record the answer with `recordDecision` either way, so the question is asked at most once per repo per machine.
- **`accepted` but the binary is gone** — treat as undecided; a failed or removed install is not a standing yes.

**A failed install means absent — never retry it.** Global installs fail on machines without permission: say so once, fall back, move on. `npx` is the shorter ladder when one command is all you want.

**An explicit invocation overrides a stored decline.** When the user asks for this skill by name, honour it and re-offer — "stop asking" must never become "never usable".

Everything below assumes the tool is present.

## What every answer carries

- **Scope** — the git root of the working directory; no flags needed inside this repository. `--repo <pathOrId>` asks about another one.
- **Confidence** — each section is labelled `CERTAIN`, `INFERRED` or `UNRESOLVED`; the answer announces anything the token budget dropped and says what an empty section means. Trust those labels.
- **Freshness** — `nustack graph` re-indexes a stale store before it answers, so never run `index` to make a query correct. `--no-refresh` answers from the index as it stands — a deliberate choice, never an accidental one.
- **A never-indexed repository** — the tool says so instead of answering wrongly. `nustack graph index` builds it, under the same ask-first rule as the install.

## The method order

Localize through the index **before** opening any file, in this order; then read only the files it points at.

1. **`nustack graph find <fragment>`** — symbols and file paths by name fragment; the entry point when you have only the issue's vocabulary. It matches symbol names only — never string literals, dict keys, or SQL fragments; grep for those.
2. **`nustack graph where-defined <symbol>`** / **`nustack graph outline <file>`** — pin the definition; map a big file. Use `outline` instead of reading a 100+ declaration file blind: it returns a navigable, line-numbered list. The default token budget truncates a large file's outline — pass `--limit N` (which lifts the budget) or `--budget 0`; an explicit `--budget` wins over `--limit`.
3. **`nustack graph who-calls <symbol>`** / **`nustack graph impact <target>`** — callers, and blast radius. `impact` reports static reach and historical co-change reach separately; never merge them — where they disagree, the disagreement is the finding. `who-calls` also tabulates the keyword arguments each call site passes, so "which call sites omit this option" is one query.
4. **`nustack graph churn --days N --path <dir>`** · **`coupling --path <file>`** · **`owners <path>`** · **`tests-covering <file>`** — the history layer: what is hot, what changes together, who knows this area and how recently, which test file historically guards this file. `coupling` surfaces *hidden coupling* — co-change with no static reference, which no amount of reading reveals.
5. **`nustack graph god-objects --minSymbols 30`** / **`hotspots`** / **`overview`** — repo-scale orientation and risk.

Then read with `sed -n` ranges at the lines the index gave you — the index does discovery, reading does diagnosis. Add `--json` when a wrapper needs structure; the answer arrives in the CLI's envelope, `{ nustackJsonVersion, command, ok, data }`.

| Other verb | Answers |
| --- | --- |
| `nustack graph blame <file> [--line N]` | The commits behind a file or one line, from git at query time |
| `nustack graph refs <name>` | The data-reference layer: where a name is used as a dict/object key, attribute/field, import, or type annotation, with read/write direction and a cross-language flag when the name spans Python and TypeScript — "who reads `x_axis_sort`" / "who writes `chart.query_context`" in one call |
| `nustack graph drift` | Near-duplicate function bodies crossed with co-change history — similar code whose files never change together is an unfinished refactor waiting to disagree |
| `nustack graph status` | What the index holds — indexed HEAD, file and symbol counts, staleness. Reach for it when an answer surprises you |
| `nustack graph index [--commits N]` | An explicit rebuild |

## The boundary — what the index cannot answer

Fall back to reading immediately for these; never retry the index.

- **Free-text string literals** (SQL templates, prose, format strings) — grep. Identifier-shaped keys and fields **are** indexed (`refs <name>`); anything longer or containing spaces is not.
- **Framework reflection** (Marshmallow `@post_load`, signal handlers, `appbuilder.add_api(Cls)` registration) — invisible to call edges. A bare decorator *application* (`@has_access_api`) **is** indexed as a reference; what the decorator does and when it runs is not.
- **Data flow and mechanism** ("does this value reach that function", "what order do these run in") — always reading work.

Indexed symbol forms: statement declarations, `const X = () => {}`, named function expressions inside wrapper calls (`typedMemo(function DataTable() {})`), TS/JS class fields, and Python class-body fields (`is_dttm = Column(...)`). `outline` renders nested members as `Parent › member`.

## Traps

- **Bare-name collision.** Call references match by name, not resolved binding. When a name has several definitions, `who-calls` lists every candidate and says so in its subject line — read that section before trusting the call sites, and pass `--scope <path-fragment>` (also on `find`, `where-defined`, `impact`) to narrow to a package.
- **`nustack graph functions` lists the graph's own verbs**, not the repository's functions. For repository functions use `find`.
- **Churn windows.** An empty `churn --days 180` does not mean quiet history — widen the window. Directory-scope churn ranks by raw commit count and buries small-but-critical files; for specific files use `owners` / `coupling` per file.
- **`tests-covering` is co-change evidence, not executed coverage** — it names the right test file to open; it cannot say the specific scenario is tested.
- **Coupling on a busy file has a noise tail** — partners below 15% shared commits are usually PR cohabitation, not real coupling. Verify a partner with a read before adding it to a touch-list.
- **The history layer sees one window, and every refresh resets it to 5000 commits.** `churn`, `owners`, `coupling`, `blame` and `tests-covering` answer over the ingested commits; an index, the automatic one included, takes the most recent 5000 unless `index --commits N` says otherwise. On a deeper repository those answers are true of the window, not the repository, and the next refresh silently narrows a wider window back. Always report them as "over the last N commits", and re-ask for the depth you need.
- **`owners` is authorship weight, not a file-touch tally** — an author counts once per commit touching the path, however many of its files that commit changed. Never transcribe its numbers as "files changed".
- **The first query after a long gap pays for the re-index** — tens of seconds on a large repository before the answer appears. That is one wait, not a per-query cost; `--no-refresh` skips it when the old answer will do.

## Division of truth in this repository

`docs/` is the source of truth for **intent** — what a feature is for, its invariants, decisions, vocabulary. The code graph is the source of truth for **facts** — where things are, who calls what, what changed together, who owns what. **Never** copy live facts (caller counts, churn numbers) into docs as if permanent; never ask the index for intent.
