Analyze
For any question about a codebase's structure, conventions, capabilities, or adherence to its own docs, ask the analyze tools first. Every claim is grounded in a real graph exploration — file paths are drawn from the index, not invented.
Analyze is exposed as two MCP tools that share the same engine. Both run the same deterministic graph queries plus the same citation-grounded synthesis, and both return the same verified 7-layer context bundle. They differ only in who drives the inner reasoning.
insrc_analyze
One-shot. A single tool call — the server runs the whole pipeline end to end. Inner narrow-LLM calls route to the daemon's configured shaperProvider (Ollama by default), so it's slower and bills separately.
insrc_analyze_step
Multi-turn. The server hands you the decomposer / synthesizer / narrow prompts + schemas, and your client emits the JSON as its next reasoning step. Stays in-session — better accuracy, no subprocess spawn, no separate billing.
insrc_analyze_step by default when you are already inside an MCP client (claude / codex). Every reasoning turn stays in your session, so the answer is more accurate and there is no second billing path. Reach for the one-shot insrc_analyze only when you specifically want the Ollama-backed path or a single fire-and-forget call.Why it beats grep + read
Manual Read / Grep / Glob gives you raw text and a mental model you assemble yourself. Analyze gives you a structured, verified bundle assembled from the graph.
- Every claim is grounded in a real exploration output — a module profile, a symbol locate, a class hierarchy, a doc-constraint enumeration. Bundle citations must trace back to a field in some exploration's structured payload.
- File paths are drawn from the indexed graph — there are no hallucinated paths, because the runners only report what the tree-sitter index actually contains.
- Contradictions in the docs are preserved verbatim — when two documents disagree, analyze surfaces both rather than silently auto-resolving to one. You decide.
The exploration recipes
A run is a plan of small, typed explorations. The decomposer picks which recipes to fire for the question's answer-type; each runner returns a structured payload (never free-form prose); the synthesizer composes those into the bundle. These are the recipe types defined in src/analyze/explore/:
Structural resolvers · deterministic
| Recipe | What it resolves |
|---|---|
concept.resolve | Resolve a natural-language concept to the concrete graph entities that implement it. |
module.profile | Profile a module — entity counts, the sub-tree, and its key exported symbols. |
symbol.locate | Locate a named symbol and report its definition site. |
class.hierarchy | Build the extends / implements hierarchy around a type. |
import.graph | Summarise the import graph around a module — what it pulls in and who pulls it. |
test.locate | Locate the tests that cover a given symbol or module. |
usage.example | Find real call-sites that exemplify how a symbol is used. |
capability.reuse-check | Answer "does the codebase already implement Y?" with candidate matches. |
Content search · deterministic
| Recipe | What it resolves |
|---|---|
search.text | Grounded text / content search across indexed files. |
Doc-side · mostly deterministic, some narrow LLM
| Recipe | What it resolves |
|---|---|
doc.mention | Find the doc passages that mention a term. |
doc.decision.trace | Trace a design decision through the documentation. |
doc.constraint.enumerate | Enumerate the documented constraints / rules relevant to a topic. |
Convention detection · deterministic
| Recipe | What it resolves |
|---|---|
convention.detect | Detect naming schemes, base-class idioms, and test-layout conventions. |
config.trace | Trace a config key through its load and use sites. |
data-model.trace | Trace a data model's fields and relations. |
Data-driver · deterministic, wraps the registered DriverPool
| Recipe | What it resolves |
|---|---|
db.connections.list | List the registered data-driver connections. |
db.tables.list | List the tables for a connection. |
db.table.describe | Describe a table's columns. |
Infra + fallback
| Recipe | What it resolves |
|---|---|
manifests.locate | Locate infra manifests via a graph-backed manifest scan. |
freeform.probe | Fallback probe when no structural recipe fits the question. |
src/analyze/explore/. The decomposer only fires the subset relevant to the question's answer-type — a "map this module" query and an "does X follow rule Y from the docs" query walk very different recipe sets.The 7-layer context bundle
Whatever recipes fire, the synthesizer folds their outputs into one bundle with seven named layers. Each layer field is required on the wire, so the model can't silently drop one; an empty layer is reported explicitly.
The step loop
With insrc_analyze_step the pipeline runs as a turn-by-turn handshake. Follow the next field in each response verbatim; prompt + schema are the authoritative instructions for the JSON you emit next. Preserve state verbatim between calls — it is a short opaque token tied to a server-side run.
- start — open a run with a focus. Server replies with the decomposer prompt + plan schema.
- plan — you emit the exploration plan JSON. Server either loops into narrow steps or jumps straight to the bundle.
- narrow ↻ — for recipes that need a narrow LLM, emit the requested JSON per
explorationId; repeat until every exploration is satisfied. - bundle — you emit the 7-layer bundle JSON. Server replies
donewith rendered markdown.
The call that opens a run:
// your tool call
insrc_analyze_step({ "phase": "start", "focus": "list the storage layers and where each is initialised" })
The response tells you exactly what to emit next:
{
"next": "emit_plan",
"prompt": "<decomposer prompt — how to choose recipes>",
"schema": { /* JSON Schema the plan must match */ },
"state": "<opaque token — pass back verbatim>"
}
→ start focus: "how does the daemon own DB access?"
← emit_plan decomposer prompt + plan schema
→ plan { answerType: "structural-map", explorations: [ … ] }
← emit_narrow explorationId=e3 · narrow prompt + schema
→ narrow { … } ↻ until every exploration is satisfied
← emit_bundle synthesizer prompt + 7-layer schema
→ bundle { system, focus, summary, structure, surface, artefacts, upstream }
← done rendered markdown — show this to the user
When to use which
Most context questions want the in-session insrc_analyze_step. The one-shot insrc_analyze is the escape hatch for a quick Ollama-backed bundle.
| Intent | Tool |
|---|---|
| Map a module / explore its tree / count entities | insrc_analyze_step |
| List conventions / naming / test layout | insrc_analyze_step |
| List indexed data sources or infra manifests | insrc_analyze_step |
| Adherence check — "does the code follow rule X from doc?" | insrc_analyze_step |
| Capability discovery — "does the codebase already do Y?" | insrc_analyze_step |
| Prose retrieval / decision trace from the docs | insrc_analyze_step |
| Quick one-shot bundle (fine if you don't mind Ollama) | insrc_analyze |
When NOT to use either
Both tools are read-only context builders. Reach for something else when:
- Editing files — analyze never writes. Use your editor /
Edittools. - Running tests or builds — that's a shell job, not an exploration.
- Non-context questions — anything not about this codebase's structure, conventions, capabilities, or docs.
- The bundle comes back empty or clearly off-topic — fall back to
Read/Grep/Globat that point rather than forcing a second run.
The repo argument
Analyze operates against one registered repo:
- Resolution is session-aware, in order: an explicit
repoargument wins; otherwise the server matches its working directory against the registered repos; otherwise it falls back to$INSRC_REPOfrom the environment (headless / cron). - Because the working directory disambiguates the session, one MCP registration serves every repo — no per-repo
INSRC_REPOneeded. - Either way, the repo must be registered with the daemon (add it via the TUI's Repos pane → a, or the
repo.addIPC) and have finished indexing.
DEPENDS_ON closure of the active repo. If you want cross-repo answers, register the dependency repos too — an entity whose repo path isn't registered never enters the graph.