diff --git a/.github/pargates-shard-weights.json b/.github/pargates-shard-weights.json index 95cea8aea..14b1fbb9a 100644 --- a/.github/pargates-shard-weights.json +++ b/.github/pargates-shard-weights.json @@ -243,6 +243,7 @@ "headsnapcachecheck.sh": 19.9, "historyoraclecheck.sh": 6.9, "hookcheck.sh": 78.0, +"hop1check.sh": 30.0, "hostilecheck.sh": 6.2, "hotspotsincecheck.sh": 8.6, "htmlcolorcheck.sh": 12.4, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db6bb20bb..fa53613af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,6 +12,8 @@ If you are about to write C++ here, §3 is the authoritative style-rules checkli - **What the tool does / which flag answers which question** → `README.md`, `docs/COMMANDS.md`, or `./build/ripwire --help` (the binary self-documents and is always current). - **How it is built internally** → `docs/ARCHITECTURE.md`. +- **Where code for a graph question goes** (a new input spelling, a new indirection shape, a new verb) → the + seeds → hops → walks layers, `docs/ARCHITECTURE.md` §6. - **How its claims are measured** → `docs/EVALS.md`. --- diff --git a/README.md b/README.md index da2b819ae..f3eacfb76 100644 --- a/README.md +++ b/README.md @@ -2120,9 +2120,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published ### In the tests
-709 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +711 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures -`test/regression.sh` names **709 gate scripts** and is the authoritative list; +`test/regression.sh` names **711 gate scripts** and is the authoritative list; `python3 test/pargates.py . ./build/ripwire -j 6` runs the same set in parallel. On top of them sit the contracts that do not fit a unit test: two runs byte-identical, warm output identical to cold, output that pipes clean through `xmllint --noout`, a sanitizer build with `-fno-sanitize-recover=all`, and a @@ -2878,7 +2878,7 @@ python3 test/pargates.py . ./build/ripwire -j 6 A new gate script must be added to `test/regression.sh` in the same change. The gate `test/manifestcheck.sh` enforces this rule. -Another gate derives the cap inventory. The tool has 276 compile-time caps and 7 ranking parameters. +Another gate derives the cap inventory. The tool has 278 compile-time caps and 7 ranking parameters. `docs/LIMITS.md` lists each cap, its value, and whether the file discloses a truncation when the cap fires, and `python3 docs/limits_build.py --check` proves that list against `src/`. `docs/TUNING.md` lists the measured cost of each cap. diff --git a/changelog.d/fc2-lego-external-parent.md b/changelog.d/fc2-lego-external-parent.md new file mode 100644 index 000000000..7f90f49d0 --- /dev/null +++ b/changelog.d/fc2-lego-external-parent.md @@ -0,0 +1,28 @@ +### Fixed — `--lego` stops listing an implementor whose own file imports its base from outside the tree + +`--lego` no longer lists a class as an implementor of an in-tree type when the class's own file imports its base from +outside the tree: `class Sub(ext.Base)`, `from ext import Base`, `import { Base } from 'lib'` / `lib.Base`, `use ext::Base; +impl Base for S`, Java/Kotlin `import org.ext.Base` / `extends org.ext.Base` (a package the tree does not declare). A star or +wildcard import, an import the tree cannot place, an import of the tree's own package name (Node self-reference, also on a +multi-root run; a single-root Python repo whose root is the package, by its directory or `[project] name`) and a same-file +declaration still keep the by-name row, and a clause dropped as outside adds no `implementors_floor=`. +For Rust the decision is inverted: a `use` head is decided OUTSIDE only when it is `std`/`core`/`alloc`/`proc_macro`/`test`, or +a registry or git dependency that some Cargo.toml of the tree declares (any dependency table, `[workspace.dependencies]` +included; the key and its `package =` name; a `path =` crate or a key `[patch]`/`[replace]` names is not one), also through +an alias the file itself spells (`use serde as sd;`, `use std::io::{Write as _};` moves no other head). A head that is a +crate of the tree keeps its row: a Cargo `[package]`/`[lib]` name from its tests/, examples/ and main.rs, a module of the +using file's OWN crate (a workspace crate directory of it spelled with `_` for its `-` directory; a name that is only another +member's directory or module, `crates/foo_core/` beside a registry `foo-core = "1"`, is no module of this file's crate), a dependency key with `path =` under the indexed root (only a path puts a key in the tree) +(`core2 = { package = "foo-core", path = "../core" }`, also a `[dependencies.core2]` table or a dotted key), a leading `::`, +an `extern crate foo_core as fc;` or `use foo_core as fc;` the file spells. EVERY other head — an unknown crate, an alias or +re-export this scan did not see (`pub extern crate foo_core as fc;` in lib.rs, `use fc::T` from another file), a rename only +`[patch]`, `[replace]` or `.cargo/config` names, a `path =` crate outside the indexed root — is undecided: it lists no row +and the interface carries `implementors_floor=`, never a confident `implementors="0"`. A tree with no Cargo.toml decides +nothing outside but `std` and its siblings. A key that two members of the tree mean differently (in the tree for one, a registry +crate for another, also when it spells a package name of the tree) and a key a `.cargo/config(.toml)` `[patch]` names — read in +each manifest's directory and above it, within the indexed root, so a config ABOVE the indexed root is not read — are undecided +too, as is a renamed key with no `path =` (`fc = { package = "foo-core", version = "1" }`: a registry crate, or a `[patch]`'s), an +inline table that does not close on its line, and a key one manifest writes as a registry crate in one dependency table and as a +`path =` crate in another; a Cargo file the run cannot read decides nothing outside. This does NOT +cover a C++ `ext::Base` or `using ext::Base`, a C# `using`, a Swift `import`, a PHP `use`, a JS `require` destructure or a +Rust `impl ext::Base for S` with no `use`: those rows are still listed by name. Gate: `test/legotransitivecheck.sh` (XP). diff --git a/changelog.d/fc3-js-written-binding.md b/changelog.d/fc3-js-written-binding.md new file mode 100644 index 000000000..4250eb4a1 --- /dev/null +++ b/changelog.d/fc3-js-written-binding.md @@ -0,0 +1,12 @@ +### Fixed — JS/TS calls through a name written after its declaration are no longer read as the declared class + +`let { Eng, Foo } = require('a'); Eng = Foo; new Eng()` builds a `Foo`, so a BARE call by a name written in its scope is +unresolved (`unresolved=`), never its declared target nor a same-spelled class. A MEMBER call through such a root (`Eng.make()`, +`tank.spill()`) is not receiver-typed: the root's spelling does not name a class, so the call keeps only the by-name hedge +(`via="name"`, every candidate), with or without a declaration (`let Eng;`, an undeclared global). A write whose value keeps the +name (`Environment = require('x').Environment` over `var Environment;`) and a stated constructor (`let app; app = new Hono()`) are +unchanged. Residual, not fixed: a member call whose receiver is a typed chain the tool does not follow (kysely +`ctx.db.selectFrom('person').selectAll().limit(100)`) still falls to the by-name ladder, and the merged caller row carries the +sites of a bare imported `limit(…)` call in the same file unhedged. Refusing every member call a free function (which JS/TS allow +through an object property) changed about 9,000 census rows on six in-sample repos (hono, kysely, nunjucks, fastify, commander, koa), +so it is left to a later card. Gate: `test/falseedgecheck.sh` arm (S). diff --git a/changelog.d/honest-lex-grep-pattern-meta.md b/changelog.d/honest-lex-grep-pattern-meta.md new file mode 100644 index 000000000..1422775fb --- /dev/null +++ b/changelog.d/honest-lex-grep-pattern-meta.md @@ -0,0 +1,11 @@ +### Fixed — `--grep` says when a literal pattern reads as a regex, and prints one row per line + +A literal `--grep='compose|HTTPException'` answered `hits="0" complete="1"` with `next=` the conceptual lens, while 31 hono +files hold one of the names; `--grep='CHANGELOG\.md'` found the one line spelling the backslash. The root now carries +`pattern_meta="…"` (the regex-shaped pieces: `|` between words, a backslash escape, `.*`/`.+` inside the pattern, a leading `^` +or trailing `$`, a `[^…]`/`[a-z]` class; a lone `.` as in `CHANGELOG.md` is not one) and `next=` is `--regex=` +whatever the hit count (a basic-grep `a\|b` is offered as `a|b`; nothing is offered when `--regex` would refuse it). The literal +answer itself, and its `complete=`, are unchanged: the scan still matched the text as written. Folded in: a line holding the +pattern twice used to print as two `` rows and count twice; it is now one row with `occ="2"`, and `hits=`, `shown=` and the +window count lines, not occurrences (the unindexed list too). The MCP `grep` verb carries `pattern_meta`, the same `next` and +`occ` per row. Gate: `test/honestlexcheck.sh` (B, M arms). diff --git a/changelog.d/honest-lex-whereis-lexical-defs.md b/changelog.d/honest-lex-whereis-lexical-defs.md new file mode 100644 index 000000000..9637a03f7 --- /dev/null +++ b/changelog.d/honest-lex-whereis-lexical-defs.md @@ -0,0 +1,16 @@ +### Fixed — `--whereis` no longer labels comment, prose and string lines `kind="def"` on the lexical fallback + +When the parsed index has no definition of the name (or the checkout drifted from it), HEAD rows are labelled by a lexical shape +test (`head_labels="lexical"`). That test took a declaration word anywhere earlier on the line, so `// a class published its +name`, a help string `"struct (like --lego)"`, a markdown line and every `struct T *data` local read `kind="def"`: 241 false +definition rows over 30 cells (10 words with no definition × this repo, hono, tmux). Now the word must sit directly before the +name (qualifiers, `*`/`&`, template or generic arguments, a qualification chain, a Go receiver, one ALL-CAPS export macro after +`class`/`struct` may sit between; `template<…> RetType NAME(` too); a line that opens a comment (`//`, `#`, `;`, `-- `, ` * `, +`"""`…), a name after a trailing comment opener and a name inside a `"…"`/`` `…` `` literal are never definitions; `#define NAME` +(object-like as well as function-like) is; a typedef defines only its own last declarator; `struct T` followed by a declarator, +`*`, `)` or `,` uses the tag and defines nothing; `using std::x;` and `using namespace x` define nothing (`using X = …` does), nor does Rust's `impl Trait for X`. The +root gains `head_defs="0"` when the lexical labels stand and the index defines no symbol of the name at all. Still a floor in both +directions, and named as one: prose whose declaration word sits right before the name reads as a definition, and an unusual +declarator (a wrapped signature, an enumerator, a field) reads as a reference. The parse chooser for other refs' blobs keeps the +old, looser reading as well, so no blob that used to be parsed goes unparsed. Control: 12 real definitions forced onto the lexical +path (C, C++, TS, Python, Rust, Go, Ruby) keep their `kind="def"` rows. Gate: `test/honestlexcheck.sh` (A, H arms). diff --git a/changelog.d/hop1-backward-hop-step.md b/changelog.d/hop1-backward-hop-step.md new file mode 100644 index 000000000..4abe156a2 --- /dev/null +++ b/changelog.d/hop1-backward-hop-step.md @@ -0,0 +1,19 @@ +### Added — one backward hop step for `--path` and `--impact` (slot and declined callers) + +`--callers=X` already names, one hop per call, who MAY call X without a graph edge: a `` row's +`called_by=` (a call through a struct field X is stored in) and a `` row (a declined call whose candidate list holds +X). The answers that join hops now read the same rows, from one provider (`src/backhops.h`): + +- `--path`'s hedged `` crosses a slot hop (`via="slot"`, `via="slot-name"` for a field-name-only join) while the + join names at most 16 calling functions (`slot_callers=`), and `slot_fns=` says how many functions the field holds + (not capped: a wide table such as tmux's 92-entry `cmd_entry.exec` is still crossed, and the row says how wide it is); a + chain whose every crossing is a slot may take three crossings, any other chain keeps two. On tmux, `--path=cmdq_fire_command,window_copy_cmd_cancel` now prints the + three-field-call chain (`cmd_entry.exec`, `window_mode.command`, the command table's `.f`); before, no chain. +- A `reachable="0"` path answer whose search met gaps and whose `to=` has such a hop ends with `next="--callers="` + (CLI and MCP `path_between`) instead of the gap rows' bodies. +- `--impact` (XML, `--json`, columnar count, MCP `impact`) adds `reaches_hedged=N` beside `reaches=`: the functions that + may reach the symbol through one or two calls through struct fields, with the two-hop ones listed as + `` (16 shown, no width bound on the d=1 set; `next=` pages the callers of the first unshown row's `calls=`, not every hidden row). Never counted in + `reaches=`, `by_depth=` or the radius counts. + +Every crossing stays a hedge: `proven="0"`, `via=` named, its width beside it (a slot crossing's `slot_callers=` is bounded at 16, its `slot_fns=` is printed, not capped), and never in `reachable=`/`hops=`/`reaches=`. diff --git a/changelog.d/hop1-typed-receiver-not-builtin.md b/changelog.d/hop1-typed-receiver-not-builtin.md new file mode 100644 index 000000000..e0e416398 --- /dev/null +++ b/changelog.d/hop1-typed-receiver-not-builtin.md @@ -0,0 +1,8 @@ +### Fixed — a declined call on a typed in-tree receiver is no longer read as the builtin method its name spells + +The bridge and the `` frontier skipped every declined call spelled like a builtin type's method, so hono's +`this.router.match( method, path )` (`router!: Router`, an in-tree interface with eight implementations) was dropped as +`String.prototype.match`. The skip now lifts for one field of `this`/`self` or of a parameter whose class the source +states, when every same-named class types that field as an in-tree class and the call's candidate list is complete (2 +to 16). A `.match` on a string parameter, an un-annotated local, a field typed `string`, or a field another same-named +class types outside the tree stays skipped. diff --git a/changelog.d/ind4b-by-name-hedge-import-rule.md b/changelog.d/ind4b-by-name-hedge-import-rule.md new file mode 100644 index 000000000..d457bc4f8 --- /dev/null +++ b/changelog.d/ind4b-by-name-hedge-import-rule.md @@ -0,0 +1,49 @@ +### Changed — by-name hedges: a free namesake needs an import, an override is a way in + + +- A by-name hedge (`` `why="name"`, a `` `via="namesake"` crossing) to a namesake with no class (a free + function, or a JavaScript/TypeScript method, whose class the extractor does not record) now needs the caller's file to be, + or to import, the namesake's file (a direct import edge). A namesake file the search merely touched no longer counts. For + C, C++ and Objective-C the rule follows the transitive `#include` closure, but no probe of ours produced a by-name hedge + to a C-family free function: those calls are declined, not bound by name. On the 880-pair seed-18 `--path` draw over 11 in-sample repos this + withholds the 2 shown by-name sites a hand-check had rejected (koa's `delegate( proto, 'response' ).method( … )` reaching + `request.js`'s `get method`, and a bundled Bootstrap `this.init( … )` reaching nunjucks' `Parser.init`). The one accepted + site, flake8's `report()`, stays. 16 rows that already showed a value or declined site keep it and now also count a by-name way in. +- A class method namesake is now also a way in when its class derives from the class of the definition the call was bound + to, at any depth, and every step of that derivation is bound in its own file: the base name a class writes resolves, + through that file's own declaration or its import of that name (a package re-export, and for Python a `src/` layout + import that resolves by package name, count), to the next class. This is override dispatch. A base clause that names a + different same-named class (one elsewhere in the tree, or an external one: `class Sub(ext.Base)`, `from ext import Base`) + does not count, however the file's other imports reach the parent's file; neither does a base no import binds. On the + same draw this shows 6 sites, and a hand-check read all 6 as correct: textual's `driver.start_application_mode()` / + `stop_application_mode()` → `LinuxDriver`, `widget.render_line( y )` → `TextArea`, and `node.compose()` → three `App` + subclasses in examples. Like every by-name hedge, each is a hedge and never a proven edge. What stays withheld: a base + the binding does not read (a dotted `a.b.Base`), and a Python base name that has any binding other than its import in + the class statement's own scope chain (its class body or function, the enclosing functions, then the module; an + enclosing class body is skipped, as Python skips it): an assignment of any form (`Base = Other`, `Base: type = Other`), + a def, class, loop, `with` or `except` target, a parameter, a `global` or `nonlocal` (also in a def that starts the + file), a `match` capture (`case Base:`, `case P as Base`, `*Base`), a PEP 695 `type Base = …` or a class or def type + parameter (`class Sub[Base](Base)`), a second import, an import under another name (`from m import Other as Base`, which + binds m's `Other`, never `Base`), a `try: + from a import Base / except ImportError: Base = None` fallback, or a star import that could bind it (one from a module + outside the tree, or from a module that defines, imports or star-imports the name), wherever in the scope it sits and + in either order. A qualified base (`q.Base`) is also withheld when another function or class imports `q` as a module + (`import a.b as q`), since the import tables are per file; this over-hedges the same import repeated in two scopes, + and a star import from an in-tree module whose `__all__` omits the name. `from pkg import other as a` with `a.Base` now + reads `other` (the member's own name), not `a`; a dotted `import pkg.x` binds the root `pkg`, which no table names, so + `pkg.Base` is withheld (this also over-hedges `import pkg.x as pkg`). `from pkg import a` (aliased or not) with `a.Base` + reads the submodule `a` only when the package's `__init__` does not bind `a` itself (an import, def, class or assignment of + it, a star import that could bind it, or a module `__getattr__`). A re-export chain (`from pkg.x import Base`, where the + package or a middle module re-exports the name) is followed only through files that bind the name by ONE plain import, + at every hop: a hop that renames it (`from pkg.x.a import Other as Base`), reassigns it afterwards (`Base = Other`, `Base +=`, + a `for`, `with` or walrus target, a def or class of it, a `global Base` in any def), star-imports after it, or has no + record of it ends the chain, and the site is withheld. A hop file that calls `globals()` or `setattr(...)` anywhere, or + defines a module `__getattr__`, counts as unknown for every name (a dynamic write no table sees; this over-hedges any such + file, and `sys.modules[__name__].a = x`, which is not a call, stays a floor). The import must be the name's one binding; + otherwise the site is withheld, never shown. Not read, and not detectable statically: a rebinding made from outside + the file (`a.Base = Other` on an imported module). + The chain's endpoint is held to the same standard: the class it finds is the name's meaning only when its own `class` + statement is the name's one module-scope binding in that file and no star import there could bind the name, so a later + `Base = Other`, a `global Base` in a def, a second def or class of it, or a trailing `from m import *` now withholds the + site (this over-hedges a class defined in both arms of an `if`/`try`). Residual: an endpoint file's `globals()` or + `setattr(...)` write is not read there (a hop file's is), so that shape may still be shown. diff --git a/changelog.d/mcp-fold.md b/changelog.d/mcp-fold.md new file mode 100644 index 000000000..6c5a71b3c --- /dev/null +++ b/changelog.d/mcp-fold.md @@ -0,0 +1,12 @@ +### Changed — MCP `quality_delta` and `edit_check` fold their bookkeeping rows like the CLI (`show_stale`) + +- MCP `quality_delta`: a regression-free answer with stale acks now drops its `sa` rows and carries `stale_by_kind` (the + stale count split by ack kind, the same string the CLI prints) and `stale_next` (`show_stale=true`), when that saves at + least a quarter of the bytes. Every count stays on the answer. The new boolean argument `show_stale=true` (the CLI + `--show-stale` twin) returns the full document unchanged. Measured on ripwire's own tree: 15,015 B to 567 B; on the eight + other sweep repos the answer has no stale acks and is byte-identical. +- MCP `edit_check`: an unchanged contract at git HEAD with no flagged caller answers the verdict and `rows_next=` (the + MCP call, `edit_check symbol=SYM limit=N`, that lists the callers), as `--edit-check` already does; `callers=` keeps the count. Median 1.72x smaller on the + repos where it applies (1.62x to 3.46x). A dirty tree, a flagged caller, `new_body` previews and an explicit `limit` or + `offset` still list every row. +- It does NOT drop a regression row, a flagged caller or any count: the folded rows are one argument away. diff --git a/changelog.d/versions.md b/changelog.d/versions.md index 889f90c2f..da0136758 100644 --- a/changelog.d/versions.md +++ b/changelog.d/versions.md @@ -1,9 +1,10 @@ ### Changed — the versions this change moves, stated once -`kParserVer` 206 → 224 (file tag 225): the Rust written-type receivers carried 202 (203) on their branch and the struct-field +`kParserVer` 206 → 226 (file tag 227): the Rust written-type receivers carried 202 (203) on their branch and the struct-field slot join 204 (205), and its fix rounds changed what 204 extracts, so a cache from that branch would replay the older slot -rows; the interface-dispatch records carried 210 and 212 on theirs; 224 sits above every live branch and pull-request number -(212 to 223). A cache written by 0.6.8 is re-parsed once. `kCacheVersion` 36 → 39: a Rust binding typed only through a trait -(`&dyn T`, `impl T`, a bound) and a Rust `impl T for X` block are new binding records, so a cache written before them is -rejected and rebuilt (the branch that added them used 37 and 38). The quality-snapshot scheme stays 22. The session legend -dictionary is `dictv=a612d2cfd7867548 entries=923`. +rows; the interface-dispatch records carried 210 and 212 on theirs; the JS/TS written-binding records carried 218 and the +Python rebind-withholding records a 224 placeholder on theirs; 226 sits above every live branch and pull-request number +(212 to 225). A cache written by 0.6.8 is re-parsed once. `kCacheVersion` 36 → 40: a Rust binding typed only through a trait +(`&dyn T`, `impl T`, a bound), a Rust `impl T for X` block and a JS/TS binding written after its declaration are new binding +records, so a cache written before them is rejected and rebuilt (the branches that added them used 37, 38 and 39). The +quality-snapshot scheme stays 22. The session legend dictionary is `dictv=2259c5025ac90271 entries=930`. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index f421bda02..a7aa2b8c7 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -733,3 +733,61 @@ The same lesson generalizes, and it is the reason the gate discipline in `CONTRI the way it is: **a gate that cannot observe what it asserts is worse than no gate**, because it reports confidence. Guard your probes — assert the thing you are about to search for actually exists, then assert the property. + +--- + +## 6. Seeds → hops → walks + +Every graph question ripwire answers about code (`--callers`, `--callees`, `--impact`, `--path`, `--connect`, +`--situ`, `--test-gate`, and the caller rows of `--for`) is built from three layers. A change to one layer should +not need a change to the other two. + +**1. Seeds: where a walk starts.** A seed resolver turns what the user typed into an ordered list of candidate +definitions and says how it got there. Input spellings: a bare name, `file:name`, `Scope::name`, `Class.method`, +the canonical id the tool prints, `@FILE:LINE`, a `*` glob, and the changed symbols of a diff. All of them go through +`resolveAllByNameQualified` (`graph.h`) or one of its tiers. The diff-hunk producer (`diffhunks.h`, `situ.h`) maps +hunks to the symbols whose bodies they touch, and falls back to the whole file when no body holds a hunk +(contributed by @llvm-x86 in #423). Matches come back in index order (ascending id); a verb that needs ONE +focus takes it from `resolveFocus` (`graph.h`): a code definition before a docs heading or a config key, the lowest id +among those, and a bodyless C/C++ declaration yields to the same-scope definition with a body. A seed resolver never +picks silently. `defs=`, `unproven_defs=`, `from_defs=`/`to_defs=` and the `` table disclose the candidates. +The rule: **any spelling the output prints is a spelling the input accepts.** + +**2. Hops: which edges leave a node, and how sure each one is.** A hop provider takes a node, a direction and a +set of hop kinds. It returns edges, and each edge carries exactly one certainty label: +- *proven*: the index pins the edge (a uniquely resolved call, an ES import binding, a SCIP pin, an FFI binding, + a single-implementor interface call `via="impl"`); +- *hedged*: a walk may follow the edge but must print its kind and width. Examples: a split arm `prov="split"`, a + name-only bind `via="name"`, a struct-field slot `via="slot"`/`"slot-name"` with its caller count, a stored + function value, a closure, a declined call whose candidate list is short enough to name; +- *declined*: a candidate set the walk counts but does not cross (too wide, gated as a builtin or standard member, + or stored without a list). +Proven edges live in the graph CSR (`outProv`, `outNameOnly`). Hedged and declined hops come from side indexes: +the value-ref and slot index (`valuerefindex.h`), declined call sites (`declinedsites.h`), interface dispatch +(`ifacedispatch.h`), and the backward hop provider (`backhops.h`). The provider reports a width. The walk decides +the bound. + +**3. Walks: how far to go and what to print.** A walk is a policy over seeds and hops. It sets a depth, a width +bound, how many hedged crossings it allows, and the rows it prints: +- `--callers`/`--callees` go one hop; +- `--impact` is the proven caller ring plus a hedged ring (`reaches_hedged=`); +- `--path` runs a shortest proven chain, then a hedged bridge, then gaps and `near_sink`, then a `next=` + continuation; +- `--connect` joins several terminals; +- `--situ`/`--test-gate` walk callers from the changed symbols. + +A walk never relabels a hop. A hedged hop is never counted in `reaches=`, `reachable=` or `hops=`. A bound that +cut something is printed (`capped=`, `total=`, `next=`). It is never reported as "not reached". + +**One coupling, stated on purpose.** When a name has several definitions, the *pick* can depend on the walk: +the right `Loop` is the one that reaches the other endpoint. The seed layer therefore returns the ordered +candidates. A walk that must choose uses the one shared terminal scorer (`scoreTerminalCandidates`, `graph.h`), +with `resolveFocus`'s order as the tie-break, and it prints the alternatives it did not take. + +**Adding to a layer.** +- A new spelling goes in the seed resolver. The verbs pick it up from there. +- A new indirection shape (a function table, a registry, a factory) is a new hop kind in the provider, with + one label and one width. Every walk then sees it. +- A new question is a new walk policy over the existing seeds and hops. +- Two copies of one rule in two verbs (two endpoint pickers, two hop vocabularies) are a fold candidate, the same + as the ingest feature families in CONTRIBUTING. diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index b3c432042..84e4711cd 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -9,7 +9,7 @@ python3 docs/docs_commands_build.py --bin build/ripwire The flag surface below is read from `ripwire --help`, so it cannot disagree with the shipped binary. `test/docscommandscheck.sh` fails if it ever does — in either direction. -Sample output is lifted from a real recorded run (`docs/captures/COMMANDS_showcase_2026-10-23.md`), trimmed to the first few lines and +Sample output is lifted from a real recorded run (`docs/captures/COMMANDS_showcase_2026-10-24.md`), trimmed to the first few lines and scrubbed of local paths. It is illustrative, not a golden: run the command yourself for the current shape. @@ -70,8 +70,8 @@ _Same map, capped to the 5 highest-ranked symbols._ $ ./build/ripwire . --top-k=5 - - + + @@ -106,7 +106,7 @@ _The same capped map without its inventory tier: --top-k=5 leaves files out, so $ ./build/ripwire . --top-k=5 --no-inventory - + @@ -140,7 +140,7 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ $ ./build/ripwire . --max-tokens=1500 - + @@ -170,7 +170,7 @@ _GATE form: exit 3 if the map's own est_tokens exceeds the budget (over-budget f ``` $ ./build/ripwire . --token-budget=100 - + ``` **Shaped by:** `--top-k`, `--max-tokens`, `--for`, `--sections`, `--recall`, `--handoff`, `--pr-context`, `--from-trace` @@ -195,7 +195,7 @@ _The honest half of the contract: a task with no ripwire-shaped evidence ABSTAIN $ ./build/ripwire . --help-task="write a cheerful release announcement" - + ``` @@ -215,14 +215,14 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -252,14 +252,14 @@ _Restore the / sections the --for bundle collapses to a counted s ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --sections=lego,compose - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() ... [21 more line(s); run it to see the whole thing] @@ -279,14 +279,14 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -314,14 +314,14 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() ... [21 more line(s); run it to see the whole thing] @@ -346,17 +346,18 @@ _Same query with routing forced OFF (plain subtoken+body BM25) — contrast with ``` $ ./build/ripwire . --for="rankGraphTeleport" --no-route - + - - - + + + uniform-teleport PageRank (the defaultinline RankedGraph rankGraph( const Graph& g, float alpha = 0.85f ) - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quali…inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) inline ChurnRanking churnRankedGraph( const MainDispatch& d ) - -... [21 more line(s); run it to see the whole thing] + +L10 (2026-09-04): the old wording claimed "the same corpus ranked by pagerank orders differently…inline constexpr const char* kChurnRankLegend = "<!-- rank_by=churn: k= is PageRank re-run with the teleport BIASED by git CHANGE-FREQUENCY over window= " "(a c… +... [20 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -377,14 +378,14 @@ _Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothin ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --adaptive - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() ... [21 more line(s); run it to see the whole thing] @@ -408,7 +409,7 @@ _Same task with the anchor disabled — the contrast the flag exists for._ ``` $ ./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25" --no-mention-boost - @@ -438,15 +439,15 @@ _The same task with doc-mention surfacing OFF — the contrast the flag exists f ``` $ ./build/ripwire . --for="quality delta acks ledger rubber stamp" --no-doc-mention - - + + - - + + L2 stale acks in the range form. A changed-files answer holds no whole target tree to check the …std::vector<rw::quality::StaleAck> refPairStaleAcks( const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const RefPairDelta& refs ) -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) -inline const char* kAcksFile = ".ripwire_quality_acks" -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +inline std::string staleByKindValue( const std::vector<StaleAck>& staleAcks ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at ... [22 more line(s); run it to see the whole thing] ``` @@ -468,7 +469,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -499,7 +500,7 @@ _The repo's best-in-class instance to imitate before writing new code (picked by $ ./build/ripwire . --exemplar="format byte sizes for humans" - + /aux/kbcorpus --recall="field affinity cache line data layout which fields are read together" --top-k=3 --max-tokens=1200 -ripwire recall — "field affinity cache line data layout which fields are read together" — 2 relevant of 2 document files, best-first — total=2 shown=1 capped=1 truncated=1 max_tokens=1200 share_bytes=2041 est_tokens=957 +ripwire recall — "field affinity cache line data layout which fields are read together" — 2 relevant of 2 document files, best-first — total=2 shown=1 capped=1 truncated=1 max_tokens=1200 share_bytes=2040 est_tokens=952 -━━ commands.md (relevance 9.990) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 587368 B; lines="3824-3828"; dropped_by_budget=163] [truncated: 1865 of 7738 bytes] -### `--field-affinity[=STRUCT]` +━━ commands.md (relevance 10.880) ━━ [sections: 1 of 165 selected (192 in doc), section-granular; whole doc 594792 B; lines="555-580"; dropped_by_budget=164] [truncated: 1854 of 4256 bytes] +#### Pattern: a directory of dumped tool output as a knowledge base -**Answers:** find fields that are read together but declared far apart — the cache-locality lens the CACHE-LOCALITY lens: which fields are READ TOGETHER but declared FAR APART. +**Answers:** can `--recall` serve as a zero-setup knowledge base over dumped tool output — a +`git log`, an API response dump, a fetched doc, ` --help` text — sitting in a scratch +directory, instead of a source repo? -Builds a static field CO-ACCESS affinity graph (one observation per indexed C-family function body) and diffs it against the DECLARED field order and 64-byte cache-line geometry, reusing --layout's LP64 offset model. Bare = every aggregate in the repo, ranked by separation cost; =STRUCT narrows the … [line truncated: 1355 more bytes on this line] +Yes, unmodified. `--recall` never distinguishes "a codebase" from any other directory it can +walk: point it at the scratch dir and query it. No index to build, no daemon, no mutable store +between runs — the whole cost is one cold parse. Two conditions decide whether it works at all, +and both are yours, because the file you write is the only thing that sets them: -(capped: 1 of 2 relevant document files omitted — raise --max-tokens/budget_tokens or narrow the query for 1 more (~2548-byte budget)) +... [17 more line(s); run it to see the whole thing] ``` ### `--tree` @@ -622,7 +628,7 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - + @@ -726,8 +732,8 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable $ ./build/ripwire . --no-stable --top-k=3 - - + + @@ -760,7 +766,7 @@ $ ./build/ripwire . --around=rankGraphTeleport - + @@ -812,18 +818,18 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - - + + - - - - - - + + + + + + ``` @@ -847,10 +853,10 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - + - - + + @@ -881,16 +887,16 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - + - + - + - + @@ -918,19 +924,19 @@ _Names referenced but never defined in-corpus (stdlib/third-party surface). The ``` $ ./build/ripwire . --external-surface - - - - - - - + + + + + + + - - - - - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -953,12 +959,12 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - + - + ``` @@ -975,10 +981,10 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + - + @@ -1001,7 +1007,7 @@ _Transitive blast radius — everything that reaches SYM. NOW carries shown/capp ``` $ ./build/ripwire . --impact=rankGraphTeleport - @@ -1085,7 +1091,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - @@ -1136,25 +1142,14 @@ Backtested on two histories (EVALS.md): a named partner is edited within the nex **Try it** -_Mid-task situational report for the current git diff — recorded against a DIRTY tree (contrast with the sandbox run below)._ +_Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below)._ ``` $ ./build/ripwire . --situ -ripwire situational-awareness — 1 changed file(s), 115 symbols in them +ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: f96bd7bdd+dirty - decl/def partners (5) not_dependents=1 (showing 4 of 5 files — shown=4 total=5 capped=1) — symbols declared there and defined here, or the reverse (header/impl, stub, partial class); NOT transitive dependents, so they are absent from the list below: - test/archmetricsfix/src/core/math.h (1 shared symbols) - test/builtinbindfix/mono/rb/c/report.rb (1 shared symbols) - test/rubyargfix/lib/script.rb (1 shared symbols) - test/rubyrecvfix/lib/script.rb (1 shared symbols) - [1] blast radius: 3 symbols across 1 files transitively depend on these changes - counts_floor=1 graph_ambiguous=2882 graph_unresolved=14926 graph_unindexed=269 (files no grammar in this build could read at all) (the map header's own gauges) — every count above is a FLOOR, never a total: call edges are name-based, so dynamic dispatch, callbacks and macros can be missing … [line truncated: 45 more bytes on this line] - bench/routing_ab_report.py (3 dependent symbols) - [2] tests to run (1) order=evidence: [changed] you edited it, [partner] named after a changed file, then hops asc (1 = direct); "(n): a, b" = n runner-less files sharing that evidence; a (run: …) is relative to root: - test/showcase_capture.py [changed] (run: not derivable) - script_gates_unmodelled=775 — test/*.sh gates never appear above: script-to-binary edges are not call edges (a path count) -... [10 more line(s); run it to see the whole thing] +at: c97f6f91c + (0 changed files — working tree is clean, nothing to analyze) ``` **Shaped by:** `--affected`, `--test-gate`, `--legend`, `--limit` @@ -1167,25 +1162,25 @@ Empty diff is fine — the packet still carries branch/sha + heuristics. Compose **Try it** -_The continuation packet for the NEXT session: disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a DIRTY tree._ +_The continuation packet for the NEXT session: disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a CLEAN tree._ ``` $ ./build/ripwire . --handoff - - - - - - - - - - - - - - -... [17 more line(s); run it to see the whole thing] + + + + + + + + + + + + +... [1 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -1202,18 +1197,12 @@ exit 4 if either is non-empty agent self-check before a PR (pair with --quality- **Try it** -_Pre-PR gate recorded against a DIRTY tree, so the obligations below are the working copy's real ones — the recorded exit code says which way it went._ +_Pre-PR gate on a CLEAN tree: no obligations, exit 0._ ``` $ ./build/ripwire . --test-gate - - - - - - - + + ``` @@ -1237,12 +1226,11 @@ _Literal trigram-indexed search. Each hit carries its MATCHED line as the ``` $ ./build/ripwire . --grep=DISCLOSE - + - + - + @@ -1540,7 +1529,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1575,8 +1564,8 @@ $ ./build/ripwire . --pack-signatures --top-k=10 - - + + @@ -1606,7 +1595,7 @@ $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 signature rows. est_tokens=: price as emitted (an upper bound under compact). root=: p= relative to it. --> - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1633,11 +1622,11 @@ _NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map ride ``` $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport - + - + + - - + + - + @@ -1809,19 +1798,19 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - - - + + + + + - - - - + + + + + - - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1846,18 +1835,18 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - - - - + + + + + - + - - - - + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1874,17 +1863,17 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + - + - + ... [17 more line(s); run it to see the whole thing] @@ -1905,10 +1894,10 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - - - - + + + + @@ -1938,7 +1927,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1973,7 +1962,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -1984,7 +1973,7 @@ $ ./build/ripwire . --ensemble --limit=8 - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2004,17 +1993,17 @@ _THE single wide-angle quality read: six families in one pass, an eligible/ranke ``` $ ./build/ripwire . --quality-panel - - - + + - + ... [17 more line(s); run it to see the whole thing] @@ -2041,13 +2030,13 @@ _The local-reasoning lens: to understand this symbol, how much must you know tha ``` $ ./build/ripwire . --context-ratio --limit=8 - - + + + - - - + + ... [23 more line(s); run it to see the whole thing] ``` @@ -2072,7 +2061,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2107,10 +2096,10 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - - + + + + @@ -2144,8 +2133,8 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - - + + @@ -2153,10 +2142,10 @@ $ ./build/ripwire . --lint --naming-locals - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2179,11 +2168,11 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - + - + @@ -2212,16 +2201,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - + + - - - + + + - + - ... [22 more line(s); run it to see the whole thing] ``` @@ -2244,16 +2233,16 @@ _Only pairs whose co-change RECURS in 2+ sub-windows of the mined window (sub_wi ``` $ ./build/ripwire . --cochange --cochange-recur=2 - - + - + + - - - + + + ... [22 more line(s); run it to see the whole thing] ``` @@ -2270,10 +2259,10 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + - + @@ -2299,19 +2288,19 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - - - - - - - - - - - - - + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2337,7 +2326,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2367,7 +2356,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2393,7 +2382,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2420,8 +2409,8 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - - + + @@ -2429,10 +2418,10 @@ $ ./build/ripwire . --lint - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2529,8 +2518,8 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + @@ -2538,10 +2527,10 @@ $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2643,15 +2632,15 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + - + - + @@ -2674,7 +2663,7 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - ``` @@ -2699,19 +2688,19 @@ _Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels ``` $ ./build/ripwire . --zoom - - - - + + - - + + - + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2736,16 +2725,16 @@ $ ./build/ripwire . --report # ripwire architecture report -3315 files · 30881 symbols · 29413 edges · 3553 modules (14301 call-graph isolated) +3589 files · 31640 symbols · 29882 edges · 3588 modules (14830 call-graph isolated) Root: `.` -Call-graph isolate provenance: 3822 declaration, 2412 header, 3226 source, 4841 document; 0 connected Louvain singletons +Call-graph isolate provenance: 3951 declaration, 2444 header, 3330 source, 5105 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3553) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1577 symbols -- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 530 symbols -- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 333 symbols +## Modules (call-graph clusters; showing 12 of 3588) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1617 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 547 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 335 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2762,12 +2751,12 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - - + + - - + + @@ -2793,15 +2782,15 @@ $ ./build/ripwire . --mermaid %% ripwire --mermaid: module (directory) dependency graph — node = dir (symbol count), edge = inter-module calls (>= 3). Render at mermaid.live. flowchart LR subgraph sg0 ["src"] - n102["src
9191"] + n102["src
9384"] n103["src/infra
1301"] end subgraph sg1 ["test"] - n104["test
5465"] - n556["test/legochargefix
546"] - n398["test/fixtures/recallpassage
314"] - n799["test/slotjoinfix
175"] - n641["test/ownerhopfix/py/pkg
158"] + n104["test
5501"] + n559["test/legochargefix
546"] + n400["test/fixtures/recallpassage
314"] + n944["test/slotjoinfix
175"] + n786["test/ownerhopfix/py/pkg
158"] n342["test/expandmodefix
151"] n344["test/expandsibsfix
149"] ... [17 more line(s); run it to see the whole thing] @@ -2822,15 +2811,15 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - + + - + - + @@ -2853,7 +2842,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2913,12 +2902,12 @@ either fold only when it saves a quarter of the bytes. Refused alone. **Try it** -_The same verdict with every stale acknowledgement printed as an row and the full header — recorded against a DIRTY tree, so the rows above it are the working copy's. Refused without --quality-delta._ +_The same clean verdict with what it folded printed back: every stale acknowledgement as an row and the full header. The regression-free answer above keeps only stale= and stale_by_kind= (and stale_next= names this call), and folds only when that saves a quarter of the bytes. Refused without --quality-delta._ ``` $ ./build/ripwire . --quality-delta --show-stale - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument in another function or at file scope holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; they gate when major. new-symbol=N: findings on NEW code; new-symbol rows never gate, except defect-shape format-arity. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. declined-call-excluded=N: symbols kept out of dead-code only because a declined call may mean them; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). head_basis=identity: the floor is this tree's own snapshot; archived-index-hidden: refused, a tracked path is skip-worktree/assume-unchanged; absent: the archived HEAD tree. --> + @@ -2942,12 +2931,12 @@ Each kind is measured against the baseline (complexity/verbosity/nesting/params/ **Try it** -_Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit._ +_On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below._ ``` $ ./build/ripwire . --quality-delta - -, acked. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. stale_by_kind=: stale= split by ack kind (kind:N,...); the sa rows are collapsed. stale_next=: the call printing the sa rows and the full header. head_basis=identity: the floor is this tree's own snapshot; archived-index-hidden: refused, a tracked path is skip-worktree/assume-unchanged; absent: the archived HEAD tree. --> + ``` @@ -2967,12 +2956,12 @@ $ ./build/ripwire . --quality-delta **Try it** -_Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit._ +_On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below._ ``` $ ./build/ripwire . --quality-delta - -, acked. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. stale_by_kind=: stale= split by ack kind (kind:N,...); the sa rows are collapsed. stale_next=: the call printing the sa rows and the full header. head_basis=identity: the floor is this tree's own snapshot; archived-index-hidden: refused, a tracked path is skip-worktree/assume-unchanged; absent: the archived HEAD tree. --> + ``` @@ -2997,7 +2986,7 @@ _A ref range run for two of the twelve kinds only: the named kinds' rows are exa ``` $ ./build/ripwire . --quality-delta=HEAD~1..HEAD --quality-kinds=complexity,dead-code -

@@ -3090,7 +3079,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3115,15 +3104,15 @@ A contract is PER DEFINITION, so a SYM matching several definition sites REFUSES **Try it** -_Fast per-symbol post-edit contract check vs git HEAD — recorded against a DIRTY tree, so the verdict describes the working copy, not HEAD alone, and the recorded exit code is the working copy's._ +_Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree)._ ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - - + + @@ -3174,12 +3163,12 @@ $ ./build/ripwire . --insert-before-symbol=nonNegativeFloatDescKey --edit-payloa {"n":"benchAdaptive","p":"bench/bench_radix_ab.cpp:157","l":[162]}, {"n":"radixSortNonNegativeFloatsDesc","p":"src/infra/sortutil.h:105","l":[114]}, {"n":"radixSortByScoreDescId","p":"src/infra/sortutil.h:120","l":[180]}], -"graph_ambiguous":2882,"graph_unresolved":14926,"graph_unindexed":269,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, {"p":"test/adaptivecutshapefix/adaptive_cut_shape_test.cpp","hops":2,"run":"bash test/adaptivecutshapecheck.sh"}, {"p":"test/includeprecise_unit.cpp","hops":2,"run":"bash test/includeprecisecheck.sh"}, {"p":"test/verify_csr.cpp","hops":2,"run":"bash test/a9disclosurecheck.sh"}, {"p":"test/rustimport_unit.cpp","hops":4,"run":"bash test/rustimportprecisecheck.sh"}], -"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":775,"graph_ambiguous":2882,"graph_unresolved":14926,"graph_unindexed":269,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":779,"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3195,12 +3184,12 @@ _Insert immediately AFTER one uniquely-resolved definition; replaced_bytes=0 bec ``` $ ./build/ripwire . --insert-after-symbol=lessByScoreDescId --edit-payload=/aux/payload_note.h {"applied":"insert_after_symbol","symbol":"lessByScoreDescId","file":"src/infra/sortutil.h","span":{"start":2375,"end":2463},"lines":{"start":56,"end":58},"replaced_bytes":0,"old_file_bytes":10824,"new_file_bytes":10912,"file_eol":"lf","eol_normalized":false,"trailing_newline_folded":true,"separator … [line truncated: 652 more bytes on this line] -"graph_ambiguous":2882,"graph_unresolved":14926,"graph_unindexed":269,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, {"p":"test/adaptivecutshapefix/adaptive_cut_shape_test.cpp","hops":2,"run":"bash test/adaptivecutshapecheck.sh"}, {"p":"test/includeprecise_unit.cpp","hops":2,"run":"bash test/includeprecisecheck.sh"}, {"p":"test/verify_csr.cpp","hops":2,"run":"bash test/a9disclosurecheck.sh"}, {"p":"test/rustimport_unit.cpp","hops":4,"run":"bash test/rustimportprecisecheck.sh"}], -"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":775,"graph_ambiguous":2882,"graph_unresolved":14926,"graph_unindexed":269,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":779,"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3246,12 +3235,12 @@ $ ./build/ripwire . --insert-before-symbol=nonNegativeFloatDescKey --edit-payloa {"n":"benchAdaptive","p":"bench/bench_radix_ab.cpp:157","l":[162]}, {"n":"radixSortNonNegativeFloatsDesc","p":"src/infra/sortutil.h:105","l":[114]}, {"n":"radixSortByScoreDescId","p":"src/infra/sortutil.h:120","l":[180]}], -"graph_ambiguous":2882,"graph_unresolved":14926,"graph_unindexed":269,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, {"p":"test/adaptivecutshapefix/adaptive_cut_shape_test.cpp","hops":2,"run":"bash test/adaptivecutshapecheck.sh"}, {"p":"test/includeprecise_unit.cpp","hops":2,"run":"bash test/includeprecisecheck.sh"}, {"p":"test/verify_csr.cpp","hops":2,"run":"bash test/a9disclosurecheck.sh"}, {"p":"test/rustimport_unit.cpp","hops":4,"run":"bash test/rustimportprecisecheck.sh"}], -"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":775,"graph_ambiguous":2882,"graph_unresolved":14926,"graph_unindexed":269,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":779,"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3310,11 +3299,11 @@ _The same plan committed: per-file locks, re-verify-before-write, atomic rename, ``` $ ./build/ripwire . --edit-plan=/aux/edit_plan.json --apply -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2882,"graph_unresolved":14926,"graph_unindexed":269,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mult … [line truncated: 521 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mult … [line truncated: 537 more bytes on this line] {"n":"benchAdaptive","p":"bench/bench_radix_ab.cpp:157","l":[162]}, {"n":"radixSortNonNegativeFloatsDesc","p":"src/infra/sortutil.h:107","l":[116]}, {"n":"radixSortByScoreDescId","p":"src/infra/sortutil.h:122","l":[182]}], -"graph_ambiguous":2882,"graph_unresolved":14926,"graph_unindexed":269,"counts_floor":true}}]} +"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3360,19 +3349,19 @@ _Bare --slice=SYM: the INVENTORY of sliceable locals (), so a calle ``` $ ./build/ripwire . --slice=rankGraphTeleport - - - - - - - - - - - - - + + + + + + + + + + + + + ... [3 more line(s); run it to see the whole thing] ``` @@ -3397,17 +3386,17 @@ _Forward flow: which statements the seed's value reaches, at the default depth b ``` $ ./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + ... [22 more line(s); run it to see the whole thing] @@ -3451,10 +3440,10 @@ $ ./build/ripwire . --slice-depth=3 _Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost._ ``` -$ ./build/ripwire . --at=src/graph.h:10410 +$ ./build/ripwire . --at=src/graph.h:11751 - - + + ``` @@ -3473,25 +3462,14 @@ The bundle is BUDGETED by default (8000 tokens, budget_default="1" on the root; **Try it** -_No-LLM review-evidence bundle for the working-tree diff — recorded against a DIRTY tree, so it is populated rather than empty._ +_No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - - - - - - - - - - - - - -... [17 more line(s); run it to see the whole thing] + + + ``` **Shaped by:** `--test-gate`, `--from-trace`, `--limit`, `--map-diff`, `--index-out` @@ -3515,20 +3493,16 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + - - - - - -... [2 more line(s); run it to see the whole thing] + + ``` **Shaped by:** `--plan-lanes`, `--plan` @@ -3598,7 +3572,7 @@ _Which refs of the `lane/` ref family still hold divergent authored work vs HEAD ``` $ ./build/ripwire . --stray-content=lane/ - + @@ -3632,14 +3606,14 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + - + @@ -3667,7 +3641,21 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi -(empty) + + + + + + + + + + + + + +... [2 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -3688,13 +3676,13 @@ _Which ref's tree defines or mentions SYM — HEAD first, then every local branc ``` $ ./build/ripwire . --whereis=rankGraphTeleport - - - - - - - + + + + + + + ... [14 more line(s); run it to see the whole thing] ``` @@ -3718,11 +3706,11 @@ _The same page with every t= line whole: the page above previews each line over ``` $ ./build/ripwire . --whereis=rankGraphTeleport --whereis-listing=all --whereis-full-text - - - - + + + ... [24 more line(s); run it to see the whole thing] @@ -3746,12 +3734,12 @@ _The whole hit list asked for by name: under the same 60-row cap HEAD's referenc ``` $ ./build/ripwire . --whereis=rankGraphTeleport --whereis-listing=all - - + + + - - + + ... [23 more line(s); run it to see the whole thing] @@ -3779,7 +3767,7 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat $ ./build/ripwire . --flags - + @@ -3860,7 +3848,7 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - @@ -3895,7 +3883,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3929,7 +3917,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3962,8 +3950,8 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + @@ -3996,7 +3984,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -4031,8 +4019,8 @@ $ ./build/ripwire . --from-trace=- AddressSanitizer:DEADLYSIGNAL ================================================================= ==41337==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000018 (pc 0x000102f4a1c8 bp 0x00016d2f1a40 sp 0x00016d2f19e0 T0) - #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:10410 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10451 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:11751 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:11792 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 #3 0x102e01a44 in main src/main.cpp:4174 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) @@ -4061,7 +4049,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4151,7 +4139,7 @@ _ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to ``` $ ./build/ripwire . --pack-task="add a new output format flag to the CLI" - + + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept @@ -4188,11 +4176,11 @@ _Fan-out form: one shared core + 3 per-agent slices carved along call-graph comm ``` $ ./build/ripwire . --pack-task="add a new output format flag to the CLI" --partition=3 - wraps a ; bundle role=core|partition i= symbols= modules= bytes= tokens=: one agent's ctx, symbols= ids assigned, bytes= its size; tokens= = est_tokens= = bytes/2.36 (flat, the densest rate; the inner ctx est_tokens= is language-weighted). Inner ctx task= root= budget_tokens= dropped_positive=: the task, p= base, the slice's token target, ranked candidates its budget cut. of_top=: rows ranked (far) / bodies (callers); s rel=caller|callee shared=: 1-hop edge, bodies reached (absent at 1). window: shown= total= capped= (capped=1 cut). run_unknown=1: no runner derivable (a guess would be worse). est_tokens=: price as emitted (an upper bound under compact). : rank N in this ranking, rows in r= order. : cyclomatic/cognitive complexity. : N callers in the index (absent: not measured). route=: the ranker: name-exact(X) = the task names symbol X (anchors: its evidence), subtoken+body = conceptual BM25 (:broad = 1-2 plain words, plain rg may also win; :declined(...) = a name hit refused as a common name). sc=: enclosing scope; the full id is p::sc::n (p= of the row or its ) and selectors take it. next=: the one pasteable follow-up. pure=1: const/constexpr signature (Swift: non-mutating) and no transitive side effect found; a hint. partitions=/requested=: partitions carved / asked for. modules=/split=: call-graph groups found / cuts forced to split one. core_symbols=/surface=: ids in the shared core / core plus the assignable remainder. budget_per_agent_tokens=: one agent's budget, core_budget_tokens= plus partition_budget_tokens=. total_bytes=N: bytes of all bundles together. overlap_mean=/overlap_max=: pairwise Jaccard over the ids partitions name, before trimming. shared_symbols=/union_symbols=: ids two or more partitions name / ids any partition names. core_overlap=: share of the core surface a partition reaches anyway. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. --> - - - + + + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept @@ -4221,7 +4209,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - @@ -4294,14 +4282,14 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - + - - + + - - + @@ -4328,14 +4316,14 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - - + - - + + - - + @@ -4363,7 +4351,7 @@ $ ./build/ripwire . --skipped - @@ -4451,13 +4439,13 @@ _Rank by git change-frequency prior instead of PageRank._ $ ./build/ripwire . --rank-by=churn --top-k=5 - - + + - + - + @@ -4487,19 +4475,19 @@ _Scope the recent-changes answer to ONE directory. The global block sta $ ./build/ripwire . --rank-by=churn-decay --since=HEAD~6 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3 - - - - - - + + + + + + + + + + + - - - - - -... [15 more line(s); run it to see the whole thing] +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` @@ -4561,7 +4549,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4589,7 +4577,7 @@ _The session legend dictionary the MCP server serves as ripwire://legend-dict/fu ``` $ ./build/ripwire . --legend-dict -ripwire legend dictionary ripwire.dict/v1 dictv=a612d2cfd7867548 entries=923 +ripwire legend dictionary ripwire.dict/v1 dictv=2259c5025ac90271 entries=930 : the answer's rows come first; its root keeps only task= changed= from= to=, and this LAST child carries every other root attribute unchanged (schema= included); legend="ref": a definition is sent once per session (this dictionary's core, or the first answer that ne … [line truncated: 83 more bytes on this line] schema=ripwire.KEY/v1: the line ripwire.KEY/v1 below reads the answer's rows window: shown= total= capped= has_more= next_offset= offset= limit= page a list (capped=1 cut; next_offset= pastes as offset=) @@ -4641,7 +4629,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -4652,7 +4640,7 @@ $ ./build/ripwire . --ensemble --limit=8 - + ... [17 more line(s); run it to see the whole thing] ``` @@ -4677,14 +4665,14 @@ _Drop matching paths (repeatable) before ranking._ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - - + + - + - + @@ -4702,25 +4690,25 @@ this is NOT a filter to only-changed symbols. changed="N" in the header names th **Try it** -_Full map re-ranked with teleport toward git-changed files — recorded against a DIRTY tree, so changed= counts the working copy's files and the teleport is live._ +_Full map re-ranked with teleport toward git-changed files — clean tree, so changed=0 and it degrades to the plain map._ ``` $ ./build/ripwire . --map-diff --top-k=5 - + - - - - - - - + + + + - + + + + + - - -... [5 more line(s); run it to see the whole thing] + +... [8 more line(s); run it to see the whole thing] ``` **Shaped by:** `--json`, `--limit` @@ -4742,8 +4730,8 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - - + + @@ -4792,8 +4780,8 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co $ ./build/ripwire . --no-cache --top-k=3 - - + + @@ -4823,8 +4811,8 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses $ ./build/ripwire . --no-ignore --top-k=3 - - + + @@ -4859,14 +4847,14 @@ _Skip files above a size bound before parsing (note the corpus shrink in the hea $ ./build/ripwire . --max-file-size=8K --top-k=3 - - + + - + - + @@ -4939,8 +4927,8 @@ _Eval-only: a per-call-site census of WHICH mechanism resolved each call, and th $ ./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3 - - + + @@ -4993,10 +4981,10 @@ _--mcp-legend=inline: every answer of the stdio session carries its own legend ( ``` $ ./build/ripwire '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whereis","arguments":{"path":".","symbol":"rankGraphTeleport"}}}' '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"whereis","arguments":{"path":".","symbol":"computeOnePairOverlap"}}}' | ./build/ripwire --mcp --mcp-legend=inline {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"ripwire","version":"1.0"},"capabilities":{"tools":{},"resources":{}},"instructions":"Map before reading files. Start a new task with explore; use from_trace for an error; use impact plus uses before changing a sym … [line truncated: 735 more bytes on this line] -{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":" | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 711 gate scripts plus the determinism, cache-transparency and golden contracts. | | **`--quality-delta`** | `src/quality.h` | Ten measured code-quality failure modes, reported only where a change made them worse. | ### The labeling protocol (why the held-out eval is allowed to disagree with the ranker) @@ -5888,7 +5888,7 @@ copy here would be exactly the dialect divergence that gate exists to catch. Com tags, wrap, stable-order defaults), seven individually invoked standalone gates (`g1freshcheck`, `skillscan`, `htmlexport`, `compresscheck`, `handoffcheck`, `releaseinstallcheck`, `taskroutecheck`), and a single loop -naming **709 gate scripts**, all of which exist on disk. +naming **711 gate scripts**, all of which exist on disk. `python3 test/pargates.py . ./build/ripwire -j 6` runs the same scripts in parallel so a full verification fits in one sitting. It does not modify `regression.sh`. @@ -7005,7 +7005,7 @@ Listed because the reason is more useful than the silence. shipped**. See `bench/locbench/anchorhop_calib.json`. The mention anchor's reproducible numbers are the ablations in §4. - **A single round gate-count.** Two in-tree numbers disagree (`test/pargates.py`'s docstring says - ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 709. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 711. The loop is the authority; the stale docstrings are a known drift. Since 2026-09-10 the number is not written by hand anywhere: `docs/gatecount_build.py` derives it from the loop and rewrites every published site, `test/gatecountcheck.sh` fails if any of them drifts, and `test/manifestcheck.sh` diff --git a/docs/LIMITS.md b/docs/LIMITS.md index 04932219e..652fa1904 100644 --- a/docs/LIMITS.md +++ b/docs/LIMITS.md @@ -30,10 +30,10 @@ a small explicit bound" is safe only when nothing inside the group repeats; it i | total caps | files | caps whose file discloses | caps whose file discloses NOTHING | | --- | --- | --- | --- | -| 276 | 108 | 165 | **111** | +| 278 | 109 | 165 | **113** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 276 + 7 is the 283 constants this generator parses out of `src/`. +are not counted as caps, and 278 + 7 is the 285 constants this generator parses out of `src/`. ## INDEXING, OUTPUT or BOUNDARY — which half of the answer a cap bounds @@ -51,8 +51,8 @@ None of them truncates anything, so none can be judged by `shown=`/`total=` and a disclosure — labelling them OUTPUT would ask for a `capped="1"` that could never honestly fire. The distinction was named in review on #108 and the rows below now carry it. -The `class` column below carries that answer where it is known. **140 of 276 caps are classified -(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 136 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **140 of 278 caps are classified +(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 138 render `—`, which means NOT YET CLASSIFIED — never "neither".** Classifications live in `docs/limits_classes.tsv`, a sidecar with a known expiry: the tag belongs on the declaration itself, and this file exists only because the round that @@ -102,7 +102,7 @@ refuse to write, so the column cannot be satisfied by pointing at nothing. ## Caps, by file -One table for each of the 108 files that declare a cap — the 276 caps counted above, and no parameter. +One table for each of the 109 files that declare a cap — the 278 caps counted above, and no parameter. ### `src/abicheck.h` @@ -129,6 +129,15 @@ Discloses: `count_capped` | --- | --- | --- | --- | | `kAtomsQueryBudget` | `100000` | — | — | +### `src/backhops.h` + +Discloses: **none** + +| constant | value | class | note | +| --- | --- | --- | --- | +| `kBackHopWidth` | `16` | — | — | +| `kImpactRingRowCap` | `16` | — | — | + ### `src/binstale.h` Discloses: **none** diff --git a/docs/TUNING.md b/docs/TUNING.md index 2c6425736..cb9c6afff 100644 --- a/docs/TUNING.md +++ b/docs/TUNING.md @@ -14,25 +14,25 @@ to production at defaults; that control is what makes these numbers mean anythin | cap declarations | distinct names | tunable | must stay `constexpr` | move >= 1 invocation | move nothing measurable | | --- | --- | --- | --- | --- | --- | -| 184 | 183 | 112 | 12 | **37** | 75 | +| 186 | 185 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **184 cap -declarations** under **183 distinct names** (`kRowCap` declared in more than one file). The sweep +The first two columns are not the same number, and the gap is not a rounding: `src/` holds **186 cap +declarations** under **185 distinct names** (`kRowCap` declared in more than one file). The sweep patches by NAME, so its own population is NAMES and not declarations — and the two columns beside those are frozen at the commit named under Provenance while this census is re-read from `src/` on -every run, which makes them a third population again. Enumerated over the 183 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **60 declared since the sweep was -prepared, which no measurement has touched** (`kBaseScopeScanCap`, `kBridgeMaxHops`, -`kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, `kClassNewRowCap`, +54 -more). 111 + 12 + 60 = 183, and `emit` refuses to render a partition that does not add up. The +every run, which makes them a third population again. Enumerated over the 185 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **62 declared since the sweep was +prepared, which no measurement has touched** (`kBackHopWidth`, `kBaseScopeScanCap`, +`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +56 +more). 111 + 12 + 62 = 185, and `emit` refuses to render a partition that does not add up. The `tunable` column above reads 112 rather than 111 because one name the sweep classified is no longer -declared in `src/` at all: `kSituTestRowsShown`. Quoting "113 of 184" would be wrong in both halves +declared in `src/` at all: `kSituTestRowsShown`. Quoting "113 of 186" would be wrong in both halves at once, which is the shape of error a generated table exists to prevent. ## Read this ratio before the tables **37 of 112 tunable caps move any invocation at all. 75 move nothing measurable.** That is the -finding, and it says what NOT to do: this is not a 184-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 186-cap audit. Most of these constants are inert on real invocations and should be left alone. The work worth doing is the small set below, plus the caps that fire SILENTLY — a cap that bites without disclosing is a defect independent of whether its value is right, and that fix is both cheaper and larger than any retuning. diff --git a/docs/captures/COMMANDS_showcase_2026-10-24.md b/docs/captures/COMMANDS_showcase_2026-10-24.md new file mode 100644 index 000000000..9d4514240 --- /dev/null +++ b/docs/captures/COMMANDS_showcase_2026-10-24.md @@ -0,0 +1,7806 @@ +# ripwire — every verb, run for real + +- **Date:** 2026-10-24 (regenerated capture; supersedes any older `docs/captures/COMMANDS_showcase_*.md`) +- **Lives in `docs/captures/`** — a directory the crawl/retrieval lenses SKIP (`kCrawlSkipDirs`, src/ingest.h): a generated doc that quotes every verb's output out-scores the source for any query about the tool and was measured at 77% of `--recall` on this repo when it sat at the root. `test/argvdiffcheck.sh` harvests its `## `-heading command lines as differential vectors — keep that format. +- **Version:** `ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=b58af84eb)` +- **Repo:** the ripwire repo @ `c97f6f91` — **CLEAN — `git status --porcelain` is empty**. The diff-aware verbs (`--situ`/`--test-gate`/`--quality-delta`/`--pr-context`/`--map-diff`/`--edit-check`) answer a question about the WORKING TREE, so that condition is part of their answer and every one of their captions below states which tree it recorded against. A clean tree is the honest default for a showcase, so they appear TWICE: once here on the clean tree (their empty/exit-0 shape) and once in the final section against a throwaway `git clone --local` sandbox carrying one deliberate regression, so their real gating shapes are visible without writing a byte into the read-only repo. +- **Corpus:** the ripwire repo itself (dogfood), via `./build/ripwire` +- **Sandbox diff** (the last section only): `.ripwire_notes | 2 ++ + .ripwire_quality_acks | 1 + + src/infra/sortutil.h | 31 ++++++++++++++++++++++++++++--- + 3 files changed, 31 insertions(+), 3 deletions(-)` — one preexisting function made deeply nested, one function's arity changed 1 -> 2, one copy-paste duplicate helper, one new 8-parameter public function. + +**How to read the blocks:** ripwire's real XML output is minified — often ONE long line. For scanability, long minified lines are displayed re-wrapped with a line break at every tag seam (`><`). Header COMMENT lines (the legends) always appear in full — they are exempt from the per-line cut; any OTHER display line over 300 bytes is cut with a `… [line truncated: N more bytes]` marker, which can hit a long root element or row. `--plan-lanes` emits JSON and is re-wrapped at object seams the same way. Long outputs are cut to their first ~30 display lines with a `… [N more display lines; full output is M bytes]` marker giving the true size. Exit codes are recorded when non-zero; wall time when >1s. + +**Not run (and why):** `ripwire ` (network clone), `--listen` / `--mcp-token` / `--allow-remote-edits` (the HTTP-server posture; `--mcp` itself IS captured in its own section as a one-shot stdio JSON-RPC exchange, and `wrap claude` shows the wiring), `--arch --baseline[-update]` (state writer against the read-only repo — `--note-add` / `--quality-baseline` / `--quality-ack` / the three edit verbs / `--edit-plan` ARE shown, inside the throwaway sandbox clone; `--index-out` / `--pin-census` write to scratch), `--eval-mined` (needs a `minedpair.jsonl` artifact from `bench/mine_traces.py`; none present in the tree), `--refetch` (git-url only), `--lsp` (the editor transport — its one-shot dialogues are gated by `test/lspcheck.sh`, not by showcase blocks), `--max-memory` (the memory guard is silent on this repo by construction; its stops are gated by `test/memguardcheck.sh`), `--force` (wrap-only modifier), `--scan-skills` bare form (would sweep `~/.claude/skills`; the explicit-DIR form is shown instead), `--help` (212 lines — read it from the binary). + + +--- + +# understand a codebase cold + +## `./build/ripwire .` + +*The default ranked symbol map — start here when landing cold in a repo.* + +**wall time: 1.41s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [735 more display lines; full output is 23597 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --top-k=5` + +*Same map, capped to the 5 highest-ranked symbols.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [67 more display lines; full output is 8256 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --top-k=5 --no-inventory` + +*The same capped map without its inventory tier: --top-k=5 leaves files out, so the default map appends (program entries, and the files the ranked rows left out) after its rows; --no-inventory drops that tier, and the ranked rows are the same.* + +````` + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --top-k=0 --expand=rankGraphTeleport` + +*NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map rides along with the body you asked for.* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --token-budget=100` + +*GATE form: exit 3 if the map's own est_tokens exceeds the budget (over-budget failure shape).* + +**exit code: 3** + +````` + + +````` + +stderr: + +````` +ripwire: --token-budget exceeded: withheld_est_tokens=9507 > budget=100 +````` + +## `./build/ripwire . --for="incremental cache invalidation when a file content hash changes"` + +*The task lens: ranked signatures + quality metrics framed for the task.* + +**wall time: 3.22s** + +````` + + + + +incremental cache (--cache): per-file content hash + raw facts so a re-run re-parses ONLY c…constexpr std::uint32_t kCacheMagic = 0x4b505443 + +and every file whose content hash differs is re-parsed — so a stale or foreign blob self-heals…constexpr std::uint32_t kHeadSnapCacheScheme = 1 +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline void prewarmTagsQueries( const std::vector<std::string>& files, const HashMap<std::string, FileFacts>& cache, long long cacheWriteNs, IngestFile … [line truncated: 20 more bytes on this line] +inline bool ingestCommitTree( const std::string& root, const std::string& sha, const std::vector<std::string>& excludes, std::size_t maxFileBytes, IngestResult& ou … [line truncated: 7 more bytes on this line] +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& imple … [line truncated: 62 more bytes on this line] +inline std::string spanTierMemoPath( const std::string& diskPath ) +inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cach … [line truncated: 98 more bytes on this line] + +persistent in-memory index (parse once, reuse across MCP calls) ---- The MCP server is long-live…struct McpIndex +inline const McpIndex& getIndex( const std::string& root ) +inline std::uint64_t contentHash64( std::string_view s ) noexcept +struct CacheEntry +inline std::uint64_t blobChecksum( std::string_view s ) noexcept +inline ContentIdIndex contentIdsBySym( const IngestResult& ing, const Graph& g, std::string_view root ) +inline std::vector<CacheWriteRow> buildCacheWritePlan( const std::vector<std::uint32_t>& orderIn, const std::vector<std::uint64_t>& pathHashes … [line truncated: 25 more bytes on this line] +inline CachePathKeys buildCachePathKeys( const std::vector<std::string>& files, std::string_view rootDir ) +inline std::string reAbsolutize( std::string_view rel, std::string_view root ) +inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t> … [line truncated: 25 more bytes on this line] +inline std::string makeHandle( const std::string& canonId, const std::string& path, const std::string& name, std::uint64_t contentHash ) +struct IngestFileScan +inline std::uint64_t byteHash( const char* data, std::size_t n ) noexcept +inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) +void emitGrepHandleLegend( bool enabled ) +… [127 more display lines; full output is 20072 bytes on 8 raw line(s)] +````` + +## `./build/ripwire . --for="rankGraphTeleport"` + +*Name-shaped query: the router picks name-exact BM25 (header says which/why).* + +**wall time: 1.66s** + +````` + + + + + +PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` + + + + + + + + + + + + + + + + + + + + + +& p, float alpha = 0.85f ) +… [28 more display lines; full output is 5868 bytes on 29 raw line(s)] +````` + +## `./build/ripwire . --for="rankGraphTeleport" --no-route` + +*Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* + +**wall time: 1.64s** + +````` + + + + + +uniform-teleport PageRank (the defaultinline RankedGraph rankGraph( const Graph& g, float alpha = 0.85f ) + +PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quali…inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) +inline ChurnRanking churnRankedGraph( const MainDispatch& d ) + +L10 (2026-09-04): the old wording claimed "the same corpus ranked by pagerank orders differently…inline constexpr const char* kChurnRankLegend = "<!-- rank_by=churn: k= is PageRank re-run with the teleport BIASED by git CHANGE-FREQUENCY over window= " "(a c… +struct RankedGraph +inline std::vector<float> takeRank( RankedGraph ranked, RankDisclosure& disclosureOut ) +inline std::vector<float> biasPrior( const Graph& g, const std::vector<float>& p ) +inline std::vector<float> churnTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, const char* since = "18 months ago& … [line truncated: 20 more bytes on this line] +inline ChurnRanking churnDecayRanking( const MainDispatch& d, const rw::SinceScope& sinceScope, bool isScoped, const char* verbLabel ) +inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) +PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double> rank… +inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = n… +inline std::string didYouMean( const IngestResult& ing, std::string_view name ) +PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span< … [line truncated: 45 more bytes on this line] +inline const McpIndex& getIndex( const std::string& root ) +inline std::vector<float> churnDecayTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, bool* outHasChurnEvidence = n … [line truncated: 12 more bytes on this line] +struct DataSectionsCut +### Fixed — `rank_by` over MCP no longer answers a different ranking than `--rank-by` on … [line truncated: 36 more bytes on this line] +### 2. Gates for A +inline std::vector<float> anchoredLexicalRank( const Graph& g, const std::vector<float>& lex ) +inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +… [155 more display lines; full output is 24115 bytes on 72 raw line(s)] +````` + +## `./build/ripwire . --for="rankGraphTeleport" --signatures-only` + +*T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribute) — contrast with the terminal default above.* + +**wall time: 1.68s** + +````` + + + + + +PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` + + + + + + + +````` + +## `./build/ripwire . --for="tree-sitter parse of a source file" --adaptive` + +*Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothing is cut and the header says so ([adaptive: kept N of N]).* + +**wall time: 1.45s** + +````` + + + + +inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) +The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u +TSTree* parseTree( TSParser* parser, std::string_view src ) + +Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() +struct FileHealth +constexpr std::array<LangEntry, 57> kLangTable = +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar…struct SliceScan +enum class AstWalk : std::uint8_t +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +std::vector<LocalNameFact> collectGatedLocalNames( std::string_view defBytes, std::uint32_t defStartLine, Lang lang ) +inline ModuleScan scanModule( std::string_view source, std::string_view path ) +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +inline bool hasMainGuard( std::string_view source ) +constexpr std::uint32_t kMaxYamlNestDepth = 64u +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +enum class DisclosureWhy : std::uint8_t +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) +struct LintRule +inline FileHealth measureHealthAdoptingMemberMacroReparse( TSParser* parser, Lang lang, std::string_view bytes, TreeGuard& tree, MemberMacroRepa … [line truncated: 19 more bytes on this line] +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [138 more display lines; full output is 19980 bytes on 44 raw line(s)] +````` + +## `./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25"` + +*Mention anchoring (default-on): a path and a Symbol literally named in the task get lifted; the header says what anchored.* + +**wall time: 1.77s** + +````` + + + + +The name-exact ranker AS THE RETRIEVAL LENS SERVES IT: whole-name BM25 plus the definition-over-…inline std::vector<float> lexicalScoresNameExactRanked( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) + +P11.12: the interpretive footer for --eval's ranker table, pulled into its own function so the 9…inline void printEvalRankerNote() +inline int runEvalRetrieval( const IngestResult& ing, const Graph& g ) + +calling agent knows to reformulate rather than trust the ranking. Calibrated empirically (2026-0…inline constexpr float kWeakLexicalScoreThreshold = 1.0f +inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) +inline std::string candidatesRootTag( std::size_t keep, std::size_t corpusCount, const CandidateProvenance& prov ) +inline std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) +inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) +struct NameCorpusStats +inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) +inline bool hasIdentifierShape( std::string_view w ) noexcept +struct LensRanking +struct Bm25Params +inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept +inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) +rw::LensRanking computeLensRanking( const MainDispatch& d, std::string_view task, bool compactCandidate = false, bool fullDistribution = false ) // deep-tail: the file-grain tail rea … [line truncated: 65 more bytes on this line] +struct CandidateProvenance +inline constexpr int kLexWeightDoc = 2 +inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) +inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +std::optional<int> runEvalViews( const MainDispatch& d ) +struct RecallSectionPick +inline double binomialChoose( std::size_t n, std::size_t k ) +… [146 more display lines; full output is 21540 bytes on 18 raw line(s)] +````` + +## `./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25" --no-mention-boost` + +*Same task with the anchor disabled — the contrast the flag exists for.* + +**wall time: 2.03s** + +````` + + + + +The name-exact ranker AS THE RETRIEVAL LENS SERVES IT: whole-name BM25 plus the definition-over-…inline std::vector<float> lexicalScoresNameExactRanked( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) + +P11.12: the interpretive footer for --eval's ranker table, pulled into its own function so the 9…inline void printEvalRankerNote() +inline int runEvalRetrieval( const IngestResult& ing, const Graph& g ) + +calling agent knows to reformulate rather than trust the ranking. Calibrated empirically (2026-0…inline constexpr float kWeakLexicalScoreThreshold = 1.0f +inline std::string candidatesRootTag( std::size_t keep, std::size_t corpusCount, const CandidateProvenance& prov ) +inline std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) +inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) +struct NameCorpusStats +inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) +inline bool hasIdentifierShape( std::string_view w ) noexcept +struct LensRanking +struct Bm25Params +inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) +inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept +inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) +rw::LensRanking computeLensRanking( const MainDispatch& d, std::string_view task, bool compactCandidate = false, bool fullDistribution = false ) // deep-tail: the file-grain tail rea … [line truncated: 65 more bytes on this line] +struct CandidateProvenance +inline constexpr int kLexWeightDoc = 2 +inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) +inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +std::optional<int> runEvalViews( const MainDispatch& d ) +struct RecallSectionPick +inline double binomialChoose( std::size_t n, std::size_t k ) +… [155 more display lines; full output is 21617 bytes on 18 raw line(s)] +````` + +## `./build/ripwire . --lego=Vehicle` + +*Interface -> implementors view: every existing impl of the named interface; the method contract is extracted for the C-family/Java/TS/Python tiers — for a Rust trait (this fixture) it discloses caveat="not-extracted-for-lang" rather than an empty list.* + +````` + + + + + + + + + +````` + +## `./build/ripwire . --exemplar="format byte sizes for humans"` + +*The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* + +**wall time: 1.18s** + +````` + + + + + +````` + +## `./build/ripwire . --help-task="calls(runDefaultMap, rankGraphTeleport)"` + +*Deterministic enhanced help: a closed claim in the task is a structured shape, so the router recommends the ONE command that answers it (--verify) with the evidence behind the pick. Advice only — nothing executes.* + +**wall time: 1.10s** + +````` + + + + +ripwire '.' --verify='calls(runDefaultMap, rankGraphTeleport)' + + +````` + +## `./build/ripwire . --help-task="write a cheerful release announcement"` + +*The honest half of the contract: a task with no ripwire-shaped evidence ABSTAINS with zero commands rather than guessing.* + +**wall time: 1.02s** + +````` + + + + +````` + +## `./build/ripwire . --recall="quality delta gating exit codes"` + +*Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* + +````` +ripwire recall — "quality delta gating exit codes" — 121 relevant of 264 document files, best-first — total=121 shown=8 capped=1 truncated=6 generated_demoted=3 max_tokens=8000 share_bytes=2468 est_tokens=4165 + +━━ README.md (relevance 8.094) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 222723 B; lines="2747-2759,2760-2767"; dropped_by_budget=23] +#### 6.2 Exit codes + +This is the part to wire into a script. + +| Code | Meaning | +| --- | --- | +| 0 | The command completed. | +| 1 | The command refused the request. A refusal names the reason on stderr. When `--callers`, `--callees`, `--uses`, `--impact` or `--path` refuse a selector that matches no indexed definition, they also print an answer on stdout: the verb's element with `found="0"` and the name to retry with. | +| 2 | A policy gate fired: `--arch` found a layering violation, `--scan-skill` found a CRITICAL, `--quality-delta` found new debt. | +| 3 | The output exceeded the token budget that you set. | +| 4 | `--test-gate` found an open obligation. | +| 5 | The memory guard stopped the run: over `--max-memory` (default 65% of the machine's memory) with no partial answer possible, or a verb other than the default map facing a partial index. One stderr line names the limit and the override. | + +#### 6.3 JSON output + +`--json` emits JSON instead of XML, with keys mirroring the XML attribute names one to one. It is an +allow-list: the default map, `--for`, `--pack-task`, `--callers`, `--callees`, `--impact`, +`--quality-delta`, `--test-gate`, `--metrics`, and `--plan-lanes`, which is JSON-native. Every other +verb refuses on stderr with exit 1 rather than silently falling back to XML, so a verb added tomorrow +refuses by default. + + + +━━ CHANGELOG.md (relevance 7.527) ━━ [sections: 1 of 109 selected (451 in doc), section-granular; whole doc 998064 B; lines="558-579"; dropped_by_budget=108] [truncated: 2264 of 3826 bytes] +### Changed — `--quality-delta=A..B` answers the kinds it can under memory pressure, and exits 6 when that answer has no gating row (#387 follow-up) + +A tripped memory guard in the range form used to give exit 5 and nothing, so on a large repository a four-file change +… [114 more lines, 10620 bytes total] +````` + +## `./build/ripwire /aux/kbcorpus --recall="field affinity cache line data layout which fields are read together" --top-k=3 --max-tokens=1200` + +*The directory-as-knowledge-base pattern: --recall pointed at a 1355363-byte scratch dir of DUMPED TOOL OUTPUT (a git log, this repo's own generated command reference, --help text, an architecture doc, a fabricated JSON access log) instead of a source repo — no index to build, no daemon.* + +````` +ripwire recall — "field affinity cache line data layout which fields are read together" — 2 relevant of 2 document files, best-first — total=2 shown=1 capped=1 truncated=1 max_tokens=1200 share_bytes=2040 est_tokens=952 + +━━ commands.md (relevance 10.880) ━━ [sections: 1 of 165 selected (192 in doc), section-granular; whole doc 594792 B; lines="555-580"; dropped_by_budget=164] [truncated: 1854 of 4256 bytes] +#### Pattern: a directory of dumped tool output as a knowledge base + +**Answers:** can `--recall` serve as a zero-setup knowledge base over dumped tool output — a +`git log`, an API response dump, a fetched doc, ` --help` text — sitting in a scratch +directory, instead of a source repo? + +Yes, unmodified. `--recall` never distinguishes "a codebase" from any other directory it can +walk: point it at the scratch dir and query it. No index to build, no daemon, no mutable store +between runs — the whole cost is one cold parse. Two conditions decide whether it works at all, +and both are yours, because the file you write is the only thing that sets them: + +1. **Dump to `.md`.** `--recall` ranks DOCUMENT files: `.md`, plus the docparse'd + `.ipynb`/`.html`/`.csv` (and Office/PDF through the optional markitdown bridge). `.txt`, + `.log`, `.json` and extensionless files are **not** documents to it. A directory of those + answers `0 relevant of 0 document files` and exits 0 — which reads like "nothing matched + your terms" when what happened is "nothing was indexed at all". Redirect to `notes.md`, + never `notes.txt`. The recorded run below is that rule's own demonstration: its scratch dir + holds five dumps, and the header says `2 relevant of 2 document files` because only the two + `.md` ones are documents — the `git log`, the `--help` text and the JSON access log are not + in the population at all. + +2. **Keep `##` headings in the dump.** A headed document is served as whole ranked SECTIONS, so + an answer buried mid-file arrives at a small `--max-tokens`, and — while the served document + SET stays fixed — a larger ceiling returns a strict superset of it; the `[sections: S of R + selected (N in doc) … lines="…"; dropped_by_budget=D]` note names the ranges you actually got + and what the ceiling cost. … + +(capped: 1 of 2 relevant document files omitted — raise --max-tokens/budget_tokens or narrow the query for 1 more (~2548-byte budget)) +````` + +stderr: + +````` +ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pass --no-redact to disable +````` + +## `./build/ripwire . --tree` + +*File-by-file orientation map (top symbols per file).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [368 more display lines; full output is 11197 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --html=/aux/map2.html` + +*Self-contained HTML force-directed call graph.* + +**wall time: 2.35s** + +````` +(empty) +````` + +Artifact written: + +````` + 156153 /aux/map2.html +````` + +## `./build/ripwire . --order=stable --top-k=5` + +*Stable (path/id) emit order — provider KV-cache hits across re-runs.* + +**wall time: 1.00s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [67 more display lines; full output is 8274 bytes on 1 raw line(s)] +````` + + +--- + +# navigate / answer a question + +## `./build/ripwire . --around=rankGraphTeleport` + +*Ego graph around one symbol — depth 1 BY DEFAULT now (the root's depth= says so): ~6 KB where the 2-hop neighbourhood is ~64 KB on this repo.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [164 more display lines; full output is 7789 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --around=rankGraphTeleport --around-depth=2` + +*The restoring knob: --around-depth=2 brings back the whole 2-hop neighbourhood (depth="2" on the root) — pay for it only when the 1-hop view was not enough.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [1947 more display lines; full output is 53159 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --around=rankGraphTeleport --around-fanout=4` + +*The other knob: --around-fanout=4 keeps only the 4 strongest edges per node (default 32) — the same 1-hop depth, a quarter of the rows.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --callers=rankGraphTeleport` + +*Who calls SYM (1-hop in-edges).* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --callers=rankGraphTeleport --legend=full` + +*The same rows under --legend=full: the default is the compact dialect (one compact comment plus schema="ripwire.callers/v1" on the root), and --legend=full restores the prose legend — every row byte and every completeness attribute (counts_floor=, graph_ambiguous=, next=) identical, ~3 KB more legend. Works on EVERY XML verb.* + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --callers=DoesNotExist` + +*Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* + +**exit code: 1** — **wall time: 1.67s** + +````` + + +````` + +stderr: + +````` +ripwire: --callers symbol not found: DoesNotExist +````` + +## `./build/ripwire . --callees=rankGraphTeleport` + +*What SYM calls (1-hop out-edges).* + +````` + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --uses=rankGraphTeleport` + +*The resolvable use-sites (call/read/write/import/extends) with file:line; count= is a floor.* + +**wall time: 1.02s** + +````` + + + + + + + + + + + + +````` + +## `./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))'` + +*Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [28 more display lines; full output is 3650 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --external-surface` + +*Names referenced but never defined in-corpus (stdlib/third-party surface). The root carries names/shown/capped; the default is a 100-row window now, so total= and a pasteable next= join them when it bites (the explicit --limit form carries the same quintet). The sh BUILTINS (cd/echo/set…) are dropped and COUNTED as builtins_excluded= — grep/sed/git stay, they ARE the surface.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5604 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --external-surface --include-builtins` + +*The restoring knob: --include-builtins keeps the shell builtins in the same 100-row window (they now compete for rows, so the window's tail differs).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5478 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --path=main,rankGraphTeleport` + +*Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_defs and resolves the right `main` (was reachable="0").* + +````` + + + + + + + + +````` + +## `./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex` + +*Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --impact=rankGraphTeleport` + +*Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [69 more display lines; full output is 6423 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --mentions=rankGraphTeleport` + +*Markdown docs that name SYM in a backtick (doc<->code edges).* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --affected=src/graph.h` + +*Test files that transitively reach the changed file.* + +````` + + + + + + + + + +````` + +## `./build/ripwire . --situ` + +*Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below).* + +**wall time: 1.29s** + +````` +ripwire situational-awareness — 0 changed file(s), 0 symbols in them +root: . +at: c97f6f91c + (0 changed files — working tree is clean, nothing to analyze) +````` + +## `./build/ripwire . --test-gate` + +*Pre-PR gate on a CLEAN tree: no obligations, exit 0.* + +**wall time: 3.81s** + +````` + + +````` + +## `./build/ripwire . --grep=DISCLOSE` + +*Literal trigram-indexed search. Each hit carries its MATCHED line as the element's own CDATA (the wrapper is gone), plus shown/capped/hits_capped and a pasteable next= on the root.* + +````` + +if data.get( "schema" ) != SCHEMA: raise SystemExit( f"{path}: unexpected schema {data.get('schema')!r} (expecte +if not str( data.get( "tasks_lock_content_sha256", "" ) ).startswith( "questions:" ): train_repos = select_tasks +if train_repos: raise SystemExit( f"{path}: records from repo(s) that re-derive to LocBench +if base and ctx and base["status"] == "ok" and ctx["status"] == "ok": paired.append( ( instance_id, base["re +if not repos: return 0.0, [] +if base["localization_hit"] is None or ctx["localization_hit"] is None: return 0.0 +if bv: ratios.append( cv / bv - 1 ) +if not ratios: return None, None +if rw is None or native is None: return None +if not paired: out["note"] = "zero complete paired (baseline,ripwire_cli) runs — nothing to analyze yet" +if contaminated: print( f" ** {contaminated} baseline run(s) invoked ripwire despite the no-ripwire contract " +if "note" in out: print( f" {out['note']}" ); return +if out["n_pairs"] != 27: failures.append( f"expected 27 paired runs, got {out['n_pairs']}" ) +if out["n_incomplete"] != 2: failures.append( f"expected 2 incomplete pairs (the orphan + the contaminated-basel +if out["n_repos"] != 3: failures.append( f"expected 3 repos, got {out['n_repos']}" ) +if out.get( "n_contaminated_baseline" ) != 1: failures.append( f"expected exactly 1 contaminated baseline run co +if not ( out["resolved_delta_mean"] > 0 ): failures.append( "expected a positive resolved-rate delta" ) +if not ( out["resolved_delta_bootstrap_95_lower"] > 0 ): failures.append( "expected a POSITIVE bootstrap 95% low +if out["tokens_out_ratio_p50"] is None or abs( out["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( f"e +if out.get( "n_resolved_pairs" ) != 27: failures.append( f"expected all 27 pairs resolution-scored, got {out.get +if out.get( "substitution_rate_baseline" ) != 0.0: failures.append( f"expected baseline substitution rate 0.0 (n +if out.get( "substitution_rate_ripwire" ) is None or abs( out["substitution_rate_ripwire"] - 0.75 ) > 1e-9: fail +if out.get( "n_substitution_ripwire" ) != 27: failures.append( f"expected 27 substitution-scored ripwire runs, g +if out3.get( "substitution_rate_ripwire" ) is not None or out3.get( "n_substitution_ripwire" ) != 0: failures.ap +if out2["n_pairs"] != 27: failures.append( f"evaluator-none: expected 27 pairs, got {out2['n_pairs']}" ) +if out2["n_resolved_pairs"] != 0: failures.append( f"evaluator-none: expected 0 resolution-scored pairs, got {ou +if out2["resolved_delta_mean"] is not None or out2["resolved_delta_bootstrap_95_lower"] is not None: failures.ap +if out2["tokens_out_ratio_p50"] is None or abs( out2["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( " +… [73 more display lines; full output is 16956 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --match='(if_statement) @i'` + +*The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* + +**wall time: 1.91s** + +````` + +if data.get( "schema" ) != SCHEMA: raise SystemExit( f"{path}: unexpected schema {data.get('schema')!r} (expecte +if not str( data.get( "tasks_lock_content_sha256", "" ) ).startswith( "questions:" ): train_repos = select_tasks +if train_repos: raise SystemExit( f"{path}: records from repo(s) that re-derive to LocBench +if base and ctx and base["status"] == "ok" and ctx["status"] == "ok": paired.append( ( instance_id, base["re +if not repos: return 0.0, [] +if base["localization_hit"] is None or ctx["localization_hit"] is None: return 0.0 +if bv: ratios.append( cv / bv - 1 ) +if not ratios: return None, None +if rw is None or native is None: return None +if not paired: out["note"] = "zero complete paired (baseline,ripwire_cli) runs — nothing to analyze yet" +if contaminated: print( f" ** {contaminated} baseline run(s) invoked ripwire despite the no-ripwire contract " +if "note" in out: print( f" {out['note']}" ); return +if out["n_pairs"] != 27: failures.append( f"expected 27 paired runs, got {out['n_pairs']}" ) +if out["n_incomplete"] != 2: failures.append( f"expected 2 incomplete pairs (the orphan + the contaminated-basel +if out["n_repos"] != 3: failures.append( f"expected 3 repos, got {out['n_repos']}" ) +if out.get( "n_contaminated_baseline" ) != 1: failures.append( f"expected exactly 1 contaminated baseline run co +if not ( out["resolved_delta_mean"] > 0 ): failures.append( "expected a positive resolved-rate delta" ) +if not ( out["resolved_delta_bootstrap_95_lower"] > 0 ): failures.append( "expected a POSITIVE bootstrap 95% low +if out["tokens_out_ratio_p50"] is None or abs( out["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( f"e +if out.get( "n_resolved_pairs" ) != 27: failures.append( f"expected all 27 pairs resolution-scored, got {out.get +if out.get( "substitution_rate_baseline" ) != 0.0: failures.append( f"expected baseline substitution rate 0.0 (n +if out.get( "substitution_rate_ripwire" ) is None or abs( out["substitution_rate_ripwire"] - 0.75 ) > 1e-9: fail +if out.get( "n_substitution_ripwire" ) != 27: failures.append( f"expected 27 substitution-scored ripwire runs, g +if out3.get( "substitution_rate_ripwire" ) is not None or out3.get( "n_substitution_ripwire" ) != 0: failures.ap +if out2["n_pairs"] != 27: failures.append( f"evaluator-none: expected 27 pairs, got {out2['n_pairs']}" ) +if out2["n_resolved_pairs"] != 0: failures.append( f"evaluator-none: expected 0 resolution-scored pairs, got {ou +if out2["resolved_delta_mean"] is not None or out2["resolved_delta_bootstrap_95_lower"] is not None: failures.ap +if out2["tokens_out_ratio_p50"] is None or abs( out2["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( " +… [73 more display lines; full output is 16840 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --query="teleport pagerank" --top-k=5` + +*Raw BM25 ranking (debug lens; --for is the real verb).* + +**wall time: 1.11s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [21 more display lines; full output is 3219 bytes on 1 raw line(s)] +````` + + +--- + +# zoom the detail ladder + +## `./build/ripwire . --for="pagerank power iteration" --detail=2` + +*Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* + +**wall time: 1.88s** + +````` + + + + + +NodeId order. Renumbering an otherwise-identical graph (the exact "same probe file, different so…inline constexpr float kScoreTieAbsEps = 1e-6f + +Render one form of the disclosure. Empty string whenever there is nothing to say — no power it…inline std::string renderDisclosure( const RankDisclosure& d, DiscloseAs as ) + +The PageRank power iteration itself — the numeric kernel every ranked document's order comes f…PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double> r … [line truncated: 10 more bytes on this line] +inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) +struct RankedGraph +inline constexpr const char* kChurnRankLegend = "<!-- rank_by=churn: k= is PageRank re-run with the teleport BIASED by git CHANGE-FREQUENCY over window= " "(a c… +inline std::uint32_t sliceRdIterationCeiling() noexcept +struct PageRankRun + +What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure +inline std::uint32_t navRelevanceWeight( const Graph& g, NodeId n ) noexcept +inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) +inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window +inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +HOT_FILES = ( "src/pagerank.cpp", # the power-iteration loop — G2's no-allocation scope "src/infra/radixSort.h", # LSD radix entry points "src/infra/radixSort.inl", # the counting … [line truncated: 76 more bytes on this line] +handle_t leftmost_leaf() const +inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) +inline std::string doctorPathPrependLine( const std::string& selfPath ) +inline std::string doctorWhichRipwire( rw::os::stat_t& st ) +inline const char* rankByLegendFor( const char* label ) noexcept +std::uint32_t testIterationCeiling() noexcept +… [156 more display lines; full output is 22215 bytes on 72 raw line(s)] +````` + +## `./build/ripwire . --pack-signatures --top-k=10` + +*Body-elided decl skeletons — recounted on this corpus. Measured as element bytes: the signature+doc elements --pack-signatures emits, against the SAME symbols' full bodies from --expand, with the CORPUS-ROOT PREFIX SUBTRACTED FROM BOTH SIDES. That subtraction is the whole methodology and the figure is meaningless without it: the root repeats inside every element's id= and p=, it is not what this verb elides, and counting it makes the headline a function of how deep the checkout happens to sit on disk — on one corpus, three spellings of the same root read 18.6 points apart before the subtraction and agree exactly after it. Root-neutralised on THIS repo: 88.8% fewer bytes at top-10, 86.2% at top-50, 87.4% at top-100 (re-derived 2026-10-10 when the C++ standard-member gate stopped std::vector/std::string calls such as out.push_back() and s.empty() from binding to in-tree namesakes by name: the ranked top-10/50 lost one-line container accessors (svector::push_back, WidePath::c_str, every empty) and gained one-line forwarding helpers and macros (kindIs, ASSUME, formatTo, emitTo) whose signature is nearly the whole body, so less of the set is elidable, from 92.7/88.7/87.6; before that, re-derived 2026-09-20 when the native Windows port landed: src/infra/os_win32.cpp and os_win32_logic.h are ~3,900 lines of new, heavily commented infra that the ranked top-10/50/100 now reach, and their bodies are this ratio's DENOMINATOR, so the figure rises without --pack-signatures eliding anything new, from top-50 87.1. Measured by test/showcasecapturecheck.sh's own recount arm on the combined tree, which is the gate that would otherwise report the drift; before that, re-derived 2026-09-17 at the self-check macro vocabulary rename: VERIFY/VERIFY_TEXT/VERIFY_DEBUG_ONLY/VERIFY_NOT_REACHED/VERIFY_SAME_THREAD/VERIFY_NO_ALIAS*/DYNMAP_VERIFY/DEGRADED_PATH_ALERT renamed to ASSUME/EXPECTS/ENSURES/DASSERT/UNREACHABLE/ASSUME_SAME_THREAD*/ASSUME_NO_ALIAS*/DYNMAP_ASSUME/DISCLOSE (plus new VALIDATE sites) across 839 identifier renames in 179 files, and a hand-written Diagnostics.h/diagnostics.cpp replacing the old ones outright: this moves both WHICH symbols the ranked top-10/50/100 hold and how large their bodies are, from top-50 85.2. A real re-derivation of a corpus that changed, not a tolerance edit; previously re-derived 2026-09-12 when the rows dropped the path-repeating id= for the short sc= scope: the signature side shrank, the bodies did not; before that, 89.5% / 81.8% / 84.3% re-derived 2026-09-10 at the sibs= cap raise: kMaxExpandSibs went 8 -> 100, so --expand's bodies now carry the file context the old cap hid — 89.3% of all sibling names — and the body side is this ratio's DENOMINATOR, so the figure rises without --pack-signatures eliding anything new. Measured on a fixed tree with the top-50 membership and the signature side unchanged: top-50 from 71.0. A real re-derivation of a corpus that changed, not a tolerance edit; previously re-derived 2026-09-09 at the printf-family -> std::print conversion: converting ~1,500 emitter call sites to rw::emitTo/emitRaw/formatTo across 93 files changes how large the ranked symbols' BODIES are, and the body side is this ratio's denominator — top-50 from 72.3. A real re-derivation of a corpus that changed, not a tolerance edit; previously re-derived 2026-09-08 at the confident-zero round, issues #62/#63/#66: that change adds symbols to src/graphlegend.h and the new src/preprocdead.h and re-homes two long comment blocks from call sites onto the helpers they explain, which moves both WHICH symbols the ranked top-50 holds and how large their bodies are — top-50 from 74.0. A real re-derivation of a corpus that changed, not a tolerance edit; previously re-derived 2026-09-06 at the stranger-audit fix round: the doctor, cache-sweep and html-provenance bodies grew this corpus's BODY side, moving top-50 from 75.6 — a real re-derivation, not a tolerance edit; before that, re-derived 2026-09-05 at the capture-audit close: lane L7's P16 caps --expand's sibs= at 8 names, which SHRINKS the body side of this ratio and moved the figure down from 84.5/80.2/80.6 — the V1 2026-08-15 re-center, when sibs=/inc= first grew the body side from 70.0/61.0/63.8, in reverse; both were real re-derivations, not tolerance edits). top-50 is the number to quote, because the sigs payload is top-50 regardless of --top-k and is therefore what THIS command emits. A single small/trivial body can still invert it (signature+doc bigger than the body), like the --format=columnar sibling below. test/showcasecapturecheck.sh (C) re-derives all three from this repo every run, in the same quantity, and fails if the caption and the recount drift apart.* + +**wall time: 1.07s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [148 more display lines; full output is 14953 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --outline=rankGraphTeleport --top-k=0` + +*Control-flow skeleton of one symbol, payload-only via the new --top-k=0.* + +````` + + + + +& p, float alpha = 0.85f ) +{ + PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); + const std::vector pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + ... + } + std::vector r( N, 0.f ); + std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); + return { std::move( r ), run.iterationCount, run.hasConverged }; +} +]]> +````` + +## `./build/ripwire . --outline=rankGraphTeleport:1-10 --top-k=0` + +*CHANGED: a line range on --outline is now STRIPPED with a stderr note (it used to refuse).* + +````` + + + + +& p, float alpha = 0.85f ) +{ + PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); + const std::vector pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + ... + } + std::vector r( N, 0.f ); + std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); + return { std::move( r ), run.iterationCount, run.hasConverged }; +} +]]> +````` + +stderr: + +````` +ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — outlining the whole symbol (use --expand=rankGraphTeleport:1-10 for a body slice) +````` + +## `./build/ripwire . --expand=rankGraphTeleport --top-k=0` + +*Full body + inline callee signatures.* + +````` + + + + +: files the ranked rows show / do not (sum = files=), each named or counted below. : a program entry by its language's convention (C/Go/Rust main, Java/C# static main, a script main its module scope calls, __main__.py), callees as c rows; manifest entries not read. : n= unshown files under dir p=; f= names its code files, the rest (tests, docs, config, test/doc/example dirs) counted. declined=K: K call sites without edges, including inert Elixir quote sites after unquote/unquote_splicing re-entry. external=K: K calls proven outside the tree, no edge. locality_pinned=K: K calls pinned by locality alone (a guess). extent_suspect_syms=K: K defs failed containment, corpus-wide. macro_blanked_files=K: K files indexed from a macro-blanked re-parse. prov=scip|binding|import|split|final-segment: how that edge bound (absent: one unique name); split = one arm of an amb= pick; final-segment = a qualified type matched by last name only. files=/symbols=: files and symbols indexed; edges= distinct call edges; shown= symbols printed, a merged row counting each def; ambiguous= calls split over several defs, corpus-wide; unresolved= calls with in-tree evidence and no edge (every def language-filtered or unreachable, or binding refused); order= rows by rank (important-first, important-last; (auto:fill) = flipped past a size threshold) or by path (stable). skipped_oversize=K: K files over a size ceiling, not indexed. unindexed=ext:N: N text files of that extension no grammar reads (6 extensions at most). unindexed_exts=E: E such extensions in all, the list cut. : a non-test row an indexed test transitively reaches (absent otherwise, never 0). : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. sc=: enclosing scope; the full id is p::sc::n (p= of the row or its ) and selectors take it. amb=K: K calls split over several defs. locals_floor=1: locals= is a floor. role=hub: in= is 8 or more. s in= out= cx= ccx= loc= params= nest= cbo= amp=: callers, callees, cyclomatic/cognitive complexity, lines, parameters, nesting depth, coupled types, callers + co-changed files. ev=/ev_why=: essential complexity (2+: jumps block extract-method; absent: 1) / the jumps behind it, tag:count. ev_floor=1: ev= is a FLOOR; noreturn calls, macro-hidden exits and unresolved gotos are unseen. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. l=N: start line; only on a same-name overload's row, one row per body (bodyless decls fold into overloads=). --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [97 more display lines; full output is 10931 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --deps` + +*File->file dependency graph (god-files, cycles).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [899 more display lines; full output is 24018 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --hotspots` + +*Complexity x recent git churn (maintenance pain).* + +**wall time: 2.24s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [14 more display lines; full output is 5767 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --clones` + +*Token-normalized duplicate bodies.* + +**wall time: 2.41s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [308 more display lines; full output is 16222 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --cochange` + +*Files that change together in git (hidden coupling).* + +**wall time: 2.45s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --hotspots --since="2 weeks ago"` + +*Hotspots scoped to RECENT churn (the regression lens).* + +**wall time: 2.42s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 5151 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --arch=test/archfix/rules.txt` + +*Enforce layering rules (exit 2 on violation) — run against the repo's own test fixture rules.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [1029 more display lines; full output is 152096 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint` + +*Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* + +**wall time: 2.63s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [685 more display lines; full output is 70581 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint-rules=test/lintrulesfix/rules` + +*User lint rules (YAML, ast-grep style) from a directory.* + +**wall time: 1.09s** + +````` + + + + +use LOG() instead of printf +use LOG() instead of printf +use LOG() instead of printf +use LOG() instead of printf +use LOG() instead of printf + +````` + +stderr: + +````` +ripwire: lint-rules: test/lintrulesfix/rules/malformed.yaml:5: expected 'key: value' — file skipped +[math degraded] lint-rules: malformed rule file skipped (lintrules.h:399, auto rw::parseLintRuleFile(const std::string &, std::string_view, std::vector &)::(anonymous class)::operator()(std::size_t, const char *) const — logged once per site) +ripwire: AST query did not compile for any grammar: (this_is_not_a_real_node @x @@@ ((( ) +````` + +## `./build/ripwire . --lint --with-profile=report.txt` + +*Join MEASURED heat onto --lint findings — runs in a tiny fabricated demo corpus (one cache-pointer-chase-loop finding under a PROFILE_SCOPE site) because a real report needs a RIPWIRE_PROFILE build; the finding inside the profiled scope gains heat_* columns from the report's #PROF_TSV row.* + +Input file: + +````` +#PROF_TSV_BEGIN one row per scope, aggregated across threads; counters are RAW integers +scope file line calls total_ms l1d_mpki +walk: chase pass x.cpp 9 12 48.500 7.250 +#PROF_TSV_END +````` + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 3428 bytes on 1 raw line(s)] +````` + +The joined finding — past the display cut above, extracted so the join is visible: + +````` +p = p->next +````` + +## `./build/ripwire . --communities` + +*Cluster the call graph into cohesive modules.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [189 more display lines; full output is 16896 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --zoom` + +*Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels_shown="2" of levels= BY DEFAULT over the 40 largest top modules (~8 KB, where the whole tree is ~220 KB); a module AT the cut carries children=.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [135 more display lines; full output is 7291 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --zoom --zoom-levels=3` + +*The restoring knob: --zoom-levels=N prints N levels (0 = the whole tree); here three, ~11 KB.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [271 more display lines; full output is 12259 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --report` + +*Architecture summary (modules, god-files, cycles) as markdown.* + +````` + + +# ripwire architecture report + +3589 files · 31640 symbols · 29882 edges · 3588 modules (14830 call-graph isolated) + +Root: `.` + +Call-graph isolate provenance: 3951 declaration, 2444 header, 3330 source, 5105 document; 0 connected Louvain singletons + +## Modules (call-graph clusters; showing 12 of 3588) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1617 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 547 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 335 symbols +- **docs::`--top-k=N`@COMMANDS.md:59:9077 [pack,allow,insert]** — 181 symbols +- **test/ownerhopfix/py/pkg::big_handler_001@broadcast.py:3:72** — 106 symbols +- **src/infra::fail@os_win32.cpp:63:2553 [open,get,close]** — 89 symbols +- **test::no@estchargecheck.sh:52:4108 [skip,set]** — 63 symbols +- **src::child@ingest_valuerefs.h:656:33874 [emit,resolve,open]** — 62 symbols +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,compute,expand]** — 54 symbols +- **src::sliceKindIs@slice.h:368:24239 [match,collect,push]** — 48 symbols +- **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [insert,erase,find]** — 44 symbols +- **src::tokText@defectshape.h:128:5308 [scan,find,join]** — 41 symbols + +## God files (most depended-on; showing 10 of 228) +- `src/model.h` — 103 dependents +- `src/infra/emit.h` — 87 dependents +- `src/infra/Diagnostics.h` — 65 dependents +- `src/graph.h` — 46 dependents +- `src/serialize.h` — 45 dependents +… [35 more lines, 3965 bytes total] +````` + +## `./build/ripwire . --seams` + +*Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [57 more display lines; full output is 9051 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --mermaid` + +*Module (directory) dependency graph as a Mermaid diagram.* + +````` +%% ripwire --mermaid: module (directory) dependency graph — node = dir (symbol count), edge = inter-module calls (>= 3). Render at mermaid.live. +flowchart LR + subgraph sg0 ["src"] + n102["src
9384"] + n103["src/infra
1301"] + end + subgraph sg1 ["test"] + n104["test
5501"] + n559["test/legochargefix
546"] + n400["test/fixtures/recallpassage
314"] + n944["test/slotjoinfix
175"] + n786["test/ownerhopfix/py/pkg
158"] + n342["test/expandmodefix
151"] + n344["test/expandsibsfix
149"] + n927["test/rubyschemafix
99"] + n419["test/forbodyfoldfix/py
98"] + n909["test/rubyattrsfix
85"] + n722["test/lib
81"] + end + n74["docs
858"] + n2[".github
625"] + n0["(root)
606"] + subgraph sg5 ["bench"] + n4["bench
545"] + n5["bench/agentloop
331"] + n49["bench/locbench/results/r5_pooling
237"] + n50["bench/locbench/results/r6_expansion
187"] + n59["bench/recalleval
113"] + n43["bench/locbench
107"] + n38["bench/headtohead/r3-headroom-2026-08-03
102"] +… [20 more lines, 1592 bytes total] +````` + +## `./build/ripwire . --owners` + +*Bus-factor: recency-weighted author ownership per file.* + +**wall time: 2.34s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [971 more display lines; full output is 106097 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --dead-code=src` + +*Internal functions with no caller found in the index — a name-based graph reading, not a confidence score. NOTE the filter is a path-COMPONENT match: 'src' matches any .../src/... segment; use ./src to pin the root directory.* + +````` + + + + +````` + +## `./build/ripwire . --exercises=test/regression.sh` + +*Which symbols a TEST FILE exercises — the reverse direction of --affected.* + +````` + + + +````` + +## `./build/ripwire . --community=0` + +*Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* + +````` + + + +````` + +## `./build/ripwire . --quality-delta` + +*On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* + +**wall time: 26.04s** + +````` + + +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +````` + +## `./build/ripwire . --quality-delta --show-stale` + +*The same clean verdict with what it folded printed back: every stale acknowledgement as an row and the full header. The regression-free answer above keeps only stale= and stale_by_kind= (and stale_next= names this call), and folds only when that saves a quarter of the bytes. Refused without --quality-delta.* + +**wall time: 19.68s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [139 more display lines; full output is 16104 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +````` + +## `./build/ripwire . --edit-check=rankGraphTeleport` + +*Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree).* + +**wall time: 14.43s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*No-LLM review-evidence bundle for the working-tree diff (clean tree = empty).* + +**wall time: 2.16s** + +````` + + + +````` + +## `./build/ripwire . --pr-context=HEAD~1` + +*The BASEREF form: diffed against merge-base(BASEREF, HEAD), never the ref tip — here the previous commit on the current line (a ref with NO merge base falls back to a disclosed two-dot diff: anchor="ref-tip-two-dot").* + +**wall time: 3.76s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [211 more display lines; full output is 14974 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --merge-scout=HEAD~2,HEAD~1` + +*Pairwise cross-arm conflict sites + suggested landing order (any committish sharing a merge base with HEAD works as an arm; one that does not is reported ok="0", never compared).* + +**wall time: 19.11s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --stray-content=lane/` + +*Which refs of the `lane/` ref family still hold divergent authored work vs HEAD, with verdicts.* + +**wall time: 29.87s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [200 more display lines; full output is 27194 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --stray-content=arm/for-howitworks-067-f2add` + +*A second selection, the one branch `arm/for-howitworks-067-f2add` (no ref family left to select on this checkout), picked at capture time from the refs this checkout really has and never the selection above: merged refs are OMITTED from the rows and counted in merged=; refs sharing no merge base with HEAD (a shallow clone, or a pre-rewrite history) land in unknown= with ok="0" — the counters always reconcile against refs=.* + +**wall time: 2.32s** + +````` + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --stray-content=lane/ --plan` + +*Select the genuinely-unmerged refs of the `lane/` ref family and feed them to merge-scout for a landing order (a fully merged selection yields an empty landing set — still a measurement, disclosed on the root).* + +**wall time: 122.29s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [33520 more display lines; full output is 4640491 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --stray-content=zzzz-no-such-ref --plan` + +*A --plan filter that selects NO ref REFUSES (exit 1) naming the substring — before the wave-3 close this fell through to the '>512 refs match' sentence, and --abi under the same filter answered an empty measurement at exit 0.* + +**exit code: 1** — **wall time: 3.12s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zero here would be a failure, not a measurement + (the filter is a substring match against refs/heads names; run bare --stray-content to list them, e.g. --stray-content=feat/) +````` + +## `./build/ripwire . --stray-content=lane/ --abi` + +*Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract drift on each ref's AUTHORED paths — exit 2 when any drift row is found (the only kind that gates), 0 when the compared refs are clean, and exit 1 if the --stray-content filter matches no ref at all.* + +**exit code: 2** — **wall time: 91.49s** + +````` + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --whereis=rankGraphTeleport` + +*Which ref's tree defines or mentions SYM — HEAD first, then every local branch. The default lists the kind="def" rows (here the first 60, most on branches, each carrying tip= and date=) and counts the references in : on this symbol HEAD's references would fill the 60-row cap before the branch definitions arrive, so the whole list (below) shows fewer definitions, and the default serves the page that shows more of them, whatever its bytes.* + +**wall time: 10.27s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --whereis=rankGraphTeleport --whereis-listing=all` + +*The whole hit list asked for by name: under the same 60-row cap HEAD's references fill the list, so it shows far fewer definitions (here HEAD's one) than the default above (here 60). The default serves the all page only when it shows the same definitions as the defs page in strictly fewer bytes (a tie serves it too).* + +**wall time: 10.16s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [36 more display lines; full output is 13362 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --quality-delta=HEAD~1..HEAD --quality-kinds=complexity,dead-code` + +*A ref range run for two of the twelve kinds only: the named kinds' rows are exactly their rows in the full answer, the header names the others (kinds_skipped=, skipped_why="selector"), and a pass no named kind needs does not run. The replay a memory-stopped range answer hands back (kinds_next=) is this flag. Refused without --quality-delta=A..B.* + +**wall time: 14.93s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [35 more display lines; full output is 14375 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --whereis=computeOnePairOverlap --with-history` + +*Same, plus a git-history row (never / removed-by-commit) for names no tree carries.* + +**wall time: 18.14s** + +````` + + + + + + + +````` + +## `./build/ripwire . --flags` + +*The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gates from comments/heredocs, so the count only reflects real ifndef/define, CMake option(), and getenv gates.* + +**wall time: 1.29s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [400 more display lines; full output is 24541 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --flags --flip=RIPWIRE_ASAN` + +*Blast radius of turning ONE gate on: live code, symbols, transitive reach, covering tests.* + +**wall time: 1.07s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --flags --flip=RIPWIRE_ASA` + +*Unknown-gate refusal (exit 1) with a did-you-mean from a real edit distance (one character off RIPWIRE_ASAN).* + +**exit code: 1** — **wall time: 1.08s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --flip: no gate named 'RIPWIRE_ASA' in . (did you mean 'RIPWIRE_ASAN'?) +ripwire: run `ripwire . --flags` for the gate table +````` + +## `./build/ripwire . --plan-lanes=3 --task="add a --since filter to the doc-drift verb and cover it with tests"` + +*NEW VERB: pre-hoc lane plan — which of 3 parallel worktrees would COLLIDE, before a line is written. JSON on stdout.* + +**wall time: 2.69s** + +````` +{"v":1,"verb":"plan-lanes","at":"c97f6f91c","root":".","task":"add a --since filter to the doc-drift verb and cover it with tests","source":"partition","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3589,"symbols":31640,"edges":298 … [line truncated: 338 more bytes on this line] +"symbols":[{"p":"./src/docdrift.h","n":"DriftResult","scope":"DriftResult","l":286,"id":"./src/docdrift.h::DriftResult::DriftResult"}, +{"p":"./src/docdrift.h","n":"computeDocDrift","scope":"docdrift","l":2441,"id":"./src/docdrift.h::docdrift::computeDocDrift"}, +{"p":"./src/docdrift.h","n":"kDocDriftLegend","scope":"docdrift","l":2739,"id":"./src/docdrift.h::docdrift::kDocDriftLegend"}, +{"p":"./src/docdrift.h","n":"writeDocDriftPage","scope":"docdrift","l":2909,"id":"./src/docdrift.h::docdrift::writeDocDriftPage"}, +{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","l":655,"id":"./src/mcpverbs.h::rw::docDriftText"}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","l":1678,"id":null}]},"lanes":[{"id":"lane-0","task":"add a --since filter to the doc-drift verb and cover it with tests","claims":{"symbols":[{"p":"./src/verbs_change.h","n":"runAbiCheck","scope":"","key":"183da9aaf74a4a92","id":null,"id_addr … [line truncated: 113 more bytes on this line] +{"p":"./src/editcheck.h","n":"editCheckPriceRoot","scope":"rw","key":"33219bb04158a6a8","id":"./src/editcheck.h::rw::editCheckPriceRoot","id_addressable":true,"id_collides_with":0,"l":1202,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":10,"churn":57,"tested":0}, +{"p":"./src/docdrift.h","n":"writeGateability","scope":"docdrift","key":"42c456dfb55faee4","id":"./src/docdrift.h::docdrift::writeGateability","id_addressable":true,"id_collides_with":0,"l":2685,"ord":0,"overloads":1,"amb":2,"cx":6,"ccx":6,"churn":36,"tested":0}, +{"p":"./src/darkflags.h","n":"writeFlags","scope":"darkflags","key":"5c2c4a2b311b8dba","id":"./src/darkflags.h::darkflags::writeFlags","id_addressable":true,"id_collides_with":0,"l":1298,"ord":0,"overloads":1,"amb":1,"cx":8,"ccx":8,"churn":32,"tested":0}, +{"p":"./src/docdrift.h","n":"docDriftNextAttr","scope":"docdrift","key":"7723ed789a1c9c09","id":"./src/docdrift.h::docdrift::docDriftNextAttr","id_addressable":true,"id_collides_with":0,"l":2864,"ord":0,"overloads":1,"amb":0,"cx":6,"ccx":5,"churn":36,"tested":0}, +{"p":"./src/docdrift.h","n":"recordWeakDisclosures","scope":"docdrift","key":"972779210e49c0cf","id":"./src/docdrift.h::docdrift::recordWeakDisclosures","id_addressable":true,"id_collides_with":0,"l":2430,"ord":0,"overloads":1,"amb":0,"cx":2,"ccx":1,"churn":36,"tested":0}, +{"p":"./src/testmap.h","n":"scriptGatesUnmodelledCount","scope":"rw","key":"98c4487e2e9e08bc","id":"./src/testmap.h::rw::scriptGatesUnmodelledCount","id_addressable":true,"id_collides_with":0,"l":1793,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":61,"tested":0}, +{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":3031,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":11,"churn":453,"tested":0}, +{"p":"./src/pageview.h","n":"secondaryCutAttrs","scope":"rw","key":"a10ea8d3bdca1dfb","id":"./src/pageview.h::rw::secondaryCutAttrs","id_addressable":true,"id_collides_with":0,"l":399,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":28,"tested":0}, +{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":691,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":453,"tested":0}, +{"p":"./src/docdrift.h","n":"sortDocsByLiveDrift","scope":"docdrift","key":"d5de682411b9a9a9","id":"./src/docdrift.h::docdrift::sortDocsByLiveDrift","id_addressable":true,"id_collides_with":0,"l":2390,"ord":0,"overloads":1,"amb":0,"cx":2,"ccx":2,"churn":36,"tested":0}, +{"p":"./src/mcpverbs.h","n":"unknownSubVerbRefusal","scope":"rw","key":"e336d39b0a6addea","id":"./src/mcpverbs.h::rw::unknownSubVerbRefusal","id_addressable":true,"id_collides_with":0,"l":5838,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":459,"tested":0}], +"files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":55}, +{"p":"./src/docdrift.h","symbols":4,"churn":36,"ccx":14,"hotspot_rank":38}, +{"p":"./src/editcheck.h","symbols":1,"churn":57,"ccx":10,"hotspot_rank":41}, +{"p":"./src/main.cpp","symbols":2,"churn":453,"ccx":12,"hotspot_rank":5}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":459,"ccx":2,"hotspot_rank":4}, +{"p":"./src/pageview.h","symbols":1,"churn":28,"ccx":2,"hotspot_rank":129}, +{"p":"./src/testmap.h","symbols":1,"churn":61,"ccx":4,"hotspot_rank":33}, +{"p":"./src/verbs_change.h","symbols":1,"churn":82,"ccx":9,"hotspot_rank":25}]},"blast_radius":{"reaches":47,"files_total":16,"capped":false,"files":["./src/darkflags.h","./src/docdrift.h","./src/editcheck.h","./src/editplan.h","./src/editpreview.h","./src/flipimpact.h","./src/main.cpp","./src/mcp.h … [line truncated: 182 more bytes on this line] +"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":47,"module_span":1,"notes":[], +"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-terra","reasoning":"high","rule":"moderate-cross-module","basis":"structural-only","signals":{"claims":12,"files":8,"module_span":1,"max_ccx":11,"sum_ccx":61,"ambiguous_calls":6,"blast_reaches":47,"contract_touches":0,"conflicts":0,"untested":47 … [line truncated: 142 more bytes on this line] +{"id":"lane-1","task":"add a --since filter to the doc-drift verb and cover it with tests","claims":{"symbols":[{"p":"./src/gitmine.h","n":"gitWindowBoundarySha","scope":"rw","key":"18627516699d7062","id":"./src/gitmine.h::rw::gitWindowBoundarySha","id_addressable":true,"id_collides_with":0,"l":1674 … [line truncated: 69 more bytes on this line] +{"p":"./src/cli.h","n":"kViewFlags","scope":"rw","key":"2a2f2487082cc6d8","id":"./src/cli.h::rw::kViewFlags","id_addressable":true,"id_collides_with":0,"l":3327,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":511,"tested":0}, +… [69 more display lines; full output is 20481 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --plan-lanes --brief=/aux/lanes_brief.txt` + +*NEW VERB, explicit form: one line per lane, lane boundaries are the ones you wrote (the defensible mode).* + +**wall time: 3.28s** + +Input file: + +````` +add a --since filter to the doc-drift verb +add the CLI parse arm and help text for the new filter +write regression tests for the new filter +````` + +````` +{"v":1,"verb":"plan-lanes","at":"c97f6f91c","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3589,"symbols":31640,"edges":29882,"ambiguous":2925,"unresolved":15128},"carve":null,"core":{"files" … [line truncated: 4 more bytes on this line] +"symbols":[]},"lanes":[{"id":"lane-0","task":"add a --since filter to the doc-drift verb","claims":{"symbols":[{"p":"./src/docdrift.h","n":"recordUnchecked","scope":"docdrift","key":"12641dab14abc8fd","id":"./src/docdrift.h::docdrift::recordUnchecked","id_addressable":true,"id_collides_with":0,"l":2 … [line truncated: 72 more bytes on this line] +{"p":"./src/docdrift.h","n":"sortWeakGroupsByPath","scope":"docdrift","key":"1944ba506280ff80","id":"./src/docdrift.h::docdrift::sortWeakGroupsByPath","id_addressable":true,"id_collides_with":0,"l":2401,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":36,"tested":0}, +{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","key":"1fa68e8d93c05a59","id":"./src/mcpverbs.h::rw::docDriftText","id_addressable":true,"id_collides_with":0,"l":655,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":459,"tested":0}, +{"p":"./src/cli.h","n":"kViewFlags","scope":"rw","key":"2a2f2487082cc6d8","id":"./src/cli.h::rw::kViewFlags","id_addressable":true,"id_collides_with":0,"l":3327,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":511,"tested":0}, +{"p":"./src/recall.h","n":"docFileMask","scope":"rw","key":"3149a219f599664c","id":"./src/recall.h::rw::docFileMask","id_addressable":true,"id_collides_with":0,"l":110,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":27,"tested":0}, +{"p":"./src/docdrift.h","n":"writeDocDriftPage","scope":"docdrift","key":"380b7de5df1cfd73","id":"./src/docdrift.h::docdrift::writeDocDriftPage","id_addressable":true,"id_collides_with":0,"l":2909,"ord":0,"overloads":1,"amb":10,"cx":14,"ccx":15,"churn":36,"tested":0}, +{"p":"./src/docdrift.h","n":"computeDocDrift","scope":"docdrift","key":"3b19cc3d8996c3b2","id":"./src/docdrift.h::docdrift::computeDocDrift","id_addressable":true,"id_collides_with":0,"l":2441,"ord":0,"overloads":1,"amb":1,"cx":26,"ccx":38,"churn":36,"tested":0}, +{"p":"./src/docdrift.h","n":"DriftResult","scope":"DriftResult","key":"422ab39546b6e0bd","id":"./src/docdrift.h::DriftResult::DriftResult","id_addressable":true,"id_collides_with":0,"l":286,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"tested":0}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","key":"904831e5c0f66b39","id":null,"id_addressable":false,"id_collides_with":0,"l":1678,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":82,"tested":0}, +{"p":"./src/ensemble.h","n":"kEnsembleChurnSince","scope":"ensemble","key":"a34fc78c05bf56a8","id":"./src/ensemble.h::ensemble::kEnsembleChurnSince","id_addressable":true,"id_collides_with":0,"l":120,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":23,"tested":0}, +{"p":"./src/mcpverbs.h","n":"unknownSubVerbRefusal","scope":"rw","key":"e336d39b0a6addea","id":"./src/mcpverbs.h::rw::unknownSubVerbRefusal","id_addressable":true,"id_collides_with":0,"l":5838,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":459,"tested":0}, +{"p":"./src/docdrift.h","n":"kDocDriftLegend","scope":"docdrift","key":"ff2ba74637bbbebf","id":"./src/docdrift.h::docdrift::kDocDriftLegend","id_addressable":true,"id_collides_with":0,"l":2739,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"tested":0}], +"files":[{"p":"./src/cli.h","symbols":1,"churn":511,"ccx":0,"hotspot_rank":6}, +{"p":"./src/docdrift.h","symbols":6,"churn":36,"ccx":54,"hotspot_rank":38}, +{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":105}, +{"p":"./src/mcpverbs.h","symbols":2,"churn":459,"ccx":2,"hotspot_rank":4}, +{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":64}, +{"p":"./src/verbs_change.h","symbols":1,"churn":82,"ccx":4,"hotspot_rank":25}], +"symbols_total":3455,"symbols_capped":true},"blast_radius":{"reaches":13,"files_total":7,"capped":false,"files":["./src/docdrift.h","./src/main.cpp","./src/mcp.h","./src/mcpserver.h","./src/mcpverbs.h","./src/recall.h","./src/verbs_for.h"]},"tests_to_run":[], +"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":13,"module_span":6,"notes":[], +"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":12,"files":6,"module_span":6,"max_ccx":38,"sum_ccx":64,"ambiguous_calls":12,"blast_reaches":13,"contract_touches":1,"conflicts":0,"untested":13,"t … [line truncated: 139 more bytes on this line] +{"id":"lane-1","task":"add the CLI parse arm and help text for the new filter","claims":{"symbols":[{"p":"./scripts/optremarks.py","n":"main","scope":"","key":"01b3b880f77d1512","id":null,"id_addressable":false,"id_collides_with":133,"l":279,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":14, … [line truncated: 12 more bytes on this line] +{"p":"./src/cli.h","n":"HelpLine","scope":"rw","key":"1489844dbc310b82","id":"./src/cli.h::rw::HelpLine","id_addressable":true,"id_collides_with":0,"l":2948,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":511,"tested":0}, +{"p":"./src/cli.h","n":"HelpFilter","scope":"HelpFilter","key":"3103a5e8bfc33d38","id":"./src/cli.h::HelpFilter::HelpFilter","id_addressable":true,"id_collides_with":0,"l":3027,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":511,"tested":0}, +{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":133,"l":865,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":17,"tested":0}, +{"p":"./src/mcprefusal.h","n":"kMcpValueFields","scope":"rw::mcprefuse","key":"4a7106e488a2aa80","id":"./src/mcprefusal.h::rw::mcprefuse::kMcpValueFields","id_addressable":true,"id_collides_with":0,"l":304,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":49,"tested":0}, +{"p":"./src/cli.h","n":"serveHelpSelector","scope":"rw","key":"4b247ee064fc4e94","id":"./src/cli.h::rw::serveHelpSelector","id_addressable":true,"id_collides_with":0,"l":3092,"ord":0,"overloads":1,"amb":1,"cx":4,"ccx":3,"churn":511,"tested":0}, +{"p":"./src/query.h","n":"tryParsePredicateOnAll","scope":"query","key":"786acc4dbfec612b","id":"./src/query.h::query::tryParsePredicateOnAll","id_addressable":false,"id_collides_with":1,"l":113,"ord":0,"overloads":2,"amb":0,"cx":1,"ccx":0,"churn":20,"tested":0}, +{"p":"./src/cli.h","n":"printUsageTier","scope":"rw","key":"82b7af67acdca479","id":"./src/cli.h::rw::printUsageTier","id_addressable":true,"id_collides_with":0,"l":3078,"ord":0,"overloads":1,"amb":0,"cx":2,"ccx":1,"churn":511,"tested":0}, +… [60 more display lines; full output is 18606 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --plan-lanes=99 --task=x` + +*Out-of-range refusal shape for the lane count.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out) +````` + +## `./build/ripwire . --layout=Symbol` + +*CPU/GPU contract view of one struct: computed offsets/sizes/padding + mirror check.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --layout=Lang` + +*The honest refusal (exit 1): Lang is an `enum class`, not a struct — no offsets are fabricated.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped enum's underlying type is not a byte layout) +````` + +## `./build/ripwire . --doc-drift` + +*Which of this repo's doc claims are now false. CHANGED: row attribute at= renamed to tgt= (at= is now only the root sha stamp).* + +**wall time: 1.28s** + +````` + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + +… [226 more display lines; full output is 23854 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --doc-drift --gateability` + +*The finishable to-do list: docs whose LIVE failing anchors a date-stamp would reclassify.* + +**wall time: 1.23s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [253 more display lines; full output is 25109 bytes on 1 raw line(s)] +````` + +Tail of the same output — the `` section: + +````` + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --doc-drift --with-history` + +*Same report, with git history splitting stale mentions into deleted-by-commit vs never-existed.* + +**wall time: 1.84s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [206 more display lines; full output is 23833 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --from-trace=-` + +*Map a pasted stack trace onto indexed symbols. CHANGED: in_corpus= now reports the real count (was 0).* + +Input file: + +````` +AddressSanitizer:DEADLYSIGNAL +================================================================= +==41337==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000018 (pc 0x000102f4a1c8 bp 0x00016d2f1a40 sp 0x00016d2f19e0 T0) + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:11751 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:11792 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 + #3 0x102e01a44 in main src/main.cpp:4174 + #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) +==41337==ABORTING +````` + +````` + + + + + + + + + + + +PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] + +uniform-teleport PageRank (the defaultinline RankedGraph rankGraph( const Graph& g, float alpha = 0.85f ) +int runDefaultMap( const MainDispatch& d ) +int main( int argc, char** argv ) + + + +& p, float alpha = 0.85f ) +{ + PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); + const std::vector pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + double teleportMass = 0.0; +… [18 more display lines; full output is 4915 bytes on 29 raw line(s)] +````` + +## `./build/ripwire . --notes` + +*List all field notes (write-side memory) — the committed .ripwire_notes at the repo root, each with the sha/branch it was recorded at.* + +````` + + + + + + + + + + + gate scripts' claim (~line 1305) is NOT enforced — the derived-vs-stated sibling loop here covers docs/EVALS.md only. It drifted 407→451 unnoticed (fixed 2026-08-23). To close: grep both files ('file:line:' parsing) in the gateCountClaims arm.]]> + + + + +````` + +## `./build/ripwire . --pack-task="add a new output format flag to the CLI"` + +*ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* + +````` + + + + +Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept + +claude's `mcp add … -- ripwire --mcp`: the flag goes on the end of that command line, ahead of its newlineinline std::string wrapCliAddPost( const AgentTarget& row, std::string_view toolsArg ) + +ONE recipe path for every agent that can shell out, and the point is that the RECOMMENDATION does not vary by agent — only the MCP alternative does. Before this there were four near-identical CLI-fiinline void wrapEmitCliFirst( const AgentTarget& row, const std::string& token, c … [line truncated: 83 more bytes on this line] + +mcp-tools pass-through. Written into the server command only where its argument shape is known to be one more plain argument: the JSON args arrays (cursor/windsurf/gemini), opencode's command arrayinline bool wrapWritesToolsArg( const AgentTarget& row ) noexcept + +L8 (train 29): the NEXT PAGE of a callers listing — the CLI --callers root's next=, find_referencing_symbols' next and find_symbol's calledBy_next all name this one call, so the three cannot spell tinline std::string callHierarchyPageNext( bool wantCallers, std::string_vi … [line truncated: 98 more bytes on this line] +inline std::string classNewGroupXml( const IngestResult& ing, std::span<const ClassNewRow> rows, std::string_view rootPrefix, EscapeFn ex ) + + + + + + + + + +… [67 more display lines; full output is 12092 bytes on 59 raw line(s)] +````` + +## `./build/ripwire . --pack-task="add a new output format flag to the CLI" --partition=3` + +*Fan-out form: one shared core + 3 per-agent slices carved along call-graph communities.* + +**wall time: 1.17s** + +````` + wraps a ; bundle role=core|partition i= symbols= modules= bytes= tokens=: one agent's ctx, symbols= ids assigned, bytes= its size; tokens= = est_tokens= = bytes/2.36 (flat, the densest rate; the inner ctx est_tokens= is language-weighted). Inner ctx task= root= budget_tokens= dropped_positive=: the task, p= base, the slice's token target, ranked candidates its budget cut. of_top=: rows ranked (far) / bodies (callers); s rel=caller|callee shared=: 1-hop edge, bodies reached (absent at 1). window: shown= total= capped= (capped=1 cut). run_unknown=1: no runner derivable (a guess would be worse). est_tokens=: price as emitted (an upper bound under compact). : rank N in this ranking, rows in r= order. : cyclomatic/cognitive complexity. : N callers in the index (absent: not measured). route=: the ranker: name-exact(X) = the task names symbol X (anchors: its evidence), subtoken+body = conceptual BM25 (:broad = 1-2 plain words, plain rg may also win; :declined(...) = a name hit refused as a common name). sc=: enclosing scope; the full id is p::sc::n (p= of the row or its ) and selectors take it. next=: the one pasteable follow-up. pure=1: const/constexpr signature (Swift: non-mutating) and no transitive side effect found; a hint. partitions=/requested=: partitions carved / asked for. modules=/split=: call-graph groups found / cuts forced to split one. core_symbols=/surface=: ids in the shared core / core plus the assignable remainder. budget_per_agent_tokens=: one agent's budget, core_budget_tokens= plus partition_budget_tokens=. total_bytes=N: bytes of all bundles together. overlap_mean=/overlap_max=: pairwise Jaccard over the ids partitions name, before trimming. shared_symbols=/union_symbols=: ids two or more partitions name / ids any partition names. core_overlap=: share of the core surface a partition reaches anyway. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. --> + + + + + +Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept + +claude's `mcp add … -- ripwire --mcp`: the flag goes on the end of that command line, ahead of…inline std::string wrapCliAddPost( const AgentTarget& row, std::string_view toolsArg ) + +ONE recipe path for every agent that can shell out, and the point is that the RECOMMENDATION doe…inline void wrapEmitCliFirst( const AgentTarget& row, const std::string& token, const std::string_view executablePath, const WrapListing& listing ) noexcept + +mcp-tools pass-through. Written into the server command only where its argument shape is known t…inline bool wrapWritesToolsArg( const AgentTarget& row ) noexcept + + + + + +inline bool printUsageTier( std::FILE* out, HelpTier tier, std::string_view want ) noexcept + + + + + + + + +NodeId order. Renumbering an otherwise-identical graph (the exact "same probe file, different so…inline constexpr float kScoreTieAbsEps = 1e-6f + +Render one form of the disclosure. Empty string whenever there is nothing to say — no power it…inline std::string renderDisclosure( const RankDisclosure& d, DiscloseAs as ) + +The PageRank power iteration itself — the numeric kernel every ranked document's order comes f…PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double> r … [line truncated: 10 more bytes on this line] +inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) +struct RankedGraph +inline constexpr const char* kChurnRankLegend = "<!-- rank_by=churn: k= is PageRank re-run with the teleport BIASED by git CHANGE-FREQUENCY over window= " "(a c… +inline std::uint32_t sliceRdIterationCeiling() noexcept +struct PageRankRun + +What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure +inline std::uint32_t navRelevanceWeight( const Graph& g, NodeId n ) noexcept +inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) +inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window +inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +HOT_FILES = ( "src/pagerank.cpp", # the power-iteration loop — G2's no-allocation scope "src/infra/radixSort.h", # LSD radix entry points "src/infra/radixSort.inl", # the counting … [line truncated: 76 more bytes on this line] +handle_t leftmost_leaf() const +inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) +inline std::string doctorPathPrependLine( const std::string& selfPath ) +inline std::string doctorWhichRipwire( rw::os::stat_t& st ) +inline const char* rankByLegendFor( const char* label ) noexcept +std::uint32_t testIterationCeiling() noexcept +… [124 more display lines; full output is 19081 bytes on 10 raw line(s)] +````` + +## `./build/ripwire . --export=cc.json:/aux/ripwire2.cc.json` + +*Per-file metrics as CodeCharta cc.json.* + +**wall time: 2.28s** + +````` +(empty) +````` + +Artifact written: + +````` + 556185 /aux/ripwire2.cc.json +{"projectName":"project","apiVersion":"1.3","attributeDescriptors":{"loc":{"title":"Lines of Code","description":"Physical line count","direction":-1},"symbols":{"title":"Symbols","description":"Definitions in the file","direction":-1},"cx":{"title":"Cyclomatic Complexity","description":"Sum of per-symbol cyclomatic complexity","direction":-1},"cognitive_cx":{"title":"Cognitive Complexity","descri +````` + +## `./build/ripwire . --batch=/aux/batch2.txt` + +*One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* + +**wall time: 2.65s** + +Input file: + +````` +for:incremental cache invalidation +callers:rankGraphTeleport +grep:DISCLOSE +lego:Vehicle +````` + +````` + + + + + + + + + +incremental cache (--cache): per-file content hash + raw facts so a re-run re-parses ONLY c…constexpr std::uint32_t kCacheMagic = 0x4b505443 + +Composed exactly the way every OTHER blob family is (quality.h): one fixed-width identity hex pe…inline std::string spanTierMemoPath( const std::string& diskPath ) + +Ingest the tree at `sha`, materialized out of `root`'s object store. The HEAD side reuses the SA…inline bool ingestCommitTree( const std::string& root, const std::string& sha, const std::vector<std::string>& excludes, std::size_t maxFileBytes, IngestResult&… … [line truncated: 4 more bytes on this line] + +P1-15 — the `_reingest` envelope field for a response whose handling ran an INCREMENTAL pass, …inline bool mcpRefreshedThisRequest( std::uint64_t passesAtEntry ) +struct FileHealth +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::ve … [line truncated: 8 more bytes on this line] +inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… +inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, const CacheLoadStats& cac … [line truncated: 66 more bytes on this line] +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline const McpIndex& getIndex( const std::string& root ) +inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) +struct McpIndex +inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) +IngestResult ingest( const char* rootDir, const std::vector<std::string>& excludeSubstr, std::string_view cacheFile, std::size_t maxFileBytes, bool captureValueUses, std::string_view excludeLabel, bool … [line truncated: 37 more bytes on this line] +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) +static bool cachePathIsDirectory( const std::string& cachePath ) +inline std::string oracleCachePath( const std::string& root, const std::string& headSha ) +HashMap<const TSLanguage*, TSQuery*>& compiledQueryCache() +… [107 more display lines; full output is 51596 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: cache : over its 2048 MiB budget — evicted 199 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) +````` + + +--- + +# self-diagnosis + +## `./build/ripwire . --doctor` + +*Environment self-check: binary staleness, grammars, cache dir, git, tracked-binary staleness — exit 1 when any check fails (here: the PATH install is older than ./build).* + +**exit code: 1** — **wall time: 10.26s** + +````` + + + + + + + + + + +````` + + +--- + +# security + +## `./build/ripwire --scan-skill=skills/ripwire-orient/SKILL.md` + +*Scan a single skill file for injection/exfiltration patterns before installing.* + +````` + + + +````` + +stderr: + +````` +ripwire scan: 0 finding(s) in skills/ripwire-orient/SKILL.md +````` + +## `./build/ripwire --scan-skills=skills` + +*Scan a whole skills directory (exit 2 = CRITICAL, 1 = WARN). Explicit-DIR form only.* + +````` + + + +````` + +stderr: + +````` +ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s) skipped, 0 denylisted subtree(s) not descended) +````` + + +--- + +# knobs / modes + +## `./build/ripwire . --rank-by=churn --top-k=5` + +*Rank by git change-frequency prior instead of PageRank.* + +**wall time: 2.36s** + +````` + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~6 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3` + +*Scope the recent-changes answer to ONE directory. The global block stays byte-identical, a second page follows it — n=/of= are its counts (of= IS the total, so the paging half carries no total=), capped="1" has_more="1" next_offset= offset= limit= page it, and next= replays THIS run's own corpus flags (--since/--exclude) so the page it names is a page of the same answer. The symbol map collapses to a disclosed stub — the map was not asked for and was not ranked at all, which is why the header carries no pr_iters=. merge_bombs_skipped= stays on the global block: it counts the window's skipped commits, not the directory's.* + +**wall time: 2.08s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --rank-by=bogus --top-k=5` + +*An unknown value REFUSES (exit 1), NAMED, with the supported set listed.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf|churn|churn-decay) +````` + +## `./build/ripwire . --callers=rankGraphTeleport --format=columnar` + +*Columnar output: paths table + parallel arrays, ~15-60% fewer tokens on MANY-row lists — small results can be LARGER (the columnar legend is a fixed cost).* + +````` + + +0=src/mcpindex.h 1=src/graph.h 2=src/eval.h 3=src/main.cpp + +0,1,1,2,3,3,3 +getIndex,rankGraph,anchoredLexicalRank,runEval,churnDecayRanking,churnRankedGraph,runDefaultMap +1184,11790,12436,171,1425,1464,1751 +fn,fn,fn,fn,fn,fn,fn +0,0,0,0,0,0,0 +src/mcpindex.h:1288,src/graph.h:11793,src/graph.h:12487,src/eval.h:328,src/main.cpp:1439,src/main.cpp:1478 src/main.cpp:1479 src/main.cpp:1493,src/main.cpp:1871 + + +````` + +## `./build/ripwire . --for="cache invalidation" --format=candidates --top-k=5` + +*Flat top-K export for an external reranker.* + +**wall time: 1.91s** + +````` + + + +inline std::string spanTierMemoPath( const std::string& diskPath ) + + +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vector<NodeId>& surfaceIds ) + + +### See the map — not just the numbers + + +inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) + + +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) + + +````` + +## `./build/ripwire . --callers=rankGraphTeleport --format=bogus` + +*An unknown --format value REFUSES (exit 1), named, with the supported set listed.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidates) +````` + +## `./build/ripwire . --callers=rankGraphTeleport --json` + +*Machine-parseable JSON, same content, keys mirror the XML attrs.* + +````` +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1184","sites_at":["src/mcpindex.h:1288"]}, +{"t":"fn","n":"rankGraph","p":"src/graph.h:11790","sites_at":["src/graph.h:11793"]}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:12436","sites_at":["src/graph.h:12487"]}, +{"t":"fn","n":"runEval","p":"src/eval.h:171","sites_at":["src/eval.h:328"]}, +{"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1425","sites_at":["src/main.cpp:1439"]}, +{"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1464","sites_at":["src/main.cpp:1478","src/main.cpp:1479","src/main.cpp:1493"]}, +{"t":"fn","n":"runDefaultMap","p":"src/main.cpp:1751","sites_at":["src/main.cpp:1871"]}]} +````` + +## `./build/ripwire . --hotspots --json` + +*JSON refusal shape: an unsupported verb refuses loudly instead of silently falling back to XML.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --json is not yet supported for --hotspots — supported: the default map, --for, --pack-task, --callers/--callees, --impact, --quality-delta, --test-gate, --metrics, and --plan-lanes which is JSON-native (e.g. ripwire

--callers=SYM --json) +````` + +## `./build/ripwire . --hotspots --limit=3 --offset=3` + +*Pagination: 3 items, skipping the first 3 (deterministic seams).* + +**wall time: 2.38s** + +````` + + + + + + + +````` + +## `./build/ripwire . --ignore-tests --top-k=5` + +*Drop test paths from the corpus before ranking.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [67 more display lines; full output is 8115 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --exclude=present --exclude=bench --top-k=5` + +*Drop matching paths (repeatable) before ranking.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [65 more display lines; full output is 8095 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --map-diff --top-k=5` + +*Full map re-ranked with teleport toward git-changed files — clean tree, so changed=0 and it degrades to the plain map.* + +**wall time: 1.48s** + +````` + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --no-cache --top-k=3` + +*Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm cost.* + +**wall time: 3.15s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [63 more display lines; full output is 8117 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3` + +*Explicit incremental cache at a path OUTSIDE the repo (first call writes it).* + +**wall time: 3.17s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [63 more display lines; full output is 8117 bytes on 1 raw line(s)] +````` + +Artifact written: + +````` + 28364081 /aux/warm2.ripwirecache +````` + +## `./build/ripwire . --max-file-size=8K --top-k=3` + +*Skip files above a size bound before parsing (note the corpus shrink in the header).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [18 more display lines; full output is 4246 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --scip=does_not_exist.scip --callers=rankGraphTeleport` + +*SCIP overlay with a missing index REFUSES (exit 1) naming the file — never silently serves the name-based map you named a precision index to improve on (it used to degrade in silence).* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather than serving the name-based map you named a precision index to improve on (generate one with scip-clang/scip-python, or drop --scip) +````` + +## `./build/ripwire src test --top-k=5` + +*Multi-root workspace: ONE merged graph over two roots, paths labeled /.* + +**wall time: 2.02s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --eval` + +*Self-eval: co-change recall vs BM25.* + +**wall time: 19.78s** + +````` +ripwire --eval (co-change recovery, averaged over 80 historical commits) + ranker recall@5 recall@10 recall@20 + ripwire 4.9% 6.4% 15.5% + BM25 10.1% 15.0% 15.9% + BM25sub 11.4% 22.7% 29.0% + BM25body 17.7% 23.7% 32.4% + fused 12.8% 14.3% 26.0% + anchored 17.7% 24.0% 32.7% + same-dir 0.9% 5.0% 8.8% + random 0.1% 0.3% 0.6% <- floor (random ranking over F=3589 files) + note: `ripwire` here is the DEFAULT MAP's structural-only PageRank (importance, not + relatedness) — it is NOT what a --for/--query retrieval call ranks with. BM25 / + BM25sub / BM25body are QUERY-TIME lexical rankers (whole-name / subtoken / + subtoken+body); fused = RRF(ripwire, BM25sub); anchored = BM25body + anchored PPR + expansion (--for --anchor, EXPERIMENTAL). The SHIPPED default for --for/--query is + the subtoken+body lexical family (routed to name-exact only for an identifier-shaped + query — lexical.h chooseForRanker), so a gap between the ripwire and BM25* rows here + is structural-importance-vs-lexical-relatedness on a co-change task, not the shipped + retrieval path losing to an alternative it was never running. +````` + +## `./build/ripwire . --eval-retrieval` + +*Known-item retrieval eval: MRR + recall@k per ranker per query mode.* + +**wall time: 151.62s** + +````` +ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) + sample: population=7969 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + (smallest fnv1a64(scope::name) over the population, cut on the key so an identity is never split; + path- and order-independent, but a corpus this size is NOT graded exhaustively — say so when citing it) + ingest: lex=rich (persisted subtoken stats; no per-query corpus re-tokenize) + ranker query-mode MRR recall@1 recall@5 recall@10 + subtoken name 0.680 54.5% 84.8% 90.0% + subtoken doc-phrase 0.903 88.3% 92.2% 93.4% + name-exact name 0.898 82.2% 95.8% 97.4% + name-exact doc-phrase 0.013 0.5% 1.9% 2.6% + anchored name 0.687 56.2% 84.1% 88.7% + anchored doc-phrase 0.899 87.5% 92.2% 92.9% + routed name 0.899 82.4% 96.0% 97.6% + routed doc-phrase 0.902 88.2% 92.0% 93.2% + note: routing chose name-exact on 3989/4000 NAME queries (a NAME query is always identifier-shaped); + the confidence gate routes doc-phrase queries to name-exact ONLY when EVERY content word names a symbol + (or an explicit camel/snake token appears) AND every matched name is specific enough to anchor on — + a common name (many definitions, or a subtoken carried by many symbol names) declines the route — so + conceptual prose falls back to subtoken+body; routed tracks the better ranker on BOTH modes + (routed==name-exact on name, ~=subtoken+body on doc-phrase). +````` + +## `./build/ripwire . --eval-stray=/aux/stray_labels2.tsv` + +*Labelled verdict-accuracy eval for --stray-content — three labels over REAL local refs (names resolved at capture time); exit 3 when accuracy is under the floor. Read got= against v= in the stray-content run: a ref the verb could not analyse (unknown) must never be credited as a merged hit.* + +**exit code: 3** — **wall time: 40.06s** + +Input file: + +````` +# refverdict labels for --eval-stray (the first three local branches, resolved at capture time; a missing branch is padded with a nonexistent name on purpose) +arm/for-howitworks-067-f2add merged +arm/for-howitworks-067-placebo unmerged +arm/orient-map-067-narrow merged +````` + +````` + + + + + + +````` + +## `./build/ripwire skills --eval-skills=/aux/skills_labels2.tsv` + +*Labelled skill-ROUTING eval over the repo's own skills/ directory (4 hand-labelled prompts).* + +Input file: + +````` +orient in an unfamiliar codebase fast ripwire-orient judged +who calls this function and what is the blast radius ripwire-navigate judged +plan parallel worktrees so the lanes do not collide ripwire-change-check judged +what is the weather in Paris none neg +````` + +````` +ripwire --eval-skills (skill routing over K=16 candidate skills [ripwire-router excluded]; 3 positive + 1 negative prompts; corpus '/aux/skills_labels2.tsv'; split test=4 dev=0) + arm hit@1 hit@2 mrr sep-auc fire/abstain@ORACLE-th (upper bound) + overlap 66.7% 66.7% 0.689 0.000 50.0% (th=-1.000) + name 33.3% 66.7% 0.537 0.667 50.0% (th=0.000) + bm25-desc 66.7% 66.7% 0.689 0.667 75.0% (th=2.865) + bm25-full 33.3% 66.7% 0.611 1.000 50.0% (th=0.426) + for-routed 33.3% 33.3% 0.556 1.000 50.0% (th=0.663) + random 6.2% 12.5% 0.211 0.500 <- floor (uniform-random ranking; auc 0.5 by definition) + provenance hit@1 (bm25-desc): router 0/0, desc 0/0, judged 2/3 (desc rows quote the descriptions - expect them easiest; judged is the honest number) + judged-only hit@1 per arm: overlap 2/3, name 1/3, bm25-desc 2/3, bm25-full 1/3, for-routed 1/3 + router-magnet: with ripwire-router ADMITTED as a candidate it takes top-1 on 1/3 positive prompts (bm25-desc arm) - why it is excluded above + per-skill (bm25-desc): name / permitted-rows / won / pos-fires / false-fires / neg-fires + ripwire-before-you-build 0 0 1 1 0 + ripwire-change-check 1 0 0 0 0 + ripwire-find-bug 0 0 0 0 0 + ripwire-fresh-eyes 0 0 0 0 1 + ripwire-graph-query 0 0 0 0 0 + ripwire-handoff 0 0 0 0 0 + ripwire-layers 0 0 0 0 0 + ripwire-mcp 0 0 0 0 0 + ripwire-navigate 1 1 1 0 0 + ripwire-opt-remarks 0 0 0 0 0 + ripwire-orient 1 1 1 0 0 + ripwire-perf-target 0 0 0 0 0 + ripwire-quality-bar 0 0 0 0 0 + ripwire-reuse-first 0 0 0 0 0 + ripwire-security-scan 0 0 0 0 0 + ripwire-write-tests 0 0 0 0 0 + misses (overlap): + line 3 want=ripwire-change-check got=ripwire-before-you-build "plan parallel worktrees so the lanes do not collide" +… [19 more lines, 3665 bytes total] +````` + +## `./build/ripwire wrap claude` + +*Print the recipe to wire ripwire into Claude Code as an MCP server.* + +````` +# ripwire -> Claude Code (CLI-first) +# RECOMMENDED — this agent can run shell commands, so call the CLI directly. It costs +# nothing until you invoke it, and it reads CLAUDE.md, so the paste block below IS the wiring: +ripwire . --for="" +# +# ...every follow-up call answers with the compact legend by default (terse definitions of only the +# attributes the answer carries; the payload is byte-identical either way). Add --legend=full when a +# definition's reasoning is needed -- `ripwire --help` carries the measured saving (one place, +# gate-held) -- this line does not repeat it, because two copies of a number is one that goes stale: +# ripwire . --callers=SYM --legend=full +# +# ALTERNATIVE — register the MCP server instead, for a warm index across calls: +claude mcp add ripwire -- ripwire --mcp +# verbs the agent can then call mid-task (33 total): +# read: analyze, rank_by, find_symbol, find_referencing_symbols, grep, cochange, memory_recall, situational_awareness, mentions, for, lego, owners, fetch_body, batch, flags, doc_drift, slice +# flagship reflex: exemplar, quality_delta, quality_baseline, impact, uses, affected, path_between, connect, explore, from_trace, edit_check, whereis, stray_content +# edit: replace_symbol_body, insert_before_symbol, insert_after_symbol +bash skills/install.sh # deploy to ${CLAUDE_CONFIG_DIR:-~/.claude}/skills (drift-gated) +bash skills/install.sh --hook # RECOMMENDED: advisory Read/Grep -> ripwire CLI nudge + session primer (opt-in, never blocks) +# +# context wiring — a binary on PATH is invisible to an agent until its rules file says when +# to reach for it. Paste the block below into CLAUDE.md: +# --- paste into CLAUDE.md --- +## ripwire — deterministic codebase maps (on PATH as `ripwire`) +Reach for it BEFORE blind grep + whole-file reads. First call ~1s cold; after that warm, ~0.1s. +- Orient on a task: `ripwire --for=""` — ranked, quality-annotated + signatures. Paste symbol/file names from the issue verbatim; named mentions get anchored. +- One task: `--pack-task="" --legend=compact`; before parallel agents: `--plan-lanes=N --task=""`, then read `lanes[].execution`. +- Have a stack trace / build error: `ripwire --from-trace=FILE --legend=compact` (`-` = stdin) — + paste the error, don't paraphrase it into a query. +… [14 more lines, 4058 bytes total] +````` + +## `./build/ripwire --version` + +*Version + short build info.* + +````` +ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=b58af84eb) +````` + + +--- + +# navigate — seeds, claims, slices, shapes + +## `./build/ripwire . --at=src/graph.h:11751` + +*Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost.* + +````` + + + + +````` + +## `./build/ripwire . --callers=@src/graph.h:11751` + +*The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --at=src/graph.h:999999` + +*A seed past the end of the file — the refusal shape for a faulted location.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 16107 lines — the seed asked for line 999999) +````` + +## `./build/ripwire . --verify="calls(runDefaultMap, rankGraphTeleport)"` + +*VERIFY a closed claim in one call: three-valued verdict (confirmed / refuted / not-established) with the evidence rows inline.* + +````` + + + + + +````` + +## `./build/ripwire . --verify="unused(rankGraphTeleport)"` + +*A claim that is FALSE — the refuted shape, with the references that refute it.* + +````` + + + + + + + + + + + + +````` + +## `./build/ripwire . --verify="contains(src/graph.h, \"no such literal anywhere\")"` + +*A literal-scan absence: refuted only with complete= evidence, never on a partial scan.* + +````` + + + +````` + +## `./build/ripwire . --verify="frobnicate(x)"` + +*An unparseable claim — the refusal names the accepted shapes.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. The claim language is CLOSED; the shapes are: calls(A, B) does A transitively call B · uses(SYM) is SYM referenced anywhere · unused(SYM) is SYM referenced nowhere · contains(FILE, "LITERAL") do FILE's bytes contain t … [line truncated: 218 more bytes on this line] +````` + +## `./build/ripwire . --slice=rankGraphTeleport` + +*Bare --slice=SYM: the INVENTORY of sliceable locals (), so a caller can pick VAR.* + +**wall time: 1.10s** + +````` + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --slice=rankGraphTeleport:teleport` + +*Intra-procedural def-use slice of ONE variable: one row per line touching it, k=def|use|both, reaching definitions flow-sensitive (reach=cfg).* + +**wall time: 1.05s** + +````` + + + + + + + teleport( pw.begin(), pw.end() );]]> + + + + + + + + +````` + +## `./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=back --slice-depth=3` + +*TRANSITIVE backward value-flow from the seed variable, bounded BFS (depth= disclosed; a cut frontier says flow_truncated=1).* + +**wall time: 1.14s** + +````` + + + + + + + teleport( pw.begin(), pw.end() );]]> + + + + + + + + + pw = biasPrior( g, p );]]> + + +& p, float alpha = 0.85f )]]> + + +& p, float alpha = 0.85f )]]> + + +````` + +## `./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd` + +*Forward flow: which statements the seed's value reaches, at the default depth bound.* + +**wall time: 1.22s** + +````` + + + + + + + teleport( pw.begin(), pw.end() );]]> + + + + + + + + + + + + + + + + + + + + + 0.0 )]]> + + + + + + + + +````` + +## `./build/ripwire . --slice=rankGraphTeleport:nosuchvar` + +*A variable the definition does not bind — the refusal shape, naming the inventory.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --slice: no occurrence of 'nosuchvar' in rankGraphTeleport — sliceable locals: alpha, g, p, pw, N, teleport, rankDouble, run, teleportMass, value, inverseMass, r (bare --slice=rankGraphTeleport lists them with first-def lines) +````` + +## `./build/ripwire . --slice-depth=3` + +*--slice-depth without --slice-flow is refused loudly rather than silently ignored.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire --slice=parseArgs:argIndex --slice-flow=fwd --slice-depth=4) +````` + +## `./build/ripwire . --slice=rankGraphTeleport:teleport --legend=compact` + +*The compact legend posture: rows byte-identical, a versioned schema id replaces the repeated explanatory prose — for a many-small-calls loop.* + +````` + + + + + + + teleport( pw.begin(), pw.end() );]]> + + + + + + + + +````` + +## `./build/ripwire . --pattern='rankGraphTeleport($A, $B, $C)'` + +*Structural search written in CODE: $NAME binds one node; grammars=/shapes= disclose what the pattern became per grammar (a 3-argument call shape — the 2-argument spelling has no call site in this repo and correctly reports hits=0).* + +**wall time: 1.75s** + +````` + +rankGraphTeleport( g, std::vector<float>( N, N ? 1.0f / float( N ) : 0.f ), alpha ) + +````` + +## `./build/ripwire . --pattern='DISCLOSE(...)'` + +*The ellipsis form over a macro-shaped call site; unsupported= names the families this verb does not serve.* + +**wall time: 1.85s** + +````` + +DISCLOSE( result, AbiResult::DisclosureWhy::NoMergeBase, "abi: no merge-base for a ref (unrelated history?) — that ref +DISCLOSE( Diagnostics::answerRefused, "--arch exits 1 with path:line and the reason on stderr; no report is printed", +DISCLOSE( baseline, ArchBaselineRead::DisclosureWhy::SymlinkRefused, "arch: refusing to read the arch baseline sidecar t +DISCLOSE( Diagnostics::answerRefused, "--baseline exits 1: pathguard names the refused link on stderr and the verb says +DISCLOSE( ex, Exclusions::DisclosureWhy::ForHeaderSaturated, "atoms: the for-header exclusion stream spent its whole bud +DISCLOSE( ex, Exclusions::DisclosureWhy::StmtCrementSaturated, "atoms: the statement-crement exclusion stream spent its +DISCLOSE( ex, Exclusions::DisclosureWhy::StmtAssignSaturated, "atoms: the statement-assign exclusion stream spent its wh +DISCLOSE( ex, Exclusions::DisclosureWhy::InnerCommaSaturated, "atoms: the inner-comma exclusion stream spent its whole b +DISCLOSE( st, Type3Stats::DisclosureWhy::PairCapHit, "clones: Type-3 pair cap hit — first N compared (both-gate-surviv +DISCLOSE( scan, CommentCoherenceScan::DisclosureWhy::UnreadableFile, "comment-coherence: an indexed file could not be re +DISCLOSE( st, StreamBlobStats::DisclosureWhy::ListUnwritable, "crossref: cannot write the blob-batch list — cross-bran +DISCLOSE( st, StreamBlobStats::DisclosureWhy::BatchNotStarted, "crossref: git cat-file --batch failed to start — cross +DISCLOSE( st, StreamBlobStats::DisclosureWhy::StreamEndedMidBlob, "crossref: git cat-file stream e +DISCLOSE( dropSink, RefEnumeration::DisclosureWhy::TipNotObjectName, "crossref: for-each-ref yielded a ref whose tip is +DISCLOSE( "crossref: refusing a diff whose revision arguments are not resolved object names" ) +DISCLOSE( sweep, ParallelSweep::DisclosureWhy::WorkerThrew, "crossref: a git worker threw — this shard of the sweep is +DISCLOSE( stats, StreamBlobStats::DisclosureWhy::BatchNotStarted, "crossref: a blob-stream slice was never read — comp +DISCLOSE( plumb, RefPlumbing::DisclosureWhy::RevisionNotObjectName, "crossref: ref tip or HEAD is not a resolved object +DISCLOSE( plumb, RefPlumbing::DisclosureWhy::MergeBaseNotObjectName, "crossref: merge-base returned something that is no +DISCLOSE( plumb, RefPlumbing::DisclosureWhy::NoMergeBase, "crossref: no merge-base for ref (shallow clone or unrelated h +DISCLOSE( "crossref: refusing to list a tree whose revision argument is not a resolved object name" ) +DISCLOSE( "whereis: the index's def sites match no HEAD row (working tree drifted from HEAD?) — keeping the lexical la +DISCLOSE( reads[ k ], BranchBlobReadBack::DisclosureWhy::NotReadBackWhole, "whereis: a blob the pa +DISCLOSE( scan, WorktreeScan::DisclosureWhy::ListingFailed, "whereis: git could not list the working tree's changes — +DISCLOSE( scan, WorktreeScan::DisclosureWhy::OverPathCap, "whereis: more changed paths than the overlay reads — the re +DISCLOSE( scan, WorktreeScan::DisclosureWhy::CopyUnread, "whereis: a changed path could not be read from the working tre +DISCLOSE( Diagnostics::answerUnchanged, "lsTree lists the same rows per ref: only slower", "crossref: +DISCLOSE( out, CMakeScan::DisclosureWhy::SymlinkEscapesRoot, "flags: a CMake file's symlink target leaves the root — f +… [73 more display lines; full output is 17778 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --pattern='x'` + +*A pattern that collapses to a bare token is REFUSED — never reported as hits=0.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --pattern: this pattern collapses to a single bare token, which is a TEXT search, not a structural one — a clean parse is not enough (a bare word parses fine in most grammars and means nothing structural). Give it a shape (foo($X), $A + $B, if ($C) { ... }) or use the grep flag for litera … [line truncated: 176 more bytes on this line] +````` + +## `./build/ripwire . --grep=DISCLOSE --and=cache` + +*Boolean grep: hits where BOTH literals share the matched line (--grep-scope=line is the default).* + +````` + + + +inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) +The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u +TSTree* parseTree( TSParser* parser, std::string_view src ) + +Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() +struct FileHealth +constexpr std::array<LangEntry, 57> kLangTable = +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar…struct SliceScan +enum class AstWalk : std::uint8_t +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +std::vector<LocalNameFact> collectGatedLocalNames( std::string_view defBytes, std::uint32_t defStartLine, Lang lang ) +inline ModuleScan scanModule( std::string_view source, std::string_view path ) +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +inline bool hasMainGuard( std::string_view source ) +constexpr std::uint32_t kMaxYamlNestDepth = 64u +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +enum class DisclosureWhy : std::uint8_t +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) +struct LintRule +inline FileHealth measureHealthAdoptingMemberMacroReparse( TSParser* parser, Lang lang, std::string_view bytes, TreeGuard& tree, MemberMacroRepa … [line truncated: 19 more bytes on this line] +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [138 more display lines; full output is 19865 bytes on 44 raw line(s)] +````` + +## `./build/ripwire . --for="tree-sitter parse of a source file" --sections=lego,compose` + +*Restore the / sections the --for bundle collapses to a counted stub by default (this is the stub's own next= spelling).* + +**wall time: 1.88s** + +````` + + + + +inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) +The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u +TSTree* parseTree( TSParser* parser, std::string_view src ) + +Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() +struct FileHealth +constexpr std::array<LangEntry, 57> kLangTable = +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar…struct SliceScan +enum class AstWalk : std::uint8_t +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +std::vector<LocalNameFact> collectGatedLocalNames( std::string_view defBytes, std::uint32_t defStartLine, Lang lang ) +inline ModuleScan scanModule( std::string_view source, std::string_view path ) +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +inline bool hasMainGuard( std::string_view source ) +constexpr std::uint32_t kMaxYamlNestDepth = 64u +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +enum class DisclosureWhy : std::uint8_t +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) +struct LintRule +inline FileHealth measureHealthAdoptingMemberMacroReparse( TSParser* parser, Lang lang, std::string_view bytes, TreeGuard& tree, MemberMacroRepa … [line truncated: 19 more bytes on this line] +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [149 more display lines; full output is 20198 bytes on 44 raw line(s)] +````` + +## `./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies` + +*Opt OUT of compact conceptual serving: restore the rank-first auto walk (bundle="auto").* + +**wall time: 1.89s** + +````` + + + + +inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) +The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u +TSTree* parseTree( TSParser* parser, std::string_view src ) + +Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() +struct FileHealth +constexpr std::array<LangEntry, 57> kLangTable = +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar…struct SliceScan +enum class AstWalk : std::uint8_t +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +std::vector<LocalNameFact> collectGatedLocalNames( std::string_view defBytes, std::uint32_t defStartLine, Lang lang ) +inline ModuleScan scanModule( std::string_view source, std::string_view path ) +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +inline bool hasMainGuard( std::string_view source ) +constexpr std::uint32_t kMaxYamlNestDepth = 64u +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +enum class DisclosureWhy : std::uint8_t +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) +struct LintRule +inline FileHealth measureHealthAdoptingMemberMacroReparse( TSParser* parser, Lang lang, std::string_view bytes, TreeGuard& tree, MemberMacroRepa … [line truncated: 19 more bytes on this line] +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [134 more display lines; full output is 20417 bytes on 55 raw line(s)] +````` + +## `./build/ripwire . --for="quality delta acks ledger rubber stamp"` + +*Doc-mention surfacing (default ON): a markdown doc naming a top-resolved symbol in a backtick rides in below that symbol — the legend's [doc mentions: …] clause says it fired.* + +**wall time: 1.96s** + +````` + + + + + +L2 stale acks in the range form. A changed-files answer holds no whole target tree to check the …std::vector<rw::quality::StaleAck> refPairStaleAcks( const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const RefPairDelta& refs ) +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +inline std::string staleByKindValue( const std::vector<StaleAck>& staleAcks ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +inline rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) +inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& wo… +inline const char* kNotesFile = ".ripwire_notes" +inline void writePlanLint( std::FILE* out, const LintResult& res ) + +The REF-PAIR floor's comparison, once loadRefPairDelta has both sides: the base snapshot is the …void refPairBasis( const rw::Config& cfg, const std::string& root, RefPairDelta& refs, DeltaBasis& out ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOf … [line truncated: 16 more bytes on this line] +std::optional<int> runQualityDelta( const MainDispatch& d ) +struct DeltaBasis +int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… … [line truncated: 4 more bytes on this line] +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root, bool showStale = false ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +inline std::string qualityAcksPath( const std::string& root ) +struct QualityDeltaOutcome +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string gitHeadSha( const std::string& root ) +… [193 more display lines; full output is 22980 bytes on 45 raw line(s)] +````` + +## `./build/ripwire . --for="quality delta acks ledger rubber stamp" --no-doc-mention` + +*The same task with doc-mention surfacing OFF — the contrast the flag exists for (no [doc mentions] clause, one fewer row).* + +**wall time: 1.84s** + +````` + + + + + +L2 stale acks in the range form. A changed-files answer holds no whole target tree to check the …std::vector<rw::quality::StaleAck> refPairStaleAcks( const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const RefPairDelta& refs ) +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +inline std::string staleByKindValue( const std::vector<StaleAck>& staleAcks ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +inline rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) +inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& wo… +inline const char* kNotesFile = ".ripwire_notes" +inline void writePlanLint( std::FILE* out, const LintResult& res ) + +The REF-PAIR floor's comparison, once loadRefPairDelta has both sides: the base snapshot is the …void refPairBasis( const rw::Config& cfg, const std::string& root, RefPairDelta& refs, DeltaBasis& out ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOf … [line truncated: 16 more bytes on this line] +std::optional<int> runQualityDelta( const MainDispatch& d ) +struct DeltaBasis +int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… … [line truncated: 4 more bytes on this line] +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root, bool showStale = false ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +inline std::string qualityAcksPath( const std::string& root ) +struct QualityDeltaOutcome +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string gitHeadSha( const std::string& root ) +… [193 more display lines; full output is 22799 bytes on 45 raw line(s)] +````` + +## `./build/ripwire . --safe-delete=rankGraphTeleport` + +*"Can I delete this?" — callers + transitive impact + every use site + how much of the radius is tested, composed in ONE call; risk= names what was found, never a verdict.* + +````` + + + + + + + + + +````` + +## `./build/ripwire . --safe-delete=DoesNotExist` + +*Unknown-symbol refusal shape for --safe-delete.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --safe-delete symbol not found: DoesNotExist +````` + +## `./build/ripwire . --handoff` + +*The continuation packet for the NEXT session: disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a CLEAN tree.* + +**wall time: 2.92s** + +````` + + + + + + + + + + + + + +````` + +## `./build/ripwire . --handoff --token-budget=1200` + +*The same packet under a hard ceiling: heuristic rows drop tail-first (withheld= disclosed), verified rows never drop.* + +**wall time: 4.10s** + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --skipped` + +*WHY a file is not in the index (oversize / excluded / unsupported-ext / gitignored) and which indexed files it cannot vouch for (degraded-parse, minified-suspect), plus the per-language census.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [441 more display lines; full output is 46965 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --no-ignore --top-k=3` + +*Crawl paths the repo's own .gitignore covers (default honours it and discloses ignored_files=/ignored_dirs= only when it dropped anything — this repo's crawl drops nothing, so the header is identical to the default map's; --skipped's ignore_mode= says which rule applied).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [63 more display lines; full output is 8117 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --no-stable --top-k=3` + +*--no-stable outside --mcp: what the flag does (or says) when there is no stable-by-default ordering to opt out of.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [63 more display lines; full output is 8117 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable ordering the server turns on) — it changed nothing here; the CLI map orders important-first unless you pass --order=stable +````` + +## `./build/ripwire . --run-trace="cat /aux/asan_trace_now.txt; exit 1"` + +*EXEC-MODE --from-trace: run a command, and on a non-zero exit map its captured output onto indexed symbols in the same call — the whole fix-loop entry. ripwire exits 4 here because the wrapped command failed, which is the signal, not an incident.* + +**exit code: 4** + +````` + + + + + + const&, float) src/graph.h:11751 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:11792 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 + #3 0x102e01a44 in main src/main.cpp:4174 + #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc)]]> pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + double teleportMass = 0.0; + for( const double value : teleport ) + { + teleportMass += value; + } + if( teleportMass > 0.0 ) + { + const double inverseMass = 1.0 / teleportMass; + for( double& value : teleport ) + { + value *= inverseMass; + } + } + run = pageRankDouble( g.inEdges, g.wOutDeg, teleport, rankDouble, PageRankConfig{ .alpha = double( alpha ) } ); + } + std::vector r( N, 0.f ); + std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); + return { std::move( r ), run.iterationCount, run.hasConverged }; +}]]>inline std::vector<float> biasPrior( const Graph& g, const std::vector<float>& p )#define PROFILE_SCOPE_DESCRIBE( desc )#define PROFILE_SCOPE_DESC … [line truncated: 289 more bytes on this line] +````` + +## `./build/ripwire . --run-trace="true"` + +*A command that exits 0: a minimal success record (exit, measured duration, disclosed output tail) and NO bundle — nothing failed, nothing to map.* + +````` + + + + +````` + +## `./build/ripwire . --run-trace="sleep 30" --run-timeout=2` + +*A command still running at the cap: its process group is killed and the run reports timed_out=1 — an honest timeout, never an empty success.* + +**exit code: 4** — **wall time: 2.73s** + +````` + + + + +````` + +stderr: + +````` +ripwire: --run-trace: TIMEOUT — the command exceeded the 2 s cap; its process group was killed +````` + +## `./build/ripwire . --run-timeout=5` + +*--run-timeout alone is refused loudly (it only modifies --run-trace).* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwire --run-trace="make -j" --run-timeout=60) +````` + + +--- + +# assess quality — the wider lens family + +## `./build/ripwire . --quality-panel` + +*THE single wide-angle quality read: six families in one pass, an eligible/ranked shortlist rather than a firehose.* + +**wall time: 6.34s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [188 more display lines; full output is 15675 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --biggest-first --limit=8` + +*Per-function size ranking, largest Halstead volume/token entropy/lines first — a size proxy, not a readability order (withdrawn, docs/EVALS.md §8) — a RANKING lens, not a grade.* + +**wall time: 1.08s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --comment-coherence --limit=8` + +*Functions WITH a doc comment, most name-restating first: c_coeff (high = the comment repeats the name) and cic (Jaccard of comment vs identifier vocabulary), both reported, never collapsed.* + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --context-ratio --limit=8` + +*The local-reasoning lens: to understand this symbol, how much must you know that is NOT in front of you (ent_ratio= edge share, read_ratio= token-weighted).* + +**wall time: 1.09s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [21 more display lines; full output is 11151 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --nonlocal-state --limit=8` + +*Per function, the non-local MUTABLE state it can reach (transitively), most writes first — unsound by construction, and the legend says where.* + +**wall time: 2.92s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [41 more display lines; full output is 7601 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --ensemble --limit=8` + +*The family join: per function, which of four orthogonal evidence families fire, ranked by how many agree.* + +**wall time: 3.72s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [36 more display lines; full output is 7488 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --field-affinity` + +*The cache-locality lens over every aggregate: fields READ TOGETHER but declared FAR APART (split-line / straddle findings, Chilimbi separation weight) — advice only, never a rewrite.* + +**wall time: 2.13s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [510 more display lines; full output is 45635 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --field-affinity=Symbol` + +*The same lens narrowed to ONE struct — the one --layout=Symbol shows the offsets for.* + +**wall time: 1.93s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --naming-consistency --limit=8` + +*The corpus's OWN case-convention vote per (language, kind) group; off-convention names get a mechanical propose= (a suggestion, never a blind rename).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 4430 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --naming-calibration` + +*Score the naming-* rules against this repo's own rename history: proxy=old/(old+new) per rule, 0.50 = chance; read pairs= (sample size) first.* + +**wall time: 13.67s** + +````` + + + + + + + + + + + +

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+… [153 more display lines; full output is 15975 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint --naming-locals` + +*The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/C++ only, only inside functions already past a size/complexity gate.* + +**wall time: 3.15s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [641 more display lines; full output is 72667 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint-catalog` + +*The built-in rule registry — one row per rule with sev=/category=/rationale/lang=/since=; no corpus needed.* + +````` + + +a C-style cast — cppcoreguidelines-pro-type-cstyle-cast prefers the explicit static_cast/const_cast/reinterpret_cast +a goto statement — cppcoreguidelines-avoid-goto +a do/while loop shape (on Swift, its do/catch block shares the same grammar node and also matches) +a call to an unbounded C string function (strcpy/strcat/sprintf/gets) +a call to a broken hash or cipher (MD5/SHA1/MD4/RC4) +a doubly-parenthesized expression — readability-redundant-parentheses +an if-body that is just `;` — bugprone-suspicious-semicolon +a C-style typedef struct/union where `using` is preferred +a non-trivial numeric literal inside a function body, outside a const/constexpr init +a catch block with an empty body +x = x — almost always a copy-paste bug +a function body over 80 lines +brace nesting depth over 4 inside a function body +a bare `return;` mixed with a value-returning return in the same function +a statement after an unconditional return/break/continue/throw/raise in the same block +a 1-2 letter Function/Method/Var name — visible far beyond any tiny scope [Beniamini/Hofmeister] +more than 5 split tokens in one name [Butler; AlSuhaibani] +foo1/foo2/... digit-suffix siblings sharing a base name in one scope [Butler] +internal consecutive underscores, or a C-family reserved __x/_X form [Butler] +snake_case and camelCase mixed inside one name [Butler] +an is/has/can/should/was-prefixed name whose KNOWN return type is not bool-like [LAPD A2] +a set-prefixed name whose KNOWN return type is not void-like [LAPD A3] +a co-visible pair within edit distance <=2, reordered tokens, or a bare/digit-suffixed twin [Namesake] +every split subtoken is corpus-ubiquitous (BM25 idf) on a body past the size floor — fires only at the low end [Sparck Jones 1972] +the comma operator inside an expression (never a for-header comma) [Gopstein FSE 2017] +++/-- evaluated inside a larger expression (never a whole statement) [Gopstein FSE 2017] +an assignment whose VALUE is consumed (a condition, an argument, ...) [Gopstein FSE 2017] +a conditional expression inside a conditional expression [Gopstein FSE 2017] +… [12 more display lines; full output is 7800 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --legend-dict` + +*The session legend dictionary the MCP server serves as ripwire://legend-dict/full — one definition per line, headed by its dictv= version; no corpus needed. =roster lists the completeness attributes it defines.* + +````` +ripwire legend dictionary ripwire.dict/v1 dictv=2259c5025ac90271 entries=930 +: the answer's rows come first; its root keeps only task= changed= from= to=, and this LAST child carries every other root attribute unchanged (schema= included); legend="ref": a definition is sent once per session (this dictionary's core, or the first answer that ne … [line truncated: 83 more bytes on this line] +schema=ripwire.KEY/v1: the line ripwire.KEY/v1 below reads the answer's rows +window: shown= total= capped= has_more= next_offset= offset= limit= page a list (capped=1 cut; next_offset= pastes as offset=) +X_capped= (any attribute ending _capped): 1 = cut +under ref, est_tokens= and over_ceiling= price the answer with its inline legend: an upper bound +under ref, a group of n <= 8 runner-less rows prints as its n single rows, each run_unknown=1 +ripwire.map/v1 : ranked symbol map: groups rows (k= rank), resolved callees; the header comment is data +ripwire.map-diff/v1 : the ranked map anchored at at=: what the diff touched, the map's row vocabulary +ripwire.metrics/v1 : the ranked map with per-symbol metrics: in/out, cx/ccx, loc, params, nest, humps/deep, locals, cbo, amp, tested, ev +ripwire.around/v1 : call neighbourhood of of=: depth= hops, fanout= kept per hop; absent rows lie outside that boundary +ripwire.query/v1 : lexical-rank map for the query term, the map's row vocabulary +ripwire.pack-signatures/v1 : the ranked map plus signature rows +ripwire.pack-top-n/v1 : the ranked map plus bodies of the top-N symbols +ripwire.skipped/v1 : why the index lacks a file ; indexed but unvouched ; census +ripwire.notes/v1 : field notes by target: holds ; the kept count comment: notes= rows, targets= rows, dangling= targets matching nothing indexed (listed, surfaced nowhere) +ripwire.lego/v1 : ONE interface/base type: , its method contract, every implementor +ripwire.expand/v1 : full bodies: of ; resolved callees +ripwire.expand-file/v1 : the file's own text: ; per scoped symbol; full id = p::sc::n +ripwire.candidate-page/v1 : budgeted-bundle candidate page: a resumable window over the ranked candidate set the bundle cuts, for task= under budget_tokens=, one tier per page, ranks global across pages +ripwire.pack-task/v1 : one-call task bundle for task= under budget_tokens=: ranking, ranked but over 1 hop out (of_top= ranked rows) > with callees > 1-hop from the bodies (o … [line truncated: 105 more bytes on this line] +ripwire.from-trace/v1 : trace frames mapped to indexed symbols, innermost first; the innermost in-corpus body included +ripwire.exemplar/v1 : the best-in-class instance of kind= for the task, chosen by role: , to imitate +ripwire.pack-task/v1 : N minimally overlapping agent bundles carved along call-graph communities plus one shared core; each wraps a ; bundle role=core|partition i= symbols= modules= bytes= tokens=: one agent's ctx, symbols= ids assigned, bytes= its size; tokens= = est_t … [line truncated: 345 more bytes on this line] +ripwire.callers/v1 : 1-hop CALLERS of of= (defs= matched, count= distinct symbols): ; hop_tested=/hop_untested= +ripwire.callees/v1 : 1-hop CALLEES of of= (defs= matched, count= distinct symbols): +ripwire.uses/v1 : resolvable use-sites of of=: +ripwire.impact/v1 : transitive blast radius of of=: reach set, importers; defs= matched, reaches= their transitive callers, radius_tested= non-tests an indexed test reaches, radius_untested= the rest; importers= files that #include/import a def's file +ripwire.path/v1 : one DIRECTED call path from= to to=, each a hop; reachable=0 hops=0 when none +ripwire.connect/v1 : minimal joining subgraph: groups, terminals, joins, edges, +… [901 more display lines; full output is 97483 bytes on 931 raw line(s)] +````` + +## `./build/ripwire . --lint --lint-select=cache-` + +*Run ONLY one rule family; the root carries selected="K of N" so a filtered zero is never confusable with an unfiltered one.* + +**wall time: 2.55s** + +````` + + + + + + + + + + +vector<std::vector<double>> +nodes[ perm[i] ] +nodes[ perm[ i + 1 ] ] +p = p->next +map +byName[ symN[i] ] +vector<std::vector<std::vector<std::uint32_t>>> +vector<std::vector<std::uint32_t>> +m[ symN[id] ] +vector<std::vector<double>> +m[ in.keys[ i ] ] +map +vector<std::vector<std::vector<std::uint32_t>>> +vector<std::vector<std::uint32_t>> +m[ symN[id] ] +map +m[ g_pool[ g_keyOf[i] ] ] +g_pool[ g_readOf[i] ] +g_pool[ g_readOf[i] ] +m[ g_pool[i] ] +… [607 more display lines; full output is 73533 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint --lint-ignore=naming-,cache-` + +*DROP two families, applied after selection; the raw select=/ignore= you passed rides on the root.* + +**wall time: 3.11s** + +````` + + + + + + + + + + + + + + + + + + + + + + + +0.5 +1023u +main (90 lines) +20 +delta < 0.02 && delta > -0.02 +0.02 +… [724 more display lines; full output is 66341 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint --lint-select=goto --lint-max-per-rule=4` + +*A rule that spends its own match budget (here 4, set BELOW the default 5000 so the floor shows on this repo): goto carries count_capped="1", its count= is a FLOOR, and the root names the call that counts the rest — findings_next= re-runs only the floored rules under a 10x budget.* + +**wall time: 2.60s** + +````` + + + +goto done; +goto epilogue; +goto epilogue; +goto epilogue; + +````` + +## `./build/ripwire . --lint --lint-select=goto --lint-max-per-rule=40` + +*Pasting that findings_next=: the same rule under a 10x budget counts its true total — no count_capped=, no findings_next=.* + +**wall time: 2.82s** + +````` + + + +goto done; +goto epilogue; +goto epilogue; +goto epilogue; +goto epilogue; +goto epilogue; +goto epilogue; +goto epilogue; +goto epilogue; +goto epilogue; +goto epilogue; +goto epilogue; +goto epilogue; +goto error; +goto error; +goto error; +goto done; +goto done; + +````` + +## `./build/ripwire . --lint --lint-select=cach-` + +*An unresolvable PREFIX refuses (exit 1) with a did-you-mean from a real edit distance (one character off cache-).* + +**exit code: 1** — **wall time: 2.63s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --lint-select: 'cach-' matches no rule or family (did you mean 'cache-'?) — see --lint-catalog for the full registry +````` + +## `./build/ripwire . --lint --lint-select=nosuchfamily` + +*A PREFIX with no near miss at all: the refusal points at --lint-catalog instead of guessing.* + +**exit code: 1** — **wall time: 2.94s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --lint-select: 'nosuchfamily' matches no rule or family — see --lint-catalog for the full registry +````` + +## `./build/ripwire . --lint --sarif` + +*The SAME findings as SARIF 2.1.0 (what github/codeql-action/upload-sarif consumes) — pure re-serialization, results count == the native run's.* + +**wall time: 2.65s** + +````` +{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[{"tool":{"driver":{"name":"ripwire","rules":[{"id":"c-style-cast","shortDescription":{"text":"c-style-cast"},"properties":{"builtin":true,"capped":false,"applicable": … [line truncated: 7 more bytes on this line] +{"id":"goto","shortDescription":{"text":"goto"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"do-while","shortDescription":{"text":"do-while"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"unsafe-c-fn","shortDescription":{"text":"unsafe-c-fn"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"weak-crypto","shortDescription":{"text":"weak-crypto"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"redundant-parens","shortDescription":{"text":"redundant-parens"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"suspicious-semicolon","shortDescription":{"text":"suspicious-semicolon"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"typedef-over-using","shortDescription":{"text":"typedef-over-using"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"magic-number","shortDescription":{"text":"magic-number"},"properties":{"builtin":true,"capped":true,"applicable":true}}, +{"id":"empty-catch","shortDescription":{"text":"empty-catch"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"self-assign","shortDescription":{"text":"self-assign"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"large-function","shortDescription":{"text":"large-function"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"deep-nesting","shortDescription":{"text":"deep-nesting"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"inconsistent-return","shortDescription":{"text":"inconsistent-return"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"unreachable-code","shortDescription":{"text":"unreachable-code"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"naming-short","shortDescription":{"text":"naming-short"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"naming-wordy","shortDescription":{"text":"naming-wordy"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"naming-series","shortDescription":{"text":"naming-series"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"naming-underscore","shortDescription":{"text":"naming-underscore"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"naming-case","shortDescription":{"text":"naming-case"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"naming-predicate","shortDescription":{"text":"naming-predicate"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"naming-setter","shortDescription":{"text":"naming-setter"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"naming-confusable","shortDescription":{"text":"naming-confusable"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"naming-uninformative","shortDescription":{"text":"naming-uninformative"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"atom-comma-operator","shortDescription":{"text":"atom-comma-operator"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"atom-embedded-crement","shortDescription":{"text":"atom-embedded-crement"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"atom-assign-as-value","shortDescription":{"text":"atom-assign-as-value"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"atom-nested-ternary","shortDescription":{"text":"atom-nested-ternary"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"atom-implicit-predicate","shortDescription":{"text":"atom-implicit-predicate"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +{"id":"atom-octal-literal","shortDescription":{"text":"atom-octal-literal"},"properties":{"builtin":true,"capped":false,"applicable":true}}, +… [13391 more display lines; full output is 1895351 bytes on 1 raw line(s)] +````` + +Parsed summary of the same SARIF (past the display cut): + +````` +sarif 2.1.0 rules= 39 results= 6691 +````` + +## `./build/ripwire . --lint --sarif --limit=5` + +*SARIF is always the FULL result set: paging alongside it refuses loudly.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M +````` + +## `./build/ripwire . --dmm` + +*The Delta Maintainability Model scalar for the WORKING TREE vs HEAD — recorded against a CLEAN tree (the sandbox section shows a real delta). UNAVAILABLE is a stated reason, never 0 or 1.* + +**wall time: 7.21s** + +````` + + +

+

+ +````` + +## `./build/ripwire . --dmm=HEAD` + +*The per-commit scalar: HEAD vs its first parent, with the three separately actionable sub-scores.* + +**wall time: 14.13s** + +````` + + +

+

+ +````` + +## `./build/ripwire . --dmm=HEAD~3..HEAD` + +*The range form: tree HEAD vs tree HEAD~3.* + +**wall time: 14.99s** + +````` + + +

+

+ +````` + +## `./build/ripwire . --cochange --cochange-groups` + +*Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none of which it depends on" — a greedy cover, disclosed as greedy.* + +**wall time: 4.12s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [847 more display lines; full output is 56290 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --cochange --cochange-recur=2` + +*Only pairs whose co-change RECURS in 2+ sub-windows of the mined window (sub_windows= is the denominator) — a one-off sprint stops reading like a structural defect.* + +**wall time: 4.19s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --html=/aux/map2.html --color-by=community` + +*The HTML graph with the initial colour mode set to community (the page embeds all five modes and keeps a live selector).* + +**wall time: 2.41s** + +````` +(empty) +````` + +Artifact written: + +````` + 156158 /aux/map2.html +````` + +## `./build/ripwire . --index-out=/aux/ci_index` + +*CI generate-and-exit: cold-parse and write BOTH committable cache families (lean + rich), no map on stdout.* + +**wall time: 6.43s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (28364081 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (58137965 bytes, rich family) +````` + +Artifact written: + +````` + 28364081 /aux/ci_index.lean.ripwirecache + 58137965 /aux/ci_index.rich.ripwirecache + 86502046 total +````` + +## `./build/ripwire . --cache=/aux/ci_index.lean.ripwirecache --top-k=3` + +*Consume the lean artifact in a PR job: restore-equivalence, never blob-byte-identity.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [63 more display lines; full output is 8117 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3` + +*Eval-only: a per-call-site census of WHICH mechanism resolved each call, and the canonical id of every surviving target.* + +**wall time: 1.07s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [63 more display lines; full output is 8117 bytes on 1 raw line(s)] +````` + +Artifact written: + +````` +# ripwire pin-census v3 C=kind\tmech\tpre\tpost\tflags\tcaller_id\tcallee\ttargets(|-sep)\tline +# v3 field escape: every id and callee field is escaped, so no separator occurs inside one. A backslash is +# written \\, TAB \t, LF \n, CR \r, every other control byte 0x00-0x1f and the targets separator | as \xHH +# (lowercase hex). Split lines on LF, fields on TAB and targets on |, THEN decode. Columns are unchanged +# from v2 and an id without those bytes is spelled exactly as in v2; a C++ out-of-line template member +# whose template-argument list spans lines keeps its line break as \n instead of splitting the row. +# line is the 1-based call-site line in the caller's file (v2, appended LAST so v1 readers are unchanged): +# the key a SCIP occurrence joins on, so a coverage loss can be classified per site instead of guessed. + 128906 /aux/pin_census.tsv +````` + +## `./build/ripwire . --plan-lint=test/planlintfix/wave.md` + +*The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when a card or ledger row gates.* + +**exit code: 2** — **wall time: 1.01s** + +````` + + + + + + + + + + + + + +````` + +## `./build/ripwire . --plan-lint=test/planlintfix/wave_ledger.md` + +*The ledger-shaped fixture through the same check (exit 2: one gating card).* + +**exit code: 2** — **wall time: 1.19s** + +````` + + + + + + + +````` + +## `./build/ripwire . --doctor --agent=claude` + +*--doctor plus a LIVE integration inspection for one agent: PATH binary, installed-skill manifest parity, hook executability, MCP wiring — read-only, fixed repair commands, never config contents; exit 1 when any check fails.* + +**exit code: 1** — **wall time: 9.30s** + +````` + + + + + + + + + + + + + +````` + +## `./build/ripwire . --doctor --agent=nosuch` + +*Other --agent values refuse.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: unsupported --agent value 'nosuch' (supported: codex, claude) +````` + + +--- + +# the dirty-tree verbs (throwaway clone, NOT the read-only repo) + +Everything below runs with `cwd` = the throwaway clone at `/dirty` (`git clone --local` of this repo, then one deliberate regression in `src/infra/sortutil.h`). The read-only repo is never touched. The binary is the same `build/ripwire`, addressed absolutely. + +## `./build/ripwire . --situ` + +*Situational report for a real diff: blast radius + tests + co-change + forgotten co-change partners.* + +**wall time: 7.58s** + +````` +ripwire situational-awareness — 1 changed file(s), 13 symbols in them +root: . +at: c97f6f91c+dirty + change_set: granularity=file changed_symbols=13 file_symbols=13 files_narrowed=0 files_file_granular=1 diff_hunks=7 diff_hunks_located=6 diff_hunks_unlocated=1 — seeds = symbols a diff -U0-vs-HEAD hunk overlaps (function/method body spans only); an unlocated hunk (module-level, deletion, rename) … [line truncated: 34 more bytes on this line] + [1] blast radius: 246 symbols across 58 files transitively depend on these changes (showing 8 of 58 files — shown=8 total=58 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=58) + counts_floor=1 graph_ambiguous=2925 graph_unresolved=15128 graph_unindexed=269 (files no grammar in this build could read at all) (the map header's own gauges) — every count above is a FLOOR, never a total: call edges are name-based, so dynamic dispatch, callbacks and macros can be missing … [line truncated: 45 more bytes on this line] + src/mcpverbs.h (33 dependent symbols) + src/quality.h (15 dependent symbols) + src/verbs_quality.h (14 dependent symbols) + src/verbs_for.h (13 dependent symbols) + src/editcheck.h (9 dependent symbols) + src/resolve.h (9 dependent symbols) + src/verbs_navigate.h (9 dependent symbols) + src/graph.h (8 dependent symbols) + [2] tests to run (5) order=evidence: [changed] you edited it, [partner] named after a changed file, then hops asc (1 = direct); "(n): a, b" = n runner-less files sharing that evidence; a (run: …) is relative to root: + test/verify_radix.cpp [hops=1] (run: bash test/greptiercheck.sh) + test/adaptivecutshapefix/adaptive_cut_shape_test.cpp [hops=2] (run: bash test/adaptivecutshapecheck.sh) + test/includeprecise_unit.cpp [hops=2] (run: bash test/includeprecisecheck.sh) + test/verify_csr.cpp [hops=2] (run: bash test/a9disclosurecheck.sh) + test/rustimport_unit.cpp [hops=4] (run: bash test/rustimportprecisecheck.sh) + script_gates_unmodelled=779 — test/*.sh gates never appear above: script-to-binary edges are not call edges (a path count) + [3] co-change — usually edited with these but NOT in your diff (0) window="18mo@HEAD" commits="5228": + (none — 5228 commits were mined and none co-edited a file outside your diff) + next: --test-gate +````` + +## `./build/ripwire . --test-gate` + +*The pre-PR gate with real obligations — exit 4 when tests-to-run or untested blast radius is non-empty.* + +**exit code: 4** — **wall time: 5.90s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --quality-delta` + +*Every row carries p="file:line", the gating rows are marked gating="1" and now bar= (the threshold each numeric row is judged against), and the exit-2 refusal prints a naming line on stderr; a gating row carries a pasteable next=.* + +**exit code: 2** — **wall time: 38.60s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [148 more display lines; full output is 17984 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: api-surface src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey at src/infra/sortutil.h:109 (was=1 now=2) +````` + +## `./build/ripwire . --quality-delta --legend=full` + +*The same gating report under --legend=full — same rows, same exit 2; the default compact comment (schema="ripwire.quality-delta/v1", the shape an agent's edit loop should run) becomes ~4 KB of prose legend.* + +**exit code: 2** — **wall time: 16.52s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [148 more display lines; full output is 22976 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: api-surface src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey at src/infra/sortutil.h:109 (was=1 now=2) +````` + +## `./build/ripwire . --quality-delta --json` + +*The same findings as JSON (one of the CI/scripting verbs --json supports) — same exit 2 as the XML form.* + +**exit code: 2** — **wall time: 17.50s** + +````` +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":166,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"c97f6f91c+dirty","renames":62,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 239 more bytes on this line] +{"kind":"complexity","sym":"src/infra/sortutil.h::rw::sortutil::lessByScoreDescId","was":1,"now":67,"p":"src/infra/sortutil.h:49","gating":true}, +{"kind":"dead-code","sym":"src/infra/sortutil.h::rw::sortutil::nonNegativeFloatAscKeyCopy","p":"src/infra/sortutil.h:119","origin":"new-symbol"}, +{"kind":"dead-code","sym":"src/infra/sortutil.h::rw::sortutil::sortScoredIdsWithOptions","p":"src/infra/sortutil.h:129","origin":"new-symbol"}, +{"kind":"duplication","members":"src/infra/sortutil.h::rw::sortutil::nonNegativeFloatAscKeyCopy | src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey","tokens":59,"p":"src/infra/sortutil.h:119","gating":true}, +{"kind":"nesting","sym":"src/infra/sortutil.h::rw::sortutil::lessByScoreDescId","was":1,"now":6,"p":"src/infra/sortutil.h:49","gating":true}, +{"kind":"new-clone-of-reused-helper","sym":"src/infra/sortutil.h::rw::sortutil::nonNegativeFloatAscKeyCopy | src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey","was":0,"now":4,"p":"src/infra/sortutil.h:119","gating":true}, +{"kind":"params","sym":"src/infra/sortutil.h::rw::sortutil::sortScoredIdsWithOptions","was":0,"now":8,"p":"src/infra/sortutil.h:129","origin":"new-symbol"}, +{"kind":"verbosity","sym":"src/infra/sortutil.h::rw::sortutil::lessByScoreDescId","was":8,"now":47,"p":"src/infra/sortutil.h:49","sev":"minor"}], +"sa":[{"kind":"api-surface","key":"085e3d408c2c4a35","why":"target-gone"}, +{"kind":"api-surface","key":"155d74d341a49f3c","why":"target-gone"}, +{"kind":"api-surface","key":"1a15386c2d1e47af","why":"target-gone"}, +{"kind":"api-surface","key":"298e798c7f075715","why":"target-gone"}, +{"kind":"api-surface","key":"56acdf9b5c314a14","why":"target-gone"}, +{"kind":"api-surface","key":"5a07390012b46e06","why":"target-gone"}, +{"kind":"api-surface","key":"6eef859578c8c376","why":"target-gone"}, +{"kind":"api-surface","key":"6f2394762f82a855","why":"target-gone"}, +{"kind":"api-surface","key":"7f2c3eefdf6e512e","why":"target-gone"}, +{"kind":"api-surface","key":"802e513731103806","why":"target-gone"}, +{"kind":"api-surface","key":"b6a24afef32a68a8","why":"target-gone"}, +{"kind":"api-surface","key":"c923e661c197b265","why":"target-gone"}, +{"kind":"api-surface:new-symbol","key":"1926b0d9e94541a0","why":"target-gone"}, +{"kind":"api-surface:new-symbol","key":"1d3814ba687a4aef","why":"target-gone"}, +{"kind":"api-surface:new-symbol","key":"1db76028879244ef","why":"target-gone"}, +{"kind":"api-surface:new-symbol","key":"25c9f0504fd376a4","why":"target-gone"}, +{"kind":"api-surface:new-symbol","key":"36e39c7ab0fc1686","why":"target-gone"}, +{"kind":"api-surface:new-symbol","key":"4bce64bd920de0c3","why":"target-gone"}, +{"kind":"api-surface:new-symbol","key":"61f0e361ef7dee27","why":"target-gone"}, +{"kind":"api-surface:new-symbol","key":"79625906f9f71ad0","why":"target-gone"}, +{"kind":"api-surface:new-symbol","key":"8ecb190954dce18a","why":"target-gone"}, +… [145 more display lines; full output is 16575 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: api-surface src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey at src/infra/sortutil.h:109 (was=1 now=2) +````` + +## `./build/ripwire . --quality-delta --quality-ack --ack-only=zzznope` + +*NEW FLAG: --ack-only matching nothing REFUSES rather than falling back to acking everything.* + +**exit code: 1** — **wall time: 13.34s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: ack provenance backfill — 0 clone row(s) reconstructed from the current tree, 214 re-derived, 20 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 56 left legacy (member set not found cloning here — a floor, not proof it is gone), 1311 ineligible (no cu … [line truncated: 200 more bytes on this line] +ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written +````` + +## `./build/ripwire . --quality-delta --quality-ack --ack-only=api-surface` + +*NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* + +**wall time: 11.86s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: ack provenance backfill — 0 clone row(s) reconstructed from the current tree, 214 re-derived, 20 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 56 left legacy (member set not found cloning here — a floor, not proof it is gone), 1311 ineligible (no cu … [line truncated: 200 more bytes on this line] +ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already acked) → ./.ripwire_quality_acks +````` + +## `./build/ripwire . --quality-delta` + +*Re-run after the partial ack: acked=3, the rest still gate (exit 2).* + +**exit code: 2** — **wall time: 16.29s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [147 more display lines; full output is 17687 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: --quality-delta gating: 4 preexisting-worse major finding(s); first: complexity src/infra/sortutil.h::rw::sortutil::lessByScoreDescId at src/infra/sortutil.h:49 (was=1 now=67) +````` + +## `./build/ripwire . --ack-only=gating` + +*--ack-only WITHOUT --quality-ack REFUSES loudly (exit 1, the pairing named) — it used to be silently ignored.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire

--quality-delta --ack-only=contract-change --quality-ack="reason") +````` + +## `./build/ripwire . --edit-check=nonNegativeFloatDescKey` + +*A real contract-change: was=1 now=2 params, with the call sites that are now provably incompatible, and a pasteable next= (--uses=SYM) on the root.* + +**wall time: 2.01s** + +````` + + + + + + +````` + +## `./build/ripwire . --edit-check=nonNegativeFloatDescKey --legend=full` + +*The same verdict under --legend=full: the default's one compact comment (the post-edit reflex at its cheapest) becomes ~5.7 KB of prose legend, every row identical.* + +**wall time: 2.29s** + +````` + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*The review-evidence bundle with an actual changed file.* + +**wall time: 5.02s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [80 more display lines; full output is 8063 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --map-diff --top-k=5` + +*The map re-ranked with a teleport toward the changed file (changed=1 here, not 0).* + +**wall time: 2.75s** + +````` + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --clones` + +*The duplicated helper the sandbox edit introduced shows up as a clone group.* + +**wall time: 3.01s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [308 more display lines; full output is 16222 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --stray-content=zz-orphan` + +*CHANGED: a ref with NO merge base with HEAD now reports v="unknown" ok="0" in its own bucket — the absence of an answer, never a claim it is merged. (The sandbox carries a deliberately parentless branch built with `git commit-tree`; a shallow CI clone puts every ref here.)* + +**wall time: 2.52s** + +````` + + + + + +````` + +stderr: + +````` +[math degraded] crossref: no merge-base for ref (shallow clone or unrelated history?) — verdict is unknown, not merged (crossref.h:1274, RefPlumbing rw::crossref::probeRefBase(const std::string &, const RefInfo &, const std::string &) — logged once per site) +````` + +## `./build/ripwire . --stray-content=zz-orphan --plan` + +*CHANGED: --plan surfaces those same refs as an row rather than silently dropping them.* + +**wall time: 3.41s** + +````` + + + + +````` + +stderr: + +````` +[math degraded] crossref: no merge-base for ref (shallow clone or unrelated history?) — verdict is unknown, not merged (crossref.h:1274, RefPlumbing rw::crossref::probeRefBase(const std::string &, const RefInfo &, const std::string &) — logged once per site) +````` + +## `./build/ripwire . --dmm` + +*The DMM scalar on a REAL delta: the sandbox edit grew one unit past the nesting/complexity thresholds and added an 8-parameter one, so dmm is low and the three sub-scores say which property moved.* + +**wall time: 7.33s** + +````` + + +

+

+ +````` + +## `./build/ripwire . --quality-delta --scope=src/graph.h` + +*OWNERSHIP partition for a shared tree: every regression here lives in src/infra/, so under a scope naming src/graph.h they ALL print under with a do-not-ack banner and never gate — scoped-out-gating= says how many would have.* + +**wall time: 17.42s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [150 more display lines; full output is 17954 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +````` + +## `./build/ripwire . --quality-delta --quality-ack --scope=src/graph.h --ack-only=api-surface` + +*The rubber-stamp guard: an --ack-only that names an OUT-OF-SCOPE row refuses (exit 1) and writes nothing.* + +**exit code: 1** — **wall time: 12.37s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=src/graph.h — refusing, and writing nothing at all: + api-surface src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey at src/infra/sortutil.h:109 + those rows belong to whoever is editing those paths. Acking them here writes their debt into a committed ledger under YOUR + reason string, which is how a per-finding ratchet becomes a rubber stamp. Narrow the pattern, or widen the scope if they really are yours. +````` + +## `./build/ripwire . --handoff` + +*The continuation packet with a REAL diff: verified changed symbols + blast radius + tests-to-run, then the heuristic rows.* + +**wall time: 6.14s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --note-add="lessByScoreDescId: keep this branch-free — it sits inside the PageRank sort comparator"` + +*Pin a field note (write-side memory) to a symbol; committed to .ripwire_notes in the sandbox. The BARE name is resolved through the same resolver the read verbs use and stored as the canonical id — the rewrite is echoed on stderr, because a silent one is not a disclosure.* + +**wall time: 1.63s** + +````` +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-24 keep this branch-free — it sits inside the PageRank sort comparator c97f6f91c588c5ccfe70aa46eb40d8beb69f31fe integration/train-34 +````` + +stderr: + +````` +ripwire: --note-add: tip: notes that say "chose X over Y because Z" surface better — consider adding the why +ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sortutil.h::rw::sortutil::lessByScoreDescId' — that is the id --for/--expand key notes by +````` + +## `./build/ripwire . --notes` + +*The note is listed under the canonical id, dangling="0" — i.e. it will actually surface. (Before the H1 fix the bare name was stored verbatim and read dangling="1": recorded, and surfaced nowhere.)* + +**wall time: 1.14s** + +````` + + + + + + + + + + + + + + + + gate scripts' claim (~line 1305) is NOT enforced — the derived-vs-stated sibling loop here covers docs/EVALS.md only. It drifted 407→451 unnoticed (fixed 2026-08-23). To close: grep both files ('file:line:' parsing) in the gateCountClaims arm.]]> + + + + +````` + +## `./build/ripwire . --note-add="gitOneLine: which one?"` + +*Two definitions carry this name, so the write REFUSES rather than pick one: a note keys ONE canonical id, and an ambiguous selector is refused, never silently narrowed. Every candidate is named, with a runnable retry.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --note-add: tip: notes that say "chose X over Y because Z" surface better — consider adding the why +ripwire: --note-add: target 'gitOneLine' is ambiguous — it matches 2 definitions in 2 distinct contracts, and a note keys ONE canonical id (it would surface on one of them and look absent on the rest). Qualify one: ./src/handoff.h:gitOneLine, ./src/quality.h:gitOneLine — e.g. --note-add="./src/h … [line truncated: 33 more bytes on this line] +````` + +## `./build/ripwire . --note-add="lessByScoreDescIdd: typo"` + +*A name that resolves to nothing is refused with the read verbs' own did-you-mean — never written as a dead note.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --note-add: tip: notes that say "chose X over Y because Z" surface better — consider adding the why +ripwire: --note-add: target not found: lessByScoreDescIdd (did you mean 'lessByScoreDescId'?) — a note keys the canonical id a read verb resolves; to note a FILE instead, pass a path (one with a '/' or an extension), which may name a file that does not exist yet +````` + +## `./build/ripwire . --note-add="src/infra/sortutil.h::rw::sortutil::lessByScoreDescId: chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort"` + +*The same symbol addressed by its CANONICAL id: already canonical, so nothing is rewritten and both notes land on ONE target.* + +**wall time: 1.09s** + +````` +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-24 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort c97f6f91c588c5ccfe70aa46eb40d8beb69f31fe integration/train-34 +````` + +## `./build/ripwire . --expand=lessByScoreDescId --top-k=0` + +*Both notes riding along with the symbol's body — the elements follow the body, past the display cut, so they are extracted below.* + +````` + + + + + + + + + + + + + +````` + +## `./build/ripwire . --insert-after-symbol=lessByScoreDescId --edit-payload=/aux/payload_note.h` + +*Insert immediately AFTER one uniquely-resolved definition; replaced_bytes=0 because the insert verbs never overwrite. The receipt carries the folded post-edit verification (lines=, edit_check, tests_to_run) so the loop closes in one call.* + +**wall time: 4.12s** + +````` +{"applied":"insert_after_symbol","symbol":"lessByScoreDescId","file":"src/infra/sortutil.h","span":{"start":2375,"end":2463},"lines":{"start":56,"end":58},"replaced_bytes":0,"old_file_bytes":10824,"new_file_bytes":10912,"file_eol":"lf","eol_normalized":false,"trailing_newline_folded":true,"separator … [line truncated: 652 more bytes on this line] +"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +{"p":"test/adaptivecutshapefix/adaptive_cut_shape_test.cpp","hops":2,"run":"bash test/adaptivecutshapecheck.sh"}, +{"p":"test/includeprecise_unit.cpp","hops":2,"run":"bash test/includeprecisecheck.sh"}, +{"p":"test/verify_csr.cpp","hops":2,"run":"bash test/a9disclosurecheck.sh"}, +{"p":"test/rustimport_unit.cpp","hops":4,"run":"bash test/rustimportprecisecheck.sh"}], +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":779,"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +````` + +stderr: + +````` +ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, tests_to_run; next: bash test/greptiercheck.sh +````` + +## `./build/ripwire . --insert-after-symbol=lessByScoreDescId --edit-payload=/aux/payload_note.h --no-post-check` + +*The opt-out: the same insert with the folded verification skipped — lines= still rides (it is free), edit_check/tests_to_run do not, and the two pasteable commands stay on stderr.* + +**wall time: 1.92s** + +````` +{"applied":"insert_after_symbol","symbol":"lessByScoreDescId","file":"src/infra/sortutil.h","span":{"start":2375,"end":2463},"lines":{"start":56,"end":58},"replaced_bytes":0,"old_file_bytes":10912,"new_file_bytes":11000,"file_eol":"lf","eol_normalized":false,"trailing_newline_folded":true,"separator … [line truncated: 572 more bytes on this line] +````` + +stderr: + +````` +ripwire edit: applied atomically; receipt carries region, blob_sha (post-check skipped); next: --edit-check=src/infra/sortutil.h:lessByScoreDescId +````` + +## `./build/ripwire . --insert-before-symbol=nonNegativeFloatDescKey --edit-payload=/aux/payload_note.h --edit-target-file=src/infra/sortutil.h` + +*Insert BEFORE, with --edit-target-file pinning which same-named definition (here unambiguous — the disambiguator is simply honoured).* + +**wall time: 4.88s** + +````` +{"applied":"insert_before_symbol","symbol":"nonNegativeFloatDescKey","file":"src/infra/sortutil.h","span":{"start":2895,"end":2983},"lines":{"start":74,"end":75},"replaced_bytes":0,"old_file_bytes":11000,"new_file_bytes":11088,"file_eol":"lf","eol_normalized":false,"trailing_newline_folded":false,"s … [line truncated: 755 more bytes on this line] +{"n":"benchAdaptive","p":"bench/bench_radix_ab.cpp:157","l":[162]}, +{"n":"radixSortNonNegativeFloatsDesc","p":"src/infra/sortutil.h:105","l":[114]}, +{"n":"radixSortByScoreDescId","p":"src/infra/sortutil.h:120","l":[180]}], +"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +{"p":"test/adaptivecutshapefix/adaptive_cut_shape_test.cpp","hops":2,"run":"bash test/adaptivecutshapecheck.sh"}, +{"p":"test/includeprecise_unit.cpp","hops":2,"run":"bash test/includeprecisecheck.sh"}, +{"p":"test/verify_csr.cpp","hops":2,"run":"bash test/a9disclosurecheck.sh"}, +{"p":"test/rustimport_unit.cpp","hops":4,"run":"bash test/rustimportprecisecheck.sh"}], +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":779,"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +````` + +stderr: + +````` +ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, tests_to_run; next: --uses=src/infra/sortutil.h:nonNegativeFloatDescKey +````` + +## `./build/ripwire . --replace-symbol-body=DoesNotExist --edit-payload=/aux/payload_note.h` + +*An unknown TARGET refuses and leaves every file byte-identical.* + +**exit code: 1** — **wall time: 2.09s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --replace-symbol-body: symbol 'DoesNotExist' not found +````` + +## `./build/ripwire . --edit-plan=/aux/edit_plan.json --dry-run` + +*A versioned multi-edit TRANSACTION preflighted without writing: the receipt shows what each op would read and touch.* + +**wall time: 2.03s** + +Input file: + +````` +{ + "version": 1, + "edits": [ + { + "op": "insert_before_symbol", + "target": "nonNegativeFloatDescKey", + "payload": "plan_note.h" + } + ] +} +````` + +````` +{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false," … [line truncated: 353 more bytes on this line] +````` + +## `./build/ripwire . --edit-plan=/aux/edit_plan.json --apply` + +*The same plan committed: per-file locks, re-verify-before-write, atomic rename, rollback on a later failure.* + +**wall time: 4.29s** + +````` +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mult … [line truncated: 537 more bytes on this line] +{"n":"benchAdaptive","p":"bench/bench_radix_ab.cpp:157","l":[162]}, +{"n":"radixSortNonNegativeFloatsDesc","p":"src/infra/sortutil.h:107","l":[116]}, +{"n":"radixSortByScoreDescId","p":"src/infra/sortutil.h:122","l":[182]}], +"graph_ambiguous":2925,"graph_unresolved":15128,"graph_unindexed":269,"counts_floor":true}}]} +````` + +## `./build/ripwire . --edit-plan=/aux/edit_plan.json` + +*Neither --dry-run nor --apply: the mode is explicit, so this refuses.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --edit-plan requires exactly one of --dry-run or --apply +````` + +## `./build/ripwire . --quality-delta` + +*After the agent's edits: the complexity/nesting rows on lessByScoreDescId are gone (the replace undid them), the rest still gate (exit 2).* + +**exit code: 2** — **wall time: 18.42s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [144 more display lines; full output is 17051 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: --quality-delta gating: 2 preexisting-worse major finding(s); first: duplication src/infra/sortutil.h::rw::sortutil::nonNegativeFloatAscKeyCopy | src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey at src/infra/sortutil.h:88 (was=0 now=59) +````` + +## `./build/ripwire . --quality-baseline` + +*REFUSES, exit 1: this sandbox tree is already regressed, and pinning here would swallow that debt into the floor so every later delta read clean. It names how many gating findings it would absorb, the first of them, and the way forward.* + +**exit code: 1** — **wall time: 11.81s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --quality-baseline: this tree already holds 2 gating finding(s) against HEAD — pinning here would absorb + them into the floor, and every later --quality-delta would read clean. First: duplication src/infra/sortutil.h::rw::sortutil::nonNegativeFloatAscKeyCopy | src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey (was=0 now=59) + Commit the tree first, or pass --allow-dirty to pin anyway (the sidecar then records the 2 absorbed, and every + report against it carries baseline_absorbed="2"). +````` + +## `./build/ripwire . --quality-baseline --allow-dirty` + +*The consent form: pin anyway. The sidecar is stamped with the dirty pin and the absorbed count, so the fact outlives the process that knew it.* + +**wall time: 15.10s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --quality-baseline --allow-dirty: pinned with 2 gating finding(s) ABSORBED into the floor (stamped in the sidecar; every --quality-delta against it carries baseline_absorbed="2") +ripwire: wrote ./.ripwire_quality_baseline (snapshot of 30704 per-symbol rows, 31642 indexed symbols total) +````` + +Artifact written: + +````` + 5197963 .ripwire_quality_baseline +# ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit +producer 819bbf9fd6fd06a8db334bbe702e4325902726c09c1613707e652f98ed144539 +head c97f6f91c588c5ccfe70aa46eb40d8beb69f31fe +dirty 1 +absorbed 2 +ccx 67c15011ee8b 0 +ccx d19d7c13cbc7 2 +ccx 1f7edbff929e5 10 +ccx 6a732f6699b +````` + +## `./build/ripwire . --quality-delta` + +*Against that sidecar the same tree reads regressions=0 — but baseline_absorbed= is on the root, so this green means clean SINCE THE PIN, never clean. A baseline is a floor YOU chose, and it belongs BEFORE the change.* + +**wall time: 18.22s** + +````` + + +````` + + +--- + +# the MCP dialect — the same verbs over stdio JSON-RPC (one-shot exchange, not a persistent server) + +## `printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | ./build/ripwire --mcp` + +*initialize + tools/list: the manifest an agent host loads at session start — every verb's name, description and input schema.* + +````` +{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"ripwire","version":"1.0"},"capabilities":{"tools":{},"resources":{}},"instructions":"Map before reading files. Start a new task with explore; use from_trace for an error; use impact plus uses before changing a sym … [line truncated: 738 more bytes on this line] +{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"analyze","description":"Architecture map for a directory: signatures and the call graph for the top symbols. Use when landing cold in a repo or subdir, before reading files; for a task-scoped inventory use 'for', for one symbol's neighborhood find_ … [line truncated: 742 more bytes on this line] +{"name":"rank_by","description":"The SAME architecture map 'analyze' serves, ranked by a different signal instead of plain PageRank. rank_by = pagerank (default, omit it — the CLI's own unbiased --rank-by=pagerank; 'analyze' can rank differently on a tree with uncommitted changes, where it biases … [line truncated: 1077 more bytes on this line] +{"name":"find_symbol","description":"A symbol's 1-hop neighborhood: the symbol (with a fetch_body handle) plus direct callers (calledBy) and callees (calls). Full transitive reach: 'impact'. Read/write/import sites, not just calls: 'uses'. JSON {symbol, calledBy, calls, defs, count, hop_tested, hop_ … [line truncated: 1181 more bytes on this line] +{"name":"find_referencing_symbols","description":"Direct (1-hop) callers of a symbol, each with a fetch_body handle. For the full transitive blast radius use 'impact', for read/write/import sites 'uses'. JSON {symbol, calledBy, defs, count, hop_tested, hop_untested, declined_calls, counts_floor}; ca … [line truncated: 945 more bytes on this line] +{"name":"grep","description":"Trigram literal search; each hit annotated with its enclosing symbol (which function/class it is in). Hits carry text; enclosing rows a fetch_body handle. pattern = the literal; in=any lifts the span tiering (default: tightest non-empty tier; rest rides as suppressed_co … [line truncated: 837 more bytes on this line] +{"name":"cochange","description":"Files that historically change together with this file — the co-edit partners. Fowler's Shotgun Surgery as change coupling. dep_capable=false means neither side could carry one (sh/md/json/binary), so surprising is undefined rather than informative. file = the fil … [line truncated: 700 more bytes on this line] +{"name":"memory_recall","description":"Most relevant memory notes / docs for a task, full text — the few that matter, not the whole corpus. path = docs/memory dir; task = what you're working on; top_k = docs to return, 1..1000 (default 8), refused outside that band, never clamped; budget_tokens = … [line truncated: 790 more bytes on this line] +{"name":"situational_awareness","description":"The 5 things to know about a diff, as JSON: blast_radius, tests_to_run, forgotten (usual co-change partners missing from this diff), hotspot_alert, modules_touched. forgotten = the Shotgun Surgery check. diff/files optional — defaults to 'git diff HEA … [line truncated: 1297 more bytes on this line] +{"name":"mentions","description":"Docs (markdown plans/designs) that name a code symbol in a backtick. symbol = the code symbol name; limit/offset page the files. An @FILE:LINE line-seed rebinds to the innermost definition enclosing that line and answers for it, disclosing the rebound name as 'sym' … [line truncated: 625 more bytes on this line] +{"name":"for","description":"Task-lens ranked, signatures-only inventory of the building blocks most relevant to a task (cx=complexity, in=reuse-count). task = the task in plain words; budget_tokens = an optional soft row target (the CLI --for --token-budget). The header carries route= (which ranker … [line truncated: 1061 more bytes on this line] +{"name":"lego","description":"Interface-to-impls view for ONE named interface/base: its method contract plus EVERY implementor (own-language only), each with file path. Use when implementing against a KNOWN interface (contrast 'for', which sprays top interfaces for a task). type = interface/base nam … [line truncated: 841 more bytes on this line] +{"name":"owners","description":"Bus-factor: recency-weighted (6-month half-life) author ownership per file. symbol = optional, restricts to the file that defines it; limit/offset page the rows. at= is the commit these numbers were computed at (+dirty = the working tree differed). An @FILE:LINE line- … [line truncated: 879 more bytes on this line] +{"name":"replace_symbol_body","description":"Replace a symbol's ENTIRE definition (signature through closing brace) with new_body — splices over the full def span, preserving every byte outside it verbatim; new_body must be a complete, well-formed definition. One trailing newline folds (trailing_n … [line truncated: 1323 more bytes on this line] +{"name":"insert_before_symbol","description":"Insert text immediately BEFORE a symbol's definition (its first byte); padded to the file's own blank-line seam (separator_padded=N). Same refusal contract as replace_symbol_body (not found / ambiguous / stale index / symlink / concurrent write, file unc … [line truncated: 877 more bytes on this line] +{"name":"insert_after_symbol","description":"Insert text immediately AFTER a symbol's definition (past its final byte, which is preserved exactly); padded to the file's own blank-line seam (separator_padded=N); one trailing newline folds (trailing_newline_folded). Same refusal contract as replace_sy … [line truncated: 878 more bytes on this line] +{"name":"fetch_body","description":"Full (or partial-range) source of a symbol's definition, addressed by the stable `handle` a read verb attached to it (bodies on request, not by default). start_line/end_line are optional, 1-based, INCLUSIVE and BODY-RELATIVE (line 1 = the def's first line), clampe … [line truncated: 1049 more bytes on this line] +{"name":"exemplar","description":"BEFORE writing a function / method / class / struct / interface / variable, get the repo's single best-in-class instance of that kind to imitate — signature AND full body. chosen by ROLE, NEVER by text similarity to your task: candidates are first filtered to cogn … [line truncated: 1375 more bytes on this line] +{"name":"quality_delta","description":"Your PR self-check, run every time you think a change is DONE — pairs with the CLI-only --test-gate (names the tests to run + the untested blast radius; not MCP-exposed) to form the two-step pre-PR gate. Reports ONLY what your working tree made WORSE vs basel … [line truncated: 862 more bytes on this line] +{"name":"quality_baseline","description":"PIN the quality floor: writes .ripwire_quality_baseline stamped with the current git HEAD sha, snapshotting complexity / duplication / dead-code / API surface. Call once at the start of non-trivial work, then quality_delta compares against this pinned floor … [line truncated: 445 more bytes on this line] +{"name":"impact","description":"IS IT SAFE TO CHANGE X? — the TRANSITIVE blast radius of a symbol via calls, nearest first (d= hop depth), then PageRank. Use before modifying or deleting a symbol; it beats find_referencing_symbols (direct callers only), and 'uses' catches the read/write/import sit … [line truncated: 1325 more bytes on this line] +{"name":"uses","description":"The STATICALLY RESOLVABLE use-sites of a symbol, not just calls: role (call | read | write | import | extends), file:line, and enclosing symbol. Use to see the footprint before renaming or changing a name — find_referencing_symbols and impact follow only calls. extern … [line truncated: 1146 more bytes on this line] +{"name":"affected","description":"WHICH TESTS TO RUN for a change — test files that transitively reach the changed files/symbols, ranked by evidence (edited > partner-named > hop distance). files = changed files and/or symbols, comma-separated: each item is tried as an indexed PATH pattern first, … [line truncated: 1238 more bytes on this line] +{"name":"path_between","description":"Does A REACH B, and HOW? — the shortest directed CALL path between two symbols, hop-by-hop. reachable=\"0\" hops=\"0\" is a valid 'not reachable' answer — call edges are name-based, so a missing dynamic/callback edge can hide a real path. Named path_between … [line truncated: 709 more bytes on this line] +{"name":"connect","description":"When a task touches 2..16 named symbols, returns the minimal subgraph RELATING them - terminals, the fewest joining intermediaries (with signatures), and call edges in true direction - finding the shared-caller joins a directed path_between cannot; unrelated symbols … [line truncated: 950 more bytes on this line] +{"name":"explore","description":"ONE-call task orientation: the routed+anchored ranking, full bodies of the top hits, their 1-hop callers, field notes, and tests_to_run — ALL under one deterministic byte budget, in a fixed section order (ranking > bodies > callers > notes > tests) that degrades gr … [line truncated: 2135 more bytes on this line] +{"name":"from_trace","description":"Paste a stack trace / sanitizer report / compiler error and get it mapped onto indexed symbols, ranked INNERMOST-first: the parsed frame map, the ranked suspects' signatures, and the innermost in-corpus symbol's FULL body. Out-of-corpus frames are listed a … [line truncated: 1233 more bytes on this line] +{"name":"edit_check","description":"Just edited a symbol? Did its CONTRACT (param count + publicness) change vs git HEAD, and which 1-hop callers are NOW INCOMPATIBLE with the new arity by fixed-arity evidence (not a guess — every folded definition disagrees)? This is call sites worth OPENING, not … [line truncated: 1908 more bytes on this line] +{"name":"whereis","description":"WHERE DOES THIS CONTENT LIVE? Which branch's tree defines or mentions a symbol, HEAD first, with on-head=0 naming the case this verb exists for: content that lives only on a branch (a finished fix stranded on 1 of 30 refs). Each distinct blob is read once (content-ad … [line truncated: 1475 more bytes on this line] +{"name":"stray_content","description":"Per branch: the lines its own divergent work AUTHORED (vs its merge-base with HEAD) that the live line does NOT have. Four verdicts (unmerged+superseded+merged+unknown=refs): v=unmerged is genuinely absent; v=superseded means the live line re-implemented the wo … [line truncated: 1123 more bytes on this line] +{"name":"flags","description":"WHAT IS BUILT BUT DARK here — the answer to 'why don't I see feature X?'. Harvests all three gate patterns (ifndef/define header gates, CMake option(), getenv reads) with each gate's kind, DEFAULT, the size of the code it guards, and its read sites. When a name is bo … [line truncated: 1434 more bytes on this line] +{"name":"doc_drift","description":"WHICH OF THIS REPO'S DOC CLAIMS ARE NOW FALSE. Verifies the CHECKABLE anchors in every markdown file against the live index and returns ONLY the ones that no longer hold: file:line refs (missing-file / past-eof / line-moved), backticked symbol mentions (undefined), … [line truncated: 1167 more bytes on this line] +{"name":"slice","description":"WHERE IS THIS VARIABLE DEFINED AND USED inside one function — NAME-BASED intra-procedural def-use rows of one variable inside ONE uniquely-resolved definition (the ARISE slicer, arXiv:2605.03117). symbol alone lists the sliceable locals to pick from; add var (or spel … [line truncated: 1722 more bytes on this line] +{"name":"batch","description":"ONE-TURN CONTEXT SWEEP: answer up to 16 heterogeneous READ sub-queries in a single call (the deterministic $0 counterpart of a parallel-search agent). queries = array over the SAME path, in EITHER grammar: {verb, ...args} objects, or the CLI --batch file's own \"verb:a … [line truncated: 2040 more bytes on this line] +````` + +The manifest, summarised (name / description bytes / required args) — what the host pays in context every session: + +````` +tools= 33 manifest_bytes= 47445 (~tokens at 4 bytes/token: 11861 ) +explore desc_bytes= 1340 schema_bytes= 958 required=['task'] +batch desc_bytes= 1235 schema_bytes= 962 required=['queries'] +edit_check desc_bytes= 1180 schema_bytes= 889 required=['symbol'] +slice desc_bytes= 953 schema_bytes= 932 required=['symbol'] +whereis desc_bytes= 701 schema_bytes= 941 required=['symbol'] +flags desc_bytes= 881 schema_bytes= 720 required=[] +exemplar desc_bytes= 954 schema_bytes= 591 required=[] +replace_symbol_body desc_bytes= 865 schema_bytes= 610 required=['symbol', 'new_body'] +impact desc_bytes= 821 schema_bytes= 666 required=['symbol'] +situational_awareness desc_bytes= 865 schema_bytes= 581 required=[] +affected desc_bytes= 790 schema_bytes= 610 required=['files'] +from_trace desc_bytes= 836 schema_bytes= 559 required=['trace'] +find_symbol desc_bytes= 752 schema_bytes= 590 required=['symbol'] +doc_drift desc_bytes= 728 schema_bytes= 604 required=[] +uses desc_bytes= 626 schema_bytes= 682 required=['symbol'] +stray_content desc_bytes= 678 schema_bytes= 604 required=[] +rank_by desc_bytes= 758 schema_bytes= 481 required=[] +for desc_bytes= 453 schema_bytes= 777 required=['task'] +fetch_body desc_bytes= 623 schema_bytes= 588 required=['handle'] +connect desc_bytes= 535 schema_bytes= 580 required=['symbols'] +find_referencing_symbols desc_bytes= 505 schema_bytes= 590 required=['symbol'] +insert_after_symbol desc_bytes= 458 schema_bytes= 577 required=['symbol', 'text'] +insert_before_symbol desc_bytes= 455 schema_bytes= 578 required=['symbol', 'text'] +owners desc_bytes= 425 schema_bytes= 625 required=[] +quality_delta desc_bytes= 742 schema_bytes= 279 required=[] +lego desc_bytes= 360 schema_bytes= 651 required=['type'] +grep desc_bytes= 445 schema_bytes= 562 required=['pattern'] +memory_recall desc_bytes= 447 schema_bytes= 500 required=['task'] +path_between desc_bytes= 357 schema_bytes= 506 required=['from', 'to'] +cochange desc_bytes= 388 schema_bytes= 476 required=['file'] +analyze desc_bytes= 546 schema_bytes= 318 required=[] +mentions desc_bytes= 305 schema_bytes= 486 required=['symbol'] +quality_baseline desc_bytes= 407 schema_bytes= 193 required=[] +````` + +## `printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | ./build/ripwire --mcp --mcp-tools=core` + +*--mcp-tools=core: the server lists only the core profile's tools (explore, batch, from_trace, impact, uses, fetch_body, edit_check, quality_delta), so a host that loads every schema at session start pays only for those.* + +````` +{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"ripwire","version":"1.0"},"capabilities":{"tools":{},"resources":{}},"instructions":"Map before reading files. Start a new task with explore; use from_trace for an error; use impact plus uses before changing a sym … [line truncated: 955 more bytes on this line] +{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"fetch_body","description":"Full (or partial-range) source of a symbol's definition, addressed by the stable `handle` a read verb attached to it (bodies on request, not by default). start_line/end_line are optional, 1-based, INCLUSIVE and BODY-RELAT … [line truncated: 1092 more bytes on this line] +{"name":"quality_delta","description":"Your PR self-check, run every time you think a change is DONE — pairs with the CLI-only --test-gate (names the tests to run + the untested blast radius; not MCP-exposed) to form the two-step pre-PR gate. Reports ONLY what your working tree made WORSE vs basel … [line truncated: 862 more bytes on this line] +{"name":"impact","description":"IS IT SAFE TO CHANGE X? — the TRANSITIVE blast radius of a symbol via calls, nearest first (d= hop depth), then PageRank. Use before modifying or deleting a symbol; it beats find_referencing_symbols (direct callers only), and 'uses' catches the read/write/import sit … [line truncated: 1325 more bytes on this line] +{"name":"uses","description":"The STATICALLY RESOLVABLE use-sites of a symbol, not just calls: role (call | read | write | import | extends), file:line, and enclosing symbol. Use to see the footprint before renaming or changing a name — find_referencing_symbols and impact follow only calls. extern … [line truncated: 1146 more bytes on this line] +{"name":"explore","description":"ONE-call task orientation: the routed+anchored ranking, full bodies of the top hits, their 1-hop callers, field notes, and tests_to_run — ALL under one deterministic byte budget, in a fixed section order (ranking > bodies > callers > notes > tests) that degrades gr … [line truncated: 2135 more bytes on this line] +{"name":"from_trace","description":"Paste a stack trace / sanitizer report / compiler error and get it mapped onto indexed symbols, ranked INNERMOST-first: the parsed frame map, the ranked suspects' signatures, and the innermost in-corpus symbol's FULL body. Out-of-corpus frames are listed a … [line truncated: 1233 more bytes on this line] +{"name":"edit_check","description":"Just edited a symbol? Did its CONTRACT (param count + publicness) change vs git HEAD, and which 1-hop callers are NOW INCOMPATIBLE with the new arity by fixed-arity evidence (not a guess — every folded definition disagrees)? This is call sites worth OPENING, not … [line truncated: 1908 more bytes on this line] +{"name":"batch","description":"ONE-TURN CONTEXT SWEEP: answer up to 16 heterogeneous READ sub-queries in a single call (the deterministic $0 counterpart of a parallel-search agent). queries = array over the SAME path, in EITHER grammar: {verb, ...args} objects, or the CLI --batch file's own \"verb:a … [line truncated: 2040 more bytes on this line] +````` + +The core manifest, summarised (name / description bytes / required args): + +````` +tools= 8 manifest_bytes= 14186 (~tokens at 4 bytes/token: 3546 ) +explore desc_bytes= 1340 schema_bytes= 958 required=['task'] +batch desc_bytes= 1235 schema_bytes= 962 required=['queries'] +edit_check desc_bytes= 1180 schema_bytes= 889 required=['symbol'] +impact desc_bytes= 821 schema_bytes= 666 required=['symbol'] +from_trace desc_bytes= 836 schema_bytes= 559 required=['trace'] +uses desc_bytes= 626 schema_bytes= 682 required=['symbol'] +fetch_body desc_bytes= 623 schema_bytes= 588 required=['handle'] +quality_delta desc_bytes= 742 schema_bytes= 279 required=[] +````` + +## `printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"for","arguments":{"path":".","task":"pagerank power iteration"}}}' | ./build/ripwire --mcp` + +*MCP `for`: always bundle=sigs (never the CLI's compact route), the same ranked signatures as --for.* + +**wall time: 2.84s** + +````` +{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"ripwire","version":"1.0"},"capabilities":{"tools":{},"resources":{}},"instructions":"Map before reading files. Start a new task with explore; use from_trace for an error; use impact plus uses before changing a sym … [line truncated: 738 more bytes on this line] +{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":". window: shown= capped= (capped=1 cut). at=: commit+dirty+shallow. next= … [line truncated: 6553 more bytes on this line] +"_index":"[index: files=3589 symbols=31640 hash=57665a56]","_fresh":"ok"}} +{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"" ); @@ -720,6 +773,7 @@ GrepScanPhases collectGrepScanPhases( const rw::Config& cfg, const rw::IngestRes phases.found = grepApplySpanTiers( ing, std::move( phases.found ), ( cfg.grepIn == "any" ) ? GrepIn::Any : GrepIn::Code, phases.tier, /*useMemo=*/!cfg.noCache, cfg.grepRegex ? std::string_view() : std::string_view( pat ) ); } + phases.found = grepFoldLines( std::move( phases.found ) ); // honest-lex: one row per line, occ= for the rest // §R-J: additive scan over CrawlSkips::unsupported — the "unsupported-ext, text-looking" population the // crawl already computed at ingest time (queries/*/tags.scm and its siblings). Reuses the SAME per-file // ceiling the crawl applies to indexed files, so a huge unsupported-ext file is excluded exactly like an @@ -981,8 +1035,7 @@ int emitGrepReport( const MainDispatch& d ) // the WINDOW: a pure slice of the sorted list. pageWindow() clamps a past-the-end offset to an empty // page, so a 64-bit offset can never index out of range. const PageWindow grepPage = pageWindow( hitCount, rowCap, cfg.pageOffset ); - const std::vector hits = grepEnrich( ing, std::span( found.raw ).subspan( grepPage.begin, grepPage.end - grepPage.begin ), - cfg.grepBefore, cfg.grepAfter ); + const std::vector hits = grepEnrichPage( ing, found, grepPage.begin, grepPage.end - grepPage.begin, cfg.grepBefore, cfg.grepAfter ); const std::vector encRows = grepEnclosingRows( ing, g, std::span( hits ) ); // R1b, before the legend const std::vector encFloors = grepEncFloors( ing, g, encRows ); const bool anyEncFloor = std::ranges::any_of( encFloors, []( const CallerFloor& f ) { return f.isFloor; } ); @@ -1190,13 +1243,14 @@ int emitGrepReport( const MainDispatch& d ) // P3 (L7): next= on the root, defined where the reader meets it "next= is the one pasteable follow-up: the at verb on the top hit; the next page (compact legend) when cut; " "the conceptual lens on a zero-hit answer. " - "{} -->", anyEncFloor ? kGrepEncFloorClause : "", anyCarrierAttr ? kGrepCarrierClause : "", - pageCarriers.skippedAttrs.empty() ? "" : kGrepCarrierSkipClause, rw::kPageRaiseCapClause ); + "{}{} -->", anyEncFloor ? kGrepEncFloorClause : "", anyCarrierAttr ? kGrepCarrierClause : "", + pageCarriers.skippedAttrs.empty() ? "" : kGrepCarrierSkipClause, + grepHonestLexClause( !grepMetaAttr( cfg, pat, esc ).empty(), !found.occ.empty() ), rw::kPageRaiseCapClause ); } emitGrepHandleLegend( cfg.grepHandles ); // G3: terms=/scope=/suppressed= — only when AND/NOT was actually given, so a plain --grep answer // stays byte-identical to before G3 landed (the "purely additive" rule every ripwire flag follows). - const std::string termsAttr = grepTermsAttrs( pat, grepTerms, grepScopeVal, termsSuppressed, esc ); + const std::string termsAttr = grepTermsAttrs( pat, grepTerms, grepScopeVal, termsSuppressed, esc ) + grepMetaAttr( cfg, pat, esc ); // G4 (2026-08-15 harvest, report-ugrep §F6): corpus_excluded=/corpus_oversize= — so hits="0" can // distinguish "not in this repo" from "in a file the crawl never scanned" (an --exclude= match, or a // file past --max-file-size). Absent when zero, matching skippedOversize's own "absent means nothing @@ -1224,26 +1278,10 @@ int emitGrepReport( const MainDispatch& d ) } scanShortAttr += pageCarriers.skippedAttrs; // L4: the reads the memory guard stopped (reads_skipped=, reads_next=), or "" const char* schemaAttr = cfg.legend == "compact" ? " schema=\"ripwire.grep/v1\"" : ""; - // P3 (L7, nextverb.h): the one follow-up. A CUT answer → the next page, under the compact legend (the page - // is what the agent wants, not the prose it has already read); a hit → the enclosing-definition chain at the - // top hit (--at=FILE:LINE, the grep→at→expand chain lens 8 named); zero hits → the conceptual fallback. - std::string grepNext; - if( grepPage.end < hitCount || hitsCapped != 0 ) - { - grepNext = rw::nextFlag( cfg.grepRegex ? "--regex=" : "--grep=", pat ) + " --offset=" + std::to_string( grepPage.end ) + " --legend=compact"; - } - else if( const std::string carrierNext = rw::grepCarrierNext( ing, hits, encRows, encCarriers ); !carrierNext.empty() ) - { - grepNext = carrierNext; // L4: the top hit sits in a carrier the uses verb lists reads for — those reads are the next hop - } - else if( !hits.empty() ) - { - grepNext = rw::nextFlag( "--at=", std::string( pathFor( hits[ 0 ].fileId ) ) + ":" + std::to_string( hits[ 0 ].line ) ); - } - else - { - grepNext = rw::nextFlag( "--for=", pat ); - } + // P3 (L7) + honest-lex: the one follow-up (grepRootNext above states the order). + const std::string grepNext = grepRootNext( cfg, pat, grepPage.end < hitCount || hitsCapped != 0, grepPage.end, + [ & ] { return rw::grepCarrierNext( ing, hits, encRows, encCarriers ); }, + [ & ] { return hits.empty() ? std::string() : std::string( pathFor( hits[ 0 ].fileId ) ) + ":" + std::to_string( hits[ 0 ].line ); } ); rw::emitTo( stdout, "", ex( pat ).c_str(), schemaAttr, rootAttr.c_str(), termsAttr.c_str(), filesMatched, hitCount, pageDisclosure( grab, sizeof( grab ), grepPage.end - grepPage.begin, hitCount, grepPage.end, @@ -1279,10 +1317,7 @@ int emitGrepReport( const MainDispatch& d ) { rw::emitTo( stdout, " in=\"{}\"", ex( h.enclosing ).c_str() ); } - if( h.lineBytes != 0 ) // the matched-line cut, disclosed: absent = the whole line is here - { - rw::emitTo( stdout, " line_bytes=\"{}\"", h.lineBytes ); - } + rw::emitTo( stdout, "{}", grepRowTailAttrs( h.lineBytes, h.occ ) ); // line_bytes= (cut line), occ= (honest-lex) if( !c.more.empty() ) { rw::emitTo( stdout, " n=\"{}\"", c.more.size() + 1 ); // 1 (this row) + the folded sites — sums to shown= diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index 80f6a8d17..47258a20f 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2572,8 +2572,17 @@ std::optional runPath( const MainDispatch& d ) const rw::PathSearchGaps pthGaps = path.empty() ? rw::pathSearchGaps( ing, g, srcDefs, *pthValueIdx ) : rw::PathSearchGaps{}; // ix2-slice: the sink-side frontier beside the forward gap rows (src/pathgaps.h pathNearSink), reachable="0" only const rw::PathNearSink pthNear = path.empty() ? rw::pathNearSink( ing, g, dstDefs, pthGaps.cone, *pthValueIdx ) : rw::PathNearSink{}; - // ix2-slice (IX-2 P1): the hedged bridge — the fewest-crossings chain over declined / value / closure / namesake hops - const rw::PathBridge pthBridge = path.empty() ? rw::pathBridge( ing, g, srcDefs, dstDefs, *pthValueIdx ) : rw::PathBridge{}; + // HOP1 (src/backhops.h): the ONE backward hop provider the bridge's slot crossings and the root next= below read + std::optional pthBackHops; + if( path.empty() ) + { + pthBackHops.emplace( ing, g, *pthValueIdx ); + } + // ix2-slice (IX-2 P1): the hedged bridge — the fewest-crossings chain over declined / value / closure / namesake / slot hops + const rw::PathBridge pthBridge = path.empty() ? rw::pathBridge( ing, g, srcDefs, dstDefs, *pthValueIdx, *pthBackHops ) : rw::PathBridge{}; + // HOP1 (#6): to= has a backward hop the provider follows → the root next= is the callers verb on to=, not the gap bodies + const std::string pthSinkCallers = path.empty() && pthGaps.any() && !dstGlob ? rw::pathSinkCallersSelector( ing, *pthBackHops, dstDefs, dstN, pthSingleRoot, pthRootPrefix ) + : std::string(); const bool pthHasVia = rw::pathHasNameOnlyHop( g, path ); // FE-B: exactly when a hop of THIS path is via="name" const bool pthHasLink = rw::pathHasNameOnlyHop( g, path, rw::kEdgeLink ); const bool pthHasMiss = rw::pathHasNameOnlyHop( g, path, 0 ); @@ -2587,7 +2596,7 @@ std::optional runPath( const MainDispatch& d ) + rw::splitArmLegend( pthHasSplit ) + rw::pathUnboundLegend( !pthBound.from || !pthBound.to ) + rw::memberOwnerLegend( rw::anyPathHopOwner( pthOwners ) ) ).c_str(), ( std::string( rw::toValueRefsLegend( pthToValueRefs.count > 0 ) ) + rw::valueRefsDepthLegend( pthToValueRefs.depthCut.files > 0 ) ).c_str(), - ( std::string( rw::pathGapsLegend( pthGaps.any() ) ) + std::string( rw::pathBridgeLegend( pthBridge.found() ) ) + ( rw::pathGapsLegend( pthGaps.any(), !pthSinkCallers.empty() ) + std::string( rw::pathBridgeLegend( pthBridge.found() ) ) + std::string( rw::nearSinkLegend( !pthNear.rows.empty() ) ) + std::string( rw::pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), rw::graphCountFloorBrief( rw::graphGaugeClauses( g ) ).c_str(), rw::rootRelPathsLegend( pthSingleRoot ) ); rw::emitTo( stdout, " runPath( const MainDispatch& d ) const rw::PathGapsXml pthUnreached = path.empty() ? rw::pathUnreachedXml( ing, pthGaps, pthSingleRoot, pthRootPrefix, { "--connect=" + std::string( srcN ) + "," + std::string( dstN ), "--uses/--impact", - ( srcDefs.size() > 1 || dstDefs.size() > 1 ) ? "; several defs share these names — qualify as file:name to pick one" : "" } ) + ( srcDefs.size() > 1 || dstDefs.size() > 1 ) ? "; several defs share these names — qualify as file:name to pick one" : "" }, + pthSinkCallers ) : rw::PathGapsXml{}; rw::emitTo( stdout, "{}>{}{}{}", pthUnreached.rootAttrs, pthUnreached.rows, rw::pathBridgeXml( ing, g, pthBridge, pthSingleRoot, pthRootPrefix ), rw::pathNearSinkXml( ing, pthNear, pthSingleRoot, pthRootPrefix ) ); @@ -2744,6 +2754,7 @@ struct ImpactView std::span seeds; // the matched defs; a depth-1 row's link mark reads the edge caller → seed const rw::ImpactDeclinedRows& dc; // lane ix2-slice (IX-4, declinedsites.h): the rows declined_calls= counts, cut std::span patternSites {}; // IX-3: template lookups that may name SYM — never in reaches= (XML rows) + const rw::ImpactRing* ring = nullptr; // HOP1 (backhops.h): the hedged slot ring — reaches_hedged= and its d=2 rows; never in reaches= }; @@ -2801,6 +2812,7 @@ int emitImpactColumnar( const ImpactView& v ) std::vector rows( v.show.begin() + v.page.begin, v.show.begin() + v.page.end ); const std::string attr = "of=\"" + std::string( escapeXml( v.sym, esc ) ) + "\" defs=\"" + std::to_string( v.defs ) + "\" reaches=\"" + std::to_string( v.reaches ) + "\"" + + rw::impactRingAttrXml( *v.ring ) // HOP1: the XML root's reaches_hedged= (its rows: XML/JSON only) + rw::unprovenDefsAttrXml( v.unprovenDefs ) // H1: where the XML root carries it + rw::defsListedAttrXml( v.attrib.groups, v.defListing ) // attrib-multidef: a cut table says so + rw::byDepthAttrXml( v.byDepth ) // 0.6.5: the XML root's, same place @@ -2867,7 +2879,7 @@ int emitImpactJson( const ImpactView& v ) char ipab[ kPageDisclosureCap ]; const std::size_t shownRows = v.page.end - v.page.begin; rw::emitTo( stdout, "{{\"of\":\"{}\",\"defs\":{},\"reaches\":{}{}{}{}", jsonStr( v.sym ).c_str(), v.defs, v.reaches, - rw::unprovenDefsKeyJson( v.unprovenDefs ).c_str(), // H1: absent at zero, like its XML twin + ( rw::impactRingKeyJson( *v.ring ) + rw::unprovenDefsKeyJson( v.unprovenDefs ) ).c_str(), // HOP1: the XML root's reaches_hedged=; H1: absent at zero, like its XML twin rw::defTableJson( v.ing, v.attrib.groups, v.defListing, v.rootPrefix, /*mcp=*/false ), // attrib-multidef: the table rw::byDepthKeyJson( v.byDepth ) ); // 0.6.5: the XML root's by_depth=, as an array rw::emitTo( stdout, ",\"importers\":{},\"shown_importers\":{},\"importers_capped\":{}", @@ -2898,7 +2910,8 @@ int emitImpactJson( const ImpactView& v ) const std::string imLinkNote = rw::impactViaLinkNote( via, v.page.begin, v.page.end ); rw::emitTo( stdout, "{}{}{}}}", rw::declinedSitesJson( v.dc.views, /*callersForm=*/true, /*mcp=*/false ), // ix2-slice: the XML rows rw::valueRefsJson( v.ing, v.valueRefs, true, rw::VrRender{ v.singleRoot, v.rootPrefix }, "vrs", - "--uses=" + std::string( v.sym ) ), imLinkNote.c_str() ); + "--uses=" + std::string( v.sym ) ) + + rw::impactRingJson( v.ing, *v.ring, rw::VrRender{ v.singleRoot, v.rootPrefix } ), imLinkNote.c_str() ); // HOP1: the XML return 0; } @@ -2914,7 +2927,7 @@ int emitImpactXml( const ImpactView& v ) char ipab[ kPageDisclosureCap ]; const std::size_t shownRows = v.page.end - v.page.begin; rw::emitTo( stdout, "", - ex( v.sym ).c_str(), v.defs, v.reaches, rw::unprovenDefsAttrXml( v.unprovenDefs ) + rw::defsListedAttrXml( v.attrib.groups, v.defListing ), // H1: beside the reaches= it qualifies; attrib-multidef + ex( v.sym ).c_str(), v.defs, v.reaches, rw::impactRingAttrXml( *v.ring ) + rw::unprovenDefsAttrXml( v.unprovenDefs ) + rw::defsListedAttrXml( v.attrib.groups, v.defListing ), // HOP1: the hedged ring beside reaches=; H1; attrib-multidef rw::byDepthAttrXml( v.byDepth ), // 0.6.5: partitions reaches= by hop depth v.imports.xmlAttrs.c_str(), v.radiusTested, v.radiusUntested, ( rw::declinedCallsAttrXml( v.declinedCalls ) + rw::declinedSiteCutAttrXml( v.dc.cut ) + rw::declinedIfaceAttrXml( v.declinedIface ) ).c_str(), // tier-3 declines into the radius (+ the cut) @@ -2946,6 +2959,7 @@ int emitImpactXml( const ImpactView& v ) rw::emitTo( stdout, "{}{}", rw::declinedSitesXml( v.dc.views, /*callersForm=*/true ), // ix2-slice (IX-4): each declined call the count names rw::valueRefsXml( v.ing, v.valueRefs, true, rw::VrRender{ v.singleRoot, v.rootPrefix }, "--uses=" + std::string( v.sym ) ) + + rw::impactRingXml( v.ing, *v.ring, rw::VrRender{ v.singleRoot, v.rootPrefix } ) // HOP1: the d=2 slot ring, after the d=1 rows + rw::patternDispatchXml( v.ing, v.patternSites, rw::VrRender{ v.singleRoot, v.rootPrefix } ) ); return 0; } @@ -2955,7 +2969,7 @@ inline void emitImpactLegend( const rw::Config& cfg, const rw::IngestResult& ing const rw::ImportTier& imports, const std::vector& seeds, const std::vector& imDepth, const std::vector& imProven, std::size_t imUnprovenDefs, std::size_t imDeclinedCalls, std::size_t imDeclinedIface, const rw::ImpactFileRollup& imFiles, const rw::ReachAttribution& imAttr, const rw::DefTableListing& imListing, - const rw::ValueRefRows& imValueRefs, const rw::RankDisclosure& prD, const rw::ImpactDeclinedRows& imDc ) + const rw::ValueRefRows& imValueRefs, const rw::RankDisclosure& prD, const rw::ImpactDeclinedRows& imDc, const rw::ImpactRing& imRing ) { if( cfg.json ) { @@ -2988,7 +3002,8 @@ inline void emitImpactLegend( const rw::Config& cfg, const rw::IngestResult& ing ( std::string( rw::valueRefsReachLegend( !imValueRefs.rows.empty() ) ) + rw::valueRefsDepthLegendFor( imValueRefs ) + rw::patternDispatchLegend( !imValueRefs.patternSites.empty() || !imValueRefs.patternWideSites.empty(), true ) // IX-3: exactly when pattern_dispatch=/pattern_too_wide= rides - + std::string( rw::slotJoinLegend( imValueRefs.slotCallers, true ) ) ).c_str(), // IX-1: exactly when a row carries via=slot|slot-name + + std::string( rw::slotJoinLegend( imValueRefs.slotCallers, true ) ) // IX-1: exactly when a row carries via=slot|slot-name + + std::string( rw::impactRingLegend( imRing.hedged > 0, !imRing.rows.empty() && !cfg.columnar ) ) ).c_str(), // HOP1: exactly when reaches_hedged= / ride rw::graphCountDisclosure( rw::graphGaugeClauses( g ) ).c_str(), rw::renderDisclosure( prD, rw::DiscloseAs::LegendClause ).c_str() ); } @@ -3076,6 +3091,9 @@ std::optional runImpact( const MainDispatch& d ) // Reference-as-value round: SYM's own binding sites, beside the radius they are not in. const rw::ValueRefIndex imVri( ing ); const rw::ValueRefRows imValueRefs = rw::withPatternRows( rw::valueRefCallerRows( ing, imVri, seeds ), rw::PatternIndex( ing, imVri ), seeds, true ); + // HOP1 (src/backhops.h): the hedged slot ring — one or two slot hops back from SYM, beside reaches=, never in it + const rw::BackwardHops imBackHops( ing, g, imVri ); + const rw::ImpactRing imRing = rw::impactSlotRing( ing, imBackHops, seeds, reach ); const std::vector& imPatterns = imValueRefs.patternSites; // IX-3: beside the radius, never in it // ix2-slice (IX-4, declinedsites.h): the rows of exactly the declines declined_calls= counts, cut as --callers cuts; // no cand= under any legend (as the MCP twin): --help promises the rows byte-identical across postures (legendcostcheck C) @@ -3084,7 +3102,7 @@ std::optional runImpact( const MainDispatch& d ) rw::VrRender{ imSingleRoot, imRootPrefix }, /*candidates=*/false ); emitImpactLegend( cfg, ing, g, reach, show, imPage, imWalk, imports, seeds, imDepth, imProven, imUnprovenDefs, - imDeclinedCalls, imDeclinedIface, imFiles, imAttr, imListing, imValueRefs, prD, imDc ); + imDeclinedCalls, imDeclinedIface, imFiles, imAttr, imListing, imValueRefs, prD, imDc, imRing ); // P2.1 + §P8 G1: the rank-ordered listing's 40 is a DEFAULT now, not a ceiling — see the §P10.3 note // above runImpact; pageDisclosure emits the ` shown= capped=` bytes this verb used to hand-roll // (src/pageview.h, THE TRUNCATION VOCABULARY, rules 1-3). LB-G: the same NAMED constant @@ -3094,7 +3112,7 @@ std::optional runImpact( const MainDispatch& d ) imports, importPage, importLazyPage, prD, imSingleRoot, imRootPrefix, imRootAttr, imSingleRoot ? cfg.roots[0] : std::string_view(), cfg.pageLimit, cfg.pageOffset, &imTestReach, imRadiusTested, imRadiusUntested, imDeclinedCalls, imDeclinedIface, g, imDepth, imByDepth, imValueRefs, - imProven, imFiles, imAttr, imListing, imWalk.typeOnly, seeds, imDc, imPatterns }; + imProven, imFiles, imAttr, imListing, imWalk.typeOnly, seeds, imDc, imPatterns, &imRing }; if( cfg.columnar ) { return emitImpactColumnar( view ); } if( cfg.json ) { return emitImpactJson( view ); } diff --git a/src/verbs_quality.h b/src/verbs_quality.h index 0e85e5efe..0976e7ec0 100644 --- a/src/verbs_quality.h +++ b/src/verbs_quality.h @@ -2272,19 +2272,7 @@ std::optional runQualityDelta( const MainDispatch& d ) std::string staleCollapseAttrs; if( collapseStale ) { - std::vector> byKind; // first-appearance order of the ledger's own row order - for( const quality::StaleAck& a : staleAcks ) - { - // the KIND, without the ack's :new-symbol/:preexisting facet — the rows (stale_next=) carry the facet - const std::string kind = a.kind.substr( 0, a.kind.find( ':' ) ); - const auto it = std::find_if( byKind.begin(), byKind.end(), [ & ]( const auto& kv ) { return kv.first == kind; } ); - if( it == byKind.end() ) { byKind.emplace_back( kind, 1 ); } else { ++it->second; } - } - std::string kinds; - for( const auto& [ kind, n ] : byKind ) - { - kinds += ( kinds.empty() ? "" : "," ) + kind + ":" + std::to_string( n ); - } + const std::string kinds = quality::staleByKindValue( staleAcks ); // shared with the MCP twin (lane lean2b) std::string replay = cfg.qualityDeltaRange.empty() ? std::string( "--quality-delta" ) : rw::nextFlag( "--quality-delta=", cfg.qualityDeltaRange ); if( scope.active() ) { diff --git a/test/compactlegendcheck.sh b/test/compactlegendcheck.sh index 66649f995..059a60e0e 100755 --- a/test/compactlegendcheck.sh +++ b/test/compactlegendcheck.sh @@ -367,6 +367,11 @@ echo "=== (A-PIN) --legend=full is BYTE-IDENTICAL to the pre-L1 default (pinned # at= and est_tokens= masked) equals it exactly. 5527 -> 5688 B. The six other pins are unchanged. # RE-ANCHORED BY HAND 2026-10-19 (markdown section links), map.xml only: the two fixture callees gain via="link", # the full legend gains the via="link" comment, and est_tokens= prices them, 901 -> 929. +# RE-ANCHORED BY HAND 2026-10-23 (lane honest-lex, CI fix round 1), grep.xml only: the lane's intended output change, nothing +# else. Checked: the pin equals the previous head's --legend=full output with exactly four edits -- the full legend gains the +# sentence "A hit is one LINE: occ= on a row (present only above one) counts the pattern's occurrences on that line, and +# hits=, shown= and the window count lines, not occurrences."; the root's hits=/shown= read 7 (was 8: the two occurrences on +# geometry.cpp:16 are one line now); that row's n="2" + its at-tagged sibling become occ="2" (one hit per line). PIN_DIR="$ROOT/test/compactlegendfix/pre_l1_full" # the one normalisation, in python on BOTH sides so no sed dialect decides it (BSD sed appends a final newline, GNU # sed does not): at="…" masked, trailing newlines dropped. @@ -695,7 +700,7 @@ ripwire.affected/v1 840 826 ripwire.verify/v1 440 421 ripwire.help-task/v1 440 429 ripwire.query/v1 790 775 -ripwire.grep/v1 1380 1368 +ripwire.grep/v1 1432 1420 ripwire.match/v1 370 355 ripwire.lego/v1 290 275 ripwire.exemplar/v1 440 422 @@ -1705,6 +1710,8 @@ grep -q '^__ERROR__' "$TMP/m.bad" && ok "(M) MCP edit_check legend:\"terse\" is # RE-PINNED 2026-09-19 (the L1 fix round, rv-r1-L1 HIGH-1: every emitted attribute defined — the (U) pins' note): uses 290 -> 510 # (measured 500), path_between 280 -> 430 (419), exemplar 260 -> 350 (331); impact and lego did not cross their pins. # Fix round 2: path_between 430 -> 540 (measured 535 — the no-path hint= reading). +# RE-PINNED 2026-10-24 (honest-lex fix 2): grep/v1 1380 -> 1432 (measured 1420; 1368 on the base binary) -- the +52 B clause +# "(beside pattern_meta=, of the literal reading only)" that says complete=/hits= speak of the literal reading (review N1). # RE-PINNED 2026-10-18 (lane ix2-slice): path_between 540 -> 860 — the same / readings the (U) table's # ripwire.path/v1 row states (one emitter, src/pathgaps.h pathNearSinkXml); the (U-twin-path) arm keeps the old 550 B CLI pin. # RE-PINNED 2026-09-23 (cut-fix C): impact 780 -> 810 (measured 797; 777 on the base binary) — the same +20 B diff --git a/test/compactlegendfix/pre_l1_full/grep.xml b/test/compactlegendfix/pre_l1_full/grep.xml index 1126db064..89b9a15dc 100644 --- a/test/compactlegendfix/pre_l1_full/grep.xml +++ b/test/compactlegendfix/pre_l1_full/grep.xml @@ -1 +1 @@ - distance]]> distance)]]> \ No newline at end of file + distance]]> distance)]]> \ No newline at end of file diff --git a/test/cppqualcheck.sh b/test/cppqualcheck.sh index e0eff76a5..67919e58b 100755 --- a/test/cppqualcheck.sh +++ b/test/cppqualcheck.sh @@ -322,10 +322,13 @@ rrun US --uses=selectBaseline \ # through the canonical helper — one new call site, same helper. # 31 -> 32 (lane/cr-qd-kinds-068): quality.h defectSitesOf reads each C++ / Python / Bash test-script file for the # defect-shape scanners through the canonical helper — one new call site, same helper. +# 32 -> 35 (lane/fc2-lego-extparent fix 1 + fix 2): cargoCrateNames and pythonRootPackageImports read each Cargo.toml / the root +# pyproject.toml through the canonical helper (two call sites, fix 1), and rustFileAlias (called by rustOutsideUses) reads a .rs file to find its +# `extern crate … as` / `use … as` crate aliases (one more, fix 2) — three new call sites, same helper. rrun RW --uses=readWholeFile \ -&& { [ "$( cnt "$RW" )" = 32 ] \ - && ok "repo: --uses=readWholeFile count=32 (docparse::detail:: — a seam the audit's rw::-anchored grep missed)" \ - || no "repo: --uses=readWholeFile expected 32, got '$( cnt "$RW" )'"; } +&& { [ "$( cnt "$RW" )" = 35 ] \ + && ok "repo: --uses=readWholeFile count=35 (docparse::detail:: — a seam the audit's rw::-anchored grep missed)" \ + || no "repo: --uses=readWholeFile expected 35, got '$( cnt "$RW" )'"; } rrun WC --callers=writeTally \ && { [ "$( cnt "$WC" )" = 1 ] \ && ok "repo: --callers=writeTally count=1 (was 0 — both template call sites are in writeDocDriftPage)" \ diff --git a/test/editcheckcheck.sh b/test/editcheckcheck.sh index 49532a696..55dd468a3 100755 --- a/test/editcheckcheck.sh +++ b/test/editcheckcheck.sh @@ -102,9 +102,9 @@ echo 'int compute(int a, int b);' > "$FOLD/src/calc.h" } > "$FOLD/src/calc.c" ( cd "$FOLD" && git init -q && git config user.email t@t && git config user.name t && git add -A && git commit -qm init >/dev/null 2>&1 ) fec(){ _s="$1"; shift; ( cd "$FOLD" && "$BIN" . --edit-check="$_s" --no-cache "$@" 2>/dev/null ); } -fold_verdict(){ # LABEL OUT CALLERS — the verdict alone: callers= counted, no rows, rows_next= naming the --limit call +fold_verdict(){ # LABEL OUT CALLERS SYM [ROWS_NEXT] — the verdict alone: callers= counted, no rows, rows_next= naming the --limit call if printf '%s' "$2" | grep -q "status=\"unchanged\" defs=\"1\" callers=\"$3\"" && [ -z "$( rows "$2" )" ] \ - && printf '%s' "$2" | grep -q "rows_next=\"--edit-check=$4 --limit=$3\""; then ok "$1"; else no "$1"; printf '%s\n' "$2"; fi + && printf '%s' "$2" | grep -q "rows_next=\"${5:---edit-check=$4 --limit=$3}\""; then ok "$1"; else no "$1"; printf '%s\n' "$2"; fi } fold_rows(){ # LABEL OUT ROW — the full document: the named row listed, no rows_next= if rows "$2" | grep -qF "$3" && ! printf '%s' "$2" | grep -q 'rows_next='; then ok "$1"; else no "$1"; printf '%s\n' "$2"; fi @@ -124,6 +124,27 @@ F_TSL="$( fec blend --limit=16 )" [ "$( rows "$F_TSL" | wc -l | tr -d ' ' )" = 16 ] && rows "$F_TSL" | grep -qF '' \ && ok "(1v-b) the rows_next= call lists all 16 callers, main.ts:go among them" \ || { no "(1v-b) the rows_next= call does not list the 16 callers"; printf '%s\n' "$F_TSL"; } +# (1v-m) lane lean2b (card #9 MCP-FOLD): the MCP edit_check verb takes the SAME verdict fold. Default: the verdict alone with +# rows_next= naming --limit=16 (= the verb's limit argument); limit=16: the 16 rows, byte-identical to the CLI --limit=16 +# rows; 3 callers: the fold does not pay and the rows ride. RED on ffbec708: the MCP default lists all 16 rows. +mec(){ printf '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"edit_check","arguments":{"path":"%s","symbol":"%s"%s}}}\n' "$FOLD" "$1" "$2" \ + | "$BIN" --mcp --no-cache 2>/dev/null \ + | python3 -c 'import sys,json +for line in sys.stdin: + try: d=json.loads(line) + except Exception: continue + c=(d.get("result") or {}).get("content") + if c: print(c[0].get("text","")); break'; } +if command -v python3 >/dev/null 2>&1; then + M_TS="$( mec blend '' )"; M_TSL="$( mec blend ',"limit":16' )" + fold_verdict "(1v-m) MCP edit_check, clean tree, 16 callers: the verdict alone, rows_next= names the MCP call (fix1 B3)" "$M_TS" 16 blend "edit_check symbol=blend limit=16" + [ "$( rows "$M_TSL" | wc -l | tr -d ' ' )" = 16 ] && [ "$( rows "$M_TSL" )" = "$( rows "$F_TSL" )" ] \ + && ok "(1v-m) MCP limit=16 lists the 16 caller rows, identical to the CLI --limit=16 rows" \ + || { no "(1v-m) MCP limit=16 does not reproduce the CLI rows"; printf '%s\n' "$M_TSL"; } + fold_rows "(1v-m) MCP edit_check, 3 callers: the fold does not pay, m1 listed" "$( mec mix '' )" ']*/>", open( doc, encoding="utf-8", errors="replace" ).read() ): + n = re.search( r' n="([^"]*)"', s ); p = re.search( r' p="([^"]*)"', s ) + if n and p and n.group( 1 ) == name and p.group( 1 ).rsplit( ":", 1 )[ 0 ] == path and ' via="name"' in s: sys.exit( 0 ) +sys.exit( 1 ) +PY + then ok "($r) --$v=$s lists [$want] as a hedged via=name row" + else no "($r) --$v=$s has no hedged via=name row [$want]"; fi +} +# unpinned ROOT VERB SEL "n file" … — each named row is absent or carries via="name": never a confident (pinned) row +unpinned(){ + local r="$1" v="$2" s="$3"; shift 3 + local f; f="$( answer "$r" "$v" "$s" )"; ran_ok "$r" "$v" "$s" "$f" || return + local want + for want in "$@"; do + if python3 - "$f" "${want% *}" "${want#* }" <<'PY' +import re, sys +doc, name, path = sys.argv[ 1: ] +for s in re.findall( r"]*/>", open( doc, encoding="utf-8", errors="replace" ).read() ): + n = re.search( r' n="([^"]*)"', s ); p = re.search( r' p="([^"]*)"', s ) + if n and p and n.group( 1 ) == name and p.group( 1 ).rsplit( ":", 1 )[ 0 ] == path and ' via="name"' not in s: sys.exit( 1 ) +sys.exit( 0 ) +PY + then ok "($r) --$v=$s has no confident row [$want]" + else no "($r) --$v=$s lists [$want] as a confident (pinned) row"; fi + done +} # exactly ROOT VERB SEL "t n file;t n file" — the exact row set (";"-separated, sorted); "" = no rows at all exactly(){ local r="$1" v="$2" s="$3" want="$4" @@ -196,7 +251,7 @@ reaches_fn(){ } # ── census: one per root, written FIRST so no arm reads a missing file ───────────────────────────────────────── -ROOTS="go gonomod gomodcomment goreplace js ts tsimport py c cpp rs" +ROOTS="go gonomod gomodcomment goreplace js ts tsimport py c cpp rs jsrebind" for r in $ROOTS; do if ! rw "$r" --pin-census="$TMP/$r.tsv" >"$TMP/$r.map.xml"; then no "($r) the census run exited non-zero"; fi if [ ! -s "$TMP/$r.tsv" ]; then no "($r) the census run wrote no census — every census arm for this root would be vacuous"; fi @@ -356,6 +411,57 @@ exactly rs callees src/assoc.rs:build "method History::new src/assoc.rs;method H exactly rs callees src/assoc.rs:count "method History::width src/assoc.rs" exactly rs callees src/ufcs.rs:draw_all "method History::render_all src/ufcs.rs" +echo "=== (S) JS/TS rebind: a written binding names nothing (FC3) ===" +# the exact row set: every case file whose binding is written is GONE; each near miss keeps its row (nestedP keeps h, +# its g wrote its own `let Eng`; classexprP's m constructs its own class expression) +exactly jsrebind callers Eng "fn h cases/nestedP.js;method m cases/classexprP.js;modscope <file-scope> cases/constwriteP.js;modscope <file-scope> cases/esimport.js;modscope <file-scope> cases/keepdestrP.js;modscope <file-scope> cases/keepmemberP.js;modscope <file-scope> cases/memberroot.js;modscope <file-scope> cases/memberwriteP.js;modscope <file-scope> cases/norewriteP.js;modscope <file-scope> cases/otherfileP_y.js;modscope <file-scope> cases/paramP.js;modscope <file-scope> cases/redeclnoinitP.js" +lacks jsrebind callers leaf "<file-scope> cases/fncall.js" +has jsrebind callers leaf "method run src/eng.js" +lacks jsrebind callers Foo "<file-scope> cases/destr.js" "<file-scope> cases/plainreq.js" # undecided is not Foo either +# a member call spelled like a written LOCAL is not that local: `let run = ''; run += ' '; reply.run( … )` keeps its row +has jsrebind callers run "fn onSendEnd cases/memberwriteP.js" +# FIX1 B3: a MEMBER call is read through its ROOT name — `Eng = Foo; Eng.run()` calls Foo's, a rebound module alias +# `m = require( b ); m.Eng()` whatever b holds (memberroot.js: a hedged row below), and a +# local class `Pack = Crate; Pack.make()` — when the write REPLACES a value the declaration bound. Near misses keep: a +# name nobody writes, and `let rig; … rig = new Rig(); rig.fire()` (the declaration bound nothing: the test-file idiom; +# measured: refusing it dropped 400+ true rows from hono alone) +# FIX2 C1: such a member call is not DROPPED either — a written root is no receiver, so the call keeps the by-name ladder's +# hedge (via="name", every candidate): the true target stays visible, never a pinned row (memberclass, memberlocal, memberroot) +hedged jsrebind callers run "<file-scope> cases/memberclass.js" +hedged jsrebind callers Eng "<file-scope> cases/memberroot.js" +exactly jsrebind callers make "modscope <file-scope> cases/memberlocal.js;modscope <file-scope> cases/membernowriteP.js" +hedged jsrebind callers make "<file-scope> cases/memberlocal.js" +hedged jsrebind callers drip "reassign cases/pinnedhedgeP.js" # `let tank = new Tank(); tank = b; tank.drip()` +# FIX2 C2: a root ASSIGNED anywhere in its scope never decides by its own SPELLING — `Eng = Foo; Eng.run()` is not the class +# `Eng`, with or without a declaration, nor `Cog = require( … ).Gear; Cog.spin()` the class `Cog`: hedged at best, never pinned +unpinned jsrebind callers spin "<file-scope> cases/spellnodeclP.js" "<file-scope> cases/spellimplicitP.js" "<file-scope> cases/spellvarP.js" +hedged jsrebind callers spin "<file-scope> cases/spellnodeclP.js" # the by-name hedge keeps both candidates, not a guess at either +has jsrebind callers fire "modscope <file-scope> cases/memberunpinnedP.js" +# FIX1 FC5: `reply.leaf( … )` is a member call: the ES-imported function `leaf` of the same file does not PIN it +hedged jsrebind callers leaf "send cases/esmemberP.js" # by name at best: not the imported `leaf` as a pinned row +# FIX1 B2: the name-keeping exception is for `var x;` / `let x;` / an undeclared name only. keepotherinit.js / keepclass.js +# (above: absent from the Eng rows) write another module's `Eng` over a declaration that already pinned one. NAMED FLOOR: +# keepsamemod.js re-assigns the SAME module's `Eng` over its destructured require — true, but the spelling is not the proof, +# so it is withheld too (honest over-withholding; the positive reading `x = mod.member` is a later lane) +lacks jsrebind callers Eng "<file-scope> cases/keepsamemod.js" +# FIX1 B1: patterns nest as deep as a file likes — an assignment target / declarator 20000 object patterns deep (a recursive +# walk overflows the stack: rc 138 / SIGBUS / ASan stack-overflow) answers, exits 0, and binds nothing it cannot read +deep="$TMP/jsdeep"; mkdir -p "$deep/src" "$deep/test" +printf 'class Eng { run() { return 1; } }\nmodule.exports = { Eng };\n' >"$deep/src/a.js" +python3 - "$deep/test" <<'PY' +import sys +d, n = sys.argv[ 1 ], 20000 +pat = "{a:" * n + "{Eng}" + "}" * n +open( d + "/assign.test.js", "w" ).write( 'let { Eng } = require("../src/a");\n(' + pat + ' = o);\ntest("x", () => { new Eng(); });\n' ) +open( d + "/decl.test.js", "w" ).write( 'var ' + pat + ' = o;\nvar Eng = require("../src/a").Eng;\ntest("x", () => { new Eng(); });\n' ) +PY +deepf="$TMP/jsdeep.callers.xml" +( cd "$deep" && "$BIN" . --no-cache --callers=Eng >"$deepf" 2>/dev/null ); deeprc=$? +if [ "$deeprc" -ne 0 ]; then no "(jsrebind) --callers=Eng over 20000-deep object patterns exited rc=$deeprc" +elif ! grep -q ']* defs="1"' "$deepf"; then no "(jsrebind) --callers=Eng over 20000-deep object patterns produced no answer about Eng" +elif grep -q 'test/assign.test.js' "$deepf"; then no "(jsrebind) the 20000-deep assignment still binds a row: $( tr '<' '\n' <"$deepf" | grep 'assign.test' | tr '\n' ';' )" +else ok "(jsrebind) 20000-deep object patterns: exit 0, the written \`Eng\` binds no row"; fi + echo "=== (F) propagation: the decoys' callers and impact ===" exactly go callers hist/history.go:append "fn Remember hist/history.go" lacks go impact hist/history.go:append "Collect algo/algo.go" diff --git a/test/falseedgefix/README.md b/test/falseedgefix/README.md index fcd59da10..572e3093c 100644 --- a/test/falseedgefix/README.md +++ b/test/falseedgefix/README.md @@ -38,3 +38,9 @@ plus near-miss calls whose edges are real and must stay. `h.render()`, `History::new()`, `History::render( &h )`, `Self::width()`. The names are paraphrases of graded false rows; the code is minimal and the gate never builds it. +- `jsrebind/` — FC3: JS/TS bindings WRITTEN after their declaration (`let { Eng, Foo } = require( … ); Eng = Foo; + new Eng()`), one case per file under `cases/` against `src/eng.js`'s `Eng`/`Foo`. A file whose name ends in `P` is a + near miss whose row must stay (no write, a write to another binding of the name, a write that throws, a write that + keeps the name); every other case file's `Eng` (or `leaf`) call has no row — except `esimport.js` (writing an ES + import throws, so the import still names the call) and `memberroot.js` (`m.Eng()` after `m = require( … )`: the member + ladder's by-name reading, a floor the gate names). diff --git a/test/falseedgefix/jsrebind/cases/augment.js b/test/falseedgefix/jsrebind/cases/augment.js new file mode 100644 index 000000000..91570557a --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/augment.js @@ -0,0 +1,2 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { Eng += ""; const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/classexprP.js b/test/falseedgefix/jsrebind/cases/classexprP.js new file mode 100644 index 000000000..76c72069d --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/classexprP.js @@ -0,0 +1 @@ +const K = class Eng { m() { try { Eng = 1; } catch (e) {} return new Eng(); } }; diff --git a/test/falseedgefix/jsrebind/cases/closurebefore.js b/test/falseedgefix/jsrebind/cases/closurebefore.js new file mode 100644 index 000000000..6ee28cccf --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/closurebefore.js @@ -0,0 +1,4 @@ +let { Eng, Foo } = require("../src/eng"); +const mk = () => new Eng(); +Eng = Foo; +test("x", () => { mk(); }); diff --git a/test/falseedgefix/jsrebind/cases/compound.js b/test/falseedgefix/jsrebind/cases/compound.js new file mode 100644 index 000000000..2884a5a64 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/compound.js @@ -0,0 +1,4 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { + [Eng] = [Foo]; const a = new Eng(); +}); diff --git a/test/falseedgefix/jsrebind/cases/compoundobj.js b/test/falseedgefix/jsrebind/cases/compoundobj.js new file mode 100644 index 000000000..5f6c94ced --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/compoundobj.js @@ -0,0 +1,4 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { + ({ Eng } = { Eng: Foo }); const a = new Eng(); +}); diff --git a/test/falseedgefix/jsrebind/cases/cond.js b/test/falseedgefix/jsrebind/cases/cond.js new file mode 100644 index 000000000..98ea08ec0 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/cond.js @@ -0,0 +1,5 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { + if (process.env.C) Eng = Foo; + const a = new Eng(); +}); diff --git a/test/falseedgefix/jsrebind/cases/constwriteP.js b/test/falseedgefix/jsrebind/cases/constwriteP.js new file mode 100644 index 000000000..2bbdc4344 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/constwriteP.js @@ -0,0 +1,2 @@ +const { Eng, Foo } = require("../src/eng"); +test("x", () => { try { Eng = Foo; } catch (e) {} const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/destr.js b/test/falseedgefix/jsrebind/cases/destr.js new file mode 100644 index 000000000..b60d9b9a6 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/destr.js @@ -0,0 +1,4 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { + Eng = Foo; const a = new Eng(); +}); diff --git a/test/falseedgefix/jsrebind/cases/esdefault.js b/test/falseedgefix/jsrebind/cases/esdefault.js new file mode 100644 index 000000000..55fd5f62c --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/esdefault.js @@ -0,0 +1,2 @@ +import Eng, * as ns from "../src/eng"; +test("x", () => { Eng = ns.Foo; ns = 1; const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/esimport.js b/test/falseedgefix/jsrebind/cases/esimport.js new file mode 100644 index 000000000..673d3c5ea --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/esimport.js @@ -0,0 +1,4 @@ +import { Eng, Foo } from "../src/eng"; +test("x", () => { + Eng = Foo; const a = new Eng(); +}); diff --git a/test/falseedgefix/jsrebind/cases/esmemberP.js b/test/falseedgefix/jsrebind/cases/esmemberP.js new file mode 100644 index 000000000..c079860e5 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/esmemberP.js @@ -0,0 +1,4 @@ +import { leaf } from "../src/util"; +export function send(reply) { + reply.leaf("x"); +} diff --git a/test/falseedgefix/jsrebind/cases/fncall.js b/test/falseedgefix/jsrebind/cases/fncall.js new file mode 100644 index 000000000..269415a9b --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/fncall.js @@ -0,0 +1,4 @@ +let { leaf, other } = require("../src/util"); +test("x", () => { + leaf = other; leaf(1); +}); diff --git a/test/falseedgefix/jsrebind/cases/fnredef.js b/test/falseedgefix/jsrebind/cases/fnredef.js new file mode 100644 index 000000000..e1ace22ea --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/fnredef.js @@ -0,0 +1,4 @@ +const { Eng: E } = require("../src/eng"); +function Eng() { return 1; } +Eng = E; +test("x", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/forof.js b/test/falseedgefix/jsrebind/cases/forof.js new file mode 100644 index 000000000..95c504e39 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/forof.js @@ -0,0 +1,2 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { for (Eng of [Foo]) { const a = new Eng(); } }); diff --git a/test/falseedgefix/jsrebind/cases/implicitglobal.js b/test/falseedgefix/jsrebind/cases/implicitglobal.js new file mode 100644 index 000000000..5531efc26 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/implicitglobal.js @@ -0,0 +1 @@ +test("x", () => { Eng = globalThis.Foo; const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/keepclass.js b/test/falseedgefix/jsrebind/cases/keepclass.js new file mode 100644 index 000000000..d2969b058 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/keepclass.js @@ -0,0 +1,3 @@ +class Eng { z() {} } +Eng = require("../src/engc").Eng; +test("x", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/keepdefaultdrop.js b/test/falseedgefix/jsrebind/cases/keepdefaultdrop.js new file mode 100644 index 000000000..564d42d9f --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/keepdefaultdrop.js @@ -0,0 +1,4 @@ +let Eng; +const mod = require("../src/eng"); +beforeEach(() => { ({ Eng = mod.Foo } = mod); }); +test("x", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/keepdestrP.js b/test/falseedgefix/jsrebind/cases/keepdestrP.js new file mode 100644 index 000000000..bef043ed4 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/keepdestrP.js @@ -0,0 +1,4 @@ +let Eng; +const mod = require("../src/eng"); +beforeEach(() => { ({ Eng } = mod); }); +test("x", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/keepmemberP.js b/test/falseedgefix/jsrebind/cases/keepmemberP.js new file mode 100644 index 000000000..6d0435862 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/keepmemberP.js @@ -0,0 +1,3 @@ +var Eng; +if (typeof require !== "undefined") { Eng = require("../src/eng").Eng; } else { Eng = window.lib.Eng; } +test("x", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/keepotherinit.js b/test/falseedgefix/jsrebind/cases/keepotherinit.js new file mode 100644 index 000000000..f1a18b9a7 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/keepotherinit.js @@ -0,0 +1,3 @@ +let { Eng } = require("../src/eng"); +Eng = require("../src/engc").Eng; +test("x", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/keepsamemod.js b/test/falseedgefix/jsrebind/cases/keepsamemod.js new file mode 100644 index 000000000..a4e95626a --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/keepsamemod.js @@ -0,0 +1,3 @@ +let { Eng } = require("../src/eng"); +Eng = require("../src/eng").Eng; +test("x", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/loopbefore.js b/test/falseedgefix/jsrebind/cases/loopbefore.js new file mode 100644 index 000000000..230168331 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/loopbefore.js @@ -0,0 +1,2 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { for (let i = 0; i < 2; i++) { const a = new Eng(); Eng = Foo; } }); diff --git a/test/falseedgefix/jsrebind/cases/memberclass.js b/test/falseedgefix/jsrebind/cases/memberclass.js new file mode 100644 index 000000000..e88110941 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/memberclass.js @@ -0,0 +1,3 @@ +let { Eng, Foo } = require("../src/eng"); +Eng = Foo; +test("x", () => { Eng.run(); }); diff --git a/test/falseedgefix/jsrebind/cases/memberlocal.js b/test/falseedgefix/jsrebind/cases/memberlocal.js new file mode 100644 index 000000000..211c7333c --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/memberlocal.js @@ -0,0 +1,5 @@ +class Box { static make() { return 1; } } +class Crate { static make() { return 2; } } +let Pack = Box; +Pack = Crate; +test("x", () => { Pack.make(); }); diff --git a/test/falseedgefix/jsrebind/cases/membernowriteP.js b/test/falseedgefix/jsrebind/cases/membernowriteP.js new file mode 100644 index 000000000..3aa0e79a7 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/membernowriteP.js @@ -0,0 +1,2 @@ +class Box2 { static make() { return 1; } } +test("x", () => { Box2.make(); }); diff --git a/test/falseedgefix/jsrebind/cases/memberroot.js b/test/falseedgefix/jsrebind/cases/memberroot.js new file mode 100644 index 000000000..527d80b4e --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/memberroot.js @@ -0,0 +1,2 @@ +let m = require("../src/eng"); +test("x", () => { m = require("../src/util"); const a = new m.Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/memberunpinnedP.js b/test/falseedgefix/jsrebind/cases/memberunpinnedP.js new file mode 100644 index 000000000..c117c6463 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/memberunpinnedP.js @@ -0,0 +1,3 @@ +class Rig { fire() { return 1; } } +let rig; +test("x", () => { rig = new Rig(); rig.fire(); }); diff --git a/test/falseedgefix/jsrebind/cases/memberwriteP.js b/test/falseedgefix/jsrebind/cases/memberwriteP.js new file mode 100644 index 000000000..75ee3ed39 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/memberwriteP.js @@ -0,0 +1,8 @@ +let { Eng, Foo } = require("../src/eng"); +const o = {}; +test("x", () => { o.Eng = Foo; const z = { Eng }; const a = new Eng(); }); +function onSendEnd (reply) { + let run = ''; + run += ' '; + reply.run(run.trim()); +} diff --git a/test/falseedgefix/jsrebind/cases/nested.js b/test/falseedgefix/jsrebind/cases/nested.js new file mode 100644 index 000000000..f9f8468f8 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/nested.js @@ -0,0 +1,5 @@ +let { Eng, Foo } = require("../src/eng"); +function g() { + Eng = Foo; const a = new Eng(); +} +function h() { return new Eng(); } diff --git a/test/falseedgefix/jsrebind/cases/nestedP.js b/test/falseedgefix/jsrebind/cases/nestedP.js new file mode 100644 index 000000000..469e814f0 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/nestedP.js @@ -0,0 +1,5 @@ +let { Eng, Foo } = require("../src/eng"); +function g() { + let Eng = Foo; Eng = Foo; const a = new Eng(); +} +function h() { return new Eng(); } diff --git a/test/falseedgefix/jsrebind/cases/norewriteP.js b/test/falseedgefix/jsrebind/cases/norewriteP.js new file mode 100644 index 000000000..7c728aa63 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/norewriteP.js @@ -0,0 +1,4 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { + const a = new Eng(); const b = Foo; +}); diff --git a/test/falseedgefix/jsrebind/cases/otherfileP.js b/test/falseedgefix/jsrebind/cases/otherfileP.js new file mode 100644 index 000000000..582a9dfbf --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/otherfileP.js @@ -0,0 +1,2 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { Eng = Foo; }); diff --git a/test/falseedgefix/jsrebind/cases/otherfileP_y.js b/test/falseedgefix/jsrebind/cases/otherfileP_y.js new file mode 100644 index 000000000..1d378f96f --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/otherfileP_y.js @@ -0,0 +1,2 @@ +let { Eng } = require("../src/eng"); +test("y", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/paramP.js b/test/falseedgefix/jsrebind/cases/paramP.js new file mode 100644 index 000000000..300224526 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/paramP.js @@ -0,0 +1,3 @@ +let { Eng, Foo } = require("../src/eng"); +function g(Eng) { Eng = Foo; return Eng; } +test("x", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/parenwrite.js b/test/falseedgefix/jsrebind/cases/parenwrite.js new file mode 100644 index 000000000..2f43ce6a0 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/parenwrite.js @@ -0,0 +1,2 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { (Eng) = Foo; const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/pinnedhedgeP.js b/test/falseedgefix/jsrebind/cases/pinnedhedgeP.js new file mode 100644 index 000000000..023d6af9d --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/pinnedhedgeP.js @@ -0,0 +1,3 @@ +class Tank { drip() { return 1; } } +class Barrel { drip() { return 2; } } +export function reassign(b) { let tank = new Tank(); tank = b; tank.drip(); } diff --git a/test/falseedgefix/jsrebind/cases/plainreq.js b/test/falseedgefix/jsrebind/cases/plainreq.js new file mode 100644 index 000000000..0716791a8 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/plainreq.js @@ -0,0 +1,4 @@ +let Eng = require("../src/engc"); +test("x", () => { + Eng = require("../src/fooc"); const a = new Eng(); +}); diff --git a/test/falseedgefix/jsrebind/cases/redeclnoinitP.js b/test/falseedgefix/jsrebind/cases/redeclnoinitP.js new file mode 100644 index 000000000..bca91bd17 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/redeclnoinitP.js @@ -0,0 +1,3 @@ +var { Eng, Foo } = require("../src/eng"); +var Eng; +test("x", () => { const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/spellimplicitP.js b/test/falseedgefix/jsrebind/cases/spellimplicitP.js new file mode 100644 index 000000000..663a9be12 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/spellimplicitP.js @@ -0,0 +1,3 @@ +const { Cog } = require("../src/gear"); +Gear = Cog; +test("x", () => { Gear.spin(); }); diff --git a/test/falseedgefix/jsrebind/cases/spellnodeclP.js b/test/falseedgefix/jsrebind/cases/spellnodeclP.js new file mode 100644 index 000000000..ecca12135 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/spellnodeclP.js @@ -0,0 +1,4 @@ +const { Cog } = require("../src/gear"); +let Gear; +Gear = Cog; +test("x", () => { Gear.spin(); }); diff --git a/test/falseedgefix/jsrebind/cases/spellvarP.js b/test/falseedgefix/jsrebind/cases/spellvarP.js new file mode 100644 index 000000000..326d9abfd --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/spellvarP.js @@ -0,0 +1,3 @@ +var Cog; +Cog = require("../src/gear").Gear; +test("x", () => { Cog.spin(); }); diff --git a/test/falseedgefix/jsrebind/cases/tsdestr.ts b/test/falseedgefix/jsrebind/cases/tsdestr.ts new file mode 100644 index 000000000..010cd917d --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/tsdestr.ts @@ -0,0 +1,4 @@ +let { Eng, Foo }: { Eng: any; Foo: any } = require("../src/eng"); +test("x", () => { + Eng = Foo; const a = new Eng(); +}); diff --git a/test/falseedgefix/jsrebind/cases/tstypeof.ts b/test/falseedgefix/jsrebind/cases/tstypeof.ts new file mode 100644 index 000000000..a6114c820 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/tstypeof.ts @@ -0,0 +1,5 @@ +const m = require("../src/eng"); +let Eng: typeof m.Eng = m.Eng; +test("x", () => { + Eng = m.Foo; const a = new Eng(); +}); diff --git a/test/falseedgefix/jsrebind/cases/typeonlyP.ts b/test/falseedgefix/jsrebind/cases/typeonlyP.ts new file mode 100644 index 000000000..d03154d9d --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/typeonlyP.ts @@ -0,0 +1,3 @@ +import type { Eng } from "../src/eng"; +let e: Eng | undefined; +test("x", () => { e = undefined; }); diff --git a/test/falseedgefix/jsrebind/cases/update.js b/test/falseedgefix/jsrebind/cases/update.js new file mode 100644 index 000000000..c83e98427 --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/update.js @@ -0,0 +1,2 @@ +let { Eng, Foo } = require("../src/eng"); +test("x", () => { Eng++; const a = new Eng(); }); diff --git a/test/falseedgefix/jsrebind/cases/varhoist.js b/test/falseedgefix/jsrebind/cases/varhoist.js new file mode 100644 index 000000000..f8c355f6d --- /dev/null +++ b/test/falseedgefix/jsrebind/cases/varhoist.js @@ -0,0 +1,3 @@ +var { Eng, Foo } = require("../src/eng"); +test("x", () => { const a = new Eng(); }); +var Eng = Foo; diff --git a/test/falseedgefix/jsrebind/src/eng.js b/test/falseedgefix/jsrebind/src/eng.js new file mode 100644 index 000000000..ecdbc006b --- /dev/null +++ b/test/falseedgefix/jsrebind/src/eng.js @@ -0,0 +1,3 @@ +import { leaf } from "./util"; +export class Eng { run(p) { return leaf(p); } } +export class Foo { other() { return 1; } } diff --git a/test/falseedgefix/jsrebind/src/engc.js b/test/falseedgefix/jsrebind/src/engc.js new file mode 100644 index 000000000..a47925cbb --- /dev/null +++ b/test/falseedgefix/jsrebind/src/engc.js @@ -0,0 +1,2 @@ +class Eng { run() { return 1; } } +module.exports = Eng; diff --git a/test/falseedgefix/jsrebind/src/fooc.js b/test/falseedgefix/jsrebind/src/fooc.js new file mode 100644 index 000000000..a1932a8db --- /dev/null +++ b/test/falseedgefix/jsrebind/src/fooc.js @@ -0,0 +1,2 @@ +class Foo { other() { return 1; } } +module.exports = Foo; diff --git a/test/falseedgefix/jsrebind/src/gear.js b/test/falseedgefix/jsrebind/src/gear.js new file mode 100644 index 000000000..0a0d68e0b --- /dev/null +++ b/test/falseedgefix/jsrebind/src/gear.js @@ -0,0 +1,2 @@ +export class Gear { static spin() { return 1; } } +export class Cog { static spin() { return 2; } } diff --git a/test/falseedgefix/jsrebind/src/util.js b/test/falseedgefix/jsrebind/src/util.js new file mode 100644 index 000000000..3256d61ad --- /dev/null +++ b/test/falseedgefix/jsrebind/src/util.js @@ -0,0 +1,2 @@ +export function leaf(p) { return p; } +export function other(p) { return p; } diff --git a/test/honestlexcheck.sh b/test/honestlexcheck.sh new file mode 100755 index 000000000..68a5f8fea --- /dev/null +++ b/test/honestlexcheck.sh @@ -0,0 +1,258 @@ +#!/usr/bin/env bash +# honestlexcheck.sh — two honesty fixes in two lexical emitters (lane honest-lex, ideas-32b cards #1 and #2). +# +# (A) WHEREIS-DEF. --whereis labels HEAD rows with the parsed index when the index defines the name +# (head_labels="index"); otherwise a LEXICAL shape test labels them (head_labels="lexical"). That test took a +# declaration word ANYWHERE earlier on the line, so a comment (`// class X …`), a help string, a doc line and every +# `struct T *data` local read kind="def" (Stage 0: 241 false def rows over 30 cells). Now the marker must sit +# directly before the name, a comment line / a name after a comment opener / a name inside a string is never a +# definition, `#define NAME` is, and head_defs="0" says when the index defines no symbol of the name at all. +# (B) GREP-META. A literal --grep whose pattern reads as a regex (`a|b`, `x\.y`, `.*` inside, `^…`, `[^…]`) answered a +# confident zero (or a narrowed list) with next= --for. Now the root carries pattern_meta= and next= is the regex +# verb on the pattern. Folded in: one row per LINE with occ=N (hits= counts lines). MCP twin keys too. +# +# Fixture: a throwaway git repo under $TMP (every name is hlx-prefixed, so no other gate's zero-hit word is touched). +# The lexical path is forced with --exclude=hlxfix/ (the index then knows no def of these names), the documented way +# to reach it on a clean tree (a dirty file keeps the index labels). +# +# RED-FIRST: recorded against base 004b4c6ba — every (A) arm that asserts a ref on a marker/comment/string line, +# the #define-object arm, head_defs, and every (B) arm but the plain-literal negatives FAIL there. +# +# Usage: test/honestlexcheck.sh [BIN] | RIPWIRE_BIN=asan/ripwire test/honestlexcheck.sh +set -u +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +. "$ROOT/test/lib/clean-env.sh" +BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" +[ "${BIN#/}" = "$BIN" ] && BIN="$ROOT/$BIN" +fail=0 +ok(){ printf ' PASS %s\n' "$*" || { fail=1; printf ' FAIL could not write the PASS line for: %s\n' "$*"; }; return 0; } +no(){ printf ' FAIL %s\n' "$*"; fail=1; } +[ -x "$BIN" ] || { echo "no ripwire binary at $BIN — build first"; exit 2; } +command -v python3 >/dev/null 2>&1 || { echo "python3 required"; exit 2; } +command -v git >/dev/null 2>&1 || { echo "git required"; exit 2; } +echo "honestlexcheck: BIN=$BIN" + +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT +R="$TMP/repo"; F="$R/hlxfix"; mkdir -p "$F" +cat > "$F/c.c" <<'EOF' +// class hlxComment is a comment, not a definition +#define HLX_OBJ 1 +#define HLX_FN(x) ((x)+1) +#ifndef HLX_OBJ +struct hlx_tag { + int n; +}; +static struct hlx_tag *hlx_local; +typedef unsigned long hlx_key; +typedef void (*hlx_cb)(hlx_key, int); +const char *hlx_s = "struct hlx_str is in a string"; +int hlx_fn2( int a ) { + return a; // struct hlx_trail is a trailing comment +} +# define HLX_SPACED 2 +EOF +cat > "$F/p.py" <<'EOF' +# def hlx_pycomment(): commented out +class HlxPy(Base): +def hlx_run( hlx_param ): + """class HlxDoc is a docstring""" +x = 1 # class HlxHashTrail trailing + yield HlxYield("a", "b") +EOF +cat > "$F/r.rs" <<'EOF' +#[inline] fn hlx_hot() {} +impl<'a> Display for HlxType<'a> {} +EOF +cat > "$F/g.go" <<'EOF' +func (r *HlxRecv) HlxMethod() int { return 1 } +EOF +cat > "$F/t.ts" <<'EOF' +export class HlxClass extends HlxBase {} +import type { HlxImported } from './x' +throw new HlxThrown() +EOF +cat > "$F/u.cpp" <<'EOF' +using std::hlx_using; +using HlxAlias = int; +using namespace hlx_ns; +class RW_API HlxExported { +template T hlx_tpl( T a ) { +EOF +cat > "$F/d.md" <<'EOF' +The `class HlxTick` spelling sits in backticks. +- (see above): hlxBullet is a markdown bullet +EOF +cat > "$F/m.m" <<'EOF' +- (void) hlxObjc { +EOF +cat > "$R/keep.c" <<'EOF' +int hlx_keep( void ) { return 0; } +int hlx_indexed( int a ) { return a; } +// hlx_indexed is mentioned in a comment +EOF +( cd "$R" && git init -q && git add -A && git -c user.name=t -c user.email=t@t commit -qm fixture ) || { echo "fixture git failed"; exit 2; } + +# kind of the HEAD row at FILE:LINE in a whereis answer (or MISSING), plus the root's head_labels/head_defs +wh() { ( cd "$R" && "$BIN" "$R" --no-cache "$@" --whereis-listing=all --limit=200 2>/dev/null ); } +kind_at() { python3 -c ' +import re,sys +s=sys.stdin.read(); p,l=sys.argv[1],sys.argv[2] +m=[k for (pp,ll,k) in re.findall(r"]*? p=\"([^\"]*)\" l=\"(\d+)\" kind=\"(\w+)\"",s) if pp==p and ll==l] +print(m[0] if m else "MISSING")' "$1" "$2"; } +arm() { # arm LABEL SYM FILE LINE WANT + local out k; out="$( wh --exclude=hlxfix/ --whereis="$2" )" + k="$( printf '%s' "$out" | kind_at "$3" "$4" )" + if [ "$k" = "$5" ]; then ok "$1 ($2 at $3:$4 kind=$k)"; else no "$1 ($2 at $3:$4: kind=$k, want $5)"; fi +} + +echo "── (A) lexical definition shape (head_labels=lexical forced) ──" +arm "A1 a comment line naming a declaration word is no definition" hlxComment hlxfix/c.c 1 ref +arm "A2 #define NAME (object-like) is a definition" HLX_OBJ hlxfix/c.c 2 def +arm "A3 #define NAME( (function-like) is a definition" HLX_FN hlxfix/c.c 3 def +arm "A4 #ifndef NAME is no definition (a preprocessor test)" HLX_OBJ hlxfix/c.c 4 ref +arm "A5 struct NAME { is a definition" hlx_tag hlxfix/c.c 5 def +arm "A6 struct NAME *local uses the tag, defines nothing" hlx_tag hlxfix/c.c 8 ref +arm "A7 typedef's last declarator is a definition" hlx_key hlxfix/c.c 9 def +arm "A8 a type a typedef only uses is no definition" hlx_key hlxfix/c.c 10 ref +arm "A9 a function-pointer typedef (*NAME) is a definition" hlx_cb hlxfix/c.c 10 def +arm "A10 a name inside a string literal is no definition" hlx_str hlxfix/c.c 11 ref +arm "A11 a C-family signature stays a definition" hlx_fn2 hlxfix/c.c 12 def +arm "A12 a name after a trailing // comment is no definition" hlx_trail hlxfix/c.c 13 ref +arm "A13 '# define NAME' (spaces after #) is a definition" HLX_SPACED hlxfix/c.c 15 def +arm "A14 a Python '# def …' comment is no definition" hlx_pycomment hlxfix/p.py 1 ref +arm "A15 class NAME(Base): is a definition" HlxPy hlxfix/p.py 2 def +arm "A16 def NAME( is a definition" hlx_run hlxfix/p.py 3 def +arm "A17 a parameter after def is no definition" hlx_param hlxfix/p.py 3 ref +arm "A18 a docstring line is no definition" HlxDoc hlxfix/p.py 4 ref +arm "A19 a name after a trailing # comment is no definition" HlxHashTrail hlxfix/p.py 5 ref +arm "A20 a Rust #[attr] line still opens a definition" hlx_hot hlxfix/r.rs 1 def +arm "A21 impl Trait for NAME implements, defines nothing" HlxType hlxfix/r.rs 2 ref +arm "A22 a Go method after its receiver is a definition" HlxMethod hlxfix/g.go 1 def +arm "A23 a Go receiver type is no definition" HlxRecv hlxfix/g.go 1 ref +arm "A24 class NAME extends … is a definition" HlxClass hlxfix/t.ts 1 def +arm "A25 the extended base is no definition" HlxBase hlxfix/t.ts 1 ref +arm "A26 an imported type is no definition" HlxImported hlxfix/t.ts 2 ref +arm "A26b throw new NAME() is a call, no definition" HlxThrown hlxfix/t.ts 3 ref +arm "A19b yield NAME( … ) is a call, no definition" HlxYield hlxfix/p.py 6 ref +arm "A27 a using-declaration is no definition" hlx_using hlxfix/u.cpp 1 ref +arm "A28 a using alias is a definition" HlxAlias hlxfix/u.cpp 2 def +arm "A29 using namespace NAME is no definition" hlx_ns hlxfix/u.cpp 3 ref +arm "A30 class EXPORT_MACRO NAME { is a definition" HlxExported hlxfix/u.cpp 4 def +arm "A31 template<…> RetType NAME( is a definition" hlx_tpl hlxfix/u.cpp 5 def +arm "A32 a name in markdown backticks is no definition" HlxTick hlxfix/d.md 1 ref +arm "A33 a markdown bullet '- (x) NAME' is no ObjC method" hlxBullet hlxfix/d.md 2 ref +arm "A34 an ObjC method '- (ret) NAME' is a definition" hlxObjc hlxfix/m.m 1 def + +echo "── (A) head_defs ──" +o="$( wh --whereis=hlxComment )" +if printf '%s' "$o" | grep -q 'head_labels="lexical" head_defs="0"'; then ok "H1 no index def of the name → head_labels=lexical head_defs=0" +else no "H1 head_defs=0 missing: $( printf '%s' "$o" | grep -o ']*>' )"; fi +o="$( wh --whereis=hlx_indexed )" +if printf '%s' "$o" | grep -q 'head_labels="index"' && ! printf '%s' "$o" | grep -q 'head_defs='; then + ok "H2 an indexed def → head_labels=index, no head_defs" +else no "H2 indexed name: $( printf '%s' "$o" | grep -o ']*>' )"; fi +if [ "$( printf '%s' "$o" | kind_at keep.c 3 )" = ref ]; then ok "H3 the index labels a comment mention of an indexed name ref"; else no "H3 keep.c:3 not ref"; fi +o="$( wh --whereis=hlxComment )" +if printf '%s' "$o" | grep -q 'head_defs=0: '; then ok "H4 the default (compact) legend defines head_defs"; else no "H4 head_defs undefined in the legend"; fi +o="$( wh --whereis=hlxComment --legend=full )" +if printf '%s' "$o" | grep -q 'head_defs="0" beside it'; then ok "H5 the full legend defines head_defs"; else no "H5 head_defs undefined in the full legend"; fi + +echo "── (B) grep: regex-shaped literals ──" +cat > "$R/b.c" <<'EOF' +int hlxa = 1; +int hlxb = hlxa + hlxa; +/* see hlxa.c and java.util.* || here */ +EOF +printf 'hlxq hlxq\n' > "$R/q.scm" +g() { ( cd "$R" && "$BIN" "$R" --no-cache "$@" 2>/dev/null ); } +root() { grep -o ']*>' | head -1; } +o="$( g --grep='hlxa|hlxb' | root )" +if printf '%s' "$o" | grep -q 'pattern_meta="|"'; then ok "B1 a|b literal → pattern_meta=|"; else no "B1 $o"; fi +if printf '%s' "$o" | grep -q "next=\"--regex='hlxa|hlxb'\""; then ok "B2 its next= is the regex verb on the pattern"; else no "B2 $o"; fi +o="$( g --regex='hlxa|hlxb' | root )" +if printf '%s' "$o" | grep -q 'pattern_meta='; then no "B3 a regex answer carries pattern_meta: $o"; else ok "B3 a --regex answer carries no pattern_meta"; fi +if printf '%s' "$o" | grep -q 'hits="2"'; then ok "B4 the named regex finds the two lines"; else no "B4 $o"; fi +o="$( g --grep='hlxa.c' | root )" +if printf '%s' "$o" | grep -q 'pattern_meta='; then no "B5 a lone literal dot is meta: $o"; else ok "B5 a lone '.' (hlxa.c) is a literal: no pattern_meta"; fi +if printf '%s' "$o" | grep -q 'next="--at='; then ok "B6 …and keeps the --at next="; else no "B6 $o"; fi +o="$( g --grep='hlxa\.c' | root )" +if printf '%s' "$o" | grep -q 'pattern_meta="\\"'; then ok "B7 a backslash escape → pattern_meta=\\"; else no "B7 $o"; fi +o="$( g --grep='||' | root )" +if printf '%s' "$o" | grep -q 'pattern_meta='; then no "B8 '||' read as alternation: $o"; else ok "B8 '||' (an operator) is no alternation"; fi +o="$( g --grep='java.util.*' | root )" +if printf '%s' "$o" | grep -q 'pattern_meta='; then no "B9 a trailing .* read as meta: $o"; else ok "B9 a trailing .* (java.util.*) stays literal"; fi +o="$( g --grep='hlxa.*hlxa' | root )" +if printf '%s' "$o" | grep -qF 'pattern_meta=".*"'; then ok "B10 .* inside the pattern → pattern_meta=.*"; else no "B10 $o"; fi +o="$( g --grep='hlxa\|hlxb' | root )" +if printf '%s' "$o" | grep -q "next=\"--regex='hlxa|hlxb'\""; then ok "B11 basic-grep a\\|b → next= the ERE a|b"; else no "B11 $o"; fi +o="$( g --grep='[^h]lxa' | root )" +if printf '%s' "$o" | grep -q 'pattern_meta="\[\]"'; then ok "B12 a [^…] class → pattern_meta=[]"; else no "B12 $o"; fi +o="$( g --grep='a[0]' | root )" +if printf '%s' "$o" | grep -q 'pattern_meta='; then no "B13 an index a[0] read as a class: $o"; else ok "B13 a[0] (an index) is no bracket class"; fi +o="$( g --grep='^hlxb' | root )" +if printf '%s' "$o" | grep -q 'pattern_meta="^\$"'; then ok "B14 a leading ^ → pattern_meta=^\$"; else no "B14 $o"; fi +# FIX ROUND 2 (review N2): ordinary literals that LOOK like regex syntax must keep their --at hop; unambiguous regex keeps the meta. +printf 'y = hlxidx[i-1] + hlxidx[i-1];\nz = hlxgrid[0][1-2];\nv.hlxuser$.subscribe();\n"hlxver": "^1.2.3"\nsee [^1] for hlxnote\n' > "$R/n.c" +for pair in 'B23 a subscript hlxidx[i-1]|hlxidx[i-1]' 'B24 a chained subscript hlxgrid[0][1-2]|hlxgrid[0][1-2]' 'B25 an RxJS suffix hlxuser$|hlxuser$' \ + 'B26 a semver ^1.2.3|^1.2.3' 'B27 a footnote [^1]|[^1]'; do + lab="${pair%%|*}"; pat="${pair#*|}" + o="$( g --grep="$pat" --grep-in=any | root )" + if printf '%s' "$o" | grep -q 'pattern_meta='; then no "$lab carries a false pattern_meta: $o" + elif printf '%s' "$o" | grep -q 'next="--at='; then ok "$lab: no pattern_meta, keeps next= --at" + else no "$lab: no --at next=: $o"; fi +done +for pair in 'B28 ^\s*int (escape + anchor)|^\s*int ' 'B29 ^hlxb$ (both anchors)|^hlxb$' 'B30 [0-9] (a leading range class)|[0-9]' \ + 'B31 [^0-9] (a negated digit range)|[^0-9]' 'B32 a class right after a word (hlxa[a-z]) reads as a subscript: quiet (known false negative)|hlxa[a-z]'; do + lab="${pair%%|*}"; pat="${pair#*|}" + o="$( g --grep="$pat" --grep-in=any | root )" + case "$lab" in + B32*) if printf '%s' "$o" | grep -q 'pattern_meta='; then no "$lab: $o"; else ok "$lab"; fi ;; + *) if printf '%s' "$o" | grep -q 'pattern_meta='; then ok "$lab keeps pattern_meta"; else no "$lab lost its pattern_meta: $o"; fi ;; + esac +done +# train 34 CR pre-pass: a UTF-8 lead/continuation byte is part of an identifier (Python, JS, Go, Rust names), so `hlxcaf\xc3\xa9|hlxtea` is an +# alternation between two words (it read as no alternation: a silent literal zero), and a subscript after a non-ASCII name is a subscript +printf 'hlxcaf\xc3\xa9 hlxtea\nhlxn\xc3\xa9[i-1]\n' > "$R/u.py" +o="$( g --grep=$'hlxcaf\xc3\xa9|hlxtea' --grep-in=any | root )" +if printf '%s' "$o" | grep -q 'pattern_meta="|"'; then ok "B35 an alternation whose word ends in a non-ASCII byte → pattern_meta=|"; else no "B35 $o"; fi +o="$( g --grep=$'hlxn\xc3\xa9[i-1]' --grep-in=any | root )" +if printf '%s' "$o" | grep -q 'pattern_meta='; then no "B36 a subscript after a non-ASCII name read as a bracket class: $o"; else ok "B36 a subscript after a non-ASCII name is no bracket class"; fi +# (N1) the compact legend and the dict say complete=/hits= speak of the LITERAL reading only. +if g --grep='hlxa' --legend=compact | grep -q 'of the literal reading only'; then ok "B33 the compact grep legend ties complete= to the literal reading"; else no "B33 compact grep legend lacks the literal-reading clause"; fi +if ( cd "$R" && "$BIN" "$R" --legend-dict 2>/dev/null ) | grep 'pattern_meta=:' | grep -q 'literal reading only'; then ok "B34 the pattern_meta dict entry ties complete=/hits= to the literal reading"; else no "B34 dict entry lacks the clause"; fi + +o="$( g --grep='hlxa|hlxb' --legend=full )" +if printf '%s' "$o" | grep -q 'pattern_meta= (present only'; then ok "B15 the full legend defines pattern_meta= when it rides"; else no "B15 legend clause missing"; fi +o="$( g --grep='hlxa' --legend=full )" +if printf '%s' "$o" | grep -q 'pattern_meta= (present only'; then no "B16 the pattern_meta clause rides without the attribute"; else ok "B16 the clause is present-only"; fi + +echo "── (B) grep: one row per line, occ= ──" +o="$( g --grep='hlxa' --grep-in=any )" +if printf '%s' "$o" | root | grep -q 'hits="3"'; then ok "B17 hits= counts LINES (3 lines hold hlxa, 4 occurrences)"; else no "B17 $( printf '%s' "$o" | root )"; fi +if printf '%s' "$o" | grep -q ']* occ="2"'; then ok "B18 the line holding hlxa twice is ONE row with occ=2"; else no "B18 no occ=2 row"; fi +if [ "$( printf '%s' "$o" | grep -o ']* occ='; then no "B20 a single occurrence carries occ="; else ok "B20 a single occurrence carries no occ="; fi +if g --grep='hlxa' --grep-in=any --legend=full | grep -q 'occ= on a row'; then ok "B21 the full legend defines occ= when it rides"; else no "B21 occ clause missing"; fi +o="$( g --grep='hlxq' )" +if ! printf '%s' "$o" | grep -q '/dev/null | tail -1 | python3 -c 'import sys,json; r=json.load(sys.stdin); print(r["result"]["content"][0]["text"] if "result" in r else "__ERROR__")'; } +j="$( mcp 'hlxa|hlxb' )" +if printf '%s' "$j" | python3 -c 'import sys,json; d=json.load(sys.stdin); sys.exit(0 if d.get("pattern_meta")=="|" and "--regex=" in d.get("next","") else 1)'; then + ok "M1 MCP grep: pattern_meta | and next= the regex verb" +else no "M1 MCP: $( printf '%s' "$j" | head -c 300 )"; fi +j="$( mcp 'hlxa' )" +if printf '%s' "$j" | python3 -c 'import sys,json; d=json.load(sys.stdin); h=[x for x in d["hits"] if x["line"]==2]; sys.exit(0 if len(h)==1 and h[0].get("occ")==2 and d.get("total")==3 else 1)'; then + ok "M2 MCP grep: one row per line, occ 2, total 3 (CLI parity)" +else no "M2 MCP: $( printf '%s' "$j" | head -c 400 )"; fi +if printf '%s' "$j" | python3 -c 'import sys,json; d=json.load(sys.stdin); sys.exit(1 if "pattern_meta" in d else 0)'; then + ok "M3 MCP grep: a plain literal carries no pattern_meta" +else no "M3 plain literal has pattern_meta"; fi + +[ "$fail" -eq 0 ] && echo "honestlexcheck: ALL PASS" || echo "honestlexcheck: FAILURES" +exit "$fail" diff --git a/test/hop1check.sh b/test/hop1check.sh new file mode 100755 index 000000000..a304dfdf0 --- /dev/null +++ b/test/hop1check.sh @@ -0,0 +1,464 @@ +#!/usr/bin/env bash +# hop1check.sh — ONE backward hop provider (src/backhops.h), three consumers: --path's hedged bridge, --path's root next=, +# --impact's hedged slot ring; and #11, the builtin-name skip of declined crossings narrowed to a receiver the index types. +# +# THE DEFECT. `--callers=X` named, one hop per call, who MAY call X without a graph edge ( called_by=, ), +# but the answers that join hops ignored it: a three-field-call chain (tmux send-keys: cmd_entry.exec, window_mode.command, +# the command table's .f) printed no bridge, its root next= pointed at gap bodies, and `--impact` said reaches="0" beside its +# own slot row. hono's `this.router.match( … )` was never crossed: `match` is spelled like String.prototype.match. +# +# THE CONTRACT. Every crossing is a hedge: proven="0" on the bridge, via= named (slot | slot-name | declined), its width +# beside it (slot_callers= at most 16, slot_fns= the field's forward width, uncapped / cands= at most 16), and never counted in reachable= / hops= / reaches=. A chain whose every +# crossing is a slot may take 3 crossings; any other keeps 2. The ring is one or two slot hops back, reaches_hedged= beside +# reaches=, rows after the rows. #11 lifts the builtin skip only for one field of this/self or of a +# parameter whose class the source states, every same-named class typing it as an in-tree class, on a Ladder decline with a +# complete list (2..16). +# +# ARMS +# (B1) 3-slot chain: bridge crossings=3, every row via="slot" slot_callers="1"; root reachable="0" hops="0". +# (B2) one step past the slot bound (4 slot crossings): no bridge; the 3-step chain in the same tree keeps it (checklist 38). +# (B3) a mixed chain (value + slot + slot = 3 crossings, not all slot): no bridge; value + slot (2) keeps one. +# (B4) width: a field called through by 17 functions is not crossed; by 16 it is (slot_callers="16"). +# (B5) slot-name (one side's struct type unknown): via="slot-name". +# (B6) position-breaker: a positional entry after a preprocessor conditional is not joined, so not crossed; the twin is. +# (B7) escape-and-cast: a call through a cast receiver is not crossed; the same call through a typed local is. +# (B8) same field name in another struct: not crossed; the same struct's caller is. +# (D1) #11: `this.router.match( m )` (router!: Router, an in-tree interface; a Ladder decline over 2 routers) is crossed. +# (D2) the same field read off a parameter whose class is stated (`app: App`) is crossed. +# (D3) `path.match( /x/ )` (path: string), (D4) an un-annotated local, (D5) `this.name.match( re )` (name: string): not crossed. +# (D6) entity identity: a second class named App typing router as a Map makes the parameter shape undecided (not crossed), +# while `this.router` inside the first App stays crossed (owner by definition, not by name). +# (D7) a typed receiver over 17 candidates is no way in (near_sink stays external). +# (D8) near_sink counts exactly the crossed sites (why="declined:2"). +# (N1) to= with a slot hop: the root next= is --callers on to=; (N2) a to= with none keeps --expand; (N3) both legend forms. +# (I1) --impact ring: reaches="0" reaches_hedged="2", one row; (I2) a ring member already in reaches= is not counted; +# (I3) no slot row: no reaches_hedged=, no ; (I4) JSON twin, same names; (I5) columnar count; (I6) row cap 16 + next=. +# (M1) MCP path_between: the same and next= as the CLI; (M2) MCP impact: the same reaches_hedged= and . +# (X1) ring cycle guard: a d=1 function is never reprinted as d=2 (ringcyc); (X2) slot_fns= prints the forward width, uncapped (fan); +# (X3) next= keeps --expand past 16 backward callers; (L3) the legends say exactly what is capped and define slot_fns=. +# (L1) full legends carry the slot / ring / callers-next sentences and parse as XML; (L2) compact legends define the terms. +# +# Usage: test/hop1check.sh [BIN] (BIN defaults to RIPWIRE_BIN, then build/ripwire) +set -u +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" +[ "${BIN#/}" = "$BIN" ] && BIN="$ROOT/$BIN" +echo "hop1check: BIN=$BIN" +[ -x "$BIN" ] || { echo " FAIL no ripwire binary at $BIN — build first"; exit 2; } +command -v python3 >/dev/null 2>&1 || { echo " FAIL python3 required"; exit 2; } +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT + +# ── fixtures ──────────────────────────────────────────────────────────────────────────────────────────────────────── +mkdir -p "$TMP/ringcyc" "$TMP/fan" "$TMP/c3" "$TMP/mix" "$TMP/w16" "$TMP/w17" "$TMP/neg" "$TMP/known" "$TMP/cap" "$TMP/ts/routers" "$TMP/ts/app" \ + "$TMP/ts2/routers" "$TMP/ts2/app" "$TMP/ts2/other" "$TMP/wide/routers" "$TMP/wide/app" +cat > "$TMP/c3/chain.c" <<'EOF' +struct entry { const char *name; void (*exec)( void ); }; +struct mode { const char *name; void (*command)( int ); }; +struct cmdt { const char *name; void (*f)( void ); }; +struct wme { const struct mode *mode; }; +struct q { void (*run)( struct entry * ); }; + +struct wme *current_wme( void ); + +static void cancel_cmd( void ) { } +static void other_cmd( void ) { } +static const struct cmdt cmd_table[] = { { "cancel", cancel_cmd }, { "other", other_cmd } }; + +void copy_command( int i ) +{ + cmd_table[i].f(); +} +const struct mode copy_mode = { .name = "copy", .command = copy_command }; + +void send_keys_exec( void ) +{ + struct wme *w = current_wme(); + w->mode->command( 0 ); +} +const struct entry send_keys_entry = { .name = "send-keys", .exec = send_keys_exec }; + +void fire_command( struct entry *e ) +{ + e->exec(); +} +const struct q top_q = { .run = fire_command }; + +void top_level( struct q *x ) +{ + x->run( 0 ); +} + +static void lonely( void ) { } +EOF +# mix: start hands send_keys_exec to an external API (a value crossing), then two slot crossings to cancel_cmd +cp "$TMP/c3/chain.c" "$TMP/mix/chain.c" +cat >> "$TMP/mix/chain.c" <<'EOF' +void register_callback( void ( *cb )( void ) ); +void start( void ) +{ + register_callback( send_keys_exec ); +} +EOF +# known: send_keys_exec ALSO calls copy_command directly — a proven caller the ring must not count again +cat > "$TMP/known/k.c" <<'EOF' +struct entry { void (*exec)( void ); }; +struct mode { void (*command)( int ); }; +struct wme { const struct mode *mode; }; +struct wme *current_wme( void ); +void copy_command( int i ) { } +const struct mode copy_mode = { .command = copy_command }; +void send_keys_exec( void ) +{ + struct wme *w = current_wme(); + w->mode->command( 0 ); + copy_command( 1 ); +} +const struct entry send_keys_entry = { .exec = send_keys_exec }; +void fire_command( struct entry *e ) { e->exec(); } +EOF +# ring cycle guard (review B1): fa and fb both call sink through A.act (d=1); fb is stored in B.go, which fa calls through, so the +# d=2 walk meets fa again from fb. self_ref calls through the field it is stored in. +cat > "$TMP/ringcyc/r.c" <<'EOF' +struct A { void (*act)( void ); }; +struct B { void (*go)( void ); }; +const struct A *ga( void ); +const struct B *gb( void ); +void sink( void ) { } +const struct A SA = { .act = sink }; +void fb( void ) { const struct A *a = ga(); a->act(); } +const struct B SB = { .go = fb }; +void fa( void ) { const struct A *a = ga(); a->act(); const struct B *b = gb(); b->go(); } +void self_ref( void ); +struct C { void (*me)( void ); }; +const struct C *gc( void ); +const struct C SC = { .me = self_ref }; +void self_ref( void ) { const struct C *c = gc(); c->me(); } +EOF +# forward width (review B2): 20 functions stored in ops.run, one caller calls through it — the crossing is 1 caller of 20 functions +{ + echo 'struct ops { const char *name; void (*run)( void ); };' + i=0; while [ "$i" -lt 20 ]; do printf 'void h%d( void ) { }\n' "$i"; i=$(( i + 1 )); done + echo 'static const struct ops table[] = {' + i=0; while [ "$i" -lt 20 ]; do printf ' { "h%d", h%d }%s\n' "$i" "$i" "$( [ "$i" -lt 19 ] && echo , )"; i=$(( i + 1 )); done + echo '};' + echo 'const struct ops *pick( int i );' + echo 'void dispatch( int i ) { const struct ops *o = pick( i ); o->run(); }' + echo 'int main( void ) { dispatch( 3 ); return 0; }' +} > "$TMP/fan/fan.c" +# width: a field the sink is stored in, called through by 16 (w16) or 17 (w17) functions +for n in 16 17; do + { + echo 'struct hooks { void (*cb)( void ); };' + echo 'static void sink( void ) { }' + echo 'const struct hooks h = { .cb = sink };' + i=1; while [ "$i" -le "$n" ]; do printf 'void c%02d( struct hooks *x ) { x->cb(); }\n' "$i"; i=$(( i + 1 )); done + } > "$TMP/w$n/w.c" +done +# cap: 17 functions call through the field copy_command is stored in — the ring's d=2 rows cut at 16 +{ + echo 'struct mode { void (*command)( int ); };' + echo 'struct cmdt { void (*f)( void ); };' + echo 'struct wme { const struct mode *mode; };' + echo 'struct wme *current_wme( void );' + echo 'static void cancel_cmd( void ) { }' + echo 'static const struct cmdt cmd_table[] = { { cancel_cmd } };' + echo 'void copy_command( int i ) { cmd_table[i].f(); }' + echo 'const struct mode copy_mode = { .command = copy_command };' + i=1; while [ "$i" -le 17 ]; do printf 'void k%02d( void ) { struct wme *w = current_wme(); w->mode->command( 0 ); }\n' "$i"; i=$(( i + 1 )); done +} > "$TMP/cap/cap.c" +cat > "$TMP/neg/neg.c" <<'EOF' +struct A { void (*cb)( void ); }; +struct B { void (*cb)( void ); }; +struct ops { int a; int b; void (*run)( void ); }; + +static void sink_a( void ) { } +const struct A a_hooks = { .cb = sink_a }; +void call_a( struct A *a ) { a->cb(); } +void call_b( struct B *b ) { b->cb(); } + +static void sink_pos( void ) { } +const struct ops pos_ok = { 1, 2, sink_pos }; +void call_pos( struct ops *p ) { p->run(); } + +static void sink_ifdef( void ) { } +const struct ops pos_bad = { 1, +#ifdef EXTRA + 3, +#endif + 2, sink_ifdef }; +void call_ifdef( struct ops *p ) { p->run(); } + +void call_cast( void *p ) { ( (struct ops *)p )->run(); } +void call_typed( void *p ) { struct ops *q = p; q->run(); } +EOF +# ts: the hono shape (#11) +cat > "$TMP/ts/routers/router.ts" <<'EOF' +export interface Router { + match(method: string): string +} +EOF +for x in a b; do +cat > "$TMP/ts/routers/$x.ts" < "$TMP/ts/app/app.ts" <<'EOF' +import type { Router } from '../routers/router' +import { aRouter } from '../routers/a' +import { bRouter } from '../routers/b' + +export class App { + router!: Router + name: string = 'app' + constructor() { + this.router = Math.random() > 0.5 ? new aRouter() : new bRouter() + } + dispatch(method: string): string { + return this.router.match(method) + } + label(re: RegExp): boolean { + return this.name.match(re) !== null + } +} + +export function viaParam(app: App): string { + return app.router.match('GET') +} + +export function onString(path: string): boolean { + return path.match(/x/) !== null +} + +export function onLocal(): boolean { + const p = 'abc' + return p.match(/b/) !== null +} +EOF +cp "$TMP/ts/routers/"*.ts "$TMP/ts2/routers/" +cp "$TMP/ts/app/app.ts" "$TMP/ts2/app/app.ts" +cat > "$TMP/ts2/other/app.ts" <<'EOF' +export class App { + router: Map = new Map() +} +EOF +# wide: 17 routers, every one imported by the app — a typed receiver over 17 candidates +{ + echo "export interface Router {"; echo " match(method: string): string"; echo "}" +} > "$TMP/wide/routers/router.ts" +imports="" +i=1; while [ "$i" -le 17 ]; do + printf "import type { Router } from './router'\nexport class R%02d implements Router {\n match(method: string): string {\n return method\n }\n}\n" "$i" > "$TMP/wide/routers/r$i.ts" + imports="${imports}import { R$( printf '%02d' "$i" ) } from '../routers/r$i' +" + i=$(( i + 1 )) +done +{ + echo "import type { Router } from '../routers/router'" + printf '%s' "$imports" + echo "export class App {" + echo " router!: Router" + echo " dispatch(method: string): string {" + echo " return this.router.match(method)" + echo " }" + echo "}" +} > "$TMP/wide/app/app.ts" + +python3 - "$BIN" "$TMP" "$ROOT" <<'PY' +import json, re, shutil, subprocess, sys +binp, tmp, repo = sys.argv[1], sys.argv[2], sys.argv[3] +fail = 0 +def ok( m ): print( " PASS " + m ) +def no( m ): + global fail + fail = 1 + print( " FAIL " + m ) +def run( d, *args ): + """One answer, read only off a run that succeeded and printed a root (checklist 14).""" + p = subprocess.run( [ binp, d, "--no-cache", *args ], cwd = d, capture_output = True, text = True, timeout = 300 ) + if p.returncode != 0 or not re.search( r'<(path|impact|callers) |^\{"of"', p.stdout ): + return None + return p.stdout +def body( t ): + return re.sub( r'', '', t, flags = re.S ) +def bridge( t ): + m = re.search( r'', body( t ) ) + return m.group( 0 ) if m else "" +def root( t, tag ): + m = re.search( r'<' + tag + r' [^>]*>', body( t ) ) + return m.group( 0 ) if m else "" +def attr( el, name ): + m = re.search( r'\s' + name + r'="([^"]*)"', el ) + return m.group( 1 ) if m else None +def check( label, cond, detail = "" ): + ( ok if cond else no )( label + ( "" if cond else " — " + detail ) ) + +def path( d, a, b, *extra ): + t = run( d, "--path=%s,%s" % ( a, b ), *extra ) + return t if t is not None else "" + +# ── B: the bridge's slot crossings ────────────────────────────────────────────────────────────────────────────────── +c3 = tmp + "/c3" +t = path( c3, "fire_command", "cancel_cmd" ); br = bridge( t ); r = root( t, "path" ) +rows = re.findall( r']*/>', br ) +check( "(B1) a 3-slot chain is bridged: crossings=3, every crossing via=slot slot_callers=1, proven=0", + attr( br, "crossings" ) == "3" and attr( br, "proven" ) == "0" and len( rows ) == 4 + and all( 'via="slot" slot_callers="1"' in x for x in rows[1:] ) + and [ attr( x, "n" ) for x in rows ] == [ "fire_command", "send_keys_exec", "copy_command", "cancel_cmd" ], repr( br ) ) +check( "(B1) the root stays reachable=0 hops=0 (a bridge is in no count)", attr( r, "reachable" ) == "0" and attr( r, "hops" ) == "0", r ) +t4 = path( c3, "top_level", "cancel_cmd" ); t3 = path( c3, "top_level", "copy_command" ) +check( "(B2) one step past the slot bound (4 slot crossings): no bridge", t4 != "" and bridge( t4 ) == "" and attr( root( t4, "path" ), "reachable" ) == "0", repr( bridge( t4 ) ) ) +check( "(B2) one step short (3 slot crossings) in the same tree: the bridge stays", attr( bridge( t3 ), "crossings" ) == "3", repr( bridge( t3 ) ) ) +mix = tmp + "/mix" +tm3 = path( mix, "start", "cancel_cmd" ); tm2 = path( mix, "start", "copy_command" ) +check( "(B3) a mixed chain of 3 crossings (value + slot + slot) is not bridged", tm3 != "" and bridge( tm3 ) == "", repr( bridge( tm3 ) ) ) +check( "(B3) value + slot (2 crossings) is", re.search( r'n="send_keys_exec"[^>]*via="value" cands="1".*n="copy_command"[^>]*via="slot" slot_callers="1"', bridge( tm2 ) ) is not None, repr( bridge( tm2 ) ) ) +t17 = path( tmp + "/w17", "c01", "sink" ); t16 = path( tmp + "/w16", "c01", "sink" ) +check( "(B4) a field 17 functions call through is not crossed", t17 != "" and bridge( t17 ) == "", repr( bridge( t17 ) ) ) +check( "(B4) a field 16 functions call through is (slot_callers=16)", 'via="slot" slot_callers="16"' in bridge( t16 ), repr( bridge( t16 ) ) ) +sj = repo + "/test/slotjoinfix" +ts5 = path( sj, "run_outer", "on_cb" ) +check( "(B5) a slot joined by field name only is crossed as via=slot-name", 'n="on_cb" p="mode.c:7" via="slot-name" slot_callers="1"' in bridge( ts5 ), repr( bridge( ts5 ) ) ) +neg = tmp + "/neg" +def crossed( a, b ): + return bridge( path( neg, a, b ) ) != "" +def not_crossed( a, b ): + t = path( neg, a, b ) # a negative arm needs its premise too: a failed run (or a mistyped symbol) answers "" and would pass + return t != "" and bridge( t ) == "" +check( "(B6) a positional entry after a preprocessor conditional is not crossed", not_crossed( "call_ifdef", "sink_ifdef" ) ) +check( "(B6) its twin without the conditional is", crossed( "call_pos", "sink_pos" ) ) +check( "(B7) a call through a cast receiver is not crossed", not_crossed( "call_cast", "sink_pos" ) ) +check( "(B7) the same call through a typed local is", crossed( "call_typed", "sink_pos" ) ) +check( "(B8) a same-named field of ANOTHER struct is not crossed", not_crossed( "call_b", "sink_a" ) ) +check( "(B8) the same struct's caller is", crossed( "call_a", "sink_a" ) ) + +# ── D: #11, the builtin-name skip narrowed ────────────────────────────────────────────────────────────────────────── +ts = tmp + "/ts" +def tsbridge( d, a ): + return bridge( path( d, a, "routers/a.ts:match" ) ) +check( "(D1) this.router.match (router!: Router, in-tree) is crossed via=declined cands=2", + 'n="match" p="routers/a.ts:3" via="declined" cands="2"' in tsbridge( ts, "dispatch" ), repr( tsbridge( ts, "dispatch" ) ) ) +check( "(D2) app.router.match on a parameter typed App is crossed", 'via="declined" cands="2"' in tsbridge( ts, "viaParam" ), repr( tsbridge( ts, "viaParam" ) ) ) +check( "(D3) path.match( /x/ ) on a parameter typed string is not crossed", tsbridge( ts, "onString" ) == "" and path( ts, "onString", "routers/a.ts:match" ) != "" ) +check( "(D4) p.match( /b/ ) on an un-annotated local is not crossed", tsbridge( ts, "onLocal" ) == "" and path( ts, "onLocal", "routers/a.ts:match" ) != "" ) +check( "(D5) this.name.match( re ) (name: string) is not crossed", tsbridge( ts, "label" ) == "" and path( ts, "label", "routers/a.ts:match" ) != "" ) +ts2 = tmp + "/ts2" +check( "(D6) a second class App typing router as a Map (not an in-tree class) leaves the parameter shape undecided (not crossed)", + tsbridge( ts2, "viaParam" ) == "" and path( ts2, "viaParam", "routers/a.ts:match" ) != "" ) +check( "(D6) this.router inside the first App stays crossed (owner by definition, not by name)", 'via="declined" cands="2"' in tsbridge( ts2, "dispatch" ), repr( tsbridge( ts2, "dispatch" ) ) ) +tw = path( tmp + "/wide", "dispatch", "routers/r1.ts:match" ) +check( "(D7) a typed receiver over 17 candidates is no way in (near_sink why=external, no bridge)", + tw != "" and bridge( tw ) == "" and 'why="external"' in body( tw ) and "declined" not in ( re.search( r'', body( tw ) ) or re.match( "", "" ) ).group( 0 ), repr( body( tw )[-400:] ) ) +td = path( ts, "onString", "routers/a.ts:match" ) +check( "(D8) near_sink counts exactly the two crossed sites (why=declined:2)", 'why="declined:2"' in body( td ), repr( re.findall( r']*>', body( td ) ) ) ) + +# ── X: review fixes (fix round 1) ─────────────────────────────────────────────────────────────────────────────────── +trc = run( tmp + "/ringcyc", "--impact=sink" ) or ""; rrc = root( trc, "impact" ) +check( "(X1) ring cycle guard: fa and fb are both d=1, so reaches_hedged=2 and NO (a d=1 function is not printed again as d=2)", + attr( rrc, "reaches_hedged" ) == "2" and "' in body( ti ), repr( body( ti )[-500:] ) ) +tk = run( tmp + "/known", "--impact=copy_command" ) or ""; rk = root( tk, "impact" ) +check( "(I2) a ring member already in reaches= is not counted again (reaches=1, reaches_hedged=1)", + attr( rk, "reaches" ) == "1" and attr( rk, "reaches_hedged" ) == "1" and '", + tn != "" and "reaches_hedged" not in tn and "]*>', body( tcap ) ) +check( "(I6) 17 two-hop rows: shown=16 of total=17, next= the callers of the first unshown row's calls=", + ring is not None and attr( ring.group( 0 ), "total" ) == "17" and attr( ring.group( 0 ), "shown" ) == "16" + and attr( ring.group( 0 ), "next" ) == "--callers=copy_command" and len( re.findall( r' 0: + resp[ o[ "id" ] ] = "".join( c.get( "text", "" ) for c in o.get( "result", {} ).get( "content", [] ) ) +m1, m2, m3 = resp.get( 1, "" ), resp.get( 2, "" ), resp.get( 3, "" ) +cli1 = path( c3, "fire_command", "cancel_cmd" ); cli2 = path( ts, "dispatch", "routers/a.ts:match" ) +check( "(M1) MCP path_between: the same and root next= as the CLI (slot chain and #11 crossing)", + bridge( m1 ) != "" and bridge( m1 ) == bridge( cli1 ) and attr( root( m1, "path" ), "next" ) == attr( root( cli1, "path" ), "next" ) + and bridge( m2 ) != "" and bridge( m2 ) == bridge( cli2 ), repr( ( bridge( m1 )[:200], bridge( m2 )[:200] ) ) ) +cli3 = run( c3, "--impact=cancel_cmd" ) or "" +ringm = re.search( r'', body( m3 ) ); ringc = re.search( r'', body( cli3 ) ) +check( "(M2) MCP impact: the same reaches_hedged= and as the CLI", + attr( root( m3, "impact" ), "reaches_hedged" ) == "2" and ringm is not None and ringc is not None and ringm.group( 0 ) == ringc.group( 0 ), repr( m3[-400:] ) ) + +# ── L: legends ────────────────────────────────────────────────────────────────────────────────────────────────────── +pf = path( c3, "fire_command", "cancel_cmd", "--legend=full" ) +imf = run( c3, "--impact=cancel_cmd", "--legend=full" ) or "" +check( "(L1) the full path legend names the slot crossing and slot_callers=", "slot_callers=N the functions that field join names" in pf and "3 when every crossing is a slot" in pf ) +check( "(L1) the full impact legend names reaches_hedged= and the rows", "reaches_hedged=N (absent when 0)" in imf and "" in imf ) +if shutil.which( "xmllint" ): + for label, doc in ( ( "path", pf ), ( "impact", imf ), ( "impact compact", ti ), ( "path compact", t ) ): + x = subprocess.run( [ "xmllint", "--noout", "-" ], input = doc, capture_output = True, text = True ) + check( "(L1) the %s answer with the new clauses parses as XML (checklist 25)" % label, x.returncode == 0 and doc != "", x.stderr[:300] ) +else: + print( " SKIP (L1) xmllint not installed: the well-formedness arm needs it" ) +check( "(L2) the compact path legend defines slot_callers=", "s via=slot|slot-name slot_callers=N:" in t ) +pfan = path( tmp + "/fan", "main", "h7", "--legend=full" ) +check( "(L3) the full bridge legend says exactly what is capped: slot_callers= at 16, slot_fns= not capped; no blanket 'each fanning out to at most 16 candidates'", + "slot_fns=K the functions the field holds, NOT capped" in pfan and "each slot one bounded on its callers" in pfan + and "each fanning out to at most 16 candidates" not in pfan and "(at most 16 callers or candidates)" in path( c3, "fire_command", "cancel_cmd", "--legend=full" ), "" ) +check( "(L3) the compact path legend defines slot_fns= (not capped)", "slot_fns=K:" in tfan and "NOT capped" in tfan, repr( tfan[ -400: ] ) ) +check( "(L3) the MCP twin of the bridge legend carries slot_fns=K too", "slot_fns=K" in m1, repr( m1[ -300: ] ) ) +check( "(L3) the full impact legend says the ring's one-hop set has no width bound", "NO width bound" in imf ) +check( "(L2) the compact impact legend defines reaches_hedged= and the ring row", "reaches_hedged=N:" in ti and "rh called_by= called_at= via= calls=:" in ti ) +sys.exit( fail ) +PY +rc=$? # the harness's own status, read immediately (checklist 14): 0 every arm passed, 1 an arm failed, anything else a crash +fail=0 +if [ "$rc" -ne 0 ]; then + printf ' FAIL the arm harness exited %s (an arm failed above, or the harness crashed)\n' "$rc" + fail=1 +fi +[ "$fail" = 0 ] && echo "ALL PASS" || echo "FAILURES ABOVE" +exit "$fail" diff --git a/test/ifacedispatchcheck.sh b/test/ifacedispatchcheck.sh index dae508f70..bdb4bc63c 100755 --- a/test/ifacedispatchcheck.sh +++ b/test/ifacedispatchcheck.sh @@ -272,7 +272,7 @@ say( af.ok and fake in cs, "[C] n_rs_test_impl", "the #[cfg(test)] implementor i # map reads — a structurally conforming TS object literal or class, a Java lambda / anonymous class, a Kotlin `fun interface` # lambda or object expression, a Rust trait mockall's attribute implements — the tier steps aside and main's by-name rows stay. print( "=== (K) completeness: an implementor without a clause opens the list ===" ) -B1TS, B1JAVA, B1KT, B1RS = ( os.path.join( FIX, d ) for d in ( "b1ts", "b1java", "b1kt", "b1rs" ) ) +B1TS, B1JAVA, B1KT, B1RS, FC2U, FC2D = ( os.path.join( FIX, d ) for d in ( "b1ts", "b1java", "b1kt", "b1rs", "fc2rs_undecided", "fc2rs_decided" ) ) def rows_are( a, want ): return a.ok and sorted( ( r.get( "n" ), r.get( "p" ), r.get( "via" ) ) for r in a.rows ) == sorted( want ) and not a.dc for root, sel, want, tag, why in ( @@ -288,10 +288,26 @@ for root, sel, want, tag, why in ( ( B1KT, "runTwo", [ ( "first", "B.kt:3", "name" ) ], "kt_object", "Kotlin: a two-method interface with an `object : Two { … }` expression" ), ( B1KT, "runLam", [ ( "call", "C.kt:3", "name" ) ], "kt_fun", "Kotlin: `fun interface Lam` a lambda implements (`runLam { it + 1 }`)" ), ( B1RS, "send_500", [ ( "send_response", "src/mock.rs:10", "name" ) ], "rs_automock", - "Rust: `#[cfg_attr(any(test, feature = \"mock\"), automock)]` writes MockFilter, an implementor no source holds (envoy send_500)" ) ): + "Rust: `#[cfg_attr(any(test, feature = \"mock\"), automock)]` writes MockFilter, an implementor no source holds (envoy send_500)" ), + ( FC2U, "go", [ ( "draw", "src/a.rs:6", "name" ), ( "draw", "src/b.rs:8", "name" ) ], "rs_undecided_use", + "Rust (train 34, fc2 x L6): `impl Render for Square` under `use mystery_crate::Render` (no module, no std sibling, no declared dependency) is undecided, " + "dropped from the implementor map (--lego=Render says implementors_floor=1): the list is open, so Circle is not the proven only implementor" ) ): a = ask( root, "callees", sel ) say( rows_are( a, want ), "[P] k_%s" % tag, "--callees=%s: main's by-name rows %s, no via=\"impl\" -- %s" % ( sel, [ w[ 1 ] for w in want ], why ), a.why() ) +# precondition and control for k_rs_undecided_use: the interface row really carries the floor (else the arm proves nothing), and the same +# tree with the head DECLARED as a registry dependency (decided outside) keeps the one proven implementor +rc, out = run( FC2U, "--lego=Render" ) +try: + ifaces = [ e.attrib for e in ET.fromstring( "" + out + "" ).iter( "iface" ) ] +except ET.ParseError: + ifaces = [] +say( rc == 0 and len( ifaces ) == 1 and ifaces[ 0 ].get( "implementors_floor" ) == "1", "[C] k_rs_undecided_floor", + "--lego=Render on the undecided tree says implementors_floor=1 (the clause is dropped, not decided)", "rc=%s ifaces=%s" % ( rc, ifaces ) ) +a = ask( FC2D, "callees", "go" ) +say( rows_are( a, [ ( "draw", "src/a.rs:6", "impl" ) ] ), "[C] k_rs_decided_outside", + "--callees=go with `mystery_crate` a declared registry dependency: Square implements an outside trait, Circle is the proven only implementor", a.why() ) + # ── (K2) the SAM count (fix round 2, C2; the review's javaeq / javaeq2 / javasame / ktjava) ──────────────────────────────────── # A lambda implements an interface with ONE abstract method (JLS 9.8), so every doubt opens the list: an Object method redeclared is # no abstract method of the count, `a.Handler` is not made non-SAM by `b.Handler`'s methods, and Java passes a lambda to a Kotlin diff --git a/test/ifacedispatchfix/fc2rs_decided/Cargo.toml b/test/ifacedispatchfix/fc2rs_decided/Cargo.toml new file mode 100644 index 000000000..43e807b5b --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_decided/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "fc2rs_decided" +version = "0.1.0" + +[dependencies] +mystery_crate = "1" diff --git a/test/ifacedispatchfix/fc2rs_decided/src/a.rs b/test/ifacedispatchfix/fc2rs_decided/src/a.rs new file mode 100644 index 000000000..d578fcda5 --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_decided/src/a.rs @@ -0,0 +1,9 @@ +use crate::Render; + +pub struct Circle; + +impl Render for Circle { + fn draw(&self) -> i32 { + 1 + } +} diff --git a/test/ifacedispatchfix/fc2rs_decided/src/b.rs b/test/ifacedispatchfix/fc2rs_decided/src/b.rs new file mode 100644 index 000000000..9e8d9d615 --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_decided/src/b.rs @@ -0,0 +1,11 @@ +// `mystery_crate` is a registry dependency Cargo.toml declares: this Render is DECIDED outside the tree, so Square implements +// no in-tree trait and the one in-tree implementor (Circle) is proven. +use mystery_crate::Render; + +pub struct Square; + +impl Render for Square { + fn draw(&self) -> i32 { + 2 + } +} diff --git a/test/ifacedispatchfix/fc2rs_decided/src/lib.rs b/test/ifacedispatchfix/fc2rs_decided/src/lib.rs new file mode 100644 index 000000000..62352dfc9 --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_decided/src/lib.rs @@ -0,0 +1,7 @@ +pub mod a; +pub mod b; +pub mod run; + +pub trait Render { + fn draw(&self) -> i32; +} diff --git a/test/ifacedispatchfix/fc2rs_decided/src/run.rs b/test/ifacedispatchfix/fc2rs_decided/src/run.rs new file mode 100644 index 000000000..253b706ce --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_decided/src/run.rs @@ -0,0 +1,5 @@ +use crate::Render; + +pub fn go(r: &dyn Render) -> i32 { + r.draw() +} diff --git a/test/ifacedispatchfix/fc2rs_undecided/Cargo.toml b/test/ifacedispatchfix/fc2rs_undecided/Cargo.toml new file mode 100644 index 000000000..725871b1e --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_undecided/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "fc2rs_undecided" +version = "0.1.0" diff --git a/test/ifacedispatchfix/fc2rs_undecided/src/a.rs b/test/ifacedispatchfix/fc2rs_undecided/src/a.rs new file mode 100644 index 000000000..d578fcda5 --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_undecided/src/a.rs @@ -0,0 +1,9 @@ +use crate::Render; + +pub struct Circle; + +impl Render for Circle { + fn draw(&self) -> i32 { + 1 + } +} diff --git a/test/ifacedispatchfix/fc2rs_undecided/src/b.rs b/test/ifacedispatchfix/fc2rs_undecided/src/b.rs new file mode 100644 index 000000000..1826dc530 --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_undecided/src/b.rs @@ -0,0 +1,11 @@ +// `mystery_crate` is no module of this tree, no std sibling and no Cargo dependency: the tree cannot say whether this Render is +// the one lib.rs defines (a re-export it did not see), so Square is neither listed as an implementor nor ruled out. +use mystery_crate::Render; + +pub struct Square; + +impl Render for Square { + fn draw(&self) -> i32 { + 2 + } +} diff --git a/test/ifacedispatchfix/fc2rs_undecided/src/lib.rs b/test/ifacedispatchfix/fc2rs_undecided/src/lib.rs new file mode 100644 index 000000000..62352dfc9 --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_undecided/src/lib.rs @@ -0,0 +1,7 @@ +pub mod a; +pub mod b; +pub mod run; + +pub trait Render { + fn draw(&self) -> i32; +} diff --git a/test/ifacedispatchfix/fc2rs_undecided/src/run.rs b/test/ifacedispatchfix/fc2rs_undecided/src/run.rs new file mode 100644 index 000000000..253b706ce --- /dev/null +++ b/test/ifacedispatchfix/fc2rs_undecided/src/run.rs @@ -0,0 +1,5 @@ +use crate::Render; + +pub fn go(r: &dyn Render) -> i32 { + r.draw() +} diff --git a/test/langidentity/runner.py b/test/langidentity/runner.py index 0428a1f31..ae6b335ff 100755 --- a/test/langidentity/runner.py +++ b/test/langidentity/runner.py @@ -468,7 +468,9 @@ def count_predicates(listing, perlang): for path, body in re.findall(r']*>(.*?)', listing, re.S): if os.path.basename(path) in perlang: continue - for line, code in re.findall(r'', body, re.S): + # honest-lex prints a line holding the pattern twice as ONE row with occ="N" (train 34): read that row too, or every + # line with two Lang:: mentions drops out of the count (336 -> 250 on the composed tree). + for line, code in re.findall(r'', body, re.S): if (path, line) in seen: continue seen.add((path, line)) diff --git a/test/legendcoveragecheck.sh b/test/legendcoveragecheck.sh index f2c6c49e5..3e8826e3b 100755 --- a/test/legendcoveragecheck.sh +++ b/test/legendcoveragecheck.sh @@ -830,5 +830,75 @@ fi [ -z "$( hline H3 )" ] && ok "(H3) the MCP stray_content twin defines every attribute it emits, diffable= included" \ || no "(H3) the MCP stray_content legend does not define: $( hline H3 )" +# ── (S) slot bridge / hedged ring (train 34, rv-hop1-delta1 D1): HOP1's slot crossings print via= / slot_callers= / +# slot_fns= on a SECOND row and the hedged ring on /, which the roster's first-instance sweep never +# enumerates. A C slot chain (hop1check's c3 fixture): every attribute ANY element of the answer carries must be +# defined (`name=`) in the leading legend of --path and --impact, default and compact. (S0) is the precondition: the +# answer really holds a via="slot" row with slot_fns= (else the arm proves nothing); (S5) is the mutation control: the +# same probe over a copy of the legend without its slot_fns= definition reports that gap (the arm can fail). +SF="$TMP/slotfix"; mkdir -p "$SF" +cat > "$SF/chain.c" <<'CEOF' +struct entry { const char *name; void (*exec)( void ); }; +struct mode { const char *name; void (*command)( int ); }; +struct cmdt { const char *name; void (*f)( void ); }; +struct wme { const struct mode *mode; }; +struct wme *current_wme( void ); +static void cancel_cmd( void ) { } +static void other_cmd( void ) { } +static const struct cmdt cmd_table[] = { { "cancel", cancel_cmd }, { "other", other_cmd } }; +void copy_command( int i ) { cmd_table[i].f(); } +const struct mode copy_mode = { .name = "copy", .command = copy_command }; +void send_keys_exec( void ) { struct wme *w = current_wme(); w->mode->command( 0 ); } +const struct entry send_keys_entry = { .name = "send-keys", .exec = send_keys_exec }; +void fire_command( struct entry *e ) { e->exec(); } +CEOF +( cd "$SF" && "$BIN" "$SF" --no-cache --path=fire_command,cancel_cmd >"$TMP/s_pdef" 2>/dev/null + "$BIN" "$SF" --no-cache --path=fire_command,cancel_cmd --legend=compact >"$TMP/s_pcmp" 2>/dev/null + "$BIN" "$SF" --no-cache --impact=cancel_cmd >"$TMP/s_idef" 2>/dev/null + "$BIN" "$SF" --no-cache --impact=cancel_cmd --legend=compact >"$TMP/s_icmp" 2>/dev/null ) +python3 - "$TMP" <<'PYS' >"$TMP/s_out" 2>&1 +import os, re, sys +T = sys.argv[1] +def rd( p ): return open( os.path.join( T, p ), "rb" ).read().decode( "utf-8", "replace" ) +def legendOf( doc ): + m = re.match( r'\A(?:\s*)+', doc, re.S ) + return m.group( 0 ) if m else "" +def defined( a, legend ): return re.search( r'(?', '', re.sub( r'', '', doc, flags = re.S ), flags = re.S ) + keys = set() + for m in re.finditer( r'<([a-zA-Z][\w-]*)((?:\s+[\w:.-]+="[^"]*")*)\s*/?>', body ): + for a in re.findall( r'\s([\w:.-]+)="', m.group( 2 ) ): + if a not in { "p", "n", "t", "id", "l", "k", "c" }: keys.add( m.group( 1 ) + "@" + a ) + return sorted( k for k in keys if not defined( k.split( "@", 1 )[ 1 ], legend ) ) +docs = { k: rd( "s_" + k ) for k in ( "pdef", "pcmp", "idef", "icmp" ) } +slot = re.compile( r']*via="slot"[^>]*slot_fns="\d+"' ) +ring = re.compile( r']*via="slot"' ) +print( "S0 %d %d %d %d" % ( bool( slot.search( docs[ "pdef" ] ) ), bool( slot.search( docs[ "pcmp" ] ) ), + bool( ring.search( docs[ "idef" ] ) ), bool( ring.search( docs[ "icmp" ] ) ) ) ) +for k in ( "pdef", "pcmp", "idef", "icmp" ): print( "S-" + k + " " + " ".join( gaps( docs[ k ] ) ) ) +cut = re.sub( r'(? ring, default and compact" +else + no "(S0) no slot row (path def/cmp, impact def/cmp = $( sline S0 )) — (S1)-(S5) prove nothing: $( head -c 300 "$TMP/s_out" )" +fi +for k in "pdef:1:--path default" "pcmp:2:--path --legend=compact" "idef:3:--impact default" "icmp:4:--impact --legend=compact"; do + key=${k%%:*}; rest=${k#*:}; n=${rest%%:*}; what=${rest#*:} + if [ -z "$( sline "S-$key" )" ]; then + ok "(S$n) the $what slot answer defines every attribute it emits (via= slot_callers= slot_fns= reaches_hedged= ring)" + else + no "(S$n) the $what slot answer's legend does not define: $( sline "S-$key" )" + fi +done +case " $( sline S5 ) " in + *" s@slot_fns "*) ok "(S5) mutation control: the probe reports s@slot_fns once the legend loses its slot_fns= definition" ;; + *) no "(S5) mutation control: a legend without slot_fns= still read as covered ($( sline S5 ))" ;; +esac + [ "$fail" -eq 0 ] && echo "ALL PASS" || echo "legendcoveragecheck: FAILURES ABOVE" exit "$fail" diff --git a/test/legotransitivecheck.sh b/test/legotransitivecheck.sh index 31756d6f3..a87cd09e2 100755 --- a/test/legotransitivecheck.sh +++ b/test/legotransitivecheck.sh @@ -25,6 +25,14 @@ # an include, the package, a qualifier). A name-only edge's row is hedged base_by="name" and not walked; an edge the # evidence binds to ANOTHER same-named type adds no row and is counted transitive_other_base=. Python's `A.B` base is # B (inside A), `Generic[K]`'s type argument and `metaclass=M` are no base. Red on 4d59c804 (the namesake rows listed). +# (XP) lane fc2-lego-extparent: a class whose base clause the file's OWN import binds OUTSIDE the tree is no implementor of an +# in-tree namesake (Python `ext.Base` / `from ext import Base`, TS/JS `import { Base } from 'lib'` / `lib.Base`, Rust +# `use ext::Base`, Java/Kotlin `import org.ext.Base` / `extends org.ext.Base`); a real in-tree implementor by bare, +# qualified, re-exported, relative, same-package or imported spelling stays; an undecided binding (a star or wildcard +# import), a workspace crate spelled with `_` for its `-` directory, and the tree's own package name keep their row. +# Red on 27eddee1 (every outside namesake listed). Corpus xp/{py,ts,js,rs,java,kt}. Fix round 1: a Cargo package/[lib] name +# (tests/, examples/, main.rs, a renamed workspace crate), the Node self-reference on a multi-root run, a root-is-the-package +# Python self import keep their row; a clause dropped for a DECIDED outside binding adds no implementors_floor. # (UW) fix 2 (rule 5): a DIRECT row whose own extends clause is not proven (a cross-package Java import, a Kotlin import, a C# # using, an ES import of a CommonJS base) is not walked below, and the answer SAYS so: the row carries unwalked="1", the # counts those rows transitive_unwalked=N and names the first one's --lego as unwalked_next=; a present-only @@ -357,6 +365,148 @@ if "$BIN" "$FIX" --no-cache --lego=BaseFormatter --legend=full >"$DIR/full" 2>"$ M="$( printf '%s\n' "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"lego\",\"arguments\":{\"path\":\"$FIX\",\"type\":\"UwCRoot\"}}}" | "$BIN" --mcp 2>/dev/null )" if printf '%s' "$M" | grep -q 'transitive_unwalked=\\"1\\" unwalked_next=\\"--lego=uw/cs/UwCMid.cs:UwCMid\\"' && printf '%s' "$M" | grep -q 'unwalked=\\"1\\"/>'; then ok "MCP lego carries the same count, next and row mark"; else no "MCP lego UwCRoot: $( printf '%s' "$M" | head -c 400 )"; fi +echo "=== (XP) a base the file imports from OUTSIDE the tree is no implementor of an in-tree namesake ===" +# xp SEL "absent rows" "present rows": one --lego answer; every absent row must be missing, every present row listed +xp(){ + local sel="$1" absent="$2" present="$3" n + lego "$sel" || return 0 + for n in $absent + do + if has ""$DIR/out" 2>"$DIR/err" + then + OUT="$( sed 's/>\n"$DIR/out" 2>"$DIR/err" +then + OUT="$( sed 's/>\n"$DIR/out" 2>"$DIR/err" +then + OUT="$( sed 's/>\n rows to stale_by_kind= and - # stale_next= (the --show-stale call); the twin LISTS those rows as "sa", so the two facts survive as the rows themselves. - "stale_by_kind": { "sa" }, - "stale_next": { "sa" }, + # quality_delta: lane lean-trio's RENAME stale_by_kind/stale_next -> "sa" is GONE (lane lean2b, card #9 MCP-FOLD). The twin + # now folds like the CLI and carries both keys BY NAME, so root parity is plain name parity again; the COUNT twin below + # asserts the fold lost nothing (stale on both surfaces, stale_by_kind summing to it, show_stale=true listing the rows). } def report( label, cliNames, twinNames, lens ): @@ -242,6 +241,45 @@ else: report( "quality_delta root", xmlRootAttrs( qdXml, "quality-delta" ) or set(), jsonTopKeys( qdMcp ), declaredLens( qdMcp ) ) +# lean2b (card #9) COUNT PARITY — the twin of the RENAME removal above. Name parity alone would pass a fold that dropped a +# row or miscounted a kind, so the values are compared where they are a closed count: CLI stale= == MCP stale; when either +# surface folds, BOTH fold, and stale_by_kind sums to stale on each and is the SAME string; MCP show_stale=true lists exactly +# `stale` "sa" rows and carries neither fold key; and the folded and show_stale=true documents agree on every other key. +def kindSum( v ): + try: return sum( int( kv.rsplit( ":", 1 )[ 1 ] ) for kv in v.split( "," ) if kv ) + except Exception: return -1 +qdFull = mcp( "quality_delta", { "path": ROOT, "show_stale": True } ) +try: + jDef = json.loads( qdMcp ) + jFull = json.loads( qdFull ) +except Exception: + jDef = jFull = None +check( jDef is not None and jFull is not None, "COUNT: MCP quality_delta default and show_stale=true both parse as JSON" ) +if jDef is not None and jFull is not None: + cliRoot = stripComments( qdXml ) + mStale = re.search( r']*\bstale="(\d+)"', cliRoot ) + mKinds = re.search( r']*\bstale_by_kind="([^"]*)"', cliRoot ) + cliStale = int( mStale.group( 1 ) ) if mStale else None + check( cliStale is not None and cliStale == jDef.get( "stale" ) == jFull.get( "stale" ), + "COUNT: stale= agrees on the CLI (%r), MCP default (%r) and show_stale=true (%r)" % ( cliStale, jDef.get( "stale" ), jFull.get( "stale" ) ) ) + mcpFolds = "stale_by_kind" in jDef + # the twin folds only where the CLI does, and declines only where its JSON fold would not PAY (foldPays: < 75 % of the bytes) + cut = qdFull.rfind( '"sa":[' ) + predict = qdFull[ :cut ] + '"stale_by_kind":"' + ( mKinds.group( 1 ) if mKinds else "" ) + '","stale_next":"show_stale=true"}' + jsonPays = bool( mKinds ) and cut >= 0 and len( predict.encode() ) * 100 < len( qdFull.encode() ) * 75 + check( mcpFolds == jsonPays and ( not mcpFolds or qdMcp == predict ), + "COUNT: the MCP default folds exactly when the CLI does AND the JSON fold pays, into the predicted bytes (cli=%s pays=%s mcp=%s)" + % ( bool( mKinds ), jsonPays, mcpFolds ) ) + if mcpFolds: + check( "sa" not in jDef and jDef.get( "stale_next" ) == "show_stale=true", + "COUNT: a folded twin drops sa and names show_stale=true in stale_next (got %r)" % ( jDef.get( "stale_next" ), ) ) + check( kindSum( jDef[ "stale_by_kind" ] ) == jDef.get( "stale" ), "COUNT: MCP stale_by_kind sums to stale (%r)" % ( jDef[ "stale_by_kind" ], ) ) + check( bool( mKinds ) and mKinds.group( 1 ) == jDef[ "stale_by_kind" ], "COUNT: CLI and MCP stale_by_kind are the same string" ) + check( "stale_by_kind" not in jFull and "stale_next" not in jFull and len( jFull.get( "sa", [] ) ) == jFull.get( "stale" ), + "COUNT: show_stale=true lists exactly stale (%r) sa rows (%r) and no fold key" % ( jFull.get( "stale" ), len( jFull.get( "sa", [] ) ) ) ) + rest = lambda d: { k: v for k, v in d.items() if k not in ( "sa", "stale_by_kind", "stale_next" ) } + check( rest( jDef ) == rest( jFull ), "COUNT: default and show_stale=true agree on every key outside the fold" ) + # A2 (found-items 2026-09-17): VALUE parity for the "sidecar present but unreadable" baseline marker. # attrparity above is deliberately NAME-only (a git stamp or a root path legitimately differs by value), # but this one string is a closed, documented vocabulary (verbs_quality.h's own legend) and CLI/MCP must diff --git a/test/pathgapcheck.sh b/test/pathgapcheck.sh index f9542a768..2ecf5a5e0 100755 --- a/test/pathgapcheck.sh +++ b/test/pathgapcheck.sh @@ -60,6 +60,37 @@ # spelled like a builtin type's method and (2) the namesake's owner (its class in its file, else its file) owning # something the forward search reached — K1 owner out of reach, K2 builtin-spelled (owner in reach), K3 both, # K4 near miss (owner in reach, not builtin: both hedges stay; S4/B4 run on the same fixture). +# (K6-K12) lane ind4b, K5 rule 2 by namesake shape (pathgaps.h NamesakeOwnerReach::admits; one helper for both hedges): +# 2b a FILE-OWNED namesake (a free definition, or a JS/TS method: no class scope) needs the caller's file to be or +# import its file — K6 the koa accessor shape and K7 the nunjucks `this.init` shape (owner in reach, no import) are +# withheld, K8 the koa near miss whose caller file requires the namesake's file keeps why=name. 2c override dispatch: +# a class-owned namesake whose class derives (by name, transitively) from the bound definition's class and whose +# file imports that class's file is a way in (K9 direct, K10 via an in-file middle class: near_sink AND bridge; K10b +# a Python src/ layout, whose import only FE-A's suffix match places); +# K11 a same-named method on an UNRELATED class (§(a) 11) and K12 a class whose parent is an EXTERNAL same-named +# class (`ext.Base`, `from ext import Base`: §(a) 2) stay withheld. +# (K13-K14) fix 1: 2c's derivation is bound per file — every hop a BaseBound::Proven implementors edge (the deriving +# class's base name bound by its own file's import of that name) — so K13 a same-named in-tree Base elsewhere, an +# external parent whose file reaches a.py transitively or directly, and an unrelated class whose file imports Base +# stay withheld (near_sink + bridge); K14 a package re-export (`from pkg.x import Base`) is a way in. +# (K13 assign/annot/tuple, K15) fix 2: a base name REBOUND at module scope after its import (`Base = Other`, `Base: type = +# Other`, `Base, spare = Other, 1`) is no binding — withheld; K15 the same assignment inside a function body rebinds no +# module name, so the override stays a way in. +# (K13 star/starfirst/starext/dupimp/clsbody/fnlocal/fnparam/fnloop, K16) fix 3: the Python rule is inverted — a base name is a +# binding only when, in the scope chain the class statement runs in, the import is its ONE binding: any other binding +# there (a star import that could bind it, a class-body or function-local rebinding, a parameter, a loop target, a +# second import) is withheld; K16 the twins whose scope chain holds only the import stay a way in. +# (K17-K18) fix 4: the file-wide import tables never decide for an import outside the class's chain (qualmod, qualfn), a `global` +# rebinding in a def at byte 0 is module scope (glob0), a match-statement capture and a PEP 695 `type` statement are bindings +# (matchas/matchbare/matchsplat/matchkw/matchdict/matchfn, typestmt); K18 qualctl and matchctl stay a way in. +# (K19-K20) fix 5: `from m import Other as Base` (memberalias), `from pkg.x import other as a` (qualalias) and `import pkg.x` (dottedroot, +# binds the root) name something else than the written name says, and a PEP 695 type parameter shadows the base (tpclass, +# tpdef): all withheld; K19 qualok now PROVES `from pkg.x import a as q` (q.Base) through the recorded member name; K20 stays. +# (K21-K22) fix 6: a package that BINDS the name (`from pkg.y import base2 as a` in pkg/x/__init__.py) is not its submodule a.py (shadowalias, +# shadowplain), and a re-export hop that RENAMES (`from pkg.x.a import Other as Base` in the package or a middle module) exports Other, +# not a.py's Base (reexpalias, reexpmod, reexpmid): all withheld; K22 re-exports under the SAME name and stays a way in. +# (K23) fix 7: a re-export hop file that rebinds the name any way but its one plain import (star, assignment, global, globals(), setattr, def ...) is withheld at every hop. +# (K24) fix 8: a chain ENDPOINT whose class is rebound later in its file (assignment, global, star import) is withheld; the def alone is the way in. # # Usage: test/pathgapcheck.sh [BIN] (BIN defaults to RIPWIRE_BIN, then build/ripwire) set -u @@ -434,6 +465,257 @@ for nm in process update; do for own in in out; do fi printf 'def target():\n return 2\n\nclass B:\n @staticmethod\n def make():\n return 0\n\n def %s(self):\n return target()\n' "$nm" > "$d/other/b.py" done; done +# K6-K8 fixtures (rule 2b, JS). kq_*: a getter/setter pair `method` in lib/req.js, lib/ctx.js's file scope calls .method('a') +# — bound by name alone to one accessor, the other is the namesake; from= kind lives in req.js, so its owner (the file) is in +# reach. kq_imp's ctx.js also requires ./req (the near miss). nj_noimp: ui/pop.js's `this.init(el)` is bound by name alone to +# ui/tip.js's prototype init; core/parser.js's class method init is the namesake; from= render lives in parser.js. +for v in noimp imp; do d="$TMP/kq_$v"; mkdir -p "$d/lib" + printf "function target() {\n return 2;\n}\nmodule.exports = {\n kind() {\n return 1;\n },\n get method() {\n return target();\n },\n set method(val) {\n this.m = val;\n }\n};\n" > "$d/lib/req.js" + req=""; [ "$v" = imp ] && req="const req = require('./req');\n" + printf "${req}function delegate(p) {\n return p;\n}\nconst proto = {};\ndelegate(proto)\n .method('a');\n" > "$d/lib/ctx.js" +done +mkdir -p "$TMP/nj_noimp/ui" "$TMP/nj_noimp/core" +printf 'function Tip(el) {\n this.el = el;\n}\nTip.prototype.init = function (el) {\n this.el = el;\n};\n' > "$TMP/nj_noimp/ui/tip.js" +printf 'function target() {\n return 2;\n}\nfunction render() {\n return 0;\n}\nclass Parser {\n init(tokens) {\n return target();\n }\n}\nmodule.exports = { Parser, render };\n' > "$TMP/nj_noimp/core/parser.js" +printf 'var Pop = function (el) {\n this.init(el);\n};\n' > "$TMP/nj_noimp/ui/pop.js" +# K10b fixture (rule 2c, Python src/ layout — textual's shape): `from tx.driver import Driver` names src/tx/driver.py, which +# the precise import adjacency's Step-A cannot place (no src/ root); FE-A's suffix match can. +mkdir -p "$TMP/c5_src/src/tx/drivers"; : > "$TMP/c5_src/src/tx/__init__.py"; : > "$TMP/c5_src/src/tx/drivers/__init__.py" +printf 'class Driver:\n def stop_mode(self):\n return 1\n' > "$TMP/c5_src/src/tx/driver.py" +printf 'from tx.driver import Driver\n\ndef target():\n return 2\n\nclass LinuxDriver(Driver):\n def stop_mode(self):\n return target()\n' > "$TMP/c5_src/src/tx/drivers/linux.py" +printf 'from tx.driver import Driver\ndef run(driver):\n return driver.stop_mode()\n' > "$TMP/c5_src/src/tx/app.py" +# K9-K12 fixtures (rule 2c, Python): run(obj) in pkg/x/c.py calls obj.render_line(), bound by name alone to the imported +# Base.render_line; other/b.py's Sub.render_line (calls target) is the namesake and nothing run reaches is owned by Sub. +for v in der der2 unrel ext extbare same exttr extdir selfnm unimp reexp assign annot tuple fnbind star starfirst starext starclean dupimp; do d="$TMP/c5_$v"; mkdir -p "$d/pkg/x" "$d/pkg/y" "$d/other" + printf 'class Base:\n def render_line(self):\n return 1\n' > "$d/pkg/x/a.py" + printf 'from pkg.x.a import Base\ndef run(obj):\n return obj.render_line()\n' > "$d/pkg/x/c.py" + case "$v" in + der) hdr='from pkg.x.a import Base\n'; cls='class Sub(Base):' ;; + der2) hdr='from pkg.x.a import Base\n'; cls='class Mid(Base):\n pass\n\nclass Sub(Mid):' ;; + unrel) hdr=''; cls='class Sub:' ;; + ext) hdr='import ext\n'; cls='class Sub(ext.Base):' ;; + extbare) hdr='from ext import Base\n'; cls='class Sub(Base):' ;; + same) hdr='from pkg.y.base2 import Base\n'; cls='class Sub(Base):'; mkdir -p "$d/pkg/y" + printf 'import pkg.x.a\n\nclass Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" ;; + exttr) hdr='import ext\nimport pkg.util\n'; cls='class Sub(ext.Base):' + printf 'import pkg.x.a\n\ndef util():\n return 4\n' > "$d/pkg/util.py" ;; + extdir) hdr='import ext\nimport pkg.x.a\n'; cls='class Sub(ext.Base):' ;; + selfnm) hdr='import ext\nimport pkg.x.a\n'; cls='class Base(ext.Base):' ;; + unimp) hdr='from pkg.x.a import Base\n'; cls='class Sub:' ;; + reexp) hdr='from pkg.x import Base\n'; cls='class Sub(Base):'; printf 'from .a import Base\n' > "$d/pkg/x/__init__.py" ;; + assign) hdr='from pkg.y.base2 import Base as Other\nfrom pkg.x.a import Base\nBase = Other\n'; cls='class Sub(Base):' + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" ;; + annot) hdr='from pkg.y.base2 import Base as Other\nfrom pkg.x.a import Base\nBase: type = Other\n'; cls='class Sub(Base):' + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" ;; + tuple) hdr='from pkg.y.base2 import Base as Other\nfrom pkg.x.a import Base\nBase, spare = Other, 1\n'; cls='class Sub(Base):' + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" ;; + fnbind) hdr='from pkg.y.base2 import Base as Other\nfrom pkg.x.a import Base\n\ndef later():\n Base = Other\n return Base\n'; cls='class Sub(Base):' + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" ;; + star) hdr='from pkg.x.a import Base\nfrom pkg.y.base2 import *\n'; cls='class Sub(Base):' + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" ;; + starfirst) hdr='from pkg.y.base2 import *\nfrom pkg.x.a import Base\n'; cls='class Sub(Base):' + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" ;; + starext) hdr='from pkg.x.a import Base\nfrom ext import *\n'; cls='class Sub(Base):' ;; + starclean) hdr='from pkg.x.a import Base\nfrom pkg.y.helpers import *\n'; cls='class Sub(Base):' + printf 'def helper():\n return 5\n' > "$d/pkg/y/helpers.py" ;; + dupimp) hdr='from pkg.x.a import Base\nfrom pkg.x.a import Base\n'; cls='class Sub(Base):' ;; + esac + printf "${hdr}\ndef target():\n return 2\n\n${cls}\n def render_line(self):\n return target()\n" > "$d/other/b.py" +done +# K16 fixtures (lane ind4b fix 3): the class statement sits INSIDE a class body or a function, so the base name is read in that scope chain +# (its own class body or function, then enclosing functions, then the module — an enclosing class body is skipped). The tail of every +# fixture is the same `class Sub(Base): def render_line` calling target(). +for v in clsbody fnlocal fnparam fnloop clsskip methcls fnimp; do d="$TMP/c5_$v"; mkdir -p "$d/pkg/x" "$d/pkg/y" "$d/other" + printf 'class Base:\n def render_line(self):\n return 1\n' > "$d/pkg/x/a.py" + printf 'from pkg.x.a import Base\ndef run(obj):\n return obj.render_line()\n' > "$d/pkg/x/c.py" + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" + imp='from pkg.y.base2 import Base as Other\nfrom pkg.x.a import Base\n\ndef target():\n return 2\n\n' + case "$v" in + clsbody) body='class Outer:\n Base = Other\n class Sub(Base):\n def render_line(self):\n return target()\n' ;; + fnlocal) body='def make():\n Base = Other\n class Sub(Base):\n def render_line(self):\n return target()\n return Sub\n' ;; + fnparam) body='def make(Base):\n class Sub(Base):\n def render_line(self):\n return target()\n return Sub\n' ;; + fnloop) body='def make():\n for Base in (Other,):\n class Sub(Base):\n def render_line(self):\n return target()\n' ;; + clsskip) body='class Outer:\n Base = Other\n class Mid:\n class Sub(Base):\n def render_line(self):\n return target()\n' ;; + methcls) body='class Outer:\n Base = Other\n def make(self):\n class Sub(Base):\n def render_line(self):\n return target()\n return Sub\n' ;; + fnimp) imp='from pkg.y.base2 import Base as Other\n\ndef target():\n return 2\n\n' + body='def make():\n from pkg.x.a import Base\n class Sub(Base):\n def render_line(self):\n return target()\n return Sub\n' ;; + esac + printf "${imp}${body}" > "$d/other/b.py" +done +# K17 fixtures (lane ind4b fix 4): the other binding forms the scope-chain rule must not miss. The tail of every fixture is the same +# `class Sub(): def render_line` calling target(); CPython (3.13) gives Sub's base for each, noted per variant. +# qualmod/qualfn: the qualifier `q` is imported in the class's chain AND (in the other form) in an unrelated function - the file-wide +# import tables hold the unrelated one; qualctl: the module-form import is the chain's, the unrelated one a `from` import (stays a way in) +# glob0/glob1: `global Base` rebinding inside `later()` - first in the file (def at byte 0), then after a comment line +# matchas/matchbare/matchsplat/matchkw/matchdict/matchfn: a match-statement capture binds Base; matchctl: it captures another name +# typestmt: `type Base = Other` (3.12): the name is a TypeAliasType +# K17 begin +for v in qualmod qualfn qualctl glob0 glob1 matchas matchbare matchsplat matchkw matchdict matchfn matchctl typestmt; do d="$TMP/c5_$v"; mkdir -p "$d/pkg/x" "$d/pkg/y" "$d/other" + printf 'class Base:\n def render_line(self):\n return 1\n' > "$d/pkg/x/a.py" + printf 'from pkg.x.a import Base\ndef run(obj):\n return obj.render_line()\n' > "$d/pkg/x/c.py" + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" + tgt='def target():\n return 2\n\n' + sub='class Sub(Base):\n def render_line(self):\n return target()\n' + glob='def later():\n global Base\n Base = Other\nfrom pkg.y.base2 import Base as Other\nfrom pkg.x.a import Base\nlater()\n\n' + pre='from pkg.y.base2 import Base as Other\nfrom pkg.x.a import Base\n' + case "$v" in + qualmod) out="from pkg.y import base2 as q\ndef g():\n import pkg.x.a as q\n return q\n\n${tgt}class Sub(q.Base):\n def render_line(self):\n return target()\n" ;; + qualfn) out="def g():\n import pkg.x.a as q\n return q\n\n${tgt}def make():\n from pkg.y import base2 as q\n class Sub(q.Base):\n def render_line(self):\n return target()\n return Sub\n" ;; + qualctl) out="import pkg.x.a as q\ndef g():\n from pkg.y import base2 as q\n return q\n\n${tgt}class Sub(q.Base):\n def render_line(self):\n return target()\n" ;; + glob0) out="${glob}${tgt}${sub}" ;; + glob1) out="# a leading comment: byte 0 is not a def\n${glob}${tgt}${sub}" ;; + matchas) out="${pre}match Other:\n case _ as Base:\n pass\n\n${tgt}${sub}" ;; + matchbare) out="${pre}match Other:\n case Base:\n pass\n\n${tgt}${sub}" ;; + matchsplat) out="${pre}match [Other]:\n case [*Base]:\n pass\n\n${tgt}${sub}" ;; + matchkw) out="${pre}match Other:\n case type(__base__=Base):\n pass\n\n${tgt}${sub}" ;; + matchdict) out="${pre}match {'k': Other}:\n case {'k': Base}:\n pass\n\n${tgt}${sub}" ;; + matchfn) out="${pre}\n${tgt}def make():\n match Other:\n case Base:\n pass\n class Sub(Base):\n def render_line(self):\n return target()\n return Sub\n" ;; + matchctl) out="${pre}match Other:\n case Other(x=1) | Other.other:\n pass\n case _ as Spare:\n pass\n\n${tgt}${sub}" ;; + typestmt) out="${pre}type Base = Other\n\n${tgt}${sub}" ;; + esac + printf "$out" > "$d/other/b.py" +done +# K17 end +# K19 fixtures (lane ind4b fix 5): the import forms whose local name is not the name the module holds, and PEP 695 type parameters. The +# tail of every fixture is `class Sub(): def render_line` calling target(); CPython (3.13) truth noted per variant. +# memberalias: `from pkg.x.a import Other as Base` - Sub's base is pkg.x.a.Other, not the Base this file also happens to hold (E1) +# qualalias: `from pkg.x import other as a` + Sub(a.Base) - a is pkg/x/other.py, not pkg/x/a.py (E2) +# dottedroot: `import pkg.x` binds the ROOT package pkg: pkg.Base is pkg/__init__.py's class, not pkg/x/__init__.py's re-export (E3) +# tpclass/tpdef: `class Sub[Base](Base)` / `def make[Base](): class Sub(Base)` - the type parameter is the name (TypeError, no base) (N8) +# Controls (stay a way in): sameas (`import Base as Base`), qualok (`from pkg.x import a as q` + q.Base is pkg/x/a.py), outform (an +# unrelated function's `import pkg.y.base2 as Base` does not touch the bare member import), dotted2 (`import pkg.x.a as q` + q.Base) +# K19 begin +for v in memberalias qualalias dottedroot tpclass tpdef sameas qualok outform dotted2; do d="$TMP/c5_$v"; mkdir -p "$d/pkg/x" "$d/pkg/y" "$d/other" + printf 'class Base:\n def render_line(self):\n return 1\n' > "$d/pkg/x/a.py" + printf 'from pkg.x.a import Base\ndef run(obj):\n return obj.render_line()\n' > "$d/pkg/x/c.py" + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" + : > "$d/pkg/__init__.py"; : > "$d/pkg/x/__init__.py"; : > "$d/pkg/y/__init__.py" + tgt='def target():\n return 2\n\n' + sub() { printf 'class Sub(%s):\n def render_line(self):\n return target()\n' "$1"; } + case "$v" in + memberalias) printf 'class Other:\n pass\n' >> "$d/pkg/x/a.py" + out="from pkg.x.a import Other as Base\n\n${tgt}$(sub Base)\n" ;; + qualalias) printf 'class Base:\n pass\n' > "$d/pkg/x/other.py" + out="from pkg.x import other as a\n\n${tgt}$(sub a.Base)\n" ;; + dottedroot) printf 'class Base:\n def other(self):\n return 4\n' > "$d/pkg/__init__.py" + printf 'from pkg.x.a import Base\n' > "$d/pkg/x/__init__.py" + out="import pkg.x\n\n${tgt}$(sub pkg.Base)\n" ;; + tpclass) out="from pkg.x.a import Base\n\n${tgt}class Sub[Base](Base):\n def render_line(self):\n return target()\n" ;; + tpdef) out="from pkg.x.a import Base\n\n${tgt}def make[Base]():\n class Sub(Base):\n def render_line(self):\n return target()\n return Sub\n" ;; + sameas) out="from pkg.x.a import Base as Base\n\n${tgt}$(sub Base)\n" ;; + qualok) out="from pkg.x import a as q\n\n${tgt}$(sub q.Base)\n" ;; + outform) out="from pkg.x.a import Base\ndef g():\n import pkg.y.base2 as Base\n return Base\n\n${tgt}$(sub Base)\n" ;; + dotted2) out="import pkg.x.a as q\n\n${tgt}$(sub q.Base)\n" ;; + esac + printf "$out" > "$d/other/b.py" +done +# K19 end +# K21 fixtures (lane ind4b fix 6): what the package a name is imported from BINDS, and the renamed import a re-export chain hops through. +# CPython (3.13) truth per variant: shadowalias/shadowplain: pkg/x/__init__.py binds `a` to pkg.y.base2, so `from pkg.x import a [as q]` is +# base2 (getattr first, the submodule only as a fallback) - F1; reexpalias/reexpmod/reexpmid: the hop file reads `from pkg.x.a import Other as +# Base`, so Sub's base is pkg.x.a.Other, not the Base a.py also holds - F2 (reexpmod: `import pkg.x as q` + q.Base; reexpmid: the hop is +# pkg/x/mid.py, not the package). Controls (stay a way in): reexpplain (the hop re-exports the SAME name), reexpmidplain (the same, through mid.py). +# K21 begin +for v in shadowalias shadowplain reexpalias reexpmod reexpmid reexpplain reexpmidplain; do d="$TMP/c6_$v"; mkdir -p "$d/pkg/x" "$d/pkg/y" "$d/other" + printf 'class Base:\n def render_line(self):\n return 1\n\nclass Other:\n pass\n' > "$d/pkg/x/a.py" + printf 'from pkg.x.a import Base\ndef run(obj):\n return obj.render_line()\n' > "$d/pkg/x/c.py" + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" + : > "$d/pkg/__init__.py"; : > "$d/pkg/x/__init__.py"; : > "$d/pkg/y/__init__.py" + tgt='def target():\n return 2\n\n' + sub() { printf 'class Sub(%s):\n def render_line(self):\n return target()\n' "$1"; } + case "$v" in + shadowalias) printf 'from pkg.y import base2 as a\n' > "$d/pkg/x/__init__.py" + out="from pkg.x import a as q\n\n${tgt}$(sub q.Base)\n" ;; + shadowplain) printf 'from pkg.y import base2 as a\n' > "$d/pkg/x/__init__.py" + out="from pkg.x import a\n\n${tgt}$(sub a.Base)\n" ;; + reexpalias) printf 'from pkg.x.a import Other as Base\n' > "$d/pkg/x/__init__.py" + out="from pkg.x import Base\n\n${tgt}$(sub Base)\n" ;; + reexpmod) printf 'from pkg.x.a import Other as Base\n' > "$d/pkg/x/__init__.py" + out="import pkg.x as q\n\n${tgt}$(sub q.Base)\n" ;; + reexpmid) printf 'from pkg.x.a import Other as Base\n' > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + reexpplain) printf 'from pkg.x.a import Base\n' > "$d/pkg/x/__init__.py" + out="from pkg.x import Base\n\n${tgt}$(sub Base)\n" ;; + reexpmidplain) printf 'from pkg.x.a import Base\n' > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + esac + printf "$out" > "$d/other/b.py" +done +# K21 end +# K23 fixtures (lane ind4b fix 7, the hop-wide rule): EVERY file a re-export chain hops through must bind the name by its one, unrenamed import. +# CPython (3.13) truth per variant: starmid/starinit/starqual/starqualmid: the hop's star import `from pkg.y.base2 import *` follows the import, so +# Base is pkg.y.base2.Base; assignmid/assigninit/assignqual/globalmid: `Base = Other` (or a `global Base; Base = Other` in a def that runs) makes it +# pkg.x.a.Other; globalsinit: `globals()['a'] = base2` in the package binds `a` to pkg.y.base2; setattrinit: setattr(sys.modules[__name__], 'a', ...) +# does the same; augmid/forinit/withinit/walrusinit/defmid/classmid: later rebinds of every other form. Controls (stay a way in): the K22 pair. +# K23 begin +for v in starmid starinit starqual starqualmid assignmid assigninit assignqual globalmid globalsinit setattrinit augmid forinit withinit walrusinit defmid classmid; do d="$TMP/c7_$v"; mkdir -p "$d/pkg/x" "$d/pkg/y" "$d/other" + printf 'class Base:\n def render_line(self):\n return 1\n\nclass Other:\n pass\n' > "$d/pkg/x/a.py" + printf 'from pkg.x.a import Base\ndef run(obj):\n return obj.render_line()\n' > "$d/pkg/x/c.py" + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" + : > "$d/pkg/__init__.py"; : > "$d/pkg/x/__init__.py"; : > "$d/pkg/y/__init__.py" + tgt='def target():\n return 2\n\n' + sub() { printf 'class Sub(%s):\n def render_line(self):\n return target()\n' "$1"; } + hop='from pkg.x.a import Base, Other\n' + case "$v" in + starmid) printf 'from pkg.x.a import Base\nfrom pkg.y.base2 import *\n' > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + starinit) printf 'from pkg.x.a import Base\nfrom pkg.y.base2 import *\n' > "$d/pkg/x/__init__.py" + out="from pkg.x import Base\n\n${tgt}$(sub Base)\n" ;; + starqual) printf 'from pkg.x.a import Base\nfrom pkg.y.base2 import *\n' > "$d/pkg/x/__init__.py" + out="import pkg.x as q\n\n${tgt}$(sub q.Base)\n" ;; + starqualmid) printf 'from pkg.x.a import Base\nfrom pkg.y.base2 import *\n' > "$d/pkg/x/mid.py" + out="import pkg.x.mid as q\n\n${tgt}$(sub q.Base)\n" ;; + assignmid) printf "${hop}Base = Other\n" > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + assigninit) printf "${hop}Base = Other\n" > "$d/pkg/x/__init__.py" + out="from pkg.x import Base\n\n${tgt}$(sub Base)\n" ;; + assignqual) printf "${hop}Base = Other\n" > "$d/pkg/x/__init__.py" + out="import pkg.x as q\n\n${tgt}$(sub q.Base)\n" ;; + globalmid) printf "${hop}def f():\n global Base\n Base = Other\nf()\n" > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + globalsinit) printf "from pkg.y import base2 as _b\nglobals()['a'] = _b\n" > "$d/pkg/x/__init__.py" + out="from pkg.x import a as q\n\n${tgt}$(sub q.Base)\n" ;; + setattrinit) printf "import sys\nfrom pkg.y import base2 as _b\nsetattr(sys.modules[__name__], 'a', _b)\n" > "$d/pkg/x/__init__.py" + out="from pkg.x import a as q\n\n${tgt}$(sub q.Base)\n" ;; + augmid) printf "${hop}Base += Other\n" > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + forinit) printf "${hop}for Base in (Other,):\n pass\n" > "$d/pkg/x/__init__.py" + out="from pkg.x import Base\n\n${tgt}$(sub Base)\n" ;; + withinit) printf "import contextlib\n${hop}with contextlib.nullcontext(Other) as Base:\n pass\n" > "$d/pkg/x/__init__.py" + out="from pkg.x import Base\n\n${tgt}$(sub Base)\n" ;; + walrusinit) printf "${hop}(Base := Other)\n" > "$d/pkg/x/__init__.py" + out="from pkg.x import Base\n\n${tgt}$(sub Base)\n" ;; + defmid) printf "from pkg.x.a import Base\ndef Base():\n pass\n" > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + classmid) printf "from pkg.x.a import Base\nclass Base:\n pass\n" > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + esac + printf "$out" > "$d/other/b.py" +done +# K23 end +# K24 fixtures (lane ind4b fix 8, the endpoint rule): the chain's ENDPOINT file holds the candidate class pkg/x/a.py:Base, which is what the importer's +# Base means only when that name has ONE module-scope binding there, its own class statement, and no star import of that file may supply it. +# CPython (3.13) truth per variant: rebdirect/rebmid: `Base = Other` after the class makes Sub's base pkg.x.a.Other; globaldirect: a `global Base; +# Base = Other` in a def that runs does the same; stardirect/starmid: a trailing `from pkg.y.base2 import *` rebinds Base, so the base is pkg.y.base2.Base. +# Controls (stay a way in): K9-K22, whose endpoints bind the name by their class alone. +# K24 begin +for v in rebdirect rebmid globaldirect stardirect starmid; do d="$TMP/c8_$v"; mkdir -p "$d/pkg/x" "$d/pkg/y" "$d/other" + printf 'class Base:\n def render_line(self):\n return 1\n\nclass Other:\n pass\n' > "$d/pkg/x/a.py" + printf 'from pkg.x.a import Base\ndef run(obj):\n return obj.render_line()\n' > "$d/pkg/x/c.py" + printf 'class Base:\n def other(self):\n return 3\n' > "$d/pkg/y/base2.py" + : > "$d/pkg/__init__.py"; : > "$d/pkg/x/__init__.py"; : > "$d/pkg/y/__init__.py" + imp='from pkg.x.a import Base\n' + case "$v" in + rebdirect|rebmid) printf '\nBase = Other\n' >> "$d/pkg/x/a.py" ;; + globaldirect) printf '\ndef f():\n global Base\n Base = Other\nf()\n' >> "$d/pkg/x/a.py" ;; + stardirect|starmid) printf '\nfrom pkg.y.base2 import *\n' >> "$d/pkg/x/a.py" ;; + esac + case "$v" in + *mid) printf 'from pkg.x.a import Base\n' > "$d/pkg/x/mid.py"; imp='from pkg.x.mid import Base\n' ;; + esac + printf "${imp}\ndef target():\n return 2\n\nclass Sub(Base):\n def render_line(self):\n return target()\n" > "$d/other/b.py" +done +# K24 end nsblock(){ grep -o ']*>.*' "$1"; } nshead(){ grep -o ']*>' "$1"; } nsrows(){ grep -o ']*/>' "$1"; } @@ -841,6 +1123,188 @@ else no "(K5x) premise: K1's answer is missing" fi +# K6-K12 (lane ind4b): K5 rule 2 by namesake shape. Exact rows: a withheld="name:1" row proves the by-name hit exists (the +# premise), so a withheld arm cannot pass because the resolver stopped making the hedged edge. +nsArm "(K6) 2b koa accessor: ctx.js never imports req.js, so the by-name .method() is withheld though req.js is in reach" "$TMP/kq_noimp" "kind,target" \ + '' \ + '' +nsArm "(K7) 2b nunjucks this.init: pop.js never imports parser.js, so Parser.init is withheld though parser.js is in reach" "$TMP/nj_noimp" "render,target" \ + '' \ + '' +nsArm "(K8) 2b near miss: ctx.js requires ./req, so the by-name .method() stays a way in" "$TMP/kq_imp" "kind,target" \ + '' \ + '' +nsArm "(K9) 2c override dispatch: Sub(Base) overrides render_line, so run's obj.render_line() may run it" "$TMP/c5_der" "run,target" \ + '' \ + '' +brArm "(K9b) 2c: the bridge crosses to the override the same way" "$TMP/c5_der" "run,target" \ + '' +nsArm "(K10) 2c transitive: Sub(Mid), Mid(Base) — the override two classes down is a way in" "$TMP/c5_der2" "run,target" \ + '' \ + '' +nsArm "(K10b) 2c src/ layout: LinuxDriver(Driver) imports src/tx/driver.py by its package name — the override is a way in" "$TMP/c5_src" "run,target" \ + '' \ + '' +nsArm "(K11) §(a) 11: render_line on an UNRELATED class Sub (no base) stays withheld" "$TMP/c5_unrel" "run,target" \ + '' \ + '' +for fx in ext extbare; do + nsArm "(K12) §(a) 2 external parent ($fx): Sub's base is an external same-named Base, so its render_line stays withheld" "$TMP/c5_$fx" "run,target" \ + '' \ + '' + brArm "(K12b) §(a) 2 external parent ($fx): no namesake bridge to it either" "$TMP/c5_$fx" "run,target" '' +done +# K13-K15 (lane ind4b fix 1): 2c binds the base NAME per file (BaseBound::Proven at every hop), not a name graph plus file +# reach. Each withheld arm's namesake file reaches pkg/x/a.py over its imports, so only the per-file binding withholds it. +for fx in same:7 exttr:8 extdir:8 selfnm:8 unimp:7 assign:9 annot:9 tuple:9 star:8 starfirst:8 starext:8 dupimp:8 clsbody:10 fnlocal:10 fnparam:9 fnloop:10; do v=${fx%%:*}; ln=${fx##*:} + case "$v" in + same) why="Sub's Base is pkg/y/base2.py's same-named class (base2.py itself imports pkg.x.a)" ;; + exttr) why="Sub(ext.Base) is external; b.py reaches pkg/x/a.py only through pkg/util.py" ;; + extdir) why="Sub(ext.Base) is external though b.py imports pkg.x.a directly" ;; + selfnm) why="class Base(ext.Base) is external though b.py imports pkg.x.a directly" ;; + unimp) why="b.py imports Base but its class Sub has no base (the derivation check alone withholds it)" ;; + assign) why="b.py imports Base, then rebinds it at module scope (Base = Other, pkg/y/base2's class): Sub derives from that" ;; + annot) why="the module-scope rebinding is annotated (Base: type = Other)" ;; + tuple) why="the module-scope rebinding is a tuple target (Base, spare = Other, 1)" ;; + star) why="a star import AFTER the explicit import (from pkg.y.base2 import *) could rebind Base to base2's class" ;; + starfirst) why="a star import BEFORE the explicit import: the name has two possible bindings, so it is undecided" ;; + starext) why="a star import from a module outside the tree (from ext import *) could bind any name" ;; + dupimp) why="the same import twice: the name has two bindings, so it is undecided" ;; + clsbody) why="class Outer: Base = Other; class Sub(Base): the base is read in Outer's class body, where Base is Other" ;; + fnlocal) why="def make(): Base = Other; class Sub(Base): the base is the function's local Base" ;; + fnparam) why="def make(Base): class Sub(Base): the base is the parameter" ;; + fnloop) why="def make(): for Base in (Other,): class Sub(Base): the base is the loop target" ;; + esac + nsArm "(K13) 2c per-file base binding ($v): $why — withheld" "$TMP/c5_$v" "run,target" \ + '' \ + "" + brArm "(K13b) 2c per-file base binding ($v): no namesake bridge to it either" "$TMP/c5_$v" "run,target" '' +done +nsArm "(K14) 2c package re-export: from pkg.x import Base binds pkg/x/__init__.py's re-export of a.Base — a way in" "$TMP/c5_reexp" "run,target" \ + '' \ + '' +brArm "(K14b) 2c package re-export: the bridge crosses to the override" "$TMP/c5_reexp" "run,target" \ + '' + +# K15 (lane ind4b fix 2): the rebinding's twin — the same names, but the assignment sits INSIDE a function body, which does not +# rebind the module's Base: Sub(Base) still derives from pkg/x/a.py's class, so the override is a way in (shown + bridge). +nsArm "(K15) 2c per-file base binding (fnbind): Base = Other inside a function body rebinds no module name — a way in" "$TMP/c5_fnbind" "run,target" \ + '' \ + '' +brArm "(K15b) 2c per-file base binding (fnbind): the bridge crosses to the override" "$TMP/c5_fnbind" "run,target" \ + '' + +# K16 (lane ind4b fix 3): the base name is read where the class statement runs. The rebinding forms above are withheld in the loop +# (clsbody, fnlocal, fnparam, fnloop, star*, dupimp). The twins below stay a way in: Python skips an enclosing class body for a +# class nested one level down or defined in a method (checked against CPython), an import inside the function that holds the class is +# the one binding, and a star import from a module that binds no Base could not rebind it. +for fx in starclean:8:4 clsskip:11:4 methcls:11:4 fnimp:9:3; do v=${fx%%:*}; rest=${fx#*:}; ln=${rest%%:*}; tl=${rest##*:} + nsArm "(K16) 2c per-file base binding ($v): Base is still pkg/x/a.py's class where Sub is defined — a way in" "$TMP/c5_$v" "run,target" \ + '' \ + "" + brArm "(K16b) 2c per-file base binding ($v): the bridge crosses to the override" "$TMP/c5_$v" "run,target" \ + "" +done + +# K17 (lane ind4b fix 4): the binding forms fix 3's scope-chain rule still missed (each withheld arm is shown + bridged at fix 3's head). +# The import tables are file-wide, so an import of the qualifier outside the class's chain must not decide (qualmod, qualfn); a `global` +# rebinding inside a def that starts at byte 0 is module scope (glob0; glob1 is the same file after a comment, withheld before too); a +# match-statement capture and a PEP 695 `type` statement bind the name. qualctl (the chain's import is the module form, the unrelated one +# a `from` import) and matchctl (a match that captures another name) stay a way in. +for fx in qualmod:10 qualfn:11 glob0:12 glob1:13 matchas:11 matchbare:11 matchsplat:11 matchkw:11 matchdict:11 matchfn:12 typestmt:9; do v=${fx%%:*}; ln=${fx##*:} + case "$v" in + qualmod) why="Sub(q.Base): q is the module-scope 'from pkg.y import base2 as q'; an unrelated function's 'import pkg.x.a as q' fills the file-wide table" ;; + qualfn) why="Sub(q.Base) inside make(), whose own 'from pkg.y import base2 as q' is the chain's import; another function's 'import pkg.x.a as q' fills the table" ;; + glob0) why="def later(): global Base; Base = Other starts the file (byte 0) and runs: Base is pkg/y/base2.py's class" ;; + glob1) why="the same file after a leading comment line (the def is no longer at byte 0)" ;; + matchas) why="match Other: case _ as Base: binds Base" ;; + matchbare) why="match Other: case Base: (a bare capture pattern) binds Base" ;; + matchsplat) why="case [*Base]: (a star capture) binds Base" ;; + matchkw) why="case type(__base__=Base): (a keyword pattern's capture) binds Base" ;; + matchdict) why="case {'k': Base}: (a mapping value capture) binds Base" ;; + matchfn) why="def make(): match Other: case Base: ... class Sub(Base): the capture is the function's local Base" ;; + typestmt) why="type Base = Other binds Base (a TypeAliasType, no derivation at all)" ;; + esac + nsArm "(K17) 2c per-scope base binding ($v): $why — withheld" "$TMP/c5_$v" "run,target" \ + '' \ + "" + brArm "(K17b) 2c per-scope base binding ($v): no namesake bridge to it either" "$TMP/c5_$v" "run,target" '' +done +for fx in qualctl:10:6 matchctl:13:9; do v=${fx%%:*}; rest=${fx#*:}; ln=${rest%%:*}; tl=${rest##*:} + nsArm "(K18) 2c per-scope base binding ($v): Base is still pkg/x/a.py's class where Sub is defined — a way in" "$TMP/c5_$v" "run,target" \ + '' \ + "" + brArm "(K18b) 2c per-scope base binding ($v): the bridge crosses to the override" "$TMP/c5_$v" "run,target" \ + "" +done + +# K19 (lane ind4b fix 5): the names an import does NOT bind, and PEP 695 type parameters (each withheld arm is shown + bridged at fix 4's head). +# `from m import Other as Base` binds the local Base to m's Other (memberalias); `from pkg.x import other as a` binds a to pkg/x/other.py +# (qualalias); `import pkg.x` binds the root pkg, not pkg.x (dottedroot); a class/def type parameter is a TypeVar, never a base (tpclass, tpdef). +# qualok (`from pkg.x import a as q`, q is pkg/x/a.py) is the one that GAINS: the alias is resolved through the recorded member name. +# sameas, outform and dotted2 stay a way in (K20): an alias equal to the name, an unrelated function's import, a dotted import with an alias. +for fx in memberalias:7 qualalias:7 dottedroot:7 tpclass:7 tpdef:8; do v=${fx%%:*}; ln=${fx##*:} + case "$v" in + memberalias) why="from pkg.x.a import Other as Base: Sub's base is pkg.x.a.Other, not the Base that file also holds" ;; + qualalias) why="from pkg.x import other as a; Sub(a.Base): a is pkg/x/other.py, not pkg/x/a.py" ;; + dottedroot) why="import pkg.x binds the root package pkg; pkg.Base is pkg/__init__.py's class" ;; + tpclass) why="class Sub[Base](Base): the type parameter is the name (CPython: TypeError, no base)" ;; + tpdef) why="def make[Base](): class Sub(Base): the function's type parameter is the name" ;; + esac + nsArm "(K19) 2c import/type-parameter binding ($v): $why — withheld" "$TMP/c5_$v" "run,target" \ + '' \ + "" + brArm "(K19b) 2c import/type-parameter binding ($v): no namesake bridge to it either" "$TMP/c5_$v" "run,target" '' +done +for fx in sameas:7:3 qualok:7:3 outform:10:6 dotted2:7:3; do v=${fx%%:*}; rest=${fx#*:}; ln=${rest%%:*}; tl=${rest##*:} + nsArm "(K20) 2c import binding ($v): Base is still pkg/x/a.py's class where Sub is defined — a way in" "$TMP/c5_$v" "run,target" \ + '' \ + "" + brArm "(K20b) 2c import binding ($v): the bridge crosses to the override" "$TMP/c5_$v" "run,target" \ + "" +done + +# K21 (lane ind4b fix 6): a name the package already binds is not its submodule, and a renamed import is not the name it is filed under. +# Every withheld arm is shown + bridged at fix 5's head (4994f5ae); the two controls re-export under the SAME name and stay a way in. +for fx in shadowalias:7 shadowplain:7 reexpalias:7 reexpmod:7 reexpmid:7; do v=${fx%%:*}; ln=${fx##*:} + case "$v" in + shadowalias) why="pkg/x/__init__.py binds a to pkg.y.base2, so from pkg.x import a as q is base2, not the submodule a.py" ;; + shadowplain) why="the same unaliased: from pkg.x import a; Sub(a.Base)" ;; + reexpalias) why="the package reads from pkg.x.a import Other as Base: the Base it exports is Other" ;; + reexpmod) why="import pkg.x as q; Sub(q.Base): the package's Base is the renamed Other" ;; + reexpmid) why="the hop is pkg/x/mid.py, which renames Other to Base" ;; + esac + nsArm "(K21) 2c package binding ($v): $why - withheld" "$TMP/c6_$v" "run,target" \ + '' \ + "" + brArm "(K21b) 2c package binding ($v): no namesake bridge to it either" "$TMP/c6_$v" "run,target" '' +done +for fx in reexpplain:7:3 reexpmidplain:7:3; do v=${fx%%:*}; rest=${fx#*:}; ln=${rest%%:*}; tl=${rest##*:} + nsArm "(K22) 2c re-export under the same name ($v): Base is still pkg/x/a.py's class - a way in" "$TMP/c6_$v" "run,target" \ + '' \ + "" + brArm "(K22b) 2c re-export under the same name ($v): the bridge crosses to the override" "$TMP/c6_$v" "run,target" \ + "" +done + +# K23 (lane ind4b fix 7): a hop file whose binding of the name is not its one unrenamed import ends the chain - at EVERY hop, whatever the shape. +# Every arm is shown + bridged at fix 6's head (a02e8439); the K22 controls above stay a way in. +for v in starmid starinit starqual starqualmid assignmid assigninit assignqual globalmid globalsinit setattrinit augmid forinit withinit walrusinit defmid classmid; do + nsArm "(K23) 2c hop file rebinds the name ($v) - withheld" "$TMP/c7_$v" "run,target" \ + '' \ + "" + brArm "(K23b) 2c hop file rebinds the name ($v): no namesake bridge to it either" "$TMP/c7_$v" "run,target" '' +done + +# K24 (lane ind4b fix 8): a chain endpoint whose class is not the name's one binding there (a later rebind of any form, a star import) is withheld. +# Every arm is shown + bridged at fix 7's head (96f6189d); the K9-K22 controls above stay a way in. +for v in rebdirect rebmid globaldirect stardirect starmid; do + nsArm "(K24) 2c endpoint class is rebound after its def ($v) - withheld" "$TMP/c8_$v" "run,target" \ + '' \ + "" + brArm "(K24b) 2c endpoint class is rebound after its def ($v): no namesake bridge to it either" "$TMP/c8_$v" "run,target" '' +done + echo "(M) MCP path_between carries the same clause" # Two MCP legend postures (lean-answers): the unchanged assertions run on --mcp-legend=inline, the pre-posture spelling # where every answer carries its root facts on ; a TWIN runs the default session posture, whose answers after the diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index 00be1e534..17d07a5c0 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -16,7 +16,7 @@ around 0 827b569b350640938954e34497f0430f757b4baac8f0daa2194306113cb46a36 e3b0c4 uses 0 5442767b638c8cac9eb0327cbfd5fde73f92fde5b85e2a995670a3982d903711 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 path 0 976a050ece8954e7e0b245df5d61b693bb10dec1f3867918ec519c72f83f4aee e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 connect 0 6ceb99d6c5ecb9f57a1b7426da2ed2118705cb7cc6b3d0aa4456b84c0ff715f4 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -grep 0 ea5c66f3062d3ba805f271d18b146ee411f3b132f75d0c77062ecf2eda13f36e e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +grep 0 5494a9c4aa9739ae3d1dfd5f7776029f049efa6ee2cb936d2ea0f0c5e8a6dfdb e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 pack_signatures 0 66de39992132b15fdc91e088e5301976e2beb972cb95802441331350983fab25 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 pack_task 0 f3de4fc3a28342cf515fedb8a02de0fea7f149ce8e948d39a7037c955a9eafd1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 arch 2 45b12ab02e3e9f2e32bf92fe0e1cef7b489e70711386e0749d50281743aeeec5 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 diff --git a/test/pyimportprecisecheck.sh b/test/pyimportprecisecheck.sh index 93fc31299..5769a8f00 100755 --- a/test/pyimportprecisecheck.sh +++ b/test/pyimportprecisecheck.sh @@ -205,7 +205,7 @@ if python3 - "$TMP/pn.fresh" "$TMP/pn.bin" "$ROOT" <<'PY' import copy, pathlib, re, struct, sys, xml.etree.ElementTree as ET from collections import Counter -# readFileRecord/writeBind in ingest_cache.h, lean cache v39 (train 33: 38 -> 39 moved the number only, above every live 38; lane l6-iface fix round 1: 37 -> 38 appended LocalBindKind::ImplBlock only; +# readFileRecord/writeBind in ingest_cache.h, lean cache v40 (train 34: 39 -> 40 appended LocalBindKind::JsRebind only, a JS/TS record no Python fixture writes; train 33: 38 -> 39 moved the number only, above every live 38; lane l6-iface fix round 1: 37 -> 38 appended LocalBindKind::ImplBlock only; # lane l6-iface: 36 -> 37 appended LocalBindKind::DispatchType only, no # record layout change; train renumbers) (train 30: 34 -> 36 moved the number only, above three lanes' # 33/33/35; name-template-dispatch: 32 -> 35 appended RefRole::Pattern = 11 only; fcall-forward: a RawRef appends the `forwarded` u8 after memberFactory; call-result-typing: a bind record gains two u8 after isFromAssignment, resultIndex and unwrapDepth, which a @@ -214,7 +214,7 @@ from collections import Counter # string searches: walk the counted arrays and require complete file coverage, clean parse health and exact EOF. blob = pathlib.Path(sys.argv[2]).read_bytes() endian = '<' if sys.byteorder == 'little' else '>' -assert blob[:4] == b'CTPK' and struct.unpack_from(endian + 'I', blob, 4)[0] == 39, 'update the cache walker for a new layout' +assert blob[:4] == b'CTPK' and struct.unpack_from(endian + 'I', blob, 4)[0] == 40, 'update the cache walker for a new layout' table, count = struct.unpack_from(endian + 'QI', blob, len(blob) - 24) assert count == 5 == struct.unpack_from(endian + 'I', blob, 21)[0], 'all five fixtures must be cached' assert table + count * 32 == len(blob) - 24, 'cache table bounds' @@ -279,9 +279,13 @@ want = { 'builtin_call.py': ([], []), 'importlib_call.py': ([include('importlib')], [bind('importlib', 'importlib', 'module')]), 'dotted.py': ([include('a.b as c')], [bind('c', 'a.b', 'module')]), + # HISTORY (lane ind4b, fix 5 -> fix 6): the Import RawBind of an ALIASED from-import used to carry importedName '' (no marker); fix 5 + # records the member's own name for it, 'from:a' (kPythonFromMemberMark + a), so `from . import a as b` + `b.Base` can be read as the + # submodule a. The change is on purpose, not erosion: the pin still asserts the exact value (a wrong or missing marker fails, see the + # wrong_marker control below), and only a plain `from m import x` or a module-form import keeps ''/'module'. # The receiver-evidence pass also writes this NameAlias. Pin all its fields and count; # its position relative to the Import bind is an implementation detail of the two passes. - 'relative.py': ([include('.')], [('b', 'a', '', 14, py, name_alias, 0, (0, 0)), bind('b', '.', '')]), + 'relative.py': ([include('.')], [('b', 'a', '', 14, py, name_alias, 0, (0, 0)), bind('b', '.', 'from:a')]), } def check(actual): assert actual.keys() == want.keys() and all( @@ -319,7 +323,7 @@ for path in ('dotted.py', 'relative.py'): else: raise AssertionError((path, channel, 'missing fact escaped the alias assertion')) print(' PASS #382: 16 corrupt-fact controls rejected by the exact Include/RawBind predicate') PY -then ok '#382: future/dynamic negatives; c -> a.b with module marker; relative Include . and b -> . without marker' +then ok '#382: future/dynamic negatives; c -> a.b with module marker; relative Include . and b -> . with the member marker from:a' else no '#382: negative/alias extraction or assertion controls failed' fi diff --git a/test/qschemetrip.hash b/test/qschemetrip.hash index ea34260e1..1db263ca9 100644 --- a/test/qschemetrip.hash +++ b/test/qschemetrip.hash @@ -1 +1 @@ -3e88ca157f2c3cda8f8af6ddc94ece9b2d534d8ec66308c0050eab4ed1d36842 +ec5455c6c9bcf87e8a617d937aa6fc94c039111cd7ae573b813e059b451ca188 diff --git a/test/qschemetripcheck.sh b/test/qschemetripcheck.sh index 96976ddfa..9afaaf79d 100755 --- a/test/qschemetripcheck.sh +++ b/test/qschemetripcheck.sh @@ -34,6 +34,12 @@ SRC="$ROOT/src/quality.h" ING="$ROOT/src/ingest_cache.h" # extraction-identity constants moved here (2026-08-29 ingest.cpp section split); the hashed CONCAT label keeps its historical spelling so the pin holds PIN="$ROOT/test/qschemetrip.hash" # RE-PIN LOG (the pin is a bare hash, so its justification has to live here). +# 2026-10-24, train 34 (lean2b, fc2-lego-extparent, honest-lex, ind4b-k5-tighten-k5c, hop1, fc3-js-rebind merged onto main +# 7abad955): RE-DERIVED ONCE with UPDATE_GOLDEN=1 on the merged tree (3e88ca157f…36842 -> ec5455c6c9…ca188). Watched text: +# kParserVer 224 -> 226 (rich file tag 227; above main / #432 224 / 225, lane ind4b's placeholder 224, lane ind5-glob-imports +# 222 / 223, lane fc3-js-rebind 218 / 219) and kCacheVersion 39 -> 40 (fc3-js-rebind's appended LocalBindKind::JsRebind; above +# every live 39), quality.h's two mirrors with them. kQSnapCacheScheme stays 22: no watched quality.h function moved (lean2b's +# staleByKindValue is new and unwatched). The ind4b and fc3-js-rebind lane pins below are superseded by this one. # 2026-10-23, train 33 (ls2-size-targets, fc1-std-qualified-name, ix5-classnew, mf1-nx, qn1-qualname, l6-iface, # changelog-fragments merged onto main 004b4c6b): RE-DERIVED ONCE with UPDATE_GOLDEN=1 on the merged tree (08e348d9d8…5b208 -> # 3e88ca157f…36842). Watched text: kParserVer 220 -> 224 (rich file tag 225; above main 220 / 221, lane ind5-glob-imports @@ -46,6 +52,13 @@ PIN="$ROOT/test/qschemetrip.hash" # lane l6-iface 212 / 213) and quality.h's kIngestParserVerMirror with it. kCacheVersion stays 36 and kQSnapCacheScheme # stays 22: IX-1 writes struct-field slots in memberPath / memberCtor of the existing value / through records. The ix1-slot # lane entry below is kept as written; its hash is superseded by this one. +# 2026-10-23, lane ind4b-k5-tighten-k5c fix round 5: RE-PINNED with UPDATE_GOLDEN=1. Fix round 4 moved kParserVer 212 -> 224 (rich file tag +# 225; quality.h's kIngestParserVerMirror with it) and left the pin; the manifest hashes that line and the comment under it, which this +# round also grew. kParserVer 224 is a PLACEHOLDER the train renumbers, and the train re-pins again then. kCacheVersion stays 36 and +# kQSnapCacheScheme stays 22: no record format and no watched quality.h function moved (importedName gains a value, same field). +# 2026-10-22, lane fc3-js-rebind (reservation 218/219; the train renumbers): RE-PINNED with UPDATE_GOLDEN=1. Watched text: +# kParserVer 212 -> 218 (LocalBindKind::JsRebind records; above #420 214 / 215 and fc1 216 / 217) and quality.h's +# kIngestParserVerMirror with it. kCacheVersion stays 36 (the kind byte is appended) and kQSnapCacheScheme stays 22. # 2026-10-21, train 32a (member-names 781a22fb, rust-receivers dd378171 merged onto main f156be85): RE-DERIVED ONCE with # UPDATE_GOLDEN=1 on the merged tree (a667ffc55e…8a05 -> c4422a58e4…2d607). Watched text: kParserVer 206 -> 212 (rich file tag # 213; above main 206 / 207, lane rust-receivers 202 / 203, ix1-slot 204 / 205, l6-iface 210 / 211; #420 reserves 214 / 215) and diff --git a/test/regression.sh b/test/regression.sh index d8406d5f5..1613f088c 100755 --- a/test/regression.sh +++ b/test/regression.sh @@ -278,7 +278,7 @@ else fi # retired: forspinecheck — the --for caller spine (Change A) was dropped by its own offline kill rule: a spine of up to 2 caller hops from the top 3 seeds named a missed rubric item on 1 of 7 in-sample rows, so the gate that pinned it went with it # retired: cacheexclkeycheck — the per-configuration auto-cache key it pinned is a registered NEGATIVE (docs/EVALS.md, "The auto-cache key ignores --exclude", RUN 2026-09-03: a 158K-file root with >= 12 gate configurations thrashed the 2 GiB sweep); the retry design keeps ONE superset blob per root and will bring its own gate -for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agentsurfacecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck astqueryregexcheck astrocheck atcheck atomscheck attribmultidefcheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck buildtypestampcheck builtinbindcheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachereservecheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck ceilingverdictcheck chacheck chaconecheck chainguardcheck chainidcheck changelogfragcheck childwalkscalecheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clojurecheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck commenttokencheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck countfloorcheck cppbenchcheck cppoperatorcheck cppqualcheck cpptmplscopecheck crashsweepcheck crawlescapecheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck danglingwarncheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck declinecheck declinedlistcheck declinedsitecheck decltodefcheck deeptailcheck defaultceilingcheck defectshapecheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck diagnoticecheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editcheckdeclcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck elixirnamearitycheck elixirsemanticcheck emitescapecheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck enumtablecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandbodyfirstcheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check extentcheck externalvetocheck falseedgecheck fcallforwardcheck fficheck fieldaffinitycheck fieldidcheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnliteralcheck fnptrcheck forautobodycheck forblowupcheck forbodyfoldcheck forbodyslotcheck forbudgetmonotoncheck forcalibfactscheck forcallerhopcheck forcompresscheck fordisclosurecheck forhdrshapecheck forheadrrcheck forlenscheck formatgatecheck formaxtokenscheck formembernamescheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forsectioncollapsecheck forsigspancheck forsoftbudgetcheck forwidencheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck gdscriptcheck genrecallcheck gitchildoptscheck gitenvhermeticcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck gokindcheck goscopecheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck hazardpatterncheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck ifacedispatchcheck impactdepthcheck impactimportcheck impactpartitioncheck impacttypecheck importcapcheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javamethodrefcheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck jsxcallcheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck langidentitycheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legendrefcheck legobundlecheck legocheck legotransitivecheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck listingpagingcheck localdeclcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck macroreparsecheck manifestcheck mapdatasectioncheck mapdiffcheck mapinventorycheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadasancheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstdiolinecapcheck mcpstrictschemacheck mcptoolprunecheck mcptoolsubsetcheck mcptranchecheck mcptwinclaimscheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck memguardcheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck noaliascheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck notesdegradecheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck osswitchcheck oswin32logiccheck outlinecheck overbudgetcommentcheck ownerhopcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck pathgapcheck pathownercheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck ppdeadrolescheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck preprocdeadscalecheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pymodulealiascheck pyshapecheck qackconcurrencycheck qackorigincheck qbaselineproducercheck qchurncheck qchurnmemocheck qddegradecheck qddialscheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qsnapproducercheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnamecheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck recentscopecheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexguardcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck rootspellingcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyargcheck rubyattrscheck rubybarecallcheck rubyclassrecvcheck rubyconstcheck rubydeclrefcheck rubydescribedclasscheck rubyinheritcheck rubymetricscheck rubyrakejbuildercheck rubyreachcheck rubyrecvcheck rubyrecvnarrowcheck rubyrequirecheck rubyrspectargetcheck rubyschemacheck rubyscopecheck rubysettercheck rubytypedrecvcheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck scroundtripcheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcheckcheck selfcontainedcheck shadowcheck shallowhistorycheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sidecarsymlinkcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck situhunkcheck situshapecheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skipclassifycheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck slotjoincheck spectimingcheck staleackcheck statgatecheck stdmembergatecheck stdqualcheck strkerncheck structlayoutcheck sublistcountcheck subscriptopcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck tempfilesymlinkcheck termmargincheck testedreachcheck testgatecheck testgatehunkcheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck traceasanlinearcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesliteralcheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do +for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agentsurfacecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck astqueryregexcheck astrocheck atcheck atomscheck attribmultidefcheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck buildtypestampcheck builtinbindcheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachereservecheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck ceilingverdictcheck chacheck chaconecheck chainguardcheck chainidcheck changelogfragcheck childwalkscalecheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clojurecheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck commenttokencheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck countfloorcheck cppbenchcheck cppoperatorcheck cppqualcheck cpptmplscopecheck crashsweepcheck crawlescapecheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck danglingwarncheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck declinecheck declinedlistcheck declinedsitecheck decltodefcheck deeptailcheck defaultceilingcheck defectshapecheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck diagnoticecheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editcheckdeclcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck elixirnamearitycheck elixirsemanticcheck emitescapecheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck enumtablecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandbodyfirstcheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check extentcheck externalvetocheck falseedgecheck fcallforwardcheck fficheck fieldaffinitycheck fieldidcheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnliteralcheck fnptrcheck forautobodycheck forblowupcheck forbodyfoldcheck forbodyslotcheck forbudgetmonotoncheck forcalibfactscheck forcallerhopcheck forcompresscheck fordisclosurecheck forhdrshapecheck forheadrrcheck forlenscheck formatgatecheck formaxtokenscheck formembernamescheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forsectioncollapsecheck forsigspancheck forsoftbudgetcheck forwidencheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck gdscriptcheck genrecallcheck gitchildoptscheck gitenvhermeticcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck gokindcheck goscopecheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck hazardpatterncheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck honestlexcheck hookcheck hop1check hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck ifacedispatchcheck impactdepthcheck impactimportcheck impactpartitioncheck impacttypecheck importcapcheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javamethodrefcheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck jsxcallcheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck langidentitycheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legendrefcheck legobundlecheck legocheck legotransitivecheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck listingpagingcheck localdeclcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck macroreparsecheck manifestcheck mapdatasectioncheck mapdiffcheck mapinventorycheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadasancheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstdiolinecapcheck mcpstrictschemacheck mcptoolprunecheck mcptoolsubsetcheck mcptranchecheck mcptwinclaimscheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck memguardcheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck noaliascheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck notesdegradecheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck osswitchcheck oswin32logiccheck outlinecheck overbudgetcommentcheck ownerhopcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck pathgapcheck pathownercheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck ppdeadrolescheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck preprocdeadscalecheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pymodulealiascheck pyshapecheck qackconcurrencycheck qackorigincheck qbaselineproducercheck qchurncheck qchurnmemocheck qddegradecheck qddialscheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qsnapproducercheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnamecheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck recentscopecheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexguardcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck rootspellingcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyargcheck rubyattrscheck rubybarecallcheck rubyclassrecvcheck rubyconstcheck rubydeclrefcheck rubydescribedclasscheck rubyinheritcheck rubymetricscheck rubyrakejbuildercheck rubyreachcheck rubyrecvcheck rubyrecvnarrowcheck rubyrequirecheck rubyrspectargetcheck rubyschemacheck rubyscopecheck rubysettercheck rubytypedrecvcheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck scroundtripcheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcheckcheck selfcontainedcheck shadowcheck shallowhistorycheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sidecarsymlinkcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck situhunkcheck situshapecheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skipclassifycheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck slotjoincheck spectimingcheck staleackcheck statgatecheck stdmembergatecheck stdqualcheck strkerncheck structlayoutcheck sublistcountcheck subscriptopcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck tempfilesymlinkcheck termmargincheck testedreachcheck testgatecheck testgatehunkcheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck traceasanlinearcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesliteralcheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do [ -f "$ROOT/test/$_g.sh" ] || continue if RIPWIRE_BIN="$BIN" bash "$ROOT/test/$_g.sh" >/dev/null 2>&1; then ok "absorb gate ($_g.sh)" diff --git a/test/staleackcheck.sh b/test/staleackcheck.sh index 3e85bba7c..07d42964d 100755 --- a/test/staleackcheck.sh +++ b/test/staleackcheck.sh @@ -124,6 +124,62 @@ fi sed 's//dev/null 2>&1; then + mcpqd(){ printf '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"quality_delta","arguments":{"path":"%s"%s}}}\n' "$REPO" "$1" \ + | env -u TMPDIR XDG_CACHE_HOME="$XDG" "$BIN" --mcp 2>/dev/null \ + | python3 -c 'import sys,json +for line in sys.stdin: + try: d=json.loads(line) + except Exception: continue + c=(d.get("result") or {}).get("content") + if c: sys.stdout.write(c[0].get("text","")); break + if d.get("error"): sys.stdout.write("__ERROR__"+json.dumps(d["error"])); break'; } + mcpfold(){ # LABEL EXPECT(fold|full) CLI_COLLAPSED CLI_SHOWSTALE + mcpqd '' >"$TMP/mcp_qd_def"; mcpqd ',"show_stale":true' >"$TMP/mcp_qd_full" + if python3 - "$TMP/mcp_qd_def" "$TMP/mcp_qd_full" "$3" "$4" "$2" <<'PY' +import json, re, sys +dT, fT = ( open( p, encoding = "utf-8" ).read() for p in sys.argv[ 1:3 ] ) +cliC, cliF = ( open( p ).read() for p in sys.argv[ 3:5 ] ) +want = sys.argv[ 5 ] +f = json.loads( fT ) +stale = int( re.search( r']*\bstale="(\d+)"', cliC ).group( 1 ) ) +kinds = re.search( r']*\bstale_by_kind="([^"]*)"', cliC ).group( 1 ) +cliKeys = sorted( re.findall( r' 0 and f.get( "regressions" ) == 0, "show_stale=true: stale == the CLI's (%r vs %r), no regression" % ( f.get( "stale" ), stale ) ), + ( sorted( a[ "key" ] for a in f.get( "sa", [] ) ) == cliKeys, "show_stale=true lists the CLI --show-stale rows by key (%d)" % len( cliKeys ) ), + ( "stale_by_kind" not in f and "stale_next" not in f, "show_stale=true carries no fold key" ), + ( pays == ( want == "fold" ), "the fixture's fold decision is the expected one (%s, pays=%s)" % ( want, pays ) ), + ( dT == ( folded if pays else fT ), "the default answer is exactly the predicted %s document" % ( "folded" if pays else "full" ) ) ): + if not cond: print( " " + msg ); ok = False +sys.exit( 0 if ok else 1 ) +PY + then ok "$1"; else no "$1"; fi + } + mcpfold "(2m) MCP quality_delta, 2 stale rows: the fold does not pay, the default is the show_stale=true document byte for byte" full "$TMP/targetgone_c" "$TMP/targetgone" + # twelve more acked symbols, then deleted: 14 stale rows — the fold pays + { cat "$REPO/src/lib.cpp"; for i in 1 2 3 4 5 6 7 8 9 10 11 12; do echo "int gone$i( int a ) { return a * $i; }"; done; } >"$TMP/lib_more.cpp" + cp "$ACKS" "$TMP/acks_saved" # restored below: the arms after this one read the two-row ledger + cp "$TMP/lib_more.cpp" "$REPO/src/lib.cpp" + run --quality-delta --quality-ack="fixture: twelve uncalled helpers" >/dev/null 2>&1 + printf 'int stable( int a ) { return a + 1; }\n' >"$REPO/src/lib.cpp" + run --quality-delta --show-stale >"$TMP/tg14" 2>/dev/null; run --quality-delta >"$TMP/tg14_c" 2>/dev/null + mcpfold "(2m) MCP quality_delta, more stale rows: the default folds to stale_by_kind + stale_next=show_stale=true, counts kept" fold "$TMP/tg14_c" "$TMP/tg14" + cp "$TMP/acks_saved" "$ACKS" +else + printf ' SKIP (2m) MCP fold (no python3)\n' +fi run --show-stale >/dev/null 2>&1 && no "(2c) --show-stale alone was accepted" || ok "(2c) --show-stale alone is refused (it modifies --quality-delta)" # ── (3) FINDING-GONE: knotty() survives (same canonId) but is simplified back under the complexity bar ──