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.

Prefer 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.

grounded, not guessed
  • 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

RecipeWhat it resolves
concept.resolveResolve a natural-language concept to the concrete graph entities that implement it.
module.profileProfile a module — entity counts, the sub-tree, and its key exported symbols.
symbol.locateLocate a named symbol and report its definition site.
class.hierarchyBuild the extends / implements hierarchy around a type.
import.graphSummarise the import graph around a module — what it pulls in and who pulls it.
test.locateLocate the tests that cover a given symbol or module.
usage.exampleFind real call-sites that exemplify how a symbol is used.
capability.reuse-checkAnswer "does the codebase already implement Y?" with candidate matches.

Content search · deterministic

RecipeWhat it resolves
search.textGrounded text / content search across indexed files.

Doc-side · mostly deterministic, some narrow LLM

RecipeWhat it resolves
doc.mentionFind the doc passages that mention a term.
doc.decision.traceTrace a design decision through the documentation.
doc.constraint.enumerateEnumerate the documented constraints / rules relevant to a topic.

Convention detection · deterministic

RecipeWhat it resolves
convention.detectDetect naming schemes, base-class idioms, and test-layout conventions.
config.traceTrace a config key through its load and use sites.
data-model.traceTrace a data model's fields and relations.

Data-driver · deterministic, wraps the registered DriverPool

RecipeWhat it resolves
db.connections.listList the registered data-driver connections.
db.tables.listList the tables for a connection.
db.table.describeDescribe a table's columns.

Infra + fallback

RecipeWhat it resolves
manifests.locateLocate infra manifests via a graph-backed manifest scan.
freeform.probeFallback probe when no structural recipe fits the question.
That is 20 recipe types in 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.

system project rules + the analyze contract focus what the question is centred on summary the grounded prose answer structure entities, trees, hierarchies from the graph surface key symbols / signatures / call-sites artefacts docs, constraints, decisions, conventions upstream dependencies + transitive DEPENDS_ON context

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 emit_plan plan emit_narrow ↻ emit_bundle done
  1. start — open a run with a focus. Server replies with the decomposer prompt + plan schema.
  2. plan — you emit the exploration plan JSON. Server either loops into narrow steps or jumps straight to the bundle.
  3. narrow ↻ — for recipes that need a narrow LLM, emit the requested JSON per explorationId; repeat until every exploration is satisfied.
  4. bundle — you emit the 7-layer bundle JSON. Server replies done with rendered markdown.

The call that opens a run:

json
// 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:

json
{
  "next":   "emit_plan",
  "prompt": "<decomposer prompt — how to choose recipes>",
  "schema": { /* JSON Schema the plan must match */ },
  "state":  "<opaque token — pass back verbatim>"
}
insrc_analyze_step — one run
 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.

IntentTool
Map a module / explore its tree / count entitiesinsrc_analyze_step
List conventions / naming / test layoutinsrc_analyze_step
List indexed data sources or infra manifestsinsrc_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 docsinsrc_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:

The repo argument

Analyze operates against one registered repo:

Graph searches span only the transitive 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.

Where next