Skip to main content

Querying the intent layer

Once a run has produced its outputs, three subcommands read them back: read-only, LLM-free, and free of charge (no backend call, no cost). Each prints exactly one JSON object on stdout by default; add --format text for a human-readable render instead. All three accept --graph PATH (default graphify-out/graph.json, same as the pipeline; may be given before or after the subcommand name) and exit 0 for any answer status (ok/weak_match/topically_weak/no_strong_match — the latter three are confidence flags, not errors), 2 for not_found/ambiguous/no_intent_layer (see Exit codes; full JSON schemas are in AGENTS.md). explain <node> resolves <node> (an exact node id, else a rapidfuzz match over node labels) and explains it: for a code/document node, the intent node(s) that motivate it (the intent-origin rationale_for edges pointing at it); for an intent node, its own claim/rationale, the node(s) it explains, and its Pass-C relations (a supersession is surfaced as superseded_by on the superseded node). Requires graph.enriched.json; returns status: "no_intent_layer" (exit 2) if it doesn’t exist yet.
list-intent [--kind decision|mechanism|constraint|tradeoff] [--min-confidence F] returns the intent inventory, optionally filtered by kind and/or a minimum confidence_score.
why "<question>" ranks intent nodes against free text with rapidfuzz token matching over each node’s label, claim, rationale, and alternatives (a stopword-stripped variant of the question is scored too, so filler words don’t dilute the match) and returns the top --top (default 5) by score (0-100); a top score below the cutoff (60) still returns its results, just with status: "no_strong_match" (or "weak_match" when it stands clearly apart from the runner-up) so a caller can tell the match was weak. Above the cutoff, each result carries its evidence — matched_terms and distinctive_matches (#81) — and a top hit with at most one distinctive match covering half or less of the question’s content terms downgrades to status: "topically_weak" (each result carries matched_coverage) — and so does a single matched term with no distinctive evidence at any coverage, so a lone corpus-ubiquitous term still downgrades: a topical accident, not an answer — even a corpus-rare lone match can’t carry a question whose real subject matched nothing (#103). Calibration rule of thumb: scores 60–80 with no corroborating distinctive terms, or with low content coverage, are weak evidence.
list-intent and why don’t require graph.enriched.json; they fall back to the .whyfile.json sidecar when it’s the only artifact present. For the commands that DO need the enriched graph (explain, changed, coverage), run whyfile sync first — it builds a deterministic enriched graph from your records and attested intent in seconds, zero LLM calls. The pipeline itself is unchanged: running whyfile with no subcommand still runs the extract/anchor/relate run described above.