#pragma once // cli.h — hand-rolled zero-dependency argument parser. Linear argv scan into one // POD Config; flags are additive; first non-flag positional is the root path. #include #include #include #include #include #include #include #include #include #include #include #include #include "ingest.h" // rw::kDefaultMaxFileBytes — the canonical crawl size ceiling (--max-file-size) #include "version.h" // configure-generated kRipwireVersion + short build info (--version) #include "infra/emit.h" // rw::emitTo + kEmitterName — --version discloses the emitter that compiled in (emit=) #include "infra/os.h" // rw::os::normalize_path_arg — path-valued arguments take the program's path spelling at intake #include "rootguard.h" // rw::noProjectRootReason — #350 layer 1: a run with no root, from a home/system directory namespace rw { enum class RankBy : std::uint8_t { PageRank, Authority, Hub, Rrf, Churn, ChurnDecay }; // --rank-by=pagerank|authority|hub|rrf|churn|churn-decay enum class ColorBy : std::uint8_t { Lang, Community, Cx, Churn, Tested }; // --color-by=lang|community|cx|churn|tested (with --html) struct Config { std::string_view rootPath; // roots[0] — kept as an alias so untouched call sites compile (A1) std::vector roots; // ALL positional roots (multi-root workspace when size() >= 2) int topK = 200; // --top-k=N; 0 = emit NO ranked map (payload verbs only, r27-emitters T2) bool topKExplicit = false; // did the user actually pass --top-k=? (gates the "ride-along map" stderr hint) int packTopN = 0; // 0 = disabled std::size_t packBudgetBytes = 65536; // 64 KB ≈ 16K tokens bool ignoreTests = false; bool mapDiff = false; bool mcp = false; bool lsp = false; // --lsp: read-only navigation LSP server over stdio (Phase 1 PoC — docs/LSP.md). std::string_view listen; // --listen=HOST:PORT (or bare PORT ⇒ loopback): serve the MCP // server over Streamable HTTP instead of stdio. // Implies --mcp. Binds 127.0.0.1 by default; a non-loopback host // requires --mcp-token (refuses to start otherwise). std::string_view mcpToken; // --mcp-token=T (or RIPWIRE_MCP_TOKEN env): shared bearer for the HTTP // transport. Required for a non-loopback bind and for --allow-remote-edits. bool allowRemoteEdits = false; // --allow-remote-edits: permit the 3 edit verbs over --listen (refused by // default); forces the token requirement even on loopback. std::string_view mcpLegend; // --mcp-legend=session|inline: the stdio server's legend posture (default session) std::string_view mcpTools; // --mcp-tools=SPEC: list (and answer) only these MCP tools — names and/or the // core/full profiles, validated against the tool table in main.cpp (mcp.h). std::vector excludes; // --exclude=SUBSTR (repeatable): drop matching paths RankBy rankBy = RankBy::PageRank; // --rank-by= (default keeps output unchanged) bool columnar = false; // --format=columnar|rows (RESEARCH lever 1): re-serialize the FLAT // list verbs (--callers/--callees/--uses/--impact + --pr-context rows) // as a path-table + parallel arrays; NEVER the nested map. Default // --format=xml keeps the minified-XML output byte-identical. bool candidates = false; // --format=candidates (A4-R6 / R6): a FLAT top-K export for EXTERNAL // rerankers — one … row // per --for/--query result (NO lens extras, NO doc bodies). Composes // with --top-k. Still XML (G4). Default (unset) leaves output unchanged. bool json = false; // --json (L2): the SAME content as the XML, machine-parseable, for the // CORE verbs ONLY (default map, --for, --pack-task, --callers/--callees/ // --impact, --quality-delta, --test-gate). Keys mirror the XML attr names std::string_view legend; // --legend=full|compact: the legend posture of every XML verb (P1, compactlegend.h); // an XML run that names none is RESOLVED to kDefaultLegendPosture (L1) bool legendDefaulted = false; // L1: legend was not asked for — validateLegendModifier resolved it to the default // posture because the run answers XML. main.cpp reads it: a defaulted posture // never refuses a run (it passes the output through), an asked one does. // 1:1. Every other verb refuses loudly (stderr + exit 1) rather than // silently emitting XML — see main.cpp's jsonUnsupportedVerb(). int detail = 0; // --detail=N (RESEARCH lever 3): with --for, emit FULL bodies for the // top-N ranked symbols + signatures for the rest, in ONE call. 0 = off // (byte-identical to no flag). Composes with --max-tokens and --adaptive. std::string_view around; // --around=SYMBOL (or file:name): focused ego pack std::string_view callers; // --callers=SYMBOL (or file:name): who calls it (1-hop in-edges) std::string_view callees; // --callees=SYMBOL (or file:name): what it calls (1-hop out-edges) std::string_view usesSym; // --uses=SYM (or file:name): the statically resolvable use-sites of SYM (call/read/write/import/extends/type) + external flag (ABS-3); file: narrows defs= + the call-role sites, other roles stay name-wide (§P10.2/§A6b) std::string_view graphQuery; // --graph-query=EXPR: composable node-set operators over the call graph (ABS-5) std::string_view verifyClaim; // --verify=CLAIM (G4): one structured claim in, a three-valued verdict + inline evidence out (src/verify.h owns the closed grammar) std::string_view helpTask; // --help-task=TASK: deterministic task -> one recommended Ripwire command (or honest abstention) bool externalSurface = false; // --external-surface: names referenced but never defined in-corpus (stdlib/3p surface) int aroundDepth = 1; // --around-depth= — P4 (L7): default 1 hop (was 2: 61,891 B / 24,973 // tokens on --around=rankGraphTeleport vs 5,860 B at depth 1; the root's // depth= discloses it, --around-depth=2 restores the old neighbourhood) int aroundFanout = 32; // --around-fanout= bool eval = false; // --eval: self-eval (co-change recall vs BM25) bool evalRetrieval = false; // --eval-retrieval: KNOWN-ITEM retrieval eval — validates // query-TIME ranker choice (name-exact/routing/anchoring) // that the seed-based --eval structurally cannot measure std::string_view evalMined; // --eval-mined=FILE: session-trace-mined retrieval eval — // consumes a bench/mine_traces.py minedpair.jsonl artifact. // Explicit path required, no silent // default lookup into ~/.ripwire/... std::string_view evalSkills; // --eval-skills=FILE: labelled skill-ROUTING eval — ROOT is a // skills directory, FILE is the prompt→permitted-skill(s) // corpus std::string_view cacheFile; // --cache=PATH: incremental index (re-parse only changed) std::string_view indexOut; // --index-out=BASE: CI generate-and-exit. Cold-parses the tree // TWICE — lean then rich — writing BASE.lean.ripwirecache + // BASE.rich.ripwirecache (both families: --for/--exemplar/--metrics // are RICH and would stay cold on a lean-only artifact), then exits 0 // WITHOUT emitting a map. Sugar over --cache (force-rebuild both paths). bool noCache = false; // --no-cache: disable the warm-by-default per-root TMPDIR cache bool noIgnore = false; // --no-ignore (§N6-C): crawl gitignored paths too — the pre-2026-09-03 // walk. Default OFF: a git root's own ignore rules shape the corpus and // the header discloses ignored_files=/ignored_dirs= std::size_t maxFileBytes = kDefaultMaxFileBytes; // --max-file-size=N[K|M|G]: crawl size ceiling (default 4 MB) std::size_t maxMemoryBytes = 0; // --max-memory=N[K|M|G] (#350): the memory guard's limit; 0 = not given (memguard.h) std::string_view scipIndex; // --scip=index.scip: SCIP precision overlay — precise edges REPLACE name-based guesses; prov="scip" tags them std::string_view pinCensus; // --pin-census=FILE: eval-only side file naming, per DECIDED call site, the mechanism // that resolved it and the canonical id of every surviving target — the identity // `` omits, and the only way an oracle can tell a right locality pin from // a wrong one. Under --scip it also carries the index's own covered sites (O rows), so // both sides of the join live in one file. stdout is untouched (src/pincensus.h). bool packSignatures = false; // --pack-signatures: body-elided decl skeletons (~72-90% fewer bytes at top-10/50/100, ~80% at the default top-50; root-neutralised, since the repeated path prefix is charged in both forms and is not what this elides — see test/showcasecapturecheck.sh arm C) std::string_view query; // --query=TERMS: pure lexical (BM25) retrieval std::string_view grep; // --grep=STR: parallel literal scan + enclosing symbol + the matched line bool grepGiven = false; // G1/F1: --grep=/--regex= was already given once this run — a SECOND occurrence // used to silently overwrite the first (the pattern a caller reaches for when they // MEAN "and"); refused instead, naming the real AND spelling. See kViewFlags' dupGuardFlag. bool grepRegex = false; // --regex=PAT: like --grep but the pattern is an ECMAScript regex bool grepHandles = false; // --handles: mint freshness-pinned handles on unique editable rows bool noPrefilter = false; // --no-prefilter: (debug) skip the regex→trigram prefilter, full-scan every file — the soundness oracle for --regex int grepBefore = 0; // --grep-before=N (ripgrep -B): N source lines emitted before each --grep/--regex hit (0 = off, unchanged output) int grepAfter = 0; // --grep-after=N (ripgrep -A): N source lines emitted after each hit // --grep-context=N (ripgrep -C): shorthand that sets BOTH grepBefore and grepAfter to N // G3 (2026-08-15 harvest): boolean AND/NOT over --grep's already-collected hits — a flat term list, // no CNF, no parens (OR stays --regex='A|B'). Literal-only: refused together with --regex (validateConfig). std::vector grepAnd; // --and=TERM (repeatable): every term must ALSO be present std::vector grepNot; // --not=TERM (repeatable): the term must be ABSENT std::string_view grepScope; // --grep-scope=line|file (default line; validated in validateConfig) // R-H (2026-08-15 harvest report-ugrep §F3/§F4, funded by E5): --grep-in=code|any — which SPAN TIER a // hit must sit in to print. Empty ⇒ the default, code (the tightest non-empty tier); "any" turns the // tier pass off entirely, which is both the escape hatch and the pre-tier answer byte for byte. std::string_view grepIn; // --grep-in=code|any (default code; validated in validateConfig) std::string_view match; // --match=QUERY: tree-sitter structural query (shape search) // R2 (2026-08-20): the pattern surface. --match makes you know each grammar's node-kind vocabulary // before you can ask anything; --pattern takes the CODE SHAPE and compiles it per grammar (src/pattern.h). std::string_view pattern; // --pattern=PAT: code-shaped structural search, e.g. 'foo($X, ...)' bool lint = false; // --lint: built-in AST checks (rides the same query pass) std::string_view lintRulesDir; // --lint-rules=DIR: load user YAML lint rules (ast-grep style) — runs alongside/instead of --lint bool sarif = false; // --sarif (with --lint / --lint-rules): SAME findings, SARIF 2.1.0 (src/sarif.h) // instead of the native XML block — the shape github/codeql-action/ // upload-sarif consumes for the code-scanning UI. Pure re-serialization: no // new analysis, no re-ranking. Refused (loudly, in validateConfig) alone, // with --match (SARIF covers --lint findings only), with --with-profile // (the heat_* join has no SARIF field yet) and with paging (SARIF always // emits the FULL result set — GitHub's action wants one complete document). bool lintCatalog = false; // --lint-catalog: the built-in rule registry (name/sev/category/rationale/ // languages/since), one row per rule — src/lintcatalog.h. Standalone: needs // neither --lint nor --lint-rules, and ignores the corpus entirely. std::string_view lintSelect; // --lint-select=PREFIX[,...]: run only rules whose name starts with one of // these (or "*" for all) — modifies --lint / --lint-rules; refused alone. std::string_view lintIgnore; // --lint-ignore=PREFIX[,...]: drop rules whose name starts with one of these // (or "*" to drop everything) — applied AFTER --lint-select narrows the set. int lintMaxPerRule = 0; // --lint-max-per-rule=N: each rule's raw-capture budget (0 = kLintMaxPerRule, the // default runaway guard). knob-honesty-068: the floored answer's findings_next= names // it, so a count_capped="1" floor is one pasteable call from its total. std::vector expand; // --expand=NAME,...: emit full def bodies for these symbols; NAME:START-END slices to 1-based lines START..END of the def (octocode partial-fetch) std::vector outline; // --outline=NAME,...: control-flow skeletons (L3 scoped snippet) std::string_view forTask; // --for=TASK: the task lens — ranked signatures inventory + metrics, framed for reuse bool anchor = false; // --anchor (with --for, EXPERIMENTAL): seed PPR personalization from the lexical anchors and blend graph expansion into the lens rank (LARGER-style; graph.h anchoredLexicalRank) bool noRoute = false; // --no-route (with --for/--query): force plain subtoken+body BM25 (the pre-default-routing behavior). Routing is the DEFAULT: a deterministic confidence-gated query-shape classifier picks name-exact vs subtoken+body and prints which/why in the header (lexical.h chooseForRanker). --route is still accepted (now a no-op: routing is already on). Routing also carries the QUERY-SHAPE document demotion: when the task text parses as a stack trace / sanitizer report / compiler diagnostic, or as a pasted issue-template form, the document tier scores down (repo meta-docs — issue templates, CONTRIBUTING, changelogs — twice as hard) and route= says so; --no-route has no route= to disclose it in, so it does not demote either. Routing also orders doc-mention surfacing (filter.h docNoiseSymbolMultipliers): change logs and translated duplicates of a default-language doc are lifted last and to x0.35 of the usual height (BM25 untouched), unless the task asks about changes or translations or names the file. bool adaptive = false; // --adaptive (with --for/--query): cut the returned set at the largest relative score gap (Adaptive-k) with a floor (~5) + the existing top-k ceiling, so a sharp query returns few and a flat/broad one hits the ceiling; prints the cut in the header bool noMentionBoost = false; // --no-mention-boost (with --for): disable the B8 query-mention anchor — by default, files / dotted modules literally NAMED in the task text have their SCORE lifted to within 5% of the top score (the measured #1 competitor-win bucket; bench/headtohead) — a score promise, not a rank one: on a flat/tied head the anchored hit can still land several ranks below #1; a directly named symbol (Scope.symbol, ns::fn, a verbatim identifier — mention.h NAMED IDENTIFIERS) takes the same first slot. Inert when the text names nothing indexed (byte-identical). Env RIPWIRE_NO_MENTION=1 disables everywhere (incl. the MCP `for` verb). bool cochangeBoost = false; // --cochange-boost (with --for): OPT-IN, EXPERIMENTAL (the --anchor precedent) — the B3 co-change prior: files that historically change WITH the top-ranked files (git co-change, last 500 commits, support >= 3) get a small bounded score boost into the lower bundle; the top seeds can never be displaced. Honest numbers (B3 held-out record): train multi-file strict@10 +6.4pp but held-out +0.0pp (n=55) at warm p50 +19% — did NOT confirm on the Python LocBench corpus; default stays OFF pending a large-C++-corpus history eval. Env RIPWIRE_COCHANGE=1 enables everywhere (incl. the MCP `for` verb, which has no per-call flags). bool noDocMention = false; // --no-doc-mention (with --for): disable the doc-mention surfacing — by default, a doc that names one of the task's top-resolved symbols in a `backtick` (the g.mentions edges `--mentions=SYM` already exposes) is lifted into the bundle, strictly below that symbol's own score. Inert when nothing resolved has a mentioning doc (byte-identical). Route-agnostic (applies under --no-route too). Env RIPWIRE_NO_DOC_MENTION=1 disables everywhere (incl. the MCP `for`/`pack_task` verbs). bool signaturesOnly = false; // --signatures-only (with --for): opt OUT of the T3 terminal-by-default bundle — no auto section, no bundle="auto" disclosure; restores the pre-T3 signatures-only lens byte-identically. Contradicts --detail=N (the explicit body knob), refused together. Pre-registered: docs/EVALS.md §4, T3 round. bool autoBodies = false; // --auto-bodies (with --for): opt OUT of COMPACT conceptual serving — restore the rank-first auto walk on the subtoken+body route, which by default serves the ranked map plus a one-hop edge section and no body CDATA. A permanent posture flag, not a transition aid. Inert on the name-exact route (the allowance is already on there). Contradicts --signatures-only (no bodies at all) and --detail=N (the explicit body knob), refused with either. Pre-registered: docs/EVALS.md, the T3 route-narrowing round. std::string_view legoType; // --lego=TYPE: the interface→impls "Lego" view for ONE named interface/base (signature + method contract + every implementor, own-language only). file:name disambiguates a same-named type across languages. std::string_view sections; // --sections=lego,compose (modifies --for=TASK only): opt IN to the full / render the ranked --for lens collapses to a counted stub by default (L2, round-1 lever B1, §9.3 disclosed cut) — restores BOTH sections byte-identically to the pre-stub render in ONE call. A closed, comma-separated, order-insensitive set; each name at most once. std::string_view exemplar; // --exemplar=TASK|KIND (Q7): the repo's best-in-class instance of what you're about to write (by ROLE, not text similarity). A plain TASK string infers KIND from the top match; a KIND token selects directly. std::string_view recall; // --recall=TASK: retrieve the most relevant DOCS (memory/notes) + emit their full bodies bool mostImportantLast = false; // --order=important-last (was --most-important-last, now a hidden alias): emit highest-rank content last (explicit; always honoured) bool stable = false; // --order=stable (was --stable, now a hidden alias): emit in path/id order (stable prefix → provider KV-cache hits on re-runs) bool noStable = false; // --no-stable: opt OUT of the stable default that --mcp turns on (P2-C) — NOT an --order alias, unchanged bool noInventory = false; // --no-inventory: drop the default map's tier (D7, serialize.h mapInventoryWanted) — the pre-D7 map shape bool noAutoOrder = false; // --order=important-first (was --no-auto-order, now a hidden alias): opt OUT of T3's fill-aware auto important-last (see serialize.h kFillOrderThreshold) int maxTokens = 0; // --max-tokens=N: budget the map to ~N tokens (binary-search top-K) — SHAPES the map to fit std::size_t tokenBudget = 0; // --token-budget=N[K]: CI gate — 0 = disabled. After the map is built, if its est_tokens // (the SAME calibrated header number, never a second counter) exceeds N, exit 3 and name // both numbers on stderr; within budget, exit 0 and emit the map unchanged. Composable with // --max-tokens: that flag SHAPES the map to fit a target, this one ASSERTS and fails — // set neither, either, or both. // T2 pagination for high-cardinality verbs (--deps/--callers/--callees/--hotspots/--tree/--lint). Default // limit=0 = NO pagination (whole result, byte-identical to pre-T2 callers/gates). --limit=N bounds one // response to N items; --offset=M skips the first M. §P8: seven more verbs honor these now (--clones / // --cochange / --owners / --communities / --doc-drift / --whereis / --grep) — before that they accepted // both flags and ignored them, so a paging loop over --cochange re-served page 0 forever. The shared // window + root disclosure live in src/pageview.h; the gate is test/pagingsweepcheck.sh. // Seams are deterministic (results already sorted) so // page M+N is the exact continuation of page M — no dropped/duplicated item across the seam. int pageLimit = 0; // --limit=N: max items in this response (0 = unbounded) int pageOffset = 0; // --offset=M: skip the first M items (0 = from the start) bool metrics = false; // --metrics: annotate symbols with fan-in/out + role (descriptive) bool deps = false; // --deps: file→file dependency view (#include/import counts) bool hotspots = false; // --hotspots: complexity × recent git churn (maintenance-pain map) bool clones = false; // --clones: token-normalized duplicate function bodies bool readability = false; // --biggest-first (was --readability, now a hidden alias): the Posnett/Hindle/Devanbu // (MSR 2011) lens — per function, Halstead volume + token entropy + line span → P, // emitted LARGEST Halstead volume/token-count/length first (a size proxy — the // readability-ordering claim is WITHDRAWN, docs/EVALS.md §8). Pages through // limit/offset like the other report verbs; a ranking lens, never a grade (readability.h) bool nonlocalState = false; // --nonlocal-state: per function, the non-local MUTABLE state it or its transitive // callees reach — globals/statics/file-scope data — with READS and WRITES kept apart // and the site or callee that explains each one. Unsound by construction (indirect // calls, aliasing, shadowing), so every count is a floor (nonlocalstate.h) bool ensemble = false; // --ensemble: the FAMILY JOIN — per function, which of the four orthogonal evidence // families (structural / lexical / confusion / historical) fire, ranked by the COUNT // of distinct families and never by a weighted composite. A family that could not be // measured is reported UNAVAILABLE, never silent (ensemble.h) bool qualityPanel = false; // --quality-panel[=strict|default|lenient]: THE SINGLE COMMAND — the whole panel of // quality checks in ONE ranked report over SIX evidence families (the ensemble's four, // plus colocation and state), ranked by the COUNT of distinct families a preset counts // and never by a weighted composite. Presets SELECT and CUT, they never weight; a family // that could not be measured is UNAVAILABLE, never silent (qualitypanel.h) std::string_view qualityPanelPreset; // --quality-panel=PRESET: which preset. An unknown value is REFUSED rather than // silently replaced — a substituted selection is a silently different report bool contextRatio = false; // --context-ratio: the LOCAL-REASONING lens — per symbol (and rolled up per // file), the distinct entities and files a reader must resolve, and the share // of them living outside the unit's own file, weighted by the TOKENS a reader // must read as well as by edge count. A REFINEMENT of Beck & Diehl's per-class // congruence (FSE 2011) / Martin's instability, not a new measure (contextratio.h) bool namingCalibration = false; // --naming-calibration: §9.5 — score the naming-* lint rules against this // repo's OWN rename history (old -> new pairs mined from git log -p). // A NOISY proxy, disclosed as one; the floor a rule must clear lives in // test/namingcalibrationcheck.sh, not here (renamemine.h) bool namingConsistency = false; // --naming-consistency: §9.2 TIER A — the corpus's own case-CONVENTION // vote per (language, kind) group; every off-convention name in a // DECIDED group gets propose=, its own subtokens mechanically // recombined into the dominant style. A lens, exit 0 always // (namingconsistency.h) bool namingLocals = false; // --naming-locals: local-variable-indexing plan Phase 2 // (docs/LOCALS_INDEXING.md) — an OPT-IN --lint extension only, folded into // the naming lens's existing naming-short/-wordy/-underscore/-case // tags for LOCAL variable names inside large/deep/complex C/C++ // functions (naminglens.h). DEFAULT-OFF, and NOT wired into a plain // --lint run without this flag: the plan's own "Hard blocker on // default-enable" is unmet (no calibration harness can score a // local-scoped rule yet, and the required real-corpus manual audit // for idiomatic-short-name skew — i/j/k/buf/tmp/err — has not run). // Deliberately breaks naminglens.h's own stated invariant ("an // un-indexed loop local can never be flagged") — see that file's // WITHDRAWN note for why shipping a plausible-but-unaudited rule // is exactly the failure mode this flag's default-off guards against. bool commentCoherence = false; // --comment-coherence: per function/method with a doc comment, TWO published // content measures — Steidl/Hummel/Juergens c_coeff (ICPC 2013, HIGH is BAD: // the comment mostly restates the name) and Scalabrino CIC (ICPC 2016/JSEP // 2018, Jaccard of comment terms vs the method's own identifier terms). // UNAVAILABLE (not scored) where no comment exists. Complements --doc-drift // (staleness) with content, over a disjoint input (commentcoherence.h) bool cochange = false; // --cochange[=FILE]: files that change together in git (hidden coupling) std::string_view cochangeFile; // --cochange=FILE: lockstep partners of one file int cochangeRecur = 0; // --cochange-recur=K (with --cochange): report only pairs whose co-change RECURS in >= K of the mined window's sub-windows (Clio, ICSE 2011 — a discrepancy is not a violation the first time it appears). 0 = unfiltered (every row still carries recur=). Disclosed as min_recur= in the header when set. bool cochangeGroups = false; // --cochange-groups (with --cochange): emit Mo/Cai/Kazman's Modularity Violation GROUPS — "X co-changes with {A,B,C}, none of which it depends on" as ONE row naming the file to fix — instead of the pair list. Greedy cover, disclosed as such; the pair form stays the default. std::string_view archRules; // --arch=FILE: enforce layering rules (exit 2 on violation) bool communities = false; // --communities: cluster the call graph into cohesive modules bool communityFlag = false; // --community was given at all (a bare/empty value still routes to the // handler and refuses loudly rather than falling through to the map) std::string_view communityId; // --community=ID (§P11.6): drill into ONE module printed by // --communities/--zoom — its full ranked member list (paged) plus its // bridge edges to other modules. The selector chain at module // granularity: before this, the ids those two verbs emit were the only // identifiers in the tool that no verb accepted. bool zoom = false; // --zoom[=depth]: NESTED module hierarchy (multi-level Louvain) + top-level bridges (S5-D) int zoomDepth = 0; // --zoom=depth: cap the hierarchy at `depth` levels (0 = auto: contract until ≤10 top modules) int zoomLevels = 0; // --zoom-levels=N (P4, L7): how many levels the document PRINTS from the top // (0 = every level); the default prints 2 (levels_shown= discloses it) bool zoomLevelsSet = false; // did the caller pass --zoom-levels=? (the guard: it modifies --zoom only) bool includeBuiltins = false; // --include-builtins (P4, L7): --external-surface lists the sh builtins it drops by default bool report = false; // --report: auto architecture summary (markdown) bool tree = false; // --tree: file-level orientation map (top symbols per file) bool seams = false; // --seams: cross-module bridges no test reaches (untested integration seams) bool mermaid = false; // --mermaid: module (directory) dependency graph as a Mermaid diagram (human-viewable) std::string_view pathSpec; // --path=SRC,DST: shortest directed call-path between two symbols std::string_view connectSpec; // --connect=A,B,C: minimal connecting subgraph over 2..16 symbols int connectRadius = 6; // --connect-radius=N: undirected BFS bound (core clamps to 1..12) std::string_view impactSym; // --impact=SYM (or file:name): transitive blast radius (everything that reaches SYM) std::string_view mentionsSym; // --mentions=SYM: markdown docs that name SYM in a `backtick` (doc↔code) std::string_view affectedFiles; // --affected=F1,F2|SYM: test files transitively reaching the changed files — or the changed SYMBOL (§P11.2a, file-first argument rule in testmap.h) bool exercisesFlag = false; // --exercises was given at all (a bare/empty value still routes to the // handler and refuses loudly rather than falling through to the map) std::string_view exercisesFile; // --exercises=TESTFILE (§P11.2b): the INVERSE of --affected — the // non-test symbols this test file transitively calls into (what it // covers). Refuses on a non-test path: the verb IS the test/non-test // partition, so on a non-test file its subtraction means nothing. bool situ = false; // --situ[=FILES]: situational awareness (blast radius + tests + co-change) for a change std::string_view situFiles; // --situ=FILES: explicit changed files (else the current git diff) bool handoff = false; // --handoff: the continuation packet for the NEXT session — disk truth (branch/sha, // changed files+symbols, blast radius, tests-to-run) + labeled suggestions // (co-change partners, committed notes, plan/design doc pointers). Composes with --token-budget. bool testGate = false; // --test-gate[=FILES]: TDAD-parity regression contract — tests-to-run + untested blast radius (exit 4 if obligations) std::string_view testGateFiles; // --test-gate=FILES: explicit changed files (else the current git diff) bool scanSkills = false; // --scan-skills[=DIR]: scan skill files for injection/exfiltration patterns std::string_view scanSkillsDir; // --scan-skills=DIR: explicit dir (else repo-local + Claude + Codex homes) std::string_view scanSkillFile; // --scan-skill=FILE: scan a single skill file std::string_view batchFile; // --batch=FILE (or '-' for stdin): A4-R3 CLI form — newline-delimited // `verb:arg` sub-queries answered in one deduped (MCP `batch` verb // is the primary surface; this is the shell-pipeline counterpart) bool force = false; // --force: proceed even if scan finds CRITICAL issues (wrap hook only) bool html = false; // --html[=FILE]: emit self-contained HTML force-directed graph (P2-A) std::string_view htmlFile; // --html=FILE: write to FILE (else stdout) ColorBy colorBy = ColorBy::Lang; // --color-by=MODE: initial node-colour mode of the --html page (all five embedded) bool colorByExplicit = false; // did the user pass --color-by=? (gates the --html-required guard) bool owners = false; // --owners[=SYM]: bus-factor — recency-weighted author ownership per file (S5-C) std::string_view ownersSym; // --owners=SYM: restrict to the file containing SYM bool compress = false; // --compress: strip comments + blank runs from --expand/--outline body output (P2-B) bool baseline = false; // --baseline: write .ripwire_arch_baseline sidecar (accept current debt), exit 0 bool baselineUpdate = false; // --baseline-update: merge current violations into baseline (accept new debt), exit 0 bool deadCode = false; // --dead-code[=DIR]: internal source functions with zero indexed callers (a name-based graph reading, not a confidence score) std::string_view deadCodeDir; // --dead-code=DIR: restrict scan to DIR — matched as WHOLE path components (a dir, a nested dir, or a filename); a filter naming nothing indexed REFUSES (§P0.3). Leading ./ anchors DIR at the repo root instead of matching that component anywhere (§A10.6) bool qualityBaseline = false; // --quality-baseline: snapshot ccx/clones/dead to .ripwire_quality_baseline bool allowDirty = false; // --allow-dirty: let --quality-baseline pin on a tree that DIFFERS from HEAD (H11) — the sidecar is // then stamped dirty=1/absorbed=N and every later --quality-delta carries baseline_absorbed= bool showStale = false; // --show-stale: with --quality-delta, print the stale-ack rows (and the full header) a // regression-free answer collapses to counts (idea #11) bool qualityDelta = false; // --quality-delta: report only code-quality regressions vs that baseline (exit 2 if any MAJOR unacked one) std::string_view qualityDeltaRange; // --quality-delta=REV|A..B (R-I): compare two COMMITTED trees instead of the working // tree vs a baseline — the WAVE-level measurement. Same grammar --dmm= takes // (quality::resolveRefSpec owns it), same 12 kinds/gating/ack contract out bool qualityAck = false; // --quality-ack[=REASON]: accept the current findings into .ripwire_quality_acks (per-finding ratchet); shares qualityDelta's baseline resolution std::string_view qualityAckReason; // the reason recorded next to each acked finding std::string_view qualityAckOnly; // --ack-only=SUBSTR[,SUBSTR]: ack only findings whose kind, canonical id or facet (e.g. format-arity) contains one of these (default: all) std::string_view qualityScope; // P1 --scope=GLOB[,GLOB...]: the OWNERSHIP partition for a shared working tree — // findings outside it are printed but never gate, and --quality-ack refuses to write them. // Grammar + floors: rw::quality's SCOPE block. Only with --quality-delta/--quality-ack (refused elsewhere) bool dmm = false; // --dmm[=REV|A..B]: the Delta Maintainability Model scalar (di Biase/Rastogi/ // Bruntink/van Deursen, TechDebt 2019) — ONE trendable number in [0,1] for a // change: the share of moved volume that made the code healthier. The // per-kind complement to --quality-delta, which says WHICH debt grew but has // no scale. A DELTA, never a level: editing bad code without growing it // scores nothing at all (dmm.h) std::string_view dmmRange; // --dmm=REV (that commit vs its first parent) or --dmm=A..B (B vs A). Bare // --dmm compares the WORKING TREE against HEAD, the way --quality-delta does std::string_view editCheckSym; // --edit-check=SYM (B11/L5): fast per-symbol post-edit contract check — SYM's // param count + publicness now vs git HEAD (unchanged/new-symbol/contract-change), // plus its 1-hop callers with any call-site provably incompatible with the NEW // arity flagged. file:name disambiguates like --around/--lego. std::string_view safeDeleteSym; // --safe-delete=SYM: "can I delete this?" composed from signals the tool already // computes — 1-hop callers, the transitive --impact blast radius, every --uses // read/write/import/call/extends site, how much of the radius the tested= lens // covers, and --dead-code's own zero-caller/internal-linkage shape at defs=1. FACTS only: // risk= names what was found (none-found/uses-exist/untested-radius/unmodelled), never a // go/no-go verdict. file:name disambiguates like --around/--lego. std::string_view sliceSpec; // --slice=SYM[:VAR] (lane/paper-slice): NAME-BASED intra-procedural def-use // slice of VAR inside the ONE uniquely-resolved definition SYM (multiple defs // refuse, listing spellings — the --edit-check §A6a rule). Bare --slice=SYM // lists the sliceable locals. file:name disambiguates like --edit-check. std::string_view sliceFlow; // --slice-flow=back|fwd|both (lane/or-arise rung 2): transitive cross-statement // data-flow slice — bounded BFS over reaching-definition def-use edges from the // seed variable, the ARISE paper's own slicer semantics (a direction parameter; // stops at the function boundary). Modifies --slice=SYM:VAR only; refused alone // and on the bare inventory (a flow needs a seed variable). Value validated in // validateConfig. int sliceDepth = 0; // --slice-depth=N (1..32): the BFS depth bound for --slice-flow; 0 = unset, // which serves the disclosed default 8 (depth= on the root always states the // bound in force). Refused without --slice-flow — there is nothing to bound. std::string_view atSpec; // --at=FILE:LINE (ARISE get_enclosing_scopes parity): the enclosing-definition // chain at one location, outermost->innermost. The SAME seed spelled @FILE:LINE // in any SYM selector position resolves to the chain's innermost definition // (graph.h::resolveAtSeed) — the no-name half of the file:line:name grammar, // for the agent holding a compiler error / diff hunk / stack frame. A seed no // indexed definition spans is refused with the shared diagnosis, never an // empty chain. std::string_view replaceSymbolBody; // CLI-first write surface: the same transaction-safe engine as MCP std::string_view insertBeforeSymbol; std::string_view insertAfterSymbol; std::string_view editPayload; // --edit-payload=FILE|-: exact bytes; required for a CLI edit std::string_view editTargetFile; // --edit-target-file=SUBSTR: disambiguate same-named definitions bool noPostCheck = false; // P9: --no-post-check — skip the receipt's folded edit_check + tests_to_run (MCP: post_check:false) std::string_view editPlan; // --edit-plan=FILE: versioned multi-edit JSON plan bool editPlanDryRun = false; // --dry-run: validate/preview without writes bool editPlanApply = false; // --apply: preflight all, then commit per-file atomically bool prContext = false; // --pr-context[=BASEREF]: no-LLM review-evidence bundle for the diff (Wave-4) std::string_view prContextBase; // --pr-context=BASEREF: diff vs this ref (else the working-tree diff) bool mergeScoutFlag = false; // --merge-scout was given at all (tracked separately from the CSV value so a bare // `--merge-scout=` still routes to the handler and refuses loudly, rather than // silently falling through to the default map) std::string_view mergeScout; // --merge-scout=REF[,REF...] (L1): read-only cross-branch overlap — per-ref // changed symbols vs its merge-base with HEAD, pairwise same-symbol conflicts / // same-file textual risks, and a fewest-conflicts-first landing order. The dirty // working tree joins as an implicit extra arm. Single-root only (like --pr-context). bool planLanesFlag = false; // --plan-lanes[=N]: the PRE-HOC lane plan — // split across N worktrees, which lanes would collide and in what order // should they land. Tracked apart from the count so a bare --plan-lanes // (the --brief form) still routes to the handler. Single-root only. int planLaneCount = 0; // --plan-lanes=N: lanes to carve, 2..16 (auto-carve only) std::string_view laneTask; // --task="GOAL": what auto-carve partitions the ranked surface of std::string_view laneBrief; // --brief=FILE: one non-blank line per lane, each ranked on its own bool whereisFlag = false; // --whereis was given at all (a bare/empty value still routes to the // handler and refuses loudly rather than falling through to the map) bool whereisFullText = false; // --whereis-full-text: every t= prints whole, nothing else lifted (crossref.h whereisFullTextNext) std::string_view whereisListing; // --whereis-listing=defs|refs|all (default: defs when it lists more definitions than all, else the strictly shorter; crossref.h whereisServedListing) std::string_view whereis; // --whereis=SYM: every ref whose TREE contains SYM, // HEAD first, with on-head= saying whether the live line has it at all. // Scans each ref's FULL tree; each distinct blob is read once (content- // addressed), so N refs cost ~one tree. Single-root only. bool strayContent = false; // --stray-content[=SUBSTR]: per ref, the lines its own // divergent work AUTHORED that HEAD lacks + a merged/superseded/unmerged // verdict. SUBSTR filters the ref names. Single-root only. std::string_view strayFilter; // --stray-content=SUBSTR: only refs whose name contains SUBSTR bool landingPlan = false; // --plan (with --stray-content): "which branches still hold // real work, and in what order to land them" — selects the // unmerged refs (dropping superseded ones) and feeds them to // merge-scout's overlap + landing-order machinery. Alone it // would silently no-op — refuses without --stray-content // (the --detail/--flip companion-flag pattern). bool abiFlag = false; // --abi (with --stray-content): the cross-branch ABI-BREAK gate // — for every ref that changed a path HEAD declares a C-family // struct/class in, model that struct LEXICALLY on the ref's // blob (layout.h's own field-offset arithmetic, reused // verbatim) and compare against HEAD's --layout-computed // fields; report only DIFFERENCES, exit 2 on a real drift. // Single-root only (like --stray-content). std::string_view evalStray; // --eval-stray=FILE: labelled verdict-accuracy // eval for --stray-content. FILE is TSV `refverdict`. // The verdict verbs classify rather than rank, so their eval is // a confusion table, not recall@k like --eval-retrieval. bool darkFlags = false; // --flags[=SUBSTR]: the dark-content dashboard — every // #ifndef/#define compile gate, CMake option() and getenv() read, with // its kind, default, guarded size and read sites. std::string_view darkFlagsFilter; // --flags=SUBSTR: only gates whose name contains SUBSTR bool flipFlag = false; // --flip was given at all (a bare/empty value still routes to the // handler and refuses loudly rather than silently doing nothing) std::string_view flipGate; // --flip=NAME (with --flags): the blast radius of turning ONE gate ON — // the #if regions AND the constexpr-bool branches it governs, the // symbols holding them, what those transitively reach, and the tests // that cover them. Traverses the alias chain in both directions. bool docDrift = false; // --doc-drift[=SUBSTR]: verify the markdown docs' // CHECKABLE anchors (file:line refs, backticked symbol mentions, // `= N` constants, `[N]` array extents) against the live index and // report only the ones that no longer hold. std::string_view docDriftFilter; // --doc-drift=SUBSTR: only docs whose path contains SUBSTR std::string_view planLintFile; // --plan-lint=FILE (P3.2): the house PLAN format's // STRUCTURE check — task cards vs the status ledger, every // card's terminal status glyph, a stale hourglass line, an // undischarged "owed" mention. Opt-in per file (never a // directory sweep); read directly like --from-trace's FILE, // not through the crawled index. Exit 2 when the file shows // the recognized dialect AND carries a gating row — see // src/planlint.h for the full grammar and its stated limits. bool gateabilityFlag = false; // --doc-drift --gateability (r26-stamp Task B): per-UNDATED-doc // "what ONE annotation would make its live rows classifiable" + // the projected repo-wide drift= if every listed doc got it. // No-op without --doc-drift (cli.h below refuses a bare use). bool withHistory = false; // --with-history: turn ON the git-history name oracle // (src/gitoracle.h) for --doc-drift and --whereis. OPT-IN // because the probe is ONE git log walk over all of HEAD's // history — 0.8 s here, 3.0 s on a 2900-file/1863-commit // repo, against default paths of 0.15 s / 0.64 s. The // result is memoized per (repo, HEAD sha), so the second // question on one commit is a cache load, not a walk. bool layoutFlag = false; // --layout was given at all (a bare/empty value still routes to the // handler and refuses loudly rather than falling through to the map) std::string_view layoutStruct; // --layout=STRUCT: the CPU/GPU contract view for one // struct/class — COMPUTED field offsets/sizes/padding, every // static_assert in the index that mentions it, and every same-name // definition compared field-by-field (the mirror/stub drift check). // file:name disambiguates like --around/--lego. Exit 2 on a mirror // mismatch or a sizeof tripwire the computed size contradicts. bool fieldAffinity = false; // --field-affinity[=STRUCT]: the CACHE-LOCALITY lens. Which fields are // READ TOGETHER (a static field co-access affinity graph, Chilimbi // PLDI 1999) but declared far apart, diffed against 64-byte cache-line // geometry via the same LP64 offset model --layout uses. Bare = the // whole repo, ranked by separation cost. ADVICE ONLY — it never // proposes a reordering, because packing advice is non-monotonic // (tight packing can induce false sharing). std::string_view fieldAffinityStruct; // --field-affinity=STRUCT: narrow the report to one aggregate. The // AMBIGUITY universe stays the whole corpus either way (a field name // declared by two aggregates is refused, never guessed) — see // src/fieldaffinity.h::buildFieldOwners. std::string_view withProfile; // --with-profile=FILE: join --lint findings to the #PROF_TSV // block a RIPWIRE_PROFILE build's report emitted — a finding whose // enclosing symbol contains a scope site gains measured heat_* // attributes (the SYZYGY advice-mode pairing: static shape × // PMU weight). Modifier of --lint only; refused loudly alone. std::string_view fromTrace; // --from-trace=FILE ('-'=stdin) (B11/L2): map a stack trace / // sanitizer report / compiler-error text onto the indexed symbols — // table-driven frame extraction (python/asan/node/compiler/generic), // innermost-first ranking over IN-CORPUS frames only (out-of-corpus // frames listed+counted, never ranked), emits a --for-style bundle. std::string_view runTrace; // --run-trace="CMD" (VT-1): EXEC-MODE --from-trace. Run CMD under // `sh -c` (the make trust model: user privileges, inherited env, // stdin=/dev/null, NO sandbox), capture stdout+stderr interleaved, // and on a non-zero exit serve the from-trace bundle for the // captured text plus a token-frugal cut of the relevant // output lines; exit 0 gets a minimal success record, NO bundle. // The command's exit code is ALWAYS disclosed on . int runTimeoutSec = 0; // --run-timeout=SECONDS: cap for --run-trace's command (0 = the // default 600 s, disclosed as timeout_s=). At the cap the whole // process group is killed and the record says timed_out="1" — an // honest TIMEOUT, never an empty success. Modifier of --run-trace // only; refused loudly alone (validateConfig). bool noteAddFlag = false; // --note-add was given at all (a bare/empty value still routes to the // handler and refuses loudly rather than falling through to the map) std::string_view noteAdd; // --note-add="TARGET: text" (B11/L3): append a field note (target = a // canonical id path::scope::name, or a file path) to the committed, // sorted .ripwire_notes at the repo root; date = git committer clock, // NOT wall time. MUTATES a repo file — single-root only; prints the // exact written line and touches nothing else. bool notesList = false; // --notes (B11/L3): list all field notes grouped by target, dangling // targets (no matching indexed symbol/file — legal) flagged. Read-only. bool packTaskFlag = false; // --pack-task was given at all (a bare/empty value still routes to the // handler and refuses loudly rather than falling through to the map) std::string_view packTask; // --pack-task="TASK" (B11/L4): the budget-shared task bundle — ONE call // assembling, under ONE deterministic budget (default 6K tokens; // --token-budget overrides), the 5-call orientation dance in FIXED order: // (1) routed+anchored ranking, (2) top-K full bodies, (3) their 1-hop // caller signatures, (4) their field notes, (5) tests_to_run for the top // files, in FIXED order ranking>bodies>callers>notes>tests. Each section // holds a FIXED, up-front proportional quota of the budget (rank40/ // body30/caller15/note5/test10, pct); an under-spent section's leftover // quota rolls forward, so a small budget still zeroes a section // eventually but never past its own fair share. Each section truncates // rank-adaptively and the header reports EVERY truncation. int partitionCount = 0; // --partition=N (with --pack-task): fan-out form of the // bundle — ONE shared common core plus N minimally-overlapping per-agent // slices carved along the call graph's own Louvain communities, each // through the SAME assembler. --token-budget then means the budget for // ONE AGENT (core + its partition), not the whole document. 0 = off; // legal range 2..16; alone (without --pack-task) it refuses loudly. bool withGraph = false; // --with-graph (R8, with --for/--pack-task): append a compact MERMAID // flowchart of the bundle's top-N (<=8) ranked anchors + their 1-hop // call edges among themselves, as // right before . Reuses the --mermaid emitter's syntax, not a new // one. Off by default (G5: additive) — omitted, output is byte-identical. bool exportCcJson = false; // --export=cc.json[:FILE]: CodeCharta interchange export (Wave-4) std::string_view exportFile; // --export=cc.json:FILE: write to FILE (else stdout) bool noRedact = false; // --no-redact: emit source/doc bodies VERBATIM (skip the credential redaction; local-only workflows) bool refetch = false; // S3: --refetch: force a fresh git clone of a git-URL root instead of reusing the cached one bool doctor = false; // --doctor: self-diagnosis (binary/PATH staleness, grammar load, cache-dir // health, git reachability, tree-sitter version) — a DIAGNOSTIC verb, not // the deterministic map; environment-dependent lines are its whole point. std::string_view agent; // --agent=codex|claude: extend --doctor over that agent's LIVE binary/skills/hooks surface bool skippedList = false; // --skipped (§P0.5d, §L1): WHY the index does not contain a file, and // which files it DOES contain but cannot vouch for — one row // per drop (oversize/excluded/unsupported-ext) plus rows for // indexed-but-suspect files (degraded-parse/minified-suspect) and // per unindexed extension. Read-only; exit 0 always. std::string_view inDir; // --in=DIR (C1-b, 2026-09-12): with --rank-by=churn-decay, a root-relative directory — // a second block follows the unchanged global one and the symbol // map collapses to a counted stub; refused with any other verb (validateModifierGuards) std::string_view since; // --since=REV|DATE: scope churn/co-change mining to commits after this point // (--hotspots/--cochange/--rank-by=churn). REV (e.g. HEAD~20, a tag) is // deterministic; a git approxidate ("2 weeks ago") is wall-clock-relative // by construction. Absent/unresolvable ⇒ unchanged all-history default — // additive flag, degrades clean (never crashes on a bad value). bool ok = true; // false ⇒ parse error / usage printed }; // THE lean/rich decision, in ONE place. A rich ingest carries per-symbol persisted subtoken statistics // (ingest_model.h builds them under this very flag), which is what lets lexicalScoresTiered score from // lookups instead of re-tokenizing the whole corpus per query. Verbs that score MANY queries against one // tree need it; single-query verbs are better off lean, because an expensive rich parse to save one scan // is a loss (measured: --recall's entire lexical cost is ~0.03 s against --for's ~0.23 s). // // It is a FUNCTION rather than an expression inlined at the dispatch site because being an unverifiable // enumeration is precisely how it went wrong: --eval-retrieval/-mined/-skills each score thousands of // queries and were absent from it, so they re-tokenized the corpus ~6,000 times per run for as long as // they existed, with nothing in any output naming the regime. Now --doctor derives its rich_verbs= roster // by ASKING this predicate per verb (verbs_doctor.h), so the published roster cannot drift from the // behaviour, and test/knownitemcheck.sh asserts that roster is both complete and discriminating. inline bool needsValueUses( const Config& cfg ) noexcept { return !cfg.usesSym.empty() || cfg.metrics || !cfg.forTask.empty() || !cfg.exemplar.empty() || cfg.contextRatio || cfg.nonlocalState || cfg.qualityPanel || !cfg.verifyClaim.empty() // uses()/unused() claims count read/write use-sites || cfg.evalRetrieval || !cfg.evalMined.empty() || !cfg.evalSkills.empty(); } inline bool startsWith( std::string_view s, std::string_view p ) noexcept { return s.size() >= p.size() && std::memcmp( s.data(), p.data(), p.size() ) == 0; } // parse a NUL-terminated "...=" value tail; reject empty / non-numeric / <1 / overflow // (so `--top-k=` no longer silently becomes 0 = "emit ALL symbols"). inline bool parsePosInt( const char* s, int& out ) noexcept { char* end = nullptr; const long v = std::strtol( s, &end, 10 ); if( end == s || *end != '\0' || v < 1 || v > 1000000000 ) { return false; } out = int( v ); return true; } // like parsePosInt but 0 is VALID (for --offset=0 = "from the start"). Rejects empty / non-numeric / // negative / overflow. Kept separate so --limit / --top-k stay strictly positive (0 = "emit all" trap). inline bool parseNonNegInt( const char* s, int& out ) noexcept { if( s[0] < '0' || s[0] > '9' ) { return false; // reject "-1"/"" (strtol would wrap/accept) } char* end = nullptr; const long v = std::strtol( s, &end, 10 ); if( end == s || *end != '\0' || v < 0 || v > 1000000000 ) { return false; } out = int( v ); return true; } // §A10.2 — WHY a --limit/--offset value was refused, not just THAT it was. Both flags used to answer every // bad value with the same one-liner, and for an out-of-range value that sentence is simply untrue: // `--offset=999999999999` was refused with "needs a non-negative integer" when the value IS non-negative // and merely exceeds the accepted range. The house refusal shape (used by --partition, --plan-lanes, // --token-budget) names the flag, states the actual problem, and shows what to type instead. // // kPageValueMax is parsePosInt's own ceiling, restated here so the two cannot drift: the paging flags stay // on exactly the range every other integer flag accepts. inline constexpr long kPageValueMax = 1000000000; // §B8.2 — the ONE sentence a bad flag VALUE is refused with, so the surface cannot grow a second dialect. // Ten numeric arms each hand-wrote their own fprintf and every one of them drifted the same way: a single // fixed sentence for empty AND garbage, with neither the offending value nor an example in it — // `--zoom=` and `--zoom=zzq` were byte-identical refusals, so a caller whose $DEPTH expanded to nothing // could not tell an unset variable from a typo without re-reading its own command line. The compliant // shape was already in this file twice (refusePageValue below, refuseEmptyValue further down): name the // FLAG, state the DOMAIN, echo what was GOT, show something RUNNABLE. // // `got` is a view because the --path arm's value is a slice of argv, not a NUL-terminated tail. inline void refuseFlagValue( const char* flag, const char* wanted, std::string_view got, const char* example ) noexcept { rw::emitTo( stderr, "ripwire: {} needs {} — got '{}', e.g. {}\n", flag, wanted, std::string_view( got.data(), got.size() ), example ); } // Parse ONE paging value and, on refusal, print the reason itself. Returns false ⇔ the caller must set // c.ok = false and bail — the arms stay one line each, which is what keeps 91 parse arms readable. inline bool refusePageValue( const char* flag, const char* s, bool isZeroAllowed, int& out ) noexcept { const long least = isZeroAllowed ? 0 : 1; const char* wanted = isZeroAllowed ? "a non-negative integer" : "a positive integer"; char* end = nullptr; // first char must be a digit: strtol would accept "-1" (and " 12"), and a negative value must read as // "not an integer we accept" rather than as an out-of-range one const bool isDigit = s[0] >= '0' && s[0] <= '9'; const long v = isDigit ? std::strtol( s, &end, 10 ) : 0; // the paging flags' example is the flag itself at a round value — built here rather than passed in so // the two paging arms stay one line each, and printed through the shared refusal above so a change to // the sentence reaches every value-taking flag at once. char example[64] = {}; rw::formatTo( example, sizeof( example ), "{}=100", flag ); if( !isDigit || end == s || *end != '\0' || v < least ) { refuseFlagValue( flag, wanted, s, example ); return false; } if( v > kPageValueMax ) { rw::emitTo( stderr, "ripwire: {}={} is out of range (the maximum is {}) — e.g. {}=100\n", flag, s, kPageValueMax, flag ); return false; } out = int( v ); return true; } // §A9 V1-4 — an EMPTY value is a MISTYPED VERB, not a request for the default map. `--grep=` (usually a // shell variable that expanded to nothing) fell through every value-taking arm and printed the whole // 6302-symbol ranked map at exit 0: the caller asked a question and silently got an atlas. Five verbs // (--whereis / --pack-task / --community / --zoom / --exercises) already refused, each through its own // hand-written sentence in its own handler. This is that refusal expressed ONCE, so a value-taking flag // inherits it by filling in two table columns instead of by remembering to copy a paragraph — and so a NEW // value-taking flag cannot ship without its author deciding, in the table, whether an empty value means // anything for it. // // §B5 (capture-audit-4) made that last clause TRUE rather than aspirational: "deciding in the table" only // binds arms that are IN the table, and 40 were not. 23 moved in, and the EmptyValue column beside the // prefix makes the decision a required, named, consteval-checked field. The counts that used to sit in this // paragraph are deliberately gone — a rotted count is trap-ledger #12, and kViewFlags/kHandWrittenFlagArms // are the two places that state them, each with a gate that re-derives them from source. // // `flag` is the table's prefix spelling ("--grep="); the trailing '=' is dropped for the sentence so the // refusal names the FLAG and the example names the spelling to type — the house shape refusePageValue and // the --partition / --plan-lanes guards already use. inline void refuseEmptyValue( std::string_view flag, const char* needs, const char* example ) noexcept { const std::string_view bare = flag.substr( 0, flag.size() - 1 ); rw::emitTo( stderr, "ripwire: {}= is empty — it needs {}, e.g. {}\n", std::string_view( bare.data(), bare.size() ), needs, example ); } // parse a NUL-terminated "...=" value tail with the same reject rules as parsePosInt: empty / // non-numeric / signed / zero / overflow (so `--pack-budget-bytes=-1` no longer wraps to 2^64-1 and // `=abc` no longer silently becomes 0). First char must be a digit — strtoull would accept "-1" by wrapping. inline bool parsePosU64( const char* s, std::size_t& out ) noexcept { if( s[0] < '0' || s[0] > '9' ) { return false; } char* end = nullptr; errno = 0; const unsigned long long v = std::strtoull( s, &end, 10 ); if( end == s || *end != '\0' || v < 1 || errno == ERANGE ) { return false; } out = std::size_t( v ); return true; } // parse a "...=[K|M|G]" BYTE-SIZE value: an optional single-char suffix (case-insensitive, powers of // 1024, a trailing "B" tolerated so "10MB" works) scales the number; a bare number is bytes. Same reject // rules as parsePosU64 (empty / signed / zero / bad-suffix / overflow). Used by --max-file-size. inline bool parseByteSize( const char* s, std::size_t& out ) noexcept { if( s[0] < '0' || s[0] > '9' ) { return false; } char* end = nullptr; errno = 0; const unsigned long long v = std::strtoull( s, &end, 10 ); if( end == s || v < 1 || errno == ERANGE ) { return false; } std::size_t mult = 1; if( *end != '\0' ) // an optional K/M/G scale suffix { switch( *end ) { case 'k': case 'K': mult = 1024ull; break; case 'm': case 'M': mult = 1024ull * 1024; break; case 'g': case 'G': mult = 1024ull * 1024 * 1024; break; default: return false; } ++end; if( *end == 'b' || *end == 'B' ) { ++end; // tolerate the "B" of KB/MB/GB } if( *end != '\0' ) { return false; } } if( v > static_cast( SIZE_MAX / mult ) ) { return false; // overflow guard } out = std::size_t( v ) * mult; return true; } // #350: the memory guard's limit — parseByteSize's grammar, and never below the 64 MiB floor (memguard::kFloorBytes, // restated here because cli.h does not include the guard): `--max-memory=512` meant 512M, and obeying it would stop // every run at once. Shared by the flag and by RIPWIRE_MAX_MEMORY (main.cpp), so the two refuse the same values. inline constexpr std::size_t kMaxMemoryFloorBytes = 64ull * 1024 * 1024; inline bool parseMemoryLimit( const char* s, std::size_t& out ) noexcept { std::size_t bytes = 0; if( !parseByteSize( s, bytes ) || bytes < kMaxMemoryFloorBytes ) { return false; } out = bytes; return true; } // The self-documenting surface: a coding agent that finds the binary learns the WHOLE surface here, // grouped by the question each flag answers. Keep this complete — it is the cold-start contract (README // mirrors it). --help prints to stdout (exit 0); a parse error prints the same to stderr (exit nonzero). // The --plan-lanes rows, split out of printUsage's one giant literal rather than appended to it: this verb's // help carries the whole claim-key argument and the auto-carve caveat (the single most important sentence in // it), which is a paragraph, not a row — and printUsage is already the longest function in this file. inline constexpr char kHelpPlanLanes[] = " --plan-lanes=N --task=GOAL plan lanes BEFORE any code: which of N parallel worktrees would collide, and how to land them\n" " PRE-HOC lane plan: BEFORE a line is written, if this task is split across N isolated\n" " worktrees (N=2..16), which lanes would COLLIDE and in what order should they land.\n" " Where --merge-scout says \"these branches already conflict\", this says \"these lanes\n" " WOULD conflict if assigned this way\" — no ref to resolve, no archive, no re-ingest.\n" " JSON on stdout, always (redirect it: > .ripwire_lanes.json); ripwire writes no file.\n" " Exit 0 whenever a plan was produced, INCLUDING when conflicts are predicted (conflicts\n" " are data, and the landing order exists to handle them); exit 1 only for refusals.\n" " A claim keys on path+scope+name, never on id= (id degrades to a bare NAME when no scope\n" " was captured, so free functions in different files would collide); id= is carried per row\n" " for addressability, null when it would be bare, with id_addressable saying so. Three\n" " separate pair classes: conflicts[] (same claim key on both lanes — git will fight),\n" " same_file_risk[] (different keys, same file, aggregated per file), contract_touch[] (one\n" " lane's claim sits in another's blast radius — an adaptation, NOT a merge conflict). The\n" " conflict test runs on CLAIMS, never on blast radii. warnings[] carries every honest limit\n" " in band with a stable code. Each lane also carries an advisory execution object: the current\n" " Codex model + reasoning effort, selecting rule, exact structural signals and caveats under\n" " policy=codex-lane/v1. basis=structural-only: it does NOT understand task semantics, runtime\n" " behavior or security sensitivity; the orchestrator must override those cases. Single-root only.\n" " AUTO-CARVE SPLITS THE RANKED SURFACE, NOT\n" " YOUR SENTENCE: if your task has enumerable parts, use --brief and write one line per part.\n" " --plan-lanes --brief=FILE the explicit form of the above: one line per lane in FILE, N = the line count\n" " the explicit form of the above: one non-blank line per lane, N = the line count. Each line\n" " is ranked on its own — no community carve, no bin packing — so the lane boundaries are the\n" " ones you wrote. This is the mode whose precision is defensible; prefer it when you can.\n" " Lane isolation is a QUALITY argument, not a speed one (CAID, arXiv 2603.21489: 63.3% vs\n" " 55.5% shared, largest gains on weaker lane models — and wall clock got WORSE).\n"; /// Print the authoritative CLI usage and flag catalog to the caller-provided output stream. inline constexpr char kHelpHead[] = "ripwire — the \"ripgrep of AI context\": parse a codebase, rank symbols by Personalized PageRank,\n" "stream a deterministic minified XML map to stdout. Zero runtime deps. Languages: C++, C, ObjC/ObjC++,\n" "Metal (MSL, .metal — C++ grammar), CUDA (.cu/.cuh — tree-sitter-cuda, <<<>>> launches are call edges),\n" "Python, TypeScript (.ts/.tsx/.mts/.cts; .astro frontmatter), JavaScript, Java, Ruby, PHP (.php/.phtml), Lua, Elixir (.ex/.exs), Dart (.dart), Kotlin (.kt), Bash, Go, Rust, Swift, C#,\n" "GDScript (.gd — Godot; .tscn/.tres/.gdshader are NOT indexed);\n" "JSON, TOML, YAML (config keys); Markdown (.md/.markdown — headings are section symbols with spans).\n\n" "usage: ripwire [flags] # default = the ranked map of on stdout\n" " ripwire ... [flags] # multi-root workspace: ONE merged graph over 2..16 checkouts\n" " # (service+client, split monorepo). Paths are labeled /;\n" " # cross-root edges only on explicit evidence (path-resolved include/\n" " # import, FFI binding) — same-name symbols in unrelated repos stay\n" " # unlinked. Root order on the command line is irrelevant (canonical).\n" " # Per-root git history; --quality-delta/--test-gate/--eval*/--arch\n" " # baselines/--pr-context stay single-root (run them per root).\n" " ripwire [flags] # shallow-clone the repo to a temp cache dir, then map it (https:// or git@)\n" " ripwire wrap # print the recipe to wire ripwire into claude|cursor|codex|aider|...\n\n" " understand a codebase cold\n" " ranked symbol map (start here)\n" " --top-k=N keep only the N highest-ranked symbols (default 200)\n" " keep the N highest-ranked symbols (default 200) — applies to the default map,\n" " plain --query, and --format=candidates (incl. with --for). --for's OWN\n" " signature/lego/compose bundle self-limits via --pack-top-n instead — --top-k is\n" " INERT there (documented, not fixed — a real fix is a behavior change); to WIDEN a\n" " --for answer use --limit=N, the file-grain page (one row per file), not --top-k.\n" " --pack-task/--from-trace/--run-trace self-budget via --token-budget, not --top-k.\n" " --top-k=0 emits NO ranked map at all — ONLY the payload you asked for\n" " (--expand/--outline/--pack-signatures/--pack-top-n). Use it when you want the\n" " body and not the ~200-symbol map that otherwise rides along with it; the \n" " root then carries est_tokens= (the payload's price, the number --token-budget\n" " gates on), since no map header is there to carry it.\n" " --no-inventory drop the default map's tier, the files and entries its ranked rows leave out.\n" " The default map appends after its ranked rows whenever they\n" " left out a file with code: rows (program entries by each language's own entry\n" " convention) and rows naming or counting every unshown file by directory. This\n" " flag drops it (the rows are unchanged either way). The tier never rides a map fitted\n" " to a token budget, nor a payload verb's ride-along map.\n" " --max-tokens=N budget the map to ~N tokens (binary-search top-K) — SHAPES the map to fit.\n" " THE FIT IS A BYTE CEILING, and it is deliberately CONSERVATIVE: N is converted at\n" " 2.36 B/tok (the densest calibrated language, so N holds for any corpus) times a 0.90\n" " headroom factor. The map's own est_tokens uses THIS corpus's language-weighted rate\n" " instead, so a conformant fit REPORTS a number below the N you asked for — expect\n" " ~10-20% of N unused. The shaped map discloses both: max_tokens=N (asked) and\n" " fit_bytes=B (honoured). Consequence for composing it with --token-budget=N below:\n" " the two Ns are different units, so the same N on both is NOT a tautology.\n" " At a SMALL N the map's fixed floor (envelope + legend) can exceed fit_bytes with\n" " even one symbol emitted — that map says over_ceiling=1 rather than overshoot in\n" " silence, and its est_tokens can then exceed N. XML only: the --json map carries\n" " no max_tokens=/fit_bytes= keys yet, and its fit is measured in XML bytes.\n" " On --recall it SHAPES the doc bundle, and the ceiling is SPLIT ACROSS the docs\n" " rather than handed to the top hit: the budget serves the longest rank PREFIX it\n" " can give each doc a readable slice, then divides the bytes equally — a doc\n" " needing LESS than its share takes only what it needs and the surplus flows to the\n" " ones needing more. One long top hit no longer erases the rest of the corpus, and a\n" " bigger ceiling never returns FEWER docs. Docs past the prefix are dropped from the\n" " BOTTOM of the ranking; selection ORDER never changes. Every cut is DISCLOSED\n" " (header total=/shown=/capped=/truncated=/share_bytes=, a per-doc [truncated: X of\n" " Y bytes] marker, and a closing (capped: …) note); share_bytes= is that per-doc\n" " ceiling and is ABSENT when it bound no doc.\n" " On --for --detail=N it bounds THE BODIES ALONE: the header, signatures, legend and\n" " symbol table are not charged against it, so the bundle can price past N. That is\n" " deliberate — a ceiling bounds the tail and never the head, and a complete small\n" " answer is not worth cutting to fit — so the root DISCLOSES the overshoot instead,\n" " carrying over_ceiling=\"1\" beside max_tokens=N whenever est_tokens exceeds N. Reach\n" " for --token-budget=N when the whole DOCUMENT must be bounded; this flag SHAPES.\n" " --token-budget=N[K|M|G] cap the whole document at N tokens — a hard gate on the map, a trim-to-fit on the task lenses\n" " two personalities depending on the verb:\n" " - default map / --query / --recall: a CI GATE — exit 3 if the emitted DOCUMENT's\n" " est_tokens exceeds N. That is the map PLUS every block appended after it\n" " (///), each charged from the bytes it actually\n" " emits at the calibrated rate for what those bytes are — so\n" " --pack-top-n=3 --token-budget=600 gates on the ~67KB it would stream, not on\n" " the map alone. (test/tokenbudgetcheck.sh reports the live MAPE vs tiktoken\n" " o200k when tiktoken is installed; the estimate is calibrated, never exact —\n" " Claude's tokenizer is not public.) Within budget: exit 0, output unchanged.\n" " ASSERTS and fails, vs --max-tokens which shapes to fit — composable: set\n" " neither, either, or both (e.g. --max-tokens=16000 --token-budget=16K), but see\n" " --max-tokens above: the two Ns are measured in different units.\n" " Over budget, nothing of the artifact reaches stdout — only a small record\n" " naming withheld_est_tokens= vs budget=, the same vocabulary --recall uses,\n" " since est_tokens= is normatively about what a run PRINTED. On --recall the\n" " check likewise runs BEFORE a byte of the bundle is emitted: stdout gets the\n" " header line naming what was withheld, never the artifact just rejected.\n" " --json GATES AT A DIFFERENT NUMBER for the same request, and NOT by a fixed\n" " factor: the flag measures the DOCUMENT that was emitted, and whether JSON or\n" " XML is smaller flips with RESULT SIZE on this corpus. Small: JSON wins\n" " (MEASURED on src --top-k=20: est_tokens 1146 XML vs 899 JSON, ~22% smaller).\n" " Large: XML wins instead (MEASURED on src --top-k=200: est_tokens 9405 XML vs\n" " 9724 JSON, ~3% LARGER) — the crossover sits near top-k~100-150 here, so the\n" " same N can pass or fail differently under --json depending on dialect AND\n" " size — never assume one direction, measure the request you actually gate.\n" " - --for / --pack-task / --from-trace / --run-trace: SHAPES instead of gating — overrides that\n" " lens's own default payload budget and trims to fit, always exit 0. --for's\n" " header reports est_tokens=\"N\" so its fit is checkable; --pack-task/--from-trace\n" " report their budget ledger in the header report line instead. On --for's auto\n" " bundle the ceiling is SPLIT, not handed to the signatures first: the sig side's\n" " claim caps at the default sig budget and the rest flows to the inline bodies,\n" " so a wider ceiling never serves fewer of them (see --for below).\n" " Its VERBATIM task echo is bytes no trim can shrink, so past some task length the\n" " header floor alone exceeds the ceiling: the lens drops the comment's DUPLICATE echo\n" " first (task_echo: dropped (ceiling); task= keeps the verbatim copy), then labels it\n" " over_ceiling (--recall: over_ceiling=1) — never a trim it did not actually do.\n" " --help-task=TASK describe a task, get ONE recommended command back — or an honest abstention\n" " deterministic enhanced help: recommend ONE executable Ripwire CLI command for this\n" " repository and task, or abstain when evidence/applicability is insufficient. Reports\n" " the intent, integer score/margin and repository facts; never calls a model, executes\n" " the recommendation, or accesses the network. Structured claims/traces/symbols outrank\n" " lexical cues. Recommendation only; pipe trace text to stdin for --from-trace=-.\n" " A symbol NAME resolves as evidence only when it is identifier-shaped (camelCase,\n" " PascalCase, snake_case, a ::/. scope) or marked as code in the task text -- a\n" " short bare word (a lone letter, a SCREAMING name, or an ordinary word that\n" " happens to match an indexed name, e.g. django's F) does not resolve on its own.\n" " Wrap it in backticks (task text: `F`) or write it in call form (F()) to route on\n" " it by name.\n" " --for=TASK the task lens: say what you are doing, get the signatures and bodies to read first\n" " the task lens: ranked signatures + metrics framed for reuse. The bundle enforces a\n" // §B7.5 (CA4): this said — the STRING ENUM the §P8 vocabulary migration // replaced with the boolean capped="1" every other truncating element spells (serialize.h's own // comment records the rename). No output the tool can emit has contained payload= since; --help was // the last place the dead spelling survived, and a reader grepping their bundle for it finds nothing. " ~7.5KB default payload budget (tail entries trim first; marks it: T rows handed to the trim, S printed). The r=1 (top-ranked)\n" " row carries next=\"--expand=FILE:NAME\" — the one pasteable follow-up, the body\n" " that ends the search (defined here, not in the bundle's own header, which the\n" " token ladder does not charge). An explicit\n" " --token-budget=N overrides the default at the conservative byte rate\n" " (SHAPES, exit 0; see --token-budget above) and the header reports the delivered est_tokens.\n" " TERMINAL BY DEFAULT: after the signatures, the top-ranked symbols' FULL bodies ride\n" " inline (CDATA + callee signatures, the --expand shape) under a fixed extra body allowance —\n" " whole-body-or-not-at-all, rank-first, capped at the --pack-task candidate cap (6). The \n" " root discloses it: bundle=\"auto\" bodies=\"N\" (bodies=\"0\" reason=\"budget\" when none fit) —\n" " on EVERY auto-mode run: a ceiling the signatures alone exhaust still carries the attribute\n" " (legend and empty shell dropped there; only the attribute has reserved bytes), and\n" " --for --json, which serves no bodies by design, says so with \"bundle\":\"sigs\". Only the\n" " caller-chosen postures (--signatures-only, --detail=N) are attribute-free.\n" " ANCHOR-ONLY when the route names one: a query that NAMES a symbol gets THAT symbol's own\n" " body or NO body — never a same-named doc section, type stub or re-export shim from another\n" " file standing in for it. If the anchor's own body does not fit, the bundle serves nothing\n" " and says so, and the per-item over-budget comment names what was dropped.\n" " COMPACT ON THE CONCEPTUAL ROUTE: a query that anchors nothing (subtoken+body) gets the\n" " ranked map plus a section — the same candidate head's ONE-HOP callee signatures,\n" " the block a body carries — and NO body CDATA, disclosed as bundle=\"compact\"\n" " bodies=\"0\" reason=\"compact-route\". Read the map, then --expand=SYM the one you want.\n" " A ranked function or method whose whole name is a word of the query (by name, not\n" " resolved) gets the first rows, past that head: up to 3, each listing its\n" " callees to 100 names (capped=1 next= past it); more are counted as qword_cut= with next=.\n" " --auto-bodies restores the body walk there. That shape discloses on every run too: a\n" " ceiling the signatures alone exhaust carries bundle=\"compact\" bodies=\"0\"\n" " reason=\"budget\" — three distinct reasons, never collapsed (compact-route = the route\n" " chose edges, no_candidates = nothing scored, budget = the ceiling was spent).\n" " An explicit --token-budget=N is a hard ceiling, split so a wider ceiling never buys less:\n" " the signature side's claim is capped at the DEFAULT ~7.5KB sig budget and every byte beyond\n" " it flows to the enrichment — at any ceiling at or above the default's effective total the\n" " block is byte-identical to the default run's, so every body (or hop row) the default\n" " serves still fits. An explicit --pack-top-n is an explicit SIG posture and keeps the\n" " whole-ceiling sig claim. --compress composes: the served bodies (auto/anchor and\n" " --detail=N alike) go through the same comment-strip --expand uses, disclosed as\n" " compress=\"1\" on the element (nothing to strip on the compact route).\n" " RANKING CONFIDENCE, disclosed not scored: the root always carries\n" " confidence=\"high|low\" margin_pct=\"N\" — derived from the SAME relevance-cliff gap\n" " statistic --adaptive cuts at (no new scorer, no behavior change; the --json dialect\n" " carries the same two keys). low means the ranking is FLAT (no material score cliff\n" " and more positive matches than the head shows) — treat the set as a starting point,\n" " not an answer; high means a material cliff inside the served head (margin_pct= is\n" " that drop as a whole percent) or every positive match already shown.\n" " COVERAGE, and the widening page: on a THIN answer only, the root also carries\n" " coverage=\"N\" — the IDF-weighted share (whole percent) of the task's subtokens found\n" " in the top-ranked symbol's name, doc or body; an unmatched subtoken weighs as the\n" " rarest, so a \"(#12147)\" token honestly lowers it. THIN = coverage under 50, or a ranked\n" " head spread over fewer than 3 files: coverage= and its legend clause ride the root\n" " (present-only: a confident answer carries neither) and the r=1 row's next= names\n" " --for=TASK --limit=40 instead of --expand — the FILE-GRAIN WIDENING PAGE:\n" " …,\n" " ONE row per positive-score file (~100 B each), ranked file-first by score= = the\n" " IDF-weighted share of the task's subtokens the file's top 8 symbols cover between\n" " them (a term counts once however often it recurs, so one huge file cannot\n" " monopolise; ties by the best symbol's lens score, then path), n= its positive-score\n" " symbols, sym= its top symbols. --limit=N sets the rows, --offset=M pages (the house\n" " quintet + next= on a cut page). The page takes NO bundle-shaping flag (--json,\n" " --format=candidates, --detail, --signatures-only, --auto-bodies, --adaptive,\n" " --token-budget, --top-k …): each is refused beside --limit, never ignored. Measured\n" " on the routing-loop ladder (RocksDB, frozen 30): the follow-up that completes\n" " answers where a body cannot.\n" " / COLLAPSE TO A COUNTED STUB BY DEFAULT (L2, a disclosed cut, never a\n" " silent one): when the ranked set names an interface with implementors, or a HAS-A\n" " field edge into/out of it, the bundle used to spend the full // method\n" " contract or row list on it — the STUB instead says only how many rows the\n" " section held: (same shape for ).\n" " No stub for a section that would be EMPTY (an absent section stays absent). total= is\n" " that section's own pre-cap row count (packLego's post-dedup interface count; every\n" " matched HAS-A edge for compose, which caps nothing). next= carries the one restoring\n" " spelling that returns BOTH sections byte-identical to the un-stubbed render, in ONE\n" " call: --sections=lego,compose.\n" " --sections=lego,compose (with --for) opt back into the full / render the stub above replaces\n" " (with --for=TASK) restore the / sections the ranked bundle collapses to\n" " a counted stub by default — this IS the stub's own next= spelling, so pasting it back\n" " unchanged is enough. A comma-separated, order-insensitive, closed set (lego and/or\n" " compose, each named at most once); every other section of the bundle is unaffected —\n" " the budget is the stubbed answer's. Under an explicit --token-budget\n" " the restored sections are charged in full, so can carry fewer rows there.\n" " --signatures-only (with --for) signatures only: no automatic bodies in the bundle\n" " (with --for) opt out of the terminal-by-default bundle: no auto bodies, no bundle=\"auto\"\n" " attribute — the signatures-only lens exactly as before. Contradicts --detail=N (refused\n" " together); --detail=N remains the explicit body knob and supersedes the automatic pick\n" " --auto-bodies (with --for) restore the automatic body walk on the conceptual route\n" " (with --for) opt out of COMPACT conceptual serving: restore the rank-first auto \n" " walk on the subtoken+body route (bundle=\"auto\", up to 6 full bodies) instead of the\n" " edge section. Inert on the name-exact route, where the allowance already runs.\n" " Contradicts --signatures-only and --detail=N (refused with either)\n" " --no-route (with --for) skip the query-shape router and always rank by subtoken+body BM25\n" " (with --for/--query) force plain subtoken+body BM25. Routing is now the DEFAULT: a deterministic,\n" " confidence-gated query-shape router picks name-exact BM25 when the query NAMES a symbol (identifier\n" " syntax, or every content word is a symbol name) else subtoken+body, and prints which/why in the\n" " header. It only routes with a query (the plain map is unaffected). --no-route restores the old behavior.\n" " A name-exact header also names its EVIDENCE: anchors: word(defining/file) per anchoring word, +N when N\n" " further definitions share that name, or word(syntax) when the word routed on camel/snake SHAPE and names\n" " nothing. Paths deeper than two segments print top/.../basename. Discount a one-use test helper yourself.\n" " Routing also carries the QUERY-SHAPE document demotion: when the task text parses as a stack trace,\n" " sanitizer report or compiler diagnostic, or as a pasted issue-template form, the DOCUMENT tier scores\n" " down (repo meta-prose - issue templates, CONTRIBUTING, changelogs - twice as hard) and route= names the\n" " shape, its evidence and both factors. Demotion, never exclusion, and the mention anchor still lifts a\n" " document the task NAMES. --no-route has no route= to disclose it in, so it does not demote either.\n" " Routing also puts CHANGE LOGS (CHANGELOG*, CHANGES*, HISTORY*, NEWS*, RELEASES*, release-notes/)\n" " and TRANSLATIONS of a default-language doc (README.zh-CN.md beside README.md, docs/ja/x.md beside\n" " docs/en/x.md) LAST in doc-mention surfacing, lifted to x0.35 of the usual height; their own match\n" " score is untouched. A task about changes (added, changed, removed, release, version...), about\n" " translation, or naming the file or its language tag keeps the usual lift; --no-route turns it off.\n" " route= is a CODE: name-exact(X) = the task names symbol X (the anchors: clause after it is the\n" " evidence); subtoken+body = the conceptual ranker over names and bodies; subtoken+body:broad = a\n" " one- or two-word query where plain rg may also win; subtoken+body:declined(word;N-carriers,M-defs)\n" " = a name hit refused because the word is a common name (N names carry it, M definitions); a shape\n" " demotion appends \"; doc tier demoted (...)\". On the rows, sc= is the enclosing scope and the full id\n" " is p::sc::n (p= of the row or its ) - the spelling --expand/--callers/--impact/--uses accept.\n" " --adaptive (with --for/--query) cut the result at the relevance cliff instead of at a fixed K\n" " (with --for/--query) cut the result at the relevance CLIFF — the largest relative score gap\n" " (Adaptive-k), floor 5, ceiling = the existing top-k; a sharp query returns few, a flat/broad one\n" " hits the ceiling. Prints [adaptive: kept K of N ...] in the header. Without it, output is unchanged.\n" " --no-mention-boost (with --for) stop lifting what the task text names: a path, module, Type.method or identifier\n" " (with --for) disable the query-mention anchor. By DEFAULT, a file or dotted module\n" " literally NAMED in the task text (a path, `pkg.module` — even inside a URL) has its\n" " symbols' SCORE lifted to within 5% of the top score; the header says what anchored. That\n" " is a score promise, not a rank one: on a flat/tied head the anchored hit can still land\n" " several ranks below #1. A SYMBOL named directly takes the same first slot: `Type.method`,\n" " `ns::fn`, `mod.fn`, or a verbatim identifier with identifier shape (snake_case, camelCase),\n" " call syntax (`name()`; off for the WHOLE task once it carries pasted code: a ``` fence, an\n" " indented line or a stack trace) or backticks, defined in at most 3 files (each file's best\n" " definition, a prototype beside its definition counting once; test/fixture files only if the\n" " task names them). Up to 8 named symbols are lifted per task: `Type.method` matches first,\n" " which may fill all 8, then identifier-resolved symbols (bare, call syntax, `ns::fn`, or a\n" " dotted name that matched no `Type.method`), at most 2 of them, in text order. The 2 count\n" " definitions, not names: a name defined in two files spends both, and a prototype beside its\n" " definition adds none. Named symbols past either cap are disclosed by mention_syms_capped=.\n" " Only the first 64 identifiers are read, and the rest are disclosed\n" " by mention_idents_capped=. Plain words never qualify (\"run the tests\" lifts no run()).\n" " Inert (byte-identical) when the text names nothing indexed. RIPWIRE_NO_MENTION=1\n" " disables it everywhere (incl. MCP `for`).\n" " --no-doc-mention (with --for) stop surfacing markdown docs that name the task's top symbols\n" " (with --for) disable doc-mention surfacing. By DEFAULT, a markdown doc that names one of\n" " the task's top-resolved symbols in a `backtick` (the same doc<->code edges --mentions=SYM\n" " reads) is lifted into the bundle, strictly below that symbol's own score — closing the\n" " \"the doc explains it but shares no words with the query\" gap. Inert (byte-identical)\n" " when no resolved symbol has a mentioning doc. RIPWIRE_NO_DOC_MENTION=1 disables it\n" " everywhere (incl. MCP `for`/`pack_task`).\n" " --lego=TYPE show one interface and every class implementing it — the contract, then the existing impls\n" " the interface->impls view for ONE named interface/base: its signature, method contract,\n" " and every implementor (own-language only). file:name disambiguates a same-named type.\n" " No contract for a language this surface cannot read soundly: methods=0 caveat=… says so.\n" " Implementors BELOW a direct one follow as via= depth= rows (transitive=N), 200 a page,\n" " walked through PROVEN extends clauses only (base_by=name: bound by spelling); --limit/--offset page them.\n" " --exemplar=TASK|KIND before you write: the repo's best existing example of this kind of code, to imitate\n" " before you write: the repo's best-in-class instance to IMITATE. Just pass a plain task —\n" " --exemplar=\"format byte sizes\" — and the KIND is inferred from the top match; or name a\n" " KIND directly (fn|method|class|struct|iface|var). Picks by ROLE — lowest cognitive cx\n" " under a hard ccx ceiling, then tested + highest fan-in; test-fixture paths de-prioritized —\n" " NOT text similarity (similar-snippet retrieval measurably hurts). A weak task match falls\n" " back to fn (low_confidence=1); an all-over-ceiling kind flags over_ccx_bar=1\n" " --recall=TASK search the DOCS, not the code — the most relevant plans, designs and notes, in full\n" " recall the most relevant DOCS — memory/plans/designs, full bodies (md, .ipynb/.html/.csv,\n" " plus Office/PDF via the optional markitdown bridge). This is the tool's LARGEST output:\n" " its header reports est_tokens + total=/shown=/capped=, where total= is the TRUE relevant\n" " count (score > 0) and shown= is what this run actually emitted. The header's \"of N document\n" " files\" denominator counts every file the index carries as a DOCUMENT — .md plus the\n" " docparse'd .ipynb/.html/.csv — so it is a SUPERSET of --doc-drift's docs=, which is an\n" " extension test (markdown only). Two populations, two names, deliberately. --top-k=N shapes HOW MANY\n" " docs are emitted (default 8, not the general --top-k default of 200). Recall defaults to an\n" " 8000-token body ceiling; --max-tokens=N overrides it and shapes to fit (disclosing each cut),\n" " while --token-budget=N gates the finished artifact (exit 3, nothing streamed).\n" " A doc WITH HEADINGS is served as whole SECTIONS in relevance order, each section's unit being\n" " its OWN PROSE — its heading line up to the next heading of any depth — so units tile instead\n" " of nesting and a parent no longer swallows the subsection that answered. Sections are\n" " admitted while they fit and the first that does not stops the walk, so WITHIN one document —\n" " with the served document SET held fixed — a bigger ceiling returns a SUPERSET rather than a\n" " repacked set. ACROSS documents that guarantee does not hold: raising the ceiling can pull in\n" " another document, and §C4 water-filling then re-divides the shared budget, which can shrink\n" " an already-served document's own slice; share_bytes= is exactly that re-division disclosed.\n" " The sections= note reports how many were served of how many matched, dropped_by_budget= what\n" " the ceiling cost, and lines= the ranges actually PRESENT in the emitted body rather than the\n" " ones merely selected.\n" " GENERATED documents rank LAST by default — a doc that declares itself generated in\n" " its first lines, or is BOTH >=5x the median doc's size AND mostly ```-fenced quoted\n" " output (a capture/API dump quotes every term, so BM25 hands it every query). Never\n" " dropped: it still wins when nothing else matches. Each one says [generated_demoted:\n" " marker|size+fences] on its own line and the header tallies generated_demoted=N\n" " --tree orient file by file: each file's top symbols, 80 files by default\n" " file-by-file orientation map (top symbols per file). Default window: the 80 files with the\n" " best-ranked symbols (shown=/capped=/total=/next_offset= disclose the cut, next= pastes the\n" " next page); --limit=N/--offset=M window it explicitly (--limit=100000 = every file)\n" " --html[=FILE] write the map as a self-contained interactive call graph you can open in a browser\n" " self-contained HTML force-directed call graph of the DEFAULT map (no CDN — redirect or\n" " write FILE). A navigation or report verb answers instead of the map, so --html beside one\n" " refuses rather than writing nothing. The page OPENS on the whole selected map, so the run is\n" " the picture; FILE#node/SYM/2 opens one symbol's neighbourhood and FILE#overview the module\n" " (community) list\n" " --color-by=MODE (with --html) colour the nodes by lang, community, complexity, churn or tested\n" " (with --html) node colour: lang (default) | community | cx | churn | tested — the page embeds all\n" " five and keeps a live selector; the flag only sets the initial mode\n" " --order=MODE choose emit order: stable, important-first (the default), or important-last\n" " emit order: stable (path/id order — provider KV-cache hits across re-runs) |\n" " important-first (rank order, the default; no auto-flip) | important-last\n" " (highest-rank content emitted last — recency bias for an LLM). Large default\n" " maps auto-flip to important-last past ~50% of a nominal 32K window\n" " (est_tokens>16000) unless MODE is explicitly given.\n" " --no-stable (--mcp/--listen only) turn off the stable ordering those two enable by default\n" " opt out of the stable ordering that --mcp/--listen enable by default. Read ONLY there:\n" " on the CLI it changes nothing and says so on stderr (the map is important-first\n" " unless you pass --order=stable)\n\n" " navigate / answer a question\n" " --around=SYM ego graph around SYM [--around-depth=N, default 1] [--around-fanout=K, default 32]\n" " (default depth 1 since 2026-09-05: depth 2 was 3x the whole default map on this repo; the\n" " root's depth= says which; --around-depth=2 restores the 2-hop neighbourhood)\n" " the root echoes all three (of= depth= fanout=), so the boundary of what could appear is readable\n" " and, when a bound actually CUT, which one: depth_truncated=\"1\" (a symbol one hop past depth= is\n" " absent) / fanout_cut=\"N\" (N distinct symbols the fanout cap dropped, absent from the whole answer,\n" " exact not a floor). Neither is emitted when its bound cut nothing, so absent = the bound did not\n" " bind and raising it would return nothing new\n" " --callers=SYM show SYM's direct callers (1-hop in-edges)\n" " who calls SYM (1-hop in-edges). file:name disambiguates a same-named symbol across files (like --around/--lego);\n" " Scope::name picks one scope's definition — the sym= spelling edit-check prints resolves everywhere.\n" " Each caller row's sites_at= lists its call sites as pasteable file:line tokens (16 at most: sites_total=)\n" " --callees=SYM what SYM calls (1-hop out-edges). file:name disambiguates like --callers\n" " --uses=SYM show every place SYM is used, not just called — reads, writes, imports, extends\n" " the statically resolvable use-sites of SYM (role=call|macro|read|write|import|extends|type, file:line); external=\"1\" if SYM has no in-corpus def.\n" " file:name or a \"::\" spelling narrows defs= AND the role=\"call\" sites (kept only where the call RESOLVES to a chosen def —\n" " --callers' own narrowing); read/write/import/extends carry no resolution and stay name-matched.\n" " narrowed_roles=/defs_of_name=/call_sites_of_name= (qualifier only) disclose what narrowed and\n" " the un-narrowed totals; a file: qualifier naming a file with no such def REFUSES, like --callers/--impact\n" " Owner.field (also Owner::field, or the id=) — a MEMBER VARIABLE's own use-sites, RESOLVED per site: this->f/self.f/bare f\n" " inside the owner pin; v.f pins through v's recorded type, else every owner is a candidate and the row carries amb=K\n" " (never a silent pin); write = assignment/compound/++ (address-of and by-reference passing are NOT claimed). A bare\n" " field name shared by several owners REFUSES with the Owner.field spellings; C/C++/Python fields only, others refuse\n" " --graph-query=EXPR query the call graph directly: pick a node set, filter it, walk it, combine the results\n" " composable node-set query over the call graph: sources name(\"X\")/all; filters kind|cx|fanin|file|layer;\n" " bounded closure callers|callees(SET[,depth]); joins and|or|not. e.g. and(callers(name(\"foo\"),2),kind(all,fn));\n" " file() regex example: file(\"src/.*\\\\.cpp\") (or in bash, use single quotes: file('src/.*\\.cpp'))\n" " layer(SET,NAME) keeps the architecture layer NAME (game|infra|render|math|audio|ai|test) — the SAME\n" " built-in directory-name taxonomy the map prints as layer= on a file node, so the two cannot disagree.\n" " It does NOT read a --arch=FILE rules file: --arch is a verb and outranks --graph-query, so the two never\n" " run together. An unknown layer word, or ANY layer() against a tree where no path names a layer, is\n" " REFUSED (exit 1) rather than answered count=\"0\" — 0 there would read as \"no such code\".\n" " a name(\"X\") literal matching NO indexed symbol refuses with a did-you-mean (a typo is not a count=0);\n" " a query whose names all resolve but that selects nothing still reports count=\"0\" — that IS a measurement\n" " (including a VALID layer with no members in a tree that does have layers).\n" " file() matches the ROOT-RELATIVE path that p= prints, so ^src/ anchors at the root and the directories\n" " the tree was cloned into never match. A file() regex that cannot be screened, compiled or finished\n" " (the engine abandons the match) is REFUSED at exit 1, never answered with a count.\n" " Ranked result set is capped at --top-k (default 200); --limit overrides that cap (raise or lower it),\n" " --offset pages past it — see --limit=N --offset=M above\n" " --external-surface list the names this repo uses but never defines — its stdlib and third-party surface\n" " names referenced but never defined in-corpus (the stdlib/third-party surface), by ref count;\n" " each row's lang= is the REFERENCING file's language — a name called from several languages\n" " (e.g. printf: C stdio call vs Bash builtin) gets one row PER language, not a merged count.\n" " Default window: 100 rows (shown=/capped=/next_offset=, next= pastes the next page; --limit=N\n" " raises it) and the sh BUILTINS (echo printf cd exit test …) are dropped — builtins_excluded=\n" " counts them; --include-builtins keeps them\n" " --path=SRC,DST shortest directed call-path SRC -> DST\n" " --connect=A,B,C show how 2..16 symbols relate: the smallest subgraph that joins them\n" " minimal connecting subgraph over 2..16 symbols: terminals + fewest joining intermediaries +\n" " call edges in TRUE direction (finds the shared-caller join a directed --path can't) [--connect-radius=N (1..12, default 6)]\n" " --impact=SYM show everything that reaches SYM — the transitive blast radius before a change\n" " — the indexed symbols that reach SYM (a floor, see counts_floor). file:name disambiguates like --callers\n" " rows run nearest first: d= is the hop depth (1 = a direct caller; printed where it changes), PageRank\n" " order within a depth; by_depth=k:n on the root counts reaches= per depth, so a cut drops the deepest first\n" " importers= is a SECOND, weaker reach beside it: the files that directly include/import a file defining SYM,\n" " emitted as rows (format=columnar carries the count only; --limit sizes it). NEVER added to reaches= —\n" " files and symbols are different units, and an importer may use a different symbol from that file, or none at all.\n" " lazy=\"1\": every one of that importer's edges is written inside a closure — a TS/JS require()/import()\n" " inside a function body, a Ruby constant receiver or argument inside a method/lambda/block, a Ruby\n" " autoload or rescue class — not at load time: still a real dependency, weaker than a top-level one\n" " a CUT symbol window opens with : the WHOLE reach set by file (rf syms= d=,\n" " 20 by default, --limit sizes it); an umbrella header's importers are a count (importers_umbrella=\"1\")\n" " counts_floor=\"1\" every count on the graph verbs is a FLOOR, never a total; a 0 means none found\n" " on --callers/--callees/--uses/--impact/--edit-check every count is a FLOOR, never a total: the\n" " call graph is extracted from source text by name, so dynamic dispatch\n" " and declarations that parse without a call expression (C++ most-vexing-parse) contribute no edge;\n" " a call through a function pointer/callback is an edge only when ONE function is bound to that\n" " variable in scope (reassigned/table-indexed/lambda-bound/escaped — address-taken or\n" " reference-bound — pointers stay edge-less, C-family);\n" " a function USED AS A VALUE (stored in a table/field, passed as an argument) is a row and\n" " value_refs=N beside the counts, never an edge and never in a count (not a proven call);\n" " a macro-generated call site contributes a role=\"macro\" edge when its name uniquely names an\n" " indexed function-like #define (t=\"macro\"); a shared name stays a plain call, an unindexed\n" " macro's site is no edge. Read a 0 as \"none found\", never as \"none exists\". Those five\n" " verbs also count DISTINCT (caller,callee) pairs, while --uses counts call SITES — see each verb's own legend\n" " pr_iters=\"N\" how many PageRank iterations produced this ordering\n" " on every PageRank-ordered document (the map, and the tree, seams, communities, zoom,\n" " impact, graph-query and exercises verbs, plus their MCP twins): how many power iterations produced that\n" " ordering. The iteration stops when the L1 residual between successive rank vectors falls below\n" " tolerance, or at a fixed iteration ceiling, whichever comes first. pr_converged=\"0\" is emitted\n" " ONLY on that second exit and means the ranking is a rank vector that stopped SHORT of tolerance,\n" " not the fixed point it approximates. ABSENCE MEANS IT CONVERGED (there is no pr_converged=\"1\":\n" " the converged path is the normal one and must cost zero bytes), and absence of pr_iters= itself\n" " means the document was not ordered by a power iteration at all (a lexical query score, or a hub\n" " or authority HITS vector), never that the count is unknown\n" " data_sections_cut=\"N\" how many data Sections the default map swapped out for code rows, with next= to page them\n" " on the default map (XML and --json, --max-tokens, MCP analyze on a clean tree, MCP\n" " rank_by=pagerank): the map's top-K is picked CODE-FIRST — while it keeps a data Section\n" " (a markdown heading, a YAML/JSON key, a schema column) and leaves a code row out, the\n" " lowest-ranked kept Section is swapped for the highest-ranked excluded code row, so a\n" " Section is shown only when every code row is. N counts the swaps; next= pages the Sections\n" " past the M still shown (--graph-query='kind(all,sec)' --offset=M --limit=K), the N swapped\n" " ones first, K rows a page. The rank (k=) is untouched. Absent when nothing was swapped\n" " --verify=\"CLAIM\" check a claim about the code in one call: confirmed, refuted, or not-established\n" " VERIFY A CLAIM about the code in ONE call: a CLOSED claim language in, a three-valued verdict out\n" " (confirmed / refuted / not-established) with the evidence rows inline — the collapse of the manual\n" " verification grep-chain. Shapes: calls(A,B) does A transitively call B; uses(SYM) / unused(SYM) is\n" " SYM referenced anywhere / nowhere; contains(FILE, \"LIT\") do FILE's indexed bytes contain the\n" " literal; defines(FILE, SYM) does FILE define SYM; reaches(SYM, \"FILE\"|LAYER) does code in that\n" " file/layer transitively call SYM (LAYER unquoted: game|infra|render|math|audio|ai|test).\n" " refuted appears ONLY with complete evidence: a clean literal-scan absence carries complete=, and an\n" " unused claim is refuted by printed witness sites. A graph or reference ZERO can never refute — it\n" " yields not-established with limit= naming the floor (call-graph-floor, reference-floor,\n" " collection-ceiling, scan-degraded, extraction-floor); see counts_floor above for why. An unknown\n" " shape refuses loudly with the whole vocabulary; SYM takes the shared selector grammar (name,\n" " file:name, Scope::name, canonical id), FILE is a path substring\n" " --mentions=SYM find the markdown docs that name SYM in backticks — the doc-to-code link\n" " markdown docs (plans/designs) that name SYM in a `backtick` (doc↔code). An @FILE:LINE\n" " seed rebinds to the innermost enclosing definition and answers, disclosing sym=\n" " (its name). unbackticked_docs=N (absent at 0): files naming SYM only outside such a span, a ceiling.\n" " the pre-PR family — plumbing (--affected) to mid-task report (--situ) to gate (--test-gate):\n" " --affected=F1,F2|SYM name the test files that reach these changed files, or this changed symbol\n" " test files that transitively reach the changed files -- or the changed SYMBOL. Each item may be\n" " `path`, `./path`, `path:LINE` / `path:N-M` (paste a --hotspots/--clones/--grep/--lint/\n" " --quality-delta row's locator verbatim; the trailing line locator is stripped, same for\n" " --situ/--test-gate), or a symbol: `NAME`, `file:NAME`, `path::scope::name`.\n" " FILE-FIRST: an item matching any indexed path is a PATH pattern (unchanged semantics --\n" " `--affected=widget` stays the ./src/widget.cpp pattern); only an item matching NO indexed\n" " path is offered to the symbol resolver, and `file:NAME` reaches the symbol reading\n" " explicitly. seeded_by=\"file|symbol|mixed\" + seeds=N report which reading fired and how\n" " many defs it seeded. An item matching NEITHER refuses (exit 1) naming both readings.\n" " seed_test_files=N of the matched files are TEST files: a test cannot reach a change it is part\n" " of, so its own symbols are not seeds of the caller walk, and its row carries seed_kind=\"test\"\n" " -- it is listed because the argument matched it (it changed, run it), not because it reaches.\n" " script_gates_unmodelled= counts the script runners under test/, recursively (a path count; not\n" " every one invokes the binary) that this call's graph walk cannot see either way (script-to-binary is not\n" " a call edge) — a corpus-wide fact, not scoped to the changed set given\n" " --exercises=TESTFILE show what a test file actually covers: the non-test symbols it reaches\n" " the INVERSE of --affected: the non-test symbols this test file transitively calls into --\n" " what it actually covers. The first question when a test fails and you have its name and\n" " nothing else. Ranked by PageRank, capped at 40 rows (raise with --limit; --offset pages).\n" " A NON-TEST path REFUSES rather than answering generically: this verb IS the test/non-test\n" " partition (it subtracts test code from the answer), which means nothing for a non-test\n" " file -- for \"what does this call\", use --callees=SYM or --graph-query callees(...)\n" " A shell harness carries harness=script: subprocess coverage is unmodelled, so reaches=0\n" " there is a stated limit, not a measurement (the inverse of script_gates_unmodelled).\n" " --situ[=F1,F2] situational awareness for a change: blast radius + tests + co-change (default = git diff)\n" " Its co-change section is the check for Fowler's SHOTGUN SURGERY: the files this change\n" " usually lands in but did not. Backtested on two histories (EVALS.md): a named partner is\n" " edited within the next 3 commits in 56% / 32% of alarms, against a 1-2% chance baseline\n" " --handoff brief the next session — verified disk truth first, labelled guesses second\n" " continuation packet for the NEXT session: disk truth (branch/sha, changed files+symbols,\n" " blast radius, tests-to-run) + labeled suggestions (co-change partners, committed notes,\n" " plan/design doc pointers via a branch+commit-subject query). Empty diff is fine — the packet still\n" " carries branch/sha + heuristics. Composes with --token-budget=N (drops heuristic rows tail-first,\n" " disclosed as withheld= in the header; verified rows are never dropped). Single-root only.\n" " --test-gate[=F1,F2] before a PR: name the tests to run and the untested blast radius; exit 4 if either is non-empty\n" " agent self-check before a PR (pair with --quality-delta): names the tests to run + the UNTESTED blast radius;\n" " exit 4 if either obligation is non-empty (run the tests, then rely on green). (default = git diff)\n" " run= on a test row --affected/--situ/--test-gate/--exercises/--pr-context/--pack-task name harness FILES, not commands. A row carries\n" " run=\"\" when a runner is DERIVABLE from real evidence: a test-dir .sh/.py whose basename stem\n" " matches the harness's, or whose TEXT names the harness file; for TS/JS, the nearest package.json\n" " above the test file naming vitest, jest, or node's own test runner (scripts.test, or a\n" " vitest/jest dependency -- never guessed from the .ts/.js extension alone). Spelled RELATIVE to\n" " the root= the document declares, so it pastes into a shell run from there, and the document\n" " does not change with where the tree is checked out (a MULTI-ROOT run declares no single root,\n" " so it stays absolute). NO run= means NOT DERIVABLE -- never a guessed suite command\n" " --grep=STR | --regex=PAT search for a literal or a regex; every hit comes back with its enclosing symbol\n" " literal / regex search + enclosing symbol + the matched line. SPAN-TIERED by default (see\n" " --grep-in below): the scan itself is exhaustive, the ANSWER serves one tier and discloses\n" " what it held back. --grep-in=any is the exhaustive VIEW -- every hit, no tiering.\n" " For task-ranked retrieval use --for=TASK (ranks by PageRank + task relevance).\n" " --regex is LINE-ORIENTED, like grep/rg: each line is its own search range, so ^ and $\n" " are LINE anchors and no match may span a newline (a trailing CR sits outside the range).\n" " --grep-context=N | --grep-before=N / --grep-after=N ripgrep-style N lines of source around each hit\n" " --and=STR (repeatable) modifies --grep=STR: keep only hits where STR is ALSO present (literal-only, no --regex)\n" " --not=STR (repeatable) modifies --grep=STR: drop hits where STR IS present (literal-only, no --regex)\n" " --grep-scope=line|file modifies --and=/--not=: must the other term hit the SAME line (default) or anywhere in the file\n" " modifies --and=/--not=: line (default) requires the SAME matched line; file requires\n" " anywhere in the same file. Second occurrence of --grep=/--regex= itself REFUSES\n" " (naming --and= as the AND spelling) rather than silently overwriting the pattern.\n" " --grep-in=code|any SPAN TIERS: which tree-sitter span a hit must sit in — code (default) or any (exhaustive)\n" " SPAN TIERS: which tree-sitter span a hit must sit in to print. code (default) serves the\n" " CODE tier when any hit is code, and otherwise comment AND string TOGETHER (tier=\n" " \"comment+string\"), disclosing what it held back (suppressed_comment=/suppressed_string=);\n" " a pattern living only in prose is still answered, never silently emptied. When every\n" " code hit is a use of the literal (a test/doc file, or a shell/YAML/TOML/JSON file) and\n" " source code (not one of those) holds it as a string, the string tier is served WITH\n" " code (tier=\"code+string\"; comments stay held back) — and so it is, while source and test\n" " files (not docs or scripts) hold under 8 of its code hits (unclassified ones count),\n" " for a literal no identifier can spell (- / . or a space), a SCREAMING_CASE one, or one with\n" " under 8 code hits. Under that same count either lift ranks its rows: files named by it, then\n" " code, then string-only, 10 by default, string_hits= kept. any turns\n" " tiering off entirely -- the exhaustive view. Hit files are parsed on\n" " demand under a fixed budget; tier_budget= says so when it stops, and hits it never\n" " classified are emitted, never suppressed.\n" " --handles (with --grep/--regex) add a stable edit handle h= to each enclosing-symbol row\n" " (with --grep/--regex) add h= to each unique editable enclosing-symbol row: a stable\n" " identity plus the file-content hash pinned when grep ran. Ambiguous or document-only\n" " rows get no handle; a later edit must refuse after any file change rather than\n" " retarget stale coordinates.\n" " --match=QUERY tree-sitter structural (shape) query\n" " --pattern=PAT structural search written in CODE, not in node kinds: --pattern='foo($X, ...)'\n" " structural search written in CODE, not in node kinds: --pattern='foo($X, ...)'. $NAME binds one\n" " node (repeat it and both sites must match structurally); $_ binds nothing; ... (or $$$) is an\n" " ellipsis over siblings, matched by ONE first-match-wins probe under a hard cap -- both facts on\n" " the element. Comments are transparent, everything else is kind- and text-exact ($A + $B does not\n" " match a - b). Served: c cpp objc java csharp javascript typescript python go rust swift; ruby,\n" " bash and the data tiers are named in unsupported= instead of answered. A pattern no served\n" " grammar resolves, or that collapses to a bare token, is REFUSED -- never reported as hits=0.\n" " A qualified call (ns::foo) is no hit for a bare foo; unmatched_qualified=N counts them.\n" " --query=TERMS raw BM25 ranking, for debugging the ranker — reach for --for instead\n" " raw BM25 ranking (debug); use --for\n" "\n" " zoom the detail ladder\n" " --detail=N (with --for) full bodies for the top N symbols, signatures for the rest\n" " (with --for) importance-weighted detail: FULL bodies for the top-N ranked symbols +\n" " signatures for the rest, in ONE call — spend body tokens only on the head the rank\n" " identifies. Composes with --max-tokens (bounds the BODIES ONLY, never the bundle —\n" " see --max-tokens: past its ceiling the root says over_ceiling=\"1\" rather than cut\n" " the rows that answered) and --adaptive. 0 = off.\n" " --pack-signatures print declaration skeletons with the bodies elided — far cheaper than full bodies\n" " body-elided decl skeletons — ~73-91% fewer element bytes than the same symbols'\n" " full --expand bodies (about 82% at the top-50 sigs payload cap — the sigs\n" " payload is top-50 whatever --top-k is set to, and --top-k's own default is 200),\n" " measured at top-10/50/100 on this repo with the corpus-root prefix subtracted\n" " from both sides: that prefix repeats inside every element, is charged in both\n" " forms, and is not what this elides — count it and the figure becomes a function\n" " of how deep your checkout sits. test/showcasecapturecheck.sh (C) re-derives\n" " this range from the SAME repo every run and fails on drift. The share RISES with the result size.\n" " Like the --format=columnar sibling, a small result can invert it — a signature\n" " plus its doc comment can be bigger than a short body.\n" " --outline=A,B,... control-flow skeletons of A,B,... (same selector grammar as --expand, minus the range)\n" " --expand=A,B,... print these symbols' full bodies, plus the signatures of what they call\n" " full bodies of A,B,... Selector grammar per item (the tail after the LAST ':' decides;\n" " a tail STARTING WITH A DIGIT is a range, anything else is a name):\n" " NAME every def of that name | FILE:NAME that file's def\n" " NAME:START-END body slice | FILE:NAME:START-END selector + slice\n" " FILE:LINE:NAME paste a row's p=\"path:line\" straight from --callers/--lint/--grep\n" " (NOT --hotspots: its p= is a BARE path — build FILE:LINE:NAME from its\n" " own p=/top_l=/top= instead, since top= is just the worst function's name)\n" " path::scope::name the id composed from a row's p= sc= n= (the map, --for, --pack-task)\n" " Scope::name the sym= spelling edit-check and grep's in= rows print — matches the name\n" " under any scope whose ::-boundary SUFFIX is Scope (Box::lid, deep::Box::lid);\n" " a wrong scope refuses, it never falls back to the bare-name union\n" " Scope.name|Scope#name the same match, dotted (Python/JS/Java) or Ruby-style; tried only\n" " when no other spelling matched\n" " (START-END is 1-based within the def's OWN body — lines=\"lo-hi/total\" marks the slice\n" " partial; out-of-range clamps. FILE matches any path substring, like --callers/--lego.)\n" " EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map\n" " defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is\n" " pure overhead in front of the one body it exists to summarize. Disclosed on the root as\n" " topk_default=\"0\" (self-describing: the change is visible without reading source). The same\n" " default applies when --expand names SEVERAL definitions (an ambiguous bare name, or several\n" " tokens) and EVERY one is served: the bodies carry p= l= t= for each, the root adds map_next=\n" " (the shell call that prints the map you did not get — orient once per session, then look up\n" " lean). If a byte budget cut some, the root adds unserved_total=N (the true count) and\n" " unserved_next=\"--expand=FILE:LINE:NAME,…\" (the call serving them; past 16 it serves the first\n" " 16 and unserved_listed=\"16\" says so — a larger --pack-budget-bytes serves the rest) — never the\n" " generic map. map_next= and unserved_next= are quoted for the shell; map_next= names every root\n" " plus the crawl-scope flags (--exclude, --max-file-size, --no-ignore, --ignore-tests). --outline\n" " follows the same rule for its one token (several --outline tokens keep the ordinary map). An\n" " EXPLICIT --top-k=N (0 included) always overrides this default.\n" " Each body also carries sibs=\"a,b,...\" sibs_total=\"N\" [sibs_capped=\"1\"] (the file's OTHER\n" " symbols, names only, capped at 40) and inc=\"x.h,...\" inc_total=\"N\" [inc_capped=\"1\"] (the\n" " file's own #include/import targets, capped at 24) — both absent when the count is 0 (a\n" " documented zero, not a degrade), so a body no longer needs a second --outline call just to\n" " learn what else lives in its file.\n" " CHEAPEST-COMPLETE-ANSWER SERVING (no explicit --top-k, no range slice): the verb ALSO measures\n" " the (possibly map-less) bundle against the requested symbols' whole FILE(s) and emits the\n" " SMALLER, disclosed on the root as mode=\"bundle|whole-file\" reason=\"the two byte counts\" — on a\n" " small file the old bundle was 5.65x the file itself; on a big file the bundle saves ~26x. The\n" " whole-file form is with the file CDATA-wrapped (redacted as usual)\n" " and every requested symbol's line anchor kept. An EXPLICIT --top-k=N (including 0) opts out of\n" " BOTH the exact-name default and mode= auto-selection and keeps the classic undecorated shape; a\n" " SYM:START-END slice opts out of mode= auto-selection only (serving the whole file would invert\n" " an explicit narrowing) but still gets the exact-name top-k=0 default when it applies.\n" " When the bundle wins AND no explicit --top-k was given, the requested bodies are served BEFORE\n" " the ranked map (not buried after it), and the map's escape hatch also rides the root as\n" " note=\"...--top-k=0 for the payload alone...\" (not stderr-only): a caller reading only stdout sees\n" " both the answer and how to drop the map on the next call. Absent whenever the bundle carries\n" " no map at all (top-k=0, exact-name default) or loses to whole-file, and whenever --top-k was\n" " explicit — the classic shape above carries no note= either.\n" " --compress strip comments and collapse blank runs from every body this run serves\n" " strip comments + collapse blank runs from SERVED BODIES (~20-35% token cut): --expand/\n" " --outline, --for's auto/anchor and --detail=N bodies, --pack-task, --from-trace and\n" " --exemplar. Disclosed per bundle as compress=\"1\" on the element; without the\n" " flag, output is byte-identical. String literals survive; the ranked SET never changes.\n" " --pack-top-n=N pack the N top symbols' bodies [--pack-budget-bytes=B]\n" " A budget cut is stated: truncated=1 lines=1-K/T on the cut file, src_cut shown= total= capped=1.\n" " --no-redact emit source and doc text verbatim, redacting nothing\n" " emit source/doc text VERBATIM, redacting nothing. Modifies the BODY-serving verbs\n" " (--expand, --for, --pack-task, --recall, --slice, --connect, --from-trace, --batch,\n" " --mcp); the default map carries no bodies (identifiers and signatures are never\n" " redacted), so bare on the map it is refused, naming one of them\n" " REDACTED by default (high-confidence credential SHAPES only, precision over recall):\n" " emitted symbol BODIES, doc/markdown bodies and doc-comment excerpts, the --outline\n" " skeleton, and SIGNATURES — a default argument carries whatever literal was written.\n" " NOT redacted, and a deliberate residual: --grep/--regex/--match hit lines and their\n" " --grep-context neighbours, and --note-add/--notes text. --grep is the exception on\n" " purpose — auditing a repo FOR secrets needs the hit you searched for shown verbatim.\n\n" " assess quality / structure\n" " --metrics annotate every symbol with fan-in, fan-out and complexity\n" " annotate fan-in/out + complexity (descriptive; coupling is the validated signal, complexity is a size-correlated one).\n" " also surfaces amp= (--metrics/--for/--exemplar): amp = |direct callers| (symbol-level, the\n" " in-edge CSR) + |co-change partners of the symbol's FILE| (file-level, mined from git history) —\n" " a deliberate GRANULARITY MIX, not a graph-only count; degrades to callers-only (still valid) when\n" " git/history is unavailable. NOT the same quantity as --impact's reaches=: reaches= is the\n" " TRANSITIVE blast radius over the call graph alone (everything that reaches SYM, any hop count);\n" " amp= is DIRECT callers plus a historical co-edit signal the call graph cannot see at all — the\n" " two numbers on the same symbol routinely differ several-fold (one seen case: 4.6x apart) because\n" " they measure different things, not because one is wrong.\n" " ppalt=N (C-family/C#): the body contains N alternative preprocessor branches (#else/#elif) —\n" " code that never coexists at compile time. cx/ccx/nest/loc/locals are summed over ALL branches\n" " (deterministic, but an over-count vs any single build; ~2x seen on a real SSE/scalar pair), so\n" " discount them accordingly. ripwire never guesses which branch your build compiles — it discloses\n" " the count instead. A bare #if with no #else adds no alternative and no ppalt=. Absent when 0.\n" " ev=N essential complexity (McCabe: 1=fully structured, 2+=jumps block extract-method\n" " cleanly — the jump makes it a rewrite, not a mechanical lift); ev_why=tag:count names\n" " which jumps raised it (guard-return, loop-escape, ...). A FLOOR (ev_floor=1): noreturn\n" " calls/macro-hidden exits go unseen; absent on a cx row means exactly 1, and Rust ?/\n" " yield/await/defer are not counted, so Bash carries no ev at all.\n" " humps=/deep=/locals= are the nesting PROFILE nest= alone cannot give: nest= is a max,\n" " so one deep line and a body that is deep throughout report the same number. humps=\n" " counts regions reaching the nesting bar, deep= the lines inside them (a floor), and\n" " locals= the local-variable-declaration count (a floor, C/C++ only). Read the three\n" " together — a tangle (many humps, few deep lines each) and a long blocked-sequential\n" " body (one hump, many deep lines) have the same nest= but opposite refactors. Absent\n" " exactly when nest is below the bar (not-deep), never a hidden 0.\n" " --deps build the file-to-file dependency graph — god-files and cycles\n" " file->file dependency graph (god-files, cycles — validated); its nccd (Lakos) is a\n" " design heuristic, not independently outcome-validated. instab= (Martin's I=Ce/(Ca+Ce))\n" " counts project includes ONLY -- system/third-party headers are excluded from Ce,\n" " matching stabledeps' gap= so gap == consumer's instab - provider's instab always.\n" " 's ccd/acd/nccd/shape are computed over dep_files= (files whose language\n" " has #include/import syntax) not files= (the raw corpus, incl. md/json/toml/yaml,\n" " which can't participate in the graph) -- --arch's propagation_cost uses the same N.\n" " names that language set, which is what makes a dep_files=/ccd/\n" " acd/nccd number comparable across builds: sh, rb, lua and ex joined it at parser\n" " version 81 and every one of those numbers moved on a corpus holding them.\n" " STRUCTURE vs USE (parser version 83): a LAZY edge -- every directive of the pair\n" " written inside a closure (Ruby method/lambda/block, TS/JS function body), a Ruby\n" " autoload or rescue class -- is a use, not a load-time dependency: it is in --impact's importer tier\n" " (lazy=1) and in the row's inc t= list, NOT in afferent/instab/transitive/godfiles/\n" " stabledeps/cycles/ccd/acd/nccd/shape. counts the pairs left out,\n" " a row's lazy_edges= its own; both absent when 0. A TS/JS import through a tsconfig paths\n" " alias or baseUrl, a workspace package or a package.json imports entry draws its edge by\n" " tsc's and Node's rules from the configs inside the crawl root, and one that names this tree\n" " yet draws no edge is counted in the root's imports_unresolved=N graph_partial=1. Configs that\n" " were not read (above the crawl root up to the git top-level, unparseable, or an extends or\n" " references target not in the tree) are counted in tsconfig_unread=N: numbers above are then\n" " over resolved edges only. The nearest tsconfig's aliases apply to every TS/JS file below it,\n" " whether or not its include lists the file (a references project owns its own)\n" " --hotspots rank files by complexity times recent git churn — where maintenance hurts\n" " complexity x recent git churn (maintenance pain); each row's top= is the worst function's\n" " BARE name, top_ccx= its cognitive complexity, top_l= its source line (build an --expand\n" " selector from p=/top_l=/top=, not from top= alone — it no longer carries a :line suffix)\n" " A function whose extent failed a containment check is LEFT OUT of ccx=/score=/top= and\n" " counted: extent_suspect_syms= on its row, unranked_extent_suspect= for a file with none left\n" " --clones token-normalized duplicate bodies\n" " --biggest-first order functions by Halstead volume, token entropy and length, largest first\n" " per-function lens, LARGEST Halstead volume/token-count/length first (a size proxy, not a\n" " readability order — see WITHDRAWN below): vol= Halstead volume V (N*log2(eta)),\n" " ent= Shannon token entropy E, lines= L, posnett= sigmoid(8.87 - 0.033V + 0.40L - 1.5E)\n" " (Posnett/Hindle/Devanbu, MSR 2011). APPROXIMATION, disclosed: ONE token-class table serves\n" " every language (keywords + punctuation = operators, identifiers + literals = operands),\n" " with no per-grammar refinement, so V is cross-language and not a per-grammar Halstead count.\n" " The formula was fitted on snippets of 20 lines or fewer, so it is a RANKING lens, not a\n" " grade: read the ORDER of the rows, not the number on any one of them. Pages with limit=N\n" " (offset=M); default 40 rows. Declarations with no body are not measured.\n" " MEASURED: on ripwire's own history at the pinned v0.6.2 tag (412 function pairs mined\n" " from 80 refactor/simplify/cleanup commits), the order between two versions of a\n" " function tracked the sign of its token-count change in 96.0% of pairs (388/404 whose\n" " count changed). Read a move as more or fewer tokens, not as more or less readable;\n" " full derivation and history in docs/EVALS.md §8. Do not read a low posnett= as proof\n" " a function needs work. WITHDRAWN: the ordering claim (that a lower posnett= predicts a\n" " later fix) is withdrawn -- stratified into narrow token-count bands, the later-fix\n" " association disappears in 8 of 10 deciles (CIs include 1), so the order is a size proxy,\n" " not an independent readability signal; derivation in docs/EVALS.md §8.\n" " --nonlocal-state per function, the non-local mutable state it can reach, most writes first\n" " per function, the NON-LOCAL MUTABLE STATE it can reach, MOST WRITES FIRST: writes= reads= are the\n" " distinct cells this function OR its transitive callees write / read; direct_writes= direct_reads=\n" " are the subsets in its own body. A cell is a file/namespace-scope variable, a function-local static,\n" " or a Python module global; a const/constexpr/consteval declaration is not a cell. Each cell child\n" " names its declaration, its direction (dir=r|w|rw) and either the use site in this body (at=) or the\n" " callee it came through (via=).\n" " Lineage: Fowler's Global Data / Mutable Data smells (2018) name the hazard and ship no metric;\n" " Marinescu's ATFD (ICSM 2004) is the closest number but is one-hop, per-class, Java, and direction-blind;\n" " QMOOD DAM and MOOD AHF/MHF count DECLARED VISIBILITY and so score a class with private fields and\n" " leaked mutable internals as perfectly encapsulated; the only published measurement of externally\n" " reachable state (Potanin/Noble/Biddle 2004) is DYNAMIC, Java-only, and its tool is unmaintained.\n" " UNSOUND BY CONSTRUCTION -- it cannot see indirect calls, pointer aliasing, macro-named cells or\n" " reflection-like dispatch, and a local SHADOWING a cell name can be charged to the cell -- so every\n" " count is a FLOOR (counts_floor=\"1\") and the blind spots are listed in the report's own legend.\n" " COVERS C++, ObjC and Python -- the languages whose read/write USE SITES the index carries. Every\n" " other indexed language is named on the root as unanalyzed_langs= and contributes no cells and no\n" " rows: that absence is NOT a measured zero. Pages with limit=N (offset=M); default 40 rows.\n" " --ensemble join four independent evidence families per function, ranked by how many agree\n" " the FAMILY JOIN: per function, which of FOUR orthogonal evidence families fire, ranked by the COUNT of distinct families\n" " --quality-panel[=PRESET] the whole quality panel in ONE ranked report: which of six evidence families fire\n" " THE SINGLE COMMAND: the whole quality panel in ONE ranked report. Per function, which of SIX evidence\n" " families fire -- structural (shape), lexical (identifier text), confusion (syntactic construct),\n" " historical (git churn), colocation (what you must read from outside this file), state (this function's\n" " OWN BODY touching non-local mutable state) -- ranked by the COUNT of distinct families, NEVER by a\n" " weighted composite, each row carrying its own evidence. PRESET selects and cuts, never weights:\n" " strict (the four families measured steady enough to gate on, 2 must agree) | default (all six, 2; the\n" " bare form) | lenient (all six, 1 -- a reading order, not a verdict). historical and colocation are out\n" " of strict: each is a fixed-size worst-40 cut over a ranking whose population moves, so both re-shuffle\n" " on code that did not change (docs/EVALS.md section 9.9). A family that could not be measured here is\n" " UNAVAILABLE, never 'did not fire', and of= drops with it. A lens: exit 0. Pages limit=N (offset=M).\n" " WHY NO COMPOSITE, in full (the emitted legend is deliberately terse and points here): averaging\n" " correlated metrics re-weights one signal and calls it six, and a single quotable number is wrong the\n" " moment it is quoted -- fam= is ORDINAL, and every row carries its own evidence so a reader can see WHY\n" " without a second command. The families are partitioned by KIND of evidence so that corroboration means\n" " the lenses failed DIFFERENTLY, not that one weakness echoed six times: the first four are the ensemble\n" " join, called through its own entry point and unchanged; colocation and state passed the same\n" " orthogonality test on the same corpora before being enabled. Every threshold is REUSED from the lens it\n" " came from, none is new: four absolute structural bars (cognitive complexity, lines, nesting, params),\n" " and three rankings with no defensible absolute cut, each firing for the worst decile of its OWN\n" " ranking (at least one row, at most that lens's default window of 40) -- an ordinal cut is RELATIVE,\n" " 'worst in THIS corpus', never 'bad in absolute terms'. The state family fires on the presence of a\n" " direct access site and deliberately uses the OWN-BODY half of the lens, not the callee closure: the\n" " panel's unit is one function's own comprehensibility, and the closure is a fact about its callees.\n" " UNAVAILABLE is never silent: an empty unavailable= means every family was measured, an empty ranking\n" " or empty language coverage counts as NOT measured, and the coverage denominators behind each verdict\n" " are published so it can be checked instead of trusted. The join=deep+untested annotation puts two\n" " facts already in the report side by side (sustained depth, no reaching test) because that pair is\n" " where a refactor is most wanted and least safe; counting it would be one family wearing a second hat.\n" " --context-ratio per symbol, how much you must know that is NOT in front of you\n" " the LOCAL-REASONING lens: to understand this symbol, how much must you know that is NOT in front\n" " of you? Per symbol (and rolled up per file) the distinct in-corpus definitions and files its\n" " reference sites resolve to, and the share of them defined OUTSIDE its own file — as an edge\n" " count (ent_ratio=) and, weighted by the tokens a reader must actually read, as read_ratio=.\n" " ATTRIBUTION: the fraction itself is published — it is Beck and Diehl's per-class congruence\n" " (FSE 2011) flipped, with Martin's instability Ce/(Ca+Ce) as its crude ancestor. What is\n" " refined here is the READER WEIGHTING and the use of EVERY reference role (call, read, write,\n" " import, base class, member type), not calls alone. Resolution is NAME-BASED and language-gated,\n" " the same heuristic level the uses verb works at; a name with several definitions contributes\n" " each of them up to defs_per_name_cap= and amb= counts it. Names with no in-corpus definition\n" " land in ext=, which locals and parameters DOMINATE, so ext= is not a dependency count and is\n" " excluded from both ratios. ents=/files= are FLOORS. Pages with limit=N (offset=M); default 40\n" " symbol rows and 40 file rows. An ORDERING, never a grade and never a threshold.\n" " --naming-calibration score the naming-* lint rules against this repo's OWN rename history\n" " score the naming-* lint rules against this repo's OWN rename history: one git log pass mines\n" " old->new identifier substitutions, joins each to the symbol it became at HEAD, and scores\n" " BOTH spellings with the same predicates --lint runs. old=fires on the abandoned spelling,\n" " new=fires on the chosen one, proxy=old/(old+new), where 0.50 is exactly chance. A NOISY\n" " PROXY, stated as one -- rebrands, moves and API changes all look like renames -- so read\n" " pairs= (the sample size) first; the group rules report scope=group-rule, not a fake 0/0.\n" " Exit 0 always: the per-rule floor lives in test/namingcalibrationcheck.sh\n" " --naming-consistency normalize to the corpus's own case convention, voted per language and kind\n" " TIER A convention normalization (section 9.2): the corpus's OWN case-convention vote per\n" " (language, kind) group among multi-token eligible names -- a single-token name, or one split\n" " only on digit boundaries, carries no case signal and is silently excluded. A group DECIDES\n" " only when its leading style (camel/pascal/snake/screaming) clears a 20-name sample floor AND\n" " a 90% agreement floor; short of either it reports style=UNAVAILABLE with why= naming which\n" " bar it missed, never a guessed winner. Every off-convention name in a DECIDED group\n" " (including mixed -- naming-case's own finding, a separator AND a transition in one name,\n" " which never wins a vote) gets propose=: its OWN subtokens mechanically recombined into the\n" " dominant style -- no dictionary, no synonym judgment, which is what keeps this derivable\n" " from the corpus rather than invented. propose= is a SUGGESTION, never a safe-to-blind-apply\n" " rename -- an actual rename needs --uses to prove the complete reference set first. Exit 0\n" " always: a lens, not a gate. Pages limit=N (offset=M); default 40 rows\n" " --naming-locals (with --lint) run the naming rules over local variables too; off by default\n" " OPT-IN --lint MODIFIER (requires --lint; refused alone), OFF by default:\n" " local-variable-indexing plan Phase 2 (docs/LOCALS_INDEXING.md). Runs the\n" " naming-short/naming-wordy/naming-underscore/naming-case predicates (same tags, same rule\n" " bodies as the existing Symbol-scoped checks) against LOCAL variable names too, C/C++ only,\n" " but ONLY inside a function that already clears an EXISTING\n" " size/complexity gate (loc>80 OR nest>4 OR ccx>=15 -- the shipped large-function/deep-nesting\n" " thresholds) AND has locals>=8 (measured floor: median locals=9 among this repo's own 377\n" " gated functions) -- never a whole-corpus local-name sweep. naming-short additionally requires\n" " the local's own declDepth>=2 (nested, not the function's own outermost block). Deliberately\n" " breaks the lens's stated invariant that an un-indexed local can never be flagged -- read the\n" " WITHDRAWN note atop src/naminglens.h before relying on this. NOT default-enabled inside a\n" " plain --lint run and not a candidate for it yet: the plan's own hard blocker (a hand-curated\n" " fixture corpus AND a manual real-corpus audit for idiomatic-short-name skew -- i/j/k/buf/tmp/\n" " err) has not run. Exit 0 always; findings ride the same naming-* tallies/floors as --lint\n" " --comment-coherence rank doc comments most-name-restating first, by two published content measures\n" " per function/method WITH A DOC COMMENT, two published content measures, MOST NAME-RESTATING\n" " FIRST: c_coeff (Steidl/Hummel/Juergens, ICPC 2013) is the fraction of the comment's words within\n" " Levenshtein distance <2 of a word in the symbol's own (split) name — HIGH c_coeff IS BAD, it\n" " means the comment mostly repeats the name and adds no information (the opposite of the naive\n" " 'high coherence sounds good' reading). cic (Scalabrino, ICPC 2016 / JSEP 2018) is the Jaccard\n" " overlap of two preprocessed term sets: the comment's vocabulary vs every identifier the\n" " definition's own span uses (operators/keywords stripped, camelCase/snake_case split, English\n" " stopwords dropped, deduplicated). The two measure different things and are expected to\n" " disagree — both are reported, never collapsed to one number. UNAVAILABLE (not scored, never a\n" " zero) where no doc comment exists, counted in no_comment= on the root. Complements --doc-drift\n" " (which checks whether a markdown CLAIM is stale) with comment CONTENT, over a disjoint input —\n" " neither verb duplicates the other. Pages with limit=N (offset=M); default 40 rows.\n" " --cochange[=FILE] files that change together in git (hidden coupling; the rows' own legend defines surprising=)\n" " = Fowler's SHOTGUN SURGERY in its measurable, historical form: change coupling (Gall 1998,\n" " Zimmermann 2005). The static form — callers spread over many files (Lanza & Marinescu\n" " 2006) — was measured on two corpora and does NOT predict it, so it is not a flag (EVALS.md)\n" " --cochange-recur=K (with --cochange) keep only pairs whose co-change recurs in K or more sub-windows\n" " (with --cochange) report only pairs whose co-change RECURS in K or more of the mined\n" " window's sub-windows, so a one-off refactor sprint stops reading like an eighteen-month\n" " structural defect (Clio, ICSE 2011). Every row carries recur= with or without this flag;\n" " the header publishes sub_windows= (the denominator) and min_recur= when the filter is on\n" " --cochange-groups (with --cochange) report co-change GROUPS instead of pairs, naming the file to fix\n" " (with --cochange, repo-wide only) emit Modularity Violation GROUPS instead of pairs:\n" " \"X co-changes with {A,B,C}, none of which it depends on\" is ONE row that names the file\n" " to fix (Mo/Cai/Kazman, IEEE TSE 2019). A greedy cover, disclosed as greedy — set cover is\n" " NP-hard, so the group count is an upper bound on the minimum, not the minimum\n" " --since=REV|DATE limit the churn-based verbs to commits after this revision or date\n" " scope --hotspots/--cochange/--rank-by=churn|churn-decay to commits after this point:\n" " a revision (HEAD~20, a tag/sha — deterministic) or a git approxidate\n" " (\"2 weeks ago\" — wall-clock-relative). e.g. --hotspots --since=\"1 week ago\"\n" " ranks by RECENT churn (the regression lens). Absent ⇒ each verb's OWN\n" // §B11.5 (CA4): this clause said "all-history default", which is false on all three verbs — every one // of them mines a BOUNDED default window, and two of them stamp it in their own output. Stating a // default the tool does not have is worse than stating none: a reader concludes an unstamped run // covered the whole history. The 12-vs-18 split itself is a RECORDED residual and is not re-litigated // here; what is fixed is that --help now names the real numbers and says which verbs publish them. " bounded default window, NOT all history: --hotspots 12 months,\n" " --rank-by=churn 18 months, --cochange 18 months (--rank-by=churn-decay is\n" " the ONE exception: its default IS all history, because the 90-day half-life\n" " makes a cut-off unnecessary — it stamps that too). All of them STAMP the\n" " window they used AND the anchor that produced it: a DEFAULT window is\n" " measured back from HEAD's OWN committer date, never the wall clock, and says so\n" " (window=\"12mo@HEAD\"/\"18mo@HEAD\"), so a pinned corpus, an archived release or\n" " a bisect checkout has its history INSIDE its own window — unanchored, a 2024\n" " checkout read in 2026 reported no repository at all. A value you pass HERE is\n" " never re-anchored: it is stamped verbatim, so --since=\"18 months ago\" is the\n" " wall-clock window on request. An UNRESOLVABLE value is\n" " refused by --hotspots (exit 1 — its window is part of the measurement)\n" " and degrades to the verb's own default window elsewhere\n" " BESIDE --slice=SYM:VAR it is not a window at all: it names the revision whose\n" " def-use slice of that variable this run is diffed against — see --slice\n" " --arch=FILE enforce layering rules from a rules file; exit 2 on a violation\n" " enforce layering rules (exit 2 on violation); the Martin Ca/Ce/I/A/D block it emits is a\n" " design heuristic, not independently outcome-validated (never gates).\n" " propagation_cost's N is dependency-capable files only, same denominator as --deps .\n" " Layer substrings and regex path-rules match the ROOT-RELATIVE path (src/core/x.cpp), not the\n" " spelling you passed, so a rules file means the same thing in every checkout\n" " --arch=FILE --baseline write .ripwire_arch_baseline (accept current debt as baseline), exit 0\n" " --arch=FILE --baseline-update merge current violations into baseline (accept new debt), exit 0\n" " --lint run the built-in AST checks: c-cast, goto, unsafe-c-fn, naming-*, cache-* data-layout\n" " built-in AST checks (c-cast, goto, unsafe-c-fn, naming-*, cache-* data-layout, ...). naming-uninformative\n" " is ONE-SIDED by design: it fires only when a name's subtokens are ALL corpus-common\n" " (BM25 idf over the identifier-name corpus) AND its body clears a size floor — a\n" " high-idf (distinctive) name is never penalised, unlike the withdrawn name<->body rule.\n" " Each row's applicability is per-LANGUAGE, not per-file-content: a rule whose\n" " registered languages (see --lint-catalog) intersect NONE of the corpus' languages carries\n" " applicable=\"0\" (its count=\"0\" is then structural inertness, not a measurement), and the\n" " root tallies inert_rules=\"N\"; see --lint-catalog for the full registry\n" " --lint-catalog list the built-in rule registry, one row per rule — no corpus needed\n" " the built-in rule registry: one row per rule with sev=/category=/rationale/lang=/since= — no\n" " corpus needed. Every built-in rule from every pack (base checks, atoms-*, cache-*, naming-*, the\n" " symbol-level checks) has exactly one row; lang= is the SAME token spelling --lint-rules'\n" " language: field accepts, so it round-trips into a user rule\n" " --lint-rules=DIR load user lint rules (YAML, ast-grep style) from DIR — runs with, or instead of, --lint\n" " --lint-select=PREFIX[,...] (with --lint) run only the rules whose name starts with one of these prefixes\n" " (with --lint / --lint-rules) run ONLY rules whose name starts with one of these PREFIXes (or\n" " '*' for all) — comma-separated, e.g. cache- selects the whole cache-* family. The root then\n" " carries selected=\"K of N\" plus the raw select=/ignore= you passed, so a filtered zero is never\n" " confusable with an unfiltered one. An unresolvable PREFIX (matches no rule) refuses (exit 1),\n" " naming the nearest rule/family by edit distance\n" " --lint-ignore=PREFIX[,...] (with --lint) drop the rules whose name starts with one of these prefixes\n" " (with --lint / --lint-rules) DROP rules whose name starts with one of these PREFIXes (or '*' to\n" " drop everything, e.g. paired with --lint-select elsewhere to isolate one family) — applied AFTER\n" " --lint-select narrows the set; same unresolvable-PREFIX refusal and root disclosure as --lint-select\n" " --lint-max-per-rule=N (with --lint / --lint-rules) set each rule's match budget, below or above the default 5000.\n" " The default is a runaway guard, not a target. A rule that spends its budget carries\n" " count_capped=\"1\" (its count= is a FLOOR) and the root names the call that counts the rest:\n" " findings_next= (SARIF: run properties findingsNext, --sarif kept) re-runs only the floored\n" " rules under a 10x budget; a re-run still floored names its own. Raising it costs time and\n" " memory in proportion to the matches kept\n" " --sarif (with --lint / --lint-rules) the SAME findings as SARIF 2.1.0 instead of the native XML\n" " block — the shape github/codeql-action/upload-sarif consumes for code scanning.\n" " Pure re-serialization (zero new analysis); results count == the native run's findings\n" " count. Levels: user severity error/warn/info -> SARIF error/warning/note; a built-in\n" " finding (a fact, never a gate) has no severity of its own and also maps to note. Fields\n" " with no SARIF home (per-rule capped= floor, enclosing symbol, raw sev=) ride in\n" " properties rather than being dropped; URIs are relative to the scanned root. Always the\n" " FULL result set — refuses loudly alongside limit=/offset= paging, --match and --with-profile\n" " --with-profile=FILE (with --lint) join measured runtime heat from a profile report onto the findings\n" " (with --lint) join MEASURED heat onto findings: FILE is a RIPWIRE_PROFILE build's report\n" " (its #PROF_TSV block); a finding whose enclosing symbol contains a PROFILE_SCOPE site\n" " gains heat_* attributes (scope, calls, total_ms, and whichever counter columns the\n" " profiled run armed — l1d_mpki etc.). Static shape x measured weight; joins nothing\n" " silently — a missing file or a FILE with no #PROF_TSV block refuses loudly\n" " --communities cluster the call graph into cohesive modules; each row's id= drills down\n" " cluster the call graph into cohesive modules (each row's id= drills down below; drill= names the verb)\n" " --community=ID open one module from the partition: its members, and its bridges to other modules\n" " ONE module from that partition: its FULL ranked member list (40 rows by default, raise with\n" " --limit, page with --offset) plus its bridge edges to every other module it touches. ID is an\n" " id= from --communities/--zoom; ids live in 0..partition-1 (the child's partition= — the full\n" " label space, isolated singletons included), so a single-member module is a legal drill-down\n" " and reports size=\"1\". modules= counts the non-isolated communities (same number as the\n" " parent's modules=). An id outside 0..partition-1 REFUSES, naming the valid range and the\n" " nearest legal id -- a bad id is a typo, not an empty module\n" " --zoom[=depth] show the nested module hierarchy plus the bridges between modules\n" " NESTED module hierarchy (multi-level Louvain) + cross-module bridges; --zoom --mermaid = nested diagram.\n" " Default window: the top 2 levels (levels_shown=; a module at the cut carries children= for its\n" " unprinted child modules; --zoom-levels=N prints N, 0 = all) over the 40 largest top modules\n" " (shown=/capped=/next_offset=, next= pastes the next page; --limit=N/--offset=M window them)\n" " --report architecture summary (modules, god-files, cycles) as markdown\n" " --seams cross-module call seams no test reaches (untested integration seams)\n" " --mermaid module (directory) dependency graph as a Mermaid diagram (paste/render)\n" " --owners[=SYM] bus factor: recency-weighted author ownership per file\n" " bus-factor: recency-weighted author ownership per file; bf=1 = one person holds >80%\n" " of weighted commits. Files with authors=1 (deterministically bf=1 share=1.00) fold into ONE\n" " summary row instead of N identical rows; --detail=N restores the full listing.\n" " An @FILE:LINE seed rebinds to the innermost enclosing definition (sym= names it) and\n" " analyses exactly that definition's file\n" " --dead-code[=DIR] list internal functions with no caller anywhere in the indexed tree\n" " internal source functions with no caller found in the indexed tree — a name-based graph reading, not a confidence score (dynamic dispatch, reflection and macro-generated callers are invisible to it); =DIR scopes to whole path components (dir or filename) and REFUSES a filter that names nothing indexed.\n" " A symbol whose definition is produced by a SELF-REGISTERING test/benchmark macro is never reported: doctest TEST_CASE/\n" " TEST_CASE_FIXTURE/SCENARIO, gtest TEST/TEST_F/TEST_P, Catch2, Google Benchmark — a static initializer registers them, so a\n" " name-based call graph cannot see the caller and every one of them would be a false positive. Extend the list for your own\n" " framework with `.ripwire_config`'s one key, `register_macros = NAME[, NAME...]` (one directive per line, # comments). The exemption\n" " is DISCLOSED, never silent: register-macro-excluded=\"N\" rides the report and prints even at 0. Exempt from dead-code only —\n" " such a symbol still participates in clone detection.\n" " A function a table, field or argument holds as a VALUE (--callers' rows) is not reported\n" " either: value-ref-excluded=\"N\" counts them, absent at 0 (matched by name, not a proven call);\n" " a function that only stores ITSELF still is. A value walk cut by deep nesting is disclosed on\n" " the root, with value_refs_depth_at= naming the first cut (the list may over-report there).\n" " A LEADING ./ anchors DIR at the repo ROOT (=./src matches only the top-level src/ subtree); a bare name (=src) matches that\n" " component ANYWHERE in the tree, including nested (test/fixture/src/…)\n" " --quality-baseline snapshot today's complexity, clones and dead code as the floor to measure against\n" " snapshot ccx/clones/dead-code to .ripwire_quality_baseline (run BEFORE a change, on a CLEAN tree). On a tree that\n" " DIFFERS from HEAD it computes the HEAD delta FIRST and REFUSES (exit 1) rather than pin the debt already in the\n" " tree as the floor — it names how many gating findings it would absorb and the first of them. Commit, or pass\n" " --allow-dirty. The pin is stamped with HEAD and with THIS build's identity, and only this build honors it.\n" " --allow-dirty (with --quality-baseline) pin the baseline even though the tree differs from HEAD\n" " (with --quality-baseline) pin anyway: the sidecar is stamped with the dirty pin and the absorbed count, and every\n" " later --quality-delta against it carries baseline_absorbed=\"N\" — so a green exit beside that attribute reads as\n" " \"clean SINCE THE PIN\", never \"clean\". Refused alone.\n" " --show-stale (with --quality-delta) print the stale-ack rows and the full header a clean answer collapses\n" " — a regression-free answer keeps only stale= and stale_by_kind= (its rows collapsed), and on a tree\n" " that IS its HEAD only the verdict; either fold only when it saves a quarter of the bytes. Refused alone.\n" " --quality-delta before a PR: report ONLY what your change made worse, across 12 kinds\n" " — pair with --test-gate. Each kind is measured against the baseline (complexity/verbosity/nesting/params/dup/dead/api-surface + error-masking/short-horizon-churn/new-clone-of-reused-helper + placeholder + defect-shape);\n" " every finding is classified by ORIGIN: a symbol that EXISTED at the baseline and got worse (preexisting-worse=\"N\", no attribute on the row) vs one that exists only\n" " because the code is NEW (new-symbol=\"N\", origin=\"new-symbol\" on the row). A small numeric delta is additionally sev=\"minor\". EXIT 2 ONLY on a major AND unacked row that is\n" " preexisting-worse or defect-shape format-arity (any origin) — the gating=\"N\" header count. New-symbol rows are still PRINTED (they are the debt you are adding — read them);\n" " new-symbol rows never gate, except defect-shape format-arity; exit 0 means\n" " \"nothing that already existed got worse\", not \"clean\". Clone kinds classify by member set (new-symbol only if EVERY member is new); short-horizon-churn is preexisting\n" " by construction. LIMIT: origin is canonId (path::scope::name) identity, so a RENAMED/MOVED symbol reads as new and a regression carried in with the move will not gate.\n" " error-masking = a NEW empty/pass/comment-only handler, or log-only (a broad handler whose body only logs and never names the error) or rethrow-only (the sole\n" " handler re-throws it unchanged); those two gate only where their precision was measured (Python) and are sev=\"minor\" in every other language.\n" " placeholder = a stub the change ADDED (todo!()/unimplemented!(), Kotlin TODO(), NotImplementedException, a bare raise NotImplementedError as a free function's body,\n" " a throw/raise/panic/assert saying \"not implemented\") or a comment line opening with TODO/FIXME that names no issue (#12, ABC-12, a URL); new-symbol by\n" " construction, so it never gates. Counted per enclosing symbol, like error-masking: a file-level TODO outside every definition is not counted.\n" " defect-shape = a known defect shape the change ADDED, named by defect=: format-arity (a literal std::format/print/format_to, fmt::, rw::emitTo/formatTo or\n" " Python \"literal\".format whose fields do not match its arguments — GATES on any origin, a defect not debt), utf8-cut and dedup-first (C++) and\n" " vacuous-assert (Bash test scripts) — report-only, always sev=\"minor\". A site is new only when its text occurs more often than in the baseline: a moved one is not.\n" " Test-fixture dirs + doc sections are exempt from dead-code/churn; churn needs COMMITTED thrash evidence (rewritten across recent commits AND again by this diff), never the current edit alone\n" // §B7.2 (CA4): the strict-sha staleness rule and — the part that matters — the fact that this verb // can DELETE a file in the user's tree were disclosed nowhere a user reads before running it. The // only pre-hoc statement of the deletion was a source comment. A side effect on the user's working // tree belongs in --help; the baseline= marker is a post-hoc receipt, not a disclosure. " WHICH FLOOR IT COMPARES AGAINST, and a side effect: the sidecar is honored only when the sha it was pinned at EQUALS the current git HEAD (strict\n" " equality — an ancestor commit describes a DIFFERENT tree, so everything committed since would read as your regression). A sidecar pinned anywhere\n" " else is STALE: this verb then DELETES it from your working tree (self-heal, so the next run does not rediscover the dead pin) and auto-compares the\n" " working tree vs git HEAD instead. Re-pin with --quality-baseline. The read-only MCP quality_delta verb applies the SAME staleness test but never\n" " deletes. A sidecar at the current HEAD that ANOTHER ripwire build pinned (its producer stamp names other sources — a dead set depends on how calls\n" " were resolved) is FOREIGN: both arms ignore it, never delete it, and auto-compare vs git HEAD. Which floor was actually used is on every report as\n" " baseline=: sidecar | git-HEAD | git-HEAD (stale sidecar removed) | git-HEAD (stale sidecar ignored) | git-HEAD (foreign sidecar ignored) — the\n" " stale two say a stale sidecar existed, and 'removed' means the file is gone. A non-git root has no HEAD to fall back to, so its sidecar is honored\n" " whenever this build pinned it; without one there, or with another build's, the verb exits 1.\n" // R-I: the WAVE-level form. Its own row rather than a bracket on the one above, because the floor it // compares against is a different KIND of thing (a commit, not a sidecar or the working tree) and the // row above spends eight lines on sidecar staleness that this form never touches. " --quality-delta=REV|A..B the same 12-kind report between two committed trees — a whole branch at once\n" " the same 12-kind report between two COMMITTED TREES instead of the working tree vs a baseline — the WAVE-level measurement (=A..B = tree B against tree A;\n" " =REV = that commit against its FIRST PARENT; an EMPTY side of the range means HEAD). Same grammar --dmm= takes, and A...B is REFUSED rather than read as A..B.\n" " Use it to measure a whole integration branch at once (--quality-delta=..): per-lane checks each compare against their own baseline and cannot\n" " see a regression the WAVE introduced. Identical output contract to the bare form — same kinds, gating=\"N\", exit 2, and the same .ripwire_quality_acks ratchet\n" " (acks are keyed root-relative, so a ledger recorded from working-tree runs applies unchanged). base_ref= and target_ref= disclose the two RESOLVED shas.\n" " No sidecar is read, written or deleted by this form, and at= is omitted: the two refs ARE the anchor. A==B is a legal, empty, exit-0 comparison.\n" " ONE KIND CANNOT BE MEASURED HERE and says so as churn=\"unavailable\": short-horizon-churn needs git history at the tree being judged, and both trees are\n" " materialized OUT of the repo into temp dirs. The other 10 kinds are computed exactly as the bare form computes them.\n" " --dmm[=REV|A..B] score a change as ONE number in [0,1], so quality trends across commits\n" " the DELTA MAINTAINABILITY MODEL scalar: ONE comparable number in [0,1] for a change, so quality becomes TRENDABLE across commits instead of a per-kind list (di Biase, Rastogi, Bruntink and van Deursen, TechDebt 2019; thresholds and arithmetic from PyDriller's deltamaintainability reference implementation). Bare = the WORKING TREE vs git HEAD (what --quality-delta compares); =REV = that commit vs its FIRST PARENT (the per-commit scalar); =A..B = tree B vs tree A.\n" " A UNIT is a function/method definition with a body; its VOLUME is its line span. Per property a unit is LOW risk iff size: loc<=15, complexity: cyclomatic<=5, interfacing: params<=2. good = low-risk volume ADDED plus high-risk volume REMOVED; bad = low-risk REMOVED plus high-risk ADDED; dmm = good/(good+bad). So DELETING a god function scores 1.000 and GROWING one scores 0.000.\n" " The three sub-scores (size/complexity/interfacing) are emitted alongside the combined one because they are separately actionable; the combined one POOLS them (summed good over summed good+bad) and is labelled combine=\"pooled\", since the paper publishes the three separately and no aggregate.\n" " IT IS A DELTA, NEVER A LEVEL: a unit you edit without changing its size, complexity or parameter count sits in the same bin with the same volume on both sides and contributes NOTHING. Touching bad code is not punished, deliberately, because a gate that punishes it is a gate people route around.\n" " dmm=\"UNAVAILABLE\" means good+bad was 0 (a rename, a literal edit, a comment reflow): the change is outside what the model measures. That is NEVER to be read as 1.000 or 0.000, and reason= says which case it was. Same token per property.\n" " VOLUME IS PHYSICAL LINE SPAN (size_metric=\"physical-loc\"), where the reference implementation uses non-comment non-blank lines, so a heavily commented unit crosses the size threshold here earlier. NO THRESHOLD, NO VERDICT, ALWAYS EXIT 0.\n" " --quality-ack[=REASON] accept the current findings as known, until one of them gets worse\n" " accept the current findings into .ripwire_quality_acks (per-finding ratchet): re-runs suppress them honestly (acked=\"N\") until one WORSENS past its acked size.\n" " =REASON implies the --quality-delta report it acks; the reason-less spelling needs --quality-delta\n" " beside it (refused alone). An ack with 0 findings to accept writes nothing and says so.\n" " --ack-only=SUBSTR[,SUBSTR] (with --quality-ack) ack only SOME findings — those whose KIND, canonical id or FACET matches\n" " (with --quality-ack) ack only SOME findings — those whose KIND, canonical id, or\n" " FACET contains one of these; the pseudo-token 'gating' selects exactly what would\n" " exit 2. Bare --quality-ack accepts the WHOLE report, so accepting one deliberate\n" " change silently accepts the rest — how a ratchet turns into a rubber stamp. Prefer\n" " the facet: --ack-only=contract-change acks the deliberate arity changes WITHOUT the\n" " never-gating api-surface new-symbol rows; a defect-shape facet (format-arity,\n" " utf8-cut, dedup-first, vacuous-assert) selects that shape alone. Matching nothing refuses (exit 1) rather\n" " than falling back to acking everything. Whatever you leave unacked stays visible.\n" " --scope=GLOB[,GLOB...] (with --quality-delta/--quality-ack) file findings by OWNERSHIP when one tree has several writers\n" " (with --quality-delta/--quality-ack) OWNERSHIP partition for a working tree that has\n" " MORE THAN ONE WRITER in it — N agent sessions sharing one checkout. The delta compares\n" " the working tree against HEAD, so every concurrent writer's uncommitted rows land in\n" " YOUR report; this files each finding by its p= path. Rows in scope gate as usual; rows\n" " outside it are STILL PRINTED, under an out-of-scope element with a do-not-ack banner,\n" " and never gate. The header carries scope=, scoped-out= and scoped-out-gating= (how many\n" " disclosed rows WOULD have gated — do not read a green exit as a clean tree).\n" " THE POINT IS THE ACK: bare --quality-ack in a dirty shared tree accepts the WHOLE\n" " report, which silently absorbs a sibling session's debt into a committed ledger under\n" " your reason string — that is how a ratchet becomes a rubber stamp. Under --scope, an\n" " out-of-scope row is never written, and an --ack-only that NAMES one refuses (exit 1,\n" " naming the rows, writing nothing). Each row written under a scope records by=,\n" " and a later run flags an ack whose by= does not cover what it suppresses\n" " (foreign-acks= plus an sa row with why=\"foreign-scope\").\n" " THE GLOB, EXACTLY (a pattern that silently fails to match is worse than a documented\n" " prefix): each comma-separated pattern is matched against the ROOT-RELATIVE path p=\n" " prints, and the list is an OR. NO wildcard = a ROOT-ANCHORED path prefix ending on a /\n" " boundary (scope=alpha matches alpha/lib.h, never alphabet/lib.h and never a nested\n" " src/alpha/ — stricter than the dead-code directory filter, on purpose). With * or ? =\n" " matched against the WHOLE path, * spanning / and ? exactly one character. NOT SUPPORTED:\n" " ** (it is two stars, and one already spans /), character classes, brace expansion, negation;\n" " whitespace and XML metacharacters in a pattern are REFUSED, not mangled. FLOORS: a\n" " clone group is in scope iff ANY member matches; a finding with no locator at all is\n" " filed OUT of scope (not provably yours); a scope naming nothing indexed REFUSES\n" " (exit 1) rather than reporting a clean zero.\n" " ONE RESERVED WORD: --scope=diff is the files the WORKING TREE changes vs the baseline,\n" " expanded to one path per changed INDEXED file (the count travels with the report as\n" " scope-diff-files=). It composes by UNION: --scope=diff,src/quality.h is that set plus that file.\n" " A directory really called diff must be spelled ./diff or diff/. IT IS SUGAR FOR THE\n" " SINGLE-WRITER CASE and wrong on its own in the shared tree this flag exists for — a\n" " sibling's edits are \"changed\" too, so name your own paths when the tree has more than\n" " one writer. Refused, never silently widened, when there is no git, when the range form\n" " is in play (it compares two COMMITTED trees), or when it expands to nothing. An ack\n" " written under it records by=diff, which a later run does NOT sweep: an auto-scope\n" " meant one file set then and another now, so re-checking it would invent findings.\n" " --edit-check=SYM after an edit: did SYM's contract change, and which callers no longer fit?\n" " fast per-symbol post-edit contract check: SYM's param count + publicness NOW vs git HEAD\n" " (unchanged/new-symbol/contract-change with was/now), plus its 1-hop callers with any call-site\n" " provably incompatible with the NEW arity flagged. A contract is PER DEFINITION, so a SYM\n" " matching several definition sites REFUSES (exit 1) and lists the file:name spellings that\n" " pick one — unlike --callers/--uses, this verb may not union overloads and disclose defs=.\n" " A .ripwire_notes entry targeting SYM (or its file) rides along as a child, the\n" " same row shape --for/--expand surface. An unchanged contract with nothing flagged, on a tree\n" " that IS its HEAD (no +dirty), answers with its verdict alone when that saves a quarter of the bytes:\n" " callers= counts the caller rows, rows_next= (--limit=N) lists them. A dirty tree lists them.\n" " PRE-APPLY PREVIEW — add --edit-payload=FILE|- --dry-run to ask the SAME question about bytes\n" " that have NOT been written yet. The payload is spliced over SYM's definition span in memory,\n" " exactly as --replace-symbol-body would write it; that one file is re-parsed, the call graph\n" " rebuilt over the re-derived tree, and the same document emitted with preview=\"1\" plus an\n" " child holding the CURRENT span the apply would replace, as on disk\n" " (over 4 KB: the head, with shown=/capped=\"1\"/elided_lines=) — preview then apply, no Read. Nothing is\n" " written, and every other file plus the git HEAD baseline stay the real tree's. Refuses, exit 1,\n" " on a payload that is unreadable, empty, oversize or NUL-bearing, on one whose splice raises\n" " the file's parse errors, on one that does not define SYM, and on a span the file's current\n" " bytes no longer fit. Single-root, and it previews a body REPLACEMENT only.\n" " --replace-symbol-body=TARGET replace one whole definition atomically with the bytes from --edit-payload\n" " atomically replace one uniquely-resolved definition with the bytes from --edit-payload=FILE|-\n" " (ONE trailing newline on the payload folds into the newline already after the span — a heredoc\n" " or echo always appends one the span never had; disclosed as trailing_newline_folded=true; a\n" " second one, a deliberate blank line, stays)\n" " --insert-before-symbol=TARGET atomically insert the payload immediately before one uniquely-resolved definition\n" " --insert-after-symbol=TARGET atomically insert the payload immediately after one uniquely-resolved definition.\n" " Both inserts PAD the block (newlines only, never removed) so it is separated from the\n" " anchor by the same blank-line run the file already uses at that seam; separator_padded=N\n" " on the receipt is the count added (0 when the payload carried its own). A payload carrying MORE\n" " …[REDACTED:kind] markers than the bytes it would replace already do (a body copied from a\n" " redacted serve) refuses: re-fetch that body with --no-redact.\n" " TARGET is a symbol name, an @FILE:LINE line-seed (edits the innermost definition\n" " enclosing that line — paste the location from a diff hunk or error; the receipt\n" " discloses resolved_from_seed, a faulted seed refuses with a specific diagnosis, and\n" " --edit-target-file may not accompany a seed), or a freshness-pinned sym# handle\n" " emitted by --grep --handles.\n" " --edit-payload=FILE|- required exact byte payload ('-' reads stdin); empty payloads refuse, never imply deletion\n" " --edit-target-file=PATH optional file-path substring disambiguating a same-named definition (relative or absolute)\n" " optional file-path substring disambiguating a same-named definition. RELATIVE (matched against\n" " the indexed spelling) or ABSOLUTE (matched against the file's resolved on-disk path), so the\n" " path a receipt or a trace hands you works verbatim. These three CLI verbs\n" " reuse the MCP edit engine: freshness hash, lock, pre-rename recheck, fsync, mode preservation\n" " and atomic rename. Every refusal leaves the target byte-identical. Success prints a JSON\n" " receipt whose span is the POST-EDIT byte range (where the payload now sits in the new\n" " file), NOT the region overwritten in the old one — for --replace-symbol-body those two\n" " lengths usually differ; replaced_bytes is the count of old bytes actually overwritten\n" " (0 for the two insert verbs, which never overwrite), lines={start,end} is that same\n" " region as FILE:LINE, and trailing_newline_folded / separator_padded say what the seam\n" " rules did to the payload. region={start,end,context,text} is the post-edit region as it\n" " is ON DISK (the applied lines plus context=3 each side; over 2 KB it carries head, tail,\n" " elided_lines and capped=true) and blob_sha is the git blob id of the written bytes (==\n" " git hash-object FILE) — the Read an agent would make to see what landed is already in\n" " hand. The receipt also carries the POST-EDIT VERIFICATION the tool would otherwise tell\n" " you to run: edit_check={status,callers,incompatible,sites} — the same answer\n" " --edit-check=FILE:SYM gives, sites naming each broken caller's call LINES — and\n" " tests_to_run, the same rows --affected=FILE gives, run recipe included; and exactly ONE\n" " next= (a contract-change with broken callers: --uses=FILE:SYM; else the first run= recipe\n" " in EVIDENCE order (a changed or partner test outranks a deeper graph hop);\n" " else --test-gate=FILE; under --no-post-check: --edit-check=FILE:SYM). Edit, see what\n" " landed, verify and find the tests to run is ONE call.\n" " --no-post-check skip that folded verification — pass it when you are about to edit again immediately\n" " skip that folded verification (the index refresh it needs is the one the next verb call\n" " would pay for anyway; pass this when you are about to edit again immediately). The MCP\n" " spelling is post_check:false. Single-root only.\n" " --edit-plan=FILE apply several edits as one transaction, described in a versioned JSON file\n" " versioned JSON multi-edit transaction: {version:1, edits:[{op,target,file?,payload}]};\n" " op is one of replace_symbol_body, insert_before_symbol, insert_after_symbol\n" " each target takes the same forms as TARGET above (a name, an @FILE:LINE seed, a handle)\n" " --dry-run | --apply the plan's mode: --dry-run preflights, --apply commits; exactly one of the two is required\n" " the plan's explicit mode: --dry-run preflights and prints the receipt without writing,\n" " --apply commits; exactly one of the two is required. Payload paths are relative to the\n" " plan file and CONFINED to its directory: a path resolving outside it (an absolute path,\n" " a '..' escape, or a symlink pointing out) refuses, naming the path it resolved to, and\n" " the receipt's payload_path shows what each op will READ. Every target/payload/span is\n" " preflighted before any write; overlaps refuse.\n" " Apply holds sorted per-file locks and atomically renames each file, re-verifying EACH\n" " file's bytes immediately before ITS OWN write (recheck_before_each_write in the receipt)\n" " so a non-cooperating external writer is detected rather than clobbered. Prior files roll\n" " back on a later write failure or such a detection; the message says which happened and\n" " how many files it restored, and ends with the ONE call that shows the state — next: a git\n" " diff (exit-code mode) over the plan's files from ; exit 0 IS the claim, checked against git.\n" " A crash between file renames remains a disclosed limit.\n" " --safe-delete=SYM \"can I delete this?\" — one call joining callers, blast radius, tests and history\n" " \"can I delete this?\" — ONE call composing signals the tool already computes for one\n" " already-resolved SYM: 1-hop callers=, the transitive --impact blast radius (impact_reaches=),\n" " every --uses read/write/import/call/extends site (uses=), how much of the blast radius the\n" " tested= lens covers (tested_self=/radius_tested=/radius_untested=), and --dead-code's own\n" " zero-caller/internal-linkage shape at defs=1 (dead_code_candidate=). ambiguous_callers= names callers\n" " whose own calls include an ambiguously-resolved one (g.ambOut) — a caveat, not a count of\n" " proven-wrong edges. FACTS only: risk= names what was found — none-found (zero callers AND\n" " zero uses), untested-radius (a radius exists and none of it is test-covered), or\n" " uses-exist (a radius exists and some of it is tested) — never a go/no-go verdict; unmodelled\n" " when nothing was found for a kind used by reading or naming it (a variable, class, struct)\n" " whose uses those counts cannot see. callers_floor=/uses_floor= mark a count the index holds\n" " evidence of a miss for, and next= is the call that lists the rest. A caller row's sites_at= is its\n" " call sites as file:line tokens (p= stays the line where the caller is defined).\n" " --slice=SYM[:VAR] trace one variable's definitions and uses inside one function\n" " NAME-BASED intra-procedural def-use slice of variable VAR inside the ONE uniquely-resolved\n" " definition SYM (statement-level def-use edges as a queryable primitive — the ARISE result,\n" " arXiv:2605.03117). One row per line touching VAR, ranked as the root's order=\"defuse\"\n" " states: def-use coverage (distinct local names on the line) descending, then line — measured\n" " (docs/EVALS.md): puts a gold line first more often than a random shuffle, among these rows\n" " only — a pre-registered attempt to rank lines over the WHOLE function span did not beat\n" " chance (docs/EVALS.md); order=\"defuse\" is not a whole-function relevance ranking. k=def|use|both|\n" " scope = a Python global/nonlocal statement, neither read nor write; t=param|decl|\n" " assign|call-arg|read|global|nonlocal = the strongest role on the line; CDATA = the trimmed source\n" " line; defs=/uses= count occurrences. JS/TS destructuring binders (`const {a, b} = o`,\n" " `[x] = arr`, destructured parameters) are locals whose def is the pattern line. A write\n" " hidden behind a call — receiver mutation, a by-reference/out-parameter, a function-like\n" " macro — is a use, never a def (stated in the legend). Bare --slice=SYM lists the sliceable locals ( rows) so a caller can pick VAR. LIMITS in the legend, not implied: no alias analysis.\n" " REACHING DEFINITIONS are FLOW-SENSITIVE inside the definition for C-family and Python\n" " (root reach=\"cfg\": a def is killed by the next unconditional def on every path, defs\n" " join at if/elif/else, switch, loop back-edge, try/finally, for/while-else, match, #ifdef\n" " merges) and source-order for JS/TS/Go/Java/Rust (reach=\"linear\", nothing joins); every\n" " use row carries rd= (the lines of the defs that reach it, \"-\" = none). The unit is the\n" " STATEMENT (uses read the entering state, defs apply after); a nested lambda/def body,\n" " ?:, short-circuit fold into their statement, goto is untracked, global/nonlocal is\n" " tracked like a local — each disclosed in the legend. Block scopes ARE separated: a name declared twice in the definition\n" " is two variables, each row of a shadowed name carries b= (the declaration line it binds\n" " to), the root bindings=, the inventory one per binding. SYM matching several\n" " definition sites REFUSES (exit 1) listing the file:name spellings that pick one, like\n" " --edit-check. Served: C/C++/ObjC (+CUDA/Metal), Python, JS/TS, Go, Java, Rust — other\n" " indexed languages refuse loudly (never an empty success). Single-root only.\n" " PREPROCESSOR (C-family): a `#if 0` body and the `#else` of `#if 1` are DEAD — dropped,\n" " the line count disclosed as preproc_rows=; every other conditional region (`#ifdef X`,\n" " `#ifndef X`, `#if defined(X)`, `#if EXPR`) is build-dependent and cannot be decided\n" " without the build's macro set, so its rows are KEPT and flagged pp=\"1\", and a pp def\n" " never hides the unconditional def before it in a flow (both are reaching).\n" " LINE-SEEDED: --at=FILE:LINE beside --slice (or --slice=@FILE:LINE) is the ARISE\n" " (file, line[, variable]) seed — the definition sliced is the innermost one enclosing\n" " the line (a seed narrows an otherwise-ambiguous SYM; a seed enclosed by none of SYM's\n" " definitions refuses naming both). A seed line naming exactly ONE sliceable local\n" " pre-picks it (disclosed: seed= var_from=\"seed\"); zero or several serve the inventory\n" " with seed_vars= and the candidate rows marked seed=\"1\", never a guess. A plain\n" " identifier spec beside --at reads as the seed's VARIABLE (--slice=VAR --at=src/f.cpp:12).\n" " SINCE: --since=REV|DATE beside --slice=SYM:VAR adds a child carrying the\n" " DEPENDENCE diff of that variable against the committed tree at REV — one row per\n" " added or removed STATEMENT of the variable, one row per added or removed def-use\n" " edge. The unit is the STATEMENT and the key is the ROLE, never the line and never the\n" " text, so a re-wrap, a comment edit, an insertion above the definition, and a rename of\n" " an unrelated local all come back EMPTY. Empty means no def-use edge of that variable\n" " moved, never that the commit changed nothing — git diff answers the second question.\n" " status= names each way the symbol can be absent at REV, and comparable=\"0\" says\n" " outright that no comparison was made and the emptiness is not evidence. Refused on the\n" " bare inventory: a dependence diff needs a seed variable.\n" " --slice-flow=back|fwd|both (with --slice) follow the value flow transitively — back, forward, or in both directions\n" " TRANSITIVE cross-statement data-flow slice (modifies --slice=SYM:VAR; refused alone or on\n" " the bare inventory — a flow needs a seed variable). Follows VALUE FLOW over\n" " reaching-definition def-use edges — a use of v reaches the last def of v in source order\n" " before it — by bounded BFS from the seed variable, the ARISE paper's own slicer semantics\n" " (arXiv:2605.03117: seed + direction, bounded BFS, stops at the function boundary; the\n" " inter-procedural half stays with --callers/--impact by the paper's own design). back =\n" " statements whose values feed the seed; fwd = statements the seed's value reaches; both =\n" " the union. Flow rows are : v= the variable at that step, d= BFS\n" " depth (seed rows are depth 0), f= the line the step was reached from. steps= counts flow\n" " rows; depth= states the bound in force. LIMITS (in the legend too): name-based, no alias\n" " analysis, line-granular ROWS (a multi-statement line merges) over statement-anchored\n" " CHAINING (a multi-LINE statement chains as ONE unit), a shadowed name's bindings walk\n" " separately (never into each other's block), data dependence only — no control\n" " dependence (the guard deciding whether a def executes is never a row).\n" " both= is UNSEEDED-REDUNDANT: unioned over a function's whole variable inventory, an\n" " unseeded both reaches no line the flat rows (bare --slice=SYM lists them) do not —\n" " disclosed as flow_redundant=\"1\", never gated. Seed it (--at=FILE:LINE) for flow to\n" " add real reach beyond the flat inventory (measured, docs/research).\n" " --slice-depth=N (with --slice-flow) bound the walk at N hops; 1..32, default 8\n" " the --slice-flow BFS depth bound, 1..32 (default 8, always disclosed as depth= on the\n" " root). A bound that cuts a live frontier is disclosed as flow_truncated=\"1\" — a short\n" " slice means \"bounded here\", never \"nothing further exists\". Refused without --slice-flow.\n" " --at=FILE:LINE name the definitions enclosing one line, outermost first — when you hold a location, not a name\n" " the ENCLOSING-DEFINITION CHAIN at one location (1-based line), outermost->innermost —\n" " for when you hold a compiler error / diff hunk / stack frame, not a name. rows, indexed definitions only; sym= names the innermost. The SAME seed composes\n" " into any SYM selector as @FILE:LINE (--callers=@src/f.cpp:120, --expand=@...,\n" " --edit-check=@..., --slice=@FILE:LINE:VAR, --replace-symbol-body=@... and the other\n" " edit TARGETs, ...) and resolves to that innermost definition — the no-name half of\n" " the file:line:name grammar. The NAME-scan verbs --mentions/--owners rebind a seed to\n" " that definition's name and answer, disclosing sym=. Beside --slice, the at flag is\n" " that verb's LINE SEED instead of a competing verb (see --slice). A malformed seed, an\n" " ambiguous or unmatched path, a line past EOF, a line inside no indexed definition, or\n" " two disjoint definitions sharing the line each REFUSE with a specific diagnosis (exit\n" " 1) — never a guess, never an empty chain.\n" " --pr-context[=BASEREF] build the review evidence for a diff: per file, its callers, blast radius, tests and owners\n" " no-LLM review-evidence bundle for the diff (working-tree, or vs BASEREF): per changed file,\n" " its symbols + callers + blast radius + affected tests + co-change partners + owners.\n" " The bundle is BUDGETED by default (8000 tokens, budget_default=\"1\" on the root; --token-budget=N\n" " or --max-tokens=N set it explicitly): per-file structural counts survive first, the deep\n" " detail (caller/co-change lists, per-symbol rows) trims deepest-first, truncated= names what\n" " was dropped and est_tokens= reports the fit. When even the structural floor of every changed\n" " file exceeds the budget, the FILES (blast-radius order) are windowed: shown=/capped=/total=/\n" " next_offset= disclose the cut and next= pastes the next page (--offset=N / --limit=N window\n" " them explicitly).\n" " ANCHORING: the BASEREF form diffs against merge-base(BASEREF,HEAD), never BASEREF's tip —\n" " \"what did THIS work change since it forked\", not \"how do the two trees differ today\".\n" " base_moved= counts the paths BASEREF moved since the fork that this work never touched\n" " (excluded, not silently); anchor=\"ref-tip-two-dot\" = no merge-base (unrelated history).\n" " direction= always names the SIDE you are reading, and a no-ref-work row fires when\n" " BASEREF's tip IS the merge base -- it carries no divergent work, so every row is HEAD's.\n" " --merge-scout=REF[,REF...] compare branches before landing: which symbols two refs both touched, and the merge order\n" " read-only cross-branch overlap: for each REF, the symbols it changed vs its merge-base\n" " with HEAD (git-archive TEMP copies — never checked out, never mutates a ref); the dirty\n" " working tree joins as an implicit extra arm. Pairwise: a changed symbol on TWO arms is a\n" " same-symbol conflict, two arms touching different symbols in the same file is a textual\n" " risk; is the fewest-conflicts-first greedy land order (ties: ref name\n" " asc). An unresolvable REF refuses loudly (exit 1, names the ref) before any archive work.\n" " ANCHORING: every arm is diffed against its OWN merge-base with HEAD, never against live\n" " HEAD — a file an arm never opened can never show up because the live line moved.\n" " head_conflicts= is what that anchor hides, kept as its own row class: symbols this arm\n" " changed that the LIVE LINE also changed since the arm forked (HEAD is not an arm, so no\n" " pairwise comparison can see it). Single-root only.\n"; // The third and last block of the catalog, after the --plan-lanes rows. inline constexpr char kHelpTail[] = " --stray-content[=SUBSTR] \"where does this content live?\" across every branch — what `git cherry` cannot answer\n" " \"where does this content live?\" across ALL branches — the question `git cherry` cannot\n" " answer. Per local ref (SUBSTR filters ref names): the lines its own divergent work\n" " AUTHORED vs its merge-base with HEAD that the live line does NOT have, and a verdict.\n" " v=\"unmerged\" = genuinely absent; v=\"superseded\" = the live line removed the SAME base\n" " code this ref removed, i.e. it re-implemented the work (git cherry still calls that\n" " commit unmerged, forever); v=\"merged\" refs are omitted. Every row shows its raw\n" " del=/redone=/sim= evidence, so a verdict is auditable, not a black box.\n" " v=\"unknown\" (ok=\"0\") = the ref has NO merge-base with HEAD, so it could not be analysed\n" " at all — a shallow clone (the actions/checkout DEFAULT) puts every ref here. It is NOT a\n" " claim the work is merged: it is the absence of an answer, counted in its own unknown=\n" " bucket so unmerged+superseded+merged+unknown always reconciles with refs=, and surfaced\n" " by --plan as an row rather than silently dropped.\n" " LIMITS: line-granular, not semantic — a rewrite that shares no deleted base line reads\n" " as unmerged; binary/oversized blobs are reported diffable=\"0\" with no counts.\n" " Read-only (cat-file/diff/ls-tree); single-root only.\n" " --plan (with --stray-content) which branches still hold real work, and in what order to land them\n" " (with --stray-content) \"of all my branches, which still hold REAL work, and in what\n" " order should I land them?\" Selects the refs --stray-content calls v=\"unmerged\", DROPS\n" " the v=\"superseded\" ones (landing them would re-do work the live line already did —\n" " the exact waste --stray-content exists to catch), and feeds the survivors to\n" " --merge-scout's existing pairwise-conflict + fewest-conflicts-first landing-order\n" " machinery — composition only, neither verb's logic is reimplemented. \n" " is unmerged work NOT fed to merge-scout THIS run (a cost bound, not a verdict);\n" " names the superseded drops and why. COST: --stray-content is a cheap per-\n" " blob sweep, but --merge-scout is per-ARM (git-archive + full ingest of each ref's tree)\n" " — measured 27s for 9 unmerged refs on a 35-branch real C++ repo (~3s/ref). kMaxPlanScout\n" " (12) bounds it to the top-N unmerged refs BY STRAY SIZE; --detail lifts the bound to\n" " scout everything. This is an EXPLICIT opt-in \"before you land\" call, not a per-\n" " question one — the default map's ~0.10s path is untouched. Read-only; single-root only.\n" " --abi (with --stray-content) the cross-branch ABI-break gate for a struct that changed shape\n" " (with --stray-content) the CROSS-BRANCH ABI-BREAK gate `--layout` and `--stray-content`\n" " each miss alone: a branch that adds one field to a dual-compile uniform struct merges\n" " textually clean and reads as a harmless \"+1 field\" to a line-granular diff. SCOPE is\n" " what each ref AUTHORED — the paths `git diff base..tip` reports against its own MERGE\n" " BASE, never `diff HEAD..tip`. A file the branch never opened cannot be a break the\n" " branch introduced, and on a long-lived shared tree that one distinction is nearly all\n" " the noise: measured on a 35-branch C++ repo, 487 drift rows fell to 4 (the rest were\n" " the live line's own evolution reflected back at the reader). For each\n" " authored path this runs --layout's OWN field-offset arithmetic lexically on that ref's\n" " git blob (never indexed) and compares it against HEAD's computed fields.\n" " LISTED by default: kind=\"drift\" (the byte contract differs — the only kind that exits\n" " 2), kind=\"unknown\" (a ref-side copy this module could not model; its caveats ride along\n" " in ref_caveat and it is NEVER reported as unchanged), kind=\"absent\" (the ref does not\n" " define the struct at that path at all).\n" " EXCLUDED by default, each on its own header counter — add --detail=N to print them:\n" " kind=\"rename\" (identical slots and field TYPES under different field NAMES: every byte\n" " stayed where it was, so it is a source change, not a byte-contract one — note a\n" " same-type field REORDER is lexically indistinguishable from a rename and lands here\n" " too), kind=\"spelling\"/\"stub\" (--layout's own harmless cases), and kind=\"head-moved\"\n" " (the ref's copy equals its own merge-base copy, so the LIVE LINE changed, not the\n" " branch). head_only= counts candidate sites on paths only the live line touched;\n" " unmodelable= counts sites skipped because HEAD's own copy carries no baseline; rows=/\n" " shown=/dropped=/excluded= reconcile the body against the sweep (capped=\"0|1\" is the\n" " tool-wide truncation BIT; dropped= is the count). Nothing is dropped\n" " without a number. Structs that match are omitted (report only differences); a ref with\n" " no rows at all counts into quiet=, a ref whose every row is an excluded kind counts into\n" " excluded_refs= (and prints under --detail=N), and broken_refs= counts REFS (not rows).\n" " Rows are ranked by SIZE DELTA so the biggest contract break leads, capped at 12 per ref\n" " with an explicit ; --detail=N lifts the cap.\n" " LIMITS: HEAD's own side is the WORKING TREE's --layout answer, not a re-fetched git blob\n" " at HEAD's commit (the same scope --layout itself claims); a nested field's OWN type\n" " resolves through HEAD's copy even when the ref also changed it; the ref-side locator is\n" " index-free and file-scope (one namespace deep) only, so a struct nested in a class or an\n" " extern \"C\" block reads absent rather than compared; a HEAD-side struct --layout itself\n" " cannot model at all (pragma pack, bitfields, ...) has no baseline and is counted in\n" " unmodelable=, not compared; the authorship anchor is per PATH, so a branch changing\n" " struct S in one file while the live line changes S's mirror in another is a merge hazard\n" " only `--layout=S` on the merged result can see. Read-only; single-root only.\n" " --whereis=SYM find which branch's tree defines or mentions SYM, HEAD first\n" " which REF's tree defines or mentions SYM — HEAD first, then every local branch, with\n" " on-head=\"0\" naming the case the verb exists for: content that lives only on a branch.\n" " Each distinct blob is read ONCE (content-addressed), so N branches cost ~one tree.\n" " kind=\"def\" on a HEAD row is the PARSED index's answer (head_labels=\"index\"); on another\n" " ref's row (ref_labels=\"parsed\") it is the same extraction run over that ref's blob, once per\n" " blob and path, and only for a blob holding a line a loose lexical test calls worth parsing\n" " (it only CHOOSES blobs): a blob without one reads kind=\"ref\". kind=\"text\" is such a line\n" " no parse read ( counts them; --detail=1 lifts the parse guard), never a def.\n" " head_labels=\"lexical\" ⇒ HEAD fell back to that lexical test (no indexed def of the name, or\n" " a working tree that drifted from HEAD). refs_scanned= is the SCAN\n" " denominator (refs read besides HEAD), not a matched count. Read-only; single-root only.\n" " A checkout that differs from HEAD is read from disk: each changed path's rows say\n" " ref=\"worktree\" and replace HEAD's, at= gains +dirty, and worktree= says whether every one was read.\n" " LIMITS: a TREE scan finds only what some ref STILL carries, so hits=\"0\" alone cannot\n" " tell a name this repo never had from one it deleted, and content dropped by every tree\n" " is invisible. Add --with-history: a row then says v=\"never\" or v=\"removed\"\n" " with the commit, date and file that removed it. Remote-tracking refs are excluded\n" " (they mirror local ones); refs are capped, narrow with --stray-content=SUBSTR.\n" " LISTING: by default only the kind=\"def\" (and kind=\"text\") rows are listed and the kind=\"ref\" rows\n" " counted in one element, when that page lists MORE definitions\n" " than the --whereis-listing=all page under the row cap, or the same ones in strictly\n" " fewer bytes; otherwise every hit is listed. On that page a definition line several refs\n" " hold (same path, same text) is ONE row with refs=\"N\", and folded= on the root counts\n" " the rows it stands for (on the first page, shown + more + folded + refs count = hits); listing=all prints each.\n" " PREVIEW: a line over 300 bytes prints t= as its first bytes (cut on a UTF-8 boundary) plus an\n" " ellipsis, 200 bytes in all, and the row carries t_bytes= (the whole line's size), when that\n" " makes the page shorter than the same page whole; counts them, and\n" " its next= (--whereis-full-text) replays that page with every line whole.\n" " --whereis-full-text with --whereis: print every t= line whole (no preview, no t_bytes=).\n" " The listing, window, rows and labels are the page's own, so the of a page\n" " that printed a preview names it. --detail lifts the preview too, with the row cap and the\n" " parse guard. Refused without --whereis. MCP: full_text=true.\n" " --whereis-listing=WHICH with --whereis: which rows to list, defs, refs or all (default: more defs, else shorter).\n" " The default lists defs when that page lists MORE definitions than all under the same\n" " row cap (references can fill a capped all page before the branch definitions arrive),\n" " whatever its bytes; when both list the same definitions, defs only when strictly shorter\n" " in bytes, else all (a tie lists all). defs lists every kind=\"def\" (and kind=\"text\") row and COUNTS\n" " the kind=\"ref\" rows in one element whose next= lists exactly\n" " them (refs). kind=\"def\" is the parser's label: a definition it does not model\n" " (define_method, setattr, a name bound by assignment) is a counted ref. With no ref\n" " row, or no def row (the mentions are then the answer), every hit is listed and the\n" " root carries no listing=. all lists every row, the whole hit list. shown=/capped= and\n" " --limit/--offset window the LISTED rows; hits= counts every row. Refused without\n" " --whereis, and on an unknown value.\n" " --flags[=SUBSTR] the dark-content dashboard: what is built but switched OFF in this repo\n" " the dark-content dashboard: what is BUILT but OFF in this repo. Harvests all three gate\n" " patterns — #ifndef/#define header gates, CMake option(), and getenv() reads — and reports\n" " gate, kind (compile/cmake/env), default, the size of the code it guards (#if regions and\n" " their LOC), and its read sites. When a name is BOTH a header gate and a CMake option the\n" " CMake default WINS (that is what the build actually passes) and both sites are listed.\n" " A gate whose default IS another gate's name (#define F_WALLS F_ALL) is resolved: it\n" " inherits the master's default and rolls its guarded size up, so a master switch shows\n" " rather than a misleading loc=\"0\".\n" " LIMITS: lexical, not preprocessed — a gate computed at configure time or set only in a\n" " CI script shows its in-repo default, never the value your build used. A gate needs a\n" " VALUE (#ifndef F / #define F 0) to be a gate: valueless pairs are include guards and\n" " are excluded, and a gate read as a VALUE (constexpr bool k = F != 0, then if constexpr)\n" " reports regions=\"0\" honestly — its code is a C++ branch, not an #if region.\n" " Pair it with --flip=NAME below to size ONE gate instead of listing them all.\n" " --flip=NAME (with --flags) the blast radius of turning ONE gate on\n" " (with --flags) the BLAST RADIUS of turning ONE gate ON: which code becomes live, how\n" " much, which SYMBOLS hold it, what those transitively reach, and which TESTS cover it —\n" " the actionable sequel to --flags' list. Reports #if regions AND the C++ branch sites a\n" " value-style gate governs (constexpr bool k = F != 0, then if constexpr( k )): the\n" " binding is followed, so the family --flags honestly sizes at regions=\"0\" gets a real\n" " radius here. Alias chains run BOTH ways — flipping a MASTER rolls up every child that\n" " #defines to it ( rows), flipping a CHILD lights only that child and names the\n" " plus the siblings its flip would add. kind=cmake means the switch becomes a\n" " -DNAME=1 compile definition, so the C++ radius is identical, but it ALSO steers the\n" " build graph (an if(NAME) target_sources can add whole files) — those CMake sites are\n" " listed as rows and deliberately NOT followed. kind=env is RUNTIME (runtime=\"1\"):\n" " there is no delimited region, so the hosts are the symbols that consult the variable\n" " and every row is conditional at its read. --detail lifts the per-list row caps.\n" " LIMITS: lexical and single-line, never preprocessed. A binding split across two lines is\n" " missed, and block comments are only skipped line-by-line. The value lane reads C-family\n" " source only and treats a file that declares its OWN constant of the same name as\n" " shadowing the gate's (C++ scoping) — but a third header's same-named constant, included\n" " rather than redeclared, would still be counted. A lit site inside no indexed def (a\n" " guarded member field, a file-scope constexpr, a test-macro body) counts into filescope=\n" " instead of a host. Single-root only (the harvest reads on-disk paths, which a merged\n" " workspace relabels) — run it per root. Exit 0 always otherwise: a report, not a gate;\n" " an unknown gate name refuses (exit 1) and names the near-misses.\n" " --layout=STRUCT show one struct's memory layout — fields in order, with computed offsets, sizes and padding\n" " the CPU/GPU contract view for ONE struct/class: its fields in declaration order with\n" " COMPUTED offsets/sizes/padding, every static_assert in the index that mentions it, and\n" " EVERY same-name definition compared field-by-field (the mirror/stub drift check that a\n" " dual-compile uniform block needs on every edit). file:name disambiguates a same-named\n" " struct (like --around/--lego). Exit 2 when the contract is BROKEN: mirror=\"mismatch\"\n" " (two definitions of the name disagree) or agree=\"0\" (a sizeof tripwire contradicts the\n" " computed size). Exit 3 when it could not be VERIFIED: a same-name definition's file\n" " was unreadable when this ran (unreadable=\"N\" on the root; 2 still wins when a readable\n" " pair already disagrees). Multi-root aware: the mirror check spans every merged root.\n" " LIMITS, read them: the offsets are a MODEL, not the ABI — a lexical walk under standard-\n" " layout assumptions on a 64-bit Apple/LP64 target (natural alignment, interior padding,\n" " trailing pad to the aggregate's own alignment). It is NOT a compiler: #pragma pack,\n" " bitfields, virtuals, base classes, nested/anonymous aggregates, #if-conditional members,\n" " templates, pointer-to-member fields and any field type it cannot size all set\n" " modeled=\"0\" with a named caveat instead of printing a number, and one unsized field\n" " un-places every field after it. alignas(N) and attribute packed ARE modelled. Array\n" " extents and macro type names resolve against the DEFINING FILE's own #define/constexpr\n" " constants only, and a macro with two definitions is accepted only when both agree on the\n" " size (the dual-compile half/__fp16 case). Definitions and asserts come from the INDEXED\n" " C-FAMILY files only (a TypeScript/Swift class has no byte layout) — .metal IS one\n" " of them (indexed under the C++ grammar, see kLangTable), so a Metal struct's layout is\n" " modelled like any other C-family aggregate.\n" " --field-affinity[=STRUCT] find fields that are read together but declared far apart — the cache-locality lens\n" " the CACHE-LOCALITY lens: which fields are READ TOGETHER but declared FAR APART. Builds a\n" " static field CO-ACCESS affinity graph (one observation per indexed C-family function\n" " body) and diffs it against the DECLARED field order and 64-byte cache-line geometry,\n" " reusing --layout's LP64 offset model. Bare = every aggregate in the repo, ranked by\n" " separation cost; =STRUCT narrows the report to one. Pairs carry Chilimbi's separation\n" " weight wt = (64 - dist)/64 (Cache-Conscious Structure Definition, PLDI 1999) — CITED,\n" " not invented here, along with the affinity graph and the points-to-free access\n" " enumeration; the advice-not-transform posture is Hundt et al., CGO 2006. Exactly TWO\n" " findings fire, both with a direction you can defend in one sentence: split-line (two\n" " fields co-accessed by 2+ functions at wt 0.00, so NO field order puts them on one line)\n" " and straddle (one co-accessed field crossing a line boundary). ADVICE ONLY: it never\n" " proposes a reordering and it has no rewrite mode, because pack-tighter/sort-by-size advice is\n" " NON-MONOTONIC (tight packing can induce false sharing — the reason the Go team keeps its\n" " own fieldalignment analyzer out of vet and gopls). LIMITS, both in the header: static\n" " access counts are NOT dynamic frequency, so fns= is a FLOOR of distinct indexed\n" " functions and w= is a call-graph reachability PROXY (1 + fan-in), never a measured\n" " count; only dot/arrow member syntax is counted (a bare field name inside its own method\n" " is indistinguishable from a local); a field name declared by TWO aggregates is REFUSED\n" " and tallied in amb_skipped= rather than guessed; and all geometry is the LP64 MODEL, so\n" " a definition --layout marks modeled=\"0\" contributes its affinity graph and NO geometry\n" " finding. validate= names the instrumented PROFILE_SCOPE whose hardware counters would\n" " confirm the hypothesis (see docs/FIELDAFFINITY.md for the worked example). C/C++/ObjC\n" " only. Exit 0 always: a report, not a gate.\n" " --doc-drift[=SUBSTR] check which of this repo's documented claims are now false\n" " which of this repo's DOC claims are now false. Verifies the CHECKABLE anchors in every\n" " markdown file (SUBSTR filters doc paths) against the live index and prints ONLY the ones\n" " that no longer hold, four kinds: file:line refs (why=\"missing-file\" the path is gone,\n" " \"past-eof\" the file is shorter than that, \"line-moved\" the line is no longer inside the\n" " symbol the doc names beside it — got= names the squatter); backticked symbol mentions\n" " (\"undefined\"); `= N` constants (\"const-value\"); and `[N]` array extents (\"array-extent\").\n" " LIMITS, stated because a doc-drift verb that cries wolf is worse than none — every lane\n" " deliberately UNDER-reports. A backticked name is called stale only when it occurs nowhere\n" " in any non-markdown file as an identifier token, so every library name is silent, and so\n" " is any repo constant the grammar does not tag as a definition (namespace-scope constexpr\n" " in C++, for one) — those are counted as unchecked r=\"not-a-definition\", never as drift.\n" " A number is compared only against a DECLARATION-shaped integer literal (a decl keyword on\n" " the line, or the name opening it) that the corpus binds UNIQUELY; two values in the tree\n" " means unchecked, not drift. A `NAME = N` whose NAME appears nowhere in the code is prose,\n" " counted in prose= and never claimed as an anchor. Symbol mentions inside ``` fences are\n" " skipped (illustrative code, not claims). checked + unchecked = anchors, always: whatever\n" " was not proved says so in an row. Read why=\"undefined\" precisely — it says\n" " the name is defined NOWHERE in this repo, which is not the same as DELETED: in a plan or\n" " design doc naming work not yet built, that is expected rather than rot. The file:line,\n" " const and array lanes are the high-precision ones; the mention lane is the weakest —\n" " --with-history is the fix, splitting it into why=\"deleted\" (history removed the name;\n" " got= names the commit and date, at= the file) versus unchecked r=\"never-in-history\"\n" " (this repo never had it, so it is not rot at all).\n" " DATED RECORDS vs ROT. An audit's finding row and a live map gone stale look identical —\n" " both are \"the code moved and the doc did not\" — so a failed anchor the AUTHOR DATED is\n" " split out as kind=\"dated-record\" and counted in dated=, leaving drift= for the LIVE rot.\n" " drift + dated is every anchor that failed: a record still prints, it is never dropped.\n" " rec= names the evidence, most specific first: \"line\" (the line itself hedges — an\n" " at-the-time / as-of-DATE note, or a row opening with an ISO date), \"block\" (the nearest\n" " heading carries an ISO date), \"title\" (the filename or H1 does), \"stamp\" (a LABELLED\n" " front-matter self-date: 'Date: …', 'Written …', 'Generated: …').\n" " WHAT THIS LANE CANNOT DO, because both were measured and rejected: it cannot use git\n" " history — 90 of this repo's 98 stale file:line anchors were CORRECT at their own doc's\n" " last commit, audit findings and live design docs alike, because \"was it true when\n" " written\" is the definition of BOTH a record and rot; and it will not read a bare date in\n" " the opening prose, which on this repo alone dated three LIVE documents on a day they\n" " merely mentioned. It reads dating MARKS, so a doc that is obviously an artifact-of-a-date\n" " to a human but never writes that date machine-readably reports LIVE (this repo has two).\n" " The bias is one-directional on purpose: a wrong \"record\" hides real rot, a wrong \"live\"\n" " only over-reports. An inception or freshness date ('opened …', 'Last updated …') is a\n" " claim the doc is CURRENT and never marks a record.\n" " NOT CHECKED AT ALL: prose, Status lines, dates, 'N of M done' tallies, and whether a\n" " code block's body is still correct. Always exits 0 — a report, not a gate.\n" " Root element carries at=\"[+dirty][+shallow]\" (omitted on a non-git root) — the commit these\n" " counts were computed against, so a number quoted from this report stays comparable\n" " across a HEAD that moves mid-session.\n" " --doc-drift --gateability turn \"CI stays non-gating\" into a finishable list: which docs still have live failing anchors\n" " turn \"CI stays non-gating\" into a finishable to-do list: for every doc that STILL has a\n" " LIVE (undated) failing anchor, prints its path and live=N (how many of its rows a date\n" " would fix), plus projected_drift= — repo-wide drift= if EVERY listed doc got the fix.\n" " The fix is always the same one this lane already reads for rec=\"title\"/\"stamp\": an ISO\n" " date in the doc's H1/filename, or a front-matter self-date line (Date:/Written:/\n" " Generated:/Recorded:/Reviewed:/Audited:/Authored:). projected_drift= is an UPPER BOUND,\n" " not a mandate — dating a doc that is genuinely a live/current reference (not a\n" " snapshot-in-time record) would hide real rot rather than honestly classify it. Requires\n" " --doc-drift (refused loudly alone).\n" " --with-history opt in: let --doc-drift and --whereis ask git history whether a name ever existed here\n" " OPT-IN: let --doc-drift and --whereis ask git HISTORY whether a name was ever in this\n" " repo, and which commit removed it. ONE `git log -p` walk over everything reachable from\n" " HEAD, tokenizing removed lines — the pickaxe's semantics without the pickaxe's cost\n" " (`git log -S` per name is ~126 s at 247 names on a 2900-file repo; this is ~3 s, and\n" " ~0.8 s on ripwire itself). Off by default because those default paths run in 0.64 s and\n" " 0.15 s. Memoized per (repo, HEAD sha) — a commit is immutable, so the cache cannot go\n" " stale — and the blob covers the WHOLE repo, so a second question on the same commit\n" " costs a cache load, and --whereis reuses whatever --doc-drift already built.\n" " LIMITS: it walks HEAD's own history, so a name that only ever lived on an unmerged\n" " branch reads as never here (use --whereis's tree scan for that); a deletion performed\n" " ONLY as a merge resolution is not seen (merge diffs are not walked); and evidence is a\n" " removed LINE carrying the name, so a name whose last removal was from a doc rather than\n" " code is reported with that doc as its site. A repo deeper than the walk bound reports\n" " truncated=\"1\" and answers unknown — never \"never\" — for anything it did not reach.\n" " --plan-lint=FILE check a PLAN/DESIGN file's structure — never its semantics\n" " the house PLAN/DESIGN format's STRUCTURE check — never semantics, that stays\n" " --doc-drift's job. FILE is read directly (like --from-trace's FILE, not through the\n" " crawled index), so it need not live inside any indexed root. GRAMMAR, narrow and\n" " opt-in on purpose (real house plans do not converge on one dialect): a card is\n" " exactly an H3 heading opening with a task id (\"T\" + 1-4 digits + up to 3 letters,\n" " e.g. T5 / T10 / T7b); a status ledger is exactly one heading (any level) whose text,\n" " stripped of a leading section mark, reads \"Status\" case-insensitively. A card's\n" " status is satisfied EITHER by a glyph on the LAST non-blank line of its own body OR\n" " by a ledger line naming its id (folded by digits — a bare card \"T7\" is also answered\n" " by a lettered ledger mention \"T7a\"/\"T7b\") that itself carries a glyph; the card's\n" " own body wins when it has one. A file showing NEITHER an H3 card NOR a ledger heading\n" " is reported dialect=\"0\" with nothing further checked — not a failing lint, since most\n" " real plans are exactly that file. Once dialect=\"1\": a card whose status did not\n" " resolve is status=\"missing\" with why=\"unlaunched\" (a ledger exists and never names\n" " this id or a lettered sub-task of it — the mid-wave \"this task was never launched\"\n" " catch), why=\"unresolved\" (the ledger names it with no glyph nearby), or\n" " why=\"no-glyph\" (no ledger exists in this document at all); an hourglass line whose\n" " git-blamed commit sits more than stale_commits= commits behind HEAD is stale=\"1\"\n" " (never claimed outside a git repo — see git=; blames whichever line the status\n" " resolved to, named by src=\"ledger\" when that is the ledger); a task id named in the\n" " ledger's own body with no matching card (same digit fold) is a ledger-orphan; a\n" " literal owed/OWED mention with no check-mark or cross anywhere LATER in the SAME\n" " document is undischarged (no cross-document tracking — a successor plan's discharge\n" " is invisible here, a stated limit, and this is substring matching with no semantic\n" " disambiguation: a doc that merely QUOTES the words reads the same as a real marker).\n" " Every gating row carries gating=\"1\"; NOT CHECKED AT ALL: whether a card's claims are\n" " true, any heading level other than three for a card, a ledger heading spelled any\n" " other way, and a document that uses card headings as plain labels with NO status\n" " mechanism anywhere (no ledger, no glyph) — every card there reads \"missing\" too, a\n" " known, disclosed gap. Exit 2 when dialect=\"1\" and gating is non-zero (unlike\n" " --doc-drift's always-0 report — nothing here has a legitimate \"dated on purpose\"\n" " reading); exit 0 clean or dialect=\"0\"; exit 1 only when FILE could not be read.\n" " --from-trace=FILE map a stack trace, sanitizer report or compiler error onto the indexed symbols\n" " map a stack trace / sanitizer report / compiler-error text ('-'=stdin) onto the indexed\n" " symbols: table-driven frame extraction (python / asan / node / compiler / generic),\n" " ranked INNERMOST-first over in-corpus frames only (out-of-corpus frames are listed and\n" " counted, never ranked). Each frame binds by its own NAME first (resolved_by=\"name\") and\n" " falls back to the def enclosing its line (resolved_by=\"line\") only when the name is\n" " absent/unknown/ambiguous — a trace older than the checkout therefore lands on the symbol\n" " it names, and a name-vs-line disagreement is disclosed as line_encloses=, never silently\n" " rebound. The counters close: in_corpus = suspects + merged + unresolved, with one\n" " row per file-matched frame no resolver could place. p= on a frame is the\n" " TRACE's own path:line; definition sites are the l= values. Emits the same bundle\n" " shape as --for — top suspects' signatures + the innermost in-corpus symbol's FULL body;\n" " composes with --token-budget, and HONORS --max-tokens=N (it bounds the bodies) — one of\n" " the six shapes that do, alongside the default map, --recall, --connect, --pr-context and\n" " --for --detail=N. --top-k is NOT read here (the frame order is the trace's, not a rank).\n" " TEST-TO-SOURCE HOP: when the innermost in-corpus frame is a TEST symbol — a failing-test\n" " trace names the assertion, not the subject — a block also serves the source\n" " symbols reached from it: via=\"callee\" is a real 1-hop call edge into non-test code,\n" " via=\"basename\" the naming-convention source pair (foo_test.go/foo.go) used only where no\n" " call edge landed there. Labelled heuristic=\"1\"; the frame map is unchanged and the\n" " innermost frame keeps rank 1, but the hop rows rank in ahead of the remaining\n" " frames and the top one's body is served beside it. A non-test trace is unaffected.\n" " Unparseable input refuses loudly (never an empty map).\n" " --run-trace=\"CMD\" run CMD, then map its failure output onto the code — the whole fix loop in one call\n" " EXEC-MODE --from-trace — the whole fix-loop entry in ONE call. Runs CMD under `sh -c`\n" " (the make trust model: your user, your environment, stdin=/dev/null, NO sandbox),\n" " captures stdout+stderr interleaved, and on a NON-ZERO exit serves the --from-trace\n" " bundle for the captured text (frames mapped innermost-first, the innermost in-corpus\n" " symbol's FULL body) plus a token-frugal cut of the error /\n" " frame-shaped output lines — shown=/relevant=/total= all disclosed, the cut never\n" " silent. The command's own exit code is ALWAYS disclosed on ; a command\n" " that exits 0 gets a minimal success record (exit, measured duration_ms, a disclosed\n" " tail of output) and NO bundle — nothing failed, so there is nothing to map. The \n" " record and captured lines are MEASURED (not deterministic, not claimed to be); the\n" " MAPPING of the captured text is byte-deterministic, and the document says which part\n" " is which. Composes with --token-budget (it bounds the bundle half, like --from-trace);\n" " --top-k / --max-tokens are not read here. ripwire's exit: 0 = the command succeeded;\n" " 4 = it failed or timed out (the report is on stdout either way); 1 = ripwire itself\n" " could not spawn it.\n" " --run-timeout=SECONDS cap --run-trace's command; default 600 s\n" " cap for --run-trace's command (default 600 s; always disclosed as timeout_s=). A\n" " command still running at the cap has its whole process group killed and is reported\n" " timed_out=\"1\" — an honest TIMEOUT, never an empty success. Modifies --run-trace\n" " only; refused loudly alone.\n" " --note-add=\"TARGET: text\" pin a note to a symbol or file, committed to .ripwire_notes and resurfaced later\n" " pin a field note (write-side memory) to TARGET — a SYMBOL in any spelling the read verbs\n" " resolve (bare name, file:name, Scope::name, the canonical id path::scope::name, or\n" " @FILE:LINE) or a FILE PATH — in the committed, sorted .ripwire_notes at the repo root.\n" " A symbol is CANONICALISED to its canonical id on write (the id --for/--expand key notes\n" " by) and the rewrite is echoed on stderr; a name matching SEVERAL definitions is refused\n" " naming each, and a name matching NONE is refused with a did-you-mean. A path target is\n" " written even when nothing indexed matches it (a note on a file you are about to add is\n" " legal), with a loud stderr warning that it is stored dangling.\n" " The date is git's committer clock (HEAD), not wall time, so the line is\n" " deterministic; prints the exact written line. Also STAMPS the writing repo's HEAD sha +\n" " branch onto the note (a \"done\"/\"fixed\" claim is then anchored to the commit it was\n" " true at) — a non-git root or an unresolvable HEAD writes the plain unstamped line rather\n" " than a wrong sha. MUTATES one file; single-root only. text with no causal/decision\n" " marker (\"because\"/\"chose\"/\"over\"/\"instead\"/etc.) gets a gentle stderr tip toward the\n" " decision shape — never a refusal, the add always proceeds.\n" " --notes list every field note, grouped by target\n" " list all field notes grouped by target; a target with no matching indexed symbol/file is\n" " flagged dangling=\"1\" (legal — surfaced nowhere, listed here). Read-only.\n" " Notes surface automatically as children on the\n" " symbols/files that --for and --expand emit (and the MCP for / fetch_body verbs); the\n" " sha/branch attrs appear only on notes stamped by this version, abbreviated (7 hex) for\n" " terseness — the full sha lives in .ripwire_notes on disk. An OLDER .ripwire_notes (3\n" " fields, pre-provenance) reads and surfaces exactly as before, with no sha/branch shown.\n" " Absent/empty file = zero effect.\n" " --pack-task=\"TASK\" the whole orientation dance in ONE call, under ONE shared budget\n" " the budget-shared task bundle: ONE call assembling, under ONE deterministic budget\n" " (default 6K tokens; --token-budget overrides), the whole orientation dance in FIXED order —\n" " (1) routed+anchored ranking, (2) top-K full bodies, (3) their 1-hop caller signatures,\n" " (4) their field notes, (5) tests_to_run for the top files, emitted in FIXED order\n" " ranking>bodies>callers>notes>tests. Each section holds a FIXED, up-front proportional quota\n" " of the budget (rank40/body30/caller15/note5/test10, percent); an under-spent section's\n" " leftover quota ROLLS FORWARD to the next section, so a small budget still zeroes a section\n" " eventually but never past its own fair share. Each section truncates rank-adaptively and\n" " the header reports EVERY truncation (no silent caps). A tiny budget degrades to\n" " ranking-only WITH the truncation note. Refuses loudly without a task string.\n" " --partition=N (with --pack-task, N=2..16) FAN OUT: one shared core plus N per-agent slices, not one bundle\n" " (with --pack-task, N=2..16) FAN-OUT form: instead of one bundle, emit ONE shared common core\n" " plus N per-agent slices, so N parallel agents stop re-deriving the same orientation. The\n" " task's ranked surface is carved along the call graph's own Louvain communities — a partition\n" " is a union of WHOLE modules (largest-first packing) so it reads coherently; when there are\n" " fewer modules than agents the widest is cut at its rank median and split=\"K\" says so. The\n" " core is exactly the anchors a plain --pack-task would have bodied. --token-budget then means\n" " ONE AGENT's budget (core + its partition), not the document's — total_bytes reports the rest.\n" " Each inner is byte-identical to a standalone call with that slice, so an orchestrator\n" " hands one bundle to one agent verbatim. LIMITS: overlap_mean/overlap_max are pairwise Jaccard\n" " over the ids each partition NAMES (window + bodies + their 1-hop neighbors) measured BEFORE\n" " budget trimming — a ceiling, not the trimmed truth; and on a task whose surface sits inside\n" " one module the split is a rank cut, not a semantic one (read split= and overlap_max before\n" " trusting the slices). Refuses loudly without --pack-task, or outside 2..16;\n" " --with-graph does not compose with it (N+1 bundles, no single graph — says so on stderr).\n" " --with-graph (with --for/--pack-task) append a small Mermaid flowchart of the bundle's top anchors\n" " (with --for/--pack-task) append a compact MERMAID flowchart of the bundle's top-N (<=8)\n" " ranked anchors + their 1-hop call edges among themselves — , right before . Reuses the --mermaid emitter's syntax.\n" " Costs tokens beyond the sigs it sits next to — worth it only when the reading agent\n" " renders mermaid natively. Off by default and purely additive: omitted, output is byte-identical.\n" " --export=cc.json[:FILE] export per-file metrics as CodeCharta cc.json for its 3D city view\n" " export per-file metrics (loc/symbols/cx/cognitive_cx/fan-in/fan-out/churn) as CodeCharta\n" " cc.json (apiVersion 1.3) — write FILE or redirect stdout; feeds a CodeCharta 3D city\n" " --batch=FILE run several lookups in one turn: FILE is newline-delimited `verb:arg` sub-queries\n" " one-turn context sweep: FILE ('-'=stdin) is newline-delimited `verb:arg` sub-queries\n" " (for/grep/callers/callees/impact/uses/mentions/analyze/lego/owners/cochange/exemplar,\n" " path_between:FROM,TO), answered in ONE deduped ; caps at 16 (over-cap = capped=1).\n" " THE SAME `verb:arg` line is accepted by the MCP `batch` verb's queries array\n" " (queries=[\"for:parse the config\",\"callers:escapeXml\"]), which also takes the\n" " {verb, ...args} object form — one grammar to learn, both front doors.\n\n" " self-diagnosis\n" " --doctor check the environment: stale binary, grammars, cache health, git, index identity\n" " environment self-check: binary-vs-PATH staleness, grammar tags.scm compile,\n" " cache-dir health, git reachability, tree-sitter version, INDEX IDENTITY, and TRACKED-BINARY\n" " staleness (a committed binary whose last commit is a git-history ANCESTOR of a\n" " same-directory/same-stem source's last commit — never mtime, which a fresh\n" " clone stamps at checkout time). \"Dependent source\" is a NAMING heuristic (same\n" " dir, same filename stem, e.g. tool <-> tool.cpp) — ripwire parses no build\n" " system, so a binary built from a differently-named or differently-located\n" " source is silently out of scope, neither flagged nor cleared. Single-root only.\n" " DIAGNOSTIC, not deterministic (env-dependent by design); exit 0 iff all ok,\n" " else 1. Root reports ; each child row\n" " carries the BOOLEAN ok=\"0|1\". passed= is the root's count (it was spelled\n" " ok= until the vocabulary pass, which collided with the child bool). A FAILING\n" " row (ok=\"0\") also carries hint=, the derived verdict (which of self=/which=\n" " is stale and the fix, which grammar(s) failed to compile, why the cache dir\n" " isn't writable, ...) — a passing row never carries hint=.\n" " The index-cache row states the INDEX-VERSION CONTRACT — cache_version=,\n" " parser_ver_lean=, parser_ver_rich=, artifact_arch= — the four numbers that decide\n" " whether a committed --index-out artifact is reusable at all, and which appear in\n" " no other output. lean=/rich= then say whether THIS binary can open the artifact\n" " this root would consume (source=\"auto\" its per-root blob, \"cache-flag\" the file\n" " you named, \"disabled\" under --no-cache), naming WHICH guard refused it: ok |\n" " absent | not-regular | unreadable | truncated | not-a-cache | format-version |\n" " parser-version | artifact-arch | checksum | corrupt-frame. BOTH families are\n" " reported because a team that commits only the lean artifact gets no warm hit on\n" " --for/--exemplar/--metrics/--uses. This is a FORMAT verdict about an artifact,\n" " never a freshness verdict about an index: every invocation re-validates each file,\n" " so a non-ok lean= costs speed, never correctness — the run cold-parses instead.\n" " Only an artifact you NAMED with --cache= and this binary cannot read is ok=\"0\"\n" " (a missing auto blob is the ordinary cold-start miss, not sickness).\n" " The root carries TWO shas, and they answer different questions: at= is the\n" " TREE's HEAD (+dirty) right now, built_from= is the commit THIS BINARY was\n" " compiled from (byte-identical to --version's own built_from=). They differ from\n" " the moment you commit until the next build — normal, so a mismatch is\n" " reported, never gated; a stale PATH copy shadowing a fresh build is the\n" " binary-path row's job and is decided on inode/mtime/size, not on this sha.\n" " The git-config-trust row reads the checkout's OWN core.fsmonitor as this process\n" " saw it at startup (fsmonitor=\"hook|builtin|off|unset\"): a HOOK-form value is a\n" " command git would run on every read-only call ripwire makes, so the process\n" " appends core.fsmonitor=false to git's GIT_CONFIG_COUNT override for its own git\n" " children (neutralised=\"1\", and a stderr line carrying git_harden=fsmonitor-hook);\n" " the boolean forms need no override (neutralised=\"0\"). Independently of this row,\n" " every git command ripwire runs carries git's no-optional-locks and core.fsmonitor=false\n" " so no monitor of either form runs for its read-only calls. Not a verdict on\n" " your setup — ok=\"1\" always; the neutralisation is the verdict.\n" " --agent=codex|claude (with --doctor) also inspect that agent's live integration with ripwire\n" " (with --doctor) also inspect that agent's LIVE CLI-first integration: PATH binary, exact\n" " installed-skill manifest parity, advisory hook executability, and the secondary\n" " mcp_servers.ripwire command/--mcp args. Read-only; emits fixed repair commands and\n" " never prints config contents or shell command lines. Other values refuse.\n" " --skipped say WHY a file is missing from the index, and which indexed files it cannot vouch for\n" " WHY the index does not contain a file, and which files it DOES contain but cannot\n" " vouch for. per DROPPED file: why=oversize (limit= names the ceiling\n" " — --max-file-size, or the fixed .json/.yaml config ceilings it does not raise),\n" " why=excluded (--exclude hit), why=unsupported-ext (ext= has no grammar in this build —\n" " the class that hides a whole LANGUAGE). per\n" " INDEXED-but-suspect file, nothing dropped: why=degraded-parse (the parse holds\n" " ERROR/MISSING nodes — a parser-state fact, never a syntax verdict) and/or\n" " why=minified-suspect (ws_freq under 0.070 over the leading 4KB). per\n" " unindexed extension — what the map header rolls up as unindexed=. per LANGUAGE this build DID extract from — the mirror of unindexed= (which\n" " names what it could NOT read at all); sorted files DESC then name ASC, absent means the\n" " language contributed nothing, never a printed zero; files= is a floor (a file with zero\n" " extracted symbols is not attributed to any language), symbols= is exact. The root states the\n" " ACCOUNTING INVARIANT indexed= + oversize= + excluded= = the enumerated candidate\n" " population, plus unsupported_ext=, excluded_dirs= (SUBTREES --exclude pruned: contents\n" " UNKNOWN, not zero), pruned_dirs= (SUBTREES this build always prunes by policy — the\n" " committed noise/vendor/build denylist and any dir holding a CMakeCache.txt — contents\n" " likewise UNKNOWN), degraded_parse=, minified_suspect=, extent_suspect_files= (files holding\n" " a definition whose extent/scope failed a containment check; the row says\n" " why=extent-suspect with extent_suspect_syms=), macro_blanked_files= (files whose\n" " symbols come from a re-parse with semicolon-less member macro invocations blanked;\n" " the row says why=macro-blanked with macro_blanked=), unmeasured= (indexed files this run\n" " never parsed) and the effective ceilings, so a zero-row report still states its\n" " bounds. rows_capped=\"1\" ⇒ rows are a sample of an exact count. Rows sort by path;\n" " composes with --max-file-size/--exclude and multi-root (rows carry the\n" "