From f9cdb78e0467306d6af1f101fa6b23a2cec4e244 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:22:35 -0400 Subject: [PATCH 01/95] fn-slot part 2 S0: slot language rows carry the call side and the type key (zero-byte refactor) model.h kVrSlotLangs: the byFieldName bool becomes VrSlotCallSide {ThroughRows (C family), MemberCallByName (Go)} and each row names its VrSlotTypeKey {TypedefFacts (C family), PackageDir (Go)}; vrSlotLang() / slotTypeKey() / slotKeysOwnTypes() read the table. SlotIndex::goTypeKeyOf -> typeKeyOf; the store end, the struct registry and --for's struct rows ask slotKeysOwnTypes / slotTypeKey instead of the Go call-side bool. Proof: S1 163 rows byte-identical vs the lane base binary (built_from=f2e6ef1f3); fnslotcheck PASS. Co-Authored-By: Claude Opus 5.5 --- src/forslots.h | 2 +- src/ingest_valuerefs.h | 10 ++++---- src/model.h | 56 +++++++++++++++++++++++++++++++----------- src/valuerefindex.h | 23 ++++++++--------- 4 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/forslots.h b/src/forslots.h index 160300034..11e75fdfa 100644 --- a/src/forslots.h +++ b/src/forslots.h @@ -413,7 +413,7 @@ inline std::size_t spliceSlotJoinRows( std::string& sigs, const IngestResult& in else { std::vector>> fields; // field → its value refs, first-seen order - const std::string typeKey = slotJoinsByFieldName( s.lang ) ? vri.slots().goTypeKeyOf( s ) : s.name; // fix1 F-2: the ONE key + const std::string typeKey = slotKeysOwnTypes( s.lang ) ? vri.slots().typeKeyOf( s ) : s.name; // fix1 F-2: the ONE key for( const auto& [ ref, field ] : vri.slots().valuesOfType( typeKey ) ) { auto it = std::ranges::find( fields, field, &std::pair>::first ); diff --git a/src/ingest_valuerefs.h b/src/ingest_valuerefs.h index f28184917..b8e6521c4 100644 --- a/src/ingest_valuerefs.h +++ b/src/ingest_valuerefs.h @@ -190,7 +190,7 @@ inline constexpr VrPatternLookup kVrPatternLookups[] = { // with the join). C has no member functions, so EVERY field call `x->f( … )` calls through a function-pointer field (or a macro // the grammar reads as one). A C++ field call is mostly a member-function call: there the receiver must be declared with the // `struct` / `union` keyword, or be declared with a struct type whose body in this same file declares the callee as a -// function-pointer field. Go (byFieldName): this walk records only the STORE side (a keyed composite-literal field, a field +// function-pointer field. Go (callSide MemberCallByName): this walk records only the STORE side (a keyed composite-literal field, a field // assignment, a parameter stored into one); the call side is the ordinary member call, joined by field name in valuerefindex.h. // A template's literal side must hold this many identifier characters to be matched (`f"_{m}"` is too wide). @@ -531,9 +531,9 @@ class ValueRefWalk m_fArguments = fieldIdFor( tl, NodeField::Arguments ); m_fSource = ts_language_field_id_for_name( tl, "source", 6 ); m_fReceiver = ts_language_field_id_for_name( tl, "receiver", 8 ); - const VrSlotLang* slotRow = findByField( kVrSlotLangs, &VrSlotLang::lang, m_lang ); - m_slot = slotRow != nullptr && !slotRow->byFieldName ? slotRow : nullptr; - m_slotByName = slotRow != nullptr && slotRow->byFieldName; + const VrSlotLang* slotRow = vrSlotLang( m_lang ); + m_slot = slotRow != nullptr && slotRow->callSide == VrSlotCallSide::ThroughRows ? slotRow : nullptr; + m_slotByName = slotRow != nullptr && slotRow->callSide == VrSlotCallSide::MemberCallByName; m_fwd = callForwardersOf( m_lang ); m_fwdArmed = !m_fwd.empty(); @@ -3098,7 +3098,7 @@ class ValueRefWalk VrFam m_fam; Lang m_lang; const VrSlotLang* m_slot = nullptr; // IX-1: this language's slot row (nullptr: no slot capture) - bool m_slotByName = false; // IX1-V2: a byFieldName row (Go): the store side only (goValueSlot) + bool m_slotByName = false; // IX1-V2: a MemberCallByName row (Go): the store side only (goValueSlot) std::vector m_structs; // IX-1: this file's struct bodies so far std::vector> m_callableFields; // IX-1: (struct, function-pointer field) this file declares VrAliases m_aliases; // IX-1: this file's typedef / using alias → struct type key diff --git a/src/model.h b/src/model.h index 4a8be7863..3b2793f84 100644 --- a/src/model.h +++ b/src/model.h @@ -579,34 +579,60 @@ inline constexpr char kValueRefSlotFactScope = 't'; // that function at that position (`Reg( Put )`) into the same field. Distinct from every other scope char. inline constexpr char kValueRefSlotParamScope = 'q'; -// IX-1 / IX1-V2: which languages record a slot join, and how its CALL side is found — the one table both the extractor -// (ingest_valuerefs.h) and the join (valuerefindex.h) read. byFieldName: a member call `x.F( … )` (an ordinary Call reference -// with memberCall) is a call through field F when some function value is stored in a field F, joined at index time by the -// field name (the receiver's type is not read: the name tier, via="slot-name"); otherwise the extractor records the field -// calls itself (C family: kValueRefSlotCallScope rows, everyFieldCall = every `x->f( … )` in C, the C++ gate otherwise). +// IX-1 / IX1-V2: which languages record a slot join, and how — the one table both the extractor (ingest_valuerefs.h) and the +// join (valuerefindex.h) read. A language's row supplies the per-language pieces of the language-blind join (SlotIndex): +// callSide — how a call THROUGH a field is found: +// ThroughRows: the extractor records the field calls itself (C family: kValueRefSlotCallScope rows; everyFieldCall = every +// `x->f( … )` in C, the C++ gate otherwise); +// MemberCallByName: an ordinary member call `x.F( … )` (a Call reference with memberCall) is a call through field F when some +// function value is stored in a field F, joined at index time by the field name (the receiver's type is not read: the name +// tier, via="slot-name"); the extractor records only the STORE side (Go). +// typeKey — how a slot's declared struct type is keyed (both sides and --for's struct rows key the same way): +// TypedefFacts: the type as written, through the typedef / field-type slot facts (C family); +// PackageDir: the package DIRECTORY the type is declared in, plus its name (Go: valuerefindex.h slot_join::GoTypeKeys). +enum class VrSlotCallSide : std::uint8_t { ThroughRows, MemberCallByName }; +enum class VrSlotTypeKey : std::uint8_t { TypedefFacts, PackageDir }; struct VrSlotLang { - Lang lang; - bool everyFieldCall; - bool byFieldName; + Lang lang; + bool everyFieldCall; + VrSlotCallSide callSide; + VrSlotTypeKey typeKey; }; inline constexpr VrSlotLang kVrSlotLangs[] = { - { Lang::C, true, false }, - { Lang::Cpp, false, false }, - { Lang::Go, false, true }, + { Lang::C, true, VrSlotCallSide::ThroughRows, VrSlotTypeKey::TypedefFacts }, + { Lang::Cpp, false, VrSlotCallSide::ThroughRows, VrSlotTypeKey::TypedefFacts }, + { Lang::Go, false, VrSlotCallSide::MemberCallByName, VrSlotTypeKey::PackageDir }, }; -inline constexpr std::array kSlotByFieldNameOfLang = [] +inline constexpr std::array kVrSlotLangOf = [] { - std::array byLang {}; + std::array byLang {}; for( const VrSlotLang& row : kVrSlotLangs ) { - byLang[ static_cast( row.lang ) ] = row.byFieldName; + byLang[ static_cast( row.lang ) ] = &row; } return byLang; }(); +// The slot row of language `l`, nullptr when it records no slot join. +inline const VrSlotLang* vrSlotLang( Lang l ) noexcept +{ + return enumTableAt( kVrSlotLangOf, l, static_cast( nullptr ) ); +} inline bool slotJoinsByFieldName( Lang l ) noexcept { - return enumTableAt( kSlotByFieldNameOfLang, l, false ); + const VrSlotLang* row = vrSlotLang( l ); + return row != nullptr && row->callSide == VrSlotCallSide::MemberCallByName; +} +// How language `l` keys a slot type (TypedefFacts for a language with no slot row: it keys nothing of its own). +inline VrSlotTypeKey slotTypeKey( Lang l ) noexcept +{ + const VrSlotLang* row = vrSlotLang( l ); + return row == nullptr ? VrSlotTypeKey::TypedefFacts : row->typeKey; +} +// A language whose slot types key by its own rule (valuerefindex.h slot_join), not through the C family's typedef facts. +inline bool slotKeysOwnTypes( Lang l ) noexcept +{ + return slotTypeKey( l ) != VrSlotTypeKey::TypedefFacts; } // A slot type key written with the `struct` / `union` keyword (`struct thandle`) leads with this mark: a C tag is no typedef // name, so the join never reads it through an alias. A slot fact whose composeRel is kValueRefSlotBodyRel declares that its diff --git a/src/valuerefindex.h b/src/valuerefindex.h index 9ebbaea05..153539f5a 100644 --- a/src/valuerefindex.h +++ b/src/valuerefindex.h @@ -69,13 +69,13 @@ inline bool vrBindLess( const IngestResult& ing, const Reference& a, const Refer return a.startByte < b.startByte; } -// ── IX1-V2: the byFieldName languages' side of the slot join (model.h kVrSlotLangs) ────────────────────────────────── +// ── IX1-V2: the per-language pieces of the slot join (model.h kVrSlotLangs) ────────────────────────────────────── namespace slot_join { -// fix1 F-2: a byFieldName (Go) slot type's key — the package DIRECTORY the type is declared in (a Go package is a directory: +// fix1 F-2: a PackageDir (Go) slot type's key — the package DIRECTORY the type is declared in (a Go package is a directory: // x/types.Cmd and y/types.Cmd are two types though both packages are named `types`), as the first file of that directory, and the // type name: jsImportKey( dirFile, name ). keyOfSymbol is the ONE spelling the store side (SlotIndex) and --for's struct rows -// (forslots.h, through SlotIndex::goTypeKeyOf) both key by. A store's type (Reference::memberCtor, keyOf): `T` → the writing +// (forslots.h, through SlotIndex::typeKeyOf) both key by. A store's type (Reference::memberCtor, keyOf): `T` → the writing // file's directory; `pkg.T` → the directory of the file's import bound to `pkg`, when exactly one in-tree Go directory is that // import path's tail, else "" (unknown: the name tier — an out-of-tree package, an unmatched or ambiguous path). fix1 F-5: an // in-tree type that is no STRUCT (`type Table map[Op]func()`, an alias) keys kNoSlot: its keyed literal names map keys, not fields. @@ -106,7 +106,7 @@ class GoTypeKeys } for( const Symbol& sym : ing.symbols ) { - if( sym.kind == SymKind::Struct && slotJoinsByFieldName( sym.lang ) ) + if( sym.kind == SymKind::Struct && slotTypeKey( sym.lang ) == VrSlotTypeKey::PackageDir ) { m_structs.try_emplace( keyOfSymbol( sym ), char( 1 ) ); } @@ -157,7 +157,7 @@ class GoTypeKeys HashMap m_structs; // every in-tree Go struct's key HashMap m_dirOf; // import path → its directory's first file (kNoFile none) }; -// The member calls `x.F( … )` of a byFieldName language whose field F holds a stored function somewhere (`holds`), as calls +// The member calls `x.F( … )` of a MemberCallByName language whose field F holds a stored function somewhere (`holds`), as calls // through that slot, in reference order. Not when that language defines a METHOD named F anywhere in the tree: the call may // then be that method, and the receiver's type is not read here (a named floor, never a guess). template @@ -331,7 +331,7 @@ class SlotIndex } m_valuesByField[ m_values.back().field ].push_back( static_cast( m_values.size() - 1 ) ); } - slot_join::addCalls( ing, slot_join::memberCalls( ing, m_valuesByField ), m_calls, m_callsBySym ); // IX1-V2: a byFieldName slot's calls + slot_join::addCalls( ing, slot_join::memberCalls( ing, m_valuesByField ), m_calls, m_callsBySym ); // IX1-V2: a MemberCallByName slot's calls if( m_calls.empty() ) { return; // no call through a struct field anywhere: no value needs a slot key @@ -348,8 +348,9 @@ class SlotIndex std::vector callsFor( std::uint32_t valueIdx ) const { return join( m_values, valueIdx, m_callsByField, m_callEnds ); } // The slot value references (with a resolved function) the slot call `callIdx` may call through. std::vector valuesFor( std::uint32_t callIdx ) const { return join( m_callEnds, callIdx, m_valuesByField, m_values ); } - // fix1 F-2: a byFieldName struct symbol's type key, as valuesOfType reads it (the store side keys the same way) - std::string goTypeKeyOf( const Symbol& s ) const { return m_goKeys.keyOfSymbol( s ); } + // fix1 F-2: the slot type key of a struct symbol of a language that keys its own types (model.h slotKeysOwnTypes), as + // valuesOfType reads it (the store side keys the same way) + std::string typeKeyOf( const Symbol& s ) const { return m_goKeys.keyOfSymbol( s ); } // IX1-V2: the slot value references stored into a field of struct type `type` (the resolved type name), with the field, // in reference order — what --for's slot_fields= lists on a struct row. std::vector> valuesOfType( std::string_view type ) const @@ -469,10 +470,10 @@ class SlotIndex End e; e.ref = refIdx; e.fam = valueRefFamily( r.lang ); - if( r.role == RefRole::Call || slotJoinsByFieldName( r.lang ) ) + if( r.role == RefRole::Call || slotKeysOwnTypes( r.lang ) ) { - // IX1-V2: a member call `x.F( … )` (slot_join::memberCalls, its receiver's type unread: the name tier), or a byFieldName - // store (package-qualified; no typedef aliases or field-type facts on this side) + // IX1-V2: a member call `x.F( … )` (slot_join::memberCalls, its receiver's type unread: the name tier), or a store of a + // language that keys its own types (Go: package-qualified; no typedef aliases or field-type facts on this side) e.type = r.role == RefRole::Call ? std::string() : m_goKeys.keyOf( r ); e.field = r.role == RefRole::Call ? r.calleeName : r.memberPath; return e; From 8089ced76054a1f12fb20ae8ba71c4e51c79e590 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:51:36 -0400 Subject: [PATCH 02/95] fn-slot part 2 S1: Rust fn-valued struct fields join the (x.f)( ... ) calls through them Rust row of kVrSlotLangs (callSide ParenFieldCall, typeKey ModuleType, pathSep "::") and ValueRefFamily::Rust. ingest_rustslots.h records only what the slot join reads: function-path arguments of path calls (scope 'a', recvVar = "Owner::name", Self resolved), struct-expression / Box::new / field-assignment stores, registrar parameter facts ('q'), and `(x.f)( ... )` slot calls ('s', typed for self.f). One cursor walk per file; bare identifiers are decided at the function's end against every name its patterns bind. valuerefindex.h: slot_join::TypeKeys dispatches Go (PackageDir) and Rust (RustTypeKeys: the nearest in-tree struct of that name: file, else crate, else repo-unique); ownedBy = the per-language method-value owner rule; resolveCallee resolves a path callee through its owner (never the bare last segment); a Rust bare name in another file resolves to its crate's one, else the repo's only one; narrowFor keys registrars by the callee leaf. kParserVer 247 -> 249 (placeholder; train renumbers). Gate: fnslotcheck R1-R6, N8-N12, X2, F3 (13 RED on f2e6ef1f3). Co-Authored-By: Claude Opus 5.5 --- changelog.d/rust-fn-valued-slots.md | 14 + src/forslots.h | 2 +- src/ingest.cpp | 1 + src/ingest_cache.h | 5 +- src/ingest_rustslots.h | 605 +++++++++++++++++++++++++ src/ingest_valuerefs.h | 91 ++-- src/model.h | 39 +- src/quality.h | 2 +- src/valuerefindex.h | 228 ++++++++-- test/fnslotcheck.sh | 52 +++ test/fnslotrust/Cargo.toml | 4 + test/fnslotrust/crates/twin/src/lib.rs | 78 ++++ test/fnslotrust/gox/g.go | 11 + test/fnslotrust/src/engine/closure.rs | 11 + test/fnslotrust/src/engine/eval.rs | 32 ++ test/fnslotrust/src/main.rs | 5 + test/fnslotrust/src/other/hooks.rs | 27 ++ 17 files changed, 1123 insertions(+), 84 deletions(-) create mode 100644 changelog.d/rust-fn-valued-slots.md create mode 100644 src/ingest_rustslots.h create mode 100644 test/fnslotrust/Cargo.toml create mode 100644 test/fnslotrust/crates/twin/src/lib.rs create mode 100644 test/fnslotrust/gox/g.go create mode 100644 test/fnslotrust/src/engine/closure.rs create mode 100644 test/fnslotrust/src/engine/eval.rs create mode 100644 test/fnslotrust/src/main.rs create mode 100644 test/fnslotrust/src/other/hooks.rs diff --git a/changelog.d/rust-fn-valued-slots.md b/changelog.d/rust-fn-valued-slots.md new file mode 100644 index 000000000..6f812a4d0 --- /dev/null +++ b/changelog.d/rust-fn-valued-slots.md @@ -0,0 +1,14 @@ +### Added — Rust functions stored in struct fields are joined to the `(x.f)( … )` calls through those fields (`--callers`, `--callees`, `--for`) + +A Rust function stored in a struct field — a struct expression (`CallEval { eval: direct_eval }`, `Self { eval }`), a value +wrapped in `Box::new` / `Rc::new` / `Arc::new`, a field assignment (`self.h = h`), or an argument of a registrar reached by a +path call that stores its parameter there (`CallEval::new( stack, eval_block_with_early_return:: )` with `Self { …, eval }` +in its body; a method's `self` is position 0) — is now joined to the calls through that field, `(self.eval)( … )` (the only +spelling Rust has for calling a function-valued field). `--callers` gains `called_by= called_at= via= cands=K` on a `` +row, `--callees` lists ``, and `--for` prints `slot_by=` / +`slot_fields="F=K@C"` on shown rows. A slot type is the in-tree struct its name spells (its own file's, else its crate's, else +the repo's only one; `Self` is the impl type), so two crates' same-named structs are two slots; a `self.f` call is typed +(`via="slot"`), a call or store through another receiver joins by the field name only (`via="slot-name"`, `~` on `--for`). Not +joined (floors): a closure stored in a field, a method-call registrar (`r.set( f )`), a value inside a macro, an enum variant's +field. Every row is a MAY-call clue, never a proven call and never in `count=`. Measured on nushell (`CallEval.eval`, called +by `CallEval::run` / `run_prebound`). Gate: `test/fnslotcheck.sh` (R*/N8-N12/X2/F3 arms). diff --git a/src/forslots.h b/src/forslots.h index 11e75fdfa..ab77582fc 100644 --- a/src/forslots.h +++ b/src/forslots.h @@ -413,7 +413,7 @@ inline std::size_t spliceSlotJoinRows( std::string& sigs, const IngestResult& in else { std::vector>> fields; // field → its value refs, first-seen order - const std::string typeKey = slotKeysOwnTypes( s.lang ) ? vri.slots().typeKeyOf( s ) : s.name; // fix1 F-2: the ONE key + const std::string typeKey = slotKeysOwnTypes( s.lang ) ? vri.slots().typeKeyOf( id ) : s.name; // fix1 F-2: the ONE key for( const auto& [ ref, field ] : vri.slots().valuesOfType( typeKey ) ) { auto it = std::ranges::find( fields, field, &std::pair>::first ); diff --git a/src/ingest.cpp b/src/ingest.cpp index e696f1972..3fd6ce625 100644 --- a/src/ingest.cpp +++ b/src/ingest.cpp @@ -203,6 +203,7 @@ extern "C" #include "ingest_ruby.h" // Ruby Rails/DSL capture lane (schema columns, Jbuilder typing, factories, class-side DSL walks) - carved verbatim 2026-10-12 #include "ingest_importcap.h" #include "ingest_valuerefs.h" +#include "ingest_rustslots.h" // FN-SLOT part 2: Rust's slot-join records (callSide ParenFieldCall) #include "ingest_sidecap.h" #include "ingest_prewarm.h" #include "ingest_parsepool.h" diff --git a/src/ingest_cache.h b/src/ingest_cache.h index c8c358fd8..def86f58b 100644 --- a/src/ingest_cache.h +++ b/src/ingest_cache.h @@ -361,7 +361,10 @@ constexpr std::uint32_t kCacheVersion = 44; // 44: train 37b — rust- // (Py `pkg.mod`, TS `./x`, Rust `crate::a::b`/`mod:x`) — // a target FORMAT change → old caches must be rejected. // 4: Include gained a `bool isAngle` (quote/angle) field -constexpr std::uint32_t kParserVer = 247; // bump on any grammar/.scm/extraction change +constexpr std::uint32_t kParserVer = 249; // bump on any grammar/.scm/extraction change + // 249 = 2026-10-27 (lane fn-slot-2, placeholder: train 41 renumbers): Rust files record the + // slot join's records (ingest_rustslots.h: function-path arguments of path calls, field stores, + // registrar parameter facts, `(x.f)( … )` slot calls); no record format changed. // 247 = 2026-10-27 (lane fn-slot, IX1-V2, placeholder: train 40 renumbers): Go value refs record the // struct-field slot of a keyed composite-literal element / field assignment (memberPath/memberCtor), // a method value `b.m` of the method's own receiver (memberRoot = its type) and a parameter stored diff --git a/src/ingest_rustslots.h b/src/ingest_rustslots.h new file mode 100644 index 000000000..955463c78 --- /dev/null +++ b/src/ingest_rustslots.h @@ -0,0 +1,605 @@ +#pragma once +#if !defined( RIPWIRE_INGEST_TU ) +#error "ingest_rustslots.h is a SECTION of src/ingest.cpp's translation unit - include it only from ingest.cpp" +#endif + +// ingest_rustslots.h — FN-SLOT part 2 (Rust): the language-specific STORE and CALL records of the slot join, read by the one +// language-blind join (valuerefindex.h SlotIndex). Rust's row in model.h kVrSlotLangs is callSide ParenFieldCall, typeKey +// ModuleType. This walk records ONLY what that join reads — it is not the general value-reference capture of the C, JS, Python +// and Go families (ingest_valuerefs.h): no parameter call, container, return or local value is recorded for Rust. +// +// RECORDS (the shapes model.h RefRole::Value / Through document; ingest_valuerefs.h RECORD SHAPE): +// * a function PATH as an argument of a call whose callee is a path (`CallEval::new( 1, eval_block )`, `reg( Editor::backspace )`, +// `f::` with its turbofish dropped): Value, scope 'a', recvVar = the callee as written with `Self` resolved to the impl +// type ("CallEval::new"), argCount = the argument's position (a `self` parameter is position 0 of a path call), name = the +// function's last segment, memberRoot = its owner TYPE when the path names one (`Editor` in `Editor::backspace`; a module +// path `eval::eval_block` names no owner). A method call's arguments (`cx.listener( Self::f )`) are a floor: no row. +// * a function path stored into a struct FIELD — a struct expression `T { f: path }` / `T { path }` (shorthand), the value +// unwrapped from `Box::new( … )` / `Rc::new( … )` / `Arc::new( … )`, or an assignment `self.f = path` / `x.f = path`: +// Value, scope 'x', memberPath = the field, memberCtor = the struct type as written, its last segment when a module path +// leads it (`Self` → the impl type; `x.f`: "" — x's type is not read, the name tier). +// * a PARAMETER stored the same way (`fn new( eval: EvalFn ) -> Self { Self { eval } }`): the slot fact of a registrar +// (model.h kValueRefSlotParamScope), recvVar = the function's name, argCount = the parameter's position. +// * a call through a field `(self.eval)( … )` (Rust calls a function-valued field only so: `x.f( … )` is always a method +// call): Through, scope kValueRefSlotCallScope, name = the root, memberPath = the last field, memberCtor = the impl type +// when the root is `self` and the chain is one field, else "" (the name tier). +// SHADOWING: an identifier bound anywhere in the enclosing function — a parameter, a `let`, closure, `for`, `match` or +// `if let` pattern — is never a function value (the whole function is one scope here: a conservative floor). An identifier +// that starts upper-case is a constant, a unit struct or a variant (`map( Some )`), never a function: no row. +// FLOORS: closures and method-call arguments are no rows; a macro body is opaque; nodes deeper than kVrMaxDepth are not +// visited — disclosed by the file's depth-cut record, as the general walk does. + +namespace rw +{ + +namespace +{ + +class RustSlotWalk +{ +public: + RustSlotWalk( Lang lang, std::uint32_t fileId, std::string_view src, std::vector& out ) noexcept + : m_lang( lang ), m_fileId( fileId ), m_src( src ), m_out( out ) + { + } + // ONE cursor walk of the file (no cursor per node, no second walk of a function body): a function's rows that name a bare + // identifier wait in its Fn until the function ends, when every name its patterns bind is known (finishFn). + void run( TSNode root ) + { + const TSLanguage* tl = ts_node_language( root ); + for( std::size_t k = 0; k < kKinds.size(); ++k ) + { + m_sym[k] = ts_language_symbol_for_name( tl, kKinds[k].data(), static_cast( kKinds[k].size() ), true ); + } + const std::size_t first = m_out.size(); + ChildCursor walker( root ); // RAII: the walk's one cursor, released on every exit (hazardpatterncheck E) + TSTreeCursor& cur = walker.cur; + std::uint32_t depth = 0; + for( ;; ) + { + const TSNode n = ts_tree_cursor_current_node( &cur ); + const bool descend = depth + 1 < kVrMaxDepth; + if( enter( n ) && descend && ts_tree_cursor_goto_first_child( &cur ) ) + { + ++depth; + continue; + } + if( !descend && ts_node_child_count( n ) > 0 && !opaque( n ) ) + { + noteVrDepthCut( m_depthCut, n ); + } + leave( n ); + bool done = false; + while( !ts_tree_cursor_goto_next_sibling( &cur ) ) + { + if( depth == 0 || !ts_tree_cursor_goto_parent( &cur ) ) + { + done = true; + break; + } + --depth; + leave( ts_tree_cursor_current_node( &cur ) ); + } + if( done ) + { + break; + } + } + while( !m_fns.empty() ) + { + finishFn(); // a cut walk never left them + } + std::stable_sort( m_out.begin() + static_cast( first ), m_out.end(), + []( const RawRef& a, const RawRef& b ) { return a.startByte < b.startByte; } ); // source order, as the other walks emit + if( m_depthCut.subtrees > 0 ) + { + m_out.push_back( vrDepthCutRecord( m_fileId, m_lang, m_depthCut ) ); + } + } + +private: + enum Kind : std::uint8_t { Impl, FnItem, Call, StructExpr, Assign, Let, For, MatchArm, LetCond, ClosureParams, + Macro, Use, Attr, InnerAttr, LineComment, BlockComment, Str, RawStr, TypeArgs, TypeParams, Where, KindCount }; + static constexpr std::array kKinds = { "impl_item", "function_item", "call_expression", "struct_expression", + "assignment_expression", "let_declaration", "for_expression", + "match_arm", "let_condition", "closure_parameters", + "macro_invocation", "use_declaration", "attribute_item", "inner_attribute_item", + "line_comment", "block_comment", "string_literal", "raw_string_literal", + "type_arguments", "type_parameters", "where_clause" }; + // A subtree no record can come from: a macro's token trees (opaque: a floor), a `use`, an attribute, a comment, a string, a + // type's arguments or bounds. The walk does not enter it (no depth cut either: nothing below it is ever read). + bool opaque( TSNode n ) const noexcept + { + const TSSymbol sy = ts_node_symbol( n ); + for( std::size_t k = Macro; k < KindCount; ++k ) + { + if( sy == m_sym[k] ) + { + return true; + } + } + return false; + } + // A row that names a bare identifier, decided when its function ends: a name the function binds is no function value — a + // store of a parameter bound once becomes the registrar's fact (paramFact), anything else bound is dropped. + struct Pending + { + RawRef r; + std::string_view id; + bool store = false; + }; + struct Fn + { + TSNode node {}; + std::string_view name; + std::vector params; // in position order; a `self` parameter is "self" + std::vector bound; // every identifier a pattern of the function binds (params included) + std::vector pending; + }; + + bool kind( TSNode n, Kind k ) const noexcept { return ts_node_symbol( n ) == m_sym[k]; } + // The node's records; false: do not enter its children (an anonymous token has none worth a visit, opaque()). + bool enter( TSNode n ) + { + if( !ts_node_is_named( n ) || opaque( n ) ) + { + return false; + } + if( kind( n, Impl ) ) + { + m_impl.push_back( typeName( field( n, "type" ) ) ); + } + else if( kind( n, FnItem ) ) + { + m_fns.push_back( fnScope( n ) ); + } + else if( kind( n, Call ) ) + { + callAt( n ); + } + else if( kind( n, StructExpr ) ) + { + structAt( n ); + } + else if( kind( n, Assign ) ) + { + assignAt( n ); + } + else if( !m_fns.empty() && ( kind( n, Let ) || kind( n, For ) || kind( n, MatchArm ) || kind( n, LetCond ) ) ) + { + bindIn( field( n, "pattern" ), m_fns.back().bound, 0 ); + } + else if( !m_fns.empty() && kind( n, ClosureParams ) ) + { + bindIn( n, m_fns.back().bound, 0 ); + } + return true; + } + void leave( TSNode n ) + { + if( kind( n, Impl ) && !m_impl.empty() ) + { + m_impl.pop_back(); + } + else if( kind( n, FnItem ) && !m_fns.empty() && ts_node_eq( m_fns.back().node, n ) ) + { + finishFn(); + } + } + void finishFn() + { + Fn f = std::move( m_fns.back() ); + m_fns.pop_back(); + for( Pending& p : f.pending ) + { + const auto bound = std::ranges::count( f.bound, p.id ); + if( bound == 0 ) + { + m_out.push_back( std::move( p.r ) ); + continue; + } + const auto at = std::ranges::find( f.params, p.id ); + if( !p.store || bound != 1 || at == f.params.end() || f.name.empty() ) + { + continue; // a local, or a parameter rebound somewhere in the function: never a function value + } + RawRef& r = p.r; // a parameter stored into a field slot: the registrar's fact, never a row + r.name.clear(); + r.memberRoot.clear(); + r.fieldName.clear(); + r.composeRel.clear(); + r.qualifier = std::string( 1, kValueRefSlotParamScope ); + r.recvVar = std::string( f.name ); + r.argCount = static_cast( std::min( at - f.params.begin(), 0xFFFF ) ); + r.argCountKnown = true; + m_out.push_back( std::move( r ) ); + } + } + // A row naming `id` (a bare identifier, "" for a path): to its function's pending list, else (outside a function) out. + void land( RawRef&& r, std::string_view id, bool store ) + { + if( !id.empty() && !m_fns.empty() ) + { + m_fns.back().pending.push_back( Pending{ std::move( r ), id, store } ); + return; + } + m_out.push_back( std::move( r ) ); + } + + std::string_view text( TSNode n ) const noexcept + { + if( ts_node_is_null( n ) ) + { + return {}; + } + const std::uint32_t a = ts_node_start_byte( n ), b = ts_node_end_byte( n ); + return a <= b && b <= m_src.size() ? m_src.substr( a, b - a ) : std::string_view(); + } + static TSNode field( TSNode n, std::string_view name ) noexcept + { + return ts_node_child_by_field_name( n, name.data(), static_cast( name.size() ) ); + } + static bool is( TSNode n, std::string_view kind ) noexcept { return !ts_node_is_null( n ) && kind == ts_node_type( n ); } + + // The type NAME a type node spells: `T`, `T` → T, `m::T` → T. "" for every other type. + std::string_view typeName( TSNode t ) const noexcept + { + if( is( t, "generic_type" ) ) + { + t = field( t, "type" ); + } + if( is( t, "scoped_type_identifier" ) ) + { + t = field( t, "name" ); + } + return is( t, "type_identifier" ) ? text( t ) : std::string_view(); + } + std::string_view implType() const noexcept { return m_impl.empty() ? std::string_view() : m_impl.back(); } + + // The function's name and its parameters in position order (their names are bound; the body's patterns bind more as the + // walk reaches them). + Fn fnScope( TSNode fn ) const + { + Fn f; + f.node = fn; + f.name = text( field( fn, "name" ) ); + const TSNode ps = field( fn, "parameters" ); + if( !ts_node_is_null( ps ) ) + { + ChildCursor c( ps ); + forEachNamedChild( ps, c.cur, [ & ]( TSNode p ) + { + if( is( p, "self_parameter" ) ) + { + f.params.push_back( "self" ); + } + else if( is( p, "parameter" ) ) + { + const TSNode pat = field( p, "pattern" ); + f.params.push_back( is( pat, "identifier" ) ? text( pat ) : std::string_view() ); // a destructuring parameter keeps its position, names none + bindIn( pat, f.bound, 0 ); + } + return true; + } ); + } + return f; + } + // Every identifier of pattern subtree `n` (the names it binds; a path or literal in it names none — over-collecting only hides). + void bindIn( TSNode n, std::vector& out, std::uint32_t depth ) const + { + if( ts_node_is_null( n ) || depth >= kVrMaxDepth ) + { + return; + } + if( is( n, "identifier" ) ) + { + out.push_back( text( n ) ); + return; + } + ChildCursor c( n ); + forEachNamedChild( n, c.cur, [ & ]( TSNode ch ) { bindIn( ch, out, depth + 1 ); return true; } ); + } + + // `Box::new` / `Rc::new` / `Arc::new`: a wrapper that hands its one argument on (the value, not a registrar). + bool wrapperCallee( TSNode fn ) const noexcept + { + if( !is( fn, "scoped_identifier" ) || text( field( fn, "name" ) ) != "new" ) + { + return false; + } + const std::string_view path = text( field( fn, "path" ) ); + return path == "Box" || path == "Rc" || path == "Arc"; + } + // A value as written, through `( v )`, `Box::new( v )`, `Rc::new( v )`, `Arc::new( v )`. + TSNode unwrap( TSNode v ) const noexcept + { + for( int hop = 0; hop < 4 && !ts_node_is_null( v ); ++hop ) + { + if( is( v, "parenthesized_expression" ) ) + { + v = ts_node_named_child( v, 0 ); + continue; + } + if( !is( v, "call_expression" ) ) + { + break; + } + const TSNode args = field( v, "arguments" ); + if( !wrapperCallee( field( v, "function" ) ) || ts_node_named_child_count( args ) != 1 ) + { + break; + } + v = ts_node_named_child( args, 0 ); + } + return v; + } + + // What a value node names: a function path (name, owner type), a bare identifier (decided at its function's end), or nothing. + struct Named + { + std::string_view name; + std::string owner; + bool bare = false; // a bare identifier: a function, a parameter or a local, as its function binds it + }; + static bool upperLead( std::string_view s ) noexcept { return !s.empty() && s[0] >= 'A' && s[0] <= 'Z'; } + Named named( TSNode v ) const + { + Named out; + v = unwrap( v ); + if( is( v, "generic_function" ) ) + { + v = field( v, "function" ); // `f::`: the turbofish picks an instance of f + } + if( is( v, "identifier" ) ) + { + const std::string_view id = text( v ); + if( !upperLead( id ) ) + { + out.name = id; + out.bare = true; // its function's patterns decide it (finishFn) + } + return out; + } + if( is( v, "scoped_identifier" ) ) + { + const TSNode nm = field( v, "name" ); + const TSNode path = field( v, "path" ); + const std::string_view leaf = text( nm ); + if( !is( nm, "identifier" ) || upperLead( leaf ) ) + { + return out; // a variant or an associated constant + } + std::string_view owner = is( path, "scoped_identifier" ) ? text( field( path, "name" ) ) + : is( path, "generic_type" ) ? typeName( path ) + : text( path ); + if( owner == "Self" ) + { + owner = implType(); + if( owner.empty() ) + { + return out; // `Self::f` outside an impl (a trait's default body): the type is not one + } + } + out.name = leaf; + out.owner = upperLead( owner ) ? std::string( owner ) : std::string(); // a module path names no owner type + } + return out; + } + + RawRef base( TSNode at ) const + { + RawRef r; + r.fileId = m_fileId; + r.startByte = ts_node_start_byte( at ); + r.line = ts_node_start_point( at ).row + 1; + r.lang = m_lang; + return r; + } + + // `callee( … )` with a path callee: each function-path argument is an argument row; `(x.f)( … )` is a call through field f. + void callAt( TSNode call ) + { + const TSNode fn = field( call, "function" ); + if( is( fn, "parenthesized_expression" ) ) + { + slotCallAt( call, ts_node_named_child( fn, 0 ) ); + return; + } + const TSNode callee = is( fn, "generic_function" ) ? field( fn, "function" ) : fn; + if( wrapperCallee( callee ) ) + { + return; // `Box::new( f )`: the value lands where the wrapper does (unwrap), not in the wrapper + } + std::string written; + if( is( callee, "identifier" ) ) + { + written = std::string( text( callee ) ); + } + else if( is( callee, "scoped_identifier" ) ) + { + const Named n = named( callee ); + if( n.name.empty() ) + { + return; + } + written = n.owner.empty() ? std::string( n.name ) : n.owner + "::" + std::string( n.name ); + } + else + { + return; // a method call `x.m( … )`, a call result called: a floor + } + const TSNode args = field( call, "arguments" ); + if( ts_node_is_null( args ) ) + { + return; + } + std::uint32_t pos = 0; + ChildCursor c( args ); + forEachNamedChild( args, c.cur, [ & ]( TSNode a ) + { + if( is( a, "line_comment" ) || is( a, "block_comment" ) || is( a, "attribute_item" ) ) + { + return true; + } + const Named n = named( a ); + if( !n.name.empty() ) + { + RawRef r = base( a ); + r.role = RefRole::Value; + r.name = std::string( n.name ); + r.memberRoot = n.owner; + r.qualifier = "a0"; + r.recvVar = written; + r.fieldName = written + "#arg" + std::to_string( pos ); + r.composeRel = "#" + std::to_string( pos ); + r.argCount = static_cast( std::min( pos, 0xFFFFu ) ); + r.argCountKnown = true; + land( std::move( r ), n.bare ? n.name : std::string_view(), false ); + } + ++pos; + return true; + } ); + } + + // `(root.a.f)( … )`: one slot call row (typed only for `self.f` inside an impl). + void slotCallAt( TSNode call, TSNode f ) + { + if( !is( f, "field_expression" ) ) + { + return; + } + const TSNode fld = field( f, "field" ); + TSNode root = field( f, "value" ); + int hops = 1; + while( is( root, "field_expression" ) ) + { + root = field( root, "value" ); + ++hops; + } + if( !is( fld, "field_identifier" ) || !( is( root, "identifier" ) || is( root, "self" ) ) ) + { + return; // a tuple field `(x.0)( … )`, a call result's field: a floor + } + RawRef r = base( call ); + r.role = RefRole::Through; + r.qualifier = std::string( 1, kValueRefSlotCallScope ); + r.name = std::string( text( root ) ); + r.fieldName = std::string( text( f ) ); + r.composeRel = "." + std::string( text( fld ) ); + r.memberPath = std::string( text( fld ) ); + r.memberCtor = hops == 1 && is( root, "self" ) ? std::string( implType() ) : std::string(); + m_out.push_back( std::move( r ) ); + } + + // `T { f: v, g }`: each field value that names a function (a row) or a parameter (a registrar's fact). + void structAt( TSNode se ) + { + const TSNode body = field( se, "body" ); + if( ts_node_is_null( body ) ) + { + return; + } + const std::string ty = structType( field( se, "name" ) ); + if( ty.empty() ) + { + return; + } + ChildCursor c( body ); + forEachNamedChild( body, c.cur, [ & ]( TSNode fi ) + { + if( is( fi, "field_initializer" ) ) + { + const TSNode fname = field( fi, "field" ); + if( is( fname, "field_identifier" ) ) + { + storeAt( fi, field( fi, "value" ), text( fname ), ty ); + } + } + else if( is( fi, "shorthand_field_initializer" ) ) + { + const TSNode id = ts_node_named_child( fi, 0 ); + if( is( id, "identifier" ) ) + { + storeAt( fi, id, text( id ), ty ); + } + } + return true; + } ); + } + // The struct type a struct expression's name spells: `Self` → the impl type; `m::T` → T (a module path leads); a + // variant `E::V` or any other path is kept whole (no in-tree struct has that name: the join keys it as no slot). + std::string structType( TSNode nm ) const + { + if( is( nm, "generic_type_with_turbofish" ) ) + { + nm = field( nm, "type" ); + } + if( is( nm, "type_identifier" ) ) + { + const std::string_view t = text( nm ); + return t == "Self" ? std::string( implType() ) : std::string( t ); + } + if( is( nm, "scoped_type_identifier" ) ) + { + const std::string_view path = text( field( nm, "path" ) ); + const std::string_view tail = path.substr( path.rfind( ':' ) == std::string_view::npos ? 0 : path.rfind( ':' ) + 1 ); + return upperLead( tail ) ? std::string( text( nm ) ) : std::string( text( field( nm, "name" ) ) ); + } + return {}; + } + // `x.f = v`: a store into field f (typed for `self.f` inside an impl, else the name tier). + void assignAt( TSNode as ) + { + const TSNode lhs = field( as, "left" ); + if( !is( lhs, "field_expression" ) ) + { + return; + } + const TSNode fld = field( lhs, "field" ); + const TSNode root = field( lhs, "value" ); + if( !is( fld, "field_identifier" ) ) + { + return; + } + storeAt( as, field( as, "right" ), text( fld ), is( root, "self" ) ? std::string( implType() ) : std::string() ); + } + void storeAt( TSNode at, TSNode value, std::string_view fieldName, const std::string& ty ) + { + const Named n = named( value ); + RawRef r = base( at ); + r.role = RefRole::Value; + r.memberPath = std::string( fieldName ); + r.memberCtor = ty; + if( n.name.empty() ) + { + return; + } + r.name = std::string( n.name ); + r.memberRoot = n.owner; + r.qualifier = "x0"; + r.fieldName = ( ty.empty() ? std::string() : ty ) + "." + std::string( fieldName ); + r.composeRel = "." + std::string( fieldName ); + land( std::move( r ), n.bare ? n.name : std::string_view(), true ); + } + + Lang m_lang; + std::uint32_t m_fileId; + std::string_view m_src; + std::vector& m_out; + std::vector m_impl; // the enclosing impl blocks' type names (`Self`) + std::vector m_fns; // the enclosing functions + std::array m_sym {}; // kKinds' grammar symbols + VrDepthCut m_depthCut; +}; + +// The Rust row of model.h kVrSlotLangs: this file's slot records (RECORDS above), appended to `refs`. +inline void captureRustSlotRefs( Lang lang, std::uint32_t fileId, std::string_view src, TSNode root, std::vector& refs ) +{ + PROFILE_SCOPE_DESCRIBE( "ingest/extractFile: Rust slot records" ); + RustSlotWalk walk( lang, fileId, src, refs ); + walk.run( root ); +} + +} // namespace + +} // namespace rw diff --git a/src/ingest_valuerefs.h b/src/ingest_valuerefs.h index b8e6521c4..9bd4c66e8 100644 --- a/src/ingest_valuerefs.h +++ b/src/ingest_valuerefs.h @@ -53,8 +53,8 @@ // scope exit / file end), so an ordinary `x.m()` adds nothing; a parameter Through always survives — its // values arrive from callers elsewhere. // -// FLOORS (stated in the legend and pinned by recallshapecheck): a member or qualified value (obj.f, self.f, -// ns::f, &Cls::m) and a function as the OBJECT of a member access (f.bind, f.name) are not rows; an import +// FLOORS (stated in the legend and pinned by recallshapecheck; this walk's families — Rust records its own slot rows, +// ingest_rustslots.h): a member or qualified value (obj.f, self.f, ns::f, &Cls::m) and a function as the OBJECT of a member access (f.bind, f.name) are not rows; an import // alias is a local binding; a macro body is opaque; classes are not functions; Python attribute calls never // count as a call through a dict; nodes deeper than kVrMaxDepth are not visited — DISCLOSED, never silent: the file's // depth-cut record (below) makes value_refs= / --dead-code say the count is a floor and name the first cut site. @@ -480,6 +480,39 @@ struct VrDepthCut disclosed = true; } }; +// A node at kVrMaxDepth whose children a value walk does not visit: a value reference below it is never captured. The +// first one's site is kept as the clue the answers print (value_refs_depth_at=). +inline void noteVrDepthCut( VrDepthCut& cut, TSNode n ) noexcept +{ + if( cut.subtrees == 0 ) + { + cut.startByte = ts_node_start_byte( n ); + cut.line = ts_node_start_point( n ).row + 1; + } + if( cut.subtrees < UINT32_MAX ) + { + ++cut.subtrees; + } +} +// The file's ONE depth-cut record (RECORD SHAPE in the header comment): the cut is disclosed through it on the answers that +// read value references (valuerefs.h value_refs_depth_capped=, --dead-code; the named exceptions are in the header comment), +// and it is cached with the file's other references, so a warm run discloses it too. +inline RawRef vrDepthCutRecord( std::uint32_t fileId, Lang lang, VrDepthCut& cut ) +{ + EXPECTS( cut.subtrees > 0, "a depth-cut record is written only when the cap stopped the walk" ); + DISCLOSE( cut, VrDepthCut::DisclosureWhy::SubtreesBelowMaxDepth, + "value references: subtrees below kVrMaxDepth were not visited (value_refs_depth_capped=)" ); + RawRef r; + r.fileId = fileId; + r.startByte = cut.startByte; + r.line = cut.line; + r.lang = lang; + r.role = RefRole::Value; + r.qualifier = std::string( 1, kValueRefDepthCutScope ); // name stays "": no identifier is empty + r.argCount = static_cast( std::min( cut.subtrees, 0xFFFFu ) ); + r.argCountKnown = true; + return r; +} struct VrAnc { @@ -556,7 +589,7 @@ class ValueRefWalk } else if( ts_node_child_count( m_anc.back().node ) > 0 ) { - noteDepthCut( m_anc.back().node ); // the cap stops the walk ABOVE these children: one subtree not visited + noteVrDepthCut( m_depthCut, m_anc.back().node ); // the cap stops the walk ABOVE these children: one subtree not visited } bool done = false; for( ;; ) @@ -594,7 +627,7 @@ class ValueRefWalk emitForwardedCalls(); if( m_depthCut.subtrees > 0 ) { - emitDepthCut(); + m_out.push_back( vrDepthCutRecord( m_fileId, m_lang, m_depthCut ) ); } } @@ -627,6 +660,7 @@ class ValueRefWalk || kindIs( t, "generator_expression" ) ? ScopeKind::Block : ScopeKind::None; case VrFam::Go: return kindIs( t, "function_declaration" ) || kindIs( t, "method_declaration" ) || kindIs( t, "func_literal" ) ? ScopeKind::Function : ScopeKind::None; + case VrFam::Rust: // never: Rust runs ingest_rustslots.h, not this walk case VrFam::None: break; } return ScopeKind::None; @@ -890,6 +924,7 @@ class ValueRefWalk } break; } + case VrFam::Rust: // never: Rust runs ingest_rustslots.h, not this walk case VrFam::None: break; } } @@ -1019,6 +1054,7 @@ class ValueRefWalk } break; } + case VrFam::Rust: // never: Rust runs ingest_rustslots.h, not this walk case VrFam::None: break; } } @@ -1126,47 +1162,12 @@ class ValueRefWalk return kindIs( t, "formal_parameters" ) && !( s.isFunction && ts_node_eq( parent.node, s.node ) ); } case VrFam::Py: + case VrFam::Rust: // never: Rust runs ingest_rustslots.h, not this walk case VrFam::None: break; } return false; } - // ── the depth cut ────────────────────────────────────────────────────────────────────────────────── - // A node at kVrMaxDepth whose children the walk does not visit: a value reference below it is never captured. The - // first one's site is kept as the clue the answers print (value_refs_depth_at=). - void noteDepthCut( TSNode n ) noexcept - { - if( m_depthCut.subtrees == 0 ) - { - m_depthCut.startByte = ts_node_start_byte( n ); - m_depthCut.line = ts_node_start_point( n ).row + 1; - } - if( m_depthCut.subtrees < UINT32_MAX ) - { - ++m_depthCut.subtrees; - } - } - - // The file's ONE depth-cut record (RECORD SHAPE in the header comment): the cut is disclosed through it on the - // answers that read value references (valuerefs.h value_refs_depth_capped=, --dead-code; the named exceptions are in - // the header comment), and it is cached with the file's other references, so a warm run discloses it too. - void emitDepthCut() - { - EXPECTS( m_depthCut.subtrees > 0, "a depth-cut record is written only when the cap stopped the walk" ); - DISCLOSE( m_depthCut, VrDepthCut::DisclosureWhy::SubtreesBelowMaxDepth, - "value references: subtrees below kVrMaxDepth were not visited (value_refs_depth_capped=)" ); - RawRef r; - r.fileId = m_fileId; - r.startByte = m_depthCut.startByte; - r.line = m_depthCut.line; - r.lang = m_lang; - r.role = RefRole::Value; - r.qualifier = std::string( 1, kValueRefDepthCutScope ); // name stays "": no identifier is empty - r.argCount = static_cast( std::min( m_depthCut.subtrees, 0xFFFFu ) ); - r.argCountKnown = true; - m_out.push_back( std::move( r ) ); - } - // ── the walk ─────────────────────────────────────────────────────────────────────────────────────── void enter( TSNode n, TSFieldId field ) { @@ -3126,8 +3127,11 @@ class ValueRefWalk TSFieldId m_fConstructor = 0, m_fType = 0, m_fIndex = 0, m_fSubscript = 0, m_fDesignator = 0, m_fAlias = 0; }; +// Rust's slot records (ingest_rustslots.h, defined after this section). +inline void captureRustSlotRefs( Lang lang, std::uint32_t fileId, std::string_view src, TSNode root, std::vector& refs ); + // The pass's entry point: one file's value references and calls-through-a-value, appended to `refs`. Inert on a -// language outside the armed set (C, C++, JavaScript/JSX, TypeScript/TSX, Python, Go). +// language outside the armed set (C, C++, JavaScript/JSX, TypeScript/TSX, Python, Go; Rust records its slot join's only). inline void captureValueRefs( Lang lang, std::uint32_t fileId, std::string_view src, TSNode root, std::vector& refs ) { const VrFam fam = valueRefFamily( lang ); @@ -3135,6 +3139,11 @@ inline void captureValueRefs( Lang lang, std::uint32_t fileId, std::string_view { return; } + if( fam == VrFam::Rust ) + { + captureRustSlotRefs( lang, fileId, src, root, refs ); // the slot join's records only (ingest_rustslots.h) + return; + } PROFILE_SCOPE_DESCRIBE( "ingest/extractFile: value references" ); ValueRefWalk walk( fam, lang, fileId, src, refs ); walk.run( root ); diff --git a/src/model.h b/src/model.h index 3b2793f84..b94f4cdcb 100644 --- a/src/model.h +++ b/src/model.h @@ -505,7 +505,7 @@ inline const char* refRoleTag( RefRole r ) noexcept // Reference-as-value round: the grammar family whose value positions src/ingest_valuerefs.h reads, and whose // visibility rule src/valuerefs.h resolves under — ONE table both sides index, so the capture and the resolver // cannot disagree about which languages are armed. None = not armed (no Value/Through rows are captured). -enum class ValueRefFamily : std::uint8_t { None, C, Js, Py, Go }; +enum class ValueRefFamily : std::uint8_t { None, C, Js, Py, Go, Rust }; // Rust: the slot join's records only (ingest_rustslots.h) inline constexpr std::array kValueRefFamilyOfLang = [] { std::array table {}; // value-initialised: every language None unless armed below @@ -515,6 +515,7 @@ inline constexpr std::array kValueRefFamilyOfLang = table[ static_cast( Lang::TypeScript ) ] = ValueRefFamily::Js; table[ static_cast( Lang::Python ) ] = ValueRefFamily::Py; table[ static_cast( Lang::Go ) ] = ValueRefFamily::Go; + table[ static_cast( Lang::Rust ) ] = ValueRefFamily::Rust; return table; }(); inline ValueRefFamily valueRefFamily( Lang l ) noexcept @@ -586,23 +587,31 @@ inline constexpr char kValueRefSlotParamScope = 'q'; // `x->f( … )` in C, the C++ gate otherwise); // MemberCallByName: an ordinary member call `x.F( … )` (a Call reference with memberCall) is a call through field F when some // function value is stored in a field F, joined at index time by the field name (the receiver's type is not read: the name -// tier, via="slot-name"); the extractor records only the STORE side (Go). +// tier, via="slot-name"); the extractor records only the STORE side (Go); +// ParenFieldCall: `(x.f)( … )`, the only spelling that calls a function-valued field (`x.f( … )` is always a method call), recorded +// by the extractor as a kValueRefSlotCallScope row, typed when x is `self` (Rust: ingest_rustslots.h). // typeKey — how a slot's declared struct type is keyed (both sides and --for's struct rows key the same way): // TypedefFacts: the type as written, through the typedef / field-type slot facts (C family); -// PackageDir: the package DIRECTORY the type is declared in, plus its name (Go: valuerefindex.h slot_join::GoTypeKeys). -enum class VrSlotCallSide : std::uint8_t { ThroughRows, MemberCallByName }; -enum class VrSlotTypeKey : std::uint8_t { TypedefFacts, PackageDir }; +// PackageDir: the package DIRECTORY the type is declared in, plus its name (Go: valuerefindex.h slot_join::GoTypeKeys); +// ModuleType: the one in-tree struct the name spells — its own file's, else its crate's, else the repo's only one (Rust: +// slot_join::RustTypeKeys; `Self` is resolved to the impl type by the extractor). +// pathSep — a registrar may be an associated function reached by a PATH call (`CallEval::new( … )`): the argument row's callee +// is written `Ownername`, resolved to the owner type's function of that name ("" = bare callees only, never a method). +enum class VrSlotCallSide : std::uint8_t { ThroughRows, MemberCallByName, ParenFieldCall }; +enum class VrSlotTypeKey : std::uint8_t { TypedefFacts, PackageDir, ModuleType }; struct VrSlotLang { - Lang lang; - bool everyFieldCall; - VrSlotCallSide callSide; - VrSlotTypeKey typeKey; + Lang lang; + bool everyFieldCall; + VrSlotCallSide callSide; + VrSlotTypeKey typeKey; + std::string_view pathSep; }; inline constexpr VrSlotLang kVrSlotLangs[] = { - { Lang::C, true, VrSlotCallSide::ThroughRows, VrSlotTypeKey::TypedefFacts }, - { Lang::Cpp, false, VrSlotCallSide::ThroughRows, VrSlotTypeKey::TypedefFacts }, - { Lang::Go, false, VrSlotCallSide::MemberCallByName, VrSlotTypeKey::PackageDir }, + { Lang::C, true, VrSlotCallSide::ThroughRows, VrSlotTypeKey::TypedefFacts, "" }, + { Lang::Cpp, false, VrSlotCallSide::ThroughRows, VrSlotTypeKey::TypedefFacts, "" }, + { Lang::Go, false, VrSlotCallSide::MemberCallByName, VrSlotTypeKey::PackageDir, "" }, + { Lang::Rust, false, VrSlotCallSide::ParenFieldCall, VrSlotTypeKey::ModuleType, "::" }, }; inline constexpr std::array kVrSlotLangOf = [] { @@ -629,6 +638,12 @@ inline VrSlotTypeKey slotTypeKey( Lang l ) noexcept const VrSlotLang* row = vrSlotLang( l ); return row == nullptr ? VrSlotTypeKey::TypedefFacts : row->typeKey; } +// The path separator of language `l`'s registrar callees ("" = bare callees only). +inline std::string_view slotPathSep( Lang l ) noexcept +{ + const VrSlotLang* row = vrSlotLang( l ); + return row == nullptr ? std::string_view() : row->pathSep; +} // A language whose slot types key by its own rule (valuerefindex.h slot_join), not through the C family's typedef facts. inline bool slotKeysOwnTypes( Lang l ) noexcept { diff --git a/src/quality.h b/src/quality.h index 4d03d2ca0..b6300c8db 100644 --- a/src/quality.h +++ b/src/quality.h @@ -2440,7 +2440,7 @@ inline std::string cacheRootKeyHex( const std::string& root ) // not include this header; it relies on ingest.cpp including quality.h (line 13) before ingest_cache.h, and a reorder // that broke that fails the build on the undeclared name rather than passing. constexpr std::uint32_t kIngestCacheVersionMirror = 44; // MUST equal ingest.cpp's kCacheVersion (gated); 44 = train 37b (rust-degraded FileHealth::useBoundsBlanked; its placeholder 42), 43 = train 36 (#155 trailingLambda ref byte; above main 40, #438's placeholder 41, rust-degraded's 42), 40 = train 34 (fc3-js-rebind JsRebind; above main 39), 39 = train 33 (above main 36 and l6-iface 38), 38 = train 32 lane l6-iface fix round 1: LocalBindKind ImplBlock (train renumbers), 37 = train 32 lane l6-iface: LocalBindKind DispatchType (train renumbers), 36 = train 30 (above call-result-typing's and fcall-forward's 33, name-template-dispatch's 35, main's 34), 34 = train 29 (above lane arity-min's and call-result-typing's 33), 33 = train 29 lane arity-min: def record paramsMin (the train renumbers), 33 = lane call-result-typing: RawBind resultIndex + unwrapDepth, 33 = lane fcall-forward: ref `forwarded` byte, 35 = lane name-template-dispatch: RefRole Pattern 11, 32 = train 28: RefRole Subscript 9 + Inert 10, 31 = train 27: lego-transitive's inherit-ref baseBind (the lane's 30), 29 = FE-B ref memberPath/memberCtor + 4 bind kinds, 28 = FE-A ref memberCall/memberRoot, 27 = corrected fnScope values, 26 = function-local def scope span (25 = #157 + #150) -constexpr std::uint32_t kIngestParserVerMirror = 247; // MUST equal ingest.cpp's kParserVer (gated) +constexpr std::uint32_t kIngestParserVerMirror = 249; // MUST equal ingest.cpp's kParserVer (gated) // 239 = 2026-10-27 (train 39: receiver-fixes typing, placeholder 237/238; above train 38 // 235/236; see the kParserVer note) // 235 = 2026-10-27 (train 38: queue-hop keyword tag on a Python queue call, placeholder diff --git a/src/valuerefindex.h b/src/valuerefindex.h index 153539f5a..3e5fb2152 100644 --- a/src/valuerefindex.h +++ b/src/valuerefindex.h @@ -157,6 +157,120 @@ class GoTypeKeys HashMap m_structs; // every in-tree Go struct's key HashMap m_dirOf; // import path → its directory's first file (kNoFile none) }; +// FN-SLOT part 2: each file's Rust crate as its path spells it — the root-relative path before the first `src/` directory (a crate's +// sources live under its own src/), else the file's directory; "" for a file of no path-call language (never compared). +inline std::vector cratesOf( const IngestResult& ing ) +{ + std::vector out( ing.files.size() ); + for( std::uint32_t f = 0; f < ing.files.size(); ++f ) + { + const std::string_view rel = rootRelPath( ing, f ); + if( !rel.ends_with( ".rs" ) || rel.starts_with( "src/" ) ) + { + continue; + } + const std::size_t at = rel.find( "/src/" ); + out[f] = at == std::string_view::npos ? includerDir( rel ) : rel.substr( 0, at ); + } + return out; +} +// The one candidate of `cands` (symbols) that file `fileId` most plausibly means: its own file's, else its crate's (`crates`, +// cratesOf), else the repo's only one; kNoNode when the tier that has any holds two (which one is unknown: never a guess). +inline NodeId nearestOf( const IngestResult& ing, std::span cands, std::uint32_t fileId, std::span crates ) +{ + NodeId pick[3] = { kNoNode, kNoNode, kNoNode }; + int n[3] = { 0, 0, 0 }; + for( const NodeId id : cands ) + { + const std::uint32_t f = ing.symbols[id].fileId; + const int tier = f == fileId ? 0 : crates[f] == crates[fileId] ? 1 : 2; + for( int t = tier; t < 3; ++t ) + { + pick[t] = id; + ++n[t]; + } + } + for( int t = 0; t < 3; ++t ) + { + if( n[t] > 0 ) + { + return n[t] == 1 ? pick[t] : kNoNode; + } + } + return kNoNode; +} +// FN-SLOT part 2: a ModuleType (Rust) slot type's key — the in-tree STRUCT symbol the written name spells (nearestOf: its own file, +// else its crate, else the repo's only one), as jsImportKey( symbolId, name ); "" when no struct or several of one tier carry the +// name (an out-of-tree type, or which one is unknown: the name tier); kNoSlot when the name is a path through a TYPE (`E::V`, a +// variant: no field slot of a struct). +class RustTypeKeys +{ +public: + void build( const IngestResult& ing ) + { + for( NodeId id = 0; id < ing.symbols.size(); ++id ) + { + const Symbol& sym = ing.symbols[id]; + if( ( sym.kind == SymKind::Struct || sym.kind == SymKind::Class ) && slotTypeKey( sym.lang ) == VrSlotTypeKey::ModuleType ) // a Rust struct is a class row + { + m_structs[ sym.name ].push_back( id ); + } + } + if( !m_structs.empty() ) + { + m_crates = cratesOf( ing ); + } + } + static std::string keyOfSymbol( const Symbol& s, NodeId id ) { return jsImportKey( id, s.name ); } + std::string keyOf( const IngestResult& ing, const Reference& r ) const + { + const std::string_view t = r.memberCtor; + if( t.empty() ) + { + return {}; + } + if( t.find( ':' ) != std::string_view::npos ) + { + return std::string( kNoSlot ); + } + const auto it = m_structs.find( std::string( t ) ); + if( it == m_structs.end() ) + { + return {}; + } + const NodeId id = nearestOf( ing, it->second, r.fileId, m_crates ); + return id == kNoNode ? std::string() : keyOfSymbol( ing.symbols[id], id ); + } + +private: + HashMap> m_structs; // struct name → the in-tree Rust structs of that name + std::vector m_crates; // cratesOf (built when any Rust struct exists) +}; +// The type key of every language that keys its own types (model.h slotKeysOwnTypes), by its row's VrSlotTypeKey. +class TypeKeys +{ +public: + void build( const IngestResult& ing ) + { + m_ing = &ing; + m_go.build( ing ); + m_rust.build( ing ); + } + std::string keyOf( const Reference& r ) + { + return slotTypeKey( r.lang ) == VrSlotTypeKey::ModuleType ? m_rust.keyOf( *m_ing, r ) : m_go.keyOf( r ); + } + std::string keyOfSymbol( NodeId id ) const + { + const Symbol& s = m_ing->symbols[id]; + return slotTypeKey( s.lang ) == VrSlotTypeKey::ModuleType ? RustTypeKeys::keyOfSymbol( s, id ) : m_go.keyOfSymbol( s ); + } + +private: + const IngestResult* m_ing = nullptr; + GoTypeKeys m_go; + RustTypeKeys m_rust; +}; // The member calls `x.F( … )` of a MemberCallByName language whose field F holds a stored function somewhere (`holds`), as calls // through that slot, in reference order. Not when that language defines a METHOD named F anywhere in the tree: the call may // then be that method, and the receiver's type is not read here (a named floor, never a guess). @@ -198,15 +312,16 @@ class ParamSlots { public: void take( std::uint32_t refIdx ) { m_refs.push_back( refIdx ); } - void build( const IngestResult& ing, GoTypeKeys& keys ) + void build( const IngestResult& ing, TypeKeys& keys ) { for( const std::uint32_t q : m_refs ) { const Reference& r = ing.references[q]; - if( r.fromSymbol >= ing.symbols.size() || ing.symbols[ r.fromSymbol ].kind != SymKind::Function - || ing.symbols[ r.fromSymbol ].name != r.recvVar ) + const bool pathCalls = !slotPathSep( r.lang ).empty(); // a path call `T::f( … )` reaches an associated function + if( r.fromSymbol >= ing.symbols.size() || ing.symbols[ r.fromSymbol ].name != r.recvVar + || !( ing.symbols[ r.fromSymbol ].kind == SymKind::Function || ( pathCalls && ing.symbols[ r.fromSymbol ].kind == SymKind::Method ) ) ) { - continue; // a method registrar (no bare call reaches it), or a fact outside its own function's definition + continue; // a method registrar no call of its language reaches by name, or a fact outside its own function's definition } const std::string to = keys.keyOf( r ) + "#" + r.memberPath; std::string& have = m_slot[ jsImportKey( r.fromSymbol, std::to_string( r.argCount ) ) ]; @@ -241,7 +356,7 @@ class ParamSlots private: static constexpr std::string_view kConflict = "\x01"; - std::vector m_refs; // the facts, keyed in build() (the type key by its file, GoTypeKeys) + std::vector m_refs; // the facts, keyed in build() (the type key by its file, TypeKeys) HashMap m_slot; // "registrarId#k" → "TypeKey#field" (kConflict: two different slots) }; // `extra` (ascending) merged into the slot calls and their per-function lists, all kept in reference order (join() and callsIn() @@ -308,8 +423,8 @@ class SlotIndex // After every take: keys the slot values (`values`, ascending, with their resolved `targets`) and the calls by field. void build( const IngestResult& ing, std::span values, std::span> targets ) { - m_goKeys.build( ing ); - m_params.build( ing, m_goKeys ); + m_keys.build( ing ); + m_params.build( ing, m_keys ); for( std::size_t k = 0; k < values.size(); ++k ) { if( targets[k].empty() ) @@ -350,7 +465,7 @@ class SlotIndex std::vector valuesFor( std::uint32_t callIdx ) const { return join( m_callEnds, callIdx, m_valuesByField, m_values ); } // fix1 F-2: the slot type key of a struct symbol of a language that keys its own types (model.h slotKeysOwnTypes), as // valuesOfType reads it (the store side keys the same way) - std::string typeKeyOf( const Symbol& s ) const { return m_goKeys.keyOfSymbol( s ); } + std::string typeKeyOf( NodeId id ) const { return m_keys.keyOfSymbol( id ); } // IX1-V2: the slot value references stored into a field of struct type `type` (the resolved type name), with the field, // in reference order — what --for's slot_fields= lists on a struct row. std::vector> valuesOfType( std::string_view type ) const @@ -383,7 +498,10 @@ class SlotIndex { return "." + e->field; } - const std::string_view t = std::string_view( e->type ).substr( 0, e->type.starts_with( '@' ) ? e->type.find( '#' ) : std::string::npos ); + // an anonymous struct's "@LINE#file" prints "@LINE"; an own-keyed type's "id#Name" (TypeKeys) prints its name + const std::size_t hash = e->type.find( '#' ); + const std::string_view t = e->type.starts_with( '@' ) ? std::string_view( e->type ).substr( 0, hash ) + : hash == std::string::npos ? std::string_view( e->type ) : std::string_view( e->type ).substr( hash + 1 ); return std::string( t ) + "." + e->field; } @@ -474,7 +592,7 @@ class SlotIndex { // IX1-V2: a member call `x.F( … )` (slot_join::memberCalls, its receiver's type unread: the name tier), or a store of a // language that keys its own types (Go: package-qualified; no typedef aliases or field-type facts on this side) - e.type = r.role == RefRole::Call ? std::string() : m_goKeys.keyOf( r ); + e.type = r.role == RefRole::Call ? std::string() : m_keys.keyOf( r ); e.field = r.role == RefRole::Call ? r.calleeName : r.memberPath; return e; } @@ -530,7 +648,7 @@ class SlotIndex HashMap m_aliasInFile; // "fileId#name" → struct type, the file's own typedef HashMap m_body; // struct type → its body's digest (kConflict: files differ) slot_join::ParamSlots m_params; // IX1-V2: registrars' parameters stored into field slots - slot_join::GoTypeKeys m_goKeys; // fix1 F-2: Go type keys by package directory + slot_join::TypeKeys m_keys; // fix1 F-2 / part 2: Go and Rust type keys std::function( const Reference& )> m_ownersOf; // fix1 F-1: a bare callee's definitions std::vector m_calls; // slot Through references, ascending HashMap> m_callsBySym; // calling function → its slot Through references @@ -812,8 +930,17 @@ class ValueRefIndex && ( keep == nullptr || keep->names.contains( s.name ) ) ) { m_fnByName[ s.name ].push_back( id ); + if( !slotPathSep( s.lang ).empty() && !s.scope.empty() ) + { + // part 2: an owner type's function (`Editor::backspace`), as ownedBy looks it up (family, "Owner::name") + m_fnByOwner[ jsImportKey( static_cast( valueRefFamily( s.lang ) ), s.scope + "::" + s.name ) ].push_back( id ); + } } } + if( !m_fnByOwner.empty() || std::ranges::any_of( ing.symbols, []( const Symbol& s ) { return !slotPathSep( s.lang ).empty(); } ) ) + { + m_crates = slot_join::cratesOf( ing ); + } markPythonMembers(); for( std::uint32_t i = 0; i < ing.references.size(); ++i ) { @@ -836,7 +963,7 @@ class ValueRefIndex m_valuesByTarget[ t ].push_back( static_cast( k ) ); } } - m_slots.resolveCalleesBy( [ this ]( const Reference& v ) { return resolveName( v, v.recvVar ); } ); + m_slots.resolveCalleesBy( [ this ]( const Reference& v ) { return resolveCallee( v ); } ); m_slots.build( ing, m_values, m_targets ); m_slots.resolveCalleesBy( nullptr ); // it captured `this`: never outlives the build } @@ -912,8 +1039,11 @@ class ValueRefIndex { continue; } - const bool named = std::ranges::binary_search( names, std::string_view( r.calleeName ), rw::sortutil::svLess ); - if( !named && r.memberPath.empty() && !( sc == 'a' && registrars.contains( r.recvVar ) ) ) + const bool named = std::ranges::binary_search( names, std::string_view( r.calleeName ), rw::sortutil::svLess ); + const std::string_view sep = slotPathSep( r.lang ); + const std::size_t at = sep.empty() ? std::string::npos : r.recvVar.rfind( sep ); + const std::string leaf = at == std::string::npos ? r.recvVar : r.recvVar.substr( at + sep.size() ); // resolveCallee's name + if( !named && r.memberPath.empty() && !( sc == 'a' && registrars.contains( leaf ) ) ) { continue; } @@ -921,7 +1051,7 @@ class ValueRefIndex k.names.try_emplace( r.calleeName, char( 1 ) ); if( sc == 'a' ) { - k.names.try_emplace( r.recvVar, char( 1 ) ); + k.names.try_emplace( leaf, char( 1 ) ); } else if( sc == 'p' ) { @@ -1063,6 +1193,49 @@ class ValueRefIndex return fam == VrLangFamily::C && fIsFn && f.scope == m.scope; // a C++ member function of the same class } + // The method-value owner rule (rv-fn-slot part-2 piece 3): `fns` (one name's functions) owned by type `owner`, as reference + // `r`'s language means it. Go (a method value `b.opPlan` of the enclosing method's receiver, ingest_valuerefs.h methodValueAt): + // that type's METHOD in r's own package directory. Rust (`Editor::backspace`, `Self::f` with Self resolved): the owner type's + // function of that name nearest r (slot_join::nearestOf: its own file, else its crate, else the repo's only one), else nothing. + std::vector ownedBy( const Reference& r, std::string_view owner, std::span fns ) const + { + const VrLangFamily fam = valueRefFamily( r.lang ); + if( !slotPathSep( r.lang ).empty() ) + { + const auto it = fns.empty() ? m_fnByOwner.end() : m_fnByOwner.find( jsImportKey( static_cast( fam ), std::string( owner ) + "::" + m_ing.symbols[ fns.front() ].name ) ); + if( it == m_fnByOwner.end() ) + { + return {}; + } + const NodeId one = slot_join::nearestOf( m_ing, it->second, r.fileId, m_crates ); + return one == kNoNode ? std::vector{} : std::vector{ one }; + } + std::vector out; + for( const NodeId id : fns ) + { + const Symbol& s = m_ing.symbols[id]; + if( s.kind == SymKind::Method && valueRefFamily( s.lang ) == fam && s.scope == owner + && includerDir( m_ing.files[ s.fileId ] ) == includerDir( m_ing.files[ r.fileId ] ) ) + { + out.push_back( id ); + } + } + return out; + } + // fix1 F-1 / part-2 piece 4: the definitions an argument row's callee (recvVar) resolves to — a bare callee by resolveName; + // a path callee `Owner::name` (a language whose slot row has a pathSep) by ownedBy, never by its bare last segment. + std::vector resolveCallee( const Reference& v ) const + { + const std::string_view sep = slotPathSep( v.lang ); + const std::size_t at = sep.empty() ? std::string::npos : v.recvVar.rfind( sep ); + if( at == std::string::npos ) + { + return resolveName( v, v.recvVar ); + } + const auto it = m_fnByName.find( v.recvVar.substr( at + sep.size() ) ); + return it == m_fnByName.end() ? std::vector{} : ownedBy( v, std::string_view( v.recvVar ).substr( 0, at ), it->second ); + } + // `name` as seen from reference `r`'s file, under the visibility rules in the header comment. std::vector resolveName( const Reference& r, std::string_view name ) const { @@ -1074,19 +1247,7 @@ class ValueRefIndex const VrLangFamily fam = valueRefFamily( r.lang ); if( r.role == RefRole::Value && !r.memberRoot.empty() ) { - // IX1-V2: a Go method value `b.opPlan` of the enclosing method's receiver (ingest_valuerefs.h methodValueAt): that - // receiver type's method of this name in this package, else nothing - std::vector out; - for( const NodeId id : it->second ) - { - const Symbol& s = m_ing.symbols[id]; - if( s.kind == SymKind::Method && valueRefFamily( s.lang ) == fam && s.scope == r.memberRoot - && includerDir( m_ing.files[ s.fileId ] ) == includerDir( m_ing.files[ r.fileId ] ) ) - { - out.push_back( id ); - } - } - return out; + return ownedBy( r, r.memberRoot, it->second ); } // A C/C++ PROTOTYPE (a bodyless declaration) names the function its definition lives in: it is the target only // when no definition with a body is visible — a prototype in this file never hides the extern definition. @@ -1148,6 +1309,15 @@ class ValueRefIndex } break; } + case VrLangFamily::Rust: + { + // FN-SLOT part 2: a bare Rust function of another file — its crate's one, else the repo's only one (nearestOf) + if( const NodeId one = slot_join::nearestOf( m_ing, other, r.fileId, m_crates ); one != kNoNode ) + { + out.push_back( one ); + } + break; + } case VrLangFamily::Js: case VrLangFamily::Py: { @@ -1188,6 +1358,8 @@ class ValueRefIndex const IngestResult& m_ing; HashMap> m_fnByName; + HashMap> m_fnByOwner; // part 2: "family#Owner::name" → a path language's owned functions + std::vector m_crates; // part 2: slot_join::cratesOf, when a path language is indexed std::vector m_pyMember; // per symbol: 1 = a Python def directly in its class (empty: no Python) std::vector m_values; // Value reference indices, ascending std::vector m_depthCuts; // the files' depth-cut records (no row), ascending diff --git a/test/fnslotcheck.sh b/test/fnslotcheck.sh index 6d0ebd3f4..37812f9dd 100755 --- a/test/fnslotcheck.sh +++ b/test/fnslotcheck.sh @@ -31,6 +31,20 @@ # T1 [P] a named MAP type's keyed literal `Table{OpPut: putTab}` is no field slot (F-5); T1b [G] a struct's still is # N3b [G] an unnamed map literal `map[Op]func(){OpGet: getMap}` is no field slot (F-6; pins goValueSlot's ty.empty() guard) # Q1 [P] a slot_by= whose only store is in test code (`assertMatch( t, MatchV1 )`) is not printed on a non-test row (F-7) +# part 2 (lane fn-slot-2, Rust; fixture test/fnslotrust/, its own root; model.h kVrSlotLangs Rust row: ParenFieldCall, ModuleType): +# R1 [P] a registrar path call `CallEval::new( 1, eval_block )` → `Self { stack, eval }` → `(self.eval)( … )` in CallEval::run: +# called_by="run" via="slot" (typed: `Self` is the impl type on both sides), cands=3 +# R2 [P] a turbofish value `eval_block_generic::` is the function; R3 [P] a struct expression `CallEval { eval: direct_eval }` +# R4 [P] `Box::new( hook_target )` in `Hooks { eval: … }` lands in Hooks.eval (no Box::new argument row); R4b [G] no row into Box::new +# R5 [P] --callees: `(self.eval)( … )` prints the typed slot and its 3 targets +# R6 [P] a path call to a METHOD `Reg::set( r, path_reg_target )` (self = position 0) feeds `self.h = h`, called in Reg::go +# N8 [G] a local closure of a stored name (`let shadowed = |x| …; CallEval::new( 3, shadowed )`) is no function value: no row +# N9 [P] a same-named struct of ANOTHER crate (crates/twin CallEval) is another slot: eval_block's cands stays 3, twin_run targets 1 +# N10 [G] (N10a [P]) an enum variant `Msg::Fire { eval: variant_target }` is no struct field slot: no called_by= +# N11 [G] a method-call registrar `r.set( method_reg_target )` is a floor: no row +# N12 [P] `x.cb = loose_target` and `(y.cb)( … )` (types not read) join by the field name only: via="slot-name", --for `@~` +# X2 [G] a Go field of the same name (gox Box.eval) never joins a Rust slot (family guard), nor the reverse +# F3 [P] --for: slot_fields= per struct (eval=3@CallEval::run, the twin's eval=1@CallEval::twin_run), slot_by= on eval_block # # Usage: bash test/fnslotcheck.sh [BIN] (BIN defaults to RIPWIRE_BIN, then build/ripwire) set -u @@ -130,5 +144,43 @@ hasnt "N3b an unnamed map literal is no field slot" "$o" 'called_by=' o="$( run '--for=how is MatchV1 scored and matched' )" has "Q1a the MatchV1 row is shown" "$o" 'n="MatchV1" p="fzt/algo.go"' hasnt "Q1 a test-only store prints no slot_by=" "$( row "$o" MatchV1 fzt/algo.go )" 'slot_by=' + +FIX="$ROOT/test/fnslotrust" +[ -d "$FIX" ] || { echo " FAIL fixture dir missing: $FIX"; exit 2; } +o="$( run --callers=eval_block )" +has "R1 a registrar path call lands in the typed slot its parameter is stored in" "$o" 'into="CallEval::new#arg1" called_by="run" called_at="src/engine/eval.rs:14" via="slot" cands="3"' +o="$( run --callers=eval_block_generic )" +has "R2 a turbofish value is the function" "$o" 'into="CallEval::new#arg1" called_by="run"' +o="$( run --callers=direct_eval )" +has "R3 a struct expression's field value" "$o" 'into="CallEval.eval" called_by="run" called_at="src/engine/eval.rs:14" via="slot"' +o="$( run --callers=hook_target )" +has "R4 Box::new( f ) lands in the field" "$o" 'into="Hooks.eval" called_by="fire" called_at="src/other/hooks.rs:7" via="slot"' +hasnt "R4b no argument row into the Box::new wrapper" "$o" 'into="Box::new' +o="$( run --callees=CallEval::run )" +has "R5 the (self.eval)( … ) call prints its typed slot and targets" "$o" 'through="self.eval" slot="CallEval.eval" via="slot" targets="3"' +o="$( run --callers=path_reg_target )" +has "R6 a path call to a method counts self as position 0" "$o" 'into="Reg::set#arg1" called_by="go" called_at="crates/twin/src/lib.rs:60" via="slot"' +o="$( run --callers=shadowed )" +hasnt "N8 a local closure of a stored name is no function value" "$o" ' usize, +} + +impl CallEval { + pub fn new(eval: fn(&str) -> usize) -> Self { + CallEval { eval: eval } + } + pub fn twin_run(&self, s: &str) -> usize { + (self.eval)(s) + } +} + +pub fn twin_target(s: &str) -> usize { + s.len() + 3 +} + +pub fn twin_make() -> CallEval { + CallEval::new(twin_target) +} + +pub enum Msg { + Fire { eval: fn(&str) -> usize }, +} + +pub fn variant_target(s: &str) -> usize { + s.len() + 4 +} + +pub fn make_variant() -> Msg { + Msg::Fire { eval: variant_target } +} + +pub struct Loose { + pub cb: fn(&str) -> usize, +} + +pub fn loose_target(s: &str) -> usize { + s.len() + 5 +} + +pub fn set_loose(x: &mut Loose) { + x.cb = loose_target; +} + +pub fn call_loose(y: &Loose) -> usize { + (y.cb)("z") +} + +pub struct Reg { + pub h: fn(&str) -> usize, +} + +impl Reg { + pub fn set(&mut self, h: fn(&str) -> usize) { + self.h = h; + } + pub fn go(&self) -> usize { + (self.h)("q") + } +} + +pub fn method_reg_target(s: &str) -> usize { + s.len() + 6 +} + +pub fn use_method_reg(r: &mut Reg) { + r.set(method_reg_target); +} + +pub fn path_reg_target(s: &str) -> usize { + s.len() + 8 +} + +pub fn use_path_reg(r: &mut Reg) { + Reg::set(r, path_reg_target); +} diff --git a/test/fnslotrust/gox/g.go b/test/fnslotrust/gox/g.go new file mode 100644 index 000000000..f4bea4493 --- /dev/null +++ b/test/fnslotrust/gox/g.go @@ -0,0 +1,11 @@ +package gox + +type Box struct { + eval func(string) int +} + +func goTarget(s string) int { return len(s) } + +func makeBox() Box { return Box{eval: goTarget} } + +func (b Box) fire() int { return b.eval("x") } diff --git a/test/fnslotrust/src/engine/closure.rs b/test/fnslotrust/src/engine/closure.rs new file mode 100644 index 000000000..10518d1ce --- /dev/null +++ b/test/fnslotrust/src/engine/closure.rs @@ -0,0 +1,11 @@ +use super::eval::{eval_block, eval_block_generic, CallEval}; + +pub fn closure_eval(block: &str) -> usize { + let mut ce = CallEval::new(1, eval_block); + ce.run(block) +} + +pub fn closure_eval_generic(block: &str) -> usize { + let mut ce = CallEval::new(2, eval_block_generic::); + ce.run(block) +} diff --git a/test/fnslotrust/src/engine/eval.rs b/test/fnslotrust/src/engine/eval.rs new file mode 100644 index 000000000..739b4b1c7 --- /dev/null +++ b/test/fnslotrust/src/engine/eval.rs @@ -0,0 +1,32 @@ +pub type EvalFn = fn(&str) -> usize; + +pub struct CallEval { + stack: usize, + eval: EvalFn, +} + +impl CallEval { + pub fn new(stack: usize, eval: EvalFn) -> Self { + Self { stack, eval } + } + + pub fn run(&mut self, block: &str) -> usize { + (self.eval)(block) + self.stack + } +} + +pub fn eval_block(block: &str) -> usize { + block.len() +} + +pub fn eval_block_generic(block: &str) -> usize { + block.len() * 2 +} + +pub fn direct_eval(block: &str) -> usize { + block.len() + 1 +} + +pub fn make_direct() -> CallEval { + CallEval { stack: 0, eval: direct_eval } +} diff --git a/test/fnslotrust/src/main.rs b/test/fnslotrust/src/main.rs new file mode 100644 index 000000000..a128b5344 --- /dev/null +++ b/test/fnslotrust/src/main.rs @@ -0,0 +1,5 @@ +mod engine; +mod other; +fn main() { + println!("{}", engine::closure::closure_eval("x")); +} diff --git a/test/fnslotrust/src/other/hooks.rs b/test/fnslotrust/src/other/hooks.rs new file mode 100644 index 000000000..dcec32602 --- /dev/null +++ b/test/fnslotrust/src/other/hooks.rs @@ -0,0 +1,27 @@ +pub struct Hooks { + eval: Box usize>, +} + +impl Hooks { + pub fn fire(&self, s: &str) -> usize { + (self.eval)(s) + } +} + +pub fn hook_target(s: &str) -> usize { + s.len() + 7 +} + +pub fn make_hooks() -> Hooks { + Hooks { eval: Box::new(hook_target) } +} + +pub fn shadowed(s: &str) -> usize { + s.len() + 9 +} + +pub fn uses_local(s: &str) -> usize { + let shadowed = |x: &str| x.len(); + let ce = crate::engine::eval::CallEval::new(3, shadowed); + 0 +} From a1db566ad1297eff34f56db428c96fb6d38a2e1b Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:56:53 -0400 Subject: [PATCH 03/95] fn-slot part 2 S1 arms: crate-tier store (N13) and registrar owner identity (R7) Co-Authored-By: Claude Opus 5.5 --- test/fnslotcheck.sh | 24 ++++++++++++++++-------- test/fnslotrust/src/engine/closure.rs | 9 +++++++++ test/fnslotrust/src/other/hooks.rs | 4 ++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/test/fnslotcheck.sh b/test/fnslotcheck.sh index 37812f9dd..24f1f6375 100755 --- a/test/fnslotcheck.sh +++ b/test/fnslotcheck.sh @@ -33,18 +33,20 @@ # Q1 [P] a slot_by= whose only store is in test code (`assertMatch( t, MatchV1 )`) is not printed on a non-test row (F-7) # part 2 (lane fn-slot-2, Rust; fixture test/fnslotrust/, its own root; model.h kVrSlotLangs Rust row: ParenFieldCall, ModuleType): # R1 [P] a registrar path call `CallEval::new( 1, eval_block )` → `Self { stack, eval }` → `(self.eval)( … )` in CallEval::run: -# called_by="run" via="slot" (typed: `Self` is the impl type on both sides), cands=3 +# called_by="run" via="slot" (typed: `Self` is the impl type on both sides), cands=4 # R2 [P] a turbofish value `eval_block_generic::` is the function; R3 [P] a struct expression `CallEval { eval: direct_eval }` # R4 [P] `Box::new( hook_target )` in `Hooks { eval: … }` lands in Hooks.eval (no Box::new argument row); R4b [G] no row into Box::new -# R5 [P] --callees: `(self.eval)( … )` prints the typed slot and its 3 targets +# R5 [P] --callees: `(self.eval)( … )` prints the typed slot and its 4 targets # R6 [P] a path call to a METHOD `Reg::set( r, path_reg_target )` (self = position 0) feeds `self.h = h`, called in Reg::go # N8 [G] a local closure of a stored name (`let shadowed = |x| …; CallEval::new( 3, shadowed )`) is no function value: no row -# N9 [P] a same-named struct of ANOTHER crate (crates/twin CallEval) is another slot: eval_block's cands stays 3, twin_run targets 1 +# N9 [P] a same-named struct of ANOTHER crate (crates/twin CallEval) is another slot: eval_block's cands stays 4, twin_run targets 1 # N10 [G] (N10a [P]) an enum variant `Msg::Fire { eval: variant_target }` is no struct field slot: no called_by= # N11 [G] a method-call registrar `r.set( method_reg_target )` is a floor: no row # N12 [P] `x.cb = loose_target` and `(y.cb)( … )` (types not read) join by the field name only: via="slot-name", --for `@~` +# N13 [P] a store in another file of the crate keys the crate's own CallEval (crate tier), typed: via="slot" +# R7 [G] a registrar's owner type decides it: `Hooks::new` (stores into Hooks.eval) never feeds CallEval.eval, nor the reverse # X2 [G] a Go field of the same name (gox Box.eval) never joins a Rust slot (family guard), nor the reverse -# F3 [P] --for: slot_fields= per struct (eval=3@CallEval::run, the twin's eval=1@CallEval::twin_run), slot_by= on eval_block +# F3 [P] --for: slot_fields= per struct (eval=4@CallEval::run, the twin's eval=1@CallEval::twin_run), slot_by= on eval_block # # Usage: bash test/fnslotcheck.sh [BIN] (BIN defaults to RIPWIRE_BIN, then build/ripwire) set -u @@ -148,16 +150,16 @@ hasnt "Q1 a test-only store prints no slot_by=" "$( row "$o" MatchV1 fzt/algo.go FIX="$ROOT/test/fnslotrust" [ -d "$FIX" ] || { echo " FAIL fixture dir missing: $FIX"; exit 2; } o="$( run --callers=eval_block )" -has "R1 a registrar path call lands in the typed slot its parameter is stored in" "$o" 'into="CallEval::new#arg1" called_by="run" called_at="src/engine/eval.rs:14" via="slot" cands="3"' +has "R1 a registrar path call lands in the typed slot its parameter is stored in" "$o" 'into="CallEval::new#arg1" called_by="run" called_at="src/engine/eval.rs:14" via="slot" cands="4"' o="$( run --callers=eval_block_generic )" has "R2 a turbofish value is the function" "$o" 'into="CallEval::new#arg1" called_by="run"' o="$( run --callers=direct_eval )" has "R3 a struct expression's field value" "$o" 'into="CallEval.eval" called_by="run" called_at="src/engine/eval.rs:14" via="slot"' o="$( run --callers=hook_target )" -has "R4 Box::new( f ) lands in the field" "$o" 'into="Hooks.eval" called_by="fire" called_at="src/other/hooks.rs:7" via="slot"' +has "R4 Box::new( f ) lands in the field" "$o" 'into="Hooks.eval" called_by="fire" called_at="src/other/hooks.rs:11" via="slot"' hasnt "R4b no argument row into the Box::new wrapper" "$o" 'into="Box::new' o="$( run --callees=CallEval::run )" -has "R5 the (self.eval)( … ) call prints its typed slot and targets" "$o" 'through="self.eval" slot="CallEval.eval" via="slot" targets="3"' +has "R5 the (self.eval)( … ) call prints its typed slot and targets" "$o" 'through="self.eval" slot="CallEval.eval" via="slot" targets="4"' o="$( run --callers=path_reg_target )" has "R6 a path call to a method counts self as position 0" "$o" 'into="Reg::set#arg1" called_by="go" called_at="crates/twin/src/lib.rs:60" via="slot"' o="$( run --callers=shadowed )" @@ -174,11 +176,17 @@ o="$( run --callers=method_reg_target )" hasnt "N11 a method-call registrar is a floor" "$o" ' usize { let mut ce = CallEval::new(2, eval_block_generic::); ce.run(block) } + +pub fn crate_tier_target(block: &str) -> usize { + block.len() + 11 +} + +// a store in ANOTHER file of the crate: CallEval is this crate's (crates/twin has its own), keyed by the crate tier +pub fn closure_direct() -> CallEval { + CallEval { stack: 9, eval: crate_tier_target } +} diff --git a/test/fnslotrust/src/other/hooks.rs b/test/fnslotrust/src/other/hooks.rs index dcec32602..37de1e518 100644 --- a/test/fnslotrust/src/other/hooks.rs +++ b/test/fnslotrust/src/other/hooks.rs @@ -3,6 +3,10 @@ pub struct Hooks { } impl Hooks { + pub fn new(eval: fn(&str) -> usize) -> Self { + Hooks { eval: Box::new(eval) } + } + pub fn fire(&self, s: &str) -> usize { (self.eval)(s) } From 76dc2a6549c9e461e8be24b281bce19da0c17900 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:03:01 -0400 Subject: [PATCH 04/95] fn-slot part 2 qd: reuse nodeTextOf / rawRefAt, index owners in their own step (ValueRefIndex::index complexity back to 14) Co-Authored-By: Claude Opus 5.5 --- src/ingest_rustslots.h | 39 ++++++++------------------------------- src/valuerefindex.h | 33 ++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/ingest_rustslots.h b/src/ingest_rustslots.h index 955463c78..a9f3571a3 100644 --- a/src/ingest_rustslots.h +++ b/src/ingest_rustslots.h @@ -177,7 +177,7 @@ class RustSlotWalk } void leave( TSNode n ) { - if( kind( n, Impl ) && !m_impl.empty() ) + if( kind( n, Impl ) && m_impl.size() > 1 ) { m_impl.pop_back(); } @@ -226,15 +226,7 @@ class RustSlotWalk m_out.push_back( std::move( r ) ); } - std::string_view text( TSNode n ) const noexcept - { - if( ts_node_is_null( n ) ) - { - return {}; - } - const std::uint32_t a = ts_node_start_byte( n ), b = ts_node_end_byte( n ); - return a <= b && b <= m_src.size() ? m_src.substr( a, b - a ) : std::string_view(); - } + std::string_view text( TSNode n ) const noexcept { return ts_node_is_null( n ) ? std::string_view() : nodeTextOf( n, m_src ); } static TSNode field( TSNode n, std::string_view name ) noexcept { return ts_node_child_by_field_name( n, name.data(), static_cast( name.size() ) ); @@ -254,7 +246,7 @@ class RustSlotWalk } return is( t, "type_identifier" ) ? text( t ) : std::string_view(); } - std::string_view implType() const noexcept { return m_impl.empty() ? std::string_view() : m_impl.back(); } + std::string_view implType() const noexcept { return m_impl.back(); } // m_impl starts with "": outside every impl // The function's name and its parameters in position order (their names are bound; the body's patterns bind more as the // walk reaches them). @@ -386,16 +378,6 @@ class RustSlotWalk return out; } - RawRef base( TSNode at ) const - { - RawRef r; - r.fileId = m_fileId; - r.startByte = ts_node_start_byte( at ); - r.line = ts_node_start_point( at ).row + 1; - r.lang = m_lang; - return r; - } - // `callee( … )` with a path callee: each function-path argument is an argument row; `(x.f)( … )` is a call through field f. void callAt( TSNode call ) { @@ -444,9 +426,7 @@ class RustSlotWalk const Named n = named( a ); if( !n.name.empty() ) { - RawRef r = base( a ); - r.role = RefRole::Value; - r.name = std::string( n.name ); + RawRef r = rawRefAt( a, m_fileId, m_lang, RefRole::Value, std::string( n.name ) ); r.memberRoot = n.owner; r.qualifier = "a0"; r.recvVar = written; @@ -480,8 +460,7 @@ class RustSlotWalk { return; // a tuple field `(x.0)( … )`, a call result's field: a floor } - RawRef r = base( call ); - r.role = RefRole::Through; + RawRef r = rawRefAt( call, m_fileId, m_lang, RefRole::Through, std::string() ); r.qualifier = std::string( 1, kValueRefSlotCallScope ); r.name = std::string( text( root ) ); r.fieldName = std::string( text( f ) ); @@ -566,8 +545,7 @@ class RustSlotWalk void storeAt( TSNode at, TSNode value, std::string_view fieldName, const std::string& ty ) { const Named n = named( value ); - RawRef r = base( at ); - r.role = RefRole::Value; + RawRef r = rawRefAt( at, m_fileId, m_lang, RefRole::Value, std::string() ); r.memberPath = std::string( fieldName ); r.memberCtor = ty; if( n.name.empty() ) @@ -586,7 +564,7 @@ class RustSlotWalk std::uint32_t m_fileId; std::string_view m_src; std::vector& m_out; - std::vector m_impl; // the enclosing impl blocks' type names (`Self`) + std::vector m_impl { std::string_view() }; // "" then the enclosing impl blocks' type names (`Self`) std::vector m_fns; // the enclosing functions std::array m_sym {}; // kKinds' grammar symbols VrDepthCut m_depthCut; @@ -596,8 +574,7 @@ class RustSlotWalk inline void captureRustSlotRefs( Lang lang, std::uint32_t fileId, std::string_view src, TSNode root, std::vector& refs ) { PROFILE_SCOPE_DESCRIBE( "ingest/extractFile: Rust slot records" ); - RustSlotWalk walk( lang, fileId, src, refs ); - walk.run( root ); + RustSlotWalk( lang, fileId, src, refs ).run( root ); } } // namespace diff --git a/src/valuerefindex.h b/src/valuerefindex.h index 3e5fb2152..289e44f26 100644 --- a/src/valuerefindex.h +++ b/src/valuerefindex.h @@ -930,17 +930,9 @@ class ValueRefIndex && ( keep == nullptr || keep->names.contains( s.name ) ) ) { m_fnByName[ s.name ].push_back( id ); - if( !slotPathSep( s.lang ).empty() && !s.scope.empty() ) - { - // part 2: an owner type's function (`Editor::backspace`), as ownedBy looks it up (family, "Owner::name") - m_fnByOwner[ jsImportKey( static_cast( valueRefFamily( s.lang ) ), s.scope + "::" + s.name ) ].push_back( id ); - } } } - if( !m_fnByOwner.empty() || std::ranges::any_of( ing.symbols, []( const Symbol& s ) { return !slotPathSep( s.lang ).empty(); } ) ) - { - m_crates = slot_join::cratesOf( ing ); - } + indexOwners( keep ); markPythonMembers(); for( std::uint32_t i = 0; i < ing.references.size(); ++i ) { @@ -967,6 +959,29 @@ class ValueRefIndex m_slots.build( ing, m_values, m_targets ); m_slots.resolveCalleesBy( nullptr ); // it captured `this`: never outlives the build } + // part 2: a path language's functions by owner type (`Editor::backspace`, ownedBy's "family#Owner::name"), and every file's + // crate (slot_join::cratesOf) when such a language is indexed. + void indexOwners( const KeepSets* keep ) + { + bool pathLang = false; + for( NodeId id = 0; id < m_ing.symbols.size(); ++id ) + { + const Symbol& s = m_ing.symbols[id]; + if( slotPathSep( s.lang ).empty() ) + { + continue; + } + pathLang = true; + if( ( s.kind == SymKind::Function || s.kind == SymKind::Method ) && !s.scope.empty() && ( keep == nullptr || keep->names.contains( s.name ) ) ) + { + m_fnByOwner[ jsImportKey( static_cast( valueRefFamily( s.lang ) ), s.scope + "::" + s.name ) ].push_back( id ); + } + } + if( pathLang ) + { + m_crates = slot_join::cratesOf( m_ing ); + } + } // Reference `i` into its table (`keep` non-null: a narrow index, which skips what the splice never reads). void indexReference( std::uint32_t i, const KeepSets* keep ) { From 08fccca05fcb704888538f2d7f60f720f29586b7 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:06:10 -0400 Subject: [PATCH 05/95] fn-slot part 2 qd: RustSlotWalk::text is nodeTextOf (null-safe already) Co-Authored-By: Claude Opus 5.5 --- src/ingest_rustslots.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ingest_rustslots.h b/src/ingest_rustslots.h index a9f3571a3..7b6618654 100644 --- a/src/ingest_rustslots.h +++ b/src/ingest_rustslots.h @@ -226,7 +226,7 @@ class RustSlotWalk m_out.push_back( std::move( r ) ); } - std::string_view text( TSNode n ) const noexcept { return ts_node_is_null( n ) ? std::string_view() : nodeTextOf( n, m_src ); } + std::string_view text( TSNode n ) const noexcept { return nodeTextOf( n, m_src ); } // "" for a null node static TSNode field( TSNode n, std::string_view name ) noexcept { return ts_node_child_by_field_name( n, name.data(), static_cast( name.size() ) ); From 2b7667932acbfe1eda7397e5d63464b335e446d4 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:41:04 -0400 Subject: [PATCH 06/95] fn-slot part 2: qschemetripcheck re-pinned once for kParserVer 247 -> 249 (RE-PIN LOG entry) Co-Authored-By: Claude Opus 5.5 --- test/qschemetrip.hash | 2 +- test/qschemetripcheck.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/qschemetrip.hash b/test/qschemetrip.hash index 25fb922b1..399f32e3f 100644 --- a/test/qschemetrip.hash +++ b/test/qschemetrip.hash @@ -1 +1 @@ -18dfbcac35d5cea56122f4750ca92c8193a595053a4e7dd38174b59f68b05e8e +127443d5853d366009d7971ab94a339873c88f04587861d1a9bb73d8b396de6d diff --git a/test/qschemetripcheck.sh b/test/qschemetripcheck.sh index 0f94b63c4..14e76e9a2 100755 --- a/test/qschemetripcheck.sh +++ b/test/qschemetripcheck.sh @@ -34,6 +34,10 @@ 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-27, lane fn-slot-2 (part 2: Rust slots, base lane/fn-slot f2e6ef1f): RE-PINNED ONCE with UPDATE_GOLDEN=1 (18dfbcac… -> 127443d5…). +# Watched text: kParserVer 247 -> 249, a PLACEHOLDER (train 41 renumbers; Rust files now record slot-join value rows, so the dead +# set is extraction-dependent), its quality.h mirror with it. kCacheVersion stays 44 (no record changes shape). +# kQSnapCacheScheme stays 24: no watched quality.h function moved. # 2026-10-27, lane fn-slot (IX1-V2, base integration/train-39 3d03da53): RE-PINNED ONCE with UPDATE_GOLDEN=1 (b4eb6e92… -> 18dfbcac…). # Watched text: kParserVer 239 -> 247, a PLACEHOLDER (train 40 renumbers; Go value refs now include field assignments, method # values and registrar-parameter slot facts, so the dead set is extraction-dependent), its quality.h mirror with it. From 906d8279e2ddf624743420e933c1ab76f5cc4f37 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:30:45 -0400 Subject: [PATCH 07/95] fn-slot part 2: optremarks HOT_FILES lists src/ingest_rustslots.h (optremarkshotcheck, CI run 40599859070) Co-Authored-By: Claude Opus 5.5 --- scripts/optremarks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/optremarks.py b/scripts/optremarks.py index 796d13d68..b1017c7f7 100755 --- a/scripts/optremarks.py +++ b/scripts/optremarks.py @@ -82,6 +82,7 @@ "src/ingest_metrics.h", # cc_walk / complexityOf — a per-symbol AST walk for the quality metrics "src/ingest_cache.h", # loadCache / saveCache / readFileRecord, per file record — ~10% of a WARM run, which is the run an agent actually pays for "src/ingest_valuerefs.h", # the reference-as-value walk, per node over every C/JS/TS/Python/Go file — +3.6..6.3% of a cold run + "src/ingest_rustslots.h", # the Rust slot-join walk, per node over every Rust file — ~+5% of a cold zed run (lane fn-slot-2) "src/ingest_model.h", # build-model: dedup, symbol-id assignment, the def-span index, the ref radix sort — per symbol and per reference, ~18% warm "src/resolve.h", # reference resolution into the call graph From 1ff506826e8a6cbee38bcacede3d8703bf067774 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:15:55 -0400 Subject: [PATCH 08/95] fn-slot part 2 fix 1: arms E1-E4, Q1-Q4, N14, N15, W1 + fixture test/fnslotrust2 (RED on 906d8279) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E*/Q*/W1 are red on the lane head (F-1 external paths, F-2 qualified paths, F-4 wrapper by last segment); N14 / N15 are green on head and red on the review's source mutants MG1 (nearestOf guesses in an ambiguous tier) and MG2 (slotCallAt types any self.… chain). Co-Authored-By: Claude Opus 5.5 --- test/fnslotcheck.sh | 51 +++++++++++++++++++++++ test/fnslotrust2/Cargo.toml | 2 + test/fnslotrust2/crates/app/src/main.rs | 10 +++++ test/fnslotrust2/crates/lib/src/lib.rs | 7 ++++ test/fnslotrust2/crates/qual/src/a.rs | 6 +++ test/fnslotrust2/crates/qual/src/c.rs | 8 ++++ test/fnslotrust2/crates/qual/src/here.rs | 11 +++++ test/fnslotrust2/crates/qual/src/lib.rs | 1 + test/fnslotrust2/crates/qual/src/nest.rs | 8 ++++ test/fnslotrust2/crates/qual/src/other.rs | 2 + test/fnslotrust2/crates/qual/src/p.rs | 2 + test/fnslotrust2/crates/qual/src/q.rs | 2 + test/fnslotrust2/crates/qual/src/r.rs | 4 ++ test/fnslotrust2/crates/qual/src/wrap.rs | 4 ++ 14 files changed, 118 insertions(+) create mode 100644 test/fnslotrust2/Cargo.toml create mode 100644 test/fnslotrust2/crates/app/src/main.rs create mode 100644 test/fnslotrust2/crates/lib/src/lib.rs create mode 100644 test/fnslotrust2/crates/qual/src/a.rs create mode 100644 test/fnslotrust2/crates/qual/src/c.rs create mode 100644 test/fnslotrust2/crates/qual/src/here.rs create mode 100644 test/fnslotrust2/crates/qual/src/lib.rs create mode 100644 test/fnslotrust2/crates/qual/src/nest.rs create mode 100644 test/fnslotrust2/crates/qual/src/other.rs create mode 100644 test/fnslotrust2/crates/qual/src/p.rs create mode 100644 test/fnslotrust2/crates/qual/src/q.rs create mode 100644 test/fnslotrust2/crates/qual/src/r.rs create mode 100644 test/fnslotrust2/crates/qual/src/wrap.rs diff --git a/test/fnslotcheck.sh b/test/fnslotcheck.sh index 24f1f6375..aade480c7 100755 --- a/test/fnslotcheck.sh +++ b/test/fnslotcheck.sh @@ -47,6 +47,21 @@ # R7 [G] a registrar's owner type decides it: `Hooks::new` (stores into Hooks.eval) never feeds CallEval.eval, nor the reverse # X2 [G] a Go field of the same name (gox Box.eval) never joins a Rust slot (family guard), nor the reverse # F3 [P] --for: slot_fields= per struct (eval=4@CallEval::run, the twin's eval=1@CallEval::twin_run), slot_by= on eval_block +# fix round 1 (review rv-fn-slot-2 F-1..F-4; fixture test/fnslotrust2/, its own root: crates app, lib, qual): +# E1 [P] `use extcrate::Hook; Hook { cb: f }` is an out-of-tree struct: never the in-tree Hook.cb (F-1) +# E2 [P] `use tokio::spawn; spawn( f )` is an out-of-tree registrar: never the in-tree `spawn` that stores into Hook.cb (F-1) +# E3 [P] `std::thread::spawn( f )` is std's: never the in-tree `spawn` (F-1); E4 [G] the in-tree Hook keeps its own store +# Q1 [P] `crate::a::Cfg { cb: f }` written in c.rs (which declares its own Cfg) is a.rs's Cfg: called_by="go" (F-2) +# Q1b [G] the twin: c.rs's bare `Cfg { cb: f }` is its own Cfg (called_by="local_go") +# Q2 [P] `crate::other::register( f )` beside a local storing `register` is other.rs's (stores nothing): no called_by= (F-2) +# Q3 [P] `S::new( crate::other::target )` is other.rs's target, never here.rs's namesake (F-2); Q3b [G] the bare twin +# `register( local_t )` still lands in S.f +# Q4 [P] `crate::p::Amb { f }` with two Amb in the crate is p.rs's (the path names the module file): called_by="go_p", typed +# N14 [G] a BARE `Amb { f }` with two Amb in the crate (neither in the writing file) is never typed: via="slot-name" — kills +# the source mutant "nearestOf guesses within an ambiguous tier" (review MG1) +# N15 [G] `(self.inner.f)( … )` is never typed as Self's field: in_t keeps its caller, out_t (Outer.f) is never via="slot" — +# kills the source mutant "slotCallAt types any self.… chain" (review MG2) +# W1 [P] `std::sync::Arc::new( f )` is the wrapper by its LAST path segment: the value lands in Dyn.g (F-4) # # Usage: bash test/fnslotcheck.sh [BIN] (BIN defaults to RIPWIRE_BIN, then build/ripwire) set -u @@ -190,5 +205,41 @@ has "F3 slot_fields= on the crate's CallEval" "$( row "$o" CallEval src/engine has "F3b slot_fields= on the twin crate's CallEval" "$( row "$o" CallEval crates/twin/src/lib.rs )" 'slot_fields="eval=1@CallEval::twin_run"' has "F3c slot_by= on the stored function (typed: no ~)" "$( row "$o" eval_block src/engine/eval.rs )" 'slot_by="CallEval::run"' has "X2b the Go struct row keeps its own name-tier caller" "$( row "$o" Box gox/g.go )" 'slot_fields="eval=1@~Box.fire"' +FIX="$ROOT/test/fnslotrust2" +[ -d "$FIX" ] || { echo " FAIL fixture dir missing: $FIX"; exit 2; } +o="$( run --callers=worker_ext_struct )" +has "E1a the out-of-tree struct store still prints" "$o" 'into="Hook.cb"' +hasnt "E1 an out-of-tree struct is never the in-tree Hook" "$o" 'called_by=' +o="$( run --callers=worker_bare_ext )" +has "E2a the argument row still prints" "$o" 'into="spawn#arg0"' +hasnt "E2 a used out-of-tree registrar is never the in-tree spawn" "$o" 'called_by=' +o="$( run --callers=worker_path_ext )" +hasnt "E3 std::thread::spawn is never the in-tree spawn" "$o" 'called_by=' +o="$( run --callers=lib_target )" +has "E4 the in-tree Hook keeps its own store" "$o" 'into="Hook.cb" called_by="fire" called_at="crates/lib/src/lib.rs:3" via="slot"' +o="$( run --callers=fc_qualified )" +has "Q1 a crate:: path to a struct is the module file's struct" "$o" 'called_by="go" called_at="crates/qual/src/a.rs:3" via="slot"' +hasnt "Q1c never the writing file's own namesake" "$o" 'local_go' +o="$( run --callers=fc_local )" +has "Q1b the bare twin is the file's own struct" "$o" 'called_by="local_go" called_at="crates/qual/src/c.rs:3" via="slot"' +o="$( run --callers=qual_t )" +has "Q2a the argument row still prints" "$o" 'into="register#arg0"' +hasnt "Q2 a crate:: registrar path is never the local namesake" "$o" 'called_by=' +o="$( run --callees=S::go )" +has "Q3 a crate:: value path is the module file's function" "$o" '' +hasnt "Q3c never the writing file's own namesake" "$o" '' +has "Q3b the bare registrar twin still lands in S.f" "$o" '' +o="$( run --callers=amb_t )" +has "Q4 crate::p::Amb is p.rs's Amb, typed" "$o" 'into="Amb.f" called_by="go_p" called_at="crates/qual/src/p.rs:2" via="slot"' +o="$( run --callers=amb_bare_t )" +has "N14a the bare store joins by the field name" "$o" 'via="slot-name"' +hasnt "N14 an ambiguous crate tier is never typed" "$o" 'via="slot"' +o="$( run --callers=in_t )" +has "N15a (self.inner.f)( … ) keeps Inner.f's caller" "$o" 'into="Inner.f" called_by="go_inner"' +o="$( run --callers=out_t )" +hasnt "N15 (self.inner.f)( … ) is never typed as Self's field" "$o" 'via="slot"' +o="$( run --callers=arc_t )" +has "W1 std::sync::Arc::new( f ) hands the value to the field" "$o" 'into="Dyn.g" called_by="go" called_at="crates/qual/src/wrap.rs:2" via="slot"' +hasnt "W1b no argument row into the Arc::new wrapper" "$o" 'into="Arc::new' if [ "$fail" -eq 0 ]; then echo "fnslotcheck: PASS"; else echo "fnslotcheck: FAIL"; fi exit "$fail" diff --git a/test/fnslotrust2/Cargo.toml b/test/fnslotrust2/Cargo.toml new file mode 100644 index 000000000..739e1bc05 --- /dev/null +++ b/test/fnslotrust2/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["crates/app", "crates/lib", "crates/qual"] diff --git a/test/fnslotrust2/crates/app/src/main.rs b/test/fnslotrust2/crates/app/src/main.rs new file mode 100644 index 000000000..c496c2e9b --- /dev/null +++ b/test/fnslotrust2/crates/app/src/main.rs @@ -0,0 +1,10 @@ +use extcrate::Hook; +use tokio::spawn; +fn worker_ext_struct() {} +fn worker_bare_ext() {} +fn worker_path_ext() {} +fn main() { + let _h = Hook { cb: worker_ext_struct }; + spawn(worker_bare_ext); + std::thread::spawn(worker_path_ext); +} diff --git a/test/fnslotrust2/crates/lib/src/lib.rs b/test/fnslotrust2/crates/lib/src/lib.rs new file mode 100644 index 000000000..1018ec6e7 --- /dev/null +++ b/test/fnslotrust2/crates/lib/src/lib.rs @@ -0,0 +1,7 @@ +pub struct Hook { cb: fn() } +impl Hook { + pub fn fire(&self) { (self.cb)() } +} +pub fn spawn(f: fn()) -> Hook { Hook { cb: f } } +pub fn lib_target() {} +pub fn mk() -> Hook { Hook { cb: lib_target } } diff --git a/test/fnslotrust2/crates/qual/src/a.rs b/test/fnslotrust2/crates/qual/src/a.rs new file mode 100644 index 000000000..0e56f85c5 --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/a.rs @@ -0,0 +1,6 @@ +pub struct Cfg { pub cb: fn() } +impl Cfg { + pub fn go(&self) { (self.cb)() } +} +pub fn fa() {} +pub fn mka() -> Cfg { Cfg { cb: fa } } diff --git a/test/fnslotrust2/crates/qual/src/c.rs b/test/fnslotrust2/crates/qual/src/c.rs new file mode 100644 index 000000000..255fece60 --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/c.rs @@ -0,0 +1,8 @@ +pub struct Cfg { pub cb: fn() } +impl Cfg { + pub fn local_go(&self) { (self.cb)() } +} +pub fn fc_local() {} +pub fn fc_qualified() {} +pub fn mk_local() -> Cfg { Cfg { cb: fc_local } } +pub fn mk_qualified() -> crate::a::Cfg { crate::a::Cfg { cb: fc_qualified } } diff --git a/test/fnslotrust2/crates/qual/src/here.rs b/test/fnslotrust2/crates/qual/src/here.rs new file mode 100644 index 000000000..7dcc7713a --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/here.rs @@ -0,0 +1,11 @@ +pub struct S { f: fn() } +impl S { pub fn new(f: fn()) -> Self { S { f } } pub fn go(&self) { (self.f)() } } +pub fn target() {} +pub fn register(f: fn()) -> S { S { f } } +pub fn local_t() {} +pub fn qual_t() {} +pub fn uses() { + let _a = S::new(crate::other::target); + let _b = crate::other::register(qual_t); + let _c = register(local_t); +} diff --git a/test/fnslotrust2/crates/qual/src/lib.rs b/test/fnslotrust2/crates/qual/src/lib.rs new file mode 100644 index 000000000..2d58ef2a6 --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/lib.rs @@ -0,0 +1 @@ +pub mod a; pub mod c; pub mod other; pub mod here; pub mod p; pub mod q; pub mod r; pub mod nest; pub mod wrap; diff --git a/test/fnslotrust2/crates/qual/src/nest.rs b/test/fnslotrust2/crates/qual/src/nest.rs new file mode 100644 index 000000000..02bb0a7a2 --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/nest.rs @@ -0,0 +1,8 @@ +pub struct Inner { pub f: fn() } +pub struct Outer { pub inner: Inner, pub f: fn() } +impl Outer { + pub fn go_inner(&self) { (self.inner.f)() } +} +pub fn in_t() {} +pub fn out_t() {} +pub fn mk() -> Outer { Outer { inner: Inner { f: in_t }, f: out_t } } diff --git a/test/fnslotrust2/crates/qual/src/other.rs b/test/fnslotrust2/crates/qual/src/other.rs new file mode 100644 index 000000000..99753fe7c --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/other.rs @@ -0,0 +1,2 @@ +pub fn target() {} +pub fn register(f: fn()) { f() } diff --git a/test/fnslotrust2/crates/qual/src/p.rs b/test/fnslotrust2/crates/qual/src/p.rs new file mode 100644 index 000000000..05b42db87 --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/p.rs @@ -0,0 +1,2 @@ +pub struct Amb { pub f: fn() } +impl Amb { pub fn go_p(&self) { (self.f)() } } diff --git a/test/fnslotrust2/crates/qual/src/q.rs b/test/fnslotrust2/crates/qual/src/q.rs new file mode 100644 index 000000000..c0da79080 --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/q.rs @@ -0,0 +1,2 @@ +pub struct Amb { pub f: fn() } +impl Amb { pub fn go_q(&self) { (self.f)() } } diff --git a/test/fnslotrust2/crates/qual/src/r.rs b/test/fnslotrust2/crates/qual/src/r.rs new file mode 100644 index 000000000..86a0a3126 --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/r.rs @@ -0,0 +1,4 @@ +pub fn amb_t() {} +pub fn amb_bare_t() {} +pub fn mk() -> crate::p::Amb { crate::p::Amb { f: amb_t } } +pub fn mk_bare() { let _x = Amb { f: amb_bare_t }; } diff --git a/test/fnslotrust2/crates/qual/src/wrap.rs b/test/fnslotrust2/crates/qual/src/wrap.rs new file mode 100644 index 000000000..c2e8eedd0 --- /dev/null +++ b/test/fnslotrust2/crates/qual/src/wrap.rs @@ -0,0 +1,4 @@ +pub struct Dyn { g: std::sync::Arc } +impl Dyn { pub fn go(&self) { (self.g)() } } +pub fn arc_t() {} +pub fn mk() -> Dyn { Dyn { g: std::sync::Arc::new(arc_t) } } From 6572a6b4349287a15f5abfc10e71eee51f6c8d7f Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:22:45 -0400 Subject: [PATCH 09/95] fn-slot part 2 fix 1 F-1 + F-2: a Rust path reaches only what its module path and the file's use records say ingest_rustslots.h keeps the written module path: a value's memberVia, a path callee's recvVar (crate::other::register, std::thread::spawn), a struct type's memberCtor (crate::a::Cfg); into= still shows the short spelling. valuerefindex.h slot_join::RustPaths (replaces nearestOf's free function) reads that path against the file's L3 Rust import records (ing.includes) and the in-tree crates: std/core/alloc, an out-of-tree crate, a use rename or an unknown first segment reach nothing; crate::/self::/super::/mod a; a::x reach the ONE file resolve.h resolveRustImport resolves (this crate's root, never the writing file's namesake); a path led by an in-tree crate's name that crate; a bare name with no use: own file, crate, then the repo-unique tier only when the file uses the defining crate. RustTypeKeys, ownedBy and resolveName / resolveCallee all read it. kParserVer stays 249 (the lane's unshipped placeholder). Arms E1-E3, Q1-Q4 green; fnslotcheck 89/89. Co-Authored-By: Claude Opus 5.5 --- src/ingest_rustslots.h | 49 +++-- src/valuerefindex.h | 466 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 460 insertions(+), 55 deletions(-) diff --git a/src/ingest_rustslots.h b/src/ingest_rustslots.h index 7b6618654..2a7395be7 100644 --- a/src/ingest_rustslots.h +++ b/src/ingest_rustslots.h @@ -13,11 +13,13 @@ // `f::` with its turbofish dropped): Value, scope 'a', recvVar = the callee as written with `Self` resolved to the impl // type ("CallEval::new"), argCount = the argument's position (a `self` parameter is position 0 of a path call), name = the // function's last segment, memberRoot = its owner TYPE when the path names one (`Editor` in `Editor::backspace`; a module -// path `eval::eval_block` names no owner). A method call's arguments (`cx.listener( Self::f )`) are a floor: no row. +// path `eval::eval_block` names no owner), memberVia = the module path written before the owner or the function +// (`crate::other`, `std::thread`, "" bare: valuerefindex.h slot_join::RustPaths decides where it can reach; recvVar keeps +// the callee's own). A method call's arguments (`cx.listener( Self::f )`) are a floor: no row. // * a function path stored into a struct FIELD — a struct expression `T { f: path }` / `T { path }` (shorthand), the value // unwrapped from `Box::new( … )` / `Rc::new( … )` / `Arc::new( … )`, or an assignment `self.f = path` / `x.f = path`: -// Value, scope 'x', memberPath = the field, memberCtor = the struct type as written, its last segment when a module path -// leads it (`Self` → the impl type; `x.f`: "" — x's type is not read, the name tier). +// Value, scope 'x', memberPath = the field, memberCtor = the struct type as written, its module path kept (`Self` → the +// impl type; `x.f`: "" — x's type is not read, the name tier), memberVia = the value's module path. // * a PARAMETER stored the same way (`fn new( eval: EvalFn ) -> Self { Self { eval } }`): the slot fact of a registrar // (model.h kValueRefSlotParamScope), recvVar = the function's name, argCount = the parameter's position. // * a call through a field `(self.eval)( … )` (Rust calls a function-valued field only so: `x.f( … )` is always a method @@ -292,6 +294,11 @@ class RustSlotWalk forEachNamedChild( n, c.cur, [ & ]( TSNode ch ) { bindIn( ch, out, depth + 1 ); return true; } ); } + static std::string_view lastSegment( std::string_view path ) noexcept + { + const std::size_t at = path.rfind( "::" ); + return at == std::string_view::npos ? path : path.substr( at + 2 ); + } // `Box::new` / `Rc::new` / `Arc::new`: a wrapper that hands its one argument on (the value, not a registrar). bool wrapperCallee( TSNode fn ) const noexcept { @@ -331,6 +338,7 @@ class RustSlotWalk { std::string_view name; std::string owner; + std::string_view module; // the module path written before the owner type or the function (`crate::a`, `std::thread`) bool bare = false; // a bare identifier: a function, a parameter or a local, as its function binds it }; static bool upperLead( std::string_view s ) noexcept { return !s.empty() && s[0] >= 'A' && s[0] <= 'Z'; } @@ -374,6 +382,14 @@ class RustSlotWalk } out.name = leaf; out.owner = upperLead( owner ) ? std::string( owner ) : std::string(); // a module path names no owner type + if( out.owner.empty() ) + { + out.module = text( path ); // `crate::other::target`: the whole path before the function + } + else if( owner == text( field( path, "name" ) ) && is( path, "scoped_identifier" ) ) + { + out.module = text( field( path, "path" ) ); // `a::Editor::f`: the module path before the owner type + } } return out; } @@ -392,10 +408,12 @@ class RustSlotWalk { return; // `Box::new( f )`: the value lands where the wrapper does (unwrap), not in the wrapper } - std::string written; + std::string written; // the callee as written (`Self` resolved): recvVar + std::string shown; // without its module path: the row's into= if( is( callee, "identifier" ) ) { written = std::string( text( callee ) ); + shown = written; } else if( is( callee, "scoped_identifier" ) ) { @@ -404,7 +422,8 @@ class RustSlotWalk { return; } - written = n.owner.empty() ? std::string( n.name ) : n.owner + "::" + std::string( n.name ); + shown = n.owner.empty() ? std::string( n.name ) : n.owner + "::" + std::string( n.name ); + written = n.module.empty() ? shown : std::string( n.module ) + "::" + shown; // resolveCallee reads the module path } else { @@ -428,9 +447,10 @@ class RustSlotWalk { RawRef r = rawRefAt( a, m_fileId, m_lang, RefRole::Value, std::string( n.name ) ); r.memberRoot = n.owner; + r.memberVia = std::string( n.module ); r.qualifier = "a0"; r.recvVar = written; - r.fieldName = written + "#arg" + std::to_string( pos ); + r.fieldName = shown + "#arg" + std::to_string( pos ); r.composeRel = "#" + std::to_string( pos ); r.argCount = static_cast( std::min( pos, 0xFFFFu ) ); r.argCountKnown = true; @@ -505,8 +525,8 @@ class RustSlotWalk return true; } ); } - // The struct type a struct expression's name spells: `Self` → the impl type; `m::T` → T (a module path leads); a - // variant `E::V` or any other path is kept whole (no in-tree struct has that name: the join keys it as no slot). + // The struct type a struct expression's name spells, its path kept as written (slot_join::RustTypeKeys reads the module + // path: `crate::a::Cfg` is a.rs's Cfg, `extcrate::Hook` no in-tree one, `E::V` a variant); `Self` → the impl type. std::string structType( TSNode nm ) const { if( is( nm, "generic_type_with_turbofish" ) ) @@ -520,12 +540,16 @@ class RustSlotWalk } if( is( nm, "scoped_type_identifier" ) ) { - const std::string_view path = text( field( nm, "path" ) ); - const std::string_view tail = path.substr( path.rfind( ':' ) == std::string_view::npos ? 0 : path.rfind( ':' ) + 1 ); - return upperLead( tail ) ? std::string( text( nm ) ) : std::string( text( field( nm, "name" ) ) ); + return std::string( text( nm ) ); } return {}; } + // A struct type as a row shows it (into=): its module path dropped (`crate::a::Cfg` → Cfg), a variant `E::V` whole. + static std::string shownType( std::string_view ty ) + { + const std::size_t at = ty.rfind( "::" ); + return at == std::string_view::npos || upperLead( lastSegment( ty.substr( 0, at ) ) ) ? std::string( ty ) : std::string( ty.substr( at + 2 ) ); + } // `x.f = v`: a store into field f (typed for `self.f` inside an impl, else the name tier). void assignAt( TSNode as ) { @@ -554,8 +578,9 @@ class RustSlotWalk } r.name = std::string( n.name ); r.memberRoot = n.owner; + r.memberVia = std::string( n.module ); r.qualifier = "x0"; - r.fieldName = ( ty.empty() ? std::string() : ty ) + "." + std::string( fieldName ); + r.fieldName = shownType( ty ) + "." + std::string( fieldName ); r.composeRel = "." + std::string( fieldName ); land( std::move( r ), n.bare ? n.name : std::string_view(), true ); } diff --git a/src/valuerefindex.h b/src/valuerefindex.h index 289e44f26..ac9035c98 100644 --- a/src/valuerefindex.h +++ b/src/valuerefindex.h @@ -174,33 +174,360 @@ inline std::vector cratesOf( const IngestResult& ing ) } return out; } -// The one candidate of `cands` (symbols) that file `fileId` most plausibly means: its own file's, else its crate's (`crates`, -// cratesOf), else the repo's only one; kNoNode when the tier that has any holds two (which one is unknown: never a guess). -inline NodeId nearestOf( const IngestResult& ing, std::span cands, std::uint32_t fileId, std::span crates ) +// FN-SLOT-2 fix 1 (review rv-fn-slot-2 F-1/F-2): where a Rust path AS WRITTEN can reach — the module path ingest_rustslots.h +// keeps (a value's memberVia, a path callee's recvVar, a struct type's memberCtor), read against the file's `use` records (the +// L3 Rust import records: ing.includes, the specifiers resolve.h resolveRustImport resolves for the call graph) and the in-tree +// crates (cratesOf). Never a guess: a path whose target is out of the tree, renamed, or not decidable reaches nothing. +// * std / core / alloc, an out-of-tree crate (`tokio::spawn`, `use extcrate::Hook`), a `use … as` rename, an unknown first +// segment (an inline module: a floor): None. +// * `crate::a::x`, `self::…`, `super::…`, `mod a; a::x`, a `use` that spells one: the ONE file resolveRustImport resolves the +// path to (this crate's root), never the writing file's own namesake (F-2); `crate::x` the crate root's. +// * a path led by an in-tree crate's name (`zed::x`, `use zed::{x}`): that crate's one, outside the writing file. +// * a bare name no `use` names: this file's, else its crate's, else the repo's only one — that last tier only when the file +// `use`s the defining crate (F-1 iv). A bare name a `use` names while this file defines a namesake too: None (the `use` +// sits in an inner scope; which one this reference sees is not read). +class RustPaths { - NodeId pick[3] = { kNoNode, kNoNode, kNoNode }; - int n[3] = { 0, 0, 0 }; - for( const NodeId id : cands ) +public: + enum class Reach : std::uint8_t { Near, File, Crate, InCrate, CrateRoot, None }; + struct At + { + Reach kind = Reach::Near; + bool viaUse = false; // Near only: decided through a `use` (a namesake in this file: None) + std::uint32_t file = kNoFile; // File: the one file + std::string_view crate; // Crate / InCrate / CrateRoot: the crate + }; + void build( const IngestResult& ing ) { - const std::uint32_t f = ing.symbols[id].fileId; - const int tier = f == fileId ? 0 : crates[f] == crates[fileId] ? 1 : 2; - for( int t = tier; t < 3; ++t ) + m_ing = &ing; + m_crates = cratesOf( ing ); + m_uses.assign( ing.files.size(), {} ); + for( const Include& inc : ing.includes ) { - pick[t] = id; - ++n[t]; + if( inc.fileId < ing.files.size() && !inc.isSymbolic && rootRelPath( ing, inc.fileId ).ends_with( ".rs" ) ) + { + m_uses[ inc.fileId ].push_back( inc.target ); + } + } + for( const std::string_view c : m_crates ) + { + if( c.empty() ) + { + continue; + } + const auto [ it, fresh ] = m_crateByName.try_emplace( crateName( c ), c ); + if( !fresh && it->second != c ) + { + it->second = std::string_view(); // two crates of one name: which one a path means is unknown + } } } - for( int t = 0; t < 3; ++t ) + std::string_view crateOf( std::uint32_t f ) const { return m_crates[f]; } + // Where `name`, written after module path `module` ("" bare) by reference `r`, can reach. + At reach( const Reference& r, std::string_view module, std::string_view name ) const { - if( n[t] > 0 ) + if( !module.empty() ) { - return n[t] == 1 ? pick[t] : kNoNode; + return moduleReach( r, module, name, 0 ); } + std::string via; + switch( useOf( r.fileId, name, via ) ) + { + case Use::None: return At{}; + case Use::Opaque: return At{ Reach::None }; + case Use::Path: break; + } + At at = moduleReach( r, via, name, 0 ); + at.viaUse = true; + return at; } - return kNoNode; -} -// FN-SLOT part 2: a ModuleType (Rust) slot type's key — the in-tree STRUCT symbol the written name spells (nearestOf: its own file, -// else its crate, else the repo's only one), as jsImportKey( symbolId, name ); "" when no struct or several of one tier carry the + // The one candidate of `cands` (symbols) `at` reaches from file `fileId`: Near — its own file's, else its crate's, else the + // repo's only one (use-gated); kNoNode when the tier that has any holds two (which one is unknown: never a guess). + NodeId nearestOf( std::span cands, std::uint32_t fileId, const At& at ) const + { + const IngestResult& ing = *m_ing; + NodeId pick[3] = { kNoNode, kNoNode, kNoNode }; + int n[3] = { 0, 0, 0 }; + for( const NodeId id : cands ) + { + const std::uint32_t f = ing.symbols[id].fileId; + const int tier = tierOf( f, fileId, at ); + if( f == fileId && at.viaUse && !( at.kind == Reach::File && at.file == fileId ) ) + { + return kNoNode; // a `use` names it AND this file defines it: the use is in an inner scope (which one is unread) + } + for( int t = tier; t >= 0 && t < 3; ++t ) + { + pick[t] = id; + ++n[t]; + } + } + for( int t = 0; t < 3; ++t ) + { + if( n[t] > 0 ) + { + return n[t] == 1 ? pick[t] : kNoNode; + } + } + return kNoNode; + } + +private: + enum class Use : std::uint8_t { None, Path, Opaque }; + static bool upperLead( std::string_view s ) noexcept { return !s.empty() && s[0] >= 'A' && s[0] <= 'Z'; } + static std::string_view trimmed( std::string_view s ) noexcept + { + while( !s.empty() && ( s.front() == ' ' || s.front() == '\n' || s.front() == '\t' || s.front() == '\r' ) ) { s.remove_prefix( 1 ); } + while( !s.empty() && ( s.back() == ' ' || s.back() == '\n' || s.back() == '\t' || s.back() == '\r' ) ) { s.remove_suffix( 1 ); } + return s; + } + static std::string_view firstSegment( std::string_view p ) noexcept { return p.substr( 0, std::min( p.find( "::" ), p.size() ) ); } + static std::string joined( std::string_view a, std::string_view b ) { return a.empty() ? std::string( b ) : std::string( a ) + "::" + std::string( b ); } + // A crate's name as a path spells it: its directory's last component, `-` as `_` (`crates/nu-engine` → nu_engine). + static std::string crateName( std::string_view crate ) + { + std::string n( crate.substr( crate.rfind( '/' ) == std::string_view::npos ? 0 : crate.rfind( '/' ) + 1 ) ); + std::ranges::replace( n, '-', '_' ); + return n; + } + // Each name one `use` specifier binds: cb( name, module path, renamed ). `a::{b, c::d as e, self}`, `a::b as c`; a glob binds none. + template + static void forEachUse( std::string_view t, const std::string& prefix, int depth, const Cb& cb ) + { + t = trimmed( t ); + if( t.starts_with( "::" ) ) + { + t.remove_prefix( 2 ); + } + if( const std::size_t br = t.find( '{' ); br != std::string_view::npos ) + { + const std::size_t close = t.rfind( '}' ); + std::string_view head = trimmed( t.substr( 0, br ) ); + if( close == std::string_view::npos || close < br || depth > 4 ) + { + return; + } + if( head.ends_with( "::" ) ) + { + head.remove_suffix( 2 ); + } + const std::string p = head.empty() ? prefix : joined( prefix, head ); + const std::string_view body = t.substr( br + 1, close - br - 1 ); + int lvl = 0; + std::size_t from = 0; + for( std::size_t i = 0; i <= body.size(); ++i ) + { + const char c = i < body.size() ? body[i] : ','; + lvl += c == '{' ? 1 : c == '}' ? -1 : 0; + if( c == ',' && lvl == 0 ) + { + forEachUse( body.substr( from, i - from ), p, depth + 1, cb ); + from = i + 1; + } + } + return; + } + std::string_view alias; + if( const std::size_t as = t.find( " as " ); as != std::string_view::npos ) + { + alias = trimmed( t.substr( as + 4 ) ); + t = trimmed( t.substr( 0, as ) ); + } + const std::size_t c = t.rfind( "::" ); + std::string_view leaf = c == std::string_view::npos ? t : t.substr( c + 2 ); + std::string module = c == std::string_view::npos ? prefix : joined( prefix, t.substr( 0, c ) ); + if( leaf == "self" ) + { + const std::size_t up = module.rfind( "::" ); // `a::b::{self}` binds module b + leaf = std::string_view( module ).substr( up == std::string::npos ? 0 : up + 2 ); + module = up == std::string::npos ? std::string() : module.substr( 0, up ); + if( alias.empty() ) + { + const std::string own( leaf ); + cb( std::string_view( own ), module, false ); + return; + } + } + if( leaf.empty() || leaf == "*" ) + { + return; + } + cb( alias.empty() ? leaf : alias, module, !alias.empty() && alias != leaf ); + } + // The module path the file's `use` binds `name` from (`use tokio::spawn` → "tokio"); Opaque: a rename, or two that differ. + Use useOf( std::uint32_t fileId, std::string_view name, std::string& via ) const + { + Use got = Use::None; + for( const std::string_view t : m_uses[ fileId ] ) + { + if( t.starts_with( "mod:" ) || got == Use::Opaque ) + { + continue; + } + forEachUse( t, std::string(), 0, [ & ]( std::string_view bound, const std::string& module, bool renamed ) + { + if( bound != name ) + { + return; + } + if( renamed || module.empty() || ( got == Use::Path && via != module ) ) + { + got = Use::Opaque; + return; + } + got = Use::Path; + via = module; + } ); + } + return got; + } + bool declaresMod( std::uint32_t fileId, std::string_view seg ) const + { + return std::ranges::any_of( m_uses[ fileId ], [ & ]( std::string_view t ) { return t.starts_with( "mod:" ) && t.substr( 4 ) == seg; } ); + } + bool usesCrate( std::uint32_t fileId, std::string_view crate ) const + { + const std::string want = crateName( crate ); + return std::ranges::any_of( m_uses[ fileId ], [ & ]( std::string_view t ) + { + t = trimmed( t.starts_with( "::" ) ? t.substr( 2 ) : t ); + return trimmed( t.substr( 0, std::min( { t.find( "::" ), t.find( '{' ), t.size() } ) ) ) == want; + } ); + } + // The candidate's tier for `at` (-1: out of reach). Near: 0 own file, 1 own crate, 2 another crate the file `use`s. + int tierOf( std::uint32_t f, std::uint32_t fileId, const At& at ) const + { + switch( at.kind ) + { + case Reach::Near: return f == fileId ? 0 : m_crates[f] == m_crates[fileId] ? 1 : usesCrate( fileId, m_crates[f] ) ? 2 : -1; + case Reach::File: return f == at.file ? 0 : -1; + case Reach::Crate: return f != fileId && m_crates[f] == at.crate ? 0 : -1; + case Reach::InCrate: return m_crates[f] == at.crate ? 0 : -1; + case Reach::CrateRoot: + { + const std::string_view p = rootRelPath( *m_ing, f ); + return m_crates[f] == at.crate && ( p.ends_with( "/lib.rs" ) || p.ends_with( "/main.rs" ) ) && includerDir( p ) == crateRootDir( f ) ? 0 : -1; + } + case Reach::None: return -1; + } + return -1; + } + // The directory a file's crate's `crate::` anchors in: `/src`, a root crate's `src`, else the crate's own directory. + std::string_view crateRootDir( std::uint32_t f ) const + { + const std::string_view rel = rootRelPath( *m_ing, f ); + if( rel.starts_with( "src/" ) ) + { + return rel.substr( 0, 3 ); + } + const std::size_t at = rel.find( "/src/" ); + return at == std::string_view::npos ? includerDir( rel ) : rel.substr( 0, at + 4 ); + } + At fileAt( std::uint32_t fileId, const std::string& target, std::uint32_t rootOf ) const + { + if( m_fileIndex.empty() ) + { + m_fileIndex.reserve( m_ing->files.size() ); + for( std::uint32_t f = 0; f < m_ing->files.size(); ++f ) + { + m_fileIndex.emplace( lexicalNormalize( rootRelPath( *m_ing, f ) ), f ); + } + } + const std::uint32_t f = resolveRustImport( rootRelPath( *m_ing, fileId ), target, m_fileIndex, crateRootDir( rootOf ), true ); + return f == kNoFile ? At{ Reach::None } : At{ Reach::File, false, f }; + } + // `module::name` led by an anchor, an in-tree crate's name, a `mod` this file declares or a module a `use` binds. + At moduleReach( const Reference& r, std::string_view module, std::string_view name, int depth ) const + { + const std::string_view seg = trimmed( firstSegment( module ) ); + const std::string_view rest = module.size() > seg.size() + 2 ? module.substr( seg.size() + 2 ) : std::string_view(); + if( seg == "std" || seg == "core" || seg == "alloc" ) + { + return At{ Reach::None }; + } + if( seg == "crate" || seg == "self" || seg == "super" ) + { + return anchored( r, seg, rest, name ); + } + if( const auto it = m_crateByName.find( std::string( seg ) ); it != m_crateByName.end() ) + { + if( it->second.empty() ) + { + return At{ Reach::None }; + } + if( rest.empty() ) + { + return At{ Reach::Crate, false, kNoFile, it->second }; + } + for( std::uint32_t f = 0; f < m_crates.size(); ++f ) + { + if( m_crates[f] == it->second ) + { + return fileAt( r.fileId, "crate::" + joined( rest, name ), f ); // that crate's root anchors it + } + } + return At{ Reach::None }; + } + if( depth == 0 && declaresMod( r.fileId, seg ) ) + { + return anchored( r, "self", module, name ); + } + std::string via; + if( depth == 0 && useOf( r.fileId, seg, via ) == Use::Path ) + { + return moduleReach( r, joined( via, module ), name, 1 ); + } + return At{ Reach::None }; + } + // `crate::…`, `self::…`, `super::…`: resolveRustImport's module-file rule from this file (`rest` the modules after the anchor). + At anchored( const Reference& r, std::string_view anchor, std::string_view rest, std::string_view name ) const + { + const IngestResult& ing = *m_ing; + const std::string_view path = rootRelPath( ing, r.fileId ); + const std::string_view dir = includerDir( path ); + std::string_view stem = path.substr( dir.empty() ? 0 : dir.size() + 1 ); + stem = stem.substr( 0, stem.rfind( '.' ) ); + const bool modFile = stem == "mod" || stem == "lib" || stem == "main"; + if( anchor == "crate" ) + { + return rest.empty() ? At{ Reach::CrateRoot, false, kNoFile, m_crates[ r.fileId ] } : fileAt( r.fileId, "crate::" + joined( rest, name ), r.fileId ); + } + if( r.fromSymbol < ing.symbols.size() ) + { + const Symbol& from = ing.symbols[ r.fromSymbol ]; + if( from.kind == SymKind::Method && !from.scope.empty() && !upperLead( from.scope ) ) + { + return At{ Reach::None }; // inside an inline `mod m { … }`: `self` / `super` are not this file's module + } + } + if( anchor == "self" ) + { + if( rest.empty() ) + { + return At{ Reach::File, false, r.fileId }; + } + return fileAt( r.fileId, modFile ? "self::" + joined( rest, name ) : "self::" + joined( joined( stem, rest ), name ), r.fileId ); + } + if( modFile ) // super of a mod.rs: its directory's parent module (a crate root has none) + { + return stem != "mod" || rest.empty() ? At{ Reach::None } : fileAt( r.fileId, "super::" + joined( rest, name ), r.fileId ); + } + if( rest.empty() ) // `super::x` in d/s.rs: x in module d (d.rs or d/mod.rs) + { + const std::string_view up = dir.substr( dir.rfind( '/' ) == std::string_view::npos ? 0 : dir.rfind( '/' ) + 1 ); + return up.empty() ? At{ Reach::None } : fileAt( r.fileId, "super::" + joined( up, name ), r.fileId ); + } + return fileAt( r.fileId, "self::" + joined( rest, name ), r.fileId ); // super::a::x in d/s.rs: d/a… + } + + const IngestResult* m_ing = nullptr; + std::vector m_crates; // cratesOf + std::vector> m_uses; // per file: its Rust `use` specifiers and `mod:x` records + HashMap m_crateByName; // a crate's path name → the crate ("" two crates share it) + mutable HashMap m_fileIndex; // root-relative normalised path → fileId, built on first anchored path +}; +// FN-SLOT part 2: a ModuleType (Rust) slot type's key — the in-tree STRUCT symbol the written path reaches (RustPaths: a bare +// name its own file's, else its crate's, else the repo's only one; a module path the file it resolves to), as jsImportKey( symbolId, name ); "" when no struct or several of one tier carry the // name (an out-of-tree type, or which one is unknown: the name tier); kNoSlot when the name is a path through a TYPE (`E::V`, a // variant: no field slot of a struct). class RustTypeKeys @@ -218,33 +545,43 @@ class RustTypeKeys } if( !m_structs.empty() ) { - m_crates = cratesOf( ing ); + m_paths.build( ing ); } } static std::string keyOfSymbol( const Symbol& s, NodeId id ) { return jsImportKey( id, s.name ); } std::string keyOf( const IngestResult& ing, const Reference& r ) const { const std::string_view t = r.memberCtor; - if( t.empty() ) + if( t.empty() || m_structs.empty() ) { return {}; } - if( t.find( ':' ) != std::string_view::npos ) + const std::size_t at = t.rfind( "::" ); + const std::string_view name = at == std::string_view::npos ? t : t.substr( at + 2 ); + const std::string_view module = at == std::string_view::npos ? std::string_view() : t.substr( 0, at ); + const std::size_t up = module.rfind( "::" ); + const std::string_view last = up == std::string_view::npos ? module : module.substr( up + 2 ); + if( !last.empty() && last[0] >= 'A' && last[0] <= 'Z' ) { - return std::string( kNoSlot ); + return std::string( kNoSlot ); // `E::V`: a variant, no struct's field } - const auto it = m_structs.find( std::string( t ) ); + const RustPaths::At reach = m_paths.reach( r, module, name ); + if( reach.kind == RustPaths::Reach::None ) + { + return std::string( kNoSlot ); // an out-of-tree or undecidable type (`extcrate::Hook`, `std::…`): no in-tree slot + } + const auto it = m_structs.find( std::string( name ) ); if( it == m_structs.end() ) { return {}; } - const NodeId id = nearestOf( ing, it->second, r.fileId, m_crates ); + const NodeId id = m_paths.nearestOf( it->second, r.fileId, reach ); return id == kNoNode ? std::string() : keyOfSymbol( ing.symbols[id], id ); } private: HashMap> m_structs; // struct name → the in-tree Rust structs of that name - std::vector m_crates; // cratesOf (built when any Rust struct exists) + RustPaths m_paths; // built when any Rust struct exists }; // The type key of every language that keys its own types (model.h slotKeysOwnTypes), by its row's VrSlotTypeKey. class TypeKeys @@ -979,7 +1316,7 @@ class ValueRefIndex } if( pathLang ) { - m_crates = slot_join::cratesOf( m_ing ); + m_paths.build( m_ing ); } } // Reference `i` into its table (`keep` non-null: a narrow index, which skips what the splice never reads). @@ -1210,9 +1547,10 @@ class ValueRefIndex // The method-value owner rule (rv-fn-slot part-2 piece 3): `fns` (one name's functions) owned by type `owner`, as reference // `r`'s language means it. Go (a method value `b.opPlan` of the enclosing method's receiver, ingest_valuerefs.h methodValueAt): - // that type's METHOD in r's own package directory. Rust (`Editor::backspace`, `Self::f` with Self resolved): the owner type's - // function of that name nearest r (slot_join::nearestOf: its own file, else its crate, else the repo's only one), else nothing. - std::vector ownedBy( const Reference& r, std::string_view owner, std::span fns ) const + // that type's METHOD in r's own package directory. Rust (`Editor::backspace`, `Self::f` with Self resolved, `module` the path + // written before the type): the owner type's function of that name in the crate the type's path reaches (slot_join::RustPaths; + // a bare type: nearest r — its own file, else its crate, else the repo's only one), else nothing. + std::vector ownedBy( const Reference& r, std::string_view owner, std::span fns, std::string_view module = {} ) const { const VrLangFamily fam = valueRefFamily( r.lang ); if( !slotPathSep( r.lang ).empty() ) @@ -1222,7 +1560,14 @@ class ValueRefIndex { return {}; } - const NodeId one = slot_join::nearestOf( m_ing, it->second, r.fileId, m_crates ); + using Reach = slot_join::RustPaths::Reach; + slot_join::RustPaths::At at = m_paths.reach( r, module, owner ); + if( at.kind == Reach::File || at.kind == Reach::Crate || at.kind == Reach::CrateRoot ) + { + // the type's own crate (an impl lives in its type's crate, not always its file) + at = { Reach::InCrate, false, kNoFile, at.kind == Reach::File ? m_paths.crateOf( at.file ) : at.crate }; + } + const NodeId one = m_paths.nearestOf( it->second, r.fileId, at ); return one == kNoNode ? std::vector{} : std::vector{ one }; } std::vector out; @@ -1245,12 +1590,51 @@ class ValueRefIndex const std::size_t at = sep.empty() ? std::string::npos : v.recvVar.rfind( sep ); if( at == std::string::npos ) { - return resolveName( v, v.recvVar ); + return valueRefFamily( v.lang ) == VrLangFamily::Rust ? resolveIn( v, v.recvVar, {} ) : resolveName( v, v.recvVar ); + } + const std::string_view path = std::string_view( v.recvVar ).substr( 0, at ); // `crate::other` / `a::Owner` / `Owner` + const std::size_t up = path.rfind( sep ); + const std::string_view owner = up == std::string_view::npos ? path : path.substr( up + sep.size() ); + if( owner.empty() || !( owner[0] >= 'A' && owner[0] <= 'Z' ) ) + { + return resolveIn( v, std::string_view( v.recvVar ).substr( at + sep.size() ), path ); // a module path: no owner type } const auto it = m_fnByName.find( v.recvVar.substr( at + sep.size() ) ); - return it == m_fnByName.end() ? std::vector{} : ownedBy( v, std::string_view( v.recvVar ).substr( 0, at ), it->second ); + return it == m_fnByName.end() ? std::vector{} : ownedBy( v, owner, it->second, up == std::string_view::npos ? std::string_view() : path.substr( 0, up ) ); } + // FN-SLOT-2 fix 1: Rust function `name` written after module path `module` ("" bare) by reference `r` — the one + // slot_join::RustPaths reaches (a bare name with no `use`: every one in r's own file, else its crate's one, else the repo's + // only one in a crate the file `use`s); a class member (an impl's or an inline module's function) is never in bare scope. + std::vector resolveIn( const Reference& r, std::string_view name, std::string_view module ) const + { + const auto it = m_fnByName.find( std::string( name ) ); + if( it == m_fnByName.end() ) + { + return {}; + } + std::vector cands, sameFile; + for( const NodeId id : it->second ) + { + const Symbol& s = m_ing.symbols[id]; + if( valueRefFamily( s.lang ) != VrLangFamily::Rust || ( isClassMember( s ) && !memberVisibleFrom( r, s, VrLangFamily::Rust ) ) ) + { + continue; + } + cands.push_back( id ); + if( s.fileId == r.fileId ) + { + sameFile.push_back( id ); + } + } + const slot_join::RustPaths::At at = m_paths.reach( r, module, name ); + if( at.kind == slot_join::RustPaths::Reach::Near && !sameFile.empty() ) + { + return sameFile; // the file's own (`#[cfg]` twins all of them, as every family's same-file rule) + } + const NodeId one = m_paths.nearestOf( cands, r.fileId, at ); + return one == kNoNode ? std::vector{} : std::vector{ one }; + } // `name` as seen from reference `r`'s file, under the visibility rules in the header comment. std::vector resolveName( const Reference& r, std::string_view name ) const { @@ -1262,7 +1646,11 @@ class ValueRefIndex const VrLangFamily fam = valueRefFamily( r.lang ); if( r.role == RefRole::Value && !r.memberRoot.empty() ) { - return ownedBy( r, r.memberRoot, it->second ); + return ownedBy( r, r.memberRoot, it->second, r.memberVia ); + } + if( valueRefFamily( r.lang ) == VrLangFamily::Rust ) + { + return resolveIn( r, name, r.role == RefRole::Value ? std::string_view( r.memberVia ) : std::string_view() ); } // A C/C++ PROTOTYPE (a bodyless declaration) names the function its definition lives in: it is the target only // when no definition with a body is visible — a prototype in this file never hides the extern definition. @@ -1324,15 +1712,7 @@ class ValueRefIndex } break; } - case VrLangFamily::Rust: - { - // FN-SLOT part 2: a bare Rust function of another file — its crate's one, else the repo's only one (nearestOf) - if( const NodeId one = slot_join::nearestOf( m_ing, other, r.fileId, m_crates ); one != kNoNode ) - { - out.push_back( one ); - } - break; - } + case VrLangFamily::Rust: break; // resolveIn above case VrLangFamily::Js: case VrLangFamily::Py: { @@ -1374,7 +1754,7 @@ class ValueRefIndex const IngestResult& m_ing; HashMap> m_fnByName; HashMap> m_fnByOwner; // part 2: "family#Owner::name" → a path language's owned functions - std::vector m_crates; // part 2: slot_join::cratesOf, when a path language is indexed + slot_join::RustPaths m_paths; // part 2 fix 1: where a Rust path reaches, when a path language is indexed std::vector m_pyMember; // per symbol: 1 = a Python def directly in its class (empty: no Python) std::vector m_values; // Value reference indices, ascending std::vector m_depthCuts; // the files' depth-cut records (no row), ascending From bb816c2f1bf760928dcff9e7e7949d845da06b60 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:22:52 -0400 Subject: [PATCH 10/95] fn-slot part 2 fix 1 F-4: Box / Rc / Arc wrappers by the path's last segment (std::sync::Arc::new( f )) Arm W1 / W1b green: the value lands in Dyn.g, no Arc::new#arg0 row. Co-Authored-By: Claude Opus 5.5 --- src/ingest_rustslots.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ingest_rustslots.h b/src/ingest_rustslots.h index 2a7395be7..2ca72cdc5 100644 --- a/src/ingest_rustslots.h +++ b/src/ingest_rustslots.h @@ -299,14 +299,14 @@ class RustSlotWalk const std::size_t at = path.rfind( "::" ); return at == std::string_view::npos ? path : path.substr( at + 2 ); } - // `Box::new` / `Rc::new` / `Arc::new`: a wrapper that hands its one argument on (the value, not a registrar). + // `Box::new` / `Rc::new` / `Arc::new` (any module path before the type): a wrapper that hands its one argument on (the value, not a registrar). bool wrapperCallee( TSNode fn ) const noexcept { if( !is( fn, "scoped_identifier" ) || text( field( fn, "name" ) ) != "new" ) { return false; } - const std::string_view path = text( field( fn, "path" ) ); + const std::string_view path = lastSegment( text( field( fn, "path" ) ) ); // `std::sync::Arc` as `Arc` return path == "Box" || path == "Rc" || path == "Arc"; } // A value as written, through `( v )`, `Box::new( v )`, `Rc::new( v )`, `Arc::new( v )`. From 8038fd6e663a397c18c0ddb74fa05f338a814d27 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:23:20 -0400 Subject: [PATCH 11/95] fn-slot part 2 fix 1 F-5 / F-6: the Rust floors in the changelog entry and the walk's header Value references are path-call arguments and field stores only (method-call arguments, let g = f, arrays, returns unread: --safe-delete / --dead-code may miss them); inline-module functions are module members, so a value or registrar there resolves to nothing; plus the module-path reach rule (F-1/F-2). docs/LIMITS.md is generated from compile-time caps only, so no line there. Co-Authored-By: Claude Opus 5.5 --- changelog.d/rust-fn-valued-slots.md | 13 ++++++++++--- src/ingest_rustslots.h | 5 ++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/changelog.d/rust-fn-valued-slots.md b/changelog.d/rust-fn-valued-slots.md index 6f812a4d0..97d5d538e 100644 --- a/changelog.d/rust-fn-valued-slots.md +++ b/changelog.d/rust-fn-valued-slots.md @@ -6,9 +6,16 @@ path call that stores its parameter there (`CallEval::new( stack, eval_block_wit in its body; a method's `self` is position 0) — is now joined to the calls through that field, `(self.eval)( … )` (the only spelling Rust has for calling a function-valued field). `--callers` gains `called_by= called_at= via= cands=K` on a `` row, `--callees` lists ``, and `--for` prints `slot_by=` / -`slot_fields="F=K@C"` on shown rows. A slot type is the in-tree struct its name spells (its own file's, else its crate's, else -the repo's only one; `Self` is the impl type), so two crates' same-named structs are two slots; a `self.f` call is typed +`slot_fields="F=K@C"` on shown rows. A slot type, a stored function and a registrar are the in-tree definition their path AS +WRITTEN reaches: a module path (`crate::a::Cfg`, `super::eval::f`, `mod a; a::f`) the one file it names, never the writing +file's own namesake; a path led by an in-tree crate's name that crate; a bare name its own file's, else its crate's, else the +repo's only one in a crate the file `use`s (`Self` is the impl type). `std` / `core` / `alloc`, an out-of-tree crate +(`use tokio::spawn`, `use extcrate::Hook`), a `use … as` rename or an unknown module reach no in-tree definition: no join. Two +crates' same-named structs are two slots; a `self.f` call is typed (`via="slot"`), a call or store through another receiver joins by the field name only (`via="slot-name"`, `~` on `--for`). Not joined (floors): a closure stored in a field, a method-call registrar (`r.set( f )`), a value inside a macro, an enum variant's -field. Every row is a MAY-call clue, never a proven call and never in `count=`. Measured on nushell (`CallEval.eval`, called +field, a value or registrar inside an inline `mod m { … }` (including `#[cfg(test)] mod tests`: its functions are module +members). Rust value references are read only as path-call arguments and field stores: a method-call argument +(`iter().map( helper )`), `let g = f`, an array element or a returned function is not read, so `--safe-delete` / `--dead-code` +can still call such a function unreferenced. Every row is a MAY-call clue, never a proven call and never in `count=`. Measured on nushell (`CallEval.eval`, called by `CallEval::run` / `run_prebound`). Gate: `test/fnslotcheck.sh` (R*/N8-N12/X2/F3 arms). diff --git a/src/ingest_rustslots.h b/src/ingest_rustslots.h index 2ca72cdc5..5747079b7 100644 --- a/src/ingest_rustslots.h +++ b/src/ingest_rustslots.h @@ -28,7 +28,10 @@ // SHADOWING: an identifier bound anywhere in the enclosing function — a parameter, a `let`, closure, `for`, `match` or // `if let` pattern — is never a function value (the whole function is one scope here: a conservative floor). An identifier // that starts upper-case is a constant, a unit struct or a variant (`map( Some )`), never a function: no row. -// FLOORS: closures and method-call arguments are no rows; a macro body is opaque; nodes deeper than kVrMaxDepth are not +// FLOORS: closures and method-call arguments are no rows, nor `let g = f`, an array element or a returned function (Rust +// value references are path-call arguments and field stores only: --safe-delete / --dead-code may miss the rest); a function +// of an inline `mod m { … }` is a module member (`t="method" sc="m"`), so a value or registrar there resolves to nothing +// (valuerefindex.h isClassMember); a macro body is opaque; nodes deeper than kVrMaxDepth are not // visited — disclosed by the file's depth-cut record, as the general walk does. namespace rw From 24c32075ecb27988c3f53b653bc0ca4ac1939261 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:27:36 -0400 Subject: [PATCH 12/95] fn-slot part 2 fix 1 F-7 + F-8: slotTypeKeyOf( Lang ) (no longer hidden by ValueRefWalk::slotTypeKey), TypeKeys as switches TypeKeys::keyOf / keyOfSymbol switch over VrSlotTypeKey with no default, so a new type-key kind fails -Werror=switch instead of keying silently as a Go package directory (the S2 trap). TypedefFacts keys nothing there (its slots key through the typedef facts; no caller asks). fnslotcheck 89/89. Co-Authored-By: Claude Opus 5.5 --- src/model.h | 4 ++-- src/valuerefindex.h | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/model.h b/src/model.h index b94f4cdcb..bd04474f1 100644 --- a/src/model.h +++ b/src/model.h @@ -633,7 +633,7 @@ inline bool slotJoinsByFieldName( Lang l ) noexcept return row != nullptr && row->callSide == VrSlotCallSide::MemberCallByName; } // How language `l` keys a slot type (TypedefFacts for a language with no slot row: it keys nothing of its own). -inline VrSlotTypeKey slotTypeKey( Lang l ) noexcept +inline VrSlotTypeKey slotTypeKeyOf( Lang l ) noexcept { const VrSlotLang* row = vrSlotLang( l ); return row == nullptr ? VrSlotTypeKey::TypedefFacts : row->typeKey; @@ -647,7 +647,7 @@ inline std::string_view slotPathSep( Lang l ) noexcept // A language whose slot types key by its own rule (valuerefindex.h slot_join), not through the C family's typedef facts. inline bool slotKeysOwnTypes( Lang l ) noexcept { - return slotTypeKey( l ) != VrSlotTypeKey::TypedefFacts; + return slotTypeKeyOf( l ) != VrSlotTypeKey::TypedefFacts; } // A slot type key written with the `struct` / `union` keyword (`struct thandle`) leads with this mark: a C tag is no typedef // name, so the join never reads it through an alias. A slot fact whose composeRel is kValueRefSlotBodyRel declares that its diff --git a/src/valuerefindex.h b/src/valuerefindex.h index ac9035c98..fe058b8c3 100644 --- a/src/valuerefindex.h +++ b/src/valuerefindex.h @@ -106,7 +106,7 @@ class GoTypeKeys } for( const Symbol& sym : ing.symbols ) { - if( sym.kind == SymKind::Struct && slotTypeKey( sym.lang ) == VrSlotTypeKey::PackageDir ) + if( sym.kind == SymKind::Struct && slotTypeKeyOf( sym.lang ) == VrSlotTypeKey::PackageDir ) { m_structs.try_emplace( keyOfSymbol( sym ), char( 1 ) ); } @@ -538,7 +538,7 @@ class RustTypeKeys for( NodeId id = 0; id < ing.symbols.size(); ++id ) { const Symbol& sym = ing.symbols[id]; - if( ( sym.kind == SymKind::Struct || sym.kind == SymKind::Class ) && slotTypeKey( sym.lang ) == VrSlotTypeKey::ModuleType ) // a Rust struct is a class row + if( ( sym.kind == SymKind::Struct || sym.kind == SymKind::Class ) && slotTypeKeyOf( sym.lang ) == VrSlotTypeKey::ModuleType ) // a Rust struct is a class row { m_structs[ sym.name ].push_back( id ); } @@ -593,14 +593,28 @@ class TypeKeys m_go.build( ing ); m_rust.build( ing ); } + // A switch with no default (rv-fn-slot-2 F-8): a new VrSlotTypeKey fails -Wswitch here instead of keying as a Go directory. + // TypedefFacts keys nothing here (its slots key through SlotIndex's typedef facts; endOf and forslots never ask). std::string keyOf( const Reference& r ) { - return slotTypeKey( r.lang ) == VrSlotTypeKey::ModuleType ? m_rust.keyOf( *m_ing, r ) : m_go.keyOf( r ); + switch( slotTypeKeyOf( r.lang ) ) + { + case VrSlotTypeKey::PackageDir: return m_go.keyOf( r ); + case VrSlotTypeKey::ModuleType: return m_rust.keyOf( *m_ing, r ); + case VrSlotTypeKey::TypedefFacts: return {}; + } + return {}; } std::string keyOfSymbol( NodeId id ) const { const Symbol& s = m_ing->symbols[id]; - return slotTypeKey( s.lang ) == VrSlotTypeKey::ModuleType ? RustTypeKeys::keyOfSymbol( s, id ) : m_go.keyOfSymbol( s ); + switch( slotTypeKeyOf( s.lang ) ) + { + case VrSlotTypeKey::PackageDir: return m_go.keyOfSymbol( s ); + case VrSlotTypeKey::ModuleType: return RustTypeKeys::keyOfSymbol( s, id ); + case VrSlotTypeKey::TypedefFacts: return {}; + } + return {}; } private: From 92fb09e133c5781e0d10d1f620f281cae3fa22e7 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:36:43 -0400 Subject: [PATCH 13/95] fn-slot part 2 fix 1 qd: reuse scopeLastSegment, CallResultTypes::joinModule and trimWs (3 clones --quality-delta gated) Co-Authored-By: Claude Opus 5.5 --- src/ingest_rustslots.h | 9 ++------- src/valuerefindex.h | 29 +++++++++++++++++------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/ingest_rustslots.h b/src/ingest_rustslots.h index 5747079b7..3eadf6ed5 100644 --- a/src/ingest_rustslots.h +++ b/src/ingest_rustslots.h @@ -297,11 +297,6 @@ class RustSlotWalk forEachNamedChild( n, c.cur, [ & ]( TSNode ch ) { bindIn( ch, out, depth + 1 ); return true; } ); } - static std::string_view lastSegment( std::string_view path ) noexcept - { - const std::size_t at = path.rfind( "::" ); - return at == std::string_view::npos ? path : path.substr( at + 2 ); - } // `Box::new` / `Rc::new` / `Arc::new` (any module path before the type): a wrapper that hands its one argument on (the value, not a registrar). bool wrapperCallee( TSNode fn ) const noexcept { @@ -309,7 +304,7 @@ class RustSlotWalk { return false; } - const std::string_view path = lastSegment( text( field( fn, "path" ) ) ); // `std::sync::Arc` as `Arc` + const std::string_view path = scopeLastSegment( text( field( fn, "path" ) ) ); // `std::sync::Arc` as `Arc` return path == "Box" || path == "Rc" || path == "Arc"; } // A value as written, through `( v )`, `Box::new( v )`, `Rc::new( v )`, `Arc::new( v )`. @@ -551,7 +546,7 @@ class RustSlotWalk static std::string shownType( std::string_view ty ) { const std::size_t at = ty.rfind( "::" ); - return at == std::string_view::npos || upperLead( lastSegment( ty.substr( 0, at ) ) ) ? std::string( ty ) : std::string( ty.substr( at + 2 ) ); + return at == std::string_view::npos || upperLead( scopeLastSegment( ty.substr( 0, at ) ) ) ? std::string( ty ) : std::string( ty.substr( at + 2 ) ); } // `x.f = v`: a store into field f (typed for `self.f` inside an impl, else the name tier). void assignAt( TSNode as ) diff --git a/src/valuerefindex.h b/src/valuerefindex.h index fe058b8c3..43a215b0a 100644 --- a/src/valuerefindex.h +++ b/src/valuerefindex.h @@ -275,14 +275,19 @@ class RustPaths private: enum class Use : std::uint8_t { None, Path, Opaque }; static bool upperLead( std::string_view s ) noexcept { return !s.empty() && s[0] >= 'A' && s[0] <= 'Z'; } + // resolve.h trimWs, newlines included (a `use` brace group spans lines) static std::string_view trimmed( std::string_view s ) noexcept { - while( !s.empty() && ( s.front() == ' ' || s.front() == '\n' || s.front() == '\t' || s.front() == '\r' ) ) { s.remove_prefix( 1 ); } - while( !s.empty() && ( s.back() == ' ' || s.back() == '\n' || s.back() == '\t' || s.back() == '\r' ) ) { s.remove_suffix( 1 ); } + for( std::size_t was = s.size() + 1; was != s.size(); ) + { + was = s.size(); + s = trimWs( s ); + s = s.starts_with( '\n' ) ? s.substr( 1 ) : s; + s = s.ends_with( '\n' ) ? s.substr( 0, s.size() - 1 ) : s; + } return s; } static std::string_view firstSegment( std::string_view p ) noexcept { return p.substr( 0, std::min( p.find( "::" ), p.size() ) ); } - static std::string joined( std::string_view a, std::string_view b ) { return a.empty() ? std::string( b ) : std::string( a ) + "::" + std::string( b ); } // A crate's name as a path spells it: its directory's last component, `-` as `_` (`crates/nu-engine` → nu_engine). static std::string crateName( std::string_view crate ) { @@ -311,7 +316,7 @@ class RustPaths { head.remove_suffix( 2 ); } - const std::string p = head.empty() ? prefix : joined( prefix, head ); + const std::string p = head.empty() ? prefix : CallResultTypes::joinModule( prefix, head ); const std::string_view body = t.substr( br + 1, close - br - 1 ); int lvl = 0; std::size_t from = 0; @@ -335,7 +340,7 @@ class RustPaths } const std::size_t c = t.rfind( "::" ); std::string_view leaf = c == std::string_view::npos ? t : t.substr( c + 2 ); - std::string module = c == std::string_view::npos ? prefix : joined( prefix, t.substr( 0, c ) ); + std::string module = c == std::string_view::npos ? prefix : CallResultTypes::joinModule( prefix, t.substr( 0, c ) ); if( leaf == "self" ) { const std::size_t up = module.rfind( "::" ); // `a::b::{self}` binds module b @@ -463,7 +468,7 @@ class RustPaths { if( m_crates[f] == it->second ) { - return fileAt( r.fileId, "crate::" + joined( rest, name ), f ); // that crate's root anchors it + return fileAt( r.fileId, "crate::" + CallResultTypes::joinModule( rest, name ), f ); // that crate's root anchors it } } return At{ Reach::None }; @@ -475,7 +480,7 @@ class RustPaths std::string via; if( depth == 0 && useOf( r.fileId, seg, via ) == Use::Path ) { - return moduleReach( r, joined( via, module ), name, 1 ); + return moduleReach( r, CallResultTypes::joinModule( via, module ), name, 1 ); } return At{ Reach::None }; } @@ -490,7 +495,7 @@ class RustPaths const bool modFile = stem == "mod" || stem == "lib" || stem == "main"; if( anchor == "crate" ) { - return rest.empty() ? At{ Reach::CrateRoot, false, kNoFile, m_crates[ r.fileId ] } : fileAt( r.fileId, "crate::" + joined( rest, name ), r.fileId ); + return rest.empty() ? At{ Reach::CrateRoot, false, kNoFile, m_crates[ r.fileId ] } : fileAt( r.fileId, "crate::" + CallResultTypes::joinModule( rest, name ), r.fileId ); } if( r.fromSymbol < ing.symbols.size() ) { @@ -506,18 +511,18 @@ class RustPaths { return At{ Reach::File, false, r.fileId }; } - return fileAt( r.fileId, modFile ? "self::" + joined( rest, name ) : "self::" + joined( joined( stem, rest ), name ), r.fileId ); + return fileAt( r.fileId, modFile ? "self::" + CallResultTypes::joinModule( rest, name ) : "self::" + CallResultTypes::joinModule( CallResultTypes::joinModule( stem, rest ), name ), r.fileId ); } if( modFile ) // super of a mod.rs: its directory's parent module (a crate root has none) { - return stem != "mod" || rest.empty() ? At{ Reach::None } : fileAt( r.fileId, "super::" + joined( rest, name ), r.fileId ); + return stem != "mod" || rest.empty() ? At{ Reach::None } : fileAt( r.fileId, "super::" + CallResultTypes::joinModule( rest, name ), r.fileId ); } if( rest.empty() ) // `super::x` in d/s.rs: x in module d (d.rs or d/mod.rs) { const std::string_view up = dir.substr( dir.rfind( '/' ) == std::string_view::npos ? 0 : dir.rfind( '/' ) + 1 ); - return up.empty() ? At{ Reach::None } : fileAt( r.fileId, "super::" + joined( up, name ), r.fileId ); + return up.empty() ? At{ Reach::None } : fileAt( r.fileId, "super::" + CallResultTypes::joinModule( up, name ), r.fileId ); } - return fileAt( r.fileId, "self::" + joined( rest, name ), r.fileId ); // super::a::x in d/s.rs: d/a… + return fileAt( r.fileId, "self::" + CallResultTypes::joinModule( rest, name ), r.fileId ); // super::a::x in d/s.rs: d/a… } const IngestResult* m_ing = nullptr; From 00cc25546e19a405712988729f2621d73325edfe Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:51:46 -0400 Subject: [PATCH 14/95] pyaliascallcheck: module __getattr__ arms (PY-ALIAS-SPLIT-MARKER, red on train 40) call_mo_ga (target defines f and __getattr__) and call_cgetattr (the caller defines __getattr__) must pin; call_mo_gacond / call_mo_gatry (f under if/try) and call_mo_gadyn (globals() write too) must not. Co-Authored-By: Claude Opus 5.5 --- test/pyaliascallcheck.sh | 30 ++++++++++++++++++++++++++++-- test/pyaliascallfix/c_getattr.py | 9 +++++++++ test/pyaliascallfix/c_override.py | 22 ++++++++++++++++++++++ test/pyaliascallfix/mo_ga.py | 6 ++++++ test/pyaliascallfix/mo_gacond.py | 9 +++++++++ test/pyaliascallfix/mo_gadyn.py | 9 +++++++++ test/pyaliascallfix/mo_gatry.py | 9 +++++++++ 7 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 test/pyaliascallfix/c_getattr.py create mode 100644 test/pyaliascallfix/mo_ga.py create mode 100644 test/pyaliascallfix/mo_gacond.py create mode 100644 test/pyaliascallfix/mo_gadyn.py create mode 100644 test/pyaliascallfix/mo_gatry.py diff --git a/test/pyaliascallcheck.sh b/test/pyaliascallcheck.sh index d32a10b0d..0260b81f9 100644 --- a/test/pyaliascallcheck.sh +++ b/test/pyaliascallcheck.sh @@ -13,6 +13,9 @@ # c_cond.py if X: from m import f as g ; g() one conditional import: the only target it can bind # c_class.py from m import Widget as Gadget ; Gadget() a class (target m.py:9 Widget) # c_shadow.py names = [g for g in fs] ; return g() a comprehension target does not leak: the call after it is the import +# c_override.py mo_ga.py: f's def + a module `def __getattr__` PEP 562: __getattr__ answers only names m lacks, so it cannot +# override the def (PY-ALIAS-SPLIT-MARKER; target mo_ga.py:1) +# c_getattr.py the CALLER file defines `__getattr__` never consulted for a name the file binds (target m.py:1) # NEAR MISSES (must NOT bind m.py's f) # c_param.py def call_param(g): g() a parameter shadows the import # c_local.py g = other ; g() a local assignment shadows it @@ -30,7 +33,9 @@ # c_override.py the TARGET module rebinds f after its def: mo_import.py (`from fast_over import f`), mo_try.py (try-import), # mo_star.py (`from mstar import *` with an f), mo_assign.py (`f = other`), mo_dyn.py (`globals()["f"] = len`), # mo_ostar.py (a star import of a file outside the tree: unknown), mo_global.py (`global f` in a def): m.f is not -# provably the live name, so no pin (the unchanged ladder); each caller must not reach its target's line-1 f +# provably the live name, so no pin (the unchanged ladder); each caller must not reach its target's line-1 f. +# With a module `__getattr__`: mo_gacond.py (f's def under `if`), mo_gatry.py (under `try`) may not run, so +# __getattr__ may answer f; mo_gadyn.py (`globals()["f"] = len` too) still withholds (PY-ALIAS-SPLIT-MARKER) # c_shadow.py [g() for g in fs] / list(g() for g in fs) / lambda g: g() a comprehension target or lambda parameter named g # covers the call (the site-spanned records localNameSet skips) # c_member.py o.gm() / self.gm() through the alias spelling a member call is not the bare name: never m.py:1 @@ -57,7 +62,7 @@ callees(){ ( cd "$FIX" || exit 1; "$BIN" "$FIX" --no-cache --callees="$1" --lege # ── 0. presence: every fixture file parses (a fixture that fails to parse would pass the near misses inert) ── ( cd "$FIX" || exit 1; "$BIN" "$FIX" --no-cache --skipped --legend=compact ) >"$TMP/skipped" 2>/dev/null perl -pe 's///g' "$TMP/skipped" >"$TMP/skipped.body" -if ! grep -q 'degraded_parse="0"' "$TMP/skipped.body" || ! grep -q 'indexed="37"' "$TMP/skipped.body" || grep -q '//; s/^/ | if grep -q 'n="call_plain"' "$TMP/callers" && grep -q 'n="call_cond"' "$TMP/callers"; then ok "--callers=m.py:f names call_plain and call_cond" else no "--callers=m.py:f misses an aliased caller"; sed 's///; s/^/ | /' "$TMP/callers"; fi +# ── 1b. a module `__getattr__` cannot override a def that exists (PY-ALIAS-SPLIT-MARKER): target side mo_ga.py:1, caller side m.py:1 ── +for a in "call_mo_ga mo_ga.py:1" "call_cgetattr m.py:1"; do + set -- $a + callees "$1" + row=$( grep -o "]*p=\"$2\"[^>]*/>" "$TMP/$1" ) + if [ -n "$row" ] && ! printf '%s' "$row" | grep -q 'via=' && ! grep -q 'p="decoy.py' "$TMP/$1"; then + ok "$1 binds $2 f as a proven edge although a module __getattr__ is present" + else + no "$1: expected $2 f (proven, no via=) and no decoy row"; sed 's///; s/^/ | /' "$TMP/$1" + fi +done + # ── 2. near misses: never m.py's f ── for c in call_param call_local call_rebind call_dynamic call_star call_twomod call_fnimport call_inner call_disagree call_reexport call_nested call_external call_twice call_comp call_gen call_lambda call_obj call_self; do callees "$c" @@ -85,6 +102,15 @@ for c in call_param call_local call_rebind call_dynamic call_star call_twomod ca else ok "$c: no import-pinned edge"; fi done +# ── 2a. a module `__getattr__` with a def that may not run (if / try), or with a globals() write: no pin ── +for a in "call_mo_gacond mo_gacond.py:4" "call_mo_gatry mo_gatry.py:2" "call_mo_gadyn mo_gadyn.py:1"; do + set -- $a + callees "$1" + if grep -q "p=\"$2\"" "$TMP/$1"; then + no "$1: pinned $2 though __getattr__ may answer f (must stay undecided)"; sed 's///; s/^/ | /' "$TMP/$1" + else ok "$1: no pin to $2"; fi +done + # ── 2b. the target module rebinds f after its def: a def-line pin to mo_*.py:1 would name the overridden def ── for c in call_mo_import call_mo_try call_mo_star call_mo_assign call_mo_dyn call_mo_ostar call_mo_global; do callees "$c" diff --git a/test/pyaliascallfix/c_getattr.py b/test/pyaliascallfix/c_getattr.py new file mode 100644 index 000000000..9daca2e35 --- /dev/null +++ b/test/pyaliascallfix/c_getattr.py @@ -0,0 +1,9 @@ +from m import f as gm0 + + +def __getattr__(name): + return len + + +def call_cgetattr(): + return gm0() diff --git a/test/pyaliascallfix/c_override.py b/test/pyaliascallfix/c_override.py index f13bd1636..6bbdef51e 100644 --- a/test/pyaliascallfix/c_override.py +++ b/test/pyaliascallfix/c_override.py @@ -33,3 +33,25 @@ def call_mo_ostar(): def call_mo_global(): return g7() + + +from mo_ga import f as g8 +from mo_gacond import f as g9 +from mo_gatry import f as g10 +from mo_gadyn import f as g11 + + +def call_mo_ga(): + return g8() + + +def call_mo_gacond(): + return g9() + + +def call_mo_gatry(): + return g10() + + +def call_mo_gadyn(): + return g11() diff --git a/test/pyaliascallfix/mo_ga.py b/test/pyaliascallfix/mo_ga.py new file mode 100644 index 000000000..813dac34c --- /dev/null +++ b/test/pyaliascallfix/mo_ga.py @@ -0,0 +1,6 @@ +def f(): + return 1 + + +def __getattr__(name): + raise AttributeError(name) diff --git a/test/pyaliascallfix/mo_gacond.py b/test/pyaliascallfix/mo_gacond.py new file mode 100644 index 000000000..57533bd82 --- /dev/null +++ b/test/pyaliascallfix/mo_gacond.py @@ -0,0 +1,9 @@ +import sys + +if sys.platform == "never": + def f(): + return 1 + + +def __getattr__(name): + return len diff --git a/test/pyaliascallfix/mo_gadyn.py b/test/pyaliascallfix/mo_gadyn.py new file mode 100644 index 000000000..ed7652740 --- /dev/null +++ b/test/pyaliascallfix/mo_gadyn.py @@ -0,0 +1,9 @@ +def f(): + return 1 + + +def __getattr__(name): + return len + + +globals()["f"] = len diff --git a/test/pyaliascallfix/mo_gatry.py b/test/pyaliascallfix/mo_gatry.py new file mode 100644 index 000000000..7f3de676d --- /dev/null +++ b/test/pyaliascallfix/mo_gatry.py @@ -0,0 +1,9 @@ +try: + def f(): + return 1 +except ImportError: + pass + + +def __getattr__(name): + return len From 363abcb684a50b5d3a8bd7e567e0d7dd55cea933 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:51:47 -0400 Subject: [PATCH 15/95] python pin: a module __getattr__ cannot override a def that exists Split the dynamic-namespace marker: globals()/setattr() now file under kPythonDynamicWrite; a module __getattr__ keeps kPythonDynamicBind. pythonRenamedImportPin withholds on the write marker, and on __getattr__ only when the target's def sits inside an if/try/with/loop at module scope (new ingest record: a second VarDecl '?name', kParserVer 255 placeholder). pythonFileBindsOnlyByImport / pythonPackageMayBind keep reading both. Co-Authored-By: Claude Opus 5.5 --- changelog.d/py-alias-split-marker.md | 9 ++++++ changelog.d/versions.md | 3 +- src/graph.h | 46 +++++++++++++++++++--------- src/ingest_binds.h | 34 ++++++++++++++++++++ src/ingest_cache.h | 2 +- src/model.h | 6 ++++ src/quality.h | 2 +- test/qschemetrip.hash | 2 +- test/qschemetripcheck.sh | 4 +++ 9 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 changelog.d/py-alias-split-marker.md diff --git a/changelog.d/py-alias-split-marker.md b/changelog.d/py-alias-split-marker.md new file mode 100644 index 000000000..c6693006c --- /dev/null +++ b/changelog.d/py-alias-split-marker.md @@ -0,0 +1,9 @@ +### Fixed — a module `__getattr__` no longer withholds a renamed-import call to a function the module defines + +A Python call through a renamed import (`from m import f as g; g()`) stayed undecided whenever `m` defined a module-level +`__getattr__` (PEP 562), as if `__getattr__` could replace `m.f`. It cannot: Python consults it only for a name the module +does not bind. The call now binds `m.f` as a proven edge in that case too, and a module `__getattr__` in the calling file no +longer withholds it either. It still stays undecided when `f`'s definition in `m` sits inside an `if`, `try`, `with` or loop +at module scope (it may not run, and `__getattr__` then answers), when `m` writes its namespace dynamically (`globals()[...]`, +`setattr(...)`), and in every case listed for the renamed-import change. On CPython's standard library this restores 11 +call sites (`re.sub`, `re.escape`, `re.compile`, `platform.system`, `platform.architecture`). Gate: `test/pyaliascallcheck.sh`. diff --git a/changelog.d/versions.md b/changelog.d/versions.md index b2dab9258..2ddd3744b 100644 --- a/changelog.d/versions.md +++ b/changelog.d/versions.md @@ -11,7 +11,8 @@ train 38 took 235 (236); the receiver typing of TS `#private` fields and of Pyth carried a 237 (238) placeholder on its branch and train 39 took 239 (240); the JS/TS naming records (a bare `module.exports` function, a named object literal's binding) carried 238, the name-keyed lookup qualifier 241 (242) and the Go function-valued slots 247 (248) on their branches, and the Rust slot join carries 249 (250) on its, so 251 sits above every live branch and -pull-request number. A cache +pull-request number. The Python conditional module-scope definition record (a `def` or `class` inside an `if`/`try`/`with`/loop +at module scope) carries a 255 (256) placeholder on its branch. A cache written by 0.6.8 is re-parsed once. `kCacheVersion` 36 → 46: 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, as is a JS/TS module-scope object literal's binding name, a Kotlin call's trailing-lambda byte (#155) widens the reference record, and a file's parse-health record gains the diff --git a/src/graph.h b/src/graph.h index 1c06a7b9a..31733e8c7 100644 --- a/src/graph.h +++ b/src/graph.h @@ -2040,7 +2040,9 @@ inline CallDisposition javaCandidateRefused( Graph& g, const Reference& r, bool // one call site. A function-LOCAL reassignment is a narrower fact (only THAT function's // calls are unsafe) and is carried the existing way instead — a `LocalBindKind::VarDecl` // Binding attributed to that function, read by `ExternalVeto::hasLocal` exactly like a -// parameter shadow already was. +// parameter shadow already was. Two pseudo-names ride the same table: kPythonDynamicBind / +// kPythonDynamicWrite (the file's dynamic namespace markers) and "?name" (kPythonCondDefPrefix: +// a module-scope def/class of name inside an `if`/`try`/`with`/loop). // freeName — C-family: the NAME of every scope-less (free) symbol or macro, declaration or definition, // anywhere in the corpus, restricted to names in the C-family table. A bare C++ call can // reach a free function given some declaration (an angle include is not path-resolvable, so @@ -2193,12 +2195,15 @@ inline bool isPythonSourcePath( std::string_view path ) noexcept return ext == ".py" || ext == ".pyi"; } -// the pseudo-name a file's DYNAMIC module-namespace writes are filed under in pythonModuleRebind ("#__getattr__"): a module -// `def __getattr__` (an ordinary top-level def, so buildPythonModuleRebindVetoes already records it) and, added by -// markPythonDynamicBinds, a `globals()` or `setattr(...)` call anywhere in the file (`globals()['a'] = x`, `setattr(sys.modules[__name__], ...)`): -// writes no static table can see. The file then binds ANY name it could be asked for; pythonFileBindsOnlyByImport and pythonPackageMayBind -// read it as unknown. `sys.modules[__name__].a = x` is not a call and stays a floor. -inline constexpr std::string_view kPythonDynamicBind = "__getattr__"; +// the two DYNAMIC module-namespace markers in pythonModuleRebind. kPythonDynamicBind ("#__getattr__"): a module `def __getattr__` +// (an ordinary top-level def, so buildPythonModuleRebindVetoes already records it; PEP 562: consulted only for a name the module does +// NOT bind). kPythonDynamicWrite ("#(globals)", a spelling no identifier has), added by markPythonDynamicBinds: a `globals()` +// or `setattr(...)` call anywhere in the file (`globals()['a'] = x`, `setattr(sys.modules[__name__], ...)`), a write no static table +// can see, so the file may rebind ANY name. pythonFileBindsOnlyByImport and pythonPackageMayBind read either as unknown +// (pythonDynamicNamespace); pythonRenamedImportPin lets `__getattr__` withhold only a name that may be unbound (kPythonCondDefPrefix). +// `sys.modules[__name__].a = x` is not a call and stays a floor. +inline constexpr std::string_view kPythonDynamicBind = "__getattr__"; +inline constexpr std::string_view kPythonDynamicWrite = "(globals)"; inline HashMap markPythonDynamicBinds( const IngestResult& ing, HashMap pythonModuleRebind ) { @@ -2208,7 +2213,7 @@ inline HashMap markPythonDynamicBinds( const IngestResult& in if( r.role == RefRole::Call && ( r.calleeName == "globals" || r.calleeName == "setattr" ) && r.fileId < ing.files.size() && isPythonSourcePath( rootRelPath( ing, r.fileId ) ) ) { - key.clear(); Narrower::appendUint( key, r.fileId ); key.push_back( '#' ); key.append( kPythonDynamicBind ); + key.clear(); Narrower::appendUint( key, r.fileId ); key.push_back( '#' ); key.append( kPythonDynamicWrite ); pythonModuleRebind.try_emplace( key, '\0' ); } } @@ -2569,6 +2574,13 @@ inline const std::string& fileNameKey( std::string& key, std::uint32_t id, std:: return key; } +// does Python file `f` carry either dynamic module-namespace marker (a module `__getattr__`, or a `globals()` / `setattr(...)` write)? +// The conservative reading: any name may be bound there by something no static table lists. +inline bool pythonDynamicNamespace( const HashMap& rebind, std::string& key, std::uint32_t f ) +{ + return rebind.contains( fileNameKey( key, f, kPythonDynamicBind ) ) || rebind.contains( fileNameKey( key, f, kPythonDynamicWrite ) ); +} + // ── Phase 5: the external-name VETO predicate (docs/EVALS.md "Phase 5", mechanism 1) ─────────────────── // `isExternalBound` says whether a call the ladder would otherwise SPRAY by name is provably bound outside // the indexed tree. Every branch that returns true is a name-resolution FACT of the language, never a guess @@ -7401,7 +7413,7 @@ inline bool pythonStarMayBind( const ExternalVetoTables& v, std::uint32_t f, std // default-withhold test a re-export chain applies at EVERY hop: exactly one module-scope binding, a member import with no rename // (`import a as name` and `from m import x as name` bind something else); no later rebinding of any form (an assignment, augmented // assignment, for/with/except target, walrus, def or class of the name, a `global` anywhere: pythonModuleRebind), no star import that could -// bind it, and no dynamic write (kPythonDynamicBind). Anything else, and anything unknown (no nameBinds row), is false: the chain stops +// bind it, and no dynamic namespace marker (pythonDynamicNamespace). Anything else, and anything unknown (no nameBinds row), is false: the chain stops // there, so the row is withheld, never shown and never a bridge. inline bool pythonFileBindsOnlyByImport( const ExternalVetoTables& v, std::uint32_t f, std::string_view name ) { @@ -7413,7 +7425,7 @@ inline bool pythonFileBindsOnlyByImport( const ExternalVetoTables& v, std::uint3 std::string key; return std::ranges::count_if( *binds, []( const PythonNameBind& b ) { return b.scope == kNoNode; } ) == 1 && std::ranges::any_of( *binds, []( const PythonNameBind& b ) { return b.scope == kNoNode && b.isImport && !b.moduleForm && b.member.empty(); } ) - && !v.pythonModuleRebind.contains( fileNameKey( key, f, name ) ) && !v.pythonModuleRebind.contains( fileNameKey( key, f, kPythonDynamicBind ) ) + && !v.pythonModuleRebind.contains( fileNameKey( key, f, name ) ) && !pythonDynamicNamespace( v.pythonModuleRebind, key, f ) && !pythonStarMayBind( v, f, name ); } @@ -7498,7 +7510,7 @@ inline bool pythonPackageMayBind( const ExternalVetoTables& v, std::uint32_t pkg { return true; } - return v.pythonModuleRebind.contains( fileNameKey( key, pkg, name ) ) || v.pythonModuleRebind.contains( fileNameKey( key, pkg, kPythonDynamicBind ) ) + return v.pythonModuleRebind.contains( fileNameKey( key, pkg, name ) ) || pythonDynamicNamespace( v.pythonModuleRebind, key, pkg ) || pythonStarMayBind( v, pkg, name ); } @@ -7653,7 +7665,7 @@ inline PythonRenamedImports buildPythonRenamedImports( const IngestResult& ing ) // PY-IMPORT-ALIAS-CALL: the ONE definition a bare Python call `y()` reaches through `from m import x as y`, else kNoNode. Decided only // when y's renamed import is its one binding where the call runs: no local or parameter of y in the caller or any def enclosing it // (localNameSet, which also holds a def-scoped import of y), no module-scope rebinding (`y = …`, `def y`, a `global y`, a dynamic -// namespace write: pythonModuleRebind), and no star import in the file (it may bind y after the rename); and only when m resolves to ONE +// namespace write: pythonModuleRebind; the caller file's own `__getattr__` is never consulted for a name it binds), and no star import in the file (it may bind y after the rename); and only when m resolves to ONE // indexed file (memberImportFile) whose module scope binds x ONLY by that one function or class (pythonEndpointBindsOnlyByClass). A package that // re-exports x defines none there: no decision, the call falls to the unchanged ladder (a floor, not a guess). A comprehension target or lambda parameter // named y that covers the call byte is a binding too (PythonRenamedImports::shadows: the site-spanned records localNameSet skips). @@ -7663,7 +7675,7 @@ inline NodeId pythonRenamedImportPin( const IngestResult& ing, const PythonRenam std::string key; const auto rit = imports.renamed.find( fileNameKey( key, r.fileId, r.calleeName ) ); if( rit == imports.renamed.end() || r.fromSymbol == kNoNode || r.fromSymbol >= ing.symbols.size() - || v.pythonModuleRebind.contains( key ) || v.pythonModuleRebind.contains( fileNameKey( key, r.fileId, kPythonDynamicBind ) ) + || v.pythonModuleRebind.contains( key ) || v.pythonModuleRebind.contains( fileNameKey( key, r.fileId, kPythonDynamicWrite ) ) || ( r.fileId < v.pythonScopes.starTargets.size() && !v.pythonScopes.starTargets[ r.fileId ].empty() ) || veto.hasLocal( r, r.calleeName ) || std::ranges::any_of( imports.shadows, [ &r ]( const PythonAliasShadow& s ) { return s.sym == r.fromSymbol && s.var == r.calleeName && r.startByte >= s.span.startByte && r.startByte < s.span.endByte; } ) ) @@ -7703,8 +7715,12 @@ inline NodeId pythonRenamedImportPin( const IngestResult& ing, const PythonRenam // m's own module scope must bind x ONLY by that def or class (pythonEndpointBindsOnlyByClass, the re-export chain's endpoint test): a later // `from fast import x`, a try-import, `x = other`, `x = deco(x)` or a star import that may bind x leaves the name something else at run time // (a star import of a file outside the tree, `from _operator import *`, is unknown: the call keeps the unchanged ladder), and a dynamic - // namespace write (`globals()[...]`, a module `__getattr__`) may bind it too - if( only != kNoNode && ( !pythonEndpointBindsOnlyByClass( v, mf->second, rit->second ) || v.pythonModuleRebind.contains( fileNameKey( key, mf->second, kPythonDynamicBind ) ) ) ) + // namespace write (`globals()[...]`, `setattr(...)`) may bind it too. A module `__getattr__` (PEP 562) answers only a name m does not + // bind, so it withholds only when x's def sits inside an `if`/`try`/`with`/loop at module scope (kPythonCondDefPrefix: it may not run) + if( only != kNoNode + && ( !pythonEndpointBindsOnlyByClass( v, mf->second, rit->second ) || v.pythonModuleRebind.contains( fileNameKey( key, mf->second, kPythonDynamicWrite ) ) + || ( v.pythonModuleRebind.contains( fileNameKey( key, mf->second, kPythonDynamicBind ) ) + && v.pythonModuleRebind.contains( fileNameKey( key, mf->second, std::string{ kPythonCondDefPrefix }.append( rit->second ) ) ) ) ) ) { return kNoNode; } diff --git a/src/ingest_binds.h b/src/ingest_binds.h index 8215d5706..0a3bbe49c 100644 --- a/src/ingest_binds.h +++ b/src/ingest_binds.h @@ -3378,6 +3378,35 @@ inline void capturePythonTypeParamDecls( TSNode n, std::uint32_t fileId, std::st } } +// is the def/class statement `n` at MODULE scope but inside a compound statement (`if`/`try`/`with`/`for`/`while`/`match`, any +// clause), so it may not run? False for a statement directly in the module (a decorator wrapping it changes nothing) and for one +// inside a def or class (not module scope). Read by the kPythonCondDefPrefix record below. +inline bool pythonDefIsConditionalAtModule( TSNode n ) +{ + TSNode p = ts_node_parent( n ); + if( !ts_node_is_null( p ) && kindIs( ts_node_type( p ), "decorated_definition" ) ) + { + p = ts_node_parent( p ); + } + if( ts_node_is_null( p ) || kindIs( ts_node_type( p ), "module" ) ) + { + return false; + } + for( ; !ts_node_is_null( p ); p = ts_node_parent( p ) ) + { + const char* pt = ts_node_type( p ); + if( kindIs( pt, "function_definition" ) || kindIs( pt, "class_definition" ) || kindIs( pt, "lambda" ) ) + { + return false; + } + if( kindIs( pt, "module" ) ) + { + return true; + } + } + return false; +} + inline void capturePythonRebindShadowDecls( TSNode n, const char* t, std::uint32_t fileId, Lang lang, std::string_view src, std::vector& binds ) { std::vector targets; // reused scratch, cleared before each shape below @@ -3464,6 +3493,11 @@ inline void capturePythonRebindShadowDecls( TSNode n, const char* t, std::uint32 { const std::uint32_t at = ts_node_start_byte( n ); // the def/class node's OWN start — see the doc above pushRawBind( fileId, lang, nodeTextOf( nm, src ), std::string{}, BindSite{ at > 0 ? at - 1 : 0, 0u, 0u }, LocalBindKind::VarDecl, binds ); + if( pythonDefIsConditionalAtModule( n ) ) + { + pushRawBind( fileId, lang, std::string{ kPythonCondDefPrefix }.append( nodeTextOf( nm, src ) ), std::string{}, + BindSite{ at > 0 ? at - 1 : 0, 0u, 0u }, LocalBindKind::VarDecl, binds ); + } } return; } diff --git a/src/ingest_cache.h b/src/ingest_cache.h index 2a08d7c38..561c48113 100644 --- a/src/ingest_cache.h +++ b/src/ingest_cache.h @@ -363,7 +363,7 @@ constexpr std::uint32_t kCacheVersion = 46; // 46: train 40 — lane j // (Py `pkg.mod`, TS `./x`, Rust `crate::a::b`/`mod:x`) — // a target FORMAT change → old caches must be rejected. // 4: Include gained a `bool isAngle` (quote/angle) field -constexpr std::uint32_t kParserVer = 251; // bump on any grammar/.scm/extraction change +constexpr std::uint32_t kParserVer = 255; // bump on any grammar/.scm/extraction change // 251 = 2026-10-27 (train 40: lanes js-def-naming (placeholder 238; JS `module.exports = ` named // by its file stem, module-scope `NAME = { … }` binding names), string-keyed (241; Pattern qualifier "t" at a // hasattr test / JS-TS subscript store) and fn-slot (247; Go function-valued slot records)). Strictly above diff --git a/src/model.h b/src/model.h index 61a3a7699..febe46677 100644 --- a/src/model.h +++ b/src/model.h @@ -1556,6 +1556,12 @@ struct RubyMainMixinRow std::uint32_t line = 0; // the constant's 1-based line }; +// PY-ALIAS-SPLIT-MARKER: a Python def or class statement at MODULE scope that sits inside a compound statement (`if`, `try`, +// `with`, `for`, `while`, `match`: it may not run) files, besides its ordinary VarDecl, a second VarDecl spelled with this prefix +// (`?f`). No identifier starts with it, so no name lookup ever matches the record; graph.h pythonRenamedImportPin reads it as "f may +// be unbound", the case where a module `__getattr__` (PEP 562, consulted only for names the module lacks) answers instead. +inline constexpr std::string_view kPythonCondDefPrefix = "?"; + struct Binding { NodeId fromSymbol = kNoNode; // enclosing function/method (the binding's scope); kNoNode if file-scope diff --git a/src/quality.h b/src/quality.h index bbdae4275..36063625a 100644 --- a/src/quality.h +++ b/src/quality.h @@ -2441,7 +2441,7 @@ inline std::string cacheRootKeyHex( const std::string& root ) // not include this header; it relies on ingest.cpp including quality.h (line 13) before ingest_cache.h, and a reorder // that broke that fails the build on the undeclared name rather than passing. constexpr std::uint32_t kIngestCacheVersionMirror = 46; // MUST equal ingest.cpp's kCacheVersion (gated); 46 = train 40 (js-def-naming JsObjectLiteral; its placeholder 45), 44 = train 37b (rust-degraded FileHealth::useBoundsBlanked; its placeholder 42), 43 = train 36 (#155 trailingLambda ref byte; above main 40, #438's placeholder 41, rust-degraded's 42), 40 = train 34 (fc3-js-rebind JsRebind; above main 39), 39 = train 33 (above main 36 and l6-iface 38), 38 = train 32 lane l6-iface fix round 1: LocalBindKind ImplBlock (train renumbers), 37 = train 32 lane l6-iface: LocalBindKind DispatchType (train renumbers), 36 = train 30 (above call-result-typing's and fcall-forward's 33, name-template-dispatch's 35, main's 34), 34 = train 29 (above lane arity-min's and call-result-typing's 33), 33 = train 29 lane arity-min: def record paramsMin (the train renumbers), 33 = lane call-result-typing: RawBind resultIndex + unwrapDepth, 33 = lane fcall-forward: ref `forwarded` byte, 35 = lane name-template-dispatch: RefRole Pattern 11, 32 = train 28: RefRole Subscript 9 + Inert 10, 31 = train 27: lego-transitive's inherit-ref baseBind (the lane's 30), 29 = FE-B ref memberPath/memberCtor + 4 bind kinds, 28 = FE-A ref memberCall/memberRoot, 27 = corrected fnScope values, 26 = function-local def scope span (25 = #157 + #150) -constexpr std::uint32_t kIngestParserVerMirror = 251; // MUST equal ingest.cpp's kParserVer (gated) +constexpr std::uint32_t kIngestParserVerMirror = 255; // MUST equal ingest.cpp's kParserVer (gated) // 251 = 2026-10-27 (train 40: js-def-naming / string-keyed / fn-slot extraction, // placeholders 238, 241, 247; above train 39's 239/240 and fn-slot-2's 249) // 239 = 2026-10-27 (train 39: receiver-fixes typing, placeholder 237/238; above train 38 diff --git a/test/qschemetrip.hash b/test/qschemetrip.hash index 0b9e25d5c..f4cc73e4c 100644 --- a/test/qschemetrip.hash +++ b/test/qschemetrip.hash @@ -1 +1 @@ -f4d5b120fc9f3f60c021a9fdec08844f52c4cea0606ff57bf4833329e2e7fe92 +eb9903e1e451d1655f4f5c104a805d5dfb4ccee8337e3b0a9672ab6da347a051 diff --git a/test/qschemetripcheck.sh b/test/qschemetripcheck.sh index d4b67e5c8..f3c54458e 100755 --- a/test/qschemetripcheck.sh +++ b/test/qschemetripcheck.sh @@ -34,6 +34,10 @@ 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-27, lane py-split-marker (PY-ALIAS-SPLIT-MARKER, base integration/train-40 1318069a; PLACEHOLDER, the train renumbers): +# RE-PINNED ONCE with UPDATE_GOLDEN=1. Watched text: kParserVer 251 -> 255 (rich 256; above fn-slot-2's 249 and train 40's 251/252: +# a Python module-scope def/class inside an if/try/with/loop files a second VarDecl `?name`), quality.h's kIngestParserVerMirror with it. +# kCacheVersion stays 46 (no record changes shape) and kQSnapCacheScheme stays 24: no watched quality.h function moved. # 2026-10-27, train 40 (lanes ci-flakes-2, r1-followup, js-def-naming, testonly-dead, string-keyed, alias-channel, fn-slot, for-route # merged onto main aba4d28e): RE-DERIVED ONCE with UPDATE_GOLDEN=1 on the composed tree (main's pin b4eb6e92… and the js-def-naming, string-keyed and # fn-slot (18dfbcac…) branch pins conflicted -> f4d5b120…). Watched text: kParserVer 239 -> 251 (rich 252; above the placeholders 238, 241, 247 and From f7da53abfd8550e5c47031aee8073de91b1d3f8f Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:58:53 -0400 Subject: [PATCH 16/95] pyaliascallcheck: a decorated def under a module __getattr__ still pins Co-Authored-By: Claude Opus 5.5 --- test/pyaliascallcheck.sh | 7 ++++--- test/pyaliascallfix/c_override.py | 7 +++++++ test/pyaliascallfix/mo_gadeco.py | 11 +++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 test/pyaliascallfix/mo_gadeco.py diff --git a/test/pyaliascallcheck.sh b/test/pyaliascallcheck.sh index 0260b81f9..4504b69bf 100644 --- a/test/pyaliascallcheck.sh +++ b/test/pyaliascallcheck.sh @@ -14,7 +14,8 @@ # c_class.py from m import Widget as Gadget ; Gadget() a class (target m.py:9 Widget) # c_shadow.py names = [g for g in fs] ; return g() a comprehension target does not leak: the call after it is the import # c_override.py mo_ga.py: f's def + a module `def __getattr__` PEP 562: __getattr__ answers only names m lacks, so it cannot -# override the def (PY-ALIAS-SPLIT-MARKER; target mo_ga.py:1) +# override the def (PY-ALIAS-SPLIT-MARKER; target mo_ga.py:1); +# mo_gadeco.py: the same with a decorated def a decorator is not a conditional (target mo_gadeco.py:6) # c_getattr.py the CALLER file defines `__getattr__` never consulted for a name the file binds (target m.py:1) # NEAR MISSES (must NOT bind m.py's f) # c_param.py def call_param(g): g() a parameter shadows the import @@ -62,7 +63,7 @@ callees(){ ( cd "$FIX" || exit 1; "$BIN" "$FIX" --no-cache --callees="$1" --lege # ── 0. presence: every fixture file parses (a fixture that fails to parse would pass the near misses inert) ── ( cd "$FIX" || exit 1; "$BIN" "$FIX" --no-cache --skipped --legend=compact ) >"$TMP/skipped" 2>/dev/null perl -pe 's///g' "$TMP/skipped" >"$TMP/skipped.body" -if ! grep -q 'degraded_parse="0"' "$TMP/skipped.body" || ! grep -q 'indexed="42"' "$TMP/skipped.body" || grep -q '//; s/^/ | /' "$TMP/callers"; fi # ── 1b. a module `__getattr__` cannot override a def that exists (PY-ALIAS-SPLIT-MARKER): target side mo_ga.py:1, caller side m.py:1 ── -for a in "call_mo_ga mo_ga.py:1" "call_cgetattr m.py:1"; do +for a in "call_mo_ga mo_ga.py:1" "call_mo_gadeco mo_gadeco.py:6" "call_cgetattr m.py:1"; do set -- $a callees "$1" row=$( grep -o "]*p=\"$2\"[^>]*/>" "$TMP/$1" ) diff --git a/test/pyaliascallfix/c_override.py b/test/pyaliascallfix/c_override.py index 6bbdef51e..c07e8443e 100644 --- a/test/pyaliascallfix/c_override.py +++ b/test/pyaliascallfix/c_override.py @@ -55,3 +55,10 @@ def call_mo_gatry(): def call_mo_gadyn(): return g11() + + +from mo_gadeco import f as g12 + + +def call_mo_gadeco(): + return g12() diff --git a/test/pyaliascallfix/mo_gadeco.py b/test/pyaliascallfix/mo_gadeco.py new file mode 100644 index 000000000..273b3ea9c --- /dev/null +++ b/test/pyaliascallfix/mo_gadeco.py @@ -0,0 +1,11 @@ +def deco(fn): + return fn + + +@deco +def f(): + return 1 + + +def __getattr__(name): + return len From 25e0fbfdaaea4315ec605ebf5ba81f14af6af5c7 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:25:39 -0400 Subject: [PATCH 17/95] python pin: read both dynamic markers through one list; the ?name record in its own helper (quality-delta) Co-Authored-By: Claude Opus 5.5 --- src/graph.h | 3 ++- src/ingest_binds.h | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/graph.h b/src/graph.h index 31733e8c7..ad0c42fa3 100644 --- a/src/graph.h +++ b/src/graph.h @@ -2578,7 +2578,8 @@ inline const std::string& fileNameKey( std::string& key, std::uint32_t id, std:: // The conservative reading: any name may be bound there by something no static table lists. inline bool pythonDynamicNamespace( const HashMap& rebind, std::string& key, std::uint32_t f ) { - return rebind.contains( fileNameKey( key, f, kPythonDynamicBind ) ) || rebind.contains( fileNameKey( key, f, kPythonDynamicWrite ) ); + return std::ranges::any_of( std::array{ kPythonDynamicBind, kPythonDynamicWrite }, + [ & ]( std::string_view marker ) { return rebind.contains( fileNameKey( key, f, marker ) ); } ); } // ── Phase 5: the external-name VETO predicate (docs/EVALS.md "Phase 5", mechanism 1) ─────────────────── diff --git a/src/ingest_binds.h b/src/ingest_binds.h index 0a3bbe49c..eb0262f30 100644 --- a/src/ingest_binds.h +++ b/src/ingest_binds.h @@ -3407,6 +3407,16 @@ inline bool pythonDefIsConditionalAtModule( TSNode n ) return false; } +// the kPythonCondDefPrefix record (`?name`) of a def/class statement `n` that may not run (pythonDefIsConditionalAtModule), at the +// def's own VarDecl site; nothing for any other statement +inline void pushPythonCondDefMark( TSNode n, std::string_view name, std::uint32_t fileId, Lang lang, BindSite at, std::vector& binds ) +{ + if( pythonDefIsConditionalAtModule( n ) ) + { + pushRawBind( fileId, lang, std::string{ kPythonCondDefPrefix }.append( name ), std::string{}, at, LocalBindKind::VarDecl, binds ); + } +} + inline void capturePythonRebindShadowDecls( TSNode n, const char* t, std::uint32_t fileId, Lang lang, std::string_view src, std::vector& binds ) { std::vector targets; // reused scratch, cleared before each shape below @@ -3493,11 +3503,7 @@ inline void capturePythonRebindShadowDecls( TSNode n, const char* t, std::uint32 { const std::uint32_t at = ts_node_start_byte( n ); // the def/class node's OWN start — see the doc above pushRawBind( fileId, lang, nodeTextOf( nm, src ), std::string{}, BindSite{ at > 0 ? at - 1 : 0, 0u, 0u }, LocalBindKind::VarDecl, binds ); - if( pythonDefIsConditionalAtModule( n ) ) - { - pushRawBind( fileId, lang, std::string{ kPythonCondDefPrefix }.append( nodeTextOf( nm, src ) ), std::string{}, - BindSite{ at > 0 ? at - 1 : 0, 0u, 0u }, LocalBindKind::VarDecl, binds ); - } + pushPythonCondDefMark( n, nodeTextOf( nm, src ), fileId, lang, BindSite{ at > 0 ? at - 1 : 0, 0u, 0u }, binds ); } return; } From 9b9611da053dc4924e149c33e07cd83508d6a33a Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:48:19 -0400 Subject: [PATCH 18/95] feat: a posted message class names its handlers (name-key post sites, --path ) Python message frameworks dispatch by a name the message class carries (textual Message.__init_subclass__: cls.handler_name = f"on_{name}"). src/handlerrule.h reads that rule from the repo's own source and keys a hedged name-key hop at the post site (post_message( Button.Pressed( self ) ) -> on_button_pressed). NameKeyHops serves the post sites beside the template lookups, so --impact, the --path bridge and near_sink follow them; a reachable=0 --path from a poster prints (rule p=, readers and what they pass the name to, posted classes, handlers <= 16 listed else count + next=). Gate test/handlerrulecheck.sh (727 gates). Co-Authored-By: Claude Opus 5.5 --- .github/pargates-shard-weights.json | 1 + README.md | 4 +- changelog.d/message-handler-rule.md | 26 ++ docs/EVALS.md | 6 +- present/deck5_ripwire_build.js | 6 +- src/backhops.h | 36 +- src/compactlegend.h | 12 +- src/handlerrule.h | 586 ++++++++++++++++++++++++++++ src/mcpverbs.h | 8 +- src/pathgaps.h | 265 ++++++++++++- src/valuerefindex.h | 5 + src/verbs_navigate.h | 9 +- test/handlerrulecheck.sh | 236 +++++++++++ test/regression.sh | 2 +- 14 files changed, 1177 insertions(+), 25 deletions(-) create mode 100644 changelog.d/message-handler-rule.md create mode 100644 src/handlerrule.h create mode 100755 test/handlerrulecheck.sh diff --git a/.github/pargates-shard-weights.json b/.github/pargates-shard-weights.json index 31be4775c..f5ac63e68 100644 --- a/.github/pargates-shard-weights.json +++ b/.github/pargates-shard-weights.json @@ -240,6 +240,7 @@ "greptiercheck.sh": 16.6, "guardmsgcheck.sh": 1.1, "handoffcheck.sh": 12.6, +"handlerrulecheck.sh": 3.0, "hasacheck.sh": 2.7, "hazardpatterncheck.sh": 5.2, "headsnapcachecheck.sh": 19.9, diff --git a/README.md b/README.md index 96f8f50c1..54a09d652 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
-726 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +727 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 **726 gate scripts** and is the authoritative list; +`test/regression.sh` names **727 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 diff --git a/changelog.d/message-handler-rule.md b/changelog.d/message-handler-rule.md new file mode 100644 index 000000000..c9cc5b0bf --- /dev/null +++ b/changelog.d/message-handler-rule.md @@ -0,0 +1,26 @@ +### Added — a posted message class names its handlers (`via="name-key"`, `` on `--path`) + +A Python message framework dispatches by a name the message's class carries: textual's `Message.__init_subclass__` writes +`cls.handler_name = f"on_{name}"`, and the pump looks the handler up with `cls.__dict__.get( method_name )`. That lookup takes a +parameter, so it can name every `on_*` method. The class is known where the message is posted, though: +`self.post_message( Button.Pressed( self ) )` names `Button.Pressed`, and its handler is `on_button_pressed`. Such a post site +is now a name-key hop to the methods of that name, with `cands=K` beside it. The hop shows in `--impact` +(`reaches_hedged_via="name-key"`), in the `--path` hedged bridge (at most 16 candidates), and in `near_sink`, where a +dispatched handler is now `why="declined:N" via="name-key"`, no longer `why="external"`. When `from=` is a poster and the +posted classes name a `to=` handler, a reachable="0" `--path` also prints ``. It holds the rule's `p=` and its +spelling, the functions that read the handler-name attribute with the method each one passes it to (textual: +`MessagePump._on_message` passes it to `MessagePump._get_dispatch_methods`, the MRO walk), and the posted classes, widest +first. The handler definitions are listed only when there are at most 16 of them. Above that, the answer gives a count and +`next=`, never a cut list. This is the post-site half of the name-keyed dispatch that lane string-keyed declined at the lookup. + +The rule is read from the repo's own source, never from a framework list. It needs a Python `__init_subclass__` that has a +`cls.A = f"P{v}"` line and calls a camel-to-snake converter (a body holding the `[a-z][A-Z]` regex and `.lower()`) on +`__qualname__`'s last two parts or on `cls.__name__`. A `{namespace}_` branch reads a `namespace="x"` class keyword. A post +site is a call to a method of a class that reads `.A` outside test paths, with a first argument that constructs a subclass: +`X( … )`, `self.X( … )`, `Owner.X( … )` or `module.X( … )`. + +What it does NOT mean: the name is matched, not traced. Which candidate runs depends on the receiving object's class, and the +index does not decide that. A hop is never counted in `reaches=`, `reachable=` or `hops=`. The floors are: a variable +argument (`msg = Tick(); post_message( msg )`); a factory (`events.Click.from_event( … )`); a converter or qualname cut of any +other shape; `@on( Message )`-decorated handlers (already shown as value refs); and a class body's own `handler_name =` +(the rule runs after the class body and overwrites it). Gate: `test/handlerrulecheck.sh`. diff --git a/docs/EVALS.md b/docs/EVALS.md index 0e2d7d4a2..4b579042d 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 726 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 727 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 **726 gate scripts**, all of which exist on disk. +naming **727 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 726. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 727. 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/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index d77c20956..e1d3067e2 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1214,7 +1214,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["726 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["727 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1238,7 +1238,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "726", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "727", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1540,7 +1540,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["192 long flags · 36 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["726 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["727 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 71 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/src/backhops.h b/src/backhops.h index f462499ab..ef593d119 100644 --- a/src/backhops.h +++ b/src/backhops.h @@ -20,6 +20,7 @@ // named, the width beside it) and never counts it in reachable= / hops= / reaches=. #include "graphlegend.h" // countFieldOrEmpty — reaches_hedged= absent at zero +#include "handlerrule.h" // HandlerRules — a posted message class names its handlers (name-key sites) #include "graph.h" // Graph — the declined records and their stored candidate lists #include "model.h" #include "nextverb.h" // nextAttrXml / nextFlag — the ring's continuation @@ -609,7 +610,7 @@ class QueueHops class NameKeyHops { public: - NameKeyHops( const IngestResult& ing, const ValueRefIndex& values ) : m_ing( ing ), m_values( values ) {} + NameKeyHops( const IngestResult& ing, const ValueRefIndex& values ) : m_ing( ing ), m_values( values ), m_rules( ing ) {} // The sink's NAME-KEY hops: one per function holding a template lookup that can name the sink (the sink's own lookups excluded). void backHops( NodeId sink, std::vector& out ) const @@ -635,7 +636,7 @@ class NameKeyHops return isCall( m_ing.references[ site ] ) && from != d && from < m_ing.symbols.size(); } ); } - return out; + return withPostSites( out, m_rules.sitesNaming( d ) ); // ON-NAME-HANDLERS-V2: a posted message class names d } // The template lookups made in u that make a call, in reference order. std::vector sitesIn( NodeId u ) const @@ -645,18 +646,39 @@ class NameKeyHops { std::ranges::copy_if( m_pix->sitesIn( std::span( &u, 1 ) ), std::back_inserter( out ), [ & ]( std::uint32_t site ) { return isCall( m_ing.references[ site ] ); } ); } - return out; + return withPostSites( out, m_rules.sitesIn( u ) ); } + // The message-handler rule (handlerrule.h): its post sites are name-key sites too. + const HandlerRules& rules() const noexcept { return m_rules; } // The definitions template lookup `site` can name, in symbol order (memoised: a walk asks once per site). A COPY: a later // insert may move the memo's storage while a caller (ForwardHops::confirm) still reads the set. std::vector targets( std::uint32_t site ) const { - EXPECTS( any(), "a name-key site comes from this index's own sites" ); const auto it = m_targets.find( site ); - return it != m_targets.end() ? it->second : m_targets.try_emplace( site, m_pix->targets( site ) ).first->second; + if( it != m_targets.end() ) + { + return it->second; + } + if( m_rules.siteAt( site ) != nullptr ) + { + return m_targets.try_emplace( site, m_rules.targets( site ) ).first->second; + } + EXPECTS( any(), "a name-key site comes from this index's own sites" ); + return m_targets.try_emplace( site, m_pix->targets( site ) ).first->second; } private: + // The template sites and the post sites, merged in reference order (each list is already in that order). + static std::vector withPostSites( std::vector sites, const std::vector& posts ) + { + if( !posts.empty() ) + { + const std::size_t mid = sites.size(); + sites.insert( sites.end(), posts.begin(), posts.end() ); + std::ranges::inplace_merge( sites, sites.begin() + std::ptrdiff_t( mid ) ); + } + return sites; + } // A usable template lookup that makes a call through the name: not too wide ("w"), not a presence test or a store ("t": hasattr, // `o[ 'p' + e ] = fn`; both are still clues, ingest_valuerefs.h emitPattern). static bool isCall( const Reference& r ) noexcept @@ -682,6 +704,7 @@ class NameKeyHops mutable bool m_built = false; mutable std::optional m_pix; mutable HashMap> m_targets; // site → the definitions it can name + HandlerRules m_rules; // ON-NAME-HANDLERS-V2: post sites keyed by the message class }; class BackwardHops @@ -1409,6 +1432,9 @@ inline constexpr std::string_view kNameKeyHopLegend = "(getattr(o, 'do_' + cmd), X.__dict__.get(f'on_{n}'), o['on' + e], o[`on${e}`]) that calls= (of= at d=1) fits (prefix and suffix around a non-empty " "hole; a self/this/cls receiver keeps the site's class family: the same class definition, its ancestors and its subclasses); cands=K the definitions that template can name. " "A presence test (hasattr) or a store (o['on' + e] = fn) is no lookup. The name is matched, never traced. " + "Or a POSTED MESSAGE (Python): called_by= passes a construction of a message class (self.post_message(Button.Pressed(self))) to a method of a class " + "that reads the handler-name attribute, and calls= is a method named by the repo's own __init_subclass__ rule for that class (on_button_pressed); " + "cands=K the methods of that name. " "Python, JavaScript, TypeScript. In a path bridge, s via=name-key is the same crossing: the previous row looks this row up by name. "; // Whether any ring row is a queue hop: the queue clause rides. inline bool impactRingHasQueue( const ImpactRing& ring ) noexcept diff --git a/src/compactlegend.h b/src/compactlegend.h index 0485c85e5..8f37e5f4d 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -365,10 +365,10 @@ inline constexpr CompactCompletenessTerm kCompactCompletenessTerms[] = // HOP1 (src/backhops.h kImpactRingLegend): --impact's hedged slot ring, present-only { "reaches_hedged", "reaches_hedged=N: N functions MAY reach of= through a hedged hop: one or two calls through a struct field or container (3 field calls within 16 callers), plus their proven callers past narrow hops; never in reaches= or any count" }, // STRING-KEYED (src/backhops.h kNameKeyHopLegend): present-only, on the impact root when a counted function joined through a name-key hop (a d=1 hop has no row) - { "reaches_hedged_via", "reaches_hedged_via=name-key: reaches_hedged= also counts functions that look a method up by a name built from a template (getattr(o, 'do_' + c), o['handle' + e]) that fits of=; matched, never traced; hasattr and a store are no lookup; a hedge" }, + { "reaches_hedged_via", "reaches_hedged_via=name-key: reaches_hedged= also counts functions that look a method up by a name built from a template (getattr(o, 'do_' + c), o['handle' + e]) that fits of=, or that post a message class whose handler name (the repo's __init_subclass__ rule) is of=; matched, never traced; hasattr and a store are no lookup; a hedge" }, { "calls", "ring d=1|2|3 total= shown= next=; rh called_by= called_at= via= calls=: called_by may call calls= through a field or container at called_at=; d=3 the third field hop; slot_fns=|cands=K the functions it may hold; via=queue d=1 a queue hand-off (a ring of only those is d=1); a hedge, in no count", false, "rh" }, // STRING-KEYED (src/backhops.h kNameKeyHopLegend): present-only, read only on a row - { "via", "rh via=name-key cands=K: called_by looks calls= up the same way; K the definitions it can name", true, "rh", MapHeaderRead::No, "name-key" }, + { "via", "rh via=name-key cands=K: called_by looks calls= up the same way (or posts a message class it is the handler of); K the definitions it can name", true, "rh", MapHeaderRead::No, "name-key" }, // hub-fold (src/backhops.h kImpactHubLegend): the folded ring member, its count and the page cut, present-only { "behind", "hub n= via= d= behind= tested= sample= next=: a ring member with over 16 proven callers, folded: behind= of reaches_hedged= unlisted (tested= a test reaches, sample= first 3: tested, no proven caller, same dir, rank); next= lists them", false, "hub" }, { "reaches_hedged_hubs", "reaches_hedged_hubs=K: K of them sit behind a hub" }, @@ -611,7 +611,7 @@ inline constexpr CompactCompletenessTerm kCompactCompletenessTerms[] = { "via", "via=impl: the receiver's interface/trait/abstract type has ONE implementor in the tree owning the target (an outside or macro-made one may exist)", true, "s", MapHeaderRead::No, "impl" }, { "via", "via=link: this edge is a markdown link, not a call", true, "s", MapHeaderRead::No, "link" }, // STRING-KEYED (src/pathgaps.h BridgeVia::NameKey): present-only, read only on a bridge row - { "via", "s via=name-key cands=K: the previous row looks this row up by a name built from a template (getattr(o, 'do_' + c), o['handle' + e]); K the definitions it can name; matched, never traced; a hedge", true, "s", MapHeaderRead::No, "name-key" }, + { "via", "s via=name-key cands=K: the previous row looks this row up by a name built from a template (getattr(o, 'do_' + c), o['handle' + e]) or posts a message class this row handles; K the definitions it can name; matched, never traced; a hedge", true, "s", MapHeaderRead::No, "name-key" }, { "anchor", "anchor=missing: the markdown link named a heading this file does not have, so the edge is the file", true, "s", MapHeaderRead::No, "missing" }, { "via", ": that callee matched by name alone (receiver unproven); it does NOT mean the edge is false", true, "c", MapHeaderRead::No, "name" }, { "via", ": the receiver's interface/trait/abstract type has ONE implementor in the tree owning the callee (an outside or macro-made one may exist)", true, "c", MapHeaderRead::No, "impl" }, @@ -862,8 +862,12 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "d", "ns d= t= n= p= callers= why=: hops back, symbol, its callers; why= un-edged ways in (declined|value|through|name|closure), external: none found; never a hop", true, "ns", MapHeaderRead::No, {}, "path" }, { "site", "ns in= site= met=1: one such site; met=1 the forward search reached in=", true, "ns", MapHeaderRead::No, {}, "path" }, // STRING-KEYED (src/pathgaps.h kNearSinkNameKeyLegend): present-only, read only on an ns row whose site is a name-key lookup - { "via", "ns via=name-key cands=K: why=declined here is a template lookup by name (getattr(o, 'do_' + c), o['handle' + e]) in=, naming K definitions incl. this one; matched, never traced, the bridge crosses at most 16; a hedge", true, "ns", MapHeaderRead::No, "name-key", "path" }, + { "via", "ns via=name-key cands=K: why=declined here is a template lookup by name (getattr(o, 'do_' + c), o['handle' + e]) or a posted message class this row handles in=, naming K definitions incl. this one; matched, never traced, the bridge crosses at most 16; a hedge", true, "ns", MapHeaderRead::No, "name-key", "path" }, { "withheld", "ns withheld=name:N: N callers whose by-name calls fail a name rule; not counted as a way in, never external", true, "ns", MapHeaderRead::No, {}, "path" }, + // ON-NAME-HANDLERS-V2 (src/pathgaps.h kHandlerRuleLegend): present-only, read only on the block and its rows + { "handlers", "handler_rule via=name-key base= attr= name= p= posts= classes= handlers= shown=: a posted message reaches a to= handler BY NAME: base's __init_subclass__ writes attr= (p=) as name= (snake = _ between lower and upper, then lower; Owner = enclosing class), read from this repo; posts= calls of from= constructing a subclass whose name fits to=, classes= those classes, handlers= the to= defs matched; next= lists one name's defs; matched, never traced; a hedge", true, "handler_rule", MapHeaderRead::No, {}, "path" }, + { "passes", "hrd n= p= passes=: a function reading attr= (not tests) and the method it hands the name to on that line", true, "hrd", MapHeaderRead::No, {}, "path" }, + { "h", "hm n= h= in= site= posts= cands=K: a posted class (widest first), its handler name, first post site, K defs of h=; every handler def when handlers= <= 16", true, "hm", MapHeaderRead::No, {}, "path" }, // quality-delta: src/verbs_quality.h (root emit) + src/quality.h identityDisclosure { "stale", "stale=N: ack ledger rows whose target no longer applies (sa rows); never gating", false, "quality-delta", MapHeaderRead::No, {}, "quality-delta" }, // idea #11 (lane lean-trio): present-only, on a regression-free answer whose stale sa rows were collapsed to counts diff --git a/src/handlerrule.h b/src/handlerrule.h new file mode 100644 index 000000000..64011aca4 --- /dev/null +++ b/src/handlerrule.h @@ -0,0 +1,586 @@ +#pragma once +// handlerrule.h — ON-NAME-HANDLERS-V2: a posted message CLASS names its handler methods (the Python layer of the name-key hop +// family, backhops.h NameKeyHops). +// +// THE GAP. A message framework dispatches by a name the message's class carries: textual's `Message.__init_subclass__` writes +// `cls.handler_name = f"on_{name}"` (name = camel_to_snake of the owner and class names), and the pump looks the handler up +// with `cls.__dict__.get( method_name )` where method_name is a PARAMETER. Nothing at the lookup is provable (every on_* def +// fits it), so lane string-keyed declined it. The class is known at the POST SITE: `self.post_message( Button.Pressed( self ) )` +// names Button.Pressed, whose handler name is on_button_pressed. +// +// THE CONTRACT. HandlerRules reads the RULE from the repo's own source, never from a framework list: +// rule a Python `__init_subclass__` of class B with a line `cls.A = f"P{v}"` (literal identifier prefix P, one hole) that +// calls a converter whose body is the `[a-z][A-Z]` regex plus `.lower()` (camel_to_snake: an underscore between a +// lower and an upper letter, then all lower), on `cls.__qualname__` cut to its last two parts (`[-2:]`: owner +// class + class) or on `cls.__name__` alone; a `{namespace}_` branch makes a `namespace="x"` class keyword the +// owner part. Any other shape: no rule, no hop (a floor). A class body that writes A is overwritten by the +// rule (it runs after the body), so the rule decides. +// readers the functions outside test paths that read A; their classes hold the POSTERS (every method name they define). +// post site a call to a poster whose FIRST argument is a construction `X( … )`, `self.X( … )`, `Owner.X( … )` or +// `module.X( … )` of a subclass of B (written bases, valuerefindex.h ClassFamilies). A variable argument, +// a factory (`X.from_event( … )`) or an unresolved class: no hop. +// targets the methods (a definition inside a class) named by the rule's name for that class: a hedge — which of them +// runs depends on the receiving object's class, which the index does not decide. +// The hop is never a call edge: consumers print it via="name-key" with its width (cands=), never in reaches=/hops=. + +#include "darkflags.h" // readWhole — the rule and the post site are read from the file text +#include "filter.h" // isTestPath — a test's reader does not make posters +#include "model.h" +#include "valuerefindex.h" // ClassFamilies — owner class and written-base family + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rw +{ + +struct HandlerRule +{ + NodeId base = kNoNode; // the class whose __init_subclass__ writes the attribute + NodeId writer = kNoNode; // that __init_subclass__ + std::uint32_t fileId = 0; // where the rule's write is (p=) + std::uint32_t line = 0; + std::string attr; // the class attribute written (handler_name) + std::string prefix; // the template's literal prefix (on_) + bool qualified = false; // __qualname__[-2:]: owner class + class; else the class name alone + bool nsKeyword = false; // a namespace= class keyword replaces the owner part +}; + +struct HandlerReader +{ + NodeId fn = kNoNode; // the function reading the attribute + std::uint32_t line = 0; // the line of its first read +}; + +struct HandlerPostSite +{ + std::uint32_t ref = 0; // the poster call reference + std::vector names; // the handler names its message class(es) carry, sorted, unique + std::vector classes; // the message class definitions the construction may mean +}; + +// camel_to_snake as the `[a-z][A-Z]` rule states it: an underscore between each non-overlapping lower-upper pair, then lower. +inline std::string snakeOfCamel( std::string_view s ) +{ + std::string out; + out.reserve( s.size() + 4 ); + for( std::size_t i = 0; i < s.size(); ++i ) + { + const unsigned char c = static_cast( s[i] ); + out.push_back( char( std::tolower( c ) ) ); + if( std::islower( c ) && i + 1 < s.size() && std::isupper( static_cast( s[ i + 1 ] ) ) ) + { + out.push_back( '_' ); + out.push_back( char( std::tolower( static_cast( s[ i + 1 ] ) ) ) ); + ++i; + } + } + return out; +} + +class HandlerRules +{ +public: + explicit HandlerRules( const IngestResult& ing ) : m_ing( ing ), m_classes( ing ) {} + + // Built on first use; a tree with no Python __init_subclass__ write pays one reference scan. + bool any() const + { + build(); + return !m_sites.empty(); + } + const std::vector& rules() const { build(); return m_rules; } + const std::vector& sites() const { build(); return m_sites; } + const ClassFamilies& classes() const noexcept { return m_classes; } + // The post site at reference `ref`, or null. + const HandlerPostSite* siteAt( std::uint32_t ref ) const + { + build(); + const auto it = std::ranges::lower_bound( m_sites, ref, {}, &HandlerPostSite::ref ); + return it != m_sites.end() && it->ref == ref ? &*it : nullptr; + } + // The post sites whose message names handler `d` (a method), in reference order. + std::vector sitesNaming( NodeId d ) const + { + std::vector out; + if( !any() || !isHandlerShaped( d ) ) + { + return out; + } + for( const HandlerPostSite& s : m_sites ) + { + if( m_ing.references[ s.ref ].fromSymbol != d && std::ranges::binary_search( s.names, std::string_view( m_ing.symbols[d].name ) ) ) + { + out.push_back( s.ref ); + } + } + return out; + } + // The post sites made in `u`, in reference order. + std::vector sitesIn( NodeId u ) const + { + std::vector out; + if( any() ) + { + for( const HandlerPostSite& s : m_sites ) + { + if( m_ing.references[ s.ref ].fromSymbol == u ) + { + out.push_back( s.ref ); + } + } + } + return out; + } + // The methods named by post site `ref`'s handler names, in symbol order. + std::vector targets( std::uint32_t ref ) const + { + std::vector out; + if( const HandlerPostSite* s = siteAt( ref ) ) + { + for( const std::string& n : s->names ) + { + const auto it = m_methodsByName.find( std::string_view( n ) ); + if( it != m_methodsByName.end() ) + { + out.insert( out.end(), it->second.begin(), it->second.end() ); + } + } + std::ranges::sort( out ); + out.erase( std::ranges::unique( out ).begin(), out.end() ); + } + return out; + } + // The rule's name for message class `c` (empty when no rule covers it). + std::string handlerNameOf( NodeId c ) const + { + build(); + for( const HandlerRule& r : m_rules ) + { + if( c != r.base && m_classes.descendsFrom( c, r.base ) ) + { + return nameUnder( r, c ); + } + } + return {}; + } + // The functions outside test paths that read the rule attribute, in symbol order. + const std::vector& readers() const { build(); return m_readers; } + // The file text (empty when unreadable). + const std::string& text( std::uint32_t fileId ) const + { + static const std::string kEmpty; + if( fileId >= m_ing.files.size() ) + { + return kEmpty; + } + if( m_text.size() != m_ing.files.size() ) + { + m_text.resize( m_ing.files.size() ); + m_textRead.assign( m_ing.files.size(), 0 ); + } + if( m_textRead[ fileId ] == 0 ) + { + m_textRead[ fileId ] = 1; + m_text[ fileId ] = darkflags::readWhole( diskPath( m_ing, fileId ) ).value_or( std::string() ); + } + return m_text[ fileId ]; + } + +private: + bool isHandlerShaped( NodeId d ) const + { + const Symbol& s = m_ing.symbols[d]; + return s.lang == Lang::Python && ( s.kind == SymKind::Function || s.kind == SymKind::Method ) && m_classes.ownerOf( d ) != kNoNode; + } + std::string_view span( const Symbol& s ) const + { + const std::string& t = text( s.fileId ); + return s.sigStartByte < s.endByte && s.endByte <= t.size() ? std::string_view( t ).substr( s.sigStartByte, s.endByte - s.sigStartByte ) : std::string_view(); + } + std::string_view lineAt( const Reference& r ) const + { + const std::string_view t( text( r.fileId ) ); + if( r.startByte >= t.size() ) + { + return {}; + } + const std::size_t b = t.rfind( '\n', r.startByte ); + const std::size_t e = t.find( '\n', r.startByte ); + const std::size_t from = b == std::string_view::npos ? 0 : b + 1; + return t.substr( from, ( e == std::string_view::npos ? t.size() : e ) - from ); + } + static bool isIdent( char c ) noexcept { return std::isalnum( static_cast( c ) ) || c == '_'; } + static std::string_view trimmed( std::string_view s ) noexcept + { + while( !s.empty() && std::isspace( static_cast( s.front() ) ) ) + { + s.remove_prefix( 1 ); + } + while( !s.empty() && std::isspace( static_cast( s.back() ) ) ) + { + s.remove_suffix( 1 ); + } + return s; + } + // `cls.A = f"P{v}"` (either quote) → prefix P; nullopt for any other line. + static std::optional templatePrefix( std::string_view line, std::string_view attr ) + { + line = trimmed( line ); + const std::string head = "cls." + std::string( attr ); + if( !line.starts_with( head ) ) + { + return std::nullopt; + } + line = trimmed( line.substr( head.size() ) ); + if( !line.starts_with( '=' ) || line.starts_with( "==" ) ) + { + return std::nullopt; + } + line = trimmed( line.substr( 1 ) ); + if( line.size() < 6 || line[0] != 'f' || ( line[1] != '"' && line[1] != '\'' ) || line.back() != line[1] ) + { + return std::nullopt; + } + const std::string_view body = line.substr( 2, line.size() - 3 ); + const std::size_t open = body.find( '{' ); + if( open == 0 || open == std::string_view::npos || body.back() != '}' || body.find( '{', open + 1 ) != std::string_view::npos + || !std::ranges::all_of( body.substr( 0, open ), isIdent ) || !std::ranges::all_of( body.substr( open + 1, body.size() - open - 2 ), isIdent ) ) + { + return std::nullopt; + } + return std::string( body.substr( 0, open ) ); + } + // The writer calls a camel_to_snake converter: a callee defined in Python whose body holds the `[a-z][A-Z]` rule and `.lower()`. + bool callsSnakeConverter( NodeId writer ) const + { + for( const Reference& r : m_ing.references ) + { + if( r.fromSymbol != writer || r.role != RefRole::Call ) + { + continue; + } + for( const Symbol& f : m_ing.symbols ) + { + if( f.name == r.calleeName && f.lang == Lang::Python && f.kind == SymKind::Function ) + { + const std::string_view body = span( f ); + if( body.find( "[a-z][A-Z]" ) != std::string_view::npos && body.find( ".lower()" ) != std::string_view::npos ) + { + return true; + } + } + } + } + return false; + } + // One `cls.A = f"P{v}"` line of __init_subclass__ `writer` (at byte `at` of its file) as a rule, or nullopt. + std::optional ruleAt( NodeId writer, std::string_view line, std::uint32_t lineNo ) const + { + const std::string_view t = trimmed( line ); + std::size_t e = 4; + while( e < t.size() && isIdent( t[e] ) ) + { + ++e; + } + if( !t.starts_with( "cls." ) || e == 4 ) + { + return std::nullopt; + } + HandlerRule rule; + rule.attr = std::string( t.substr( 4, e - 4 ) ); + const std::optional prefix = templatePrefix( t, rule.attr ); + rule.writer = writer; + rule.base = m_classes.ownerOf( writer ); + if( !prefix || rule.base == kNoNode || !callsSnakeConverter( writer ) ) + { + return std::nullopt; + } + const std::string_view body = span( m_ing.symbols[ writer ] ); + rule.qualified = body.find( "__qualname__" ) != std::string_view::npos; + if( rule.qualified ? body.find( "[-2:]" ) == std::string_view::npos : body.find( "cls.__name__" ) == std::string_view::npos ) + { + return std::nullopt; // a qualname cut other than the last two parts, or no class name at all: not a rule we read + } + rule.nsKeyword = body.find( "{namespace}_" ) != std::string_view::npos; + rule.fileId = m_ing.symbols[ writer ].fileId; + rule.line = lineNo; + rule.prefix = *prefix; + return rule; + } + // `namespace="x"` in class c's header, else empty. + std::string namespaceKeyword( NodeId c ) const + { + const Symbol& s = m_ing.symbols[c]; + const std::string_view all = span( s ); + const std::string_view head = all.substr( 0, std::min( all.find( ':' ), all.size() ) ); + const std::size_t at = head.find( "namespace" ); + if( at == std::string_view::npos ) + { + return {}; + } + std::string_view v = trimmed( head.substr( at + 9 ) ); + if( !v.starts_with( '=' ) ) + { + return {}; + } + v = trimmed( v.substr( 1 ) ); + if( v.size() < 2 || ( v[0] != '"' && v[0] != '\'' ) ) + { + return {}; + } + const std::size_t close = v.find( v[0], 1 ); + return close == std::string_view::npos ? std::string() : std::string( v.substr( 1, close - 1 ) ); + } + std::string nameUnder( const HandlerRule& r, NodeId c ) const + { + const std::string ns = r.nsKeyword ? namespaceKeyword( c ) : std::string(); + if( !ns.empty() ) + { + return r.prefix + ns + "_" + snakeOfCamel( m_ing.symbols[c].name ); + } + const NodeId owner = r.qualified ? m_classes.ownerOf( c ) : kNoNode; + return r.prefix + ( owner != kNoNode ? snakeOfCamel( m_ing.symbols[ owner ].name ) + "_" : std::string() ) + snakeOfCamel( m_ing.symbols[c].name ); + } + // The construction a post site's first argument spells: `a.b.X(` after `callee(` → {a, b, X}; empty when it is not one. + std::vector constructionAt( const Reference& r ) const + { + const std::string_view t( text( r.fileId ) ); + std::size_t at = r.startByte < t.size() ? t.find( r.calleeName, r.startByte ) : std::string_view::npos; + if( at == std::string_view::npos || at > r.startByte + 256 ) + { + return {}; + } + std::size_t i = at + r.calleeName.size(); + const auto skip = [ & ] { while( i < t.size() && std::isspace( static_cast( t[i] ) ) ) { ++i; } }; + skip(); + if( i >= t.size() || t[i] != '(' ) + { + return {}; + } + ++i; + skip(); + std::vector segs; + for( ;; ) + { + const std::size_t b = i; + while( i < t.size() && isIdent( t[i] ) ) + { + ++i; + } + if( i == b || std::isdigit( static_cast( t[b] ) ) ) + { + return {}; + } + segs.push_back( t.substr( b, i - b ) ); + if( i < t.size() && t[i] == '.' ) + { + ++i; + continue; + } + break; + } + skip(); + return i < t.size() && t[i] == '(' ? segs : std::vector(); + } + // The message classes construction `segs` in reference r may mean: same name; the owner the qualifier spells. + std::vector classesFor( const Reference& r, std::span segs ) const + { + std::vector out; + const auto it = m_messagesByName.find( segs.back() ); + if( it == m_messagesByName.end() ) + { + return out; + } + const NodeId caller = r.fromSymbol < m_ing.symbols.size() ? m_classes.ownerOf( r.fromSymbol ) : kNoNode; + const std::string_view q = segs.size() >= 2 ? segs[ segs.size() - 2 ] : std::string_view(); + const bool viaSelf = segs.size() == 2 && ( q == "self" || q == "cls" ); + const bool viaOwner = !viaSelf && !q.empty() && std::isupper( static_cast( q.front() ) ); + std::ranges::copy_if( it->second, std::back_inserter( out ), [ & ]( NodeId c ) + { + const NodeId owner = m_classes.ownerOf( c ); + if( viaSelf ) + { + return owner != kNoNode && caller != kNoNode && m_classes.sameFamily( caller, owner ); + } + if( viaOwner ) + { + return owner != kNoNode && m_ing.symbols[ owner ].name == q; + } + return owner == kNoNode; // a bare or module-qualified name: a top-level class + } ); + return out; + } + // Every Python __init_subclass__'s lines, read from the file (the index keeps no attribute writes on a lean verb). + void collectRules() const + { + for( const Symbol& w : m_ing.symbols ) + { + if( w.lang != Lang::Python || w.name != "__init_subclass__" || ( w.kind != SymKind::Function && w.kind != SymKind::Method ) ) + { + continue; + } + const std::string_view body = span( w ); + std::uint32_t lineNo = w.line; + for( std::size_t at = 0; at < body.size(); ) + { + const std::size_t nl = std::min( body.find( '\n', at ), body.size() ); + if( std::optional rule = ruleAt( w.id, body.substr( at, nl - at ), lineNo ) ) + { + m_rules.push_back( std::move( *rule ) ); + } + at = nl + 1; + ++lineNo; + } + } + } + void collectMessagesAndMethods() const + { + for( const Symbol& s : m_ing.symbols ) + { + if( s.lang != Lang::Python ) + { + continue; + } + if( s.kind == SymKind::Class && !handlerNameOf( s.id ).empty() ) + { + m_messagesByName[ std::string_view( s.name ) ].push_back( s.id ); + } + else if( isHandlerShaped( s.id ) ) + { + m_methodsByName[ std::string_view( s.name ) ].push_back( s.id ); + } + } + } + // The innermost Python function definition of `fileId` holding byte `at`, else kNoNode. + NodeId functionAt( std::uint32_t fileId, std::uint32_t at ) const + { + NodeId best = kNoNode; + for( const NodeId f : m_fnsOfFile[ fileId ] ) + { + const Symbol& s = m_ing.symbols[f]; + if( s.sigStartByte <= at && at < s.endByte && ( best == kNoNode || s.sigStartByte >= m_ing.symbols[ best ].sigStartByte ) ) + { + best = f; + } + } + return best; + } + // Readers of a rule attribute (`.A` not assigned) in Python files outside test paths, read from the text, and every method + // name their classes define (the posters). + HashMap collectPosters() const + { + m_fnsOfFile.assign( m_ing.files.size(), {} ); + for( const Symbol& s : m_ing.symbols ) + { + if( s.lang == Lang::Python && ( s.kind == SymKind::Function || s.kind == SymKind::Method ) ) + { + m_fnsOfFile[ s.fileId ].push_back( s.id ); + } + } + std::vector readerClasses; + for( std::uint32_t f = 0; f < m_ing.files.size(); ++f ) + { + if( m_fnsOfFile[f].empty() || isTestPath( m_ing.files[f] ) ) + { + continue; + } + const std::string_view t( text( f ) ); + for( const HandlerRule& rule : m_rules ) + { + const std::string needle = "." + rule.attr; + for( std::size_t at = t.find( needle ); at != std::string_view::npos; at = t.find( needle, at + 1 ) ) + { + const std::size_t end = at + needle.size(); + std::size_t next = end; + while( next < t.size() && t[ next ] == ' ' ) + { + ++next; + } + const bool store = next < t.size() && t[ next ] == '=' && ( next + 1 >= t.size() || t[ next + 1 ] != '=' ); + if( ( end < t.size() && isIdent( t[ end ] ) ) || store ) + { + continue; + } + const NodeId fn = functionAt( f, std::uint32_t( at ) ); + if( fn == kNoNode || fn == rule.writer || std::ranges::contains( m_readers, fn, &HandlerReader::fn ) ) + { + continue; + } + m_readers.push_back( HandlerReader{ fn, std::uint32_t( 1 + std::ranges::count( t.substr( 0, at ), '\n' ) ) } ); + if( const NodeId owner = m_classes.ownerOf( fn ); owner != kNoNode ) + { + readerClasses.push_back( owner ); + } + } + } + } + std::ranges::sort( m_readers, {}, &HandlerReader::fn ); + std::ranges::sort( readerClasses ); + HashMap posters; + for( const auto& [ name, defs ] : m_methodsByName ) + { + if( std::ranges::any_of( defs, [ & ]( NodeId d ) { return std::ranges::binary_search( readerClasses, m_classes.ownerOf( d ) ); } ) ) + { + posters[ name ] = 1; + } + } + return posters; + } + void build() const + { + if( m_built ) + { + return; + } + m_built = true; + collectRules(); + if( m_rules.empty() ) + { + return; + } + collectMessagesAndMethods(); + const HashMap posters = collectPosters(); + for( std::uint32_t i = 0; i < m_ing.references.size(); ++i ) + { + const Reference& r = m_ing.references[i]; + if( r.role != RefRole::Call || r.lang != Lang::Python || r.fromSymbol >= m_ing.symbols.size() || !posters.contains( std::string_view( r.calleeName ) ) ) + { + continue; + } + const std::vector segs = constructionAt( r ); + HandlerPostSite site { i, {}, segs.empty() ? std::vector() : classesFor( r, segs ) }; + for( const NodeId c : site.classes ) + { + site.names.push_back( handlerNameOf( c ) ); + } + std::ranges::sort( site.names ); + site.names.erase( std::ranges::unique( site.names ).begin(), site.names.end() ); + if( !site.names.empty() ) + { + m_sites.push_back( std::move( site ) ); + } + } + } + + const IngestResult& m_ing; + ClassFamilies m_classes; + mutable bool m_built = false; + mutable std::vector m_rules; + mutable std::vector m_sites; // reference order + mutable std::vector m_readers; + mutable std::vector> m_fnsOfFile; // per file: its Python function definitions + mutable HashMap> m_messagesByName; + mutable HashMap> m_methodsByName; + mutable std::vector m_text; + mutable std::vector m_textRead; +}; + +} // namespace rw diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 32b9aac4b..525fd80f0 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -3805,6 +3805,7 @@ inline std::optional pathText( const std::string& root, const std:: ptBackHops.emplace( ing, g, valueRefIndexOf( ix ) ); } const PathNearSink ptNear = pth.empty() ? pathNearSink( ing, g, dstDefs, ptGaps.cone, valueRefIndexOf( ix ), ptBackHops->names() ) : PathNearSink{}; // ix2-slice, as on the CLI + const PathHandlerRule ptHandler = pth.empty() ? pathHandlerRule( ing, ptBackHops->names(), srcDefs, dstDefs ) : PathHandlerRule{}; // ON-NAME-HANDLERS-V2, as on the CLI // TRACE-EP: the CLI's reaching definition pairs, by the same call (src/pathpairs.h) const PathPairs ptPairs = pth.empty() || srcGlob || dstGlob ? PathPairs{} : pathReachingPairs( ing, g, srcDefs, dstDefs, pth, std::string( from ) + "," + std::string( to ), [ & ]( std::span dstMembers ) @@ -3833,7 +3834,7 @@ inline std::optional pathText( const std::string& root, const std:: + memberOwnerLegend( ptHasOwner ) + memberOwnerContainedLegend( ptMarks.ownerContained ) + std::string( pathPairsLegend( ptPairs ) ) + pathOrderLegend( ptPairs, ptTwin ) ).c_str(), ( std::string( toValueRefsLegend( ptToValueRefs.count > 0 ) ) + valueRefsDepthLegend( ptToValueRefs.depthCut.files > 0 ) ).c_str(), ( pathGapsLegend( ptGaps.any(), !ptSinkCallers.empty() ) + pathBridgeLegend( ptBridge ) - + nearSinkLegend( ptNear ) + std::string( pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), + + nearSinkLegend( ptNear ) + std::string( pathHandlerRuleLegend( ptHandler ) ) + std::string( pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), graphCountFloorBrief( rw::graphGaugeClauses( g ) ).c_str(), rootRelPathsLegend( ptSingleRoot ) ); rw::emitTo( mem, " pathText( const std::string& root, const std:: const PathGapsXml ptUnreached = pth.empty() ? pathUnreachedXml( ing, ptGaps, ptSingleRoot, ptRootPrefix, { "the connect verb on " + from + "," + to, "uses/impact", "" }, ptSinkCallers ) : PathGapsXml{}; - rw::emitTo( mem, "{}>{}{}{}", ptUnreached.rootAttrs, ptUnreached.rows, pathBridgeXml( ing, g, ptBridge, ptSingleRoot, ptRootPrefix ), - pathNearSinkXml( ing, ptNear, ptSingleRoot, ptRootPrefix ) ); + rw::emitTo( mem, "{}>{}{}{}{}", ptUnreached.rootAttrs, ptUnreached.rows, pathBridgeXml( ing, g, ptBridge, ptSingleRoot, ptRootPrefix ), + pathNearSinkXml( ing, ptNear, ptSingleRoot, ptRootPrefix ), + pth.empty() ? pathHandlerRuleXml( ing, ptBackHops->names(), ptHandler, ptSingleRoot, ptRootPrefix ) : std::string() ); emitPathAnswerRows( mem, ing, g, ptPairs, pth, ptOwners, ptSingleRoot, ptRootPrefix ); // the CLI's own row emitter (pathpairs.h / defattrib.h) rw::emitRaw( mem, "" ); return mcpAnswerText( stream ); // nullopt = the buffer lost bytes (dispatch answers -32603), "" stays not-found diff --git a/src/pathgaps.h b/src/pathgaps.h index 946d50e0c..bcee32a51 100644 --- a/src/pathgaps.h +++ b/src/pathgaps.h @@ -45,6 +45,7 @@ #include "infra/namesplit.h" // afterLast — a bound scope's final segment, the base index key (K5 rule 2c) #include +#include #include #include #include @@ -1045,7 +1046,8 @@ inline constexpr std::string_view kNearSinkLegend = inline constexpr std::string_view kNearSinkNameKeyLegend = "ns via=name-key cands=K: the declined site is a NAME-KEYED lookup, not a call: in= looks a method up by a name built from a template " "(getattr(o, 'do_' + c), o['on' + e]) that fits this row, a template that can name K definitions including it; matched, never traced; " - "the bridge crosses a lookup of at most 16, so a wider one is a way in with no bridge row. "; + "the bridge crosses a lookup of at most 16, so a wider one is a way in with no bridge row. Or a posted message: in= passes a construction of a " + "message class whose handler name (the repo's own __init_subclass__ rule) is this row's name, at site=; K the methods of that name. "; inline std::string nearSinkLegend( const PathNearSink& ns ) { const std::size_t shown = std::min( ns.rows.size(), kNearSinkRows ); @@ -1535,4 +1537,265 @@ inline std::string pathBridgeXml( const IngestResult& ing, const Graph& g, const return out + ""; } +// ── ON-NAME-HANDLERS-V2: the message-handler rule on a reachable=0 --path ───────────────────────────────────────────────── +// When from= is a poster (handlerrule.h: a method of a class that reads the rule attribute) called at post sites whose +// message class names a to= handler, the answer says HOW the message gets there by name: the rule (where the repo writes the +// handler name and its spelling), the readers that pass the name on (the dispatch side, by name on the read's own line), +// and the posted classes with the width of each handler name. All of it is a HEDGE: matched by name, never traced, never in +// reachable=/hops=; a handler name with more than kHandlerRuleListMax definitions is a count with next=, never a cut list. +inline constexpr std::size_t kHandlerRuleShown = 3; // rows: the widest posted classes first +inline constexpr std::size_t kHandlerRuleReaders = 4; // rows +inline constexpr std::size_t kHandlerRuleListMax = 16; // the bridge's own width limit: at most this many defs are listed as + +struct PathHandlerRow +{ + NodeId cls = kNoNode; + std::uint32_t site = 0; // the first post site of that class + std::uint32_t posts = 0; + std::uint32_t cands = 0; + std::string handler; +}; +struct PathHandlerRule +{ + const HandlerRule* rule = nullptr; + std::uint32_t posts = 0; + std::uint32_t defs = 0; + std::vector rows; // every posted class, widest first + std::vector listed; // the handler defs when defs <= kHandlerRuleListMax + bool found() const noexcept { return rule != nullptr && !rows.empty(); } +}; + +inline PathHandlerRule pathHandlerRule( const IngestResult& ing, const NameKeyHops& names, std::span srcDefs, std::span dstDefs ) +{ + PathHandlerRule out; + const HandlerRules& hr = names.rules(); + if( !hr.any() ) + { + return out; + } + std::vector fromNames; + for( const NodeId s : srcDefs ) + { + fromNames.push_back( ing.symbols[s].name ); + } + std::vector toNames; + for( const NodeId d : dstDefs ) + { + toNames.push_back( ing.symbols[d].name ); + } + std::ranges::sort( toNames, sortutil::svLess ); + toNames.erase( std::ranges::unique( toNames ).begin(), toNames.end() ); + std::vector hit; // the handler names a counted post site carries that to= names + for( const HandlerPostSite& site : hr.sites() ) + { + const Reference& r = ing.references[ site.ref ]; + if( !std::ranges::contains( fromNames, std::string_view( r.calleeName ) ) ) + { + continue; + } + bool counted = false; + for( std::size_t k = 0; k < site.classes.size(); ++k ) + { + const std::string h = hr.handlerNameOf( site.classes[k] ); + if( !std::ranges::binary_search( toNames, std::string_view( h ), sortutil::svLess ) ) + { + continue; + } + counted = true; + hit.push_back( h ); + const auto row = std::ranges::find( out.rows, site.classes[k], &PathHandlerRow::cls ); + if( row != out.rows.end() ) + { + ++row->posts; + continue; + } + const std::vector t = hr.targets( site.ref ); + out.rows.push_back( PathHandlerRow{ site.classes[k], site.ref, 1, std::uint32_t( std::ranges::count_if( t, [ & ]( NodeId d ) { return ing.symbols[d].name == h; } ) ), h } ); + } + out.posts += counted ? 1 : 0; + } + if( out.rows.empty() ) + { + return out; + } + std::ranges::sort( hit ); + hit.erase( std::ranges::unique( hit ).begin(), hit.end() ); + for( const NodeId d : dstDefs ) + { + if( std::ranges::binary_search( hit, ing.symbols[d].name ) && hr.classes().ownerOf( d ) != kNoNode ) + { + ++out.defs; + out.listed.push_back( d ); + } + } + if( out.defs > kHandlerRuleListMax ) + { + out.listed.clear(); + } + std::ranges::stable_sort( out.rows, []( const PathHandlerRow& a, const PathHandlerRow& b ) { return a.cands > b.cands; } ); + const NodeId first = out.rows.front().cls; + for( const HandlerRule& rule : hr.rules() ) + { + if( hr.classes().descendsFrom( first, rule.base ) ) + { + out.rule = &rule; + break; + } + } + return out; +} + +// Owner.name for a definition inside a class, else the name. +inline std::string handlerRuleQualified( const IngestResult& ing, const HandlerRules& hr, NodeId d ) +{ + const NodeId owner = hr.classes().ownerOf( d ); + return owner != kNoNode ? ing.symbols[ owner ].name + "." + ing.symbols[d].name : ing.symbols[d].name; +} + +// The line `n` of `text` (1-based), or empty. +inline std::string_view handlerRuleLine( std::string_view text, std::uint32_t n ) +{ + std::size_t at = 0; + for( std::uint32_t k = 1; k < n && at != std::string_view::npos; ++k ) + { + at = text.find( '\n', at ); + at = at == std::string_view::npos ? at : at + 1; + } + if( at == std::string_view::npos || at >= text.size() ) + { + return {}; + } + return text.substr( at, std::min( text.find( '\n', at ), text.size() ) - at ); +} +// Does `line` pass word `w` as a call argument (`(w` or `, w` before a `,` or `)`)? +inline bool handlerRulePassesWord( std::string_view line, std::string_view w ) +{ + for( std::size_t at = line.find( w ); at != std::string_view::npos && !w.empty(); at = line.find( w, at + 1 ) ) + { + std::size_t b = at; + while( b > 0 && line[ b - 1 ] == ' ' ) + { + --b; + } + std::size_t e = at + w.size(); + while( e < line.size() && line[e] == ' ' ) + { + ++e; + } + if( b > 0 && ( line[ b - 1 ] == '(' || line[ b - 1 ] == ',' ) && e < line.size() && ( line[e] == ',' || line[e] == ')' ) ) + { + return true; + } + } + return false; +} +// The definition a reader passes the name to: a call of a method of the reader's own class (same file) on the line of its +// read, or on a later line that passes the local the read was assigned to (`n = m.attr` … `self.f( n, m )`). +inline NodeId handlerRulePassee( const IngestResult& ing, const HandlerRules& hr, const HandlerReader& reader ) +{ + const Symbol& fn = ing.symbols[ reader.fn ]; + const NodeId owner = hr.classes().ownerOf( reader.fn ); + const std::string_view text( hr.text( fn.fileId ) ); + const std::string_view read = handlerRuleLine( text, reader.line ); + std::string_view local; + if( const std::size_t eq = read.find( '=' ); eq != std::string_view::npos && read.find( "==" ) == std::string_view::npos ) + { + local = read.substr( 0, eq ); + while( !local.empty() && ( local.front() == ' ' || local.front() == '\t' ) ) + { + local.remove_prefix( 1 ); + } + while( !local.empty() && local.back() == ' ' ) + { + local.remove_suffix( 1 ); + } + if( !std::ranges::all_of( local, []( char c ) { return std::isalnum( static_cast( c ) ) || c == '_'; } ) ) + { + local = {}; + } + } + for( const Reference& c : ing.references ) + { + if( c.fromSymbol != reader.fn || c.role != RefRole::Call || c.line < reader.line + || ( c.line != reader.line && !handlerRulePassesWord( handlerRuleLine( text, c.line ), local ) ) ) + { + continue; + } + for( const Symbol& s : ing.symbols ) + { + if( s.name == c.calleeName && s.fileId == fn.fileId && hr.classes().ownerOf( s.id ) == owner && s.id != reader.fn ) + { + return s.id; + } + } + } + return kNoNode; +} + +inline std::string pathHandlerRuleXml( const IngestResult& ing, const NameKeyHops& names, const PathHandlerRule& hrule, bool singleRoot, std::string_view rootPrefix ) +{ + if( !hrule.found() || hrule.rule == nullptr ) + { + return {}; + } + const HandlerRules& hr = names.rules(); + const HandlerRule& rule = *hrule.rule; + std::vector esc; + const auto ex = [ & ]( std::string_view s ) -> std::string { return std::string( escapeXml( s, esc ) ); }; + const auto rel = [ & ]( std::uint32_t fileId ) -> std::string + { + return ex( singleRoot ? sarif::rootRelativeUri( ing.files[ fileId ], rootPrefix ) : std::string_view( ing.files[ fileId ] ) ); + }; + const auto loc = [ & ]( NodeId n ) { return rel( ing.symbols[n].fileId ) + ":" + std::to_string( ing.symbols[n].line ); }; + const std::string spelled = rule.prefix + ( rule.qualified ? "{snake(Owner)_}" : "" ) + "{snake(Class)}" + ( rule.nsKeyword ? " | " + rule.prefix + "{namespace}_{snake(Class)}" : std::string() ); + const std::size_t shown = std::min( hrule.rows.size(), kHandlerRuleShown ); + std::string out = ""; + } + for( std::size_t i = 0; i < shown; ++i ) + { + const PathHandlerRow& row = hrule.rows[i]; + const Reference& site = ing.references[ row.site ]; + out += ""; + } + for( const NodeId d : hrule.listed ) + { + out += ""; + } + return out + ""; +} + +// Present-only: rides exactly when prints. +inline constexpr std::string_view kHandlerRuleLegend = + ": HOW a posted message reaches a to= handler BY NAME, not a call: " + "base='s __init_subclass__ writes attr= (p=) as name= (snake: an underscore between a lower and an upper letter, then lower case; Owner = the class it is " + "nested in), read from this repo's own source; posts= calls of from= whose first argument constructs a subclass of base= that names a to= handler, " + "classes= those classes, handlers= the to= definitions those names match (methods; which one runs depends on the receiver's class). " + ": a function that reads attr= (outside tests), and the method it hands the name to on that line (the lookup side). " + ": one posted class (widest first, shown= of classes=), its handler name, its first post site, cands=K definitions of h=. " + ": every handler definition when handlers= is at most 16; above that next= lists one name's definitions. Matched, never traced; a hedge, never a hop. "; +inline std::string_view pathHandlerRuleLegend( const PathHandlerRule& hrule ) noexcept +{ + return hrule.found() ? kHandlerRuleLegend : std::string_view(); +} + } // namespace rw diff --git a/src/valuerefindex.h b/src/valuerefindex.h index ffdb022fe..cc1f31384 100644 --- a/src/valuerefindex.h +++ b/src/valuerefindex.h @@ -1246,6 +1246,11 @@ class ClassFamilies } return isAncestor( member, site ) || isAncestor( site, member ); } + // Is class `base` an ancestor of class `c` (through the bound written bases)? + bool descendsFrom( NodeId c, NodeId base ) const + { + return isAncestor( c, base ); + } private: struct ClassSpan diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index 82d5c644a..02df5ec67 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2578,6 +2578,8 @@ std::optional runPath( const MainDispatch& d ) // ix2-slice: the sink-side frontier beside the forward gap rows (src/pathgaps.h pathNearSink), reachable="0" only; its name-key // ways in come from the same provider const rw::PathNearSink pthNear = path.empty() ? rw::pathNearSink( ing, g, dstDefs, pthGaps.cone, *pthValueIdx, pthBackHops->names() ) : rw::PathNearSink{}; + // ON-NAME-HANDLERS-V2: a poster from= whose posted message classes name to= handlers (src/pathgaps.h pathHandlerRule) + const rw::PathHandlerRule pthHandler = path.empty() ? rw::pathHandlerRule( ing, pthBackHops->names(), srcDefs, dstDefs ) : rw::PathHandlerRule{}; // TRACE-EP (src/pathpairs.h): a reached answer whose names have several definitions lists EVERY reaching definition pair // (up to kPathPairChains chains, the rest alt=), never one silently; a glob endpoint keeps its any-of-these meaning const rw::PathPairs pthPairs = path.empty() || srcGlob || dstGlob ? rw::PathPairs{} @@ -2620,7 +2622,7 @@ std::optional runPath( const MainDispatch& d ) + rw::memberOwnerLegend( pthHasOwner ) + rw::memberOwnerContainedLegend( pthMarks.ownerContained ) + std::string( rw::pathPairsLegend( pthPairs ) ) + rw::pathOrderLegend( pthPairs, pthTwin ) ).c_str(), ( std::string( rw::toValueRefsLegend( pthToValueRefs.count > 0 ) ) + rw::valueRefsDepthLegend( pthToValueRefs.depthCut.files > 0 ) ).c_str(), ( rw::pathGapsLegend( pthGaps.any(), !pthSinkCallers.empty() ) + rw::pathBridgeLegend( pthBridge ) - + rw::nearSinkLegend( pthNear ) + std::string( rw::pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), + + rw::nearSinkLegend( pthNear ) + std::string( rw::pathHandlerRuleLegend( pthHandler ) ) + 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 ) ( 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 ) ); + rw::emitTo( stdout, "{}>{}{}{}{}", pthUnreached.rootAttrs, pthUnreached.rows, rw::pathBridgeXml( ing, g, pthBridge, pthSingleRoot, pthRootPrefix ), + rw::pathNearSinkXml( ing, pthNear, pthSingleRoot, pthRootPrefix ), + path.empty() ? rw::pathHandlerRuleXml( ing, pthBackHops->names(), pthHandler, pthSingleRoot, pthRootPrefix ) : std::string() ); // FE-B via="name", #409 via="link"/anchor="missing", attrib-multidef prov="split", PATH-OWNERS n="Owner.name": one emitter, shared with MCP path_between rw::emitPathAnswerRows( stdout, ing, g, pthPairs, path, pthOwners, pthSingleRoot, pthRootPrefix ); // TRACE-EP: pairs or today's rows, shared with MCP rw::emitTo( stdout, "" ); diff --git a/test/handlerrulecheck.sh b/test/handlerrulecheck.sh new file mode 100755 index 000000000..26348ace3 --- /dev/null +++ b/test/handlerrulecheck.sh @@ -0,0 +1,236 @@ +#!/usr/bin/env bash +# handlerrulecheck.sh — ON-NAME-HANDLERS-V2 (src/handlerrule.h HandlerRules, read by backhops.h NameKeyHops and pathgaps.h +# pathHandlerRule): a posted message CLASS names its handler methods, by the rule the repo's own Message base writes. +# +# THE DEFECT. A message framework (textual: `self.post_message( Button.Pressed( self ) )` → the pump → `cls.__dict__.get( method_name )` +# with method_name = message.handler_name, set by `Message.__init_subclass__` to f"on_{name}") has no edge from the post site to +# on_button_pressed, and the lookup itself names every on_* def. --path=post_message,on_* ended at on_event, --impact of a +# handler counted nothing, and near_sink called a dispatched handler why="external". +# +# THE CONTRACT. The rule is READ from the source (a `cls.A = f"P{v}"` line in a Python __init_subclass__ that calls a `[a-z][A-Z]` +# + `.lower()` converter on the qualname's last two parts or the class name; a `{namespace}_` branch reads a namespace= class +# keyword). A call to a method of a class that reads `.A` (outside tests) whose FIRST argument constructs a subclass is a post +# site: a name-key hop (hedged, cands= the methods of that name) to the handlers. --path from a poster whose sites name a to= +# handler prints (rule p=, the readers and the method each passes the name to, the posted classes widest first, +# the handler defs listed only when at most 16, else next=). reachable= / hops= / reaches= never move. +# +# ARMS +# (A1) --path=post_message,on_* : with p= at the rule line, base=Message attr=handler_name, the reader +# Pump._on_message passes= Pump._get_dispatch_methods, , and the +# two handler defs as rows (handlers=2 <= 16, no next=); reachable="0". +# (A2) --impact=widgets.py:on_button_pressed: reaches_hedged=1 reaches_hedged_via=name-key (Button.press), reaches="0". +# (A3) a nested class's name is owner-qualified, a namespace= keyword replaces the owner: on_worker_changed is reached hedged. +# (A4) a top-level class posted by name (Tick()) hops to on_tick: the bridge --path=tick,leaf crosses via="name-key" cands="1". +# (N1) a variable argument (msg = Tick(); post_message( msg )) is no post site: --path=poke,leaf has no name-key crossing. +# (N2) a repo whose __init_subclass__ builds the name another way (no converter) has no rule: no , no name-key. +# (N3) a call that is not a poster's (log( Tick() )) is no post site. +# (W1) 17 handler defs: handlers="17", no rows, next= lists one name's defs. +# (L1) the clause rides only with the block (full + compact); every answer parses as XML. +# (M1) MCP path_between carries the same . +# +# Usage: test/handlerrulecheck.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 "handlerrulecheck: 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 + +mkdir -p "$TMP/fw" "$TMP/norule" "$TMP/wide" +cat > "$TMP/fw/case.py" <<'EOF' +import re + + +def camel_to_snake(name, _re=re.compile("[a-z][A-Z]")): + def repl(m): + lower, upper = m.group() + return f"{lower}_{upper.lower()}" + + return _re.sub(repl, name).lower() +EOF +cat > "$TMP/fw/message.py" <<'EOF' +from case import camel_to_snake + + +class Message: + namespace = "" + + def __init_subclass__(cls, namespace=None): + super().__init_subclass__() + if namespace is not None: + name = f"{namespace}_{camel_to_snake(cls.__name__)}" + else: + parts = cls.__qualname__.rsplit(".", 2)[-2:] + name = "_".join(camel_to_snake(p) for p in parts) + cls.handler_name = f"on_{name}" +EOF +cat > "$TMP/fw/pump.py" <<'EOF' +class Pump: + def post_message(self, message): + self._queue.append(message) + return True + + def _on_message(self, message): + handler_name = message.handler_name + for method in self._get_dispatch_methods(handler_name, message): + method(message) + + def _get_dispatch_methods(self, method_name, message): + for cls in self.__class__.__mro__: + method = cls.__dict__.get(method_name) + if method is not None: + yield method.__get__(self, cls) +EOF +cat > "$TMP/fw/widgets.py" <<'EOF' +from message import Message +from pump import Pump + + +def leaf(x): + return x + + +class Tick(Message): + pass + + +class Button(Pump): + class Pressed(Message): + pass + + def press(self): + self.post_message(self.Pressed()) + + +class Worker(Pump): + class Changed(Message, namespace="worker"): + pass + + def change(self): + self.post_message(Worker.Changed()) + + +class Form(Pump): + def on_button_pressed(self, m): + return leaf(m) + + def on_worker_changed(self, m): + return m + + def on_tick(self, m): + return leaf(m) + + +def log(m): + return m + + +class Clock(Pump): + def tick(self): + self.post_message(Tick()) + + def poke(self): + msg = Tick() + self.post_message(msg) + + def note(self): + log(Tick()) +EOF +cat > "$TMP/fw/other.py" <<'EOF' +class Panel: + def on_button_pressed(self, m): + return m +EOF +cp "$TMP/fw/case.py" "$TMP/fw/pump.py" "$TMP/fw/widgets.py" "$TMP/norule/" +cat > "$TMP/norule/message.py" <<'EOF' +class Message: + def __init_subclass__(cls): + super().__init_subclass__() + name = cls.__name__.lower() + cls.handler_name = f"on_{name}" +EOF +cp "$TMP/fw/case.py" "$TMP/fw/message.py" "$TMP/fw/pump.py" "$TMP/wide/" +{ + printf 'from message import Message\nfrom pump import Pump\n\n\nclass Wide(Message):\n pass\n\n\nclass Src(Pump):\n def send(self):\n self.post_message(Wide())\n' + for i in $( seq 1 17 ); do printf '\n\nclass H%s:\n def on_wide(self, m):\n return m\n' "$i"; done +} > "$TMP/wide/w.py" + +python3 - "$BIN" "$TMP" <<'PY' +import json, re, subprocess, sys +binp, tmp = sys.argv[1], sys.argv[2] +fail = 0 +def ok( m ): print( " PASS " + m ) +def no( m ): + global fail + fail = 1 + print( " FAIL " + m ) +def check( c, m ): ( ok if c else no )( m ) +def run( d, *args ): + p = subprocess.run( [ binp, d, "--no-cache", *args ], cwd = d, capture_output = True, text = True, timeout = 120 ) + return p.stdout if p.returncode == 0 else None +def xml_ok( t ): + if t is None: + return False + q = subprocess.run( [ "xmllint", "--noout", "-" ], input = t, capture_output = True, text = True ) + return q.returncode == 0 +def root( t, tag ): + m = re.search( "<" + tag + r"\b[^>]*>", t or "" ) + return m.group( 0 ) if m else "" +def attr( el, a ): + m = re.search( r'\s' + a + r'="([^"]*)"', el or "" ) + return m.group( 1 ) if m else None +fw, nr, wd = f"{tmp}/fw", f"{tmp}/norule", f"{tmp}/wide" + +t = run( fw, "--path=post_message,on_*" ) +hr = root( t, "handler_rule" ) +check( xml_ok( t ), "A1 answer parses as XML" ) +check( attr( root( t, "path" ), "reachable" ) == "0", "A1 reachable stays 0" ) +check( attr( hr, "p" ) == "message.py:14" and attr( hr, "base" ) == "Message" and attr( hr, "attr" ) == "handler_name", f"A1 the rule is read from message.py:14 ({hr[:200]})" ) +check( '' in ( t or "" ), "A1 the reader passes the name to the MRO lookup" ) +check( re.search( r'', t or "" ) is not None, "A1 Button.Pressed -> on_button_pressed, cands=2" ) +check( '", t or "" ) +check( br is not None and '' in br.group( 0 ), f"A4 bridge tick -> on_tick via=name-key cands=1 ({br.group( 0 )[:240] if br else t and t[-300:]})" ) +check( attr( root( t, "path" ), "reachable" ) == "0" and attr( root( t, "path" ), "hops" ) == "0", "A4 reachable/hops stay 0" ) +t = run( fw, "--path=poke,leaf" ) +check( t is not None and 'in="poke"' not in t and ", no clause" ) +t = run( nr, "--impact=widgets.py:on_button_pressed" ) +check( t is not None and attr( root( t, "impact" ), "reaches_hedged_via" ) is None, "N2 no rule: no name-key hop" ) +t = run( wd, "--path=post_message,on_*" ) +hr = root( t, "handler_rule" ) +check( attr( hr, "handlers" ) == "17" and "" ) +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/regression.sh b/test/regression.sh index fb40292ad..bf671e186 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 composedhopscheck 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 epmanifestcheck 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 fnslotcheck forautobodycheck forblowupcheck forbodyfoldcheck forbodyslotcheck forbudgetmonotoncheck forcalibfactscheck forcallerhopcheck forcompresscheck forctorhopcheck fordisclosurecheck forhdrshapecheck forheadrrcheck forinitslotscheck forlenscheck formatgatecheck formaxtokenscheck formembernamescheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forroutecheck 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 jsdefnamingcheck 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 mapstemcheck 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 namekeyhopcheck 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 pathpaircheck 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 pyaliascallcheck 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 queuehopcheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck receiverfixescheck 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 testonlydeadcheck 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 writtenspellingcheck 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 composedhopscheck 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 epmanifestcheck 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 fnslotcheck forautobodycheck forblowupcheck forbodyfoldcheck forbodyslotcheck forbudgetmonotoncheck forcalibfactscheck forcallerhopcheck forcompresscheck forctorhopcheck fordisclosurecheck forhdrshapecheck forheadrrcheck forinitslotscheck forlenscheck formatgatecheck formaxtokenscheck formembernamescheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forroutecheck 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 handlerrulecheck 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 jsdefnamingcheck 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 mapstemcheck 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 namekeyhopcheck 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 pathpaircheck 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 pyaliascallcheck 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 queuehopcheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck receiverfixescheck 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 testonlydeadcheck 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 writtenspellingcheck 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)" From 73f1c56dbbf47a5149e55f77e3266b4c3bc5c7fb Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:50:59 -0400 Subject: [PATCH 19/95] selectors: Owner.#name (a JS/TS private member) resolves as the rows print it A '#' that starts a spelling segment is part of the name, not Ruby's Class#method separator: --callees=Hono.#dispatch and --path=Hono.#dispatch,compose bound nothing on hono (readout-40 G). writtenspellingcheck (1b): 3 bind arms red on 1318069a, 5 controls. Co-Authored-By: Claude Opus 5.5 --- changelog.d/private-member-selectors.md | 7 +++++++ src/graph.h | 13 +++++++++---- test/writtenspellingcheck.sh | 10 ++++++++++ .../ts/src/driver/runtime-driver.ts | 2 ++ test/writtenspellingfix/ts/src/other.ts | 2 ++ 5 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 changelog.d/private-member-selectors.md diff --git a/changelog.d/private-member-selectors.md b/changelog.d/private-member-selectors.md new file mode 100644 index 000000000..cf88025c9 --- /dev/null +++ b/changelog.d/private-member-selectors.md @@ -0,0 +1,7 @@ +### Fixed — a JS/TS private member resolves as the rows print it: `Hono.#dispatch` + +Train 40 started printing owner-qualified names (`n="Hono.#dispatch"`) on `--path`, `--callers` and `--callees` rows, but pasting +one back failed: `--callees=Hono.#dispatch` answered "not found" and `--path=Hono.#dispatch,compose` exited 1 on hono, because the +spelling splitter read the `#` as Ruby's `Class#method` separator and saw an empty segment. A `#` that starts a segment is now part +of the name, so `Owner.#name` and `file:Owner.#name` bind the one private member they name; Ruby `Cart#total` reads as before, and +`Owner.name` without the `#` still names nothing. Found by the readout-40 grading pass. Gate: `test/writtenspellingcheck.sh` (1b). diff --git a/src/graph.h b/src/graph.h index 1c06a7b9a..c8a696601 100644 --- a/src/graph.h +++ b/src/graph.h @@ -13273,9 +13273,12 @@ inline std::vector resolveAllByDottedScope( const IngestResult& ing, std return found; } std::string scoped; - for( const char c : spec ) + for( std::size_t i = 0; i < spec.size(); ++i ) { - scoped += ( c == '.' || c == '#' ) ? std::string_view( "::" ) : std::string_view( &c, 1 ); + const char c = spec[i]; + // a '#' that starts a segment is a JS/TS private name's own first character (splitSpellingSegments' rule) + const bool sep = c == '.' || ( c == '#' && i > 0 && spec[ i - 1 ] != '.' && spec[ i - 1 ] != '#' ); + scoped += sep ? std::string_view( "::" ) : std::string_view( &c, 1 ); } return resolveAllByScopeQualified( ing, scoped ); } @@ -13331,7 +13334,9 @@ struct WrittenSpec std::string_view leaf; }; -// segments of `text` split on "::", '.' and '#'; false when any segment is empty +// segments of `text` split on "::", '.' and '#'; false when any segment is empty. A '#' that STARTS a segment is part of the +// name, not a separator: a JS/TS private member (`Hono.#dispatch`, the n= the --path/--callers rows print) — Ruby's +// `Class#method` separator always follows a non-empty owner segment, so the two never collide (lane D2, readout-40 G). inline bool splitSpellingSegments( std::string_view text, std::vector& out ) { std::size_t start = 0; @@ -13339,7 +13344,7 @@ inline bool splitSpellingSegments( std::string_view text, std::vector { return tsBeginHelper(x) } commitTransaction(x: number): number { return tsCommitHelper(x) } + #dispatch(x: number): number { return tsPrivHelper(x) } } export const AliasDriver = RuntimeDriver function tsBeginHelper(x: number): number { return x } function tsCommitHelper(x: number): number { return x } +function tsPrivHelper(x: number): number { return x } diff --git a/test/writtenspellingfix/ts/src/other.ts b/test/writtenspellingfix/ts/src/other.ts index caf6059d8..caa2a82da 100644 --- a/test/writtenspellingfix/ts/src/other.ts +++ b/test/writtenspellingfix/ts/src/other.ts @@ -1,4 +1,6 @@ export class Other { beginTransaction(): number { return tsOtherHelper() } + #dispatch(): number { return tsOtherPrivHelper() } } function tsOtherHelper(): number { return 1 } +function tsOtherPrivHelper(): number { return 2 } From d20ac460eaf22dc380668e82d48310bfebf4d0bd Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:10:11 -0400 Subject: [PATCH 20/95] defs: --defs=NAME[,NAME...] on one shared lookup (src/defslookup.h rw::lookupDefs) The definition groups each name binds (defattrib.h's group rule), with the spelling that picks each alone (sel=, proven by re-resolving it), class=, lang=, in= callers, data_defs= for JSON/YAML keys and doc headings. XML, JSON and the compact dialect; shared not-found refusal for one name. Co-Authored-By: Claude Opus 5.5 --- changelog.d/defs-lookup-verb.md | 11 ++ src/cli.h | 6 +- src/compactlegend.h | 7 + src/defslookup.h | 307 ++++++++++++++++++++++++++++++++ src/main.cpp | 11 +- src/verbs_navigate.h | 166 +++++++++++++++++ 6 files changed, 505 insertions(+), 3 deletions(-) create mode 100644 changelog.d/defs-lookup-verb.md create mode 100644 src/defslookup.h diff --git a/changelog.d/defs-lookup-verb.md b/changelog.d/defs-lookup-verb.md new file mode 100644 index 000000000..f2fc8a9cb --- /dev/null +++ b/changelog.d/defs-lookup-verb.md @@ -0,0 +1,11 @@ +### Added — `--defs=NAME[,NAME…]`: which definitions a name binds, with the spelling that picks each one alone + +One call answers "which definitions does this name bind?" for up to 16 names in one index load. Each definition group (a C-family +prototype with the definition it declares is one; a TypeScript overload with its implementation is one) is a row with its +owner-qualified name, kind, `file:line`, language, `class=source|test` and its distinct graph callers (a floor), plus `sel=`: a +spelling every symbol verb accepts that the lookup re-resolved and proved binds that definition and nothing else (`name`, +`file:name`, `file:Owner.name`, then the `@file:line` seed; `sel_proven="0"` when none does). Source rows come first, then by +callers. A JSON/YAML key or a markdown heading of the same name is counted as `data_defs=` and never listed as code; a definition +the parser does not model (an assignment, `define_method`, `setattr`) is not one. More than 16 rows page with `--offset`; a single +name that binds nothing answers with the shared "not found" refusal. `--json` gives the same rows. The lookup lives in one header +(`src/defslookup.h`, `rw::lookupDefs`), reusing the definition-group rule the callers/impact tables use. diff --git a/src/cli.h b/src/cli.h index 87904bd8a..cdf574163 100644 --- a/src/cli.h +++ b/src/cli.h @@ -276,6 +276,7 @@ struct Config bool seams = false; // --seams: cross-module bridges no test reaches (untested integration seams) bool mermaid = false; // --mermaid: module (directory) dependency graph as a Mermaid diagram (human-viewable) std::string_view pathSpec; // --path=SRC,DST: shortest directed call-path between two symbols + std::string_view defsSpec; // --defs=NAME[,NAME…]: the definitions each name binds (defslookup.h) std::string_view connectSpec; // --connect=A,B,C: minimal connecting subgraph over 2..16 symbols int connectRadius = 6; // --connect-radius=N: undirected BFS bound (core clamps to 1..12) std::string_view impactSym; // --impact=SYM (or file:name): transitive blast radius (everything that reaches SYM) @@ -1195,6 +1196,8 @@ inline constexpr char kHelpHead[] = " raises it) and the sh BUILTINS (echo printf cd exit test …) are dropped — builtins_excluded=\n" " counts them; --include-builtins keeps them\n" " --path=SRC,DST shortest directed call-path SRC -> DST\n" + " --defs=NAME[,NAME...] list the definitions each name binds (up to 16 names), with the spelling that picks each alone\n" + " per definition: file:line, kind, class=source|test, callers, and sel= — paste it into any SYM verb\n" " --connect=A,B,C show how 2..16 symbols relate: the smallest subgraph that joins them\n" " minimal connecting subgraph over 2..16 symbols: terminals + fewest joining intermediaries +\n" " call edges in TRUE direction (finds the shared-caller join a directed --path can't) [--connect-radius=N (1..12, default 6)]\n" @@ -3338,6 +3341,7 @@ inline constexpr ViewFlag kViewFlags[] = // architecture + graph paths { "--arch=", &Config::archRules , EmptyValue::Refuse, "a layering-rules file path", "--arch=arch_rules.txt" }, { "--path=", &Config::pathSpec , EmptyValue::Refuse, "two symbol names, FROM,TO", "--path=main,rankGraph" }, + { "--defs=", &Config::defsSpec , EmptyValue::Refuse, "up to 16 symbol names, A[,B…]", "--defs=parseArgs,rankGraph" }, { "--connect=", &Config::connectSpec , EmptyValue::Refuse, "two or more symbol names, A,B[,C]", "--connect=parseArgs,serialize,rankGraph" }, { "--impact=", &Config::impactSym , EmptyValue::Refuse, "a symbol name", "--impact=parseArgs" }, { "--mentions=", &Config::mentionsSym , EmptyValue::Refuse, "a symbol name", "--mentions=parseArgs" }, @@ -3643,7 +3647,7 @@ inline constexpr IntFlag kIntFlags[] = // nowhere to keep) // • a bare no-op / bare pair --route, --quality-ack (the =REASON form is a kViewFlags row) inline constexpr std::size_t kHandWrittenFlagArms = 26; // +2 lane/lean-answers-068 (2026-10-07): --whereis-listing= and --mcp-legend= (closed-value arms, same shape as --grep-in=); +1 #350 (2026-09-28): --max-memory= (byte-size arm, same shape as --max-file-size=); +1: --color-by= (enum-value arm); +3 G3 (2026-08-15 harvest): --and=/--not=/--grep-scope= (repeatable-value arms, same shape as --exclude=); +1 R-H: --grep-in= (closed-value arm, same shape as --grep-scope=); +1 lane/flag-biggest-first (2026-09-23): --readability (deprecation-warning alias, same shape as --stable/--most-important-last/--no-auto-order) — its kBoolFlags row is renamed to --biggest-first (same Config::readability member), so the old spelling moves OUT of the table and becomes a hand-written arm, per the house rule that deprecation-warning aliases stay hand-written -inline constexpr std::size_t kTotalFlagArms = 222; // +1 lane/qd-memory-degrade (2026-10-14): --quality-kinds= (kViewFlags row) — run only the named quality kinds of the range form (the replay a degraded answer names); +1 lane/whereis-lean (2026-10-10, review C1): --whereis-full-text (kBoolFlags row) — the same --whereis page with every t= line whole, the replay; +1 lane/lean-trio (2026-10-10): --show-stale (kBoolFlags row) — prints the stale-ack rows a regression-free --quality-delta collapses; +1 lane/map-inventory-d7 (2026-10-08): --no-inventory (kBoolFlags row) — the default map without its tier; +1 knob-honesty-068 (2026-10-07): --lint-max-per-rule= (kIntFlags row) — the per-rule lint budget a floored answer's findings_next= names; +2 lane/lean-answers-068 (2026-10-07): --whereis-listing= and --mcp-legend= (hand-written closed-value arms, see kHandWrittenFlagArms); +1 lane/mcp-tool-profile-065 (2026-09-26): --mcp-tools= (kViewFlags row) — list only a subset of the MCP tools (names and/or the core/full profiles); +1 #350 (2026-09-28): --max-memory= (hand-written byte-size arm, see kHandWrittenFlagArms); +1 lane/flag-biggest-first (2026-09-23): --readability renamed to --biggest-first, kept working as a hand-written deprecated alias (see kHandWrittenFlagArms); +1 lane/r1-for-sections-stub (2026-09-19, L2/B1): --sections= (kViewFlags row) — the closed-set opt-in that restores the / sections --for collapses to a counted stub by default; +1 --lsp (kBoolFlags row, 2026-09-15): the navigation LSP server stdio entry point — Phase 1 PoC, docs/LSP.md; +1 lane/recent-scope (2026-09-12, C1-b): --in= (kViewFlags row) — the directory-scoped block of --rank-by=churn-decay; +2 P4 (capture-audit 2026-09-04, lane L7): --zoom-levels= (kIntFlags row, the printed-levels ceiling) and --include-builtins (kBoolFlags row, the external-surface builtin opt-in); +1 P9 (capture-audit 2026-09-04, lane L8): --no-post-check (kBoolFlags row, the edit receipt's folded verification opt-out); +1 lane/ca-L2 (2026-09-04, H11): --allow-dirty (kBoolFlags row) — the explicit consent --quality-baseline needs before it pins a floor on a tree that differs from HEAD; +1 lane/n6-c (2026-09-03): --no-ignore (kBoolFlags row, the .gitignore-by-default escape hatch); +1 lane/af-scope (2026-08-29): --scope= (kViewFlags row, the quality-delta ownership partition); +1 --quality-delta= (kViewFlags, R-I ref-pair form); +1 --help-task= (kViewFlags); +2 VT-1: --run-trace= (kViewFlags) and --run-timeout= (kIntFlags); +1: --handoff (kBoolFlags row); +1 --readability (kBoolFlags row); +2 §CLIO: --cochange-groups (kBoolFlags), --cochange-recur= (kIntFlags); +1 --context-ratio (kBoolFlags row); +1 --nonlocal-state (kBoolFlags row); +2 --field-affinity (kBoolFlags) and --field-affinity= (kViewFlags); +1 --comment-coherence (kBoolFlags row); +2 --dmm (kBoolFlags) and --dmm= (kViewFlags); +2 --quality-panel (kBoolFlags) and --quality-panel= (kViewFlags); +1 --naming-consistency (kBoolFlags row); +1 --naming-locals (kBoolFlags row, local-variable-indexing plan Phase 2); +1 --skipped (kBoolFlags row, §P0.5d itemization); +1 --with-profile= (kViewFlags row, the --lint × #PROF_TSV heat join); +1 --color-by= (hand-written enum-value arm); +1 --sarif (kBoolFlags row, W1-SARIF: SARIF 2.1.0 export for --lint); +1 --signatures-only (kBoolFlags row, T3 terminal-by-default --for opt-out); +3 L7: --lint-catalog (kBoolFlags), --lint-select= and --lint-ignore= (kViewFlags); +3 G3 (2026-08-15 harvest): --and=/--not=/--grep-scope= (hand-written arms); +1 R-H: --grep-in= (hand-written arm); +1 R2: --pattern= (kViewFlags row, the code-shaped structural search); +1 lane/safe-delete (2026-08-21): --safe-delete= (kViewFlags row, the composed "can I delete this?" read); +1 lane/compact-conceptual (2026-08-22): --auto-bodies (kBoolFlags row, the compact-conceptual-serving opt-out); +5 CLI edit bridge (2026-08-27): --replace-symbol-body=/--insert-before-symbol=/--insert-after-symbol=/--edit-payload=/--edit-target-file= (kViewFlags rows); +1 --handles (kBoolFlags row, grep edit handles); +1 --legend= (kViewFlags row, compact schema dialect); +3 edit-plan: --edit-plan= (kViewFlags) and --dry-run/--apply (kBoolFlags rows); +1 --agent= (kViewFlags row, the --doctor Codex surface); +1 lane/paper-slice (2026-08-28): --slice= (kViewFlags row, the ARISE-motivated def-use slice); +1 lane/af-planlint (2026-08-29): --plan-lint= (kViewFlags row, the PLAN-format structure gate, P3.2); +2 lane/or-arise (2026-08-30): --slice-flow= (kViewFlags row) and --slice-depth= (kIntFlags row) — the ARISE rung-2 cross-statement data-flow slice; +1 lane/at-seed (2026-08-30): --at= (kViewFlags row) — the FILE:LINE enclosing-chain report, with the @FILE:LINE selector spelling resolved in graph.h (no flag arm of its own); +1 CARD-1 phase 2 (2026-08-31): --pin-census= (kViewFlags row) — the eval-only S6-C silent-pin census, written beside the map and never into it +inline constexpr std::size_t kTotalFlagArms = 223; // +1 lane/defs-lookup (2026-10-27): --defs= (kViewFlags row) — the definitions each name binds (defslookup.h); +1 lane/qd-memory-degrade (2026-10-14): --quality-kinds= (kViewFlags row) — run only the named quality kinds of the range form (the replay a degraded answer names); +1 lane/whereis-lean (2026-10-10, review C1): --whereis-full-text (kBoolFlags row) — the same --whereis page with every t= line whole, the replay; +1 lane/lean-trio (2026-10-10): --show-stale (kBoolFlags row) — prints the stale-ack rows a regression-free --quality-delta collapses; +1 lane/map-inventory-d7 (2026-10-08): --no-inventory (kBoolFlags row) — the default map without its tier; +1 knob-honesty-068 (2026-10-07): --lint-max-per-rule= (kIntFlags row) — the per-rule lint budget a floored answer's findings_next= names; +2 lane/lean-answers-068 (2026-10-07): --whereis-listing= and --mcp-legend= (hand-written closed-value arms, see kHandWrittenFlagArms); +1 lane/mcp-tool-profile-065 (2026-09-26): --mcp-tools= (kViewFlags row) — list only a subset of the MCP tools (names and/or the core/full profiles); +1 #350 (2026-09-28): --max-memory= (hand-written byte-size arm, see kHandWrittenFlagArms); +1 lane/flag-biggest-first (2026-09-23): --readability renamed to --biggest-first, kept working as a hand-written deprecated alias (see kHandWrittenFlagArms); +1 lane/r1-for-sections-stub (2026-09-19, L2/B1): --sections= (kViewFlags row) — the closed-set opt-in that restores the / sections --for collapses to a counted stub by default; +1 --lsp (kBoolFlags row, 2026-09-15): the navigation LSP server stdio entry point — Phase 1 PoC, docs/LSP.md; +1 lane/recent-scope (2026-09-12, C1-b): --in= (kViewFlags row) — the directory-scoped block of --rank-by=churn-decay; +2 P4 (capture-audit 2026-09-04, lane L7): --zoom-levels= (kIntFlags row, the printed-levels ceiling) and --include-builtins (kBoolFlags row, the external-surface builtin opt-in); +1 P9 (capture-audit 2026-09-04, lane L8): --no-post-check (kBoolFlags row, the edit receipt's folded verification opt-out); +1 lane/ca-L2 (2026-09-04, H11): --allow-dirty (kBoolFlags row) — the explicit consent --quality-baseline needs before it pins a floor on a tree that differs from HEAD; +1 lane/n6-c (2026-09-03): --no-ignore (kBoolFlags row, the .gitignore-by-default escape hatch); +1 lane/af-scope (2026-08-29): --scope= (kViewFlags row, the quality-delta ownership partition); +1 --quality-delta= (kViewFlags, R-I ref-pair form); +1 --help-task= (kViewFlags); +2 VT-1: --run-trace= (kViewFlags) and --run-timeout= (kIntFlags); +1: --handoff (kBoolFlags row); +1 --readability (kBoolFlags row); +2 §CLIO: --cochange-groups (kBoolFlags), --cochange-recur= (kIntFlags); +1 --context-ratio (kBoolFlags row); +1 --nonlocal-state (kBoolFlags row); +2 --field-affinity (kBoolFlags) and --field-affinity= (kViewFlags); +1 --comment-coherence (kBoolFlags row); +2 --dmm (kBoolFlags) and --dmm= (kViewFlags); +2 --quality-panel (kBoolFlags) and --quality-panel= (kViewFlags); +1 --naming-consistency (kBoolFlags row); +1 --naming-locals (kBoolFlags row, local-variable-indexing plan Phase 2); +1 --skipped (kBoolFlags row, §P0.5d itemization); +1 --with-profile= (kViewFlags row, the --lint × #PROF_TSV heat join); +1 --color-by= (hand-written enum-value arm); +1 --sarif (kBoolFlags row, W1-SARIF: SARIF 2.1.0 export for --lint); +1 --signatures-only (kBoolFlags row, T3 terminal-by-default --for opt-out); +3 L7: --lint-catalog (kBoolFlags), --lint-select= and --lint-ignore= (kViewFlags); +3 G3 (2026-08-15 harvest): --and=/--not=/--grep-scope= (hand-written arms); +1 R-H: --grep-in= (hand-written arm); +1 R2: --pattern= (kViewFlags row, the code-shaped structural search); +1 lane/safe-delete (2026-08-21): --safe-delete= (kViewFlags row, the composed "can I delete this?" read); +1 lane/compact-conceptual (2026-08-22): --auto-bodies (kBoolFlags row, the compact-conceptual-serving opt-out); +5 CLI edit bridge (2026-08-27): --replace-symbol-body=/--insert-before-symbol=/--insert-after-symbol=/--edit-payload=/--edit-target-file= (kViewFlags rows); +1 --handles (kBoolFlags row, grep edit handles); +1 --legend= (kViewFlags row, compact schema dialect); +3 edit-plan: --edit-plan= (kViewFlags) and --dry-run/--apply (kBoolFlags rows); +1 --agent= (kViewFlags row, the --doctor Codex surface); +1 lane/paper-slice (2026-08-28): --slice= (kViewFlags row, the ARISE-motivated def-use slice); +1 lane/af-planlint (2026-08-29): --plan-lint= (kViewFlags row, the PLAN-format structure gate, P3.2); +2 lane/or-arise (2026-08-30): --slice-flow= (kViewFlags row) and --slice-depth= (kIntFlags row) — the ARISE rung-2 cross-statement data-flow slice; +1 lane/at-seed (2026-08-30): --at= (kViewFlags row) — the FILE:LINE enclosing-chain report, with the @FILE:LINE selector spelling resolved in graph.h (no flag arm of its own); +1 CARD-1 phase 2 (2026-08-31): --pin-census= (kViewFlags row) — the eval-only S6-C silent-pin census, written beside the map and never into it static_assert( std::size( kBoolFlags ) + std::size( kViewFlags ) + std::size( kIntFlags ) + kHandWrittenFlagArms == kTotalFlagArms, "a --flag arm was added or removed without updating the ledger above — count the arms in parseArgs and fix the counter" ); diff --git a/src/compactlegend.h b/src/compactlegend.h index 0485c85e5..e8025eb4f 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -103,6 +103,7 @@ inline constexpr CompactLegendSpec kCompactLegendSpecs[] = // symbol (a seed), so a test in the reach set lands in radius_untested=; importers= is impactImportTier's one-hop set of // files whose include/import resolves to a def's file. shown_importers= is a present-only term: the columnar form omits it. { "impact", "impact", "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" }, + { "defs", "defs", "per the parsed definitions the name binds (defs= groups; a prototype with its definition is one): , sel= binds that one alone in any SYM verb, in= distinct callers (a floor); source first, then in=, then path" }, { "path", "path", "one DIRECTED call path from= to to=, each a hop; reachable=0 hops=0 when none" }, { "connect", "connect", "minimal joining subgraph: groups, terminals, joins, edges, " }, { "at", "at", "enclosing-definition chain at p=:l=: sym= innermost, chain= outermost-first, spans" }, @@ -830,6 +831,12 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "decided", "decided=N: groups whose leading style cleared both the sample and agreement floors", false, "naming-consistency", MapHeaderRead::No, {}, "naming-consistency" }, { "flagged", "flagged=N: off-convention names in decided groups (the f rows)", false, "naming-consistency", MapHeaderRead::No, {}, "naming-consistency" }, // path: src/verbs_navigate.h (the root emit) + { "sel_proven", "d sel_proven=0: no spelling tried binds it alone; sel= names its file and owner and may bind more", true, "d", MapHeaderRead::No, {}, "defs" }, + { "decl_only", "d decl_only=1: no body: an interface or trait contract, or a declaration no listed definition ties to", true, "d", MapHeaderRead::No, {}, "defs" }, + { "data_defs", "name data_defs=K: data keys or doc headings of that name (JSON/YAML key, markdown heading), not code, not in defs=", true, "name", MapHeaderRead::No, {}, "defs" }, + { "unproven_defs", "name unproven_defs=K: same-named definitions a file:name spelling could not tie to that file; not in defs=", true, "name", MapHeaderRead::No, {}, "defs" }, + { "shown", "name shown=K offset=M: rows M..M+K of defs=; next= pages the rest", true, "name", MapHeaderRead::No, {}, "defs" }, // also defines offset= + { "next", "name next=: the next page, or (defs=0) the single-name call whose refusal names near names", true, "name", MapHeaderRead::No, {}, "defs" }, { "from_p", "from_p=/to_p=: the definitions from= and to= were bound to", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_p= { "from_defs", "from_defs=/to_defs=: definitions of each name, all searched; above 1, qualify file:name", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_defs= // TRACE-EP (src/pathpairs.h kPathPairsLegend), present only when several definition pairs reach: chains and rows diff --git a/src/defslookup.h b/src/defslookup.h new file mode 100644 index 000000000..2c862d692 --- /dev/null +++ b/src/defslookup.h @@ -0,0 +1,307 @@ +#pragma once +// defslookup.h — THE one definition lookup every verb shares (lane D2 DEFS-LOOKUP, PLAN_DEFS_LOOKUP 2026-10-27). +// +// "Which definitions does this name bind?" had three partial answers: the resolver's count (from_defs=7 on --path), the shared +// refusal's file list (selectorrefuse.h, failure only) and MCP find_symbol's def table. lookupDefs answers it once: the shared +// resolver (resolveAllByNameQualified — every spelling a SYM verb accepts), folded into definition groups by defattrib.h's ONE +// grouping rule (a C-family prototype joins the definition it declares; a TS overload joins its implementation), each group +// with the spelling that picks it ALONE (sel=, proven by re-resolving it), its class (the --path test-twin rule) and its +// in-graph caller count (a floor). Consumers: the --defs verb (verbs_navigate.h runDefs) and --path's inline alternatives +// (defAltsXml), which print the other definitions of an endpoint whose pick is not provably unique. +// +// WHAT IT DOES NOT MEAN (checklist 3): a definition the parser does not model (an assignment `x.f = …`, define_method, +// setattr) is no row and is not counted — defs= counts parsed definitions; a JSON/YAML key is never one. in= counts distinct +// graph callers, a floor (declined and unbound calls are not edges). sel_proven="0" says no spelling this lookup tried picks +// that group alone; the sel= it prints then names the group's file and owner and may bind more than it. + +#include "defattrib.h" // DefGroups, defGroupsOf — the ONE definition-group rule +#include "filter.h" // isTestSymbol, pathTierOf (via pathDefInTests) +#include "graph.h" // resolveAllByNameQualified — the shared resolver +#include "mapinventory.h" // isInventoryCodeSymbol — a data Section (doc heading, JSON/YAML key) is no code definition +#include "memberowner.h" // MemberOwners, qualifiedMemberName — n= as every row spells it +#include "model.h" +#include "nextverb.h" // nextFlag / nextAttrXml +#include "pathpairs.h" // pathDefInTests — the one test-path rule --path's twins use +#include "serialize.h" // escapeXml / jsonStr / symTag + +#include +#include +#include +#include +#include +#include + +namespace rw +{ + +// The house cap for definition groups (defattrib.h kDefTableCap): a page of rows, never a cut — past it the answer pages (next=). +inline constexpr std::size_t kDefRowsCap = kDefTableCap; +// --defs takes at most this many names in one call (one index load). +inline constexpr std::size_t kDefsNamesMax = 16; + +struct DefRow +{ + std::uint32_t group = 0; // the DefGroups index + NodeId rep = kNoNode; // the group's bodied member (else its first match) + std::string sel; // the spelling that picks this group alone (when selProven) + bool selProven = false; // re-resolving sel binds this group's members and nothing else + bool test = false; // pathDefInTests: a test/bench file or a test symbol + std::size_t callers = 0; // distinct in-graph callers over the group's members (a floor) +}; + +struct DefLookup +{ + std::string_view spec; + std::vector matches; // the resolver's answer, in its order + DefGroups groups; + std::vector rows; // one per group: source before test, then callers (desc), then path + std::size_t unprovenDefs = 0; // the resolver's decl→def residue (graph.h H1), as --path counts it + std::size_t dataDefs = 0; // matches that are data Sections (a doc heading, a JSON/YAML key): not rows, counted + + std::size_t defs() const noexcept { return rows.size(); } +}; + +namespace defslookup_detail +{ +// distinct non-prose callers of `n` in the graph's in-edges (markdown links excluded: a doc naming it is no caller) +inline void collectCallers( const IngestResult& ing, const Graph& g, NodeId n, std::vector& into ) +{ + if( n >= g.inEdges.rows() ) + { + return; + } + const auto* ro = g.inEdges.rowOffsets(); + const auto* ci = g.inEdges.colIndices(); + for( auto k = ro[n]; k < ro[ n + 1 ]; ++k ) + { + const NodeId c = NodeId( ci[k] ); + if( c < ing.symbols.size() && pathTierOf( rootRelPath( ing, ing.symbols[c].fileId ) ) != PathTier::Doc ) + { + into.push_back( c ); + } + } +} +// does `spell` resolve to members of group `k` and nothing else? +inline bool picksAlone( const IngestResult& ing, std::string_view spell, const std::vector& members ) +{ + const std::vector got = resolveAllByNameQualified( ing, spell ); + return !got.empty() && std::all_of( got.begin(), got.end(), [ & ]( NodeId n ) { return std::find( members.begin(), members.end(), n ) != members.end(); } ); +} +} // namespace defslookup_detail + +// THE lookup. `g` supplies the caller counts. +inline DefLookup lookupDefs( const IngestResult& ing, const Graph& g, std::string_view spec ) +{ + DefLookup out; + out.spec = spec; + out.matches = resolveAllByNameQualified( ing, spec, &out.unprovenDefs ); + const auto data = std::remove_if( out.matches.begin(), out.matches.end(), [ & ]( NodeId n ) { return !isInventoryCodeSymbol( ing.symbols[n] ); } ); + out.dataDefs = std::size_t( out.matches.end() - data ); + out.matches.erase( data, out.matches.end() ); + if( out.matches.empty() ) + { + return out; + } + out.groups = defGroupsOf( ing, out.matches ); + const MemberOwners owners( ing ); + std::vector> members( out.groups.size() ); + for( std::size_t i = 0; i < out.matches.size(); ++i ) + { + members[ out.groups.ofMatch[i] ].push_back( out.matches[i] ); + } + out.rows.resize( out.groups.size() ); + std::vector callers; + for( std::uint32_t k = 0; k < out.groups.size(); ++k ) + { + DefRow& r = out.rows[k]; + r.group = k; + r.rep = out.groups.rep[k]; + r.test = pathDefInTests( ing, r.rep ); + callers.clear(); + for( const NodeId m : members[k] ) + { + defslookup_detail::collectCallers( ing, g, m, callers ); + } + std::sort( callers.begin(), callers.end() ); + callers.erase( std::unique( callers.begin(), callers.end() ), callers.end() ); + // a member calling itself (recursion) is no outside caller + r.callers = std::size_t( std::count_if( callers.begin(), callers.end(), [ & ]( NodeId c ) + { return std::find( members[k].begin(), members[k].end(), c ) == members[k].end(); } ) ); + // sel=: the shortest spelling the resolver binds to this group alone — the name as given (one group), file:name, + // file:Owner.name, then the @file:line seed (one definition starting on that line) + const Symbol& s = ing.symbols[ r.rep ]; + const std::string file( rootRelPath( ing, s.fileId ) ); + const std::string owned = qualifiedMemberName( owners, s ); + std::vector tries; + if( out.groups.size() == 1 ) + { + tries.emplace_back( spec ); + } + tries.push_back( file + ":" + s.name ); + if( owned != s.name ) + { + tries.push_back( file + ":" + owned ); + } + tries.push_back( "@" + file + ":" + std::to_string( s.line ) ); + for( const std::string& t : tries ) + { + if( defslookup_detail::picksAlone( ing, t, members[k] ) ) + { + r.sel = t; + r.selProven = true; + break; + } + } + if( !r.selProven ) + { + r.sel = owned == s.name ? file + ":" + s.name : file + ":" + owned; + } + } + std::stable_sort( out.rows.begin(), out.rows.end(), [ & ]( const DefRow& a, const DefRow& b ) + { + if( a.test != b.test ) + { + return !a.test; + } + if( a.callers != b.callers ) + { + return a.callers > b.callers; + } + const Symbol& sa = ing.symbols[ a.rep ]; + const Symbol& sb = ing.symbols[ b.rep ]; + const std::string_view pa = rootRelPath( ing, sa.fileId ), pb = rootRelPath( ing, sb.fileId ); + return pa != pb ? pa < pb : sa.line < sb.line; + } ); + return out; +} + +// One definition row: . `compact` (the --path alternatives) +// keeps only what the next step needs: sel=, p=, class=. +inline void defRowXml( std::string& out, const IngestResult& ing, const DefLookup& L, const DefRow& r, const MemberOwners& owners, bool compact ) +{ + std::vector esc; + const Symbol& s = ing.symbols[ r.rep ]; + out += " esc; + std::string out = "" + rows + ""; +} + +// Both --path transports (verbs_navigate.h runPath, mcpverbs.h pathText): the two endpoints' , from= first. A glob endpoint +// stands for every fitting definition by design (from_glob=), so it lists none. +struct PathAlts +{ + std::string xml; + bool any() const noexcept { return !xml.empty(); } +}; +inline PathAlts pathAltsOf( const IngestResult& ing, const Graph& g, std::string_view srcN, std::string_view dstN, bool srcGlob, bool dstGlob, + NodeId srcBound, NodeId dstBound ) +{ + PathAlts out; + if( !srcGlob ) + { + out.xml += defAltsXml( ing, lookupDefs( ing, g, srcN ), srcBound, "from" ); + } + if( !dstGlob ) + { + out.xml += defAltsXml( ing, lookupDefs( ing, g, dstN ), dstBound, "to" ); + } + return out; +} + +// The legend clause for — present only when an answer carries one. +inline const char* defAltsLegend( bool on ) noexcept +{ + return on ? " lists the OTHER definitions the endpoint's name binds (the pick is not provably unique): sel= pastes " + "as that endpoint to bind one alone, p= where it is, class=source|test; alts_shown=K defs=N: K of the other rows shown, " + "next= pages the rest. " + : ""; +} + +} // namespace rw diff --git a/src/main.cpp b/src/main.cpp index b501ae6e9..f9f6c0cd2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include "pageview.h" // §P8: the ONE --limit/--offset window + root-element shown=/capped= disclosure #include "graphlegend.h" // §H4 §3.4: the ONE counts_floor= marker + the shared graph-count legend wording #include "pathgaps.h" // PATH-GAP: --path's gap analysis + clause, shared with MCP path_between +#include "defslookup.h" // D2: --defs and --path's inline alternatives (lookupDefs) #include "pathpairs.h" // TRACE-EP: --path's reaching definition pairs, shared with MCP path_between #include "columnar.h" // RESEARCH lever 1: opt-in columnar re-serialization for the flat list verbs (--format=columnar) #include "redact.h" // RedactCounts + reportRedactions for the emitted-body secret redaction @@ -3062,6 +3063,7 @@ VerbPrecedence scanReportVerbPrecedence( const rw::Config& c ) { "--callers", !c.callers.empty() }, { "--callees", !c.callees.empty() }, { "--graph-query", !c.graphQuery.empty() }, { "--uses", !c.usesSym.empty() }, { "--verify", !c.verifyClaim.empty() }, // G4: dispatches between --uses and --external-surface (runVerify) + { "--defs", !c.defsSpec.empty() }, // D2: dispatches between --verify and --external-surface (runDefs) { "--external-surface", c.externalSurface }, { "--path", !c.pathSpec.empty() }, { "--connect", !c.connectSpec.empty() }, { "--impact", !c.impactSym.empty() }, { "--mentions", !c.mentionsSym.empty() }, { "--affected", !c.affectedFiles.empty() }, @@ -3233,7 +3235,7 @@ void warnMapModifierDiscarded( const rw::Config& c, const VerbPrecedence& prec ) // the verbs with a JSON twin — the read sites of cfg.json outside this refusal (`ripwire . --grep=cfg.json`) inline constexpr std::string_view kJsonVerbs[] = { - "--for", "--pack-task", "--callers", "--callees", "--impact", "--quality-delta", "--test-gate", "--metrics", + "--for", "--pack-task", "--callers", "--callees", "--impact", "--quality-delta", "--test-gate", "--metrics", "--defs", "--plan-lanes", // JSON-NATIVE: emits JSON with or without the flag, and says so (the redundancy note in main) }; @@ -4335,7 +4337,7 @@ static int dispatchMain( const rw::Config& cfg, char** argv ) // The sentence is the allow-list (kJsonVerbs) spelled out; keep the two in step. rw::emitTo( stderr, "ripwire: --json is not yet supported for {} — 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)\n", std::string_view( unsupported.data(), unsupported.size() ) ); + "--metrics, --defs, and --plan-lanes which is JSON-native (e.g. ripwire --callers=SYM --json)\n", std::string_view( unsupported.data(), unsupported.size() ) ); } return 1; } @@ -5680,6 +5682,11 @@ static int dispatchMain( const rw::Config& cfg, char** argv ) return *handled; } + if( std::optional handled = runDefs( dsp ) ) + { + return *handled; + } + if( std::optional handled = runExternalSurface( dsp ) ) { return *handled; diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index 82d5c644a..c8c9ac22e 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2476,6 +2476,172 @@ std::optional runExternalSurface( const MainDispatch& d ) return std::nullopt; } +// --defs=NAME[,NAME…] (lane D2 DEFS-LOOKUP): which definitions each name binds — src/defslookup.h lookupDefs, the one lookup +// --path's inline alternatives read too. One index load for up to kDefsNamesMax names; a single name that binds nothing gets +// the shared refusal (selectorrefuse.h), a missing name among several a defs="0" row whose next= is that refusal. +std::optional runDefs( const MainDispatch& d ) +{ + using namespace rw; + const Config& cfg = d.cfg; + const IngestResult& ing = d.ing; + if( cfg.defsSpec.empty() ) + { + return std::nullopt; + } + std::vector names; + for( std::string_view rest = cfg.defsSpec; ; ) + { + const std::size_t comma = rest.find( ',' ); + names.push_back( rest.substr( 0, comma ) ); + if( comma == std::string_view::npos ) + { + break; + } + rest.remove_prefix( comma + 1 ); + } + for( std::size_t i = 0; i < names.size(); ++i ) + { + if( names[i].empty() ) + { + rw::emitTo( stderr, "{}\n", rw::emptyListItemMessage( "--defs", i + 1, "--defs=parseArgs" ).c_str() ); + return 1; + } + } + if( names.size() > kDefsNamesMax ) + { + rw::refuseFlagValue( "--defs", "up to 16 symbol names, A[,B…]", cfg.defsSpec, "--defs=parseArgs,rankGraph" ); + return 1; + } + std::vector looks; + looks.reserve( names.size() ); + for( const std::string_view n : names ) + { + looks.push_back( lookupDefs( ing, d.g, n ) ); + } + if( names.size() == 1 && looks[0].matches.empty() && looks[0].dataDefs == 0 ) + { + return refuseNotFoundWithAnswer( ing, cfg, rw::NotFoundAnswer{ "defs", { { "of", std::string( names[0] ) } }, {}, + rw::notFoundNear( ing, names[0], notFoundGitRoot( ing, cfg ) ) }, + "ripwire: --defs symbol not found: ", names[0], "--defs=" ); // the shared refusal + } + const std::size_t offset = names.size() == 1 ? std::size_t( std::max( cfg.pageOffset, 0 ) ) : 0; // --offset pages one name's rows + const MemberOwners owners( ing ); + const bool singleRoot = ing.realPaths.empty() && cfg.roots.size() == 1; + std::vector esc; + bool anyProvenMiss = false, anyCut = false, anyMissing = false, anyBodyless = false, anyUnproven = false, anyData = false; + std::string body; + for( const DefLookup& L : looks ) + { + const std::size_t begin = std::min( offset, L.defs() ); + const std::size_t end = std::min( begin + kDefRowsCap, L.defs() ); + if( cfg.json ) + { + body += std::string( body.empty() ? "" : "," ) + "{\"q\":\"" + jsonStr( L.spec ) + "\",\"defs\":" + std::to_string( L.defs() ); + } + else + { + body += " 0 ) + { + anyUnproven = true; + body += cfg.json ? ",\"unproven_defs\":" + std::to_string( L.unprovenDefs ) : rw::unprovenDefsAttrXml( L.unprovenDefs ); + } + if( L.dataDefs > 0 ) + { + anyData = true; + body += cfg.json ? ",\"data_defs\":" + std::to_string( L.dataDefs ) : " data_defs=\"" + std::to_string( L.dataDefs ) + "\""; + } + std::string next; + if( L.matches.empty() && L.dataDefs == 0 ) + { + anyMissing = true; + next = nextFlag( "--defs=", L.spec ); // the single-name call answers with the shared refusal and its near= + } + else if( end < L.defs() || begin > 0 ) + { + anyCut = true; + body += cfg.json ? ",\"shown\":" + std::to_string( end - begin ) + ",\"offset\":" + std::to_string( begin ) + : " shown=\"" + std::to_string( end - begin ) + "\" offset=\"" + std::to_string( begin ) + "\""; + if( end < L.defs() ) + { + next = nextFlag( "--defs=", L.spec ) + " --offset=" + std::to_string( end ); + } + } + if( cfg.json ) + { + body += next.empty() ? std::string() : ",\"next\":\"" + jsonStr( next ) + "\""; + body += ",\"rows\":["; + } + else + { + body += nextAttrXml( next ) + ">"; + } + for( std::size_t i = begin; i < end; ++i ) + { + const DefRow& r = L.rows[i]; + anyProvenMiss = anyProvenMiss || !r.selProven; + anyBodyless = anyBodyless || L.groups.bodyless[ r.group ]; + if( cfg.json ) + { + if( i > begin ) + { + body += ","; + } + defRowJson( body, ing, L, r, owners ); + } + else + { + defRowXml( body, ing, L, r, owners, /*compact=*/false ); + } + } + body += cfg.json ? "]}" : ""; + } + std::string rootAttr; + if( singleRoot ) + { + rootAttr = cfg.json ? ",\"root\":\"" + jsonStr( cfg.roots[0] ) + "\"" : " root=\"" + std::string( escapeXml( cfg.roots[0], esc ) ) + "\""; + } + if( cfg.json ) + { + rw::emitTo( stdout, "{{\"schema\":\"ripwire.defs/v1\"{},\"names\":[{}]}}\n", rootAttr, body ); + return 0; + } + // present-only legend: every clause rides only when an attribute it defines is on the page + std::string legend = "{}{}", legend, rw::rootRelPathsLegend( singleRoot ), rootAttr, body ); + return 0; +} + std::optional runPath( const MainDispatch& d ) { using namespace rw; From 45c426f4a9a7acfa6a33bdccbd3ca1b7e1ddcc81 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:10:12 -0400 Subject: [PATCH 21/95] path: inline beside from_defs=/to_defs= when the pick is not provably unique hono-12: --path=fetch,compose bound one of seven fetch definitions and named none of the others. Both transports (runPath, MCP pathText) now list the other definition groups (sel=, p=, class=) up to 16, alts_shown= defs= next=--defs=NAME past that; a unique pick adds 0 bytes. Co-Authored-By: Claude Opus 5.5 --- changelog.d/path-inline-alternatives.md | 7 +++++++ src/compactlegend.h | 1 + src/mcpverbs.h | 5 ++++- src/verbs_navigate.h | 5 ++++- 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 changelog.d/path-inline-alternatives.md diff --git a/changelog.d/path-inline-alternatives.md b/changelog.d/path-inline-alternatives.md new file mode 100644 index 000000000..98373def9 --- /dev/null +++ b/changelog.d/path-inline-alternatives.md @@ -0,0 +1,7 @@ +### Changed — `--path` lists the other definitions of an endpoint whose pick is not provably unique + +`--path=fetch,compose` on hono printed `from_defs="7"`, bound one `fetch` and named none of the others, so an agent had to guess a +`file:name` spelling and retry. When an endpoint's name binds two or more definition groups, the answer (CLI and MCP +`path_between`) now ends with ``: one compact row per other definition with `p=`, `class=` and a +pasteable `sel=`. Up to 16 rows; past that `alts_shown=K defs=N next=--defs=NAME` pages the rest. An endpoint that binds one +definition, or a `*` glob endpoint, adds nothing. diff --git a/src/compactlegend.h b/src/compactlegend.h index e8025eb4f..9efa1e551 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -837,6 +837,7 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "unproven_defs", "name unproven_defs=K: same-named definitions a file:name spelling could not tie to that file; not in defs=", true, "name", MapHeaderRead::No, {}, "defs" }, { "shown", "name shown=K offset=M: rows M..M+K of defs=; next= pages the rest", true, "name", MapHeaderRead::No, {}, "defs" }, // also defines offset= { "next", "name next=: the next page, or (defs=0) the single-name call whose refusal names near names", true, "name", MapHeaderRead::No, {}, "defs" }, + { "alts", ": the OTHER definitions that endpoint's name binds (the pick is not provably unique): , sel= pastes as that endpoint; alts_shown=K defs=N next= pages the rest", true, {}, MapHeaderRead::No, {}, "path" }, { "from_p", "from_p=/to_p=: the definitions from= and to= were bound to", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_p= { "from_defs", "from_defs=/to_defs=: definitions of each name, all searched; above 1, qualify file:name", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_defs= // TRACE-EP (src/pathpairs.h kPathPairsLegend), present only when several definition pairs reach: chains and rows diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 32b9aac4b..7a4a26d56 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -44,6 +44,7 @@ #include "forgraphverb.h" // FOR-ROUTE-DECLINED: the `for` verb's subject-read graph-verb block (the CLI twin's wrapper) #include "pathgaps.h" // PATH-GAP: the shared gap analysis + clause of --path / path_between #include "pathpairs.h" // TRACE-EP: the reaching definition pairs of --path / path_between +#include "defslookup.h" // D2 part 5: pathAltsOf — path_between's inline alternatives, as on the CLI #include // §B6 M3: the shared root-path existence/directory check (mcpRootRefusal below) #include // mcpAnswerText / usesText: nullopt is an answer buffer that failed, never an empty answer @@ -3827,8 +3828,9 @@ inline std::optional pathText( const std::string& root, const std:: const PathTestTwin ptTwin = srcGlob || dstGlob ? PathTestTwin{} : pathTestTwins( ing, g, { srcDefs, from, ptBound.from }, { dstDefs, to, ptBound.to }, ptShown ); // zed-03, as on the CLI const std::vector ptOwners = pathHopOwners( ing, pth ); // PATH-OWNERS, as on the CLI const bool ptHasOwner = ptMarks.owner; + const PathAlts ptAlts = pathAltsOf( ing, g, from, to, srcGlob, dstGlob, ptBound.from ? srcUsed : kNoNode, ptBound.to ? dstUsed : kNoNode ); // D2 part 5, as on the CLI rw::emitTo( mem, "{}", ( unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + ptKindLegend + "graph holds none. {}{}{}{}-->{}", ( std::string( defAltsLegend( ptAlts.any() ) ) + unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + ptKindLegend + splitArmLegend( ptHasSplit ) + pathUnboundLegend( !ptBound.from || !ptBound.to ) + memberOwnerLegend( ptHasOwner ) + memberOwnerContainedLegend( ptMarks.ownerContained ) + std::string( pathPairsLegend( ptPairs ) ) + pathOrderLegend( ptPairs, ptTwin ) ).c_str(), ( std::string( toValueRefsLegend( ptToValueRefs.count > 0 ) ) + valueRefsDepthLegend( ptToValueRefs.depthCut.files > 0 ) ).c_str(), @@ -3850,6 +3852,7 @@ inline std::optional pathText( const std::string& root, const std:: rw::emitTo( mem, "{}>{}{}{}", ptUnreached.rootAttrs, ptUnreached.rows, pathBridgeXml( ing, g, ptBridge, ptSingleRoot, ptRootPrefix ), pathNearSinkXml( ing, ptNear, ptSingleRoot, ptRootPrefix ) ); emitPathAnswerRows( mem, ing, g, ptPairs, pth, ptOwners, ptSingleRoot, ptRootPrefix ); // the CLI's own row emitter (pathpairs.h / defattrib.h) + rw::emitRaw( mem, ptAlts.xml.c_str() ); // D2 part 5, as on the CLI rw::emitRaw( mem, "" ); return mcpAnswerText( stream ); // nullopt = the buffer lost bytes (dispatch answers -32603), "" stays not-found } diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index c8c9ac22e..1f9ebf666 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2779,8 +2779,10 @@ std::optional runPath( const MainDispatch& d ) const rw::PathTestTwin pthTwin = srcGlob || dstGlob ? rw::PathTestTwin{} : rw::pathTestTwins( ing, g, { srcDefs, srcN, pthBound.from }, { dstDefs, dstN, pthBound.to }, pthShown ); const std::vector pthOwners = rw::pathHopOwners( ing, path ); // PATH-OWNERS: n="Owner.name" per hop const bool pthHasOwner = pthMarks.owner; + // D2 part 5 (defslookup.h): the other definitions of an endpoint whose pick is not provably unique, inline + const rw::PathAlts pthAlts = rw::pathAltsOf( ing, g, srcN, dstN, srcGlob, dstGlob, pthBound.from ? srcUsed : rw::kNoNode, pthBound.to ? dstUsed : rw::kNoNode ); rw::emitTo( stdout, "{}", ( rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Path, pthUnprovenDefs > 0 ) + rw::viaNameLegend( pthHasVia ) + "graph holds none. {}{}{}{}-->{}", ( std::string( rw::defAltsLegend( pthAlts.any() ) ) + rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Path, pthUnprovenDefs > 0 ) + rw::viaNameLegend( pthHasVia ) + rw::viaLinkLegend( pthHasLink ) + rw::anchorMissLegend( pthHasMiss ) + rw::splitArmLegend( pthHasSplit ) + rw::pathUnboundLegend( !pthBound.from || !pthBound.to ) + rw::memberOwnerLegend( pthHasOwner ) + rw::memberOwnerContainedLegend( pthMarks.ownerContained ) + std::string( rw::pathPairsLegend( pthPairs ) ) + rw::pathOrderLegend( pthPairs, pthTwin ) ).c_str(), @@ -2810,6 +2812,7 @@ std::optional runPath( const MainDispatch& d ) rw::pathNearSinkXml( ing, pthNear, pthSingleRoot, pthRootPrefix ) ); // FE-B via="name", #409 via="link"/anchor="missing", attrib-multidef prov="split", PATH-OWNERS n="Owner.name": one emitter, shared with MCP path_between rw::emitPathAnswerRows( stdout, ing, g, pthPairs, path, pthOwners, pthSingleRoot, pthRootPrefix ); // TRACE-EP: pairs or today's rows, shared with MCP + rw::emitRaw( stdout, pthAlts.xml.c_str() ); // D2 part 5: empty when both picks are provably unique rw::emitTo( stdout, "" ); return 0; } From 239cf07c9f1a204c93ad7b4699e0c5ce59720ea2 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:57:02 -0400 Subject: [PATCH 22/95] refactor: handlerrule reuses isIdentChar/trimWs, keyed site lookup, merge helper outside NameKeyHops (qd gating) Co-Authored-By: Claude Opus 5.5 --- src/backhops.h | 15 +------- src/handlerrule.h | 97 +++++++++++++++++++---------------------------- 2 files changed, 42 insertions(+), 70 deletions(-) diff --git a/src/backhops.h b/src/backhops.h index ef593d119..4ff0e23db 100644 --- a/src/backhops.h +++ b/src/backhops.h @@ -636,7 +636,7 @@ class NameKeyHops return isCall( m_ing.references[ site ] ) && from != d && from < m_ing.symbols.size(); } ); } - return withPostSites( out, m_rules.sitesNaming( d ) ); // ON-NAME-HANDLERS-V2: a posted message class names d + return mergedSiteLists( out, m_rules.sitesNaming( d ) ); // ON-NAME-HANDLERS-V2: a posted message class names d } // The template lookups made in u that make a call, in reference order. std::vector sitesIn( NodeId u ) const @@ -646,7 +646,7 @@ class NameKeyHops { std::ranges::copy_if( m_pix->sitesIn( std::span( &u, 1 ) ), std::back_inserter( out ), [ & ]( std::uint32_t site ) { return isCall( m_ing.references[ site ] ); } ); } - return withPostSites( out, m_rules.sitesIn( u ) ); + return mergedSiteLists( out, m_rules.sitesIn( u ) ); } // The message-handler rule (handlerrule.h): its post sites are name-key sites too. const HandlerRules& rules() const noexcept { return m_rules; } @@ -668,17 +668,6 @@ class NameKeyHops } private: - // The template sites and the post sites, merged in reference order (each list is already in that order). - static std::vector withPostSites( std::vector sites, const std::vector& posts ) - { - if( !posts.empty() ) - { - const std::size_t mid = sites.size(); - sites.insert( sites.end(), posts.begin(), posts.end() ); - std::ranges::inplace_merge( sites, sites.begin() + std::ptrdiff_t( mid ) ); - } - return sites; - } // A usable template lookup that makes a call through the name: not too wide ("w"), not a presence test or a store ("t": hasattr, // `o[ 'p' + e ] = fn`; both are still clues, ingest_valuerefs.h emitPattern). static bool isCall( const Reference& r ) noexcept diff --git a/src/handlerrule.h b/src/handlerrule.h index 64011aca4..c8110db53 100644 --- a/src/handlerrule.h +++ b/src/handlerrule.h @@ -26,6 +26,8 @@ #include "darkflags.h" // readWhole — the rule and the post site are read from the file text #include "filter.h" // isTestPath — a test's reader does not make posters #include "model.h" +#include "resolve.h" // trimWs +#include "infra/namesplit.h" // isIdentChar #include "valuerefindex.h" // ClassFamilies — owner class and written-base family #include @@ -33,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +88,15 @@ inline std::string snakeOfCamel( std::string_view s ) return out; } +// Two reference-ordered site lists merged in reference order (NameKeyHops: template lookups beside post sites). +inline std::vector mergedSiteLists( std::vector sites, const std::vector& more ) +{ + const std::size_t mid = sites.size(); + sites.insert( sites.end(), more.begin(), more.end() ); + std::ranges::inplace_merge( sites, sites.begin() + std::ptrdiff_t( mid ) ); + return sites; +} + class HandlerRules { public: @@ -103,8 +115,8 @@ class HandlerRules const HandlerPostSite* siteAt( std::uint32_t ref ) const { build(); - const auto it = std::ranges::lower_bound( m_sites, ref, {}, &HandlerPostSite::ref ); - return it != m_sites.end() && it->ref == ref ? &*it : nullptr; + const auto it = m_siteOfRef.find( ref ); + return it == m_siteOfRef.end() ? nullptr : &m_sites[ it->second ]; } // The post sites whose message names handler `d` (a method), in reference order. std::vector sitesNaming( NodeId d ) const @@ -195,56 +207,31 @@ class HandlerRules } private: + // A Python function definition inside a class (collectMessagesAndMethods marks them). bool isHandlerShaped( NodeId d ) const { - const Symbol& s = m_ing.symbols[d]; - return s.lang == Lang::Python && ( s.kind == SymKind::Function || s.kind == SymKind::Method ) && m_classes.ownerOf( d ) != kNoNode; + return d < m_inClass.size() && m_inClass[d] != 0; } std::string_view span( const Symbol& s ) const { const std::string& t = text( s.fileId ); return s.sigStartByte < s.endByte && s.endByte <= t.size() ? std::string_view( t ).substr( s.sigStartByte, s.endByte - s.sigStartByte ) : std::string_view(); } - std::string_view lineAt( const Reference& r ) const - { - const std::string_view t( text( r.fileId ) ); - if( r.startByte >= t.size() ) - { - return {}; - } - const std::size_t b = t.rfind( '\n', r.startByte ); - const std::size_t e = t.find( '\n', r.startByte ); - const std::size_t from = b == std::string_view::npos ? 0 : b + 1; - return t.substr( from, ( e == std::string_view::npos ? t.size() : e ) - from ); - } - static bool isIdent( char c ) noexcept { return std::isalnum( static_cast( c ) ) || c == '_'; } - static std::string_view trimmed( std::string_view s ) noexcept - { - while( !s.empty() && std::isspace( static_cast( s.front() ) ) ) - { - s.remove_prefix( 1 ); - } - while( !s.empty() && std::isspace( static_cast( s.back() ) ) ) - { - s.remove_suffix( 1 ); - } - return s; - } // `cls.A = f"P{v}"` (either quote) → prefix P; nullopt for any other line. static std::optional templatePrefix( std::string_view line, std::string_view attr ) { - line = trimmed( line ); + line = trimWs( line ); const std::string head = "cls." + std::string( attr ); if( !line.starts_with( head ) ) { return std::nullopt; } - line = trimmed( line.substr( head.size() ) ); + line = trimWs( line.substr( head.size() ) ); if( !line.starts_with( '=' ) || line.starts_with( "==" ) ) { return std::nullopt; } - line = trimmed( line.substr( 1 ) ); + line = trimWs( line.substr( 1 ) ); if( line.size() < 6 || line[0] != 'f' || ( line[1] != '"' && line[1] != '\'' ) || line.back() != line[1] ) { return std::nullopt; @@ -252,7 +239,7 @@ class HandlerRules const std::string_view body = line.substr( 2, line.size() - 3 ); const std::size_t open = body.find( '{' ); if( open == 0 || open == std::string_view::npos || body.back() != '}' || body.find( '{', open + 1 ) != std::string_view::npos - || !std::ranges::all_of( body.substr( 0, open ), isIdent ) || !std::ranges::all_of( body.substr( open + 1, body.size() - open - 2 ), isIdent ) ) + || !std::ranges::all_of( body.substr( 0, open ), namesplit::isIdentChar ) || !std::ranges::all_of( body.substr( open + 1, body.size() - open - 2 ), namesplit::isIdentChar ) ) { return std::nullopt; } @@ -284,9 +271,9 @@ class HandlerRules // One `cls.A = f"P{v}"` line of __init_subclass__ `writer` (at byte `at` of its file) as a rule, or nullopt. std::optional ruleAt( NodeId writer, std::string_view line, std::uint32_t lineNo ) const { - const std::string_view t = trimmed( line ); + const std::string_view t = trimWs( line ); std::size_t e = 4; - while( e < t.size() && isIdent( t[e] ) ) + while( e < t.size() && namesplit::isIdentChar( t[e] ) ) { ++e; } @@ -326,12 +313,12 @@ class HandlerRules { return {}; } - std::string_view v = trimmed( head.substr( at + 9 ) ); + std::string_view v = trimWs( head.substr( at + 9 ) ); if( !v.starts_with( '=' ) ) { return {}; } - v = trimmed( v.substr( 1 ) ); + v = trimWs( v.substr( 1 ) ); if( v.size() < 2 || ( v[0] != '"' && v[0] != '\'' ) ) { return {}; @@ -371,7 +358,7 @@ class HandlerRules for( ;; ) { const std::size_t b = i; - while( i < t.size() && isIdent( t[i] ) ) + while( i < t.size() && namesplit::isIdentChar( t[i] ) ) { ++i; } @@ -443,35 +430,28 @@ class HandlerRules } void collectMessagesAndMethods() const { + m_inClass.assign( m_ing.symbols.size(), 0 ); for( const Symbol& s : m_ing.symbols ) { - if( s.lang != Lang::Python ) + const bool fn = s.kind == SymKind::Function || s.kind == SymKind::Method; + if( s.lang == Lang::Python && fn && m_classes.ownerOf( s.id ) != kNoNode ) { - continue; + m_inClass[ s.id ] = 1; + m_methodsByName[ std::string_view( s.name ) ].push_back( s.id ); } - if( s.kind == SymKind::Class && !handlerNameOf( s.id ).empty() ) + else if( s.lang == Lang::Python && s.kind == SymKind::Class && !handlerNameOf( s.id ).empty() ) { m_messagesByName[ std::string_view( s.name ) ].push_back( s.id ); } - else if( isHandlerShaped( s.id ) ) - { - m_methodsByName[ std::string_view( s.name ) ].push_back( s.id ); - } } } - // The innermost Python function definition of `fileId` holding byte `at`, else kNoNode. + // The innermost Python function definition of `fileId` holding byte `at`, else kNoNode (definition order: an inner function + // follows the one it is nested in, so the last that holds `at` is the innermost). NodeId functionAt( std::uint32_t fileId, std::uint32_t at ) const { - NodeId best = kNoNode; - for( const NodeId f : m_fnsOfFile[ fileId ] ) - { - const Symbol& s = m_ing.symbols[f]; - if( s.sigStartByte <= at && at < s.endByte && ( best == kNoNode || s.sigStartByte >= m_ing.symbols[ best ].sigStartByte ) ) - { - best = f; - } - } - return best; + const auto holds = [ & ]( NodeId f ) { return m_ing.symbols[f].sigStartByte <= at && at < m_ing.symbols[f].endByte; }; + const auto hit = std::ranges::find_if( m_fnsOfFile[ fileId ] | std::views::reverse, holds ); + return hit == ( m_fnsOfFile[ fileId ] | std::views::reverse ).end() ? kNoNode : *hit; } // Readers of a rule attribute (`.A` not assigned) in Python files outside test paths, read from the text, and every method // name their classes define (the posters). @@ -505,7 +485,7 @@ class HandlerRules ++next; } const bool store = next < t.size() && t[ next ] == '=' && ( next + 1 >= t.size() || t[ next + 1 ] != '=' ); - if( ( end < t.size() && isIdent( t[ end ] ) ) || store ) + if( ( end < t.size() && namesplit::isIdentChar( t[ end ] ) ) || store ) { continue; } @@ -565,6 +545,7 @@ class HandlerRules site.names.erase( std::ranges::unique( site.names ).begin(), site.names.end() ); if( !site.names.empty() ) { + m_siteOfRef[ i ] = std::uint32_t( m_sites.size() ); m_sites.push_back( std::move( site ) ); } } @@ -575,6 +556,8 @@ class HandlerRules mutable bool m_built = false; mutable std::vector m_rules; mutable std::vector m_sites; // reference order + mutable HashMap m_siteOfRef; // post-site reference -> its m_sites index + mutable std::vector m_inClass; // per symbol: a Python function definition inside a class mutable std::vector m_readers; mutable std::vector> m_fnsOfFile; // per file: its Python function definitions mutable HashMap> m_messagesByName; From 08fa6b000449733556745f4d35433ad63a2b2e0d Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:57:07 -0400 Subject: [PATCH 23/95] defs: not-found compact readings, a shaping-flag row, the ripwire.defs/v1 legend pin, COMMANDS.md regen compactlegendcheck (U)/(UG) and shapingflagcheck (F) caught --defs=zzqq9: an unpinned compact legend, found=/of= undefined, and --top-k/--max-tokens/ --token-budget accepted silently. helpbudgetcheck (I): a one-line summary. Co-Authored-By: Claude Opus 5.5 --- docs/COMMANDS.md | 6 +++++- src/cli.h | 1 + src/compactlegend.h | 5 +++++ test/compactlegendcheck.sh | 3 +++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 6209e15fc..a78169204 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -40,7 +40,7 @@ Two limits apply to nearly everything here and are not repeated in every section **understand a codebase cold** — [`--top-k`](#--top-kn) · [`--no-inventory`](#--no-inventory) · [`--max-tokens`](#--max-tokensn) · [`--token-budget`](#--token-budgetnkmg) · [`--help-task`](#--help-tasktask) · [`--for`](#--fortask) · [`--sections`](#--sectionslegocompose) · [`--signatures-only`](#--signatures-only) · [`--auto-bodies`](#--auto-bodies) · [`--no-route`](#--no-route) · [`--adaptive`](#--adaptive) · [`--no-mention-boost`](#--no-mention-boost) · [`--no-doc-mention`](#--no-doc-mention) · [`--lego`](#--legotype) · [`--exemplar`](#--exemplartaskkind) · [`--recall`](#--recalltask) · [`--tree`](#--tree) · [`--html`](#--htmlfile) · [`--color-by`](#--color-bymode) · [`--order`](#--ordermode) · [`--no-stable`](#--no-stable) -**navigate / answer a question** — [`--around`](#--aroundsym) · [`--callers`](#--callerssym) · [`--callees`](#--calleessym) · [`--uses`](#--usessym) · [`--graph-query`](#--graph-queryexpr) · [`--external-surface`](#--external-surface) · [`--path`](#--pathsrcdst) · [`--connect`](#--connectabc) · [`--impact`](#--impactsym) · [`--verify`](#--verifyclaim) · [`--mentions`](#--mentionssym) · [`--affected`](#--affectedf1f2sym) · [`--exercises`](#--exercisestestfile) · [`--situ`](#--situf1f2) · [`--handoff`](#--handoff) · [`--test-gate`](#--test-gatef1f2) · [`--grep`](#--grepstr----regexpat) · [`--grep-context`](#--grep-contextn----grep-beforen----grep-aftern) · [`--and`](#--andstr) · [`--not`](#--notstr) · [`--grep-scope`](#--grep-scopelinefile) · [`--grep-in`](#--grep-incodeany) · [`--handles`](#--handles) · [`--match`](#--matchquery) · [`--pattern`](#--patternpat) · [`--query`](#--queryterms) +**navigate / answer a question** — [`--around`](#--aroundsym) · [`--callers`](#--callerssym) · [`--callees`](#--calleessym) · [`--uses`](#--usessym) · [`--graph-query`](#--graph-queryexpr) · [`--external-surface`](#--external-surface) · [`--path`](#--pathsrcdst) · [`--defs`](#--defsnamename) · [`--connect`](#--connectabc) · [`--impact`](#--impactsym) · [`--verify`](#--verifyclaim) · [`--mentions`](#--mentionssym) · [`--affected`](#--affectedf1f2sym) · [`--exercises`](#--exercisestestfile) · [`--situ`](#--situf1f2) · [`--handoff`](#--handoff) · [`--test-gate`](#--test-gatef1f2) · [`--grep`](#--grepstr----regexpat) · [`--grep-context`](#--grep-contextn----grep-beforen----grep-aftern) · [`--and`](#--andstr) · [`--not`](#--notstr) · [`--grep-scope`](#--grep-scopelinefile) · [`--grep-in`](#--grep-incodeany) · [`--handles`](#--handles) · [`--match`](#--matchquery) · [`--pattern`](#--patternpat) · [`--query`](#--queryterms) **zoom the detail ladder** — [`--detail`](#--detailn) · [`--pack-signatures`](#--pack-signatures) · [`--outline`](#--outlineab) · [`--expand`](#--expandab) · [`--compress`](#--compress) · [`--pack-top-n`](#--pack-top-nn) · [`--no-redact`](#--no-redact) @@ -971,6 +971,10 @@ $ ./build/ripwire . --path=main,rankGraphTeleport **Shaped by:** `--connect`, `--limit` +### `--defs=NAME[,NAME...]` + +**Answers:** list the definitions each name binds (up to 16 names), with the spelling that picks each alone per definition: file:line, kind, class=source|test, callers, and sel= — paste it into any SYM verb + ### `--connect=A,B,C` **Answers:** show how 2..16 symbols relate: the smallest subgraph that joins them minimal connecting subgraph over 2..16 symbols: terminals + fewest joining intermediaries + call edges in TRUE direction (finds the shared-caller join a directed --path can't) [--connect-radius=N (1..12, default 6)] diff --git a/src/cli.h b/src/cli.h index cdf574163..d21b10eb0 100644 --- a/src/cli.h +++ b/src/cli.h @@ -4235,6 +4235,7 @@ inline constexpr ShapingVerb kShapingVerbs[] = { { "--exemplar", nullptr, &Config::exemplar }, { "--around", nullptr, &Config::around }, { "--path", nullptr, &Config::pathSpec }, + { "--defs", nullptr, &Config::defsSpec }, // lane/defs-lookup: a fixed 16-row page (--offset pages), no budget { "--lego", nullptr, &Config::legoType }, { "--report", &Config::report, nullptr }, { "--slice", nullptr, &Config::sliceSpec }, diff --git a/src/compactlegend.h b/src/compactlegend.h index 9efa1e551..b2b3bb693 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -1165,6 +1165,11 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "near", "near=: the indexed name to retry with", false, "impact", MapHeaderRead::No, {}, "impact" }, { "near_renamed", "near_renamed=1: a definition of the selector left a changed file that now defines near= (a rename, not a mention)", false, "impact", MapHeaderRead::No, {}, "impact" }, { "refused", "refused=N: N definitions fit the selector and it cannot pick one (a refusal, not an absence; found=0 counts the rows listed)", false, "impact", MapHeaderRead::No, {}, "impact" }, + { "found", "found=0: no indexed definition matched the selector, nothing listed or counted (zero = none found, not none exists); exit stays 1", false, "defs", MapHeaderRead::No, {}, "defs" }, + { "of", "of=: the name asked for", false, "defs", MapHeaderRead::No, {}, "defs" }, + { "near", "near=: the indexed name to retry with", false, "defs", MapHeaderRead::No, {}, "defs" }, + { "near_renamed", "near_renamed=1: a definition of the selector left a changed file that now defines near= (a rename, not a mention)", false, "defs", MapHeaderRead::No, {}, "defs" }, + { "refused", "refused=N: N definitions fit the selector and it cannot pick one (a refusal, not an absence; found=0 counts the rows listed)", false, "defs", MapHeaderRead::No, {}, "defs" }, { "found", "found=0: no indexed definition matched the selector, nothing listed or counted (zero = none found, not none exists); exit stays 1", false, "path", MapHeaderRead::No, {}, "path" }, { "near", "near=: the indexed name to retry with", false, "path", MapHeaderRead::No, {}, "path" }, { "near_renamed", "near_renamed=1: a definition of the selector left a changed file that now defines near= (a rename, not a mention)", false, "path", MapHeaderRead::No, {}, "path" }, diff --git a/test/compactlegendcheck.sh b/test/compactlegendcheck.sh index d9fb4cd98..933854403 100755 --- a/test/compactlegendcheck.sh +++ b/test/compactlegendcheck.sh @@ -739,7 +739,10 @@ ripwire.pack-top-n/v1 830 813 ripwire.expand/v1 280 265 ripwire.expand-file/v1 510 508 ripwire.for/v1 1100 1099 +ripwire.defs/v1 340 316 ' +# PINNED 2026-10-27 (lane/defs-lookup): ripwire.defs/v1 316 B measured on this gate's --defs=zzqq9 probe (the shared not-found +# answer's readings: found=, of=, near=), pin 340. pinFor() { printf '%s\n' "$PIN_TABLE" | awk -v schema="$1" '$1 == schema { printf "%s", $2; exit }' From 00cb4ac6021cc5a6dd32afbbd9f91e477e305499 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 00:06:33 -0400 Subject: [PATCH 24/95] refactor: handlerrule drops the one-line isHandlerShaped (qd clone) Co-Authored-By: Claude Opus 5.5 --- src/handlerrule.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/handlerrule.h b/src/handlerrule.h index c8110db53..ba6401435 100644 --- a/src/handlerrule.h +++ b/src/handlerrule.h @@ -122,7 +122,7 @@ class HandlerRules std::vector sitesNaming( NodeId d ) const { std::vector out; - if( !any() || !isHandlerShaped( d ) ) + if( !any() || d >= m_inClass.size() || m_inClass[d] == 0 ) // a Python function definition inside a class { return out; } @@ -207,11 +207,6 @@ class HandlerRules } private: - // A Python function definition inside a class (collectMessagesAndMethods marks them). - bool isHandlerShaped( NodeId d ) const - { - return d < m_inClass.size() && m_inClass[d] != 0; - } std::string_view span( const Symbol& s ) const { const std::string& t = text( s.fileId ); From 2e9eeabe23f8f7572c4ba0f94be4c279b34703bb Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 00:30:26 -0400 Subject: [PATCH 25/95] defs: prove sel= only for the rows a page prints dispose binds 2619 groups on vscode: re-resolving up to four spellings per group made --defs=dispose 69-81 s warm against the callers verb's 30 s. Now sel= is proven for the printed window (16, 17 for --path's ); measured 30-47 s / 10-11 s / 6 s on vscode / cockroach / zed, equal to the callers verb's warm floor there. Output unchanged. Co-Authored-By: Claude Opus 5.5 --- src/defslookup.h | 53 +++++++++++++++++++++++++------------------- src/verbs_navigate.h | 2 +- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/defslookup.h b/src/defslookup.h index 2c862d692..6f7114403 100644 --- a/src/defslookup.h +++ b/src/defslookup.h @@ -43,7 +43,7 @@ struct DefRow { std::uint32_t group = 0; // the DefGroups index NodeId rep = kNoNode; // the group's bodied member (else its first match) - std::string sel; // the spelling that picks this group alone (when selProven) + std::string sel; // the spelling that picks this group alone (when selProven); filled for the printed window only bool selProven = false; // re-resolving sel binds this group's members and nothing else bool test = false; // pathDefInTests: a test/bench file or a test symbol std::size_t callers = 0; // distinct in-graph callers over the group's members (a floor) @@ -89,8 +89,8 @@ inline bool picksAlone( const IngestResult& ing, std::string_view spell, const s } } // namespace defslookup_detail -// THE lookup. `g` supplies the caller counts. -inline DefLookup lookupDefs( const IngestResult& ing, const Graph& g, std::string_view spec ) +// THE lookup. `g` supplies the caller counts; sel= is proven for the sorted rows [selBegin, selBegin + selCount) only. +inline DefLookup lookupDefs( const IngestResult& ing, const Graph& g, std::string_view spec, std::size_t selBegin = 0, std::size_t selCount = kDefRowsCap + 1 ) { DefLookup out; out.spec = spec; @@ -127,11 +127,33 @@ inline DefLookup lookupDefs( const IngestResult& ing, const Graph& g, std::strin // a member calling itself (recursion) is no outside caller r.callers = std::size_t( std::count_if( callers.begin(), callers.end(), [ & ]( NodeId c ) { return std::find( members[k].begin(), members[k].end(), c ) == members[k].end(); } ) ); + } + std::stable_sort( out.rows.begin(), out.rows.end(), [ & ]( const DefRow& a, const DefRow& b ) + { + if( a.test != b.test ) + { + return !a.test; + } + if( a.callers != b.callers ) + { + return a.callers > b.callers; + } + const Symbol& sa = ing.symbols[ a.rep ]; + const Symbol& sb = ing.symbols[ b.rep ]; + const std::string_view pa = rootRelPath( ing, sa.fileId ), pb = rootRelPath( ing, sb.fileId ); + return pa != pb ? pa < pb : sa.line < sb.line; + } ); + // sel= only for the rows a page prints (the window [selBegin, selBegin + selCount) of the sorted rows): each try re-resolves a + // spelling, and dispose binds 2619 groups on vscode: proving every one measured 69-81 s warm (the callers verb: 30 s) for a 16-row page + const std::size_t selEnd = std::min( out.rows.size(), selBegin + selCount ); + for( std::size_t i = std::min( selBegin, selEnd ); i < selEnd; ++i ) + { // sel=: the shortest spelling the resolver binds to this group alone — the name as given (one group), file:name, // file:Owner.name, then the @file:line seed (one definition starting on that line) - const Symbol& s = ing.symbols[ r.rep ]; - const std::string file( rootRelPath( ing, s.fileId ) ); - const std::string owned = qualifiedMemberName( owners, s ); + DefRow& r = out.rows[i]; + const Symbol& s = ing.symbols[ r.rep ]; + const std::string file( rootRelPath( ing, s.fileId ) ); + const std::string owned = qualifiedMemberName( owners, s ); std::vector tries; if( out.groups.size() == 1 ) { @@ -145,7 +167,7 @@ inline DefLookup lookupDefs( const IngestResult& ing, const Graph& g, std::strin tries.push_back( "@" + file + ":" + std::to_string( s.line ) ); for( const std::string& t : tries ) { - if( defslookup_detail::picksAlone( ing, t, members[k] ) ) + if( defslookup_detail::picksAlone( ing, t, members[ r.group ] ) ) { r.sel = t; r.selProven = true; @@ -154,24 +176,9 @@ inline DefLookup lookupDefs( const IngestResult& ing, const Graph& g, std::strin } if( !r.selProven ) { - r.sel = owned == s.name ? file + ":" + s.name : file + ":" + owned; + r.sel = file + ":" + owned; } } - std::stable_sort( out.rows.begin(), out.rows.end(), [ & ]( const DefRow& a, const DefRow& b ) - { - if( a.test != b.test ) - { - return !a.test; - } - if( a.callers != b.callers ) - { - return a.callers > b.callers; - } - const Symbol& sa = ing.symbols[ a.rep ]; - const Symbol& sb = ing.symbols[ b.rep ]; - const std::string_view pa = rootRelPath( ing, sa.fileId ), pb = rootRelPath( ing, sb.fileId ); - return pa != pb ? pa < pb : sa.line < sb.line; - } ); return out; } diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index 1f9ebf666..d18295e56 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2516,7 +2516,7 @@ std::optional runDefs( const MainDispatch& d ) looks.reserve( names.size() ); for( const std::string_view n : names ) { - looks.push_back( lookupDefs( ing, d.g, n ) ); + looks.push_back( lookupDefs( ing, d.g, n, names.size() == 1 ? std::size_t( std::max( cfg.pageOffset, 0 ) ) : 0, kDefRowsCap ) ); } if( names.size() == 1 && looks[0].matches.empty() && looks[0].dataDefs == 0 ) { From f966224bdbf341d7f7a8f62c3f9cbf807f2f858a Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 00:49:32 -0400 Subject: [PATCH 26/95] defs: --limit/--offset page one name (refused beside several); test/defslookupcheck.sh --offset was refused by the paging honor list and --limit would have been ignored. The new gate: namesakes + test copy + JSON key, a C prototype folded with its definition, every sel= re-resolved by --callers, spellings, zero found, the 16-row cap and paging, several names, the JSON twin, and --path only when the pick is not provably unique. Gate count 727. Co-Authored-By: Claude Opus 5.5 --- README.md | 4 +- docs/EVALS.md | 6 +- present/deck5_ripwire_build.js | 6 +- src/cli.h | 3 +- src/verbs_navigate.h | 13 +++- test/defslookupcheck.sh | 104 ++++++++++++++++++++++++++++ test/defslookupfix/config.json | 1 + test/defslookupfix/gen/m01.ts | 1 + test/defslookupfix/gen/m02.ts | 1 + test/defslookupfix/gen/m03.ts | 1 + test/defslookupfix/gen/m04.ts | 1 + test/defslookupfix/gen/m05.ts | 1 + test/defslookupfix/gen/m06.ts | 1 + test/defslookupfix/gen/m07.ts | 1 + test/defslookupfix/gen/m08.ts | 1 + test/defslookupfix/gen/m09.ts | 1 + test/defslookupfix/gen/m10.ts | 1 + test/defslookupfix/gen/m11.ts | 1 + test/defslookupfix/gen/m12.ts | 1 + test/defslookupfix/gen/m13.ts | 1 + test/defslookupfix/gen/m14.ts | 1 + test/defslookupfix/gen/m15.ts | 1 + test/defslookupfix/gen/m16.ts | 1 + test/defslookupfix/gen/m17.ts | 1 + test/defslookupfix/gen/m18.ts | 1 + test/defslookupfix/src/a.ts | 5 ++ test/defslookupfix/src/b.ts | 3 + test/defslookupfix/src/work.c | 2 + test/defslookupfix/src/work.h | 1 + test/defslookupfix/test/run.test.ts | 1 + test/regression.sh | 2 +- 31 files changed, 156 insertions(+), 13 deletions(-) create mode 100755 test/defslookupcheck.sh create mode 100644 test/defslookupfix/config.json create mode 100644 test/defslookupfix/gen/m01.ts create mode 100644 test/defslookupfix/gen/m02.ts create mode 100644 test/defslookupfix/gen/m03.ts create mode 100644 test/defslookupfix/gen/m04.ts create mode 100644 test/defslookupfix/gen/m05.ts create mode 100644 test/defslookupfix/gen/m06.ts create mode 100644 test/defslookupfix/gen/m07.ts create mode 100644 test/defslookupfix/gen/m08.ts create mode 100644 test/defslookupfix/gen/m09.ts create mode 100644 test/defslookupfix/gen/m10.ts create mode 100644 test/defslookupfix/gen/m11.ts create mode 100644 test/defslookupfix/gen/m12.ts create mode 100644 test/defslookupfix/gen/m13.ts create mode 100644 test/defslookupfix/gen/m14.ts create mode 100644 test/defslookupfix/gen/m15.ts create mode 100644 test/defslookupfix/gen/m16.ts create mode 100644 test/defslookupfix/gen/m17.ts create mode 100644 test/defslookupfix/gen/m18.ts create mode 100644 test/defslookupfix/src/a.ts create mode 100644 test/defslookupfix/src/b.ts create mode 100644 test/defslookupfix/src/work.c create mode 100644 test/defslookupfix/src/work.h create mode 100644 test/defslookupfix/test/run.test.ts diff --git a/README.md b/README.md index 96f8f50c1..54a09d652 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
-726 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +727 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 **726 gate scripts** and is the authoritative list; +`test/regression.sh` names **727 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 diff --git a/docs/EVALS.md b/docs/EVALS.md index 0e2d7d4a2..4b579042d 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 726 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 727 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 **726 gate scripts**, all of which exist on disk. +naming **727 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 726. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 727. 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/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index d77c20956..e1d3067e2 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1214,7 +1214,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["726 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["727 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1238,7 +1238,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "726", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "727", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1540,7 +1540,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["192 long flags · 36 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["726 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["727 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 71 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/src/cli.h b/src/cli.h index d21b10eb0..a78c2f855 100644 --- a/src/cli.h +++ b/src/cli.h @@ -3872,7 +3872,7 @@ inline void validatePlanLanes( Config& c ) noexcept // first PROSE member — it has no XML root, so it spells the same shown=/total=/capped= facts in its section // headers (situ.h) and test/pagingsweepcheck.sh (L) reads it as prose rather than parsing a root element. constexpr const char* kPagingHonoringVerbs = - "--lint --hotspots --callers --callees --tree --deps --cochange --owners --clones --doc-drift " + "--lint --hotspots --callers --callees --defs --tree --deps --cochange --owners --clones --doc-drift " "--communities --community --whereis --grep/--regex --match --pattern --impact --uses --exercises " "--seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate " "--biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence " @@ -3889,6 +3889,7 @@ inline bool honorsPaging( const Config& c ) noexcept || c.readability || c.ensemble || c.qualityPanel || c.contextRatio || c.nonlocalState || c.commentCoherence || c.namingConsistency || !c.safeDeleteSym.empty() || c.prContext // P4 (L7): the changed-file window || !c.editCheckSym.empty() // 2026-09-10: --edit-check windows its UNFLAGGED caller rows (editcheck.h) + || !c.defsSpec.empty() // lane/defs-lookup: --defs pages one name's definition rows (16 by default) || c.darkFlags // 2026-09-10 (C1 F-07): --flags' per-gate sites, and --flip's six listings || c.situ || !c.situFiles.empty() // 2026-09-10 (C1 F-10): --situ sections [1] and [3] (section [2] is the answer) // --in=DIR is deliberately NOT a member, and was: it is a MODIFIER of the default map, not a verb, and diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index d18295e56..f60c9c6fc 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2512,11 +2512,18 @@ std::optional runDefs( const MainDispatch& d ) rw::refuseFlagValue( "--defs", "up to 16 symbol names, A[,B…]", cfg.defsSpec, "--defs=parseArgs,rankGraph" ); return 1; } + if( names.size() > 1 && cfg.pageOffset > 0 ) + { + rw::refuseFlagValue( "--offset", "a page of ONE --defs name (several names each print their first page)", std::to_string( cfg.pageOffset ), "--defs=parseArgs --offset=16" ); + return 1; + } + // the page: kDefRowsCap rows unless --limit names another size; --offset pages one name only + const std::size_t pageRows = cfg.pageLimit > 0 ? std::size_t( cfg.pageLimit ) : kDefRowsCap; std::vector looks; looks.reserve( names.size() ); for( const std::string_view n : names ) { - looks.push_back( lookupDefs( ing, d.g, n, names.size() == 1 ? std::size_t( std::max( cfg.pageOffset, 0 ) ) : 0, kDefRowsCap ) ); + looks.push_back( lookupDefs( ing, d.g, n, names.size() == 1 ? std::size_t( std::max( cfg.pageOffset, 0 ) ) : 0, pageRows ) ); } if( names.size() == 1 && looks[0].matches.empty() && looks[0].dataDefs == 0 ) { @@ -2533,7 +2540,7 @@ std::optional runDefs( const MainDispatch& d ) for( const DefLookup& L : looks ) { const std::size_t begin = std::min( offset, L.defs() ); - const std::size_t end = std::min( begin + kDefRowsCap, L.defs() ); + const std::size_t end = std::min( begin + pageRows, L.defs() ); if( cfg.json ) { body += std::string( body.empty() ? "" : "," ) + "{\"q\":\"" + jsonStr( L.spec ) + "\",\"defs\":" + std::to_string( L.defs() ); @@ -2565,7 +2572,7 @@ std::optional runDefs( const MainDispatch& d ) : " shown=\"" + std::to_string( end - begin ) + "\" offset=\"" + std::to_string( begin ) + "\""; if( end < L.defs() ) { - next = nextFlag( "--defs=", L.spec ) + " --offset=" + std::to_string( end ); + next = nextFlag( "--defs=", L.spec ) + " --offset=" + std::to_string( end ) + ( cfg.pageLimit > 0 ? " --limit=" + std::to_string( cfg.pageLimit ) : std::string() ); } } if( cfg.json ) diff --git a/test/defslookupcheck.sh b/test/defslookupcheck.sh new file mode 100755 index 000000000..8646dc637 --- /dev/null +++ b/test/defslookupcheck.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# defslookupcheck.sh — `--defs=NAME[,NAME…]` (src/defslookup.h rw::lookupDefs) and --path's inline (lane D2 DEFS-LOOKUP). +# +# RED-FIRST (recorded 2026-10-28 against the train-40 base 1318069a): every arm below fails there — the base has no --defs +# flag (rc=1 "unknown flag") and its --path answer carries no ; the (P2) unique-pick negative passes on both. +# Arms: namesakes across files with a test copy and a JSON key (A), a C prototype folded with its definition (B), every +# printed sel= re-resolved by another verb to one definition (C), file:name / Owner.name spellings (D), zero found (E), over +# the 16-row cap with --offset paging (F), several names in one call (G), the JSON twin spelling every row as the XML does (J), +# --path alternatives present only when the pick is not provably unique (P). +# +# Usage: test/defslookupcheck.sh [BIN] (BIN falls back to RIPWIRE_BIN, then build/ripwire). Exits non-zero on any failure. + +set -u +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" +[ "${BIN#/}" = "$BIN" ] && BIN="$ROOT/$BIN" +FIX="$ROOT/test/defslookupfix" +fail=0 +ok(){ printf ' PASS %s\n' "$*"; } +no(){ printf ' FAIL %s\n' "$*"; fail=1; } +[ -x "$BIN" ] || { echo "no ripwire binary at $BIN — build first (cmake --build build -j)"; exit 2; } +echo "defslookupcheck: BIN=$BIN FIX=$FIX" +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT +run(){ ( cd "$FIX" && "$BIN" "$FIX" --no-cache "$@" ); } +# the answer without its legend comments (the compact legend itself spells `` and ``) +body(){ perl -0pe 's///gs' "$1"; } + +echo "-- (A) namesakes: two source methods, one test copy, one JSON key" +run --defs=run --legend=full >"$TMP/a.xml" 2>"$TMP/a.err"; rc=$? +name="$( body "$TMP/a.xml" | grep -o ']*>' )" +[ "$rc" -eq 0 ] && printf '%s' "$name" | grep -q 'defs="3"' && ok "defs=3 (two classes + the test copy)" || no "defs= not 3 (rc=$rc): $name" +printf '%s' "$name" | grep -q 'data_defs="1"' && ok "the JSON key is data_defs=1" || no "no data_defs=1: $name" +grep -q 'lang="json"' "$TMP/a.xml" && no "a JSON key is listed as a definition" || ok "no row is a JSON key" +classes="$( grep -o 'class="[a-z]*"' "$TMP/a.xml" | tr '\n' ' ' )" +[ "$classes" = 'class="source" class="source" class="test" ' ] && ok "source rows before the test row" || no "row order by class wrong: $classes" +xmllint --noout "$TMP/a.xml" 2>/dev/null && ok "--legend=full answer is well-formed XML" || no "--legend=full answer is not well-formed" +grep -q 'sel_proven=' "$TMP/a.xml" && no "a sel= was not proven: $( grep -o ']*>' "$TMP/a.xml" )" || ok "every sel= proven" + +echo "-- (B) a C prototype and its definition are one group" +run --defs=work >"$TMP/b.xml" 2>/dev/null +grep -q '' "$TMP/b.xml" && grep -q 'p="src/work.c:2"' "$TMP/b.xml" && ok "work: one group, the bodied definition" || no "work not one group: $( grep -o ' table)" +n=0; bad=0 +for s in $( grep -o 'sel="[^"]*"' "$TMP/a.xml" "$TMP/b.xml" | sed 's/.*sel="//; s/"$//' ); do + n=$(( n + 1 )) + out="$( run --callers="$s" 2>/dev/null )" + { printf '%s' "$out" | grep -q ']*>' | head -c 200 )"; } +done +[ "$n" -ge 4 ] && [ "$bad" -eq 0 ] && ok "$n sel= spellings each bind one definition group" || no "$bad of $n sel= spellings did not bind one definition group" + +echo "-- (D) spellings the shared resolver accepts" +run --defs=src/b.ts:run >"$TMP/d1.xml" 2>/dev/null +grep -q 'defs="1"' "$TMP/d1.xml" && grep -q 'sel="src/b.ts:run"' "$TMP/d1.xml" && ok "file:name -> one row, sel= as given" || no "file:name: $( grep -o '"$TMP/d2.xml" 2>/dev/null +grep -q 'defs="1"' "$TMP/d2.xml" && grep -q 'n="A.run"' "$TMP/d2.xml" && ok "Owner.name -> one row" || no "Owner.name: $( grep -o '"$TMP/e.xml" 2>"$TMP/e.err"; rc=$? +[ "$rc" -eq 1 ] && grep -q 'found="0"' "$TMP/e.xml" && grep -q 'symbol not found' "$TMP/e.err" && ok "rc=1, found=0, stderr refusal" || no "zero found: rc=$rc $( head -c 300 "$TMP/e.xml" )" + +echo "-- (F) over the cap: 16 rows, defs= the true count, --offset pages" +run --defs=many >"$TMP/f1.xml" 2>/dev/null +rows="$( body "$TMP/f1.xml" | grep -o ']*>' "$TMP/f1.xml" )" +run --defs=many --offset=16 >"$TMP/f2.xml" 2>/dev/null +rows2="$( body "$TMP/f2.xml" | grep -o '"$TMP/f3.xml" 2>/dev/null +grep -q 'defs="18" shown="5" offset="5" next="--defs=many --offset=10 --limit=5"' "$TMP/f3.xml" && [ "$( body "$TMP/f3.xml" | grep -o ']*>' "$TMP/f3.xml" | tail -1 )" +run --defs=many,run --offset=3 >/dev/null 2>"$TMP/f4.err"; rc=$? +[ "$rc" -eq 1 ] && grep -q -- '--offset' "$TMP/f4.err" && ok "--offset beside several names refuses (never ignored)" || no "--offset with several names: rc=$rc" + +echo "-- (G) several names, one call" +run --defs=run,work,zzqdefsnone >"$TMP/g.xml" 2>/dev/null; rc=$? +[ "$rc" -eq 0 ] && [ "$( body "$TMP/g.xml" | grep -o '' "$TMP/g.xml" \ + && ok "3 rows; the missing one defs=0 with its refusal as next=" || no "multi-name: rc=$rc $( grep -o ']*>' "$TMP/g.xml" | tr '\n' ' ' )" + +echo "-- (J) the JSON twin spells every row as the XML does (checklist 41)" +run --defs=run --json >"$TMP/j.json" 2>/dev/null +python3 - "$TMP/j.json" "$TMP/a.xml" <<'PY' && ok "JSON rows equal the XML rows (n, p, sel, class, in)" || no "JSON twin differs from XML" +import json, re, sys +j = json.load( open( sys.argv[1] ) )["names"][0]["rows"] +x = re.findall( r'"$TMP/p1.xml" 2>/dev/null +alts="$( grep -o '' "$TMP/p1.xml" )" +printf '%s' "$alts" | grep -q '^' && [ "$( printf '%s' "$alts" | grep -o ' lists the 2 others (B.run, the test copy), not the bound one" || no "P1: $alts" +run --path=A.run,helperA >"$TMP/p2.xml" 2>/dev/null +grep -q 'reachable="1"' "$TMP/p2.xml" && ! grep -q ' (0 bytes)" || no "P2: $( head -c 400 "$TMP/p2.xml" )" +run --path=many,helperA --legend=full >"$TMP/p3.xml" 2>/dev/null +grep -q '' "$TMP/p3.xml" && xmllint --noout "$TMP/p3.xml" 2>/dev/null \ + && ok "P3: 17 others past the cap: alts_shown=16 defs=18 next=--defs=many, well-formed" || no "P3: $( grep -o ']*>' "$TMP/p3.xml" )" + +[ "$fail" -eq 0 ] && echo "defslookupcheck: OK" || echo "defslookupcheck: FAILURES above" +exit "$fail" diff --git a/test/defslookupfix/config.json b/test/defslookupfix/config.json new file mode 100644 index 000000000..443040900 --- /dev/null +++ b/test/defslookupfix/config.json @@ -0,0 +1 @@ +{ "run": 1 } diff --git a/test/defslookupfix/gen/m01.ts b/test/defslookupfix/gen/m01.ts new file mode 100644 index 000000000..1a74764fb --- /dev/null +++ b/test/defslookupfix/gen/m01.ts @@ -0,0 +1 @@ +export function many(): number { return 1 } diff --git a/test/defslookupfix/gen/m02.ts b/test/defslookupfix/gen/m02.ts new file mode 100644 index 000000000..eee05c983 --- /dev/null +++ b/test/defslookupfix/gen/m02.ts @@ -0,0 +1 @@ +export function many(): number { return 2 } diff --git a/test/defslookupfix/gen/m03.ts b/test/defslookupfix/gen/m03.ts new file mode 100644 index 000000000..d8637c8ed --- /dev/null +++ b/test/defslookupfix/gen/m03.ts @@ -0,0 +1 @@ +export function many(): number { return 3 } diff --git a/test/defslookupfix/gen/m04.ts b/test/defslookupfix/gen/m04.ts new file mode 100644 index 000000000..ff55ed99f --- /dev/null +++ b/test/defslookupfix/gen/m04.ts @@ -0,0 +1 @@ +export function many(): number { return 4 } diff --git a/test/defslookupfix/gen/m05.ts b/test/defslookupfix/gen/m05.ts new file mode 100644 index 000000000..38f4bb42e --- /dev/null +++ b/test/defslookupfix/gen/m05.ts @@ -0,0 +1 @@ +export function many(): number { return 5 } diff --git a/test/defslookupfix/gen/m06.ts b/test/defslookupfix/gen/m06.ts new file mode 100644 index 000000000..0bbb5a9e2 --- /dev/null +++ b/test/defslookupfix/gen/m06.ts @@ -0,0 +1 @@ +export function many(): number { return 6 } diff --git a/test/defslookupfix/gen/m07.ts b/test/defslookupfix/gen/m07.ts new file mode 100644 index 000000000..7d13ad717 --- /dev/null +++ b/test/defslookupfix/gen/m07.ts @@ -0,0 +1 @@ +export function many(): number { return 7 } diff --git a/test/defslookupfix/gen/m08.ts b/test/defslookupfix/gen/m08.ts new file mode 100644 index 000000000..17433c2f3 --- /dev/null +++ b/test/defslookupfix/gen/m08.ts @@ -0,0 +1 @@ +export function many(): number { return 8 } diff --git a/test/defslookupfix/gen/m09.ts b/test/defslookupfix/gen/m09.ts new file mode 100644 index 000000000..b0cf7c184 --- /dev/null +++ b/test/defslookupfix/gen/m09.ts @@ -0,0 +1 @@ +export function many(): number { return 9 } diff --git a/test/defslookupfix/gen/m10.ts b/test/defslookupfix/gen/m10.ts new file mode 100644 index 000000000..fa80a9d31 --- /dev/null +++ b/test/defslookupfix/gen/m10.ts @@ -0,0 +1 @@ +export function many(): number { return 10 } diff --git a/test/defslookupfix/gen/m11.ts b/test/defslookupfix/gen/m11.ts new file mode 100644 index 000000000..e99dcf4e0 --- /dev/null +++ b/test/defslookupfix/gen/m11.ts @@ -0,0 +1 @@ +export function many(): number { return 11 } diff --git a/test/defslookupfix/gen/m12.ts b/test/defslookupfix/gen/m12.ts new file mode 100644 index 000000000..98b3f60c3 --- /dev/null +++ b/test/defslookupfix/gen/m12.ts @@ -0,0 +1 @@ +export function many(): number { return 12 } diff --git a/test/defslookupfix/gen/m13.ts b/test/defslookupfix/gen/m13.ts new file mode 100644 index 000000000..513f33b68 --- /dev/null +++ b/test/defslookupfix/gen/m13.ts @@ -0,0 +1 @@ +export function many(): number { return 13 } diff --git a/test/defslookupfix/gen/m14.ts b/test/defslookupfix/gen/m14.ts new file mode 100644 index 000000000..4c3c0d613 --- /dev/null +++ b/test/defslookupfix/gen/m14.ts @@ -0,0 +1 @@ +export function many(): number { return 14 } diff --git a/test/defslookupfix/gen/m15.ts b/test/defslookupfix/gen/m15.ts new file mode 100644 index 000000000..35872c983 --- /dev/null +++ b/test/defslookupfix/gen/m15.ts @@ -0,0 +1 @@ +export function many(): number { return 15 } diff --git a/test/defslookupfix/gen/m16.ts b/test/defslookupfix/gen/m16.ts new file mode 100644 index 000000000..ac4450ce4 --- /dev/null +++ b/test/defslookupfix/gen/m16.ts @@ -0,0 +1 @@ +export function many(): number { return 16 } diff --git a/test/defslookupfix/gen/m17.ts b/test/defslookupfix/gen/m17.ts new file mode 100644 index 000000000..aca02c730 --- /dev/null +++ b/test/defslookupfix/gen/m17.ts @@ -0,0 +1 @@ +export function many(): number { return 17 } diff --git a/test/defslookupfix/gen/m18.ts b/test/defslookupfix/gen/m18.ts new file mode 100644 index 000000000..006c53c08 --- /dev/null +++ b/test/defslookupfix/gen/m18.ts @@ -0,0 +1 @@ +export function many(): number { return 18 } diff --git a/test/defslookupfix/src/a.ts b/test/defslookupfix/src/a.ts new file mode 100644 index 000000000..155594fae --- /dev/null +++ b/test/defslookupfix/src/a.ts @@ -0,0 +1,5 @@ +export class A { + run(): number { return helperA() } +} +export function helperA(): number { return 1 } +export function callsA(a: A): number { return a.run() } diff --git a/test/defslookupfix/src/b.ts b/test/defslookupfix/src/b.ts new file mode 100644 index 000000000..a64209691 --- /dev/null +++ b/test/defslookupfix/src/b.ts @@ -0,0 +1,3 @@ +export class B { + run(): number { return 2 } +} diff --git a/test/defslookupfix/src/work.c b/test/defslookupfix/src/work.c new file mode 100644 index 000000000..0f93fe2f4 --- /dev/null +++ b/test/defslookupfix/src/work.c @@ -0,0 +1,2 @@ +#include "work.h" +void work( int x ) { (void)x; } diff --git a/test/defslookupfix/src/work.h b/test/defslookupfix/src/work.h new file mode 100644 index 000000000..39bcd9166 --- /dev/null +++ b/test/defslookupfix/src/work.h @@ -0,0 +1 @@ +void work( int x ); diff --git a/test/defslookupfix/test/run.test.ts b/test/defslookupfix/test/run.test.ts new file mode 100644 index 000000000..7efb53c94 --- /dev/null +++ b/test/defslookupfix/test/run.test.ts @@ -0,0 +1 @@ +export function run(): number { return 3 } diff --git a/test/regression.sh b/test/regression.sh index fb40292ad..f095903e2 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 composedhopscheck 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 epmanifestcheck 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 fnslotcheck forautobodycheck forblowupcheck forbodyfoldcheck forbodyslotcheck forbudgetmonotoncheck forcalibfactscheck forcallerhopcheck forcompresscheck forctorhopcheck fordisclosurecheck forhdrshapecheck forheadrrcheck forinitslotscheck forlenscheck formatgatecheck formaxtokenscheck formembernamescheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forroutecheck 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 jsdefnamingcheck 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 mapstemcheck 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 namekeyhopcheck 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 pathpaircheck 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 pyaliascallcheck 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 queuehopcheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck receiverfixescheck 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 testonlydeadcheck 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 writtenspellingcheck 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 composedhopscheck 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 defslookupcheck 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 epmanifestcheck 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 fnslotcheck forautobodycheck forblowupcheck forbodyfoldcheck forbodyslotcheck forbudgetmonotoncheck forcalibfactscheck forcallerhopcheck forcompresscheck forctorhopcheck fordisclosurecheck forhdrshapecheck forheadrrcheck forinitslotscheck forlenscheck formatgatecheck formaxtokenscheck formembernamescheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forroutecheck 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 jsdefnamingcheck 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 mapstemcheck 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 namekeyhopcheck 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 pathpaircheck 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 pyaliascallcheck 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 queuehopcheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck receiverfixescheck 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 testonlydeadcheck 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 writtenspellingcheck 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)" From b2be608ab89576f804eb2475a4facb70bec2a6f8 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 01:38:54 -0400 Subject: [PATCH 27/95] fix: handlerrule keeps the Lang ratchet (Python family helper), byte comparators on string searches, LIMITS/TUNING regenerated CI 40630779403 reds: langidentity ratchet 339 > 333, portablebuildcheck #6 (ordered search over string_view with the default comparator), LIMITS.md and TUNING.md stale after the three new kHandlerRule* caps. Co-Authored-By: Claude Opus 5.5 --- docs/LIMITS.md | 12 +++++++----- docs/TUNING.md | 18 +++++++++--------- src/handlerrule.h | 20 ++++++++++++-------- src/pathgaps.h | 4 ++-- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/docs/LIMITS.md b/docs/LIMITS.md index 1470fb7b8..4590fda5f 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 | | --- | --- | --- | --- | -| 287 | 113 | 170 | **117** | +| 289 | 113 | 172 | **117** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 287 + 7 is the 294 constants this generator parses out of `src/`. +are not counted as caps, and 289 + 7 is the 296 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 287 caps are classified -(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 147 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **140 of 289 caps are classified +(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 149 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 113 files that declare a cap — the 287 caps counted above, and no parameter. +One table for each of the 113 files that declare a cap — the 289 caps counted above, and no parameter. ### `src/abicheck.h` @@ -868,6 +868,8 @@ Discloses: `gap_syms_capped`, `rows_capped` | constant | value | class | note | | --- | --- | --- | --- | | `kBridgeMaxHops` | `8` | — | a longer chain is no answer a reader would follow: none is printed | +| `kHandlerRuleListMax` | `16` | — | the bridge's own width limit: at most this many defs are listed as | +| `kHandlerRuleShown` | `3` | — | rows: the widest posted classes first | | `kNearSinkDepth` | `2` | — | proven caller hops back from to= (1 = a direct caller) | | `kNearSinkRows` | `4` | — | rows printed; rows= counts them all, rows_capped="1" says so | | `kPathGapRows` | `3` | — | rows printed; gap_syms= counts them all, gap_syms_capped="1" says so | diff --git a/docs/TUNING.md b/docs/TUNING.md index 4b8df8701..4124a83dc 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 | | --- | --- | --- | --- | --- | --- | -| 195 | 194 | 112 | 12 | **37** | 75 | +| 197 | 196 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **195 cap -declarations** under **194 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 **197 cap +declarations** under **196 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 194 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **71 declared since the sweep was +every run, which makes them a third population again. Enumerated over the 196 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **73 declared since the sweep was prepared, which no measurement has touched** (`kBackHopWidth`, `kBaseScopeScanCap`, -`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +65 -more). 111 + 12 + 71 = 194, and `emit` refuses to render a partition that does not add up. The +`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +67 +more). 111 + 12 + 73 = 196, 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 195" would be wrong in both halves +declared in `src/` at all: `kSituTestRowsShown`. Quoting "113 of 197" 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 195-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 197-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/src/handlerrule.h b/src/handlerrule.h index ba6401435..2133892f0 100644 --- a/src/handlerrule.h +++ b/src/handlerrule.h @@ -28,6 +28,7 @@ #include "model.h" #include "resolve.h" // trimWs #include "infra/namesplit.h" // isIdentChar +#include "infra/sortutil.h" // svLess — the explicit byte order for string searches (portablebuildcheck #6) #include "valuerefindex.h" // ClassFamilies — owner class and written-base family #include @@ -100,6 +101,9 @@ inline std::vector mergedSiteLists( std::vector si class HandlerRules { public: + // The rule is a Python construct: the Python value-ref family is the one language it reads. + static bool isPython( Lang l ) noexcept { return valueRefFamily( l ) == ValueRefFamily::Py; } + explicit HandlerRules( const IngestResult& ing ) : m_ing( ing ), m_classes( ing ) {} // Built on first use; a tree with no Python __init_subclass__ write pays one reference scan. @@ -128,7 +132,7 @@ class HandlerRules } for( const HandlerPostSite& s : m_sites ) { - if( m_ing.references[ s.ref ].fromSymbol != d && std::ranges::binary_search( s.names, std::string_view( m_ing.symbols[d].name ) ) ) + if( m_ing.references[ s.ref ].fromSymbol != d && std::ranges::binary_search( s.names, std::string_view( m_ing.symbols[d].name ), sortutil::svLess ) ) { out.push_back( s.ref ); } @@ -251,7 +255,7 @@ class HandlerRules } for( const Symbol& f : m_ing.symbols ) { - if( f.name == r.calleeName && f.lang == Lang::Python && f.kind == SymKind::Function ) + if( f.name == r.calleeName && isPython( f.lang ) && f.kind == SymKind::Function ) { const std::string_view body = span( f ); if( body.find( "[a-z][A-Z]" ) != std::string_view::npos && body.find( ".lower()" ) != std::string_view::npos ) @@ -405,7 +409,7 @@ class HandlerRules { for( const Symbol& w : m_ing.symbols ) { - if( w.lang != Lang::Python || w.name != "__init_subclass__" || ( w.kind != SymKind::Function && w.kind != SymKind::Method ) ) + if( !isPython( w.lang ) || w.name != "__init_subclass__" || ( w.kind != SymKind::Function && w.kind != SymKind::Method ) ) { continue; } @@ -429,12 +433,12 @@ class HandlerRules for( const Symbol& s : m_ing.symbols ) { const bool fn = s.kind == SymKind::Function || s.kind == SymKind::Method; - if( s.lang == Lang::Python && fn && m_classes.ownerOf( s.id ) != kNoNode ) + if( isPython( s.lang ) && fn && m_classes.ownerOf( s.id ) != kNoNode ) { m_inClass[ s.id ] = 1; m_methodsByName[ std::string_view( s.name ) ].push_back( s.id ); } - else if( s.lang == Lang::Python && s.kind == SymKind::Class && !handlerNameOf( s.id ).empty() ) + else if( isPython( s.lang ) && s.kind == SymKind::Class && !handlerNameOf( s.id ).empty() ) { m_messagesByName[ std::string_view( s.name ) ].push_back( s.id ); } @@ -455,7 +459,7 @@ class HandlerRules m_fnsOfFile.assign( m_ing.files.size(), {} ); for( const Symbol& s : m_ing.symbols ) { - if( s.lang == Lang::Python && ( s.kind == SymKind::Function || s.kind == SymKind::Method ) ) + if( isPython( s.lang ) && ( s.kind == SymKind::Function || s.kind == SymKind::Method ) ) { m_fnsOfFile[ s.fileId ].push_back( s.id ); } @@ -526,7 +530,7 @@ class HandlerRules for( std::uint32_t i = 0; i < m_ing.references.size(); ++i ) { const Reference& r = m_ing.references[i]; - if( r.role != RefRole::Call || r.lang != Lang::Python || r.fromSymbol >= m_ing.symbols.size() || !posters.contains( std::string_view( r.calleeName ) ) ) + if( r.role != RefRole::Call || !isPython( r.lang ) || r.fromSymbol >= m_ing.symbols.size() || !posters.contains( std::string_view( r.calleeName ) ) ) { continue; } @@ -536,7 +540,7 @@ class HandlerRules { site.names.push_back( handlerNameOf( c ) ); } - std::ranges::sort( site.names ); + std::ranges::sort( site.names, sortutil::svLess ); site.names.erase( std::ranges::unique( site.names ).begin(), site.names.end() ); if( !site.names.empty() ) { diff --git a/src/pathgaps.h b/src/pathgaps.h index bcee32a51..605085bc3 100644 --- a/src/pathgaps.h +++ b/src/pathgaps.h @@ -1618,11 +1618,11 @@ inline PathHandlerRule pathHandlerRule( const IngestResult& ing, const NameKeyHo { return out; } - std::ranges::sort( hit ); + std::ranges::sort( hit, sortutil::svLess ); hit.erase( std::ranges::unique( hit ).begin(), hit.end() ); for( const NodeId d : dstDefs ) { - if( std::ranges::binary_search( hit, ing.symbols[d].name ) && hr.classes().ownerOf( d ) != kNoNode ) + if( std::ranges::binary_search( hit, std::string_view( ing.symbols[d].name ), sortutil::svLess ) && hr.classes().ownerOf( d ) != kNoNode ) { ++out.defs; out.listed.push_back( d ); From e5fe98bb8a2955c8a14cf2307adce282b5bc8c8b Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 01:46:41 -0400 Subject: [PATCH 28/95] defs: gate verdicts as if/else, README flag count 193, LIMITS regen, printf parity re-pin gateexitcheck G1/G2 on defslookupcheck.sh; readmedriftcheck (B)/(L1). RE-PIN LOG printf_parity.manifest: moved exactly help, help_all (the --defs help lines) and uses_json, grep_json (the --json refusal sentence now names --defs); 38 labels unchanged. Co-Authored-By: Claude Opus 5.5 --- README.md | 6 ++--- docs/LIMITS.md | 18 +++++++++---- test/defslookupcheck.sh | 51 ++++++++++++++++++------------------- test/printf_parity.manifest | 8 +++--- 4 files changed, 45 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 54a09d652..7f4ae42c3 100644 --- a/README.md +++ b/README.md @@ -982,9 +982,9 @@ Full retrieval tables — including the MRR figures behind the router numbers ab ## What it answers
-192 long flags across seven families, plus the MCP server — and --help-task names the ONE command a task wants, or abstains honestly when the evidence is too thin +193 long flags across seven families, plus the MCP server — and --help-task names the ONE command a task wants, or abstains honestly when the evidence is too thin -Around the core sit 192 long flags advertised in `--help`, across seven families — plus an MCP +Around the core sit 193 long flags advertised in `--help`, across seven families — plus an MCP server, so a coding agent can call any of them mid-task instead of grepping and reading whole files. `--help` prints one line per flag (~4.5K tokens); `--help=--FLAG` prints that flag's full entry with every caveat, `--help=SECTION` one family, and `--help=all` the whole catalog. @@ -2684,7 +2684,7 @@ identity of the index, and says which one is at fault. ### 5. Command families -The `--help` output groups 192 long flags advertised in `--help` into seven families. The `--help=` +The `--help` output groups 193 long flags advertised in `--help` into seven families. The `--help=` column below is the argument that prints one family: `ripwire --help=navigate`. `ripwire --help=--FLAG` prints one flag's full text — the caveats, the units, what it refuses and why. `ripwire --help=all` is the whole catalog, about 46,000 tokens. diff --git a/docs/LIMITS.md b/docs/LIMITS.md index 1470fb7b8..120c48d41 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 | | --- | --- | --- | --- | -| 287 | 113 | 170 | **117** | +| 288 | 114 | 170 | **118** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 287 + 7 is the 294 constants this generator parses out of `src/`. +are not counted as caps, and 288 + 7 is the 295 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 287 caps are classified -(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 147 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **140 of 288 caps are classified +(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 148 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 113 files that declare a cap — the 287 caps counted above, and no parameter. +One table for each of the 114 files that declare a cap — the 288 caps counted above, and no parameter. ### `src/abicheck.h` @@ -252,6 +252,14 @@ Discloses: **none** | --- | --- | --- | --- | | `kDefTableCap` | `16` | — | — | +### `src/defslookup.h` + +Discloses: **none** + +| constant | value | class | note | +| --- | --- | --- | --- | +| `kDefsNamesMax` | `16` | — | — | + ### `src/didyoumean.h` Discloses: **none** diff --git a/test/defslookupcheck.sh b/test/defslookupcheck.sh index 8646dc637..3c9379349 100755 --- a/test/defslookupcheck.sh +++ b/test/defslookupcheck.sh @@ -2,7 +2,9 @@ # defslookupcheck.sh — `--defs=NAME[,NAME…]` (src/defslookup.h rw::lookupDefs) and --path's inline (lane D2 DEFS-LOOKUP). # # RED-FIRST (recorded 2026-10-28 against the train-40 base 1318069a): every arm below fails there — the base has no --defs -# flag (rc=1 "unknown flag") and its --path answer carries no ; the (P2) unique-pick negative passes on both. +# flag (18 FAIL) and its --path answer carries no . Three NEGATIVES pass vacuously there (no row is a JSON key, every +# sel= proven, P2 no ); each was proven able to fail by a mutant on f966224b: the data-Section filter dropped (6 FAIL), +# picksAlone always false (4 FAIL), defPickProvablyUnique false at one group (P2 FAIL). # Arms: namesakes across files with a test copy and a JSON key (A), a C prototype folded with its definition (B), every # printed sel= re-resolved by another verb to one definition (C), file:name / Owner.name spellings (D), zero found (E), over # the 16-row cap with --offset paging (F), several names in one call (G), the JSON twin spelling every row as the XML does (J), @@ -16,7 +18,7 @@ BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" [ "${BIN#/}" = "$BIN" ] && BIN="$ROOT/$BIN" FIX="$ROOT/test/defslookupfix" fail=0 -ok(){ printf ' PASS %s\n' "$*"; } +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 (cmake --build build -j)"; exit 2; } echo "defslookupcheck: BIN=$BIN FIX=$FIX" @@ -28,17 +30,17 @@ body(){ perl -0pe 's///gs' "$1"; } echo "-- (A) namesakes: two source methods, one test copy, one JSON key" run --defs=run --legend=full >"$TMP/a.xml" 2>"$TMP/a.err"; rc=$? name="$( body "$TMP/a.xml" | grep -o ']*>' )" -[ "$rc" -eq 0 ] && printf '%s' "$name" | grep -q 'defs="3"' && ok "defs=3 (two classes + the test copy)" || no "defs= not 3 (rc=$rc): $name" -printf '%s' "$name" | grep -q 'data_defs="1"' && ok "the JSON key is data_defs=1" || no "no data_defs=1: $name" +if [ "$rc" -eq 0 ] && printf '%s' "$name" | grep -q 'defs="3"'; then ok "defs=3 (two classes + the test copy)"; else no "defs= not 3 (rc=$rc): $name"; fi +if printf '%s' "$name" | grep -q 'data_defs="1"'; then ok "the JSON key is data_defs=1"; else no "no data_defs=1: $name"; fi grep -q 'lang="json"' "$TMP/a.xml" && no "a JSON key is listed as a definition" || ok "no row is a JSON key" classes="$( grep -o 'class="[a-z]*"' "$TMP/a.xml" | tr '\n' ' ' )" -[ "$classes" = 'class="source" class="source" class="test" ' ] && ok "source rows before the test row" || no "row order by class wrong: $classes" -xmllint --noout "$TMP/a.xml" 2>/dev/null && ok "--legend=full answer is well-formed XML" || no "--legend=full answer is not well-formed" +if [ "$classes" = 'class="source" class="source" class="test" ' ]; then ok "source rows before the test row"; else no "row order by class wrong: $classes"; fi +if xmllint --noout "$TMP/a.xml" 2>/dev/null; then ok "--legend=full answer is well-formed XML"; else no "--legend=full answer is not well-formed"; fi grep -q 'sel_proven=' "$TMP/a.xml" && no "a sel= was not proven: $( grep -o ']*>' "$TMP/a.xml" )" || ok "every sel= proven" echo "-- (B) a C prototype and its definition are one group" run --defs=work >"$TMP/b.xml" 2>/dev/null -grep -q '' "$TMP/b.xml" && grep -q 'p="src/work.c:2"' "$TMP/b.xml" && ok "work: one group, the bodied definition" || no "work not one group: $( grep -o '' "$TMP/b.xml" && grep -q 'p="src/work.c:2"' "$TMP/b.xml"; then ok "work: one group, the bodied definition"; else no "work not one group: $( grep -o ' table)" n=0; bad=0 @@ -47,58 +49,55 @@ for s in $( grep -o 'sel="[^"]*"' "$TMP/a.xml" "$TMP/b.xml" | sed 's/.*sel="//; out="$( run --callers="$s" 2>/dev/null )" { printf '%s' "$out" | grep -q ']*>' | head -c 200 )"; } done -[ "$n" -ge 4 ] && [ "$bad" -eq 0 ] && ok "$n sel= spellings each bind one definition group" || no "$bad of $n sel= spellings did not bind one definition group" +if [ "$n" -ge 4 ] && [ "$bad" -eq 0 ]; then ok "$n sel= spellings each bind one definition group"; else no "$bad of $n sel= spellings did not bind one definition group"; fi echo "-- (D) spellings the shared resolver accepts" run --defs=src/b.ts:run >"$TMP/d1.xml" 2>/dev/null -grep -q 'defs="1"' "$TMP/d1.xml" && grep -q 'sel="src/b.ts:run"' "$TMP/d1.xml" && ok "file:name -> one row, sel= as given" || no "file:name: $( grep -o ' one row, sel= as given"; else no "file:name: $( grep -o '"$TMP/d2.xml" 2>/dev/null -grep -q 'defs="1"' "$TMP/d2.xml" && grep -q 'n="A.run"' "$TMP/d2.xml" && ok "Owner.name -> one row" || no "Owner.name: $( grep -o ' one row"; else no "Owner.name: $( grep -o '"$TMP/e.xml" 2>"$TMP/e.err"; rc=$? -[ "$rc" -eq 1 ] && grep -q 'found="0"' "$TMP/e.xml" && grep -q 'symbol not found' "$TMP/e.err" && ok "rc=1, found=0, stderr refusal" || no "zero found: rc=$rc $( head -c 300 "$TMP/e.xml" )" +if [ "$rc" -eq 1 ] && grep -q 'found="0"' "$TMP/e.xml" && grep -q 'symbol not found' "$TMP/e.err"; then ok "rc=1, found=0, stderr refusal"; else no "zero found: rc=$rc $( head -c 300 "$TMP/e.xml" )"; fi echo "-- (F) over the cap: 16 rows, defs= the true count, --offset pages" run --defs=many >"$TMP/f1.xml" 2>/dev/null rows="$( body "$TMP/f1.xml" | grep -o ']*>' "$TMP/f1.xml" )" +if grep -q 'defs="18" shown="16" offset="0" next="--defs=many --offset=16"' "$TMP/f1.xml" && [ "$rows" = 16 ]; then ok "page 1: 16 rows of 18, next= names page 2"; else no "page 1: rows=$rows $( grep -o ']*>' "$TMP/f1.xml" )"; fi run --defs=many --offset=16 >"$TMP/f2.xml" 2>/dev/null rows2="$( body "$TMP/f2.xml" | grep -o '"$TMP/f3.xml" 2>/dev/null -grep -q 'defs="18" shown="5" offset="5" next="--defs=many --offset=10 --limit=5"' "$TMP/f3.xml" && [ "$( body "$TMP/f3.xml" | grep -o ']*>' "$TMP/f3.xml" | tail -1 )" +if grep -q 'defs="18" shown="5" offset="5" next="--defs=many --offset=10 --limit=5"' "$TMP/f3.xml" && [ "$( body "$TMP/f3.xml" | grep -o ']*>' "$TMP/f3.xml" | tail -1 )"; fi run --defs=many,run --offset=3 >/dev/null 2>"$TMP/f4.err"; rc=$? -[ "$rc" -eq 1 ] && grep -q -- '--offset' "$TMP/f4.err" && ok "--offset beside several names refuses (never ignored)" || no "--offset with several names: rc=$rc" +if [ "$rc" -eq 1 ] && grep -q -- '--offset' "$TMP/f4.err"; then ok "--offset beside several names refuses (never ignored)"; else no "--offset with several names: rc=$rc"; fi echo "-- (G) several names, one call" run --defs=run,work,zzqdefsnone >"$TMP/g.xml" 2>/dev/null; rc=$? -[ "$rc" -eq 0 ] && [ "$( body "$TMP/g.xml" | grep -o '' "$TMP/g.xml" \ - && ok "3 rows; the missing one defs=0 with its refusal as next=" || no "multi-name: rc=$rc $( grep -o ']*>' "$TMP/g.xml" | tr '\n' ' ' )" +if [ "$rc" -eq 0 ] && [ "$( body "$TMP/g.xml" | grep -o '' "$TMP/g.xml"; then ok "3 rows; the missing one defs=0 with its refusal as next="; else no "multi-name: rc=$rc $( grep -o ']*>' "$TMP/g.xml" | tr '\n' ' ' )"; fi echo "-- (J) the JSON twin spells every row as the XML does (checklist 41)" run --defs=run --json >"$TMP/j.json" 2>/dev/null -python3 - "$TMP/j.json" "$TMP/a.xml" <<'PY' && ok "JSON rows equal the XML rows (n, p, sel, class, in)" || no "JSON twin differs from XML" +python3 - "$TMP/j.json" "$TMP/a.xml" <<'PY' import json, re, sys j = json.load( open( sys.argv[1] ) )["names"][0]["rows"] x = re.findall( r'"$TMP/p1.xml" 2>/dev/null alts="$( grep -o '' "$TMP/p1.xml" )" -printf '%s' "$alts" | grep -q '^' && [ "$( printf '%s' "$alts" | grep -o ' lists the 2 others (B.run, the test copy), not the bound one" || no "P1: $alts" +if printf '%s' "$alts" | grep -q '^' && [ "$( printf '%s' "$alts" | grep -o ' lists the 2 others (B.run, the test copy), not the bound one"; else no "P1: $alts"; fi run --path=A.run,helperA >"$TMP/p2.xml" 2>/dev/null -grep -q 'reachable="1"' "$TMP/p2.xml" && ! grep -q ' (0 bytes)" || no "P2: $( head -c 400 "$TMP/p2.xml" )" +if grep -q 'reachable="1"' "$TMP/p2.xml" && ! grep -q ' (0 bytes)"; else no "P2: $( head -c 400 "$TMP/p2.xml" )"; fi run --path=many,helperA --legend=full >"$TMP/p3.xml" 2>/dev/null -grep -q '' "$TMP/p3.xml" && xmllint --noout "$TMP/p3.xml" 2>/dev/null \ - && ok "P3: 17 others past the cap: alts_shown=16 defs=18 next=--defs=many, well-formed" || no "P3: $( grep -o ']*>' "$TMP/p3.xml" )" +if grep -q '' "$TMP/p3.xml" && xmllint --noout "$TMP/p3.xml" 2>/dev/null; then ok "P3: 17 others past the cap: alts_shown=16 defs=18 next=--defs=many, well-formed"; else no "P3: $( grep -o ']*>' "$TMP/p3.xml" )"; fi [ "$fail" -eq 0 ] && echo "defslookupcheck: OK" || echo "defslookupcheck: FAILURES above" exit "$fail" diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index 540355e21..03463f499 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -9,7 +9,7 @@ pattern 0 b7a2467faab2b69e4fe8e3d8204f158329f6b349d644e5681af860ce6f8ba6ca e3b0c callers 0 1dd0a41c39f89e4b35fb40750a4b2fd56376daf2161d24b03d32423d788b352d e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 impact 0 c48ba09589d13cad6011bd6fadbb2898d68e2f5bd97a10bf44324901cfa08d49 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 clones 0 a082f92ce84cd952ced8341e3faff33a23685729bfa4ab6f0eda0f7867e41d7c e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help 0 ecc9e1ea480e502fb228fb129f1309e37e30918d8baf3f36e9b9f01be0277d59 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help 0 8b4e21e4ea09ab3316a40a16ddaa689942d3e66a1fe62dcc6e2f2fb8d3317469 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 expand 0 0cca7164a48d6543694bf07b4252209558d189771d50e913e46783bb98ae4bed e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 callees 0 cbd133fa53917bad05926d46615d4d8b4457ba9285513dc4b193fa57a1ef8b85 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 around 0 47510713fadffc2c6f8745bc4dbb895bedf00062bd27deb026709c72605f0d61 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 @@ -30,13 +30,13 @@ bad_flag 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 18c3 callers_json 0 b4cb44825705aaef95881ec889ba75da78fc3f18ba9a6b8c5e4dfec7122de4aa e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 callees_json 0 da139bc671d2f0676022a938cee69537c4f9282951fa6f487fdae19f7d7cd65f e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 impact_json 0 c9cf3824750b16b493eb177644306987b7420db4ae37f07be18e9fa088574b8b e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -uses_json 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 96ca20ae3803368f04315f1f2f0062893e7827cee740fb44e04278ff5f32e791 -grep_json 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 278827e2a7740aeeefa653b87ec47f74c98ec97a1758ce2e5426855a845a80c4 +uses_json 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 367ea0426632880940ea61ba78927234e09539dcc9076b04d20d9b37272abe77 +grep_json 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5f71cf9056a7cf72301ae4f9f577509330a04a4e9129d84cb804111f8f625af4 expand_missing 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 10f2dda880ec34c8d5a3f5bc50bd1bf50d0abe20c308de11ca8a16714f87b496 callers_missing 1 39a4cf4b2702863152edcc6c270255bac015a17d25e834dbdff181d809181326 0770e05552f4baf1c25d213e35c5f273c6f341e3c518e8233a83ff849abcd1c9 safe_delete 0 16990750bd7c003d73fa9c4ebb85ca8a9a3e6ffe8bf8f9987ed47f5459232403 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 verify_layer 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5288c345d7d6e335f88b9c1daa8935db22e1dcf89c0c8bc1f6140d4cb5af0b48 graph_query 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 bfa4319feb9dee09cfbd3991cf6fbd752297e99d14de9e75768820d2a9c8832f callers_limit 0 98a50bcc6b32183d7967ee6af8f9219f083c00bde882f3aff4ebe1f39f4166e4 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help_all 0 c297d80e427d8b1a709ed3df1164ce411c21d4295a9521162c1163295035b7c6 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help_all 0 ede426d1e2c6afc384c7cfd7115043587e485ee91ee363c120bd7fac7af82f47 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help_one 0 938ff4f8802a8f5d9cb10f9a1f3804ac755be44c8ab00e24e45bfe8255224a21 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 From 00d4c21d3580924d27e169ecc12621eaa79ffd9c Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 01:51:10 -0400 Subject: [PATCH 29/95] docs: README cap inventory 287 -> 289 (the kHandlerRule* caps) Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54a09d652..8abc456d1 100644 --- a/README.md +++ b/README.md @@ -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 287 compile-time caps and 7 ranking parameters. +Another gate derives the cap inventory. The tool has 289 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. From ce1ad8799e18b29fa7e55a248ee9d7d16b4478dd Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:20:03 -0400 Subject: [PATCH 30/95] compactlegendcheck: re-pin ripwire.defs/v1 to 490 on 472 B (the not-found readings the first pin predated) Co-Authored-By: Claude Opus 5.5 --- test/compactlegendcheck.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/compactlegendcheck.sh b/test/compactlegendcheck.sh index 933854403..cf572375d 100755 --- a/test/compactlegendcheck.sh +++ b/test/compactlegendcheck.sh @@ -739,10 +739,11 @@ ripwire.pack-top-n/v1 830 813 ripwire.expand/v1 280 265 ripwire.expand-file/v1 510 508 ripwire.for/v1 1100 1099 -ripwire.defs/v1 340 316 +ripwire.defs/v1 490 472 ' -# PINNED 2026-10-27 (lane/defs-lookup): ripwire.defs/v1 316 B measured on this gate's --defs=zzqq9 probe (the shared not-found -# answer's readings: found=, of=, near=), pin 340. +# PINNED 2026-10-27 (lane/defs-lookup): ripwire.defs/v1 on this gate's --defs=zzqq9 probe. First pin 340 on 316 B, measured BEFORE +# the not-found readings existed; RE-PINNED 2026-10-28 to 490 on 472 B = the verb's purpose line (~290 B) plus the shared not-found +# answer's found=/of=/near= readings (compactlegendcheck UG required them). Present-only: none of the defs row readings ride here. pinFor() { printf '%s\n' "$PIN_TABLE" | awk -v schema="$1" '$1 == schema { printf "%s", $2; exit }' From 066388d8355b081c5ac05e09ca9da5624e1dd6f4 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:31:08 -0400 Subject: [PATCH 31/95] docs: TUNING.md regen (capsweepcheck C: one more cap declaration, kDefsNamesMax) Co-Authored-By: Claude Opus 5.5 --- docs/TUNING.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/TUNING.md b/docs/TUNING.md index 4b8df8701..685c1efa5 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 | | --- | --- | --- | --- | --- | --- | -| 195 | 194 | 112 | 12 | **37** | 75 | +| 196 | 195 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **195 cap -declarations** under **194 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 **196 cap +declarations** under **195 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 194 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **71 declared since the sweep was +every run, which makes them a third population again. Enumerated over the 195 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **72 declared since the sweep was prepared, which no measurement has touched** (`kBackHopWidth`, `kBaseScopeScanCap`, -`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +65 -more). 111 + 12 + 71 = 194, and `emit` refuses to render a partition that does not add up. The +`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +66 +more). 111 + 12 + 72 = 195, 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 195" would be wrong in both halves +declared in `src/` at all: `kSituTestRowsShown`. Quoting "113 of 196" 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 195-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 196-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. From de9d903b2fd847266c9a091d94c41e7b91b58e47 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:37:57 -0400 Subject: [PATCH 32/95] defs: compact readings keyed on real attributes (side=, alts_shown=); --defs in the navigate skill and the showcase CI on 2e9eeabe: legendrefcheck (A) 'alts' is no attribute; agentsurfacecheck / skillinstallcheck: --defs had no skill home; showcasecapturecheck (D) --defs uncovered. Co-Authored-By: Claude Opus 5.5 --- skills/ripwire-navigate/SKILL.md | 1 + src/compactlegend.h | 3 ++- test/showcase_capture.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/skills/ripwire-navigate/SKILL.md b/skills/ripwire-navigate/SKILL.md index 1bca667cb..dbb569db3 100644 --- a/skills/ripwire-navigate/SKILL.md +++ b/skills/ripwire-navigate/SKILL.md @@ -112,6 +112,7 @@ two hops away, a read/write that never calls SYM, or an `#include` that pulls it - **Transitive blast radius** — `ripwire --impact=SYM --legend=compact` - **Neighborhood** (bounded k-hop ego graph) — `ripwire --around=SYM --legend=compact [--around-depth=2] [--around-fanout=32]` - **How does X reach Y** (shortest call-path) — `ripwire --path=SRC,DST --legend=compact` +- **Which definitions does a name bind** (each with `sel=`, the spelling that picks it alone in any verb above) — `ripwire --defs=NAME[,NAME…] --legend=compact` - **How do N symbols relate** (minimal connecting subgraph, shared-caller joins) — `ripwire --connect=A,B,C --legend=compact [--connect-radius=N]` - **Verify a CLAIM in one call** — "does X really call Y?", "is Z ever used?", "does this file contain/define A?" → `ripwire --verify='calls(A,B)' --legend=compact` (also `uses(SYM)` / `unused(SYM)` / `contains(FILE, "LIT")` / diff --git a/src/compactlegend.h b/src/compactlegend.h index b2b3bb693..cbe2363cd 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -837,7 +837,8 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "unproven_defs", "name unproven_defs=K: same-named definitions a file:name spelling could not tie to that file; not in defs=", true, "name", MapHeaderRead::No, {}, "defs" }, { "shown", "name shown=K offset=M: rows M..M+K of defs=; next= pages the rest", true, "name", MapHeaderRead::No, {}, "defs" }, // also defines offset= { "next", "name next=: the next page, or (defs=0) the single-name call whose refusal names near names", true, "name", MapHeaderRead::No, {}, "defs" }, - { "alts", ": the OTHER definitions that endpoint's name binds (the pick is not provably unique): , sel= pastes as that endpoint; alts_shown=K defs=N next= pages the rest", true, {}, MapHeaderRead::No, {}, "path" }, + { "side", "alts side=from|to of=: the OTHER definitions that endpoint's name binds (the pick is not provably unique): , sel= pastes as that endpoint", true, "alts", MapHeaderRead::No, {}, "path" }, + { "alts_shown", "alts alts_shown=K defs=N: K of the other rows shown; next= pages the rest", true, "alts", MapHeaderRead::No, {}, "path" }, // also defines defs= there { "from_p", "from_p=/to_p=: the definitions from= and to= were bound to", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_p= { "from_defs", "from_defs=/to_defs=: definitions of each name, all searched; above 1, qualify file:name", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_defs= // TRACE-EP (src/pathpairs.h kPathPairsLegend), present only when several definition pairs reach: chains and rows diff --git a/test/showcase_capture.py b/test/showcase_capture.py index 5a49f0e10..36415e3d4 100644 --- a/test/showcase_capture.py +++ b/test/showcase_capture.py @@ -390,6 +390,7 @@ def add(section, cmd, what, **opts): add(S2, f"{BIN} . --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.") add(S2, f"{BIN} . --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).") add(S2, f"{BIN} . --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\").") +add(S2, f"{BIN} . --defs=main,rankGraphTeleport", "Which definitions each name binds, one row per definition group with sel= (the spelling that picks it alone in any verb), class=source|test and in= callers; source first.") add(S2, f"{BIN} . --connect=rankGraphTeleport,runEval,getIndex", "Minimal connecting subgraph over 3 symbols (finds shared-caller joins).") add(S2, f"{BIN} . --impact=rankGraphTeleport", "Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.") add(S2, f"{BIN} . --mentions=rankGraphTeleport", "Markdown docs that name SYM in a backtick (doc<->code edges).") From 6ad9ef1192c2a435c37f89f84676431dc4eadb91 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:42:50 -0400 Subject: [PATCH 33/95] path: a 2-hop --path prints its middle hop's body (PATH-HOP-BODY) A reached --path / path_between whose shown chain has 2 hops now prints, after the hop rows, with the middle hop's body (the --expand element, no callee rows; never an endpoint). Each body is whole up to 4000 B, else cut at a line end with truncated="1" lines= next=. A longer chain prints no body and its root carries next="--expand=" when no other continuation owns next=. Inside a --for graph-verb block a middle hop whose text the answer already carries is not printed twice (forroutecheck v1 now runs under RIPWIRE_NO_HOP_BODY=1; v1b pins the one by-design difference). RIPWIRE_NO_HOP_BODY=1 is the A/B handle. Hop gate and cap chosen by measurement over the in-sample --path rows: hono-12 0 -> 6 of 7 items; 3 and 4 hops only add bytes to rows already complete. spanInside/overlapsServed move from forfold.h to hopbody.h (one containment reading). Gate: test/hopbodycheck.sh. Co-Authored-By: Claude Opus 5.5 --- README.md | 4 +- changelog.d/path-hop-body.md | 16 +++ docs/EVALS.md | 6 +- docs/LIMITS.md | 19 ++- docs/TUNING.md | 18 +-- present/deck5_ripwire_build.js | 6 +- src/compactlegend.h | 2 + src/forfold.h | 46 +------ src/hopbody.h | 180 ++++++++++++++++++++++++++ src/mcpverbs.h | 14 +- src/verbs_for.h | 7 +- src/verbs_navigate.h | 23 +++- test/forroutecheck.sh | 30 ++++- test/hopbodycheck.sh | 201 +++++++++++++++++++++++++++++ test/hopbodyfix/web/src/big.ts | 161 +++++++++++++++++++++++ test/hopbodyfix/web/src/chain.ts | 16 +++ test/hopbodyfix/web/src/compose.ts | 11 ++ test/hopbodyfix/web/src/router.ts | 11 ++ test/hopbodyfix/web/src/server.ts | 29 +++++ test/hopbodyfix/web/src/trip.ts | 26 ++++ test/regression.sh | 2 +- 21 files changed, 752 insertions(+), 76 deletions(-) create mode 100644 changelog.d/path-hop-body.md create mode 100644 src/hopbody.h create mode 100644 test/hopbodycheck.sh create mode 100644 test/hopbodyfix/web/src/big.ts create mode 100644 test/hopbodyfix/web/src/chain.ts create mode 100644 test/hopbodyfix/web/src/compose.ts create mode 100644 test/hopbodyfix/web/src/router.ts create mode 100644 test/hopbodyfix/web/src/server.ts create mode 100644 test/hopbodyfix/web/src/trip.ts diff --git a/README.md b/README.md index 96f8f50c1..54a09d652 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
-726 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +727 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 **726 gate scripts** and is the authoritative list; +`test/regression.sh` names **727 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 diff --git a/changelog.d/path-hop-body.md b/changelog.d/path-hop-body.md new file mode 100644 index 000000000..6b5611d3f --- /dev/null +++ b/changelog.d/path-hop-body.md @@ -0,0 +1,16 @@ +### Added — a short `--path` answer prints the body of its middle hop + +A reached `--path` (and MCP `path_between`) whose chain has 2 hops now prints, after its hop rows, the body of its +middle hop inside ``: the same `` element `--expand` prints, without callee rows. The endpoints never get a +body. Each body is whole up to 4000 bytes; a longer one is cut at a line end and says so on the element (`truncated="1" +lines="1-K/T" next=` the call that serves the rest). A chain of more hops prints no body, and its root carries +`next="--expand=…"` naming the first middle hop. Inside a `--for` answer's graph-verb block, a middle hop whose body the +answer already prints is not printed twice. `RIPWIRE_NO_HOP_BODY=1` turns it off. + +Why: `--path=fetch,compose` on hono named `Hono.fetch -> Hono.#dispatch -> compose` and stopped there, while the whole request +flow (path extraction, router match, Context, the one-handler fast path, compose, error handling) lives in `#dispatch`'s body. +Measured on the 163-row S1 replay: hono-12 goes from 0 to 6 of 7 required items (✗ -> ◐ by Rule C, ✗ -> ✓ legacy), 0 rows +lower; 9 of the 15 `--path` rows change, +3.1 KB in total (hono-12 +2.1 KB, kysely-14 +0.5 KB, six longer chains +~90 B of +`next=` each). The 2-hop gate is measured: 3 hops would add 1.6 KB on one already-complete row, 4 hops 13 KB on two more, and no +item. On LARGE-DEV one `--for` graph-verb path block gains a middle body (zed `get_any_active_multi_workspace`, +1.0 KB) and seven +gain only `next=`. Gate: `test/hopbodycheck.sh`. diff --git a/docs/EVALS.md b/docs/EVALS.md index 0e2d7d4a2..4b579042d 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 726 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 727 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 **726 gate scripts**, all of which exist on disk. +naming **727 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 726. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 727. 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 1470fb7b8..39d5fb062 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 | | --- | --- | --- | --- | -| 287 | 113 | 170 | **117** | +| 289 | 114 | 172 | **117** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 287 + 7 is the 294 constants this generator parses out of `src/`. +are not counted as caps, and 289 + 7 is the 296 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 287 caps are classified -(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 147 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **140 of 289 caps are classified +(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 149 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 113 files that declare a cap — the 287 caps counted above, and no parameter. +One table for each of the 114 files that declare a cap — the 289 caps counted above, and no parameter. ### `src/abicheck.h` @@ -507,6 +507,15 @@ Discloses: `capped`, `syms_capped` | `kHandoffSymbolsPerCodeFile` | `50` | OUTPUT | — | | `kHandoffSymbolsPerDocFile` | `12` | OUTPUT | — | +### `src/hopbody.h` + +Discloses: `capped` + +| constant | value | class | note | +| --- | --- | --- | --- | +| `kHopBodyCapBytes` | `4000` | — | — | +| `kPathHopBodyMaxHops` | `2` | — | — | + ### `src/infra/blanktext.h` Discloses: **none** diff --git a/docs/TUNING.md b/docs/TUNING.md index 4b8df8701..4124a83dc 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 | | --- | --- | --- | --- | --- | --- | -| 195 | 194 | 112 | 12 | **37** | 75 | +| 197 | 196 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **195 cap -declarations** under **194 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 **197 cap +declarations** under **196 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 194 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **71 declared since the sweep was +every run, which makes them a third population again. Enumerated over the 196 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **73 declared since the sweep was prepared, which no measurement has touched** (`kBackHopWidth`, `kBaseScopeScanCap`, -`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +65 -more). 111 + 12 + 71 = 194, and `emit` refuses to render a partition that does not add up. The +`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +67 +more). 111 + 12 + 73 = 196, 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 195" would be wrong in both halves +declared in `src/` at all: `kSituTestRowsShown`. Quoting "113 of 197" 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 195-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 197-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/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index d77c20956..e1d3067e2 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1214,7 +1214,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["726 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["727 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1238,7 +1238,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "726", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "727", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1540,7 +1540,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["192 long flags · 36 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["726 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["727 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 71 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/src/compactlegend.h b/src/compactlegend.h index 0485c85e5..a1653c639 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -864,6 +864,8 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = // STRING-KEYED (src/pathgaps.h kNearSinkNameKeyLegend): present-only, read only on an ns row whose site is a name-key lookup { "via", "ns via=name-key cands=K: why=declined here is a template lookup by name (getattr(o, 'do_' + c), o['handle' + e]) in=, naming K definitions incl. this one; matched, never traced, the bridge crosses at most 16; a hedge", true, "ns", MapHeaderRead::No, "name-key", "path" }, { "withheld", "ns withheld=name:N: N callers whose by-name calls fail a name rule; not counted as a way in, never external", true, "ns", MapHeaderRead::No, {}, "path" }, + // PATH-HOP-BODY (src/hopbody.h): present-only, read only on a reached path's (value-qualified) + { "of", "bodies of=hops: the body of the middle hop (never an endpoint) of a shown chain of 2 hops; b t= l= p= n= as expand prints it, no callee rows, whole up to 4000 B", true, "bodies", MapHeaderRead::No, "hops", "path" }, // quality-delta: src/verbs_quality.h (root emit) + src/quality.h identityDisclosure { "stale", "stale=N: ack ledger rows whose target no longer applies (sa rows); never gating", false, "quality-delta", MapHeaderRead::No, {}, "quality-delta" }, // idea #11 (lane lean-trio): present-only, on a regression-free answer whose stale sa rows were collapsed to counts diff --git a/src/forfold.h b/src/forfold.h index fe387b22b..0fd441aa0 100644 --- a/src/forfold.h +++ b/src/forfold.h @@ -30,6 +30,7 @@ // RIPWIRE_NO_BODY_FOLD (any value) turns the fold off — the A/B handle, the RIPWIRE_NO_BODY_SLOT precedent. #include "commentcoherence.h" // isCommentStopword — the member slice's question words +#include "hopbody.h" // renderBodyElement, spanInside / overlapsServed — the one body render and containment reading #include "forowner.h" // isParsedMemberOf — a class's members BY THE PARSER (the body slot's member reading) #include "lexical.h" // subtokens, isRouteStopword, lexicalScanText — a sliced class's member text, read as the index read it #include "model.h" @@ -169,27 +170,7 @@ inline bool holdsWord( std::string_view text, const std::vector& wo // front of the . nullopt when the buffer failed or the span rendered nothing. inline std::optional renderOneBody( const BodyFoldInputs& in, NodeId id ) { - static const std::vector kNoOff; - static const std::vector kNoTargets; - static const std::vector kNoNameOnly; - ChargedSection sec = chargeSection( [ & ]( std::FILE* f ) - { packBodies( f, in.ing, { id }, /*budgetBytes=*/0, kNoOff, kNoTargets, CalleeEdgeBytes( kNoNameOnly ), in.compress, in.redact, - /*ranges=*/nullptr, /*notes=*/nullptr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/false, - /*withFileContext=*/false, in.rootArg, &in.lensRank, {} ); }, - kBytesPerTokenBody ); - if( !sec.isRendered ) - { - return std::nullopt; - } - std::string& x = sec.xml; - const std::size_t open = x.find( ""; - if( close == std::string::npos || !x.ends_with( kClose ) || x.find( "& ids, bool orHolds ) noexcept -{ - for( const NodeId b : ids ) - { - if( a == b || spanInside( ing, a, b ) || ( orHolds && spanInside( ing, b, a ) ) ) - { - return true; - } - } - return false; -} +// spanInside / overlapsServed (each text once, FIX1 N2) live in hopbody.h, the one containment reading the fold and the +// path hop bodies share. // a class's direct members by the parser (isParsedMemberOf), in source order; one nested in another member is inside that // member's text, so it is not a member of its own diff --git a/src/hopbody.h b/src/hopbody.h new file mode 100644 index 000000000..591ebb01f --- /dev/null +++ b/src/hopbody.h @@ -0,0 +1,180 @@ +#pragma once +// hopbody.h — THE BODY OF A MIDDLE HOP (lane path-hop-body, train 41): two answers that name a function and stop short of what it does. +// +// WHY. A reached --path answer prints each hop as a row and nothing else. When the path is short, the question is often +// what happens INSIDE the middle hop: hono's `Hono.fetch -> Hono.#dispatch -> compose` names #dispatch, and the whole request flow +// (path extraction, router match, Context, the one-handler fast path, compose, error handling) lives in its body, which the answer +// never printed (readout-40 G: 0 of 7 items; the same body through --expand: 6 of 7). +// +// WHAT. (1) PATH-HOP-BODY: a reached --path whose shown chain has at most kPathHopBodyMaxHops hops prints, after its hop rows, the body +// of each INTERMEDIATE hop (never an endpoint, never one whose text the answer already carries: fold_detail::overlapsServed) inside , +// each the --expand body element (no callee rows, no file context). A chain past the gate prints no body and the root carries +// next= (the first middle hop's --expand) unless another continuation already owns next=. +// THE BOUND IS A SPEW GUARD, NEVER A COMPLETENESS CUT (owner rule): each body is whole up to kHopBodyCapBytes; a longer one is cut at a +// line end by packBodies' own truncation, which says so on the element (truncated="1" lines="1-K/T" next= the call serving the rest). + +#include "model.h" +#include "nextverb.h" // nextFlag +#include "sarif.h" // rootPrefixOf / rootRelativeUri — the --expand selector's path +#include "serialize.h" // packBodies, chargeSection, escapeXml + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rw +{ + +// The per-body spew guard (bytes of source text): whole below it, cut at a line end and disclosed above it. Measured over the in-sample +// --path rows (reports/path-hop-body.md): the bodies these answers need are 0.1-3.4 KB. +inline constexpr std::size_t kHopBodyCapBytes = 4000; +// The hop-count gate: a chain of at most this many hops prints its middle hop's body (kPathHopBodyMaxHops - 1 = one body). Measured +// (reports/path-hop-body.md, the curve over the in-sample --path rows): 2 hops buys hono-12 its 6 items; 3 hops adds flake8-14 +// (+1.6 KB, already complete), 4 hops fzf-14 and tmux-14 (+13 KB, both already complete) and nothing else; past the gate next= names the body. +inline constexpr std::size_t kPathHopBodyMaxHops = 2; + +namespace fold_detail +{ +// does a's text lie inside b's: the same file and b's span holds a's (two equal spans: the later id is the inner one). A body +// inside another the answer serves is already there — the fold serves each text once (FIX1 N2). +inline bool spanInside( const IngestResult& ing, NodeId a, NodeId b ) noexcept +{ + const Symbol& x = ing.symbols[a]; + const Symbol& y = ing.symbols[b]; + return a != b && x.fileId == y.fileId && y.sigStartByte <= x.sigStartByte && x.endByte <= y.endByte + && ( y.sigStartByte < x.sigStartByte || x.endByte < y.endByte || b < a ); +} +// does a's text overlap a body in `ids` the answer already serves: a is one of them or lies inside one, or (orHolds) holds one +inline bool overlapsServed( const IngestResult& ing, NodeId a, std::span ids, bool orHolds ) noexcept +{ + for( const NodeId b : ids ) + { + if( a == b || spanInside( ing, a, b ) || ( orHolds && spanInside( ing, b, a ) ) ) + { + return true; + } + } + return false; +} +} // namespace fold_detail + +// The A/B switch (the RIPWIRE_NO_BODY_FOLD precedent): the hop bodies ride unless RIPWIRE_NO_HOP_BODY is set. +inline bool hopBodiesOn() +{ + return std::getenv( "RIPWIRE_NO_HOP_BODY" ) == nullptr; +} + +// One body through packBodies (the --expand body shape, no callee rows: the empty CSR has no out-edge for any id), unwrapped from its +// envelope; the legend comments packBodies writes ahead of or inside the envelope stay in front of the . capBytes 0 = +// whole; else a body over it is cut at a line end with truncated="1" lines= next= (packBodies' first-body truncation). nullopt when +// the buffer failed or the span rendered nothing. +inline std::optional renderBodyElement( const IngestResult& ing, NodeId id, std::size_t capBytes, bool compress, RedactCounts* redact, + std::string_view rootArg, const std::vector* rank ) +{ + static const std::vector kNoOff; + static const std::vector kNoTargets; + static const std::vector kNoNameOnly; + ChargedSection sec = chargeSection( [ & ]( std::FILE* f ) + { packBodies( f, ing, { id }, capBytes, kNoOff, kNoTargets, CalleeEdgeBytes( kNoNameOnly ), compress, redact, + /*ranges=*/nullptr, /*notes=*/nullptr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/capBytes > 0, + /*withFileContext=*/false, rootArg, rank, {} ); }, + kBytesPerTokenBody ); + if( !sec.isRendered ) + { + return std::nullopt; + } + std::string& x = sec.xml; + const std::size_t open = x.find( ""; + if( close == std::string::npos || !x.ends_with( kClose ) || x.find( " pathMiddleHops( const IngestResult& ing, std::span chain, std::span served ) +{ + std::vector out; + if( chain.size() < 3 || chain.size() - 1 > kPathHopBodyMaxHops ) + { + return out; + } + for( std::size_t i = 1; i + 1 < chain.size(); ++i ) + { + const NodeId n = chain[i]; + if( n < ing.symbols.size() && ing.symbols[n].kind != SymKind::ModuleScope && ing.symbols[n].endByte > ing.symbols[n].sigStartByte + && std::ranges::find( out, n ) == out.end() && !fold_detail::overlapsServed( ing, n, served, /*orHolds=*/true ) ) + { + out.push_back( n ); + } + } + return out; +} + +// next= for a reached chain past the hop gate: the first middle hop's --expand (unescaped), "" when the chain has no middle or is +// within the gate (its bodies print). +inline std::string pathMiddleHopNext( const IngestResult& ing, std::span chain, std::string_view rootArg ) +{ + if( chain.size() < 3 || chain.size() - 1 <= kPathHopBodyMaxHops ) + { + return {}; + } + return expandSelectorOf( ing, chain[1], rootArg ); +} + +// THE PATH'S ELEMENT: every middle hop's body, each whole up to kHopBodyCapBytes; "" when there is none. +inline std::string pathHopBodiesXml( const IngestResult& ing, std::span hops, std::string_view rootArg ) +{ + std::string children; + std::size_t shown = 0; + bool cut = false; + for( const NodeId n : hops ) + { + if( const std::optional b = renderBodyElement( ing, n, kHopBodyCapBytes, /*compress=*/false, /*redact=*/nullptr, rootArg, nullptr ) ) + { + children += *b; + ++shown; + cut = cut || b->find( " truncated=\"1\"" ) != std::string::npos; + } + } + if( shown == 0 ) + { + return {}; + } + return "" + children + ""; +} + +// The full dialect's clause for , present-only (no "--": it rides inside the path's XML comment). +static_assert( kHopBodyCapBytes == 4000 && kPathHopBodyMaxHops == 2, "the path and fold clauses spell both bounds: re-word them with the knob" ); +inline std::string_view pathHopBodiesLegend( bool on ) noexcept +{ + return on ? "bodies of=hops: the body of the middle hop of the shown chain (never an endpoint), when it has 2 hops; " + "b t= l= p= n= as expand prints it (no callee rows), whole up to 4000 bytes (a longer one is cut, truncated=1). " + : ""; +} +inline std::string_view pathHopNextLegend( bool on ) noexcept +{ + return on ? "next=: the chain has more than 2 hops, so no middle hop body prints; the first middle hop's expand. " : ""; +} + +} // namespace rw diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 32b9aac4b..96cd101ce 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -43,6 +43,7 @@ #include "testmap.h" // lane/t10-mcp-coverage: writeAffectedReport — the shared --affected / `affected` verb renderer #include "forgraphverb.h" // FOR-ROUTE-DECLINED: the `for` verb's subject-read graph-verb block (the CLI twin's wrapper) #include "pathgaps.h" // PATH-GAP: the shared gap analysis + clause of --path / path_between +#include "hopbody.h" // PATH-HOP-BODY: the middle hops' bodies of a reached path_between #include "pathpairs.h" // TRACE-EP: the reaching definition pairs of --path / path_between #include // §B6 M3: the shared root-path existence/directory check (mcpRootRefusal below) @@ -3827,10 +3828,18 @@ inline std::optional pathText( const std::string& root, const std:: const PathTestTwin ptTwin = srcGlob || dstGlob ? PathTestTwin{} : pathTestTwins( ing, g, { srcDefs, from, ptBound.from }, { dstDefs, to, ptBound.to }, ptShown ); // zed-03, as on the CLI const std::vector ptOwners = pathHopOwners( ing, pth ); // PATH-OWNERS, as on the CLI const bool ptHasOwner = ptMarks.owner; + // PATH-HOP-BODY (src/hopbody.h), as on the CLI + const std::string_view ptBodyRoot = ptSingleRoot ? std::string_view( root ) : std::string_view(); + const bool ptBodiesOn = !pth.empty() && hopBodiesOn(); + const std::string ptHopBodies = ptBodiesOn ? pathHopBodiesXml( ing, pathMiddleHops( ing, ptShown, {} ), ptBodyRoot ) : std::string(); + std::string ptRootAttrs = pathTestTwinRootAttrs( ing, pathPairsRootAttrs( ing, ptPairs, ptSingleRoot, ptRootPrefix ).rootAttrs, ptTwin, ptSingleRoot, ptRootPrefix ); + const std::string ptHopNext = ptBodiesOn && ptRootAttrs.find( " next=\"" ) == std::string::npos ? pathMiddleHopNext( ing, ptShown, ptBodyRoot ) : std::string(); + ptRootAttrs += ptHopNext.empty() ? std::string() : nextAttrXml( ptHopNext ); rw::emitTo( mem, "{}", ( unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + ptKindLegend + splitArmLegend( ptHasSplit ) + pathUnboundLegend( !ptBound.from || !ptBound.to ) - + memberOwnerLegend( ptHasOwner ) + memberOwnerContainedLegend( ptMarks.ownerContained ) + std::string( pathPairsLegend( ptPairs ) ) + pathOrderLegend( ptPairs, ptTwin ) ).c_str(), + + memberOwnerLegend( ptHasOwner ) + memberOwnerContainedLegend( ptMarks.ownerContained ) + std::string( pathPairsLegend( ptPairs ) ) + pathOrderLegend( ptPairs, ptTwin ) + + std::string( pathHopBodiesLegend( !ptHopBodies.empty() ) ) + std::string( pathHopNextLegend( !ptHopNext.empty() ) ) ).c_str(), ( std::string( toValueRefsLegend( ptToValueRefs.count > 0 ) ) + valueRefsDepthLegend( ptToValueRefs.depthCut.files > 0 ) ).c_str(), ( pathGapsLegend( ptGaps.any(), !ptSinkCallers.empty() ) + pathBridgeLegend( ptBridge ) + nearSinkLegend( ptNear ) + std::string( pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), @@ -3840,7 +3849,7 @@ inline std::optional pathText( const std::string& root, const std:: ptBound.to ? " to_p=\"" + loc( dstUsed ) + "\"" : std::string(), srcDefs.size(), dstDefs.size(), ( unprovenDefsAttrXml( unprovenDefs ) + pathGlobAttrXml( srcGlob, dstGlob ) ).c_str(), // H1: as the CLI root carries it; ix2-slice: a glob endpoint pth.empty() ? 0 : 1, ptShown.empty() ? std::size_t( 0 ) : ptShown.size() - 1, - ( pathTestTwinRootAttrs( ing, pathPairsRootAttrs( ing, ptPairs, ptSingleRoot, ptRootPrefix ).rootAttrs, ptTwin, ptSingleRoot, ptRootPrefix ) + ptRootAttr ).c_str(), + ( ptRootAttrs + ptRootAttr ).c_str(), graphCountFloorAttrXml( g ).c_str() ); // M15: gauge + marker rw::emitTo( mem, "{}{}", countAttrXmlOrEmpty( "to_value_refs", ptToValueRefs.count ), // absent at zero, as on the CLI valueRefsDepthAttrXml( ing, ptToValueRefs.depthCut, VrRender{ ptSingleRoot, ptRootPrefix } ).c_str() ); // + the depth cut, as the CLI @@ -3850,6 +3859,7 @@ inline std::optional pathText( const std::string& root, const std:: rw::emitTo( mem, "{}>{}{}{}", ptUnreached.rootAttrs, ptUnreached.rows, pathBridgeXml( ing, g, ptBridge, ptSingleRoot, ptRootPrefix ), pathNearSinkXml( ing, ptNear, ptSingleRoot, ptRootPrefix ) ); emitPathAnswerRows( mem, ing, g, ptPairs, pth, ptOwners, ptSingleRoot, ptRootPrefix ); // the CLI's own row emitter (pathpairs.h / defattrib.h) + rw::emitRaw( mem, ptHopBodies.c_str() ); // PATH-HOP-BODY, as on the CLI rw::emitRaw( mem, "" ); return mcpAnswerText( stream ); // nullopt = the buffer lost bytes (dispatch answers -32603), "" stays not-found } diff --git a/src/verbs_for.h b/src/verbs_for.h index 2fd6af4fe..73e4e8478 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2652,6 +2652,7 @@ struct ForLensPassMode }; // the verb sections (verbs_navigate.h) come later in this translation unit; the --for block calls their CLI entry points std::optional runPath( const MainDispatch& d ); +std::optional runPathServed( const MainDispatch& d, std::span served ); std::optional runCallHierarchy( const MainDispatch& d ); std::optional runConnect( const MainDispatch& d ); @@ -2659,7 +2660,7 @@ std::optional runConnect( const MainDispatch& d ); // gets that verb's DEFAULT answer inside , just before , on the graph this run built // (no re-index). Every byte before it is the ordinary answer; nothing resolves → nothing is printed. XML only (the // --json / columnar dialects keep their single document; the verb is one call away there). -inline void appendSubjectGraphVerb( const MainDispatch& d ) +inline void appendSubjectGraphVerb( const MainDispatch& d, std::span served ) { const rw::Config& cfg = d.cfg; if( cfg.json || cfg.columnar || cfg.candidates || d.multiRoot ) @@ -2694,7 +2695,7 @@ inline void appendSubjectGraphVerb( const MainDispatch& d ) { switch( args->form ) { - case rw::GraphSubjectForm::Path: (void)runPath( sd ); break; + case rw::GraphSubjectForm::Path: (void)runPathServed( sd, served ); break; case rw::GraphSubjectForm::Callers: (void)runCallHierarchy( sd ); break; case rw::GraphSubjectForm::Common: (void)runConnect( sd ); break; } @@ -4405,7 +4406,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co { rw::emitChargedSection( stdout, graphSection, [ & ]{ packGraphBlock( stdout, ing, lensRank, g.outOff, g.outTargets ); } ); } - appendSubjectGraphVerb( d ); // FOR-ROUTE-DECLINED: "" unless the task is spelled as a graph question that resolves + appendSubjectGraphVerb( d, servedAnswerBodies().bodies ); // FOR-ROUTE-DECLINED: "" unless the task is spelled as a graph question that resolves; the bodies this answer carries print no second copy in its path hop bodies rw::emitRaw( stdout, "" ); reportRedactions( stderr, redactCounts ); diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index 82d5c644a..ccfb8e718 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -4,6 +4,7 @@ #endif #include "infra/emit.h" // rw::emitTo — THE emitter (std::print, or std::format+fputs where is absent) +#include "hopbody.h" // PATH-HOP-BODY: the middle hops' bodies of a reached --path #include "typeuse.h" // L7: --impact of a type seeds its walk from the symbols that name it #include // std::string_view — the %.*s (precision, pointer) pair collapses to one view @@ -2476,7 +2477,9 @@ std::optional runExternalSurface( const MainDispatch& d ) return std::nullopt; } -std::optional runPath( const MainDispatch& d ) +// served: bodies the enclosing answer already carries (the --for graph-verb block passes its fold's and slot's); a middle hop among them +// prints no second copy (hopbody.h). The bare --path passes none. +std::optional runPathServed( const MainDispatch& d, std::span served ) { using namespace rw; const Config& cfg = d.cfg; @@ -2613,11 +2616,19 @@ std::optional runPath( const MainDispatch& d ) const rw::PathTestTwin pthTwin = srcGlob || dstGlob ? rw::PathTestTwin{} : rw::pathTestTwins( ing, g, { srcDefs, srcN, pthBound.from }, { dstDefs, dstN, pthBound.to }, pthShown ); const std::vector pthOwners = rw::pathHopOwners( ing, path ); // PATH-OWNERS: n="Owner.name" per hop const bool pthHasOwner = pthMarks.owner; + // PATH-HOP-BODY (src/hopbody.h): a reached chain of <= 3 hops prints its middle hops' bodies; a longer one names the first in next= + const std::string_view pthBodyRoot = pthSingleRoot ? std::string_view( cfg.roots[0] ) : std::string_view(); + const bool pthBodiesOn = !path.empty() && rw::hopBodiesOn(); + const std::string pthHopBodies = pthBodiesOn ? rw::pathHopBodiesXml( ing, rw::pathMiddleHops( ing, pthShown, served ), pthBodyRoot ) : std::string(); + std::string pthRootAttrs = rw::pathTestTwinRootAttrs( ing, rw::pathPairsRootAttrs( ing, pthPairs, pthSingleRoot, pthRootPrefix ).rootAttrs, pthTwin, pthSingleRoot, pthRootPrefix ); + const std::string pthHopNext = pthBodiesOn && pthRootAttrs.find( " next=\"" ) == std::string::npos ? rw::pathMiddleHopNext( ing, pthShown, pthBodyRoot ) : std::string(); + pthRootAttrs += pthHopNext.empty() ? std::string() : rw::nextAttrXml( pthHopNext ); rw::emitTo( stdout, "{}", ( rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Path, pthUnprovenDefs > 0 ) + rw::viaNameLegend( pthHasVia ) + rw::viaLinkLegend( pthHasLink ) + rw::anchorMissLegend( pthHasMiss ) + rw::splitArmLegend( pthHasSplit ) + rw::pathUnboundLegend( !pthBound.from || !pthBound.to ) - + rw::memberOwnerLegend( pthHasOwner ) + rw::memberOwnerContainedLegend( pthMarks.ownerContained ) + std::string( rw::pathPairsLegend( pthPairs ) ) + rw::pathOrderLegend( pthPairs, pthTwin ) ).c_str(), + + rw::memberOwnerLegend( pthHasOwner ) + rw::memberOwnerContainedLegend( pthMarks.ownerContained ) + std::string( rw::pathPairsLegend( pthPairs ) ) + rw::pathOrderLegend( pthPairs, pthTwin ) + + std::string( rw::pathHopBodiesLegend( !pthHopBodies.empty() ) ) + std::string( rw::pathHopNextLegend( !pthHopNext.empty() ) ) ).c_str(), ( std::string( rw::toValueRefsLegend( pthToValueRefs.count > 0 ) ) + rw::valueRefsDepthLegend( pthToValueRefs.depthCut.files > 0 ) ).c_str(), ( rw::pathGapsLegend( pthGaps.any(), !pthSinkCallers.empty() ) + rw::pathBridgeLegend( pthBridge ) + rw::nearSinkLegend( pthNear ) + std::string( rw::pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), @@ -2627,7 +2638,7 @@ std::optional runPath( const MainDispatch& d ) pthBound.to ? " to_p=\"" + loc( dstUsed ) + "\"" : std::string(), srcDefs.size(), dstDefs.size(), ( rw::unprovenDefsAttrXml( pthUnprovenDefs ) + rw::pathGlobAttrXml( srcGlob, dstGlob ) ).c_str(), // H1: beside the defs counts it is not in; ix2-slice: a glob endpoint path.empty() ? 0 : 1, pthShown.empty() ? std::size_t( 0 ) : pthShown.size() - 1, - ( rw::pathTestTwinRootAttrs( ing, rw::pathPairsRootAttrs( ing, pthPairs, pthSingleRoot, pthRootPrefix ).rootAttrs, pthTwin, pthSingleRoot, pthRootPrefix ) + pthRootAttr ).c_str(), + ( pthRootAttrs + pthRootAttr ).c_str(), rw::graphCountFloorAttrXml( g ).c_str() ); // absent at zero: byte-identical otherwise; the depth cut beside it makes the count (or its absence) a floor rw::emitTo( stdout, "{}{}", rw::countAttrXmlOrEmpty( "to_value_refs", pthToValueRefs.count ), @@ -2644,12 +2655,18 @@ std::optional runPath( const MainDispatch& d ) rw::pathNearSinkXml( ing, pthNear, pthSingleRoot, pthRootPrefix ) ); // FE-B via="name", #409 via="link"/anchor="missing", attrib-multidef prov="split", PATH-OWNERS n="Owner.name": one emitter, shared with MCP path_between rw::emitPathAnswerRows( stdout, ing, g, pthPairs, path, pthOwners, pthSingleRoot, pthRootPrefix ); // TRACE-EP: pairs or today's rows, shared with MCP + rw::emitRaw( stdout, pthHopBodies.c_str() ); // PATH-HOP-BODY: after the hop rows they explain ("" when none) rw::emitTo( stdout, "" ); return 0; } return std::nullopt; } +std::optional runPath( const MainDispatch& d ) +{ + return runPathServed( d, {} ); +} + std::optional runConnect( const MainDispatch& d ) { using namespace rw; diff --git a/test/forroutecheck.sh b/test/forroutecheck.sh index 710609fff..6d6c531aa 100755 --- a/test/forroutecheck.sh +++ b/test/forroutecheck.sh @@ -7,7 +7,9 @@ # (p1) path form: the --path answer for the two named symbols, reachable="1", once, right before , well-formed # (p2) the qualifier tail is cut (to_span= stops before "for"), and `main` is the one plain word that is an endpoint # (c1) callers form: the --callers answer (both callers listed); (c2) the qualifier tail "with ..." is cut -# (v1)/(v2) the block's verb answer is byte-identical to the bare --path= / --callers= call (default caps, compact legend) +# (v1)/(v2) the block's verb answer is byte-identical to the bare --path= / --callers= call (default caps, compact legend); +# v1 under RIPWIRE_NO_HOP_BODY=1 (the pre-PATH-HOP-BODY premise). (v1b) hop bodies on: identical but for a middle hop body +# whose text the --for answer already carries outside the block (src/hopbody.h; not printed twice) # (m1) common-callers form: the --connect answer for both names # (t1) an unresolved last-first candidate falls through to the next one, and tried="2" says so # (n1) "path from X to serialising a dict" does NOT fire, though `dict` is a symbol here @@ -99,15 +101,37 @@ import re, sys s = open( sys.argv[1], encoding="utf8", errors="replace" ).read() m = re.search( r"]*>(.*)$", s, re.S ) sys.stdout.write( m.group( 1 ) if m else "NO-BLOCK" )' "$1"; } -for arm in "v1|p1|--path=startEntry,leaf_sink" "v2|c1|--callers=leaf_sink" +# (v1) under the PATH-HOP-BODY A/B handle (src/hopbody.h), so its premise is the pre-lane one: the block's bytes ARE the bare call's. +# With hop bodies on, (v1b) below states the one by-design difference. +RIPWIRE_NO_HOP_BODY=1 run --for="path from startEntry to leaf_sink" >"$TMP/p1off.xml" +for arm in "v1|p1off|--path=startEntry,leaf_sink" "v2|c1|--callers=leaf_sink" do id="${arm%%|*}"; rest="${arm#*|}"; src="${rest%%|*}"; flag="${rest#*|}" - run "$flag" >"$TMP/$id.bare"; inner "$TMP/$src.xml" >"$TMP/$id.inner" + if [ "$id" = v1 ]; then RIPWIRE_NO_HOP_BODY=1 run "$flag" >"$TMP/$id.bare"; else run "$flag" >"$TMP/$id.bare"; fi + inner "$TMP/$src.xml" >"$TMP/$id.inner" if [ -s "$TMP/$id.bare" ] && cmp -s "$TMP/$id.bare" "$TMP/$id.inner" then ok "($id) the block's verb answer is byte-identical to the bare $flag call (same default caps, same compact legend)" else no "($id) the block's verb answer differs from the bare $flag call ($( wc -c <"$TMP/$id.inner" ) vs $( wc -c <"$TMP/$id.bare" ) B)"; fi done +# (v1b) PATH-HOP-BODY, hop bodies on: the block is the bare call, except that a middle hop body whose text the --for answer +# already carries (outside the block) is not printed twice — then the bare call's and its legend reading are the +# whole difference. A middle hop the answer does not carry stays in the block byte for byte. +run --path=startEntry,leaf_sink >"$TMP/v1b.bare"; inner "$TMP/p1.xml" >"$TMP/v1b.inner" +if python3 - "$TMP/v1b.bare" "$TMP/v1b.inner" "$TMP/p1.xml" <<'PY' +import re, sys +bare, inner, full = (open(f, encoding="utf8", errors="replace").read() for f in sys.argv[1:4]) +if bare == inner: sys.exit(0) +el = re.search(r'', bare, re.S) +if not el or ']*p="%s"' % re.escape(m.group(1)), outside) for m in re.finditer(r']*p="([^"]*)"', el.group(0))) +nolegend = lambda t: re.sub(r'', '', t, flags=re.S) # its present-only readings (bodies of=, the window) go with it; (v1) pins the legend +sys.exit(0 if held and nolegend(bare.replace(el.group(0), "")) == nolegend(inner) else 1) +PY +then ok "(v1b) hop bodies on: the block is the bare --path call, less only a middle hop body the answer already carries" +else no "(v1b) hop bodies on: the block differs from the bare --path call beyond the already-carried middle hop body"; fi + run --for="common callers of mid_hop and other_caller" >"$TMP/m1.xml" f="$( facts "$TMP/m1.xml" )" if [ "${f%% tried=*}" = "n=1 endpoints=subject verb=--connect=mid_hop,other_caller from=- to=- of=mid_hop and=other_caller" ] && grep -q ' holding the body of its INTERMEDIATE hop (never an endpoint), each +# the --expand body element (b t= l= p= n=, CDATA = the definition's source text, no callee rows). A body +# over 4000 B is cut at a line end and says so (truncated="1" lines="1-K/T" next= serves the rest). A chain of +# more hops prints no body and its root carries next= (the first middle hop's --expand). Inside a --for answer's +# graph-verb block a middle hop whose text the answer already carries (a served class holding it) prints no copy. +# LEGEND every new reading is present-only: the compact path reading `bodies of=hops` and the full path clause ride +# exactly when the element does. +# A/B RIPWIRE_NO_HOP_BODY=1 is the answer without it. +# +# Arms (colour on 5163ffbdb in brackets): +# P1 [P] fetch -> dispatch -> compose: one with dispatch's body, CDATA = its source text; no endpoint body +# P2 [P] a 3-hop chain (past the gate) prints no body; root next="--expand=src/chain.ts:5:stepTwo" +# P3 [P] a 4-hop chain prints no body; root next="--expand=src/chain.ts:5:stepTwo", and that call serves stepTwo's body +# P9 [P] the --for graph-verb block: gatewayEntry -> relayHop prints relayHop's body (P9a); Server.fetch's twin, whose +# answer already serves the Server class holding dispatch, prints no copy (P9b) — kills a NodeId-only dedupe +# P4 [P] a middle body over the guard: truncated="1" lines="1-K/T" next=, capped="1", served text <= 4000 B, next= serves the rest +# P5 [G] a 1-hop chain and an unreached pair: no , no next= +# P6 [P] legend present-only, compact and full: the reading rides iff the element does; the past-the-gate next= reading in full +# P7 [P] MCP path_between prints the CLI's element byte for byte (P1, P4) and the 3- and 4-hop next= +# P8 [G] RIPWIRE_NO_HOP_BODY=1: the path answer is the head answer minus the element, its clause and the 4-hop next= +# X1 [G] determinism X2 [G] xmllint well-formed X3 [G] no "--" inside a legend comment +# I [optional, RIPWIRE_BASE_BIN=] RIPWIRE_NO_HOP_BODY=1 head == the base binary on every question here +# +# Usage: bash test/hopbodycheck.sh [BIN] | RIPWIRE_BIN=asan/ripwire bash test/hopbodycheck.sh +# BIN is $1, else RIPWIRE_BIN, else build/ripwire; the first output line names the binary used. Exits non-zero on any failure. + +set -u +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +. "$ROOT/test/lib/clean-env.sh" +unset RIPWIRE_NO_HOP_BODY RIPWIRE_NO_BODY_FOLD RIPWIRE_NO_BODY_SLOT +BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" +[ "${BIN#/}" = "$BIN" ] && BIN="$ROOT/$BIN" +echo "hopbodycheck: BIN=$BIN" +[ -x "$BIN" ] || { echo "no ripwire binary at $BIN — build first"; exit 2; } +command -v python3 >/dev/null 2>&1 || { echo "python3 required"; exit 2; } +cd "$ROOT" +WEB="test/hopbodyfix/web" +[ -d "$WEB/src" ] || { echo "fixture test/hopbodyfix missing"; exit 2; } + +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT +export HOME="$TMP/home"; mkdir -p "$HOME" +run(){ local d="$1"; shift; ( cd "$d" || exit 1; "$BIN" "$ROOT/$d" --no-cache "$@" 2>/dev/null ); } + +run "$WEB" --path=fetch,compose >"$TMP/p1" +run "$WEB" --path=stepOne,stepFour >"$TMP/p2" +run "$WEB" --path=stepOne,stepFive >"$TMP/p3" +run "$WEB" --path=bigEntry,bigSink >"$TMP/p4" +run "$WEB" --path=stepFour,stepFive >"$TMP/p5a" +run "$WEB" --path=compose,fetch >"$TMP/p5b" +run "$WEB" --for="Trace the path from gatewayEntry() to finalStage()" >"$TMP/p9a" +run "$WEB" --for="Trace the path from Server.fetch to compose()" >"$TMP/p9b" +run "$WEB" --path=fetch,compose --legend=full >"$TMP/p1f" +run "$WEB" --path=stepOne,stepFive --legend=full >"$TMP/p3f" +run "$WEB" --path=stepFour,stepFive --legend=full >"$TMP/p5af" +RIPWIRE_NO_HOP_BODY=1 run "$WEB" --path=fetch,compose >"$TMP/p1.off" +RIPWIRE_NO_HOP_BODY=1 run "$WEB" --path=stepOne,stepFour >"$TMP/p2.off" +RIPWIRE_NO_HOP_BODY=1 run "$WEB" --path=stepOne,stepFive >"$TMP/p3.off" +RIPWIRE_NO_HOP_BODY=1 run "$WEB" --path=bigEntry,bigSink >"$TMP/p4.off" +cat > "$TMP/chk.py" <<'PY' +import html, re, shlex, subprocess, sys, os +T, BIN, ROOT = sys.argv[1], sys.argv[2], sys.argv[3] +fails = 0 +def rd(n): return open(os.path.join(T, n), "rb").read().decode("utf-8", "replace") +def ok(m): print(" PASS " + m) +def no(m): + global fails; fails += 1; print(" FAIL " + m) +def check(c, m): ok(m) if c else no(m) +def attrs(s): return dict((k, html.unescape(v)) for k, v in re.findall(r'(\w+)="([^"]*)"', s)) +def cdata(x): return "".join(re.findall(r'', x, re.S)) +def bodies(x): return [(attrs(m.group(1)), cdata(m.group(2)), m.group(0)) for m in re.finditer(r']*)>(.*?)', x, re.S)] +def hopel(t): + m = re.search(r']*>.*?', t, re.S); return m.group(0) if m else "" +def root(t): + m = re.search(r']*)>', t); return attrs(m.group(1)) if m else {} +def legend(t): return " ".join(re.findall(r'', t, re.S)) +def is_source(b, d): + """the body's CDATA is its definition's source text: a substring of the file p= starting on line l=""" + a, cd = b[0], b[1] + try: src = open(os.path.join(ROOT, d, a.get("p", "")), encoding="utf-8").read() + except OSError: return False + i = src.find(cd) + return cd != "" and i >= 0 and src[:i].count("\n") + 1 == int(a.get("l", "0")) +def paste(nxt, d): + argv = shlex.split(nxt) + return subprocess.run([BIN, os.path.join(ROOT, d), "--no-cache"] + argv, cwd=os.path.join(ROOT, d), capture_output=True).stdout.decode("utf-8", "replace") +WEB = "test/hopbodyfix/web" + +# P1 +p1 = rd("p1"); el = hopel(p1); bs = bodies(el) +check(len(bs) == 1 and bs[0][0].get("n") == "dispatch" and bs[0][0].get("l") == "10", "(P1) one middle-hop body: dispatch l=10") +check(bs and is_source(bs[0], WEB) and "return composed(request)" in bs[0][1], "(P1) its CDATA is the definition's source text, whole") +check(not re.search(r']*n="(fetch|compose)"', el) and "', t, re.S) or re.search(r'$', t)).group(0) +g9a, g9b = gv(rd("p9a")), gv(rd("p9b")) +b9a = bodies(hopel(g9a)) +check(g9a != "" and len(b9a) == 1 and b9a[0][0].get("n") == "relayHop" and is_source(b9a[0], WEB), "(P9a) the graph-verb path block prints the middle hop's body") +check(re.search(r']*n="Server"', rd("p9b")) and g9b != "" and hopel(g9b) == "", "(P9b) a middle hop inside a body the answer serves prints no copy") +# P3 +p3 = rd("p3"); r = root(p3) +check(hopel(p3) == "" and r.get("hops") == "4", "(P3) a 4-hop chain prints no body") +check(r.get("next") == "--expand=src/chain.ts:5:stepTwo", "(P3) root next= is the first middle hop's --expand: %r" % r.get("next")) +check(r.get("next") and "return stepThree(n * 2)" in cdata(paste(r["next"], WEB)), "(P3) the pasted next= serves stepTwo's body") +# P4 +el = hopel(rd("p4")); bs = bodies(el); a = bs[0][0] if bs else {} +check(a.get("truncated") == "1" and re.fullmatch(r'1-\d+/\d+', a.get("lines", "")) and a.get("next", "").startswith("--expand=src/big.ts:5:bigMiddle:"), "(P4) the long middle body is cut and says so: %r" % {k: a.get(k) for k in ("truncated", "lines", "next")}) +check('capped="1"' in el and bs and len(bs[0][1].encode()) <= 4000, "(P4) capped=1 and the served text is <= 4000 B") +if bs and a.get("next"): + rest = cdata(paste(a["next"], WEB)); hi = int(a["lines"].split("-")[1].split("/")[0]); tot = int(a["lines"].split("/")[1]) + check(rest.count("\n") + 1 == tot - hi and bs[0][1].endswith("\n") is False, "(P4) next= serves exactly the lines the cut left (%d of %d)" % (tot - hi, tot)) +# P5 +for f in ("p5a", "p5b"): + t = rd(f); check(hopel(t) == "" and "next=" not in (re.search(r']*>', t).group(0) if re.search(r']*>', t) else ""), "(P5) %s: no , no root next=" % f) +check('reachable="0"' in rd("p5b"), "(P5) compose,fetch is the unreached twin") +# P6 +check("bodies of=hops" in legend(p1) and "bodies of=hops" not in legend(rd("p3")) and "bodies of=hops" not in legend(rd("p5a")), "(P6) compact: the reading rides iff the element does") +check("bodies of=hops" in legend(rd("p1f")) and "bodies of=hops" not in legend(rd("p5af")), "(P6) full: the clause rides iff the element does") +check("first middle hop" in legend(rd("p3f")) and "first middle hop" not in legend(rd("p1f")), "(P6) full: the 4-hop next= reading rides iff that next= does") +# P8 +for f in ("p1", "p4"): + on, off = rd(f), rd(f + ".off") + strip = re.sub(r'', '', on, flags=re.S) + strip = re.sub(r' bodies of=hops: .*?(?= -->|\. (?=[a-z_]+[=:])|$)\.?', '', strip, flags=re.S) + check(hopel(off) == "" and re.sub(r'', '', strip, flags=re.S) == re.sub(r'', '', off, flags=re.S), "(P8) %s: RIPWIRE_NO_HOP_BODY=1 == head minus the element (payload)" % f) +check('next=' not in re.search(r']*>', rd("p3.off")).group(0) and 'next=' not in re.search(r']*>', rd("p2.off")).group(0), "(P8) RIPWIRE_NO_HOP_BODY=1: no past-the-gate next=") +# X3 +for f in ("p1", "p1f", "p3f", "p4"): + check(all("--" not in c for c in re.findall(r'', rd(f), re.S)), "(X3) %s: no -- inside a legend comment" % f) +sys.exit(1 if fails else 0) +PY +python3 "$TMP/chk.py" "$TMP" "$BIN" "$ROOT"; fail=$? + +# P7: MCP path_between twin +python3 - "$BIN" "$ROOT/$WEB" "$TMP" <<'PY' || fail=1 +import json, re, subprocess, sys +binp, web, tmp = sys.argv[1], sys.argv[2], sys.argv[3] +cases = [("fetch", "compose", "p1"), ("stepOne", "stepFour", "p2"), ("bigEntry", "bigSink", "p4"), ("stepOne", "stepFive", "p3")] +reqs = [{"jsonrpc": "2.0", "id": 0, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "g", "version": "1"}}}] +for i, (a, b, _) in enumerate(cases): + reqs.append({"jsonrpc": "2.0", "id": i + 1, "method": "tools/call", "params": {"name": "path_between", "arguments": {"path": web, "from": a, "to": b}}}) +p = subprocess.run([binp, "--mcp", "--mcp-legend=inline"], input="".join(json.dumps(r) + "\n" for r in reqs), capture_output=True, text=True, timeout=120, cwd=web) +resp = {} +for line in p.stdout.splitlines(): + try: o = json.loads(line) + except ValueError: continue + if isinstance(o.get("id"), int) and o["id"] > 0: + resp[o["id"]] = "".join(c.get("text", "") for c in o.get("result", {}).get("content", [])) +bad = 0 +el = lambda t: (re.search(r'', t, re.S) or re.search(r'$', t)).group(0) +for i, (a, b, f) in enumerate(cases): + cli = open(tmp + "/" + f).read(); mcp = resp.get(i + 1, "") + if f in ("p2", "p3"): + m = re.search(r']*next="([^"]*)"', mcp); good = bool(m) and m.group(1) == "--expand=src/chain.ts:5:stepTwo" and el(mcp) == "" + else: + good = el(mcp) != "" and el(mcp) == el(cli) + print(" %s (P7) MCP path_between %s,%s: %s" % ("PASS" if good else "FAIL", a, b, "the CLI's element" if f not in ("p2", "p3") else "the past-the-gate next=")) + bad += 0 if good else 1 +sys.exit(1 if bad else 0) +PY + +# X1 determinism, X2 well-formed +run "$WEB" --path=fetch,compose >"$TMP/x1" +if cmp -s "$TMP/p1" "$TMP/x1"; then echo " PASS (X1) deterministic"; else echo " FAIL (X1) two runs differ"; fail=1; fi +if command -v xmllint >/dev/null 2>&1; then + for f in p1 p2 p3 p4 p1f p9a p9b; do + if xmllint --noout "$TMP/$f" 2>/dev/null; then echo " PASS (X2) $f well-formed"; else echo " FAIL (X2) $f not well-formed"; fail=1; fi + done +else + echo " SKIP (X2) xmllint absent" +fi + +# I: optional base-binary identity of the A/B handle +if [ -n "${RIPWIRE_BASE_BIN:-}" ] && [ -x "${RIPWIRE_BASE_BIN}" ]; then + i=0 + while IFS='|' read -r d a; do + i=$(( i + 1 )) + ( cd "$d" || exit 1; "$RIPWIRE_BASE_BIN" "$ROOT/$d" --no-cache "$a" 2>/dev/null ) | sed 's/ at="[^"]*"//' >"$TMP/i.base" + ( cd "$d" || exit 1; RIPWIRE_NO_HOP_BODY=1 "$BIN" "$ROOT/$d" --no-cache "$a" 2>/dev/null ) | sed 's/ at="[^"]*"//' >"$TMP/i.head" + if [ -s "$TMP/i.base" ] && cmp -s "$TMP/i.base" "$TMP/i.head"; then echo " PASS (I$i) RIPWIRE_NO_HOP_BODY=1 == base: $d $a"; else echo " FAIL (I$i) differs from base: $d $a"; fail=1; fi + done < Promise + +export function compose(handlers: Handler[]): Handler { + return async (r: Request) => { + let res = new Response('') + for (const h of handlers) { + res = await h(r) + } + return res + } +} diff --git a/test/hopbodyfix/web/src/router.ts b/test/hopbodyfix/web/src/router.ts new file mode 100644 index 000000000..686d54617 --- /dev/null +++ b/test/hopbodyfix/web/src/router.ts @@ -0,0 +1,11 @@ +import type { Handler } from './compose' + +const table: Record = {} + +export function getPath(request: Request): string { + return new URL(request.url).pathname +} + +export function matchRoute(path: string): Handler[] { + return table[path] ?? [] +} diff --git a/test/hopbodyfix/web/src/server.ts b/test/hopbodyfix/web/src/server.ts new file mode 100644 index 000000000..3739e8bd4 --- /dev/null +++ b/test/hopbodyfix/web/src/server.ts @@ -0,0 +1,29 @@ +// A tiny request pipeline: Server.fetch is a one-line wrapper, Server.dispatch holds the flow. +import { compose } from './compose' +import { getPath, matchRoute } from './router' + +export class Server { + fetch(request: Request): Promise { + return this.dispatch(request) + } + + dispatch(request: Request): Promise { + const path = getPath(request) + const handlers = matchRoute(path) + if (handlers.length === 0) { + return Promise.resolve(new Response('not found', { status: 404 })) + } + const composed = compose(handlers) + return composed(request) + } + + // two calls: not a thin wrapper, never followed + listen(request: Request): Promise { + auditRequest(request) + return this.dispatch(request) + } +} + +export function auditRequest(request: Request): void { + console.log(request.url) +} diff --git a/test/hopbodyfix/web/src/trip.ts b/test/hopbodyfix/web/src/trip.ts new file mode 100644 index 000000000..1f4c1f4a3 --- /dev/null +++ b/test/hopbodyfix/web/src/trip.ts @@ -0,0 +1,26 @@ +// A two-hop chain whose middle hop no question word names: the graph-verb block prints its body. +export function gatewayEntry(input: string): string { + const a = input.length + const b = a + 1 + const c = String(b) + return relayHop(input + c) +} +export function relayHop(input: string): string { + const trimmed = input.trim() + const upper = trimmed.toUpperCase() + const marked = upper + '!' + return finalStage(marked) +} +export function finalStage(input: string): string { + return input +} +// decoys the question's words rank above the middle hop +export function gatewayEntryCount(): number { + return 1 +} +export function finalStageCount(): number { + return 2 +} +export function gatewayFinalStageLabel(): string { + return 'gateway final stage' +} diff --git a/test/regression.sh b/test/regression.sh index fb40292ad..7b133752a 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 composedhopscheck 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 epmanifestcheck 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 fnslotcheck forautobodycheck forblowupcheck forbodyfoldcheck forbodyslotcheck forbudgetmonotoncheck forcalibfactscheck forcallerhopcheck forcompresscheck forctorhopcheck fordisclosurecheck forhdrshapecheck forheadrrcheck forinitslotscheck forlenscheck formatgatecheck formaxtokenscheck formembernamescheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forroutecheck 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 jsdefnamingcheck 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 mapstemcheck 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 namekeyhopcheck 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 pathpaircheck 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 pyaliascallcheck 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 queuehopcheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck receiverfixescheck 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 testonlydeadcheck 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 writtenspellingcheck 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 composedhopscheck 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 epmanifestcheck 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 fnslotcheck forautobodycheck forblowupcheck forbodyfoldcheck forbodyslotcheck forbudgetmonotoncheck forcalibfactscheck forcallerhopcheck forcompresscheck forctorhopcheck fordisclosurecheck forhdrshapecheck forheadrrcheck forinitslotscheck forlenscheck formatgatecheck formaxtokenscheck formembernamescheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forroutecheck 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 hopbodycheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck ifacedispatchcheck impactdepthcheck impactimportcheck impactpartitioncheck impacttypecheck importcapcheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javamethodrefcheck javarubycheck jsdefnamingcheck 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 mapstemcheck 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 namekeyhopcheck 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 pathpaircheck 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 pyaliascallcheck 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 queuehopcheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck receiverfixescheck 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 testonlydeadcheck 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 writtenspellingcheck 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)" From 103b7c36f9c811ae097df0ff873f10e5ea67cdd4 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:47:35 -0400 Subject: [PATCH 34/95] for: the body fold follows a thin wrapper into its callee (WRAPPER-FOLLOW) When a body the --for fold serves (or the body slot's owner) is a thin delegator (exactly one proven out-edge, no by-name/ambiguous/unresolved call beside it, at most 3 statement lines past the signature, e.g. `fetch` returning `this.#dispatch(...)`), the callee's body follows it as , at most 2 levels, outside budget_bytes= and whole up to the same 4000 B guard (else truncated="1" lines= next=). A delegate whose text the answer already carries stops the chain. Present-only follow_of= clause in both dialects; RIPWIRE_NO_HOP_BODY=1 turns it off. hono-12 default --for now carries #dispatch's body after fetch. In-sample: S1 3 rows (+4.3 KB), MF1-DEV 2 rows, LARGE-DEV 3 rows; no row loses an item. Gate: test/hopbodycheck.sh F1-F6. Co-Authored-By: Claude Opus 5.5 --- changelog.d/for-wrapper-follow.md | 13 ++++ docs/LIMITS.md | 11 ++-- docs/TUNING.md | 18 +++--- src/forfold.h | 71 ++++++++++++++++++++-- src/hopbody.h | 92 +++++++++++++++++++++++++++-- src/verbs_for.h | 11 +++- test/hopbodycheck.sh | 70 ++++++++++++++++++---- test/hopbodyfix/follow/src/core.ts | 10 ++++ test/hopbodyfix/follow/src/entry.ts | 14 +++++ test/hopbodyfix/follow/src/route.ts | 6 ++ test/hopbodyfix/follow/src/stats.ts | 11 ++++ test/hopbodyfix/loose/src/entry.ts | 4 ++ test/hopbodyfix/loose/src/tally.ts | 11 ++++ test/hopbodyfix/loose/src/worker.ts | 7 +++ 14 files changed, 315 insertions(+), 34 deletions(-) create mode 100644 changelog.d/for-wrapper-follow.md create mode 100644 test/hopbodyfix/follow/src/core.ts create mode 100644 test/hopbodyfix/follow/src/entry.ts create mode 100644 test/hopbodyfix/follow/src/route.ts create mode 100644 test/hopbodyfix/follow/src/stats.ts create mode 100644 test/hopbodyfix/loose/src/entry.ts create mode 100644 test/hopbodyfix/loose/src/tally.ts create mode 100644 test/hopbodyfix/loose/src/worker.ts diff --git a/changelog.d/for-wrapper-follow.md b/changelog.d/for-wrapper-follow.md new file mode 100644 index 000000000..6fbd96c1d --- /dev/null +++ b/changelog.d/for-wrapper-follow.md @@ -0,0 +1,13 @@ +### Added — the `--for` body fold follows a one-line wrapper into the function it calls + +When a body the `--for` fold serves is a thin delegator (one proven callee, no other call, at most 3 statement lines, such as +`fetch` returning `this.#dispatch(...)`), the callee's body is now printed right after it as ``, up to two +levels (`handle -> fetch -> #dispatch`). A callee bound by name alone, a body with a second call or a fourth statement line, and +a body the answer already carries are never followed. The followed bodies ride outside the fold's `budget_bytes=`, each whole up +to 4000 bytes (a longer one is cut and says so). `RIPWIRE_NO_HOP_BODY=1` turns it off. + +Measured: hono's default `--for "How does a request flow from app.fetch to a handler's Response?"` now carries `#dispatch`'s body +(router match, Context, the one-handler fast path, `compose`, error handling) after `fetch` (+1.9 KB). Over the in-sample tables +the follow fires on 3 of 163 S1 rows (+4.3 KB, all already complete), 2 of 39 MF1-DEV rows (+3.0 KB; ninja `Plan::AddTarget -> +AddSubTarget`) and 3 of 56 LARGE-DEV rows (+5.7 KB, one body cut at the 4000-byte guard); no row loses an item. +Gate: `test/hopbodycheck.sh`. diff --git a/docs/LIMITS.md b/docs/LIMITS.md index 39d5fb062..19052beaf 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 | | --- | --- | --- | --- | -| 289 | 114 | 172 | **117** | +| 290 | 114 | 173 | **117** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 289 + 7 is the 296 constants this generator parses out of `src/`. +are not counted as caps, and 290 + 7 is the 297 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 289 caps are classified -(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 149 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **140 of 290 caps are classified +(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 150 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 114 files that declare a cap — the 289 caps counted above, and no parameter. +One table for each of the 114 files that declare a cap — the 290 caps counted above, and no parameter. ### `src/abicheck.h` @@ -515,6 +515,7 @@ Discloses: `capped` | --- | --- | --- | --- | | `kHopBodyCapBytes` | `4000` | — | — | | `kPathHopBodyMaxHops` | `2` | — | — | +| `kWrapperFollowDepth` | `2` | — | — | ### `src/infra/blanktext.h` diff --git a/docs/TUNING.md b/docs/TUNING.md index 4124a83dc..530705fd4 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 | | --- | --- | --- | --- | --- | --- | -| 197 | 196 | 112 | 12 | **37** | 75 | +| 198 | 197 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **197 cap -declarations** under **196 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 **198 cap +declarations** under **197 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 196 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **73 declared since the sweep was +every run, which makes them a third population again. Enumerated over the 197 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **74 declared since the sweep was prepared, which no measurement has touched** (`kBackHopWidth`, `kBaseScopeScanCap`, -`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +67 -more). 111 + 12 + 73 = 196, and `emit` refuses to render a partition that does not add up. The +`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +68 +more). 111 + 12 + 74 = 197, 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 197" would be wrong in both halves +declared in `src/` at all: `kSituTestRowsShown`. Quoting "113 of 198" 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 197-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 198-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/src/forfold.h b/src/forfold.h index 0fd441aa0..9d38c35a0 100644 --- a/src/forfold.h +++ b/src/forfold.h @@ -30,7 +30,7 @@ // RIPWIRE_NO_BODY_FOLD (any value) turns the fold off — the A/B handle, the RIPWIRE_NO_BODY_SLOT precedent. #include "commentcoherence.h" // isCommentStopword — the member slice's question words -#include "hopbody.h" // renderBodyElement, spanInside / overlapsServed — the one body render and containment reading +#include "hopbody.h" // WRAPPER-FOLLOW: thinDelegate, renderBodyElement — a thin wrapper's callee body #include "forowner.h" // isParsedMemberOf — a class's members BY THE PARSER (the body slot's member reading) #include "lexical.h" // subtokens, isRouteStopword, lexicalScanText — a sliced class's member text, read as the index read it #include "model.h" @@ -105,6 +105,13 @@ inline constexpr std::string_view kForCompactBodyFoldLegend = "; fold kind=bodies: b t= n= p= l= top ranked rows' whole bodies, not the answer; served= of total= in budget_bytes=; " "slice/skip p= n= l=; slice members= of=: a big class's ctor, question-word members; skip bytes= next=; " "a skip makes sigs next= it, sigs_next= the signatures-only re-call"; +// WRAPPER-FOLLOW: present-only beside a (BodyFoldRender::follows > 0), after the fold clause. No "--". +inline constexpr std::string_view kForFoldFollowLegend = + "; b follow_of=W: the body of the one proven call W makes, W a thin wrapper served just before it (one callee, at most 3 statement " + "lines), followed at most 2 levels; outside budget_bytes=, whole up to 4000 bytes"; +inline constexpr std::string_view kForCompactFoldFollowLegend = + "; b follow_of=W: body of the one call thin wrapper W (<=3 lines) makes, <=2 levels, past budget_bytes=, whole to 4000 B"; +static_assert( kHopBodyCapBytes == 4000 && kWrapperFollowDepth == 2 && kThinBodyLines == 3, "the follow clauses spell the bounds: re-word them with the knobs" ); static_assert( kForCompactBodyFoldLegend.find( "b t= n= p= l=" ) != std::string_view::npos && kForBodyFoldLegend.find( "b t= n= p= l=" ) != std::string_view::npos, "both fold clauses define the element the fold carries (FIX1 B2)" ); @@ -118,6 +125,7 @@ struct BodyFoldInputs std::string_view rootArg; RedactCounts* redact; // no NoteIndex: a ranked row's note rides on its row once, never again in the fold bool compress; // --compress (the CLI only) + const Graph* g = nullptr; // WRAPPER-FOLLOW (hopbody.h thinDelegate): nullptr = no body is followed }; struct BodyFoldRender @@ -128,6 +136,7 @@ struct BodyFoldRender std::size_t served = 0; std::size_t total = 0; std::size_t slices = 0; // classes served as a member slice (each also has its skip row) + std::size_t follows = 0; // WRAPPER-FOLLOW: delegate bodies served after the thin wrapper that calls them () std::string firstSkipNext; // the first skip's next= value, XML-escaped; empty when every candidate was served std::vector bodies; // member-names: every body this answer carries whole (the slot's owner, the fold's, slice members) std::vector sliced; // member-names: the classes served as a member slice @@ -191,7 +200,7 @@ inline std::string skipRow( const BodyFoldInputs& in, NodeId id, std::string& es return x; } -// spanInside / overlapsServed (each text once, FIX1 N2) live in hopbody.h, the one containment reading the fold and the +// spanInside / overlapsServed (each text once, FIX1 N2) live in hopbody.h, the one containment reading the fold, its follows and the // path hop bodies share. // a class's direct members by the parser (isParsedMemberOf), in source order; one nested in another member is inside that @@ -345,6 +354,44 @@ inline std::optional refundOfInner( const IngestResult& ing, NodeId } return refund; } +// WRAPPER-FOLLOW: from a body the answer serves, the delegate bodies a chain of thin wrappers leads to (hopbody.h thinDelegate), at +// most kWrapperFollowDepth levels, each whole up to kHopBodyCapBytes (else cut and disclosed by packBodies' +// truncation). A delegate whose text the answer already carries stops the chain (its own follows ride where it is served). +struct FollowRender +{ + std::string xml; + std::size_t bytes = 0, count = 0; +}; +inline FollowRender renderFollows( const BodyFoldInputs& in, NodeId from, std::vector& served ) +{ + FollowRender out; + if( in.g == nullptr || !hopBodiesOn() ) + { + return out; + } + std::vector esc; + NodeId cur = from; + for( std::size_t depth = 0; depth < kWrapperFollowDepth; ++depth ) + { + const NodeId t = thinDelegate( in.ing, *in.g, cur ); + if( t == kNoNode || overlapsServed( in.ing, t, served, /*orHolds=*/true ) ) + { + break; + } + std::optional b = renderBodyElement( in.ing, t, kHopBodyCapBytes, in.compress, in.redact, in.rootArg, &in.lensRank ); + if( !b ) + { + break; + } + addBodyAttrs( *b, " follow_of=\"" + std::string( escapeXml( in.ing.symbols[cur].name, esc ) ) + "\"" ); + out.bytes += b->size(); + ++out.count; + out.xml += *b; + served.push_back( t ); + cur = t; + } + return out; +} } // namespace fold_detail // THE FOLD'S RENDER: the walk the header comment states. A body that does not fit is never rendered into the answer, and the @@ -420,16 +467,32 @@ inline BodyFoldRender renderForBodyFold( const BodyFoldInputs& in, const std::ve } } std::string bodies; + std::size_t followBytes = 0; + const auto follow = [ & ]( NodeId from ) + { + fold_detail::FollowRender f = fold_detail::renderFollows( in, from, served ); + bodies += f.xml; + followBytes += f.bytes; + out.follows += f.count; + }; + if( in.bodied != kNoNode ) + { + follow( in.bodied ); // the slot owner's body is its own element before the fold: its delegate leads the fold + } for( const fold_detail::FoldPiece& p : pieces ) { bodies += p.xml; + if( p.id != kNoNode ) + { + follow( p.id ); // right after the wrapper it explains + } } const std::string open = ""; constexpr std::string_view kClose = ""; out.xml = open + bodies + skips + std::string( kClose ); - out.bodyTokens = tokensForEmittedBytes( used, kBytesPerTokenBody ); - out.markupBytes = out.xml.size() - used; + out.bodyTokens = tokensForEmittedBytes( used + followBytes, kBytesPerTokenBody ); + out.markupBytes = out.xml.size() - used - followBytes; out.bodies = std::move( served ); ENSURES( used <= kForBodyFoldBudgetBytes, "the served bodies fit the fold's budget" ); return out; diff --git a/src/hopbody.h b/src/hopbody.h index 591ebb01f..a944dd5a8 100644 --- a/src/hopbody.h +++ b/src/hopbody.h @@ -4,15 +4,20 @@ // WHY. A reached --path answer prints each hop as a row and nothing else. When the path is short, the question is often // what happens INSIDE the middle hop: hono's `Hono.fetch -> Hono.#dispatch -> compose` names #dispatch, and the whole request flow // (path extraction, router match, Context, the one-handler fast path, compose, error handling) lives in its body, which the answer -// never printed (readout-40 G: 0 of 7 items; the same body through --expand: 6 of 7). +// never printed (readout-40 G: 0 of 7 items; the same body through --expand: 6 of 7). The --for body fold has the twin gap: it serves +// `fetch`, a one-line wrapper around `this.#dispatch(...)`, and stops there. // // WHAT. (1) PATH-HOP-BODY: a reached --path whose shown chain has at most kPathHopBodyMaxHops hops prints, after its hop rows, the body // of each INTERMEDIATE hop (never an endpoint, never one whose text the answer already carries: fold_detail::overlapsServed) inside , // each the --expand body element (no callee rows, no file context). A chain past the gate prints no body and the root carries -// next= (the first middle hop's --expand) unless another continuation already owns next=. +// next= (the first middle hop's --expand) unless another continuation already owns next=. (2) WRAPPER-FOLLOW: a body the --for fold +// serves that is a THIN DELEGATOR (thinDelegate: one proven callee, no other call, at most kThinBodyLines statement lines) is followed by +// its callee's body, , at most kWrapperFollowDepth levels. // THE BOUND IS A SPEW GUARD, NEVER A COMPLETENESS CUT (owner rule): each body is whole up to kHopBodyCapBytes; a longer one is cut at a // line end by packBodies' own truncation, which says so on the element (truncated="1" lines="1-K/T" next= the call serving the rest). +#include "graph.h" +#include "lexical.h" // lexicalScanText — the body text the index read #include "model.h" #include "nextverb.h" // nextFlag #include "sarif.h" // rootPrefixOf / rootRelativeUri — the --expand selector's path @@ -32,12 +37,16 @@ namespace rw { // The per-body spew guard (bytes of source text): whole below it, cut at a line end and disclosed above it. Measured over the in-sample -// --path rows (reports/path-hop-body.md): the bodies these answers need are 0.1-3.4 KB. +// --path rows and the --for fold follows (reports/path-hop-body.md): the bodies these answers need are 0.4-3.6 KB. inline constexpr std::size_t kHopBodyCapBytes = 4000; // The hop-count gate: a chain of at most this many hops prints its middle hop's body (kPathHopBodyMaxHops - 1 = one body). Measured // (reports/path-hop-body.md, the curve over the in-sample --path rows): 2 hops buys hono-12 its 6 items; 3 hops adds flake8-14 // (+1.6 KB, already complete), 4 hops fzf-14 and tmux-14 (+13 KB, both already complete) and nothing else; past the gate next= names the body. inline constexpr std::size_t kPathHopBodyMaxHops = 2; +// A thin delegator's body: at most this many statement lines (a line holding more than brackets and separators, not a comment). +inline constexpr std::size_t kThinBodyLines = 3; +// How many delegations the fold follows from one served body (fetch -> #dispatch is one; a handle -> fetch -> #dispatch two). +inline constexpr std::size_t kWrapperFollowDepth = 2; namespace fold_detail { @@ -64,7 +73,7 @@ inline bool overlapsServed( const IngestResult& ing, NodeId a, std::span renderBodyElement( const IngestResult& ing, No return x.substr( 0, open ) + x.substr( close + 1, x.size() - kClose.size() - close - 1 ); } +// Is `line` a statement line: anything but whitespace and bracket/separator characters, and not a whole-line comment. +inline bool isStatementLine( std::string_view line ) noexcept +{ + const std::size_t a = line.find_first_not_of( " \t\r" ); + if( a == std::string_view::npos ) + { + return false; + } + const std::string_view t = line.substr( a ); + if( t.starts_with( "//" ) || t.starts_with( "#" ) || t.starts_with( "/*" ) || t.starts_with( "*" ) || t.starts_with( "--" ) ) + { + return false; + } + return t.find_first_not_of( "{}()[];, \t\r" ) != std::string_view::npos; +} + +// THE THIN DELEGATOR: the callee `id` wraps, or kNoNode. Decided only when provably unique (the inverted honesty rule): exactly one +// resolved out-edge, a proven call (not by name alone, not a link), no ambiguous or unresolved call beside it, a callee with a body, and +// at most kThinBodyLines statement lines in the body past the signature. Anything else is not a delegator and nothing is followed. +inline NodeId thinDelegate( const IngestResult& ing, const Graph& g, NodeId id ) +{ + if( id >= ing.symbols.size() || id + 1 >= g.outOff.size() || g.outOff[id + 1] - g.outOff[id] != 1 ) + { + return kNoNode; + } + const std::uint32_t e = g.outOff[id]; + const NodeId t = g.outTargets[e]; + if( t == id || t >= ing.symbols.size() || ( e < g.outNameOnly.size() && g.outNameOnly[e] != 0 ) + || ( id < g.ambOut.size() && g.ambOut[id] != 0 ) || ( id < g.unresolvedOut.size() && g.unresolvedOut[id] != 0 ) ) + { + return kNoNode; + } + const Symbol& s = ing.symbols[id]; + const Symbol& ts = ing.symbols[t]; + if( s.kind == SymKind::ModuleScope || ts.kind == SymKind::ModuleScope || ts.endByte <= ts.sigStartByte || s.endByte <= s.sigEndByte ) + { + return kNoNode; + } + std::string scratch; + const std::string* text = lexicalScanText( ing, s.fileId, scratch ); + if( text == nullptr || s.endByte > text->size() || s.sigEndByte < s.sigStartByte ) + { + return kNoNode; + } + std::string_view body = std::string_view( *text ).substr( s.sigEndByte, s.endByte - s.sigEndByte ); + std::size_t statements = 0; + while( !body.empty() ) + { + const std::size_t nl = body.find( '\n' ); + const std::string_view line = body.substr( 0, nl ); + statements += isStatementLine( line ) ? 1u : 0u; + if( statements > kThinBodyLines ) + { + return kNoNode; + } + body = nl == std::string_view::npos ? std::string_view() : body.substr( nl + 1 ); + } + return statements > 0 ? t : kNoNode; +} + // The --expand selector of one definition, as the answer spells p= (root-relative on a single root), unescaped. inline std::string expandSelectorOf( const IngestResult& ing, NodeId id, std::string_view rootArg ) { @@ -177,4 +246,19 @@ inline std::string_view pathHopNextLegend( bool on ) noexcept return on ? "next=: the chain has more than 2 hops, so no middle hop body prints; the first middle hop's expand. " : ""; } +// Insert ` attrs` into the first `', at ); + if( close != std::string::npos ) + { + bodyXml.insert( close, attrs ); + } +} + } // namespace rw diff --git a/src/verbs_for.h b/src/verbs_for.h index 73e4e8478..8770b27df 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -578,6 +578,8 @@ struct ForLensHeaderParts bool bodyFoldPresent = false; // body-fold: a compact-route answer in the default regime has fold candidates // (forfold.h forBodyFoldCandidates), so a rides and its clause defines it; // set after construction, cleared with the section + bool foldFollowPresent = false; // WRAPPER-FOLLOW (forfold.h renderFollows): the fold carries a (set after + // the fold renders), so its clause defines it bool memberNamesPresent = false; // member-names (formembers.h): a shown owner row carries mem= (set after the // splice), so its clause defines it bool initSlotsPresent = false; // FOR-SLOTS (forslots.h): a shown initializer row carries slots= (set after the @@ -920,6 +922,7 @@ inline void appendCompactForLegend( std::string& h, const ForLensHeaderParts& p, h += ownerHopLegend( p, /*compact=*/true ); // owner-hop: present-only, right after the hops clause h += bodySlotLegend( p, /*compact=*/true ); // for-body-slot: present-only, after the owner clause h += bodyFoldLegend( p, /*compact=*/true ); // body-fold: present-only, after the slot clause + h += presentOnlyClause( p.foldFollowPresent, /*compact=*/true, rw::kForCompactFoldFollowLegend, rw::kForFoldFollowLegend ); // WRAPPER-FOLLOW h += presentOnlyClause( p.memberNamesPresent, /*compact=*/true, rw::kForCompactMemberNamesLegend, rw::kForMemberNamesLegend ); // member-names: after the fold clause h += peerRowClauses( p, /*compact=*/true ); // FOR-SLOTS + OWNER-CTOR-HOP: present-only, after mem= h += presentOnlyClause( p.callerHopPresent, /*compact=*/true, rw::kForCompactCallerHopLegend, rw::kForCallerHopLegend ); // mf1-nx: present-only, after the fold clause @@ -1068,6 +1071,7 @@ inline std::string forLensHeaderText( const ForLensHeaderParts& p, bool withRout h.append( ownerHopLegend( p, /*compact=*/false ) ); // owner-hop: present-only, right after the bundle clause h.append( bodySlotLegend( p, /*compact=*/false ) ); // for-body-slot: present-only, after the owner clause h.append( bodyFoldLegend( p, /*compact=*/false ) ); // body-fold: present-only, after the slot clause + h.append( presentOnlyClause( p.foldFollowPresent, /*compact=*/false, rw::kForCompactFoldFollowLegend, rw::kForFoldFollowLegend ) ); // WRAPPER-FOLLOW h.append( presentOnlyClause( p.memberNamesPresent, /*compact=*/false, rw::kForCompactMemberNamesLegend, rw::kForMemberNamesLegend ) ); // member-names h.append( peerRowClauses( p, /*compact=*/false ) ); // FOR-SLOTS + OWNER-CTOR-HOP h.append( presentOnlyClause( p.callerHopPresent, /*compact=*/false, rw::kForCompactCallerHopLegend, rw::kForCallerHopLegend ) ); // mf1-nx: present-only, after the fold clause @@ -4064,7 +4068,12 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co { if( sigsPreRendered && !enrich.surfaceOff && !enrich.hopsCutMarker ) { - fold = rw::renderForBodyFold( rw::BodyFoldInputs{ ing, cfg.forTask, slot.served ? slotOwner.id : rw::kNoNode, lensRank, flRootArg, redactPtr, cfg.compress }, foldIds ); + fold = rw::renderForBodyFold( rw::BodyFoldInputs{ ing, cfg.forTask, slot.served ? slotOwner.id : rw::kNoNode, lensRank, flRootArg, redactPtr, cfg.compress, &g }, foldIds ); + if( fold.follows > 0 && !fold.xml.empty() ) + { + headerParts.foldFollowPresent = true; // WRAPPER-FOLLOW: the clause rides beside the attribute it defines + headerStr = buildForHeader( /*withRouteAttr=*/true, /*withTaskEcho=*/true, {} ); + } } if( fold.xml.empty() ) { diff --git a/test/hopbodycheck.sh b/test/hopbodycheck.sh index f0047fa2d..9d7604e86 100644 --- a/test/hopbodycheck.sh +++ b/test/hopbodycheck.sh @@ -10,9 +10,12 @@ # over 4000 B is cut at a line end and says so (truncated="1" lines="1-K/T" next= serves the rest). A chain of # more hops prints no body and its root carries next= (the first middle hop's --expand). Inside a --for answer's # graph-verb block a middle hop whose text the answer already carries (a served class holding it) prints no copy. -# LEGEND every new reading is present-only: the compact path reading `bodies of=hops` and the full path clause ride -# exactly when the element does. -# A/B RIPWIRE_NO_HOP_BODY=1 is the answer without it. +# WRAPPER-FOLLOW a --for fold body that is a THIN DELEGATOR (one proven callee, no other call, at most 3 statement lines) is +# followed by its callee's body , at most 2 levels, never a text the answer already +# carries; a by-name edge, a second call, or a fourth statement line is not a delegator. +# LEGEND every new reading is present-only: the compact path reading `bodies of=hops`, the full path clause, the +# fold's follow_of= clause ride exactly when the element/attribute does. +# A/B RIPWIRE_NO_HOP_BODY=1 is the answer without either mechanism. # # Arms (colour on 5163ffbdb in brackets): # P1 [P] fetch -> dispatch -> compose: one with dispatch's body, CDATA = its source text; no endpoint body @@ -25,6 +28,13 @@ # P6 [P] legend present-only, compact and full: the reading rides iff the element does; the past-the-gate next= reading in full # P7 [P] MCP path_between prints the CLI's element byte for byte (P1, P4) and the 3- and 4-hop next= # P8 [G] RIPWIRE_NO_HOP_BODY=1: the path answer is the head answer minus the element, its clause and the 4-hop next= +# F1 [P] the 2-level follow: handleRequest -> forwardInput -> runPipeline, each right after its wrapper, +# CDATA = the source text; the follow_of clause rides +# F2 [G] a one-call body of four statement lines (handleTimedRequest) is never followed +# F3 [G] a two-call body (listen) is never followed; a delegate inside a served body (fetch -> dispatch in Server) prints no copy +# F4 [G] a by-name delegate (worker.crunchInput, via=name) is never followed +# F5 [G] each text once: no two of one answer share p= and l= +# F6 [G] RIPWIRE_NO_HOP_BODY=1: no follow_of=, no clause, answer == head minus the follow bodies and clause # X1 [G] determinism X2 [G] xmllint well-formed X3 [G] no "--" inside a legend comment # I [optional, RIPWIRE_BASE_BIN=] RIPWIRE_NO_HOP_BODY=1 head == the base binary on every question here # @@ -41,13 +51,19 @@ echo "hopbodycheck: BIN=$BIN" [ -x "$BIN" ] || { echo "no ripwire binary at $BIN — build first"; exit 2; } command -v python3 >/dev/null 2>&1 || { echo "python3 required"; exit 2; } cd "$ROOT" -WEB="test/hopbodyfix/web" -[ -d "$WEB/src" ] || { echo "fixture test/hopbodyfix missing"; exit 2; } +WEB="test/hopbodyfix/web"; FOL="test/hopbodyfix/follow"; LOO="test/hopbodyfix/loose" +[ -d "$WEB/src" ] && [ -d "$FOL/src" ] && [ -d "$LOO/src" ] || { echo "fixture test/hopbodyfix missing"; exit 2; } TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT export HOME="$TMP/home"; mkdir -p "$HOME" run(){ local d="$1"; shift; ( cd "$d" || exit 1; "$BIN" "$ROOT/$d" --no-cache "$@" 2>/dev/null ); } +T_FOLLOW="How is a request handled?" +T_TIMED="How does handling a request work?" +T_WEB="How does a request flow from fetch to its Response?" +T_LISTEN="How does the server listen for a request?" +T_LOOSE="How is a loose request handled?" + run "$WEB" --path=fetch,compose >"$TMP/p1" run "$WEB" --path=stepOne,stepFour >"$TMP/p2" run "$WEB" --path=stepOne,stepFive >"$TMP/p3" @@ -63,6 +79,15 @@ RIPWIRE_NO_HOP_BODY=1 run "$WEB" --path=fetch,compose >"$TMP/p1.off" RIPWIRE_NO_HOP_BODY=1 run "$WEB" --path=stepOne,stepFour >"$TMP/p2.off" RIPWIRE_NO_HOP_BODY=1 run "$WEB" --path=stepOne,stepFive >"$TMP/p3.off" RIPWIRE_NO_HOP_BODY=1 run "$WEB" --path=bigEntry,bigSink >"$TMP/p4.off" +run "$FOL" --for="$T_FOLLOW" >"$TMP/f1" +run "$FOL" --for="$T_FOLLOW" --legend=full >"$TMP/f1f" +RIPWIRE_NO_HOP_BODY=1 run "$FOL" --for="$T_FOLLOW" >"$TMP/f1.off" +run "$FOL" --for="$T_TIMED" >"$TMP/f2" +run "$WEB" --for="$T_WEB" >"$TMP/f3a" +run "$WEB" --for="$T_LISTEN" >"$TMP/f3b" +run "$LOO" --for="$T_LOOSE" >"$TMP/f4" +run "$LOO" --callees=handleLooseRequest >"$TMP/f4c" + cat > "$TMP/chk.py" <<'PY' import html, re, shlex, subprocess, sys, os T, BIN, ROOT = sys.argv[1], sys.argv[2], sys.argv[3] @@ -90,7 +115,7 @@ def is_source(b, d): def paste(nxt, d): argv = shlex.split(nxt) return subprocess.run([BIN, os.path.join(ROOT, d), "--no-cache"] + argv, cwd=os.path.join(ROOT, d), capture_output=True).stdout.decode("utf-8", "replace") -WEB = "test/hopbodyfix/web" +WEB, FOL = "test/hopbodyfix/web", "test/hopbodyfix/follow" # P1 p1 = rd("p1"); el = hopel(p1); bs = bodies(el) @@ -135,8 +160,30 @@ for f in ("p1", "p4"): strip = re.sub(r' bodies of=hops: .*?(?= -->|\. (?=[a-z_]+[=:])|$)\.?', '', strip, flags=re.S) check(hopel(off) == "" and re.sub(r'', '', strip, flags=re.S) == re.sub(r'', '', off, flags=re.S), "(P8) %s: RIPWIRE_NO_HOP_BODY=1 == head minus the element (payload)" % f) check('next=' not in re.search(r']*>', rd("p3.off")).group(0) and 'next=' not in re.search(r']*>', rd("p2.off")).group(0), "(P8) RIPWIRE_NO_HOP_BODY=1: no past-the-gate next=") +# F1 +f1 = rd("f1"); bs = bodies(f1); names = [b[0].get("n") for b in bs] +fo = {b[0].get("n"): b for b in bs if "follow_of" in b[0]} +check(fo.get("forwardInput", [{}])[0].get("follow_of") == "handleRequest" and fo.get("runPipeline", [{}])[0].get("follow_of") == "forwardInput", "(F1) the 2-level follow: %r" % {k: v[0].get("follow_of") for k, v in fo.items()}) +check("handleRequest" in names and names.index("forwardInput") == names.index("handleRequest") + 1 and names.index("runPipeline") == names.index("forwardInput") + 1 if "forwardInput" in names and "runPipeline" in names and "handleRequest" in names else False, "(F1) each follow right after its wrapper") +check("forwardInput" in fo and is_source(fo["forwardInput"], FOL) and "runPipeline" in fo and is_source(fo["runPipeline"], FOL) and "segments.length === 0" in fo["runPipeline"][1], "(F1) follow CDATA are the definitions' source text, whole") +check("follow_of=W" in legend(f1) and "follow_of=W" in legend(rd("f1f")), "(F1) the follow_of clause rides in both legends") +# F2-F4 +check(not re.search(r'follow_of="handleTimedRequest"', rd("f2")) and re.search(r']*n="handleTimedRequest"', rd("f2")), "(F2) the 4-statement-line wrapper is served and never followed") +check("follow_of=" not in rd("f3a") and "follow_of=W" not in legend(rd("f3a")), "(F3) fetch's delegate lies inside the served Server body: no copy, no clause") +check(re.search(r']*n="listen"', rd("f3b")) and 'follow_of="listen"' not in rd("f3b"), "(F3) the two-call listen is never followed") +check('via="name"' in rd("f4c") and re.search(r']*n="handleLooseRequest"', rd("f4")) and "follow_of=" not in rd("f4"), "(F4) a by-name delegate is never followed") +# F5 +for f in ("f1", "f2", "f3a", "f3b", "f4", "p1", "p9a", "p9b"): + keys = [(b[0].get("p"), b[0].get("l")) for b in bodies(rd(f))] + check(len(keys) == len(set(keys)), "(F5) %s: each text once" % f) +# F6 +off = rd("f1.off") +check("follow_of" not in off, "(F6) RIPWIRE_NO_HOP_BODY=1: no follow_of= and no clause") +strip = re.sub(r']*follow_of="[^"]*"[^>]*>.*?', '', f1, flags=re.S) +mask = lambda t: re.sub(r' est_tokens="\d+"', '', re.sub(r'', '', t, flags=re.S)) +check(mask(strip) == mask(off), "(F6) the --for answer minus the follow bodies == the RIPWIRE_NO_HOP_BODY=1 answer (payload, est_tokens masked)") # X3 -for f in ("p1", "p1f", "p3f", "p4"): +for f in ("p1", "p1f", "p3f", "p4", "f1", "f1f"): check(all("--" not in c for c in re.findall(r'', rd(f), re.S)), "(X3) %s: no -- inside a legend comment" % f) sys.exit(1 if fails else 0) PY @@ -171,10 +218,10 @@ sys.exit(1 if bad else 0) PY # X1 determinism, X2 well-formed -run "$WEB" --path=fetch,compose >"$TMP/x1" -if cmp -s "$TMP/p1" "$TMP/x1"; then echo " PASS (X1) deterministic"; else echo " FAIL (X1) two runs differ"; fail=1; fi +run "$WEB" --path=fetch,compose >"$TMP/x1"; run "$FOL" --for="$T_FOLLOW" >"$TMP/x2" +if cmp -s "$TMP/p1" "$TMP/x1" && cmp -s "$TMP/f1" "$TMP/x2"; then echo " PASS (X1) deterministic"; else echo " FAIL (X1) two runs differ"; fail=1; fi if command -v xmllint >/dev/null 2>&1; then - for f in p1 p2 p3 p4 p1f p9a p9b; do + for f in p1 p2 p3 p4 p1f f1 f1f p9a p9b; do if xmllint --noout "$TMP/$f" 2>/dev/null; then echo " PASS (X2) $f well-formed"; else echo " FAIL (X2) $f not well-formed"; fail=1; fi done else @@ -193,7 +240,8 @@ if [ -n "${RIPWIRE_BASE_BIN:-}" ] && [ -x "${RIPWIRE_BASE_BIN}" ]; then $WEB|--path=fetch,compose $WEB|--path=stepOne,stepFive $WEB|--path=bigEntry,bigSink -$WEB|--for=Trace the path from gatewayEntry() to finalStage() +$FOL|--for=$T_FOLLOW +$WEB|--for=$T_WEB ARGV else echo " SKIP (I) RIPWIRE_BASE_BIN unset or not executable — base-binary identity not run here" diff --git a/test/hopbodyfix/follow/src/core.ts b/test/hopbodyfix/follow/src/core.ts new file mode 100644 index 000000000..20ebc54d6 --- /dev/null +++ b/test/hopbodyfix/follow/src/core.ts @@ -0,0 +1,10 @@ +// where the flow lives +export async function runPipeline(input: Request): Promise { + const url = new URL(input.url) + const segments = url.pathname.split('/').filter(Boolean) + if (segments.length === 0) { + return new Response('index', { status: 200 }) + } + const text = segments.map((s) => s.toUpperCase()).join('-') + return new Response(text, { status: 200 }) +} diff --git a/test/hopbodyfix/follow/src/entry.ts b/test/hopbodyfix/follow/src/entry.ts new file mode 100644 index 000000000..93e0c4ab3 --- /dev/null +++ b/test/hopbodyfix/follow/src/entry.ts @@ -0,0 +1,14 @@ +import { forwardInput } from './route' + +// the public entry: a one-line wrapper +export function handleRequest(request: Request): Promise { + return forwardInput(request) +} + +// one call, but four statement lines: not thin, never followed +export function handleTimedRequest(request: Request): Promise { + const started = Date.now() + const label = request.url + const tag = label + started + return forwardInput(new Request(tag)) +} diff --git a/test/hopbodyfix/follow/src/route.ts b/test/hopbodyfix/follow/src/route.ts new file mode 100644 index 000000000..8b8ecf411 --- /dev/null +++ b/test/hopbodyfix/follow/src/route.ts @@ -0,0 +1,6 @@ +import { runPipeline } from './core' + +// a second one-line wrapper: the follow goes on to its callee +export function forwardInput(input: Request): Promise { + return runPipeline(input) +} diff --git a/test/hopbodyfix/follow/src/stats.ts b/test/hopbodyfix/follow/src/stats.ts new file mode 100644 index 000000000..9381ca6b3 --- /dev/null +++ b/test/hopbodyfix/follow/src/stats.ts @@ -0,0 +1,11 @@ +// bookkeeping about handled requests (ranked by the question's words, never on the path) +let handledRequests = 0 + +export function countHandledRequest(request: Request): number { + handledRequests = handledRequests + (request.url.length > 0 ? 1 : 0) + return handledRequests +} + +export function resetHandledRequests(): void { + handledRequests = 0 +} diff --git a/test/hopbodyfix/loose/src/entry.ts b/test/hopbodyfix/loose/src/entry.ts new file mode 100644 index 000000000..c7fb249e0 --- /dev/null +++ b/test/hopbodyfix/loose/src/entry.ts @@ -0,0 +1,4 @@ +// one call through an untyped receiver: bound by name alone, so never followed +export function handleLooseRequest(request: Request, worker: any): Promise { + return worker.crunchInput(request) +} diff --git a/test/hopbodyfix/loose/src/tally.ts b/test/hopbodyfix/loose/src/tally.ts new file mode 100644 index 000000000..2c39e847c --- /dev/null +++ b/test/hopbodyfix/loose/src/tally.ts @@ -0,0 +1,11 @@ +// bookkeeping about handled loose requests (ranked by the question's words, never on the path) +let looseRequests = 0 + +export function countLooseRequest(request: Request): number { + looseRequests = looseRequests + (request.url.length > 0 ? 1 : 0) + return looseRequests +} + +export function resetLooseRequests(): void { + looseRequests = 0 +} diff --git a/test/hopbodyfix/loose/src/worker.ts b/test/hopbodyfix/loose/src/worker.ts new file mode 100644 index 000000000..6909524c2 --- /dev/null +++ b/test/hopbodyfix/loose/src/worker.ts @@ -0,0 +1,7 @@ +export class Cruncher { + crunchInput(input: Request): Promise { + const url = new URL(input.url) + const parts = url.pathname.split('/') + return Promise.resolve(new Response(parts.join(':'))) + } +} From 6b06c191f0a1164fe8012281b33d03c601ae3821 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 02:53:49 -0400 Subject: [PATCH 35/95] for/path: one open-tag splice helper; fold clause pair in one call; graph-verb block renamed quality-delta over 68b7daa2..HEAD gated 3: the body slot's pick= splice and the follow_of= splice were one helper written twice (now hopbody.h insertOpenTagAttrs, used by both); appendCompactForLegend grew past its verbosity bar (the follow_of clause now rides inside bodyFoldLegend); appendSubjectGraphVerb's arity change reads as a contract change, so the served-bodies variant is appendSubjectGraphVerbBlock (hazardpatterncheck's table row follows). No output byte changes. Co-Authored-By: Claude Opus 5.5 --- src/forfold.h | 2 +- src/forgraphverb.h | 2 +- src/forowner.h | 12 ++---------- src/hopbody.h | 11 ++++++----- src/lexical.h | 2 +- src/verbs_for.h | 14 +++++++------- test/hazardpatterncheck.sh | 2 +- 7 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/forfold.h b/src/forfold.h index 9d38c35a0..61c6f722c 100644 --- a/src/forfold.h +++ b/src/forfold.h @@ -383,7 +383,7 @@ inline FollowRender renderFollows( const BodyFoldInputs& in, NodeId from, std::v { break; } - addBodyAttrs( *b, " follow_of=\"" + std::string( escapeXml( in.ing.symbols[cur].name, esc ) ) + "\"" ); + insertOpenTagAttrs( *b, "size(); ++out.count; out.xml += *b; diff --git a/src/forgraphverb.h b/src/forgraphverb.h index 8655ed927..914c530f6 100644 --- a/src/forgraphverb.h +++ b/src/forgraphverb.h @@ -1,7 +1,7 @@ #pragma once // forgraphverb.h — FOR-ROUTE-DECLINED: a --for task SPELLED as a graph question (`path from A to B`, `callers of X`, // `common callers of A and B`) gets the graph verb it names appended after its ordinary answer, run on the -// already-built index (CLI src/verbs_for.h appendSubjectGraphVerb, MCP `for` src/mcpverbs.h forTaskText — one wrapper). +// already-built index (CLI src/verbs_for.h appendSubjectGraphVerbBlock, MCP `for` src/mcpverbs.h forTaskText — one wrapper). // // WHY. The ranked --for bundle answers these by subtoken overlap and misses the chain between the two names; the // verb answers them from the call graph. Measured (ORCH reports/for-route-s0.md, train-39 binary, LARGE-DEV): appending diff --git a/src/forowner.h b/src/forowner.h index b075b3ae5..e34394f8e 100644 --- a/src/forowner.h +++ b/src/forowner.h @@ -30,6 +30,7 @@ #include "filter.h" // isTestSymbol #include "graph.h" // langCompatible +#include "hopbody.h" // insertOpenTagAttrs — the one open-tag attribute splice #include "lexical.h" // isRouteStopword #include "model.h" #include "serialize.h" // kForLensDefaultTopN, kForDocExcerptRankCount, packBodies @@ -334,16 +335,7 @@ inline ForBodySlotOwner forBodySlotOwner( const IngestResult& ing, const std::ve // pick= on the rendered element (packBodies writes the element; the slot names how its one body was chosen) inline void spliceBodySlotPick( std::string& xml, BodySlotPick pick ) { - const std::size_t at = xml.find( "' of the first open tag that starts with `openPrefix` (the fold's , the body slot's +// ); an unexpected shape leaves the document as it was. +inline void insertOpenTagAttrs( std::string& xml, std::string_view openPrefix, std::string_view attrs ) { - const std::size_t at = bodyXml.find( "', at ); + const std::size_t close = xml.find( '>', at ); if( close != std::string::npos ) { - bodyXml.insert( close, attrs ); + xml.insert( close, attrs ); } } diff --git a/src/lexical.h b/src/lexical.h index 4383e5314..e331f4416 100644 --- a/src/lexical.h +++ b/src/lexical.h @@ -2212,7 +2212,7 @@ inline std::vector splitRouteWords( std::string_view query ) return words; } -// FOR-ROUTE-DECLINED (src/verbs_for.h appendSubjectGraphVerb): a --for task that is SPELLED as a graph question — +// FOR-ROUTE-DECLINED (src/verbs_for.h appendSubjectGraphVerbBlock): a --for task that is SPELLED as a graph question — // `path from A to B`, `callers of X`, `common callers of A and B` — gets the --path / --callers / --connect verb // appended to its ordinary answer. This is the subject-only half: no index, no gold, the words of the task alone. // Each span is cut at its first qualifier word (for/with/via/in/on/when/using/during/at/near); a slot's candidates diff --git a/src/verbs_for.h b/src/verbs_for.h index 8770b27df..a393d96f1 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -807,9 +807,11 @@ inline std::string_view hopsCallsNextLegend( const ForLensHeaderParts& p, bool c return presentOnlyClause( p.hopsCallsNextPresent, compact, kForCompactHopsCallsNextClause, kForHopsCallsNextClause ); } // body-fold: the fold's clause in this dialect, present-only (bodyFoldPresent), right after the slot clause -inline std::string_view bodyFoldLegend( const ForLensHeaderParts& p, bool compact ) noexcept +// WRAPPER-FOLLOW: the follow_of= clause rides right after it, present-only (foldFollowPresent) +inline std::string bodyFoldLegend( const ForLensHeaderParts& p, bool compact ) { - return presentOnlyClause( p.bodyFoldPresent, compact, rw::kForCompactBodyFoldLegend, rw::kForBodyFoldLegend ); + return std::string( presentOnlyClause( p.bodyFoldPresent, compact, rw::kForCompactBodyFoldLegend, rw::kForBodyFoldLegend ) ) + + std::string( presentOnlyClause( p.foldFollowPresent, compact, rw::kForCompactFoldFollowLegend, rw::kForFoldFollowLegend ) ); } inline constexpr std::string_view kForCompactLegendTail = "; t p= file outside sigs (weaker), t sym= its top symbols (a pointer, not the answer), r= rank (gap = trimmed)"; @@ -922,7 +924,6 @@ inline void appendCompactForLegend( std::string& h, const ForLensHeaderParts& p, h += ownerHopLegend( p, /*compact=*/true ); // owner-hop: present-only, right after the hops clause h += bodySlotLegend( p, /*compact=*/true ); // for-body-slot: present-only, after the owner clause h += bodyFoldLegend( p, /*compact=*/true ); // body-fold: present-only, after the slot clause - h += presentOnlyClause( p.foldFollowPresent, /*compact=*/true, rw::kForCompactFoldFollowLegend, rw::kForFoldFollowLegend ); // WRAPPER-FOLLOW h += presentOnlyClause( p.memberNamesPresent, /*compact=*/true, rw::kForCompactMemberNamesLegend, rw::kForMemberNamesLegend ); // member-names: after the fold clause h += peerRowClauses( p, /*compact=*/true ); // FOR-SLOTS + OWNER-CTOR-HOP: present-only, after mem= h += presentOnlyClause( p.callerHopPresent, /*compact=*/true, rw::kForCompactCallerHopLegend, rw::kForCallerHopLegend ); // mf1-nx: present-only, after the fold clause @@ -1071,7 +1072,6 @@ inline std::string forLensHeaderText( const ForLensHeaderParts& p, bool withRout h.append( ownerHopLegend( p, /*compact=*/false ) ); // owner-hop: present-only, right after the bundle clause h.append( bodySlotLegend( p, /*compact=*/false ) ); // for-body-slot: present-only, after the owner clause h.append( bodyFoldLegend( p, /*compact=*/false ) ); // body-fold: present-only, after the slot clause - h.append( presentOnlyClause( p.foldFollowPresent, /*compact=*/false, rw::kForCompactFoldFollowLegend, rw::kForFoldFollowLegend ) ); // WRAPPER-FOLLOW h.append( presentOnlyClause( p.memberNamesPresent, /*compact=*/false, rw::kForCompactMemberNamesLegend, rw::kForMemberNamesLegend ) ); // member-names h.append( peerRowClauses( p, /*compact=*/false ) ); // FOR-SLOTS + OWNER-CTOR-HOP h.append( presentOnlyClause( p.callerHopPresent, /*compact=*/false, rw::kForCompactCallerHopLegend, rw::kForCallerHopLegend ) ); // mf1-nx: present-only, after the fold clause @@ -2664,7 +2664,7 @@ std::optional runConnect( const MainDispatch& d ); // gets that verb's DEFAULT answer inside , just before , on the graph this run built // (no re-index). Every byte before it is the ordinary answer; nothing resolves → nothing is printed. XML only (the // --json / columnar dialects keep their single document; the verb is one call away there). -inline void appendSubjectGraphVerb( const MainDispatch& d, std::span served ) +inline void appendSubjectGraphVerbBlock( const MainDispatch& d, std::span served ) { const rw::Config& cfg = d.cfg; if( cfg.json || cfg.columnar || cfg.candidates || d.multiRoot ) @@ -2715,7 +2715,7 @@ inline void appendSubjectGraphVerb( const MainDispatch& d, std::span= 0 ) { rw::os::close( saved ); } if( capture != nullptr ) { std::fclose( capture ); } @@ -4415,7 +4415,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co { rw::emitChargedSection( stdout, graphSection, [ & ]{ packGraphBlock( stdout, ing, lensRank, g.outOff, g.outTargets ); } ); } - appendSubjectGraphVerb( d, servedAnswerBodies().bodies ); // FOR-ROUTE-DECLINED: "" unless the task is spelled as a graph question that resolves; the bodies this answer carries print no second copy in its path hop bodies + appendSubjectGraphVerbBlock( d, servedAnswerBodies().bodies ); // FOR-ROUTE-DECLINED: "" unless the task is spelled as a graph question that resolves; the bodies this answer carries print no second copy in its path hop bodies rw::emitRaw( stdout, "" ); reportRedactions( stderr, redactCounts ); diff --git a/test/hazardpatterncheck.sh b/test/hazardpatterncheck.sh index f1478bd1a..b576933e2 100755 --- a/test/hazardpatterncheck.sh +++ b/test/hazardpatterncheck.sh @@ -559,7 +559,7 @@ verbs_change.h runCommandCapture pipe 1 both ends closed in the child, the write verbs_doctor.h doctorParseProbe ts_parser_new 1 deleted before the return verbs_doctor.h doctorParseProbe ts_parser_parse_string 1 deleted inside the same if verbs_doctor.h doctorProbeGrammars ts_query_new 1 a compile probe, deleted inside the same if -verbs_for.h appendSubjectGraphVerb dup 1 closed on the failure path and after the restore on the success path (runWithCompactLegend's capture, for the --for graph-verb block) +verbs_for.h appendSubjectGraphVerbBlock dup 1 closed on the failure path and after the restore on the success path (runWithCompactLegend's capture, for the --for graph-verb block) REGE judge_static(){ # $1 = scan output dir, $2 = label (src | probe); echoes one line per violation From ae3ebc38ab1cd5af57fd37d96280106023e11fb2 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:18:22 -0400 Subject: [PATCH 36/95] hopbodycheck: F2 reaches the statement-line bound directly The four-statement-line wrapper now delegates to a callee nothing else reaches (stampInput), so a widened kThinBodyLines prints it and F2 goes red on its own (before, the mutant was caught only incidentally by P9a/F3). Test-only. Co-Authored-By: Claude Opus 5.5 --- test/hopbodycheck.sh | 2 +- test/hopbodyfix/follow/src/entry.ts | 4 ++-- test/hopbodyfix/follow/src/route.ts | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/test/hopbodycheck.sh b/test/hopbodycheck.sh index 9d7604e86..e131837f8 100644 --- a/test/hopbodycheck.sh +++ b/test/hopbodycheck.sh @@ -168,7 +168,7 @@ check("handleRequest" in names and names.index("forwardInput") == names.index("h check("forwardInput" in fo and is_source(fo["forwardInput"], FOL) and "runPipeline" in fo and is_source(fo["runPipeline"], FOL) and "segments.length === 0" in fo["runPipeline"][1], "(F1) follow CDATA are the definitions' source text, whole") check("follow_of=W" in legend(f1) and "follow_of=W" in legend(rd("f1f")), "(F1) the follow_of clause rides in both legends") # F2-F4 -check(not re.search(r'follow_of="handleTimedRequest"', rd("f2")) and re.search(r']*n="handleTimedRequest"', rd("f2")), "(F2) the 4-statement-line wrapper is served and never followed") +check(not re.search(r'follow_of="handleTimedRequest"', rd("f2")) and re.search(r']*n="handleTimedRequest"', rd("f2")) and not re.search(r']*n="stampInput"', rd("f2")), "(F2) the 4-statement-line wrapper is served and never followed (its callee stampInput never prints)") check("follow_of=" not in rd("f3a") and "follow_of=W" not in legend(rd("f3a")), "(F3) fetch's delegate lies inside the served Server body: no copy, no clause") check(re.search(r']*n="listen"', rd("f3b")) and 'follow_of="listen"' not in rd("f3b"), "(F3) the two-call listen is never followed") check('via="name"' in rd("f4c") and re.search(r']*n="handleLooseRequest"', rd("f4")) and "follow_of=" not in rd("f4"), "(F4) a by-name delegate is never followed") diff --git a/test/hopbodyfix/follow/src/entry.ts b/test/hopbodyfix/follow/src/entry.ts index 93e0c4ab3..d21eee8bb 100644 --- a/test/hopbodyfix/follow/src/entry.ts +++ b/test/hopbodyfix/follow/src/entry.ts @@ -1,4 +1,4 @@ -import { forwardInput } from './route' +import { forwardInput, stampInput } from './route' // the public entry: a one-line wrapper export function handleRequest(request: Request): Promise { @@ -10,5 +10,5 @@ export function handleTimedRequest(request: Request): Promise { const started = Date.now() const label = request.url const tag = label + started - return forwardInput(new Request(tag)) + return stampInput(new Request(tag)) } diff --git a/test/hopbodyfix/follow/src/route.ts b/test/hopbodyfix/follow/src/route.ts index 8b8ecf411..6be8900b4 100644 --- a/test/hopbodyfix/follow/src/route.ts +++ b/test/hopbodyfix/follow/src/route.ts @@ -4,3 +4,8 @@ import { runPipeline } from './core' export function forwardInput(input: Request): Promise { return runPipeline(input) } + +// the four-line wrapper's callee: reached from nowhere else, so only a (wrong) follow would print it +export function stampInput(input: Request): Promise { + return Promise.resolve(new Response(input.url)) +} From d29297c6b591a2f701c070353d0f42f3e8dd525e Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:24:13 -0400 Subject: [PATCH 37/95] regen: showcase capture 2026-10-28 (adds --defs), COMMANDS.md, deck flag count 193 Co-Authored-By: Claude Opus 5.5 --- docs/COMMANDS.md | 733 +- docs/captures/COMMANDS_showcase_2026-10-28.md | 8015 +++++++++++++++++ present/deck5_ripwire_build.js | 6 +- 3 files changed, 8410 insertions(+), 344 deletions(-) create mode 100644 docs/captures/COMMANDS_showcase_2026-10-28.md diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index a78169204..9d7c45ac3 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-27.md`), trimmed to the first few lines and +Sample output is lifted from a real recorded run (`docs/captures/COMMANDS_showcase_2026-10-28.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. @@ -71,8 +71,8 @@ $ ./build/ripwire . --top-k=5 - - + + @@ -107,7 +107,7 @@ $ ./build/ripwire . --top-k=5 --no-inventory - + @@ -141,7 +141,7 @@ $ ./build/ripwire . --max-tokens=1500 - + @@ -196,7 +196,7 @@ _The honest half of the contract: a task with no ripwire-shaped evidence ABSTAIN $ ./build/ripwire . --help-task="write a cheerful release announcement" - + ``` @@ -216,14 +216,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` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -253,7 +253,7 @@ _Restore the / sections the --for bundle collapses to a counted s ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --sections=lego,compose - + @@ -280,14 +280,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` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -315,7 +315,7 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - + @@ -347,18 +347,17 @@ _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 ) +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… -... [20 more line(s); run it to see the whole thing] +... [21 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -379,7 +378,7 @@ _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 - + @@ -410,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 - @@ -440,16 +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 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] +... [23 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -470,7 +468,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -501,7 +499,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=2040 est_tokens=952 +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=957 -━━ commands.md (relevance 11.012) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 588226 B; lines="556-581"; dropped_by_budget=163] [truncated: 1854 of 4257 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base +━━ commands.md (relevance 10.141) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 592169 B; lines="3836-3840"; dropped_by_budget=164] [truncated: 1865 of 7741 bytes] +### `--field-affinity[=STRUCT]` -**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? +**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. -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: +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] -... [17 more line(s); run it to see the whole thing] +(capped: 1 of 2 relevant document files omitted — raise --max-tokens/budget_tokens or narrow the query for 1 more (~2548-byte budget)) ``` ### `--tree` @@ -629,7 +622,7 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - + @@ -734,8 +727,8 @@ $ ./build/ripwire . --no-stable --top-k=3 - - + + @@ -768,7 +761,7 @@ $ ./build/ripwire . --around=rankGraphTeleport - + @@ -819,7 +812,7 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - + @@ -854,16 +847,16 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - + - - + - - + + + ``` @@ -888,16 +881,16 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - + - + - + @@ -925,19 +918,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] ``` @@ -959,14 +952,21 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - - - - - - + + + + + + - + + + + + + + +... [12 more line(s); run it to see the whole thing] ``` **Shaped by:** `--connect`, `--limit` @@ -975,6 +975,29 @@ $ ./build/ripwire . --path=main,rankGraphTeleport **Answers:** list the definitions each name binds (up to 16 names), with the spelling that picks each alone per definition: file:line, kind, class=source|test, callers, and sel= — paste it into any SYM verb +**Try it** + +_Which definitions each name binds, one row per definition group with sel= (the spelling that picks it alone in any verb), class=source|test and in= callers; source first._ + +``` +$ ./build/ripwire . --defs=main,rankGraphTeleport + + + + + + + + + + + + + + +... [10 more line(s); run it to see the whole thing] +``` + ### `--connect=A,B,C` **Answers:** show how 2..16 symbols relate: the smallest subgraph that joins them minimal connecting subgraph over 2..16 symbols: terminals + fewest joining intermediaries + call edges in TRUE direction (finds the shared-caller join a directed --path can't) [--connect-radius=N (1..12, default 6)] @@ -986,7 +1009,7 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + @@ -1012,7 +1035,7 @@ _Transitive blast radius — everything that reaches SYM. NOW carries shown/capp ``` $ ./build/ripwire . --impact=rankGraphTeleport - @@ -1096,7 +1119,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - @@ -1153,10 +1176,19 @@ _Mid-task situational report for the current git diff — recorded against a CLE ``` $ ./build/ripwire . --situ -ripwire situational-awareness — 0 changed file(s), 0 symbols in them +ripwire situational-awareness — 1 changed file(s), 25 symbols in them root: . -at: 19beec1c7 - (0 changed files — working tree is clean, nothing to analyze) +at: de9d903b2+dirty + change_set: granularity=file changed_symbols=25 file_symbols=25 files_narrowed=0 files_file_granular=1 diff_hunks=3 diff_hunks_located=0 diff_hunks_unlocated=3 — 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] + decl/def partners (3) not_dependents=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/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: 0 symbols across 0 files transitively depend on these changes + counts_floor=1 graph_ambiguous=2988 graph_unresolved=15914 graph_unindexed=275 (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] + [2] tests to run (0): (none transitively reach these files) + script_gates_unmodelled=795 — 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] ``` **Shaped by:** `--affected`, `--test-gate`, `--legend`, `--limit` @@ -1173,21 +1205,21 @@ _The continuation packet for the NEXT session: disk truth (branch/sha ``` $ ./build/ripwire . --handoff - - - - - - - - - - - - -... [1 more line(s); run it to see the whole thing] + + + + + + + + + + + + + + +... [17 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -1208,8 +1240,12 @@ _Pre-PR gate on a CLEAN tree: no obligations, exit 0._ ``` $ ./build/ripwire . --test-gate - - + untested blast radius ; exit 4 while either exists. tests_capped=/untested_capped=: 1 = cut. counts_floor=1: every count is a FLOOR, never a total. graph_ambiguous=/graph_unresolved=: resolver gauge. graph_unindexed=N: N files no grammar could read (the map header's unindexed=); their calls raise neither gauge. at=: commit+dirty+shallow. root=: p= relative to it. next=: the one pasteable follow-up. impacted=N: symbols that transitively call the change (changed symbols excluded). shown_tests=/shown_untested=: t rows and u rows printed, two independent counts. script_gates_unmodelled=N: test/*.sh runners in the corpus, a path count; not call-graph modelled. script_gates_registered=N: shell gates test/regression.sh registers as suite members. script_gates_mapped=N: registered gates with exact dependency evidence (literal paths or RIPWIRE_TEST_DEPS). script_gates_unresolved_dynamic=N: registered gates with no mappable deps; they may cover the change unlisted. t evidence=script_literal|manifest_declared: why a shell gate joins tests=, its text names the changed path or its RIPWIRE_TEST_DEPS does. ccx_bar=N: the cognitive-complexity bar a u row's ccx= is read against. change_granularity=: how the change set seeded — symbol = only symbols a git hunk (diff -U0 vs HEAD) locates inside a function/method body; file = whole changed files (an unlocated hunk — module-level, deletion, rename — returns its file to file-granular); mixed = both. changed_symbols=/file_symbols=: the seeds the walk used vs all symbols of the changed files. files_narrowed=/files_file_granular=: changed files under each treatment. diff_hunks=/diff_hunks_located=/diff_hunks_unlocated=: the hunk tally; an unlocated hunk is WHY a file went file-granular. untested_modscope=N: owners excluded from untested= (#324, uncallable); still in impacted=. tests=/untested=: tests to run (t total) / impacted symbols no test reaches (u total). --> + + + + ``` @@ -1505,14 +1541,14 @@ $ ./build/ripwire . --query="teleport pagerank" --top-k=5 - + - + - + ... [17 more line(s); run it to see the whole thing] @@ -1536,7 +1572,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1572,8 +1608,8 @@ $ ./build/ripwire . --pack-signatures --top-k=10 signature rows. est_tokens=: price as emitted (an upper bound under compact). root=: p= relative to it. pr_iters=N: PageRank iterations. declined=K: K call sites without edges, including inert Elixir quote sites after unquote/unquote_splicing re-entry. external=K: K calls proven … [line truncated: 1465 more bytes on this line] - - + + @@ -1702,7 +1738,7 @@ $ ./build/ripwire . --pack-top-n=3 --top-k=0 - + : 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= lcom4= amp=: callers, callees, cyclomatic/cognitive complexity, lines, parameters, nesting depth, coupled types, class cohesion, 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=). --> - - + + @@ -1805,19 +1841,19 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - + + + - + - - + + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1842,17 +1878,17 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - - + + - - - + + + + - ... [17 more line(s); run it to see the whole thing] ``` @@ -1870,7 +1906,7 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + @@ -1901,13 +1937,13 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - + - - + + @@ -1934,7 +1970,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1969,7 +2005,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -2000,7 +2036,7 @@ _THE single wide-angle quality read: six families in one pass, an eligible/ranke ``` $ ./build/ripwire . --quality-panel - @@ -2010,7 +2046,7 @@ $ ./build/ripwire . --quality-panel - + ... [17 more line(s); run it to see the whole thing] @@ -2037,12 +2073,12 @@ _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] ``` @@ -2068,7 +2104,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2103,12 +2139,12 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - + + + - + @@ -2140,8 +2176,8 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - - + + @@ -2149,10 +2185,10 @@ $ ./build/ripwire . --lint --naming-locals - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2175,7 +2211,7 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - + @@ -2208,16 +2244,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - + - + - - - + + + ... [22 more line(s); run it to see the whole thing] ``` @@ -2240,16 +2276,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] ``` @@ -2266,7 +2302,7 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + @@ -2274,7 +2310,7 @@ $ ./build/ripwire . --cochange --cochange-groups - + @@ -2295,16 +2331,16 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - - + + - + - + - + @@ -2333,7 +2369,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2363,7 +2399,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2389,7 +2425,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2416,8 +2452,8 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - - + + @@ -2425,10 +2461,10 @@ $ ./build/ripwire . --lint - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2525,8 +2561,8 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + @@ -2534,10 +2570,10 @@ $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2639,19 +2675,19 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + + - - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2670,7 +2706,7 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - ``` @@ -2695,15 +2731,15 @@ _Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels ``` $ ./build/ripwire . --zoom - - + + - - + + - + @@ -2732,16 +2768,16 @@ $ ./build/ripwire . --report # ripwire architecture report -3902 files · 33122 symbols · 31168 edges · 3871 modules (15563 call-graph isolated) +3931 files · 33184 symbols · 31243 edges · 3837 modules (15600 call-graph isolated) Root: `.` -Call-graph isolate provenance: 4125 declaration, 2508 header, 3690 source, 5240 document; 0 connected Louvain singletons +Call-graph isolate provenance: 4128 declaration, 2511 header, 3713 source, 5248 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3871) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1341 symbols +## Modules (call-graph clusters; showing 12 of 3837) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1495 symbols - **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 555 symbols -- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 269 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 254 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2758,8 +2794,8 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - + @@ -2789,17 +2825,17 @@ $ ./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
9790"] + n102["src
9807"] n103["src/infra
1301"] end subgraph sg1 ["test"] - n104["test
5590"] - n602["test/legochargefix
546"] - n415["test/fixtures/recallpassage
314"] - n1029["test/slotjoinfix
175"] - n839["test/ownerhopfix/py/pkg
158"] - n352["test/expandmodefix
151"] - n354["test/expandsibsfix
149"] + n104["test
5595"] + n606["test/legochargefix
546"] + n419["test/fixtures/recallpassage
314"] + n1033["test/slotjoinfix
175"] + n843["test/ownerhopfix/py/pkg
158"] + n356["test/expandmodefix
151"] + n358["test/expandsibsfix
149"] ... [17 more line(s); run it to see the whole thing] ``` @@ -2818,8 +2854,8 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - + + @@ -2849,7 +2885,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2913,8 +2949,8 @@ _The same clean verdict with what it folded printed back: every stale acknowledg ``` $ ./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). --> + @@ -2942,8 +2978,8 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - -, 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. 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. 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. --> + ``` @@ -2967,8 +3003,8 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - -, 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. 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. 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. --> + ``` @@ -2993,7 +3029,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 -

@@ -3086,7 +3122,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3116,13 +3152,13 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - - - + + + ``` @@ -3170,12 +3206,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":2980,"graph_unresolved":15905,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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":794,"graph_ambiguous":2980,"graph_unresolved":15905,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3191,12 +3227,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":2980,"graph_unresolved":15905,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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":794,"graph_ambiguous":2980,"graph_unresolved":15905,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3242,12 +3278,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":2980,"graph_unresolved":15905,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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":794,"graph_ambiguous":2980,"graph_unresolved":15905,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3306,11 +3342,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":2980,"graph_unresolved":15905,"graph_unindexed":275,"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] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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":2980,"graph_unresolved":15905,"graph_unindexed":275,"counts_floor":true}}]} +"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3356,7 +3392,7 @@ _Bare --slice=SYM: the INVENTORY of sliceable locals (), so a calle ``` $ ./build/ripwire . --slice=rankGraphTeleport - + @@ -3393,7 +3429,7 @@ _Forward flow: which statements the seed's value reaches, at the default depth b ``` $ ./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd - + @@ -3473,10 +3509,21 @@ _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` @@ -3500,16 +3547,20 @@ _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` @@ -3579,7 +3630,7 @@ _A second selection, the `fix/` ref family, picked at capture time from the refs ``` $ ./build/ripwire . --stray-content=fix/ - + ``` @@ -3604,7 +3655,7 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + @@ -3640,18 +3691,18 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi - + - + - + ... [7 more line(s); run it to see the whole thing] ``` @@ -3674,12 +3725,12 @@ _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] ``` @@ -3704,13 +3755,13 @@ _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] ``` @@ -3732,14 +3783,14 @@ _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] ``` @@ -3765,7 +3816,7 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat $ ./build/ripwire . --flags - + @@ -3846,8 +3897,8 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - + @@ -3881,7 +3932,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3915,7 +3966,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3948,8 +3999,8 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + @@ -3982,7 +4033,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -4019,8 +4070,8 @@ 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:11977 #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:12018 - #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1754 - #3 0x102e01a44 in main src/main.cpp:4175 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1755 + #3 0x102e01a44 in main src/main.cpp:4177 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) ==41337==ABORTING ``` @@ -4047,7 +4098,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4147,7 +4198,7 @@ $ ./build/ripwire . --pack-task="add a new output format flag to the CLI" (run= when derivable). window: shown= total= capped= (capped=1 cut). est_tokens=: price as emitted (an upper bound under compact). root=: p= relative to it. : rank N in this ranking, rows in r= order. : cyclomatic/cognitive complexity. : N callers in the ind … [line truncated: 384 more bytes on this line] - + 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 ) @@ -4180,7 +4231,7 @@ $ ./build/ripwire . --pack-task="add a new output format flag to the CLI" --part - + 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 ) @@ -4207,7 +4258,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - + @@ -4280,13 +4331,13 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - + - - + + - + @@ -4314,13 +4365,13 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - - + - - + + - + @@ -4350,7 +4401,7 @@ $ ./build/ripwire . --skipped - @@ -4438,8 +4489,8 @@ $ ./build/ripwire . --rank-by=churn --top-k=5 - - + + @@ -4470,22 +4521,22 @@ a trailing slash is ignored; absolute paths and '..' are refused). 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._ ``` -$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~4 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3 +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3 - - - - - - - - - - - - -... [15 more line(s); run it to see the whole thing] + + + + + + + + + + + + +... [5 more line(s); run it to see the whole thing] ``` **Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` @@ -4547,7 +4598,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4575,7 +4626,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=a3aaaa12b102080c entries=958 +ripwire legend dictionary ripwire.dict/v1 dictv=80dbc95318c95d64 entries=972 : 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=) @@ -4628,7 +4679,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -4665,8 +4716,8 @@ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - - + + @@ -4693,20 +4744,20 @@ _Full map re-ranked with teleport toward git-changed files — clean tree, so ch ``` $ ./build/ripwire . --map-diff --top-k=5 - + - - - - + + + + - - - - - + + + + + ... [9 more line(s); run it to see the whole thing] ``` @@ -4730,8 +4781,8 @@ $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - - + + @@ -4780,8 +4831,8 @@ $ ./build/ripwire . --no-cache --top-k=3 - - + + @@ -4811,8 +4862,8 @@ $ ./build/ripwire . --no-ignore --top-k=3 - - + + @@ -4847,7 +4898,7 @@ $ ./build/ripwire . --max-file-size=8K --top-k=3 - + @@ -4927,8 +4978,8 @@ $ ./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3 - - + + @@ -4980,10 +5031,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":" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [751 more display lines; full output is 24638 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --top-k=5` + +*Same map, capped to the 5 highest-ranked symbols.* + +**wall time: 1.20s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [68 more display lines; full output is 8605 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.* + +**wall time: 1.18s** + +````` + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.* + +**wall time: 1.34s** + +````` + + + + + + + + + + + +````` + +## `./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** — **wall time: 1.41s** + +````` + + +````` + +stderr: + +````` +ripwire: --token-budget exceeded: withheld_est_tokens=9929 > 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: 5.04s** + +````` + + + + +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 +void emitGrepHandleLegend( bool enabled ) +inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) +… [127 more display lines; full output is 20022 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: 3.29s** + +````` + + + +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 5869 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: 3.07s** + +````` + + + + + +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… +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 std::string didYouMean( const IngestResult& ing, std::string_view name ) +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] +inline const McpIndex& getIndex( const std::string& root ) +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 +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +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 ) +… [155 more display lines; full output is 24105 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: 3.04s** + +````` + + + + + +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: 3.60s** + +````` + + + + +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 bool hasPytestTable( std::string_view source ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [139 more display lines; full output is 19852 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: 3.06s** + +````` + + + + +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 ) +struct NameCorpusStats +inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) +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] +inline constexpr int kLexWeightDoc = 2 +struct CandidateProvenance +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 ) +inline double binomialChoose( std::size_t n, std::size_t k ) +struct RecallSectionPick +… [146 more display lines; full output is 21546 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: 3.05s** + +````` + + + + +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 ) +struct NameCorpusStats +inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) +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 RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) +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] +inline constexpr int kLexWeightDoc = 2 +struct CandidateProvenance +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 ) +inline double binomialChoose( std::size_t n, std::size_t k ) +struct RecallSectionPick +… [156 more display lines; full output is 21837 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.* + +**wall time: 1.11s** + +````` + + + + + + + + + +````` + +## `./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: 2.12s** + +````` + + + + + +````` + +## `./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.65s** + +````` + + + + +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.49s** + +````` + + + + +````` + +## `./build/ripwire . --recall="quality delta gating exit codes"` + +*Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* + +**wall time: 1.29s** + +````` +ripwire recall — "quality delta gating exit codes" — 124 relevant of 305 document files, best-first — total=124 shown=8 capped=1 truncated=6 generated_demoted=3 max_tokens=8000 share_bytes=2468 est_tokens=4165 + +━━ README.md (relevance 8.062) ━━ [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. + + + +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.513) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +### Root spelling (maintainer-owned; reproduce it once so you recognise it) + +```bash +… [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 1297593-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=957 + +━━ commands.md (relevance 10.141) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 592169 B; lines="3836-3840"; dropped_by_budget=164] [truncated: 1865 of 7741 bytes] +### `--field-affinity[=STRUCT]` + +**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. + +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] + +(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).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [367 more display lines; full output is 11127 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --html=/aux/map2.html` + +*Self-contained HTML force-directed call graph.* + +**wall time: 3.98s** + +````` +(empty) +````` + +Artifact written: + +````` + 156113 /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.32s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [68 more display lines; full output is 8623 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [165 more display lines; full output is 8029 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [1965 more display lines; full output is 53822 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [11 more display lines; full output is 3893 bytes on 1 raw line(s)] +````` + +## `./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.* + +**wall time: 1.08s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --callers=DoesNotExist` + +*Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* + +**exit code: 1** — **wall time: 2.27s** + +````` + + +````` + +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.38s** + +````` + + + + + + + + + + + + +````` + +## `./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))'` + +*Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* + +**wall time: 1.10s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [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.* + +**wall time: 1.09s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5602 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 5481 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 . --defs=main,rankGraphTeleport` + +*Which definitions each name binds, one row per definition group with sel= (the spelling that picks it alone in any verb), class=source|test and in= callers; source first.* + +**wall time: 1.01s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.* + +**wall time: 1.02s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [70 more display lines; full output is 6872 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.* + +**wall time: 1.17s** + +````` + + + + + + + + + + + +````` + +## `./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: 4.99s** + +````` +ripwire situational-awareness — 1 changed file(s), 25 symbols in them +root: . +at: de9d903b2+dirty + change_set: granularity=file changed_symbols=25 file_symbols=25 files_narrowed=0 files_file_granular=1 diff_hunks=3 diff_hunks_located=0 diff_hunks_unlocated=3 — 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] + decl/def partners (3) not_dependents=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/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: 0 symbols across 0 files transitively depend on these changes + counts_floor=1 graph_ambiguous=2988 graph_unresolved=15914 graph_unindexed=275 (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] + [2] tests to run (0): (none transitively reach these files) + script_gates_unmodelled=795 — 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 (117) window="18mo@HEAD" commits="5516" (showing 8 of 117 files — shown=8 total=117 capped=1; next: --situ --limit=117): + README.md (co-edited in 93% of commits) + docs/EVALS.md (co-edited in 82% of commits) + test/regression.sh (co-edited in 65% of commits) + CHANGELOG.md (co-edited in 29% of commits) + docs/COMMANDS.md (co-edited in 24% of commits) + src/cli.h (co-edited in 22% of commits) + .github/pargates-shard-weights.json (co-edited in 20% of commits) + src/quality.h (co-edited in 18% of commits) + next: --test-gate +````` + +## `./build/ripwire . --test-gate` + +*Pre-PR gate on a CLEAN tree: no obligations, exit 0.* + +**exit code: 4** — **wall time: 5.40s** + +````` + + + + + + +````` + +## `./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.* + +**wall time: 1.24s** + +````` + +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.75s** + +````` + +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.18s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [22 more display lines; full output is 3459 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: 2.74s** + +````` + + + + + +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 path_prepend_hint( std::string_view dir ) +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +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 ) +std::uint32_t testIterationCeiling() noexcept +inline const char* rankByLegendFor( const char* label ) noexcept +… [156 more display lines; full output is 22225 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.28s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [150 more display lines; full output is 15426 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.* + +**wall time: 1.13s** + +````` + + + + +& 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).* + +**wall time: 1.29s** + +````` + + + + +& 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.* + +**wall time: 1.30s** + +````` + + + + +: 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= lcom4= amp=: callers, callees, cyclomatic/cognitive complexity, lines, parameters, nesting depth, coupled types, class cohesion, 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=). --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [98 more display lines; full output is 11226 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --deps` + +*File->file dependency graph (god-files, cycles).* + +**wall time: 1.17s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [874 more display lines; full output is 23544 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --hotspots` + +*Complexity x recent git churn (maintenance pain).* + +**wall time: 4.60s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [14 more display lines; full output is 5771 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --clones` + +*Token-normalized duplicate bodies.* + +**wall time: 3.46s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [308 more display lines; full output is 16263 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --cochange` + +*Files that change together in git (hidden coupling).* + +**wall time: 3.89s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --hotspots --since="2 weeks ago"` + +*Hotspots scoped to RECENT churn (the regression lens).* + +**wall time: 5.28s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 5138 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.* + +**wall time: 1.02s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [1142 more display lines; full output is 168443 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint` + +*Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* + +**wall time: 2.97s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [682 more display lines; full output is 70695 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.36s** + +````` + + + + +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.* + +**wall time: 1.41s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [189 more display lines; full output is 16823 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=.* + +**wall time: 1.02s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [137 more display lines; full output is 7477 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [293 more display lines; full output is 13250 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --report` + +*Architecture summary (modules, god-files, cycles) as markdown.* + +**wall time: 1.07s** + +````` + + +# ripwire architecture report + +3931 files · 33184 symbols · 31243 edges · 3837 modules (15600 call-graph isolated) + +Root: `.` + +Call-graph isolate provenance: 4128 declaration, 2511 header, 3713 source, 5248 document; 0 connected Louvain singletons + +## Modules (call-graph clusters; showing 12 of 3837) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1495 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 555 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 254 symbols +- **docs::`--top-k=N`@COMMANDS.md:59:9108 [pack,allow,insert]** — 182 symbols +- **test/ownerhopfix/py/pkg::big_handler_001@broadcast.py:3:72** — 106 symbols +- **src::buf@infra/svector.h:125:8942 [insert,find,push]** — 95 symbols +- **src/infra::fail@os_win32.cpp:63:2553 [open,get,close]** — 89 symbols +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,compute,collect]** — 66 symbols +- **test::no@estchargecheck.sh:52:4108 [skip,set]** — 63 symbols +- **src::child@ingest_valuerefs.h:651:34179 [emit,resolve,open]** — 59 symbols +- **src::active@pathpairs.h:106:7669 [emit,split,resolve]** — 56 symbols +- **src::sliceKindIs@slice.h:368:24239 [match,collect,push]** — 48 symbols + +## God files (most depended-on; showing 10 of 236) +- `src/model.h` — 108 dependents +- `src/infra/emit.h` — 88 dependents +- `src/infra/Diagnostics.h` — 65 dependents +- `src/graph.h` — 50 dependents +- `src/serialize.h` — 49 dependents +… [35 more lines, 3936 bytes total] +````` + +## `./build/ripwire . --seams` + +*Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* + +**wall time: 1.34s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [82 more display lines; full output is 12194 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
9807"] + n103["src/infra
1301"] + end + subgraph sg1 ["test"] + n104["test
5595"] + n606["test/legochargefix
546"] + n419["test/fixtures/recallpassage
314"] + n1033["test/slotjoinfix
175"] + n843["test/ownerhopfix/py/pkg
158"] + n356["test/expandmodefix
151"] + n358["test/expandsibsfix
149"] + n1016["test/rubyschemafix
99"] + n456["test/forbodyfoldfix/py
98"] + n998["test/rubyattrsfix
85"] + end + n74["docs
866"] + n2[".github
632"] + 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"] + n65["bench/skillrater/results/2026-09-07/arms
100"] +… [19 more lines, 1575 bytes total] +````` + +## `./build/ripwire . --owners` + +*Bus-factor: recency-weighted author ownership per file.* + +**wall time: 5.13s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [979 more display lines; full output is 107182 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.* + +**wall time: 1.31s** + +````` + + + + +````` + +## `./build/ripwire . --exercises=test/regression.sh` + +*Which symbols a TEST FILE exercises — the reverse direction of --affected.* + +**wall time: 1.04s** + +````` + + + +````` + +## `./build/ripwire . --community=0` + +*Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* + +**wall time: 1.06s** + +````` + + + +````` + +## `./build/ripwire . --quality-delta` + +*On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* + +**wall time: 36.95s** + +````` + + +````` + +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: 20.23s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [140 more display lines; full output is 15980 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: 3.97s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*No-LLM review-evidence bundle for the working-tree diff (clean tree = empty).* + +**wall time: 6.58s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [71 more display lines; full output is 7949 bytes on 1 raw line(s)] +````` + +## `./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: 7.41s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 4481 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: 20.72s** + +````` + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --stray-content=lane/` + +*Which refs of the `lane/` ref family still hold divergent authored work vs HEAD, with verdicts.* + +**wall time: 45.15s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [281 more display lines; full output is 35941 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --stray-content=fix/` + +*A second selection, the `fix/` ref family, 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: 5.00s** + +````` + + + +````` + +## `./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: 128.45s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [33541 more display lines; full output is 4642024 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: 2.92s** + +````` +(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: 169.62s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 13.56s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 14.96s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [36 more display lines; full output is 13829 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: 20.38s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [35 more display lines; full output is 14842 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.22s** + +````` + + + + + + + +````` + +## `./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.30s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [424 more display lines; full output is 25917 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.13s** + +````` +(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: 4.23s** + +````` +{"v":1,"verb":"plan-lanes","at":"de9d903b2+dirty","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":3931,"symbols":33184,"edge … [line truncated: 344 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":658,"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":1211,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":10,"churn":61,"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":3033,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":11,"churn":455,"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":693,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":455,"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":5897,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":490,"tested":0}], +"files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":56}, +{"p":"./src/docdrift.h","symbols":4,"churn":36,"ccx":14,"hotspot_rank":40}, +{"p":"./src/editcheck.h","symbols":1,"churn":61,"ccx":10,"hotspot_rank":43}, +{"p":"./src/main.cpp","symbols":2,"churn":455,"ccx":12,"hotspot_rank":5}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":490,"ccx":2,"hotspot_rank":4}, +{"p":"./src/pageview.h","symbols":1,"churn":28,"ccx":2,"hotspot_rank":132}, +{"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":26}]},"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/cli.h","n":"inHostActive","scope":"rw","key":"4446994deb74226c","id":"./src/cli.h::rw::inHostActive","id_addressable":true,"id_collides_with":0,"l":4675,"ord":0,"overloads":1," … [line truncated: 46 more bytes on this line] +{"p":"./src/jsrunner.h","n":"looksLikeJsTestFile","scope":"rw::jsrunner","key":"64417bc2d02fc89b","id":"./src/jsrunner.h::rw::jsrunner::looksLikeJsTestFile","id_addressable":true,"id_collides_with":0,"l":511,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":8,"churn":17,"tested":0}, +… [69 more display lines; full output is 20529 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.62s** + +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":"de9d903b2+dirty","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3931,"symbols":33184,"edges":31243,"ambiguous":2988,"unresolved":15914},"carve":null,"core":{" … [line truncated: 10 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":658,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":490,"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/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/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":5897,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":490,"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/docdrift.h","symbols":7,"churn":36,"ccx":60,"hotspot_rank":40}, +{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":107}, +{"p":"./src/mcpverbs.h","symbols":2,"churn":490,"ccx":2,"hotspot_rank":4}, +{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":65}, +{"p":"./src/verbs_change.h","symbols":1,"churn":82,"ccx":4,"hotspot_rank":26}], +"symbols_total":3537,"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":5,"notes":[], +"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":12,"files":5,"module_span":5,"max_ccx":38,"sum_ccx":70,"ambiguous_calls":14,"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":136,"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":2953,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":515,"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":3032,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":515,"tested":0}, +{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":136,"l":885,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":18,"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":3097,"ord":0,"overloads":1,"amb":1,"cx":4,"ccx":3,"churn":515,"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":3083,"ord":0,"overloads":1,"amb":0,"cx":2,"ccx":1,"churn":515,"tested":0}, +{"p":"./src/crossref.h","n":"writeWhereisTextCut","scope":"crossref","key":"9a3ef6403f53ce21","id":"./src/crossref.h::crossref::writeWhereisTextCut","id_addressable":true,"id_collides_with":0,"l":4542,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":3,"churn":95,"tested":0}, +… [58 more display lines; full output is 18354 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.37s** + +````` + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + +… [244 more display lines; full output is 24823 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.47s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [274 more display lines; full output is 26240 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: 2.18s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [224 more display lines; full output is 24886 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:11977 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:12018 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1755 + #3 0x102e01a44 in main src/main.cpp:4177 + #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=.* + +**wall time: 1.01s** + +````` + + + + +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.05s** + +````` + 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 path_prepend_hint( std::string_view dir ) +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +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 ) +std::uint32_t testIterationCeiling() noexcept +inline const char* rankByLegendFor( const char* label ) noexcept +… [124 more display lines; full output is 19115 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.80s** + +````` +(empty) +````` + +Artifact written: + +````` + 610617 /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: 6.54s** + +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 ) +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) +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] +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 51615 bytes on 1 raw line(s)] +````` + + +--- + +# 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: 29.04s** + +````` + + + + + + + + + + +````` + + +--- + +# 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: 10.61s** + +````` + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --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: 17.01s** + +````` + + + + + + + + + + + + + + + + + + + +````` + +## `./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).* + +**wall time: 3.89s** + +````` + + +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,12016,12662,171,1427,1466,1753 +fn,fn,fn,fn,fn,fn,fn +0,0,0,0,0,0,0 +src/mcpindex.h:1288,src/graph.h:12019,src/graph.h:12713,src/eval.h:328,src/main.cpp:1441,src/main.cpp:1480 src/main.cpp:1481 src/main.cpp:1495,src/main.cpp:1873 + + +````` + +## `./build/ripwire . --for="cache invalidation" --format=candidates --top-k=5` + +*Flat top-K export for an external reranker.* + +**wall time: 10.01s** + +````` + + + +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.* + +**wall time: 2.65s** + +````` +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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:12016","sites_at":["src/graph.h:12019"]}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:12662","sites_at":["src/graph.h:12713"]}, +{"t":"fn","n":"runEval","p":"src/eval.h:171","sites_at":["src/eval.h:328"]}, +{"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1427","sites_at":["src/main.cpp:1441"]}, +{"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1466","sites_at":["src/main.cpp:1480","src/main.cpp:1481","src/main.cpp:1495"]}, +{"t":"fn","n":"runDefaultMap","p":"src/main.cpp:1753","sites_at":["src/main.cpp:1873"]}]} +````` + +## `./build/ripwire . --hotspots --json` + +*JSON refusal shape: an unsupported verb refuses loudly instead of silently falling back to XML.* + +**exit code: 1** — **wall time: 1.02s** + +````` +(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, --defs, 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: 9.80s** + +````` + + + + + + + +````` + +## `./build/ripwire . --ignore-tests --top-k=5` + +*Drop test paths from the corpus before ranking.* + +**wall time: 2.81s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [67 more display lines; full output is 8224 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --exclude=present --exclude=bench --top-k=5` + +*Drop matching paths (repeatable) before ranking.* + +**wall time: 4.32s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [66 more display lines; full output is 8444 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: 9.02s** + +````` + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --no-cache --top-k=3` + +*Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm cost.* + +**wall time: 8.93s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [65 more display lines; full output is 8460 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: 9.93s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [65 more display lines; full output is 8460 bytes on 1 raw line(s)] +````` + +Artifact written: + +````` + 29623015 /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).* + +**wall time: 3.33s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [19 more display lines; full output is 4470 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: 8.81s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --eval` + +*Self-eval: co-change recall vs BM25.* + +**wall time: 31.96s** + +````` +ripwire --eval (co-change recovery, averaged over 80 historical commits) + ranker recall@5 recall@10 recall@20 + ripwire 1.0% 1.2% 2.8% + BM25 14.8% 15.0% 16.0% + BM25sub 12.9% 14.4% 18.5% + BM25body 3.4% 15.6% 22.2% + fused 1.1% 4.2% 12.1% + anchored 3.4% 15.6% 22.2% + same-dir 0.0% 0.6% 6.3% + random 0.1% 0.3% 0.5% <- floor (random ranking over F=3931 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: 193.63s** + +````` +ripwire --eval-retrieval (known-item, 4002 doc-commented symbols; gold is in-corpus by construction) + sample: population=8397 scored=4002 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.674 53.6% 84.0% 89.4% + subtoken doc-phrase 0.889 86.8% 90.8% 92.1% + name-exact name 0.888 80.5% 95.2% 97.4% + name-exact doc-phrase 0.011 0.4% 1.7% 2.3% + anchored name 0.679 55.1% 83.2% 88.0% + anchored doc-phrase 0.884 86.0% 90.7% 91.4% + routed name 0.889 80.7% 95.3% 97.6% + routed doc-phrase 0.887 86.7% 90.6% 91.8% + note: routing chose name-exact on 3991/4002 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: 102.98s** + +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).* + +**wall time: 7.17s** + +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.427) + 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.* + +**wall time: 1.52s** + +````` +# 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.* + +**wall time: 1.14s** + +````` +ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=de9d903b2) +````` + + +--- + +# navigate — seeds, claims, slices, shapes + +## `./build/ripwire . --at=src/graph.h:11977` + +*Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost.* + +**wall time: 2.64s** + +````` + + + + +````` + +## `./build/ripwire . --callers=@src/graph.h:11977` + +*The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* + +**wall time: 2.59s** + +````` + + + + + + + + + + +````` + +## `./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** — **wall time: 1.83s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 17116 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.* + +**wall time: 4.48s** + +````` + + + + + +````` + +## `./build/ripwire . --verify="unused(rankGraphTeleport)"` + +*A claim that is FALSE — the refuted shape, with the references that refute it.* + +**wall time: 4.60s** + +````` + + + + + + + + + + + + +````` + +## `./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.* + +**wall time: 2.26s** + +````` + + + +````` + +## `./build/ripwire . --verify="frobnicate(x)"` + +*An unparseable claim — the refusal names the accepted shapes.* + +**exit code: 1** — **wall time: 5.58s** + +````` +(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: 5.32s** + +````` + + + + + + + + + + + + + + + + + +````` + +## `./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: 5.70s** + +````` + + + + + + + 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: 4.01s** + +````` + + + + + + + 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: 6.31s** + +````` + + + + + + + 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** — **wall time: 2.49s** + +````` +(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.* + +**wall time: 3.15s** + +````` + + + + + + + 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: 2.86s** + +````` + +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: 2.96s** + +````` + +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 17776 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** — **wall time: 2.86s** + +````` +(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).* + +**wall time: 2.77s** + +````` + + + +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 bool hasPytestTable( std::string_view source ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [139 more display lines; full output is 19737 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: 8.79s** + +````` + + + + +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 bool hasPytestTable( std::string_view source ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [146 more display lines; full output is 19788 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: 14.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 bool hasPytestTable( std::string_view source ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [137 more display lines; full output is 20512 bytes on 57 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: 9.56s** + +````` + + + + + +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 std::string gitHeadSha( const std::string& root ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +… [193 more display lines; full output is 23001 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: 6.48s** + +````` + + + + + +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 std::string gitHeadSha( const std::string& root ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +… [193 more display lines; full output is 22820 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.* + +**wall time: 2.14s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --safe-delete=DoesNotExist` + +*Unknown-symbol refusal shape for --safe-delete.* + +**exit code: 1** — **wall time: 2.87s** + +````` +(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: 18.24s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [18 more display lines; full output is 2321 bytes on 1 raw line(s)] +````` + +## `./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: 21.53s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 2314 bytes on 1 raw line(s)] +````` + +## `./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.* + +**wall time: 3.32s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [458 more display lines; full output is 49347 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).* + +**wall time: 2.35s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [65 more display lines; full output is 8460 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.* + +**wall time: 4.79s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [65 more display lines; full output is 8460 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** — **wall time: 4.45s** + +````` + + + + + + const&, float) src/graph.h:11977 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:12018 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1755 + #3 0x102e01a44 in main src/main.cpp:4177 + #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.* + +**wall time: 3.23s** + +````` + + + + +````` + +## `./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: 6.00s** + +````` + + + + +````` + +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: 17.10s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [189 more display lines; full output is 15697 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: 2.47s** + +````` + + + + + + + + + + + +````` + +## `./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.* + +**wall time: 2.58s** + +````` + + + + + + + + + + + +````` + +## `./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: 3.25s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [21 more display lines; full output is 11156 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: 5.19s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [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: 11.02s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [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: 4.92s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [518 more display lines; full output is 46439 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: 2.86s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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).* + +**wall time: 1.78s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [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: 18.40s** + +````` + + + + + + + + + + + +

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+… [167 more display lines; full output is 17121 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: 5.17s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [639 more display lines; full output is 72718 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.* + +**wall time: 2.56s** + +````` + + +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=80dbc95318c95d64 entries=972 +: 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 +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.defs/v1 : per the parsed definitions the name binds (defs= groups; a prototype with its definition is one): , sel= binds that one alone in any SYM verb, in= distinct callers (a floor); source first, then in=, then path +ripwire.path/v1 : one DIRECTED call path from= to to=, each a hop; reachable=0 hops=0 when none +… [943 more display lines; full output is 103495 bytes on 973 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: 4.90s** + +````` + + + + + + + + + + +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 73637 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: 4.55s** + +````` + + + + + + + + + + + + + + + + + + + + + + + +0.5 +1023u +main (90 lines) +20 +delta < 0.02 && delta > -0.02 +0.02 +… [720 more display lines; full output is 66538 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: 5.06s** + +````` + + + +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: 4.87s** + +````` + + + +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: 6.32s** + +````` +(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: 3.82s** + +````` +(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: 5.81s** + +````` +{"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}}, +… [13781 more display lines; full output is 1953686 bytes on 1 raw line(s)] +````` + +Parsed summary of the same SARIF (past the display cut): + +````` +sarif 2.1.0 rules= 39 results= 6886 +````` + +## `./build/ripwire . --lint --sarif --limit=5` + +*SARIF is always the FULL result set: paging alongside it refuses loudly.* + +**exit code: 1** — **wall time: 5.98s** + +````` +(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: 32.70s** + +````` + + +

+

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

+

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

+

+ +````` + +## `./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: 8.53s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [851 more display lines; full output is 56606 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: 6.23s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 5.91s** + +````` +(empty) +````` + +Artifact written: + +````` + 156118 /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: 7.73s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (29623015 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (60635651 bytes, rich family) +````` + +Artifact written: + +````` + 29623015 /aux/ci_index.lean.ripwirecache + 60635651 /aux/ci_index.rich.ripwirecache + 90258666 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.* + +**wall time: 2.09s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [65 more display lines; full output is 8460 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: 2.45s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [65 more display lines; full output is 8460 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. + 133165 /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: 5.08s** + +````` + + + + + + + + + + + + + +````` + +## `./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: 3.67s** + +````` + + + + + + + +````` + +## `./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: 34.63s** + +````` + + + + + + + + + + + + + +````` + +## `./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: 14.90s** + +````` +ripwire situational-awareness — 1 changed file(s), 13 symbols in them +root: . +at: de9d903b2+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: 251 symbols across 60 files transitively depend on these changes (showing 8 of 60 files — shown=8 total=60 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=60) + counts_floor=1 graph_ambiguous=2988 graph_unresolved=15914 graph_unindexed=275 (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_for.h (14 dependent symbols) + src/verbs_quality.h (14 dependent symbols) + src/verbs_navigate.h (10 dependent symbols) + src/editcheck.h (9 dependent symbols) + src/resolve.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=795 — 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="5516": + (none — 5516 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: 9.12s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 43.96s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [149 more display lines; full output is 18050 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: 32.56s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [149 more display lines; full output is 23042 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: 41.13s** + +````` +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":167,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"de9d903b2+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"}, +… [146 more display lines; full output is 16644 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: 30.62s** + +````` +(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, 213 re-derived, 21 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: 33.00s** + +````` +(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, 213 re-derived, 21 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: 27.97s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [148 more display lines; full output is 17753 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: 4.76s** + +````` + + + + + + +````` + +## `./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: 4.23s** + +````` + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*The review-evidence bundle with an actual changed file.* + +**wall time: 11.47s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [80 more display lines; full output is 8065 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: 6.84s** + +````` + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --clones` + +*The duplicated helper the sandbox edit introduced shows up as a clone group.* + +**wall time: 4.40s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [308 more display lines; full output is 16263 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: 11.24s** + +````` + + + + + +````` + +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: 14.50s** + +````` + + + + +````` + +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: 14.69s** + +````` + + +

+

+ +````` + +## `./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: 31.41s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [151 more display lines; full output is 18020 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: 20.93s** + +````` +(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: 11.12s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 3.07s** + +````` +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 keep this branch-free — it sits inside the PageRank sort comparator de9d903b2fd847266c9a091d94c41e7b91b58e47 lane/defs-lookup +````` + +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.51s** + +````` + + + + + + + + + + + + + + + + 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** — **wall time: 1.58s** + +````` +(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** — **wall time: 1.35s** + +````` +(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: 3.46s** + +````` +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort de9d903b2fd847266c9a091d94c41e7b91b58e47 lane/defs-lookup +````` + +## `./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.* + +**wall time: 2.41s** + +````` + + + + + + + + + + + + + +````` + +## `./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: 8.30s** + +````` +{"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":2988,"graph_unresolved":15914,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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: 3.10s** + +````` +{"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: 9.57s** + +````` +{"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":2988,"graph_unresolved":15914,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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: 3.67s** + +````` +(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: 3.29s** + +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":2988,"graph_unresolved":15914,"graph_unindexed":275,"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: 10.12s** + +````` +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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":2988,"graph_unresolved":15914,"graph_unindexed":275,"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: 43.28s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [145 more display lines; full output is 17117 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: 34.73s** + +````` +(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: 22.70s** + +````` +(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 32179 per-symbol rows, 33186 indexed symbols total) +````` + +Artifact written: + +````` + 5446333 .ripwire_quality_baseline +# ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit +producer 3ada5f8adf8c098b8e95cdb1aa98721e542fbba9885d4731d55abb94b303eb41 +head de9d903b2fd847266c9a091d94c41e7b91b58e47 +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: 22.07s** + +````` + + +````` + + +--- + +# 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: 5.81s** + +````` +{"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: 7021 more bytes on this line] +"_index":"[index: files=3931 symbols=33184 hash=c1506576]","_fresh":"ok"}} +{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"', rd(f), re.S)), "(X3) %s: no -- inside a legend comment" % f) sys.exit(1 if fails else 0) PY -python3 "$TMP/chk.py" "$TMP" "$BIN" "$ROOT"; fail=$? +python3 "$TMP/chk.py" "$TMP" "$BIN" "$ROOT" || fail=1 # P7: MCP path_between twin python3 - "$BIN" "$ROOT/$WEB" "$TMP" <<'PY' || fail=1 From 707b46da8634e724a4208bedf485bb8960c7b616 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 04:23:04 -0400 Subject: [PATCH 45/95] pathgapcheck: K23 arms for a write marker in a re-export hop (globalsmid, setattrmid, globalsinitre) A hop file that re-exports the name and then writes its namespace through globals() or setattr(sys.modules[__name__], ...) must still withhold the chain. The split reads both dynamic markers there (pythonFileBindsOnlyByImport); a mutant that reads only the __getattr__ marker survived every gate. The three variants (mid.py hop, mid.py hop with setattr, __init__ hop) go red on it. Co-Authored-By: Claude Sonnet 5.5 --- test/pathgapcheck.sh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/pathgapcheck.sh b/test/pathgapcheck.sh index 2ecf5a5e0..0a2bfa9cf 100755 --- a/test/pathgapcheck.sh +++ b/test/pathgapcheck.sh @@ -646,9 +646,10 @@ done # 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. +# does the same; globalsmid/setattrmid/globalsinitre: the same two writes after the hop's own re-export of Base (`globals()['Base'] = Other`), in a mid.py hop and in +# an __init__ hop, which reads the write marker in pythonFileBindsOnlyByImport (the re-export hop, not the package-attribute path); 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" +for v in starmid starinit starqual starqualmid assignmid assigninit assignqual globalmid globalsinit setattrinit globalsmid setattrmid globalsinitre 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" @@ -677,6 +678,12 @@ for v in starmid starinit starqual starqualmid assignmid assigninit assignqual g 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" ;; + globalsmid) printf "${hop}globals()['Base'] = Other\n" > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + setattrmid) printf "import sys\n${hop}setattr(sys.modules[__name__], 'Base', Other)\n" > "$d/pkg/x/mid.py" + out="from pkg.x.mid import Base\n\n${tgt}$(sub Base)\n" ;; + globalsinitre) printf "${hop}globals()['Base'] = Other\n" > "$d/pkg/x/__init__.py" + out="from pkg.x import Base\n\n${tgt}$(sub 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" @@ -1289,7 +1296,7 @@ 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 +for v in starmid starinit starqual starqualmid assignmid assigninit assignqual globalmid globalsinit setattrinit globalsmid setattrmid globalsinitre augmid forinit withinit walrusinit defmid classmid; do nsArm "(K23) 2c hop file rebinds the name ($v) - withheld" "$TMP/c7_$v" "run,target" \ '' \ "" From 7c774e1f124a2d10b159b4b97b0ba92c53967f43 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 04:23:05 -0400 Subject: [PATCH 46/95] python pin: name the dynamic-write floor and match in the changelog fragment vars()/locals()/exec/__dict__/module-attribute writes are not dynamic markers, so a pin into such a file can be wrong (an old floor the split now reaches in __getattr__ files too). Comment and changelog text only; no behaviour change. Co-Authored-By: Claude Sonnet 5.5 --- changelog.d/py-alias-split-marker.md | 8 +++++--- src/graph.h | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/changelog.d/py-alias-split-marker.md b/changelog.d/py-alias-split-marker.md index c6693006c..52d78095e 100644 --- a/changelog.d/py-alias-split-marker.md +++ b/changelog.d/py-alias-split-marker.md @@ -3,7 +3,9 @@ A Python call through a renamed import (`from m import f as g; g()`) stayed undecided whenever `m` defined a module-level `__getattr__` (PEP 562), as if `__getattr__` could replace `m.f`. It cannot: Python consults it only for a name the module does not bind. The call now binds `m.f` as a proven edge in that case too, and a module `__getattr__` in the calling file no -longer withholds it either. It still stays undecided when `f`'s definition in `m` sits inside an `if`, `try`, `with` or loop -at module scope (it may not run, and `__getattr__` then answers), when `m` writes its namespace dynamically (`globals()[...]`, -`setattr(...)`), and in every case listed for the renamed-import change. On CPython's standard library this restores 11 +longer withholds it either. It still stays undecided when `f`'s definition in `m` sits inside an `if`, `try`, `with`, `match` or +loop at module scope (it may not run, and `__getattr__` then answers), when `m` writes its namespace dynamically (`globals()[...]`, +`setattr(...)`), and in every case listed for the renamed-import change. Known limit, unchanged: a module that rebinds a +name some other way (`vars()[...]`, `locals()[...]`, `exec(...)`, `__dict__[...]`, `sys.modules[__name__].name = ...`) is not +recognized as a dynamic write, so a pin into it can be wrong, now also in files that define `__getattr__`. On CPython's standard library this restores 11 call sites (`re.sub`, `re.escape`, `re.compile`, `platform.system`, `platform.architecture`). Gate: `test/pyaliascallcheck.sh`. diff --git a/src/graph.h b/src/graph.h index ad0c42fa3..8d7fbe4d7 100644 --- a/src/graph.h +++ b/src/graph.h @@ -2201,7 +2201,9 @@ inline bool isPythonSourcePath( std::string_view path ) noexcept // or `setattr(...)` call anywhere in the file (`globals()['a'] = x`, `setattr(sys.modules[__name__], ...)`), a write no static table // can see, so the file may rebind ANY name. pythonFileBindsOnlyByImport and pythonPackageMayBind read either as unknown // (pythonDynamicNamespace); pythonRenamedImportPin lets `__getattr__` withhold only a name that may be unbound (kPythonCondDefPrefix). -// `sys.modules[__name__].a = x` is not a call and stays a floor. +// KNOWN FLOOR (not detected, not markers): zero-argument `vars()[...] =` / `locals()[...] =` at module scope, `exec('a = x')`, +// `sys.modules[__name__].__dict__[...] =` and `sys.modules[__name__].a = x` also rebind a module name; a pin in a file that uses one +// of them can be wrong (the same old floor the base has in files without a `__getattr__`). Extending the marker is a carded change. inline constexpr std::string_view kPythonDynamicBind = "__getattr__"; inline constexpr std::string_view kPythonDynamicWrite = "(globals)"; From 9d7bd5e0eef02fb949511dc2764c4847040da085 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 04:26:08 -0400 Subject: [PATCH 47/95] regen: showcase capture 2026-10-28 on a clean tree, COMMANDS.md Co-Authored-By: Claude Opus 5.5 --- docs/COMMANDS.md | 315 ++--- docs/captures/COMMANDS_showcase_2026-10-28.md | 1234 ++++++++--------- 2 files changed, 740 insertions(+), 809 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 9d7c45ac3..ef9d48114 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -196,7 +196,7 @@ _The honest half of the contract: a task with no ripwire-shaped evidence ABSTAIN $ ./build/ripwire . --help-task="write a cheerful release announcement" - + ``` @@ -216,7 +216,7 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - @@ -253,7 +253,7 @@ _Restore the / sections the --for bundle collapses to a counted s ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --sections=lego,compose - + @@ -280,7 +280,7 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - + @@ -315,7 +315,7 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - + @@ -347,7 +347,7 @@ _Same query with routing forced OFF (plain subtoken+body BM25) — contrast with ``` $ ./build/ripwire . --for="rankGraphTeleport" --no-route - + @@ -357,7 +357,8 @@ $ ./build/ripwire . --for="rankGraphTeleport" --no-route 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):** @@ -378,7 +379,7 @@ _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 - + @@ -409,7 +410,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 - @@ -439,7 +440,7 @@ _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 - + @@ -447,7 +448,8 @@ $ ./build/ripwire . --for="quality delta acks ledger rubber stamp" --no-doc-ment 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 ) -... [23 more line(s); run it to see the whole thing] +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] ``` **Caveats (stated by the binary):** @@ -530,7 +532,7 @@ _Most relevant DOCS' full bodies (markdown only) — recall what is already writ $ ./build/ripwire . --recall="quality delta gating exit codes" ripwire recall — "quality delta gating exit codes" — 124 relevant of 305 document files, best-first — total=124 shown=8 capped=1 truncated=6 generated_demoted=3 max_tokens=8000 share_bytes=2468 est_tokens=4165 -━━ README.md (relevance 8.062) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 222723 B; lines="2747-2759,2760-2767"; dropped_by_budget=23] +━━ README.md (relevance 8.063) ━━ [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. @@ -593,20 +595,25 @@ and both are yours, because the file you write is the only thing that sets them: **Try it** -_The directory-as-knowledge-base pattern: --recall pointed at a 1297593-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._ +_The directory-as-knowledge-base pattern: --recall pointed at a 1307034-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._ ``` $ ./build/ripwire /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=2040 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 10.141) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 592169 B; lines="3836-3840"; dropped_by_budget=164] [truncated: 1865 of 7741 bytes] -### `--field-affinity[=STRUCT]` +━━ commands.md (relevance 10.971) ━━ [sections: 1 of 166 selected (193 in doc), section-granular; whole doc 602072 B; lines="555-580"; dropped_by_budget=165] [truncated: 1854 of 4257 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` @@ -1176,19 +1183,10 @@ _Mid-task situational report for the current git diff — recorded against a CLE ``` $ ./build/ripwire . --situ -ripwire situational-awareness — 1 changed file(s), 25 symbols in them +ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: de9d903b2+dirty - change_set: granularity=file changed_symbols=25 file_symbols=25 files_narrowed=0 files_file_granular=1 diff_hunks=3 diff_hunks_located=0 diff_hunks_unlocated=3 — 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] - decl/def partners (3) not_dependents=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/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: 0 symbols across 0 files transitively depend on these changes - counts_floor=1 graph_ambiguous=2988 graph_unresolved=15914 graph_unindexed=275 (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] - [2] tests to run (0): (none transitively reach these files) - script_gates_unmodelled=795 — 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: 70c265cd6 + (0 changed files — working tree is clean, nothing to analyze) ``` **Shaped by:** `--affected`, `--test-gate`, `--legend`, `--limit` @@ -1205,21 +1203,21 @@ _The continuation packet for the NEXT session: disk truth (branch/sha ``` $ ./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):** @@ -1240,12 +1238,8 @@ _Pre-PR gate on a CLEAN tree: no obligations, exit 0._ ``` $ ./build/ripwire . --test-gate - - - - - + + ``` @@ -1544,11 +1538,11 @@ $ ./build/ripwire . --query="teleport pagerank" --top-k=5 - + - + ... [17 more line(s); run it to see the whole thing] @@ -1572,7 +1566,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1878,7 +1872,7 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - + @@ -2005,7 +1999,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -2036,7 +2030,7 @@ _THE single wide-angle quality read: six families in one pass, an eligible/ranke ``` $ ./build/ripwire . --quality-panel - @@ -2104,7 +2098,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2244,16 +2238,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - - - + + - - - + + + + - + ... [22 more line(s); run it to see the whole thing] ``` @@ -2276,16 +2270,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] ``` @@ -2302,7 +2296,7 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + @@ -2331,7 +2325,7 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - + @@ -2854,7 +2848,7 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - + @@ -2949,8 +2943,8 @@ _The same clean verdict with what it folded printed back: every stale acknowledg ``` $ ./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. --> + @@ -2978,8 +2972,8 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./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. --> + ``` @@ -3003,8 +2997,8 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./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. --> + ``` @@ -3029,7 +3023,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 -

-… [167 more display lines; full output is 17121 bytes on 1 raw line(s)] +… [167 more display lines; full output is 17115 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: 5.17s** +**wall time: 3.57s** ````` @@ -6012,7 +5964,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The built-in rule registry — one row per rule with sev=/category=/rationale/lang=/since=; no corpus needed.* -**wall time: 2.56s** +**wall time: 1.08s** ````` @@ -6090,7 +6042,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *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: 4.90s** +**wall time: 3.11s** ````` @@ -6130,7 +6082,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *DROP two families, applied after selection; the raw select=/ignore= you passed rides on the root.* -**wall time: 4.55s** +**wall time: 2.88s** ````` @@ -6170,7 +6122,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *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: 5.06s** +**wall time: 3.05s** ````` @@ -6187,7 +6139,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *Pasting that findings_next=: the same rule under a 10x budget counts its true total — no count_capped=, no findings_next=.* -**wall time: 4.87s** +**wall time: 2.52s** ````` @@ -6218,7 +6170,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *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: 6.32s** +**exit code: 1** — **wall time: 3.14s** ````` (empty) @@ -6234,7 +6186,7 @@ ripwire: --lint-select: 'cach-' matches no rule or family (did you mean 'cache-' *A PREFIX with no near miss at all: the refusal points at --lint-catalog instead of guessing.* -**exit code: 1** — **wall time: 3.82s** +**exit code: 1** — **wall time: 2.90s** ````` (empty) @@ -6250,7 +6202,7 @@ ripwire: --lint-select: 'nosuchfamily' matches no rule or family — see --lint- *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: 5.81s** +**wall time: 2.94s** ````` {"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] @@ -6296,7 +6248,7 @@ sarif 2.1.0 rules= 39 results= 6886 *SARIF is always the FULL result set: paging alongside it refuses loudly.* -**exit code: 1** — **wall time: 5.98s** +**exit code: 1** ````` (empty) @@ -6312,11 +6264,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *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: 32.70s** +**wall time: 9.23s** ````` -

@@ -6327,11 +6279,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *The per-commit scalar: HEAD vs its first parent, with the three separately actionable sub-scores.* -**wall time: 31.87s** +**wall time: 16.12s** ````` -

@@ -6342,11 +6294,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *The range form: tree HEAD vs tree HEAD~3.* -**wall time: 22.05s** +**wall time: 15.95s** ````` -

@@ -6357,11 +6309,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *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: 8.53s** +**wall time: 7.14s** ````` - + @@ -6390,27 +6342,27 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M -… [851 more display lines; full output is 56606 bytes on 1 raw line(s)] +… [851 more display lines; full output is 56600 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: 6.23s** +**wall time: 5.29s** ````` - - + - - + + + @@ -6423,12 +6375,12 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M + + + - - - - + @@ -6439,7 +6391,7 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *The HTML graph with the initial colour mode set to community (the page embeds all five modes and keeps a live selector).* -**wall time: 5.91s** +**wall time: 4.98s** ````` (empty) @@ -6448,14 +6400,14 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M Artifact written: ````` - 156118 /aux/map2.html + 156112 /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: 7.73s** +**wall time: 8.31s** ````` (empty) @@ -6465,22 +6417,22 @@ stderr: ````` ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (29623015 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (60635651 bytes, rich family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (60637669 bytes, rich family) ````` Artifact written: ````` 29623015 /aux/ci_index.lean.ripwirecache - 60635651 /aux/ci_index.rich.ripwirecache - 90258666 total + 60637669 /aux/ci_index.rich.ripwirecache + 90260684 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.* -**wall time: 2.09s** +**wall time: 2.17s** ````` @@ -6520,7 +6472,7 @@ Artifact written: *Eval-only: a per-call-site census of WHICH mechanism resolved each call, and the canonical id of every surviving target.* -**wall time: 2.45s** +**wall time: 1.87s** ````` @@ -6574,11 +6526,11 @@ Artifact written: *The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when a card or ledger row gates.* -**exit code: 2** — **wall time: 5.08s** +**exit code: 2** — **wall time: 2.90s** ````` - + @@ -6596,11 +6548,11 @@ Artifact written: *The ledger-shaped fixture through the same check (exit 2: one gating card).* -**exit code: 2** — **wall time: 3.67s** +**exit code: 2** — **wall time: 2.65s** ````` - + @@ -6612,17 +6564,17 @@ Artifact written: *--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: 34.63s** +**exit code: 1** — **wall time: 23.62s** ````` - + - - + + - + @@ -6659,12 +6611,12 @@ Everything below runs with `cwd` = the throwaway clone at `/dirty` (`gi *Situational report for a real diff: blast radius + tests + co-change + forgotten co-change partners.* -**wall time: 14.90s** +**wall time: 14.29s** ````` ripwire situational-awareness — 1 changed file(s), 13 symbols in them root: . -at: de9d903b2+dirty +at: 70c265cd6+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: 251 symbols across 60 files transitively depend on these changes (showing 8 of 60 files — shown=8 total=60 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=60) counts_floor=1 graph_ambiguous=2988 graph_unresolved=15914 graph_unindexed=275 (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] @@ -6683,8 +6635,8 @@ at: de9d903b2+dirty 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=795 — 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="5516": - (none — 5516 commits were mined and none co-edited a file outside your diff) + [3] co-change — usually edited with these but NOT in your diff (0) window="18mo@HEAD" commits="5518": + (none — 5518 commits were mined and none co-edited a file outside your diff) next: --test-gate ````` @@ -6692,7 +6644,7 @@ at: de9d903b2+dirty *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: 9.12s** +**exit code: 4** — **wall time: 8.78s** ````` @@ -6734,11 +6686,11 @@ at: de9d903b2+dirty *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: 43.96s** +**exit code: 2** — **wall time: 54.05s** ````` - @@ -6773,6 +6725,7 @@ at: de9d903b2+dirty stderr: ````` +ripwire: cache : over its 2048 MiB budget — evicted 72 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) 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) ````` @@ -6781,11 +6734,11 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap *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: 32.56s** +**exit code: 2** — **wall time: 27.26s** ````` - @@ -6828,10 +6781,10 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap *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: 41.13s** +**exit code: 2** — **wall time: 23.76s** ````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":167,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"de9d903b2+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] +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":167,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"70c265cd6+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"}, @@ -6875,7 +6828,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap *NEW FLAG: --ack-only matching nothing REFUSES rather than falling back to acking everything.* -**exit code: 1** — **wall time: 30.62s** +**exit code: 1** — **wall time: 19.40s** ````` (empty) @@ -6893,7 +6846,7 @@ ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written *NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* -**wall time: 33.00s** +**wall time: 20.84s** ````` (empty) @@ -6911,11 +6864,11 @@ ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already *Re-run after the partial ack: acked=3, the rest still gate (exit 2).* -**exit code: 2** — **wall time: 27.97s** +**exit code: 2** — **wall time: 27.56s** ````` - @@ -6974,11 +6927,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6990,11 +6943,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row identical.* -**wall time: 4.23s** +**wall time: 7.34s** ````` - @@ -7006,11 +6959,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -7046,14 +6999,14 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire ) and selectors take it. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. --> - + @@ -7078,7 +7031,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire of ; dup_loc=/dup_pct=. window: shown= total= capped= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). root=: p= relative to it. groups=/type3=: Type-2 and Type-3 group totals over all groups; total= is their sum. exempt_groups=N: groups whose members all sit on fixture/shell-runner paths quality-delta duplication ignores. idiom_groups=/demoted_groups=: groups of one recognized idiom / those quality-delta demotes to minor; floors. clone_groups=N: clusters after merging pairs (rows sharing gid=); a floor under type3_capped=1. total_loc=N: lines of every function body the detector considered; dup_pct= is dup_loc= over it. group exempt=fixture|shell-runner: every member is on such a path; quality-delta duplication ignores it. group idiom=: the recognized shape every member spells: threshold-ladder, switch-name-table, builder-chain. --> @@ -7118,12 +7071,12 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire ; v=unknown = no merge base. at=: commit+dirty+shallow. head=: the HEAD commit, bare 9-hex sha (at= adds +dirty). head_ref=: HEAD's branch (HEAD when detached); that branch itself is not scanned. refs=N: local branches scanned (refs/heads only); unmerged + superseded + merged + unknown = refs. blobs=N: distinct git blobs read for the sweep. unmerged=N: refs whose authored work the live line genuinely lacks. superseded=N: refs whose work the live line re-implemented (removed the same base code). merged=N: refs whose work HEAD already has; omitted from the rows. name=: the local branch. tip=: the branch tip commit (9 hex). date=: the tip's committer date, YYYY-MM-DD. files=N: files with stray lines; rows capped at 12, a more element counts the rest (detail=1 lists all). ref superseded=N>: of this ref's stray= lines, those in files the live line re-implemented. unknown=N: refs that could not be analysed (v=unknown, e.g. no merge base); never counted merged. --> - - + + ````` @@ -7138,11 +7091,11 @@ stderr: *CHANGED: --plan surfaces those same refs as an row rather than silently dropping them.* -**wall time: 14.50s** +**wall time: 8.14s** ````` - + ````` @@ -7157,11 +7110,11 @@ stderr: *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: 14.69s** +**wall time: 10.07s** ````` -

@@ -7172,11 +7125,11 @@ stderr: *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: 31.41s** +**wall time: 24.44s** ````` - @@ -7218,7 +7171,7 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 20.93s** +**exit code: 1** — **wall time: 17.02s** ````` (empty) @@ -7238,11 +7191,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr *The continuation packet with a REAL diff: verified changed symbols + blast radius + tests-to-run, then the heuristic rows.* -**wall time: 11.12s** +**wall time: 12.84s** ````` - + @@ -7267,11 +7220,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr - - - - - + + + + + ````` @@ -7280,10 +7233,10 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr *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: 3.07s** +**wall time: 4.01s** ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 keep this branch-free — it sits inside the PageRank sort comparator de9d903b2fd847266c9a091d94c41e7b91b58e47 lane/defs-lookup +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 keep this branch-free — it sits inside the PageRank sort comparator 70c265cd61f6db9323b3860e35d1dedba212255c lane/defs-lookup ````` stderr: @@ -7297,7 +7250,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort *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.51s** +**wall time: 1.97s** ````` @@ -7310,7 +7263,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort - + @@ -7327,7 +7280,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort *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** — **wall time: 1.58s** +**exit code: 1** — **wall time: 1.91s** ````` (empty) @@ -7344,7 +7297,7 @@ ripwire: --note-add: target 'gitOneLine' is ambiguous — it matches 2 definitio *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** — **wall time: 1.35s** +**exit code: 1** — **wall time: 1.47s** ````` (empty) @@ -7361,17 +7314,17 @@ ripwire: --note-add: target not found: lessByScoreDescIdd (did you mean 'lessByS *The same symbol addressed by its CANONICAL id: already canonical, so nothing is rewritten and both notes land on ONE target.* -**wall time: 3.46s** +**wall time: 2.51s** ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort de9d903b2fd847266c9a091d94c41e7b91b58e47 lane/defs-lookup +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 70c265cd61f6db9323b3860e35d1dedba212255c lane/defs-lookup ````` ## `./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.* -**wall time: 2.41s** +**wall time: 1.90s** ````` @@ -7410,10 +7363,10 @@ src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 chose the flat The element on the same output — past the 30-line display cut above: ````` - + - + ````` @@ -7438,7 +7391,7 @@ ripwire: --edit-payload is empty; empty never means delete *Whole-symbol replace without a whole-file read: the payload is the ORIGINAL flat body, so this edit undoes the sandbox's deep-nesting regression. The receipt's span is the POST-edit byte range; replaced_bytes counts the old bytes overwritten.* -**wall time: 11.74s** +**wall time: 11.62s** Input file: @@ -7466,7 +7419,6 @@ inline bool lessByScoreDescId( const std::vector& scores, std::uint32_t a stderr: ````` -ripwire: cache : over its 2048 MiB budget — evicted 116 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, tests_to_run; next: bash test/greptiercheck.sh ````` @@ -7474,15 +7426,15 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, *The closed loop: contract unchanged (same params, same publicness) after the replace — nothing provably incompatible.* -**wall time: 8.18s** +**wall time: 5.91s** ````` - + - + @@ -7496,7 +7448,7 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, *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: 8.30s** +**wall time: 10.38s** ````` {"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] @@ -7518,7 +7470,7 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_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: 3.10s** +**wall time: 3.51s** ````` {"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] @@ -7534,7 +7486,7 @@ ripwire edit: applied atomically; receipt carries region, blob_sha (post-check s *Insert BEFORE, with --edit-target-file pinning which same-named definition (here unambiguous — the disambiguator is simply honoured).* -**wall time: 9.57s** +**wall time: 7.52s** ````` {"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] @@ -7559,7 +7511,7 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, *An unknown TARGET refuses and leaves every file byte-identical.* -**exit code: 1** — **wall time: 3.67s** +**exit code: 1** — **wall time: 3.10s** ````` (empty) @@ -7575,7 +7527,7 @@ ripwire: --replace-symbol-body: symbol 'DoesNotExist' not found *A versioned multi-edit TRANSACTION preflighted without writing: the receipt shows what each op would read and touch.* -**wall time: 3.29s** +**wall time: 3.19s** Input file: @@ -7600,7 +7552,7 @@ Input file: *The same plan committed: per-file locks, re-verify-before-write, atomic rename, rollback on a later failure.* -**wall time: 10.12s** +**wall time: 7.57s** ````` {"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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] @@ -7630,11 +7582,11 @@ ripwire: --edit-plan requires exactly one of --dry-run or --apply *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: 43.28s** +**exit code: 2** — **wall time: 22.49s** ````` - @@ -7677,7 +7629,7 @@ ripwire: --quality-delta gating: 2 preexisting-worse major finding(s); first: du *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: 34.73s** +**exit code: 1** — **wall time: 14.47s** ````` (empty) @@ -7696,7 +7648,7 @@ ripwire: --quality-baseline: this tree already holds 2 gating finding(s) against *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: 22.70s** +**wall time: 21.98s** ````` (empty) @@ -7712,10 +7664,10 @@ ripwire: wrote ./.ripwire_quality_baseline (snapshot of 32179 per-symbol rows, 3 Artifact written: ````` - 5446333 .ripwire_quality_baseline + 5446331 .ripwire_quality_baseline # ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit producer 3ada5f8adf8c098b8e95cdb1aa98721e542fbba9885d4731d55abb94b303eb41 -head de9d903b2fd847266c9a091d94c41e7b91b58e47 +head 70c265cd61f6db9323b3860e35d1dedba212255c dirty 1 absorbed 2 ccx 67c15011ee8b 0 @@ -7728,11 +7680,11 @@ ccx 6a732f6699b *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: 22.07s** +**wall time: 24.07s** ````` - ````` @@ -7855,57 +7807,57 @@ quality_delta desc_bytes= 742 schema_bytes= 279 required=[] *MCP `for`: always bundle=sigs (never the CLI's compact route), the same ranked signatures as --for.* -**wall time: 5.81s** +**wall time: 3.83s** ````` {"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: 7021 more bytes on this line] -"_index":"[index: files=3931 symbols=33184 hash=c1506576]","_fresh":"ok"}} -{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"', t, re.S)) +i = legend.find("; callers by=rank of=:") +if i < 0 or "; callers of=: the route named" in legend: sys.exit(2) +clause = legend[i:] +names = set(re.findall(r'\s([a-z_0-9]+)="', m.group(0))) +miss = [n for n in names if not re.search(r'(?"$TMP/r3" +R3="$( hop "$TMP/r3" )" +if grep -q ']*n="parse_manifest"[^>]*in="4"[^>]*r="1"' "$TMP/r3" && printf '%s' "$R3" | grep -q '^"$TMP/r4" +if [ -s "$TMP/r4" ] && grep -q ']*n="release_lockfile"[^>]*r="1"' "$TMP/r4" && ! grep -q '"$TMP/r5" +if [ -s "$TMP/r5" ] && grep -q ']*n="verify_dag"[^>]*r="1"' "$TMP/r5" && ! grep -q '"$TMP/r6" +if [ -s "$TMP/r6" ] && grep -q ']*n="poll_socket_once"[^>]*in="1"[^>]*r="[4-9]"' "$TMP/r6" && ! grep -q '"$TMP/r7on.$lg"; strip "$TMP/r7on.$lg" >"$TMP/r7on.s.$lg" + python3 - "$TMP/r7on.s.$lg" <<'PY' +import re, sys +p = sys.argv[1]; t = open(p, encoding="utf-8").read() +t = re.sub(r'; callers by=rank of=:.*?next=(?: lists)? the (?:whole caller )?radius', '', t, flags=re.S) +open(p, "w", encoding="utf-8").write(t) +PY + RIPWIRE_NO_CALLER_HOP=1 run "$FR" --for="dependency cycle error" --legend=$lg >"$TMP/r7off.$lg"; strip "$TMP/r7off.$lg" >"$TMP/r7off.s.$lg" + if [ -s "$TMP/r7off.s.$lg" ] && cmp -s "$TMP/r7on.s.$lg" "$TMP/r7off.s.$lg" && ! grep -q ' $r8on)"; else no "(R8) est_tokens did not grow with the ranked hop (on=[$r8on] off=[$r8off])"; fi +for extra in --json --signatures-only --token-budget=3000; do + run "$FR" --for="dependency cycle error" "$extra" >"$TMP/r9.on" + RIPWIRE_NO_CALLER_HOP=1 run "$FR" --for="dependency cycle error" "$extra" >"$TMP/r9.off" + if [ -s "$TMP/r9.on" ] && cmp -s "$TMP/r9.on" "$TMP/r9.off"; then ok "(R9) [G] $extra: no ranked hop, byte-identical to the hop-off answer"; else no "(R9) $extra: the ranked hop changed the answer"; fi +done +run "$FR" --callers=verify_dag >"$TMP/r10" +python3 - "$TMP/r1" "$TMP/r10" <<'PY' +import re, sys +hop = re.search(r']*>(.*?)', open(sys.argv[1]).read(), re.S) +cal = open(sys.argv[2]).read() +if not hop: sys.exit(1) +key = lambda r: (re.search(r' n="([^"]*)"', r).group(1), re.search(r' p="([^"]*)"', r).group(1)) +first = [key(r) for r in re.findall(r']*/>', hop.group(1)) if ' d="2"' not in r] +verb = {key(r) for r in re.findall(r']*/>', cal) if ' n="' in r and ' p="' in r} +sys.exit(0 if first and all(k in verb for k in first) else 1) +PY +verdict $? "(R10) every first-hop row of the ranked hop is --callers=verify_dag's own row" "(R10) a ranked first-hop row differs from --callers=verify_dag" + # ── X ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── -python3 - "$TMP/p1" "$TMP/p4f" <<'PY' +python3 - "$TMP/p1" "$TMP/p4f" "$TMP/r1" "$TMP/r1f" <<'PY' import re, sys for p in sys.argv[1:]: for c in re.findall(r'', open(p).read(), re.S): @@ -254,7 +331,7 @@ verdict $? "(X3) no -- inside a legend comment" "(X3) a legend comment holds --" run "$FC" --for=start_server >"$TMP/x2" cmp -s "$TMP/p1" "$TMP/x2"; verdict $? "(X1) deterministic" "(X1) two runs differ" if command -v xmllint >/dev/null 2>&1; then - for f in p1 p2 p3 p4f p8 b3 f1.on dp.f; do xmllint --noout "$TMP/$f" 2>/dev/null; verdict $? "(X2) well-formed: $f" "(X2) xmllint rejects $f"; done + for f in p1 p2 p3 p4f p8 b3 f1.on dp.f r1 r1f r3; do xmllint --noout "$TMP/$f" 2>/dev/null; verdict $? "(X2) well-formed: $f" "(X2) xmllint rejects $f"; done else printf ' SKIP (X2) xmllint not installed\n' fi diff --git a/test/forcallerhopfix/rank/graph.c b/test/forcallerhopfix/rank/graph.c new file mode 100644 index 000000000..56208b4d5 --- /dev/null +++ b/test/forcallerhopfix/rank/graph.c @@ -0,0 +1,32 @@ +/* forcallerhopfix/rank — UPWARD-2HOP fixture: an error-shaped task ranks a narrow-funnel function (verify_dag, one caller). */ +#include + +void report_problem( const char* what ) +{ + fprintf( stderr, "%s\n", what ); +} + +int verify_dag( int node ) +{ + if( node < 0 ) + { + report_problem( "dependency cycle detected" ); + return 0; + } + return 1; +} + +int recompute_node( int node ) +{ + return verify_dag( node ); +} + +int recompute_dirty( int node ) +{ + return recompute_node( node ); +} + +int recompute_edges( int node ) +{ + return recompute_node( node + 1 ); +} diff --git a/test/forcallerhopfix/rank/lock.c b/test/forcallerhopfix/rank/lock.c new file mode 100644 index 000000000..bf45a58e3 --- /dev/null +++ b/test/forcallerhopfix/rank/lock.c @@ -0,0 +1,9 @@ +/* every caller of release_lockfile is a test: the chain already ends there, so no ranked hop */ +int release_lockfile( int fd ) +{ + if( fd < 0 ) + { + return -1; /* lockfile release stale */ + } + return 0; +} diff --git a/test/forcallerhopfix/rank/main.c b/test/forcallerhopfix/rank/main.c new file mode 100644 index 000000000..c1012c671 --- /dev/null +++ b/test/forcallerhopfix/rank/main.c @@ -0,0 +1,7 @@ +int recompute_dirty( int node ); +int recompute_edges( int node ); +int load_one( void ); +int main( void ) +{ + return recompute_dirty( 1 ) + recompute_edges( 2 ) + load_one(); +} diff --git a/test/forcallerhopfix/rank/sock.c b/test/forcallerhopfix/rank/sock.c new file mode 100644 index 000000000..27af76d94 --- /dev/null +++ b/test/forcallerhopfix/rank/sock.c @@ -0,0 +1,14 @@ +/* r=4: three socket timeout types rank above the one narrow function, so the ranked hop (r=1..3 only) carries none */ +struct socket_timeout_state { int socket_timeout; }; +struct socket_timeout_limits { int socket_timeout_max; }; +struct socket_timeout_stats { int socket_timeout_count; }; + +int poll_socket_once( int fd ) +{ + return fd > 0 ? 0 : -1; /* socket timeout */ +} + +int poll_loop( int fd ) +{ + return poll_socket_once( fd ); +} diff --git a/test/forcallerhopfix/rank/tests/lock_test.c b/test/forcallerhopfix/rank/tests/lock_test.c new file mode 100644 index 000000000..eff7b680f --- /dev/null +++ b/test/forcallerhopfix/rank/tests/lock_test.c @@ -0,0 +1,2 @@ +int release_lockfile( int fd ); +int test_release_lockfile( void ) { return release_lockfile( 3 ); } diff --git a/test/forcallerhopfix/rank/wide.c b/test/forcallerhopfix/rank/wide.c new file mode 100644 index 000000000..2091acac5 --- /dev/null +++ b/test/forcallerhopfix/rank/wide.c @@ -0,0 +1,14 @@ +/* the wide funnel: parse_manifest has four callers, so a task about its error carries no ranked hop */ +int parse_manifest( const char* text ) +{ + if( text == 0 ) + { + return -1; /* manifest syntax malformed */ + } + return 0; +} + +int load_one( void ) { return parse_manifest( "a" ); } +int load_two( void ) { return parse_manifest( "b" ); } +int load_three( void ) { return parse_manifest( "c" ); } +int load_four( void ) { return parse_manifest( "d" ); } From e9e6c1621ede0d32cc19e53ba212f648bb3147d2 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:42:30 -0400 Subject: [PATCH 56/95] for: the ranked caller hop's posture in one helper; the task-word gate on the shared word helpers Keeps runForLensPass's complexity flat (--quality-delta) and drops forRankedCallerHopTask's own scanner for namesplit::isIdentChar + splitRouteWords. Byte-identical answers. Co-Authored-By: Claude Opus 5.5 --- src/forcallerhop.h | 34 +++++++----------------------- src/verbs_for.h | 52 +++++++++++++++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/forcallerhop.h b/src/forcallerhop.h index d8741f30b..9ba2a9ac6 100644 --- a/src/forcallerhop.h +++ b/src/forcallerhop.h @@ -30,6 +30,8 @@ #include "defattrib.h" // attributeOneHopAnswer — via="name" on a row, as the callers verb decides it #include "filter.h" // pathTierOf — the ranked seed's noise gate (every first-hop caller a test or bench path) #include "graph.h" // viaNameAttr +#include "infra/namesplit.h" // isIdentChar — the ranked seed's task-word fold +#include "lexical.h" // splitRouteWords — the ranked seed's task words #include "memberowner.h" // MemberOwners / qualifiedMemberName — n= as the callers verb prints it (QUALNAME-1, train 33) #include "model.h" #include "nextverb.h" // nextFlag / nextAttrXml @@ -153,37 +155,17 @@ inline constexpr std::size_t kForRankedHopHeadRows = 3; // r=1..3: the emitte inline constexpr std::size_t kForRankedHopMaxCallers = 3; // the narrow-funnel bar: a wider fan-in is the callers verb's page, not a chain // The task names an error: a whole word error(s) / exception(s) / panic(s) / fail(s|ed|ure|ures) / crash(es), ASCII case-folded. -inline bool forRankedCallerHopTask( std::string_view task ) noexcept +inline bool forRankedCallerHopTask( std::string_view task ) { static constexpr std::string_view kWords[] = { "error", "errors", "exception", "exceptions", "panic", "panics", "fail", "fails", "failed", "failure", "failures", "crash", "crashes" }; - const auto wordByte = []( char c ) noexcept { return ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= '0' && c <= '9' ) || c == '_'; }; - for( std::size_t at = 0; at < task.size(); ) + std::string folded( task ); + for( char& c : folded ) { - if( !wordByte( task[at] ) ) - { - ++at; - continue; - } - std::size_t end = at; - while( end < task.size() && wordByte( task[end] ) ) { ++end; } - const std::string_view w = task.substr( at, end - at ); - for( const std::string_view k : kWords ) - { - bool same = w.size() == k.size(); - for( std::size_t i = 0; same && i < k.size(); ++i ) - { - const char c = w[i] >= 'A' && w[i] <= 'Z' ? char( w[i] - 'A' + 'a' ) : w[i]; - same = c == k[i]; - } - if( same ) - { - return true; - } - } - at = end; + c = !namesplit::isIdentChar( c ) ? ' ' : ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : c; } - return false; + const std::vector words = splitRouteWords( folded ); + return std::ranges::any_of( words, []( std::string_view w ) { return std::ranges::find( kWords, w ) != std::end( kWords ); } ); } inline std::vector forRankedCallerHopTargets( const IngestResult& ing, const Graph& g, std::span rankedHead ) diff --git a/src/verbs_for.h b/src/verbs_for.h index bdbff6e35..c0cd78a9c 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2566,6 +2566,31 @@ ForAutoBodiesResult buildForEnrichment( const rw::Config& cfg, const rw::IngestR return out; } +// UPWARD-2HOP (forcallerhop.h forRankedCallerHopTargets): the ranked caller hop is a LATE splice, on the late splices' posture (the +// compact auto bundle, XML, the default regime, the whole answer measured, no hops cut marker), never beside the name-exact hop, +// only on an error-shaped task; it reads the rows EMITTED (r= order), so its clause's "r=1..3" holds. `held` is the +// named hop's render, returned unchanged when that hop rode. +struct RankedHopPosture +{ + bool bundleCompact = false; // the auto bundle on the compact route + bool sigsRendered = false; + bool surfaceOff = false; + bool hopsCutMarker = false; + bool namedHop = false; // the name-exact hop already rode + const char* routeTag = ""; +}; +inline rw::CallerHopRender rankedCallerHop( const RankedHopPosture& p, const rw::Config& cfg, const rw::IngestResult& ing, const rw::Graph& g, + const std::vector& shownSigIds, std::string_view rootArg, rw::CallerHopRender held ) +{ + if( p.namedHop || !p.bundleCompact || !p.sigsRendered || p.surfaceOff || p.hopsCutMarker || rw::isNameExactRouteTag( p.routeTag ) || cfg.json + || cfg.tokenBudget > 0 || !rw::forCallerHopOn() || !rw::forRankedCallerHopTask( cfg.forTask ) ) + { + return held; + } + const std::vector ids = rw::forRankedCallerHopTargets( ing, g, shownSigIds ); + return ids.empty() ? held : rw::renderForCallerHop( ing, g, ids, rootArg, /*ranked=*/true ); +} + // mf1-nx (forcallerhop.h): the ONE function whose callers ride — `nameExactBodies` is the posture runForLensPass knows (the auto // bundle on the name-exact route); this adds the regime (XML, no explicit --token-budget, the A/B switch) and the target rule. inline std::vector forCallerHopIds( const rw::Config& cfg, const rw::IngestResult& ing, const rw::Graph& g, const std::vector& lensSurfaceIds, @@ -4286,24 +4311,17 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co headerStr = buildForHeader( /*withRouteAttr=*/true, /*withTaskEcho=*/true, {} ); } } - // ── UPWARD-2HOP (forcallerhop.h forRankedCallerHopTargets): an error-shaped task on a ranked route whose r=1..3 rows hold a - // narrow-funnel function carries that function's — seeded from the rows EMITTED (r= order), so its - // clause's "r=1..3" holds; the same posture as the late splices above, and never beside the name-exact hop - if( autoBundleMode && plan.compact && !cfg.json && cfg.tokenBudget <= 0 && rw::forCallerHopOn() && sigsPreRendered - && !enrich.surfaceOff && !enrich.hopsCutMarker && !headerParts.callerHopPresent && !isNameExactRouteTag( lr.routeTag ) - && rw::forRankedCallerHopTask( cfg.forTask ) ) + // ── UPWARD-2HOP (forcallerhop.h forRankedCallerHopTargets): an error-shaped task's ranked caller hop, a late splice on the + // rows EMITTED (rankedCallerHop states the posture); its clause rides beside it + callerHop = rankedCallerHop( RankedHopPosture{ .bundleCompact = autoBundleMode && plan.compact, .sigsRendered = sigsPreRendered, + .surfaceOff = enrich.surfaceOff, .hopsCutMarker = enrich.hopsCutMarker, + .namedHop = headerParts.callerHopPresent, .routeTag = lr.routeTag }, + cfg, ing, g, shownSigIds, flRootArg, std::move( callerHop ) ); + if( !callerHop.xml.empty() && !headerParts.callerHopPresent ) { - const std::vector rankedIds = rw::forRankedCallerHopTargets( ing, g, shownSigIds ); - if( !rankedIds.empty() ) - { - callerHop = rw::renderForCallerHop( ing, g, rankedIds, flRootArg, /*ranked=*/true ); - } - if( !callerHop.xml.empty() ) - { - headerParts.callerHopPresent = true; // the ranked clause rides beside the element it defines - headerParts.callerHopRanked = true; - headerStr = buildForHeader( /*withRouteAttr=*/true, /*withTaskEcho=*/true, {} ); - } + headerParts.callerHopPresent = true; // the ranked clause rides beside the element it defines + headerParts.callerHopRanked = true; + headerStr = buildForHeader( /*withRouteAttr=*/true, /*withTaskEcho=*/true, {} ); } // PR #135: the priced root this header will carry — every late splice and the est_tokens fixpoint, in // finishForLensHeader (above runForLens, with each splice's rationale). From 0158d95d20d7acf586418e1c763f8dd412c2ac60 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:48:46 -0400 Subject: [PATCH 57/95] defs: register --defs where three gates still miss it (F1-F3 of the review) ripwirepubliccheck arm 9 exempts the two recall prose lines of the 2026-10-28 capture (same content hashes as the 10-08/10-11/10-27 captures); mcpcontractcheck (G) classifies --defs as a paging CLI verb with no MCP twin yet (find_symbol stays on its own table until DEFS-ONE-CORE); --help's HONORED-by list (and its docs/COMMANDS.md quote) names --defs, as kPagingHonoringVerbs already does (testgatepagecheck PC-2). Red first on the lane head binary (built_from de9d903b2): arm 9 "unpinned git log" x2, mcpcontractcheck (G) "--defs", testgatepagecheck PC-2 "runtime honors --defs"; green after. Co-Authored-By: Claude Sonnet 5.5 --- docs/COMMANDS.md | 2 +- src/cli.h | 2 +- test/mcpcontractcheck.sh | 3 +++ test/ripwirepubliccheck.sh | 7 ++++++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index ef9d48114..2933ba959 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -4649,7 +4649,7 @@ $ ./build/ripwire . --hotspots --json **Answers:** paginate a high-cardinality verb paginate a high-cardinality verb. -HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for --lego. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's rows; its rows repeat on every page, complete). --edit-check is the same shape for a different reason: its rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --token-budget the same way; --for takes --limit/--offset as its FILE page, where the budget flags are refused in turn (bare --pack-task --limit refuses: it has no page without a budget), and --for/--pack-task beside --token-budget take the window as the BUDGETED-BUNDLE CANDIDATE PAGE — one window over the ranked candidates, signatures only (bodies come via next= or --expand), their resumable continuation; the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all. +HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for --lego --defs. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's rows; its rows repeat on every page, complete). --edit-check is the same shape for a different reason: its rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --token-budget the same way; --for takes --limit/--offset as its FILE page, where the budget flags are refused in turn (bare --pack-task --limit refuses: it has no page without a budget), and --for/--pack-task beside --token-budget take the window as the BUDGETED-BUNDLE CANDIDATE PAGE — one window over the ranked candidates, signatures only (bodies come via next= or --expand), their resumable continuation; the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all. **Try it** diff --git a/src/cli.h b/src/cli.h index a78c2f855..f7a607a03 100644 --- a/src/cli.h +++ b/src/cli.h @@ -2731,7 +2731,7 @@ inline constexpr char kHelpTail[] = " --zoom --external-surface --dead-code --mentions --graph-query --stray-content\n" " --test-gate --biggest-first --ensemble --quality-panel --context-ratio\n" " --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context\n" - " --edit-check --flags --situ --for --lego.\n" + " --edit-check --flags --situ --for --lego --defs.\n" " Emit at most N rows, skipping the first M; N overrides the verb's own display cap\n" " (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40\n" " impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio\n" diff --git a/test/mcpcontractcheck.sh b/test/mcpcontractcheck.sh index ea2a21e64..b25337776 100755 --- a/test/mcpcontractcheck.sh +++ b/test/mcpcontractcheck.sh @@ -371,6 +371,9 @@ TWIN = { # lane lego-transitive (2026-10-10): --lego joined the paging set the same conditional way for its DEEPER implementor # rows (serialize.h legoClosurePage); its twin takes the same limit/offset through mcpPageArgs. "--lego": "lego", + # lane D2 DEFS-LOOKUP (2026-10-28): --defs pages (one name's definition rows); no MCP twin yet -- MCP find_symbol keeps its own + # table until PLAN_DEFS_LOOKUP part 2 moves it onto rw::lookupDefs (card DEFS-ONE-CORE), so --defs is CLI-only for now. + "--defs": "", } unmapped = sorted( v for v in pagingCli if v not in TWIN ) check( not unmapped, "(G) every paging CLI verb is classified twin-or-not (%s)" % ( ",".join( unmapped ) or "none unmapped" ) ) diff --git a/test/ripwirepubliccheck.sh b/test/ripwirepubliccheck.sh index b36e4d76b..2cc3ef282 100755 --- a/test/ripwirepubliccheck.sh +++ b/test/ripwirepubliccheck.sh @@ -1295,6 +1295,9 @@ fi # docs/captures/COMMANDS_showcase_2026-10-27.md:606,621 — the same two prose lines, byte for byte (same content hashes), in the # train 40 re-capture (the recall picks that docs/COMMANDS.md section again after the regenerated COMMANDS.md). Re-keyed under # this date as above; nothing walks git history there. +# docs/captures/COMMANDS_showcase_2026-10-28.md:618,633 — the same two prose lines, byte for byte (same content hashes), in the +# lane/defs-lookup re-capture (adds --defs; the recall picks that docs/COMMANDS.md section again). Re-keyed under this date +# as above; nothing walks git history there. ARM9_OK='bench/cppbench/run_cppbench.py:e5d3f485c59b0187 bench/cppbench/run_cppbench.py:7e89ac16d50cd5d2 bench/cppbench/run_cppbench.py:5d65fe73e9406e01 @@ -1311,7 +1314,9 @@ docs/captures/COMMANDS_showcase_2026-10-08.md:22be6025a57af773 docs/captures/COMMANDS_showcase_2026-10-11.md:118fbfbb8f6823fe docs/captures/COMMANDS_showcase_2026-10-11.md:22be6025a57af773 docs/captures/COMMANDS_showcase_2026-10-27.md:118fbfbb8f6823fe -docs/captures/COMMANDS_showcase_2026-10-27.md:22be6025a57af773' +docs/captures/COMMANDS_showcase_2026-10-27.md:22be6025a57af773 +docs/captures/COMMANDS_showcase_2026-10-28.md:118fbfbb8f6823fe +docs/captures/COMMANDS_showcase_2026-10-28.md:22be6025a57af773' python3 - "$TMP/tracked.z" "$ARM9_OK" > "$TMP/arm9" <<'PY' import hashlib, os, re, sys paths = [p.decode('utf-8', 'surrogateescape') From ac80e6550c5f9fef58702eb904ced0ba22e45b24 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:56:56 -0400 Subject: [PATCH 58/95] defs: prints only what changes the next step (F4-F6 of the review) --path's inline alternatives were noise in three ways. tmux-14: both rows were C header prototypes of the bound definitions, while --path itself called the endpoint bound and unique (endpointDefinitionCount drops untied prototypes). ninja-14 / flake8-14 / nunjucks-14: definitions the answer already prints as endpoints were listed again as "other" definitions. And a compact row dropped decl_only, so a header declaration of the bound definition read as a second definition. - defattrib.h candidateGroupCount: the groups less the untied declarations, now the one rule behind both endpointDefinitionCount and defPickProvablyUnique. - defAltsXml skips untied groups and every group holding a printed pair endpoint (pathPrintedEnds, the pairs emitPathPairs prints), prints nothing when no row is left, and keeps decl_only="1" on compact rows with its reading in the legends (CLI full + compact, MCP path_between). - the legend clause is built from what the answer carries (PathAlts.declOnly): present-only. New fixture rows (ping1-3.ts: two pair endpoints, a tied interface contract, a third definition; proto/: a header prototype no include ties). Red first on the lane head binary (built_from de9d903b2): P4 and 5 P5 arms FAIL; green after. Mutants in the commits that follow. Co-Authored-By: Claude Sonnet 5.5 --- changelog.d/path-inline-alternatives.md | 6 +- src/compactlegend.h | 3 +- src/defattrib.h | 9 ++- src/defslookup.h | 101 ++++++++++++++++-------- src/mcpverbs.h | 4 +- src/verbs_navigate.h | 4 +- test/defslookupcheck.sh | 19 +++++ test/defslookupfix/proto/orphan.c | 1 + test/defslookupfix/proto/orphan.h | 1 + test/defslookupfix/src/ping1.ts | 4 + test/defslookupfix/src/ping2.ts | 4 + test/defslookupfix/src/ping3.ts | 4 + 12 files changed, 118 insertions(+), 42 deletions(-) create mode 100644 test/defslookupfix/proto/orphan.c create mode 100644 test/defslookupfix/proto/orphan.h create mode 100644 test/defslookupfix/src/ping1.ts create mode 100644 test/defslookupfix/src/ping2.ts create mode 100644 test/defslookupfix/src/ping3.ts diff --git a/changelog.d/path-inline-alternatives.md b/changelog.d/path-inline-alternatives.md index 98373def9..dca11d702 100644 --- a/changelog.d/path-inline-alternatives.md +++ b/changelog.d/path-inline-alternatives.md @@ -3,5 +3,7 @@ `--path=fetch,compose` on hono printed `from_defs="7"`, bound one `fetch` and named none of the others, so an agent had to guess a `file:name` spelling and retry. When an endpoint's name binds two or more definition groups, the answer (CLI and MCP `path_between`) now ends with ``: one compact row per other definition with `p=`, `class=` and a -pasteable `sel=`. Up to 16 rows; past that `alts_shown=K defs=N next=--defs=NAME` pages the rest. An endpoint that binds one -definition, or a `*` glob endpoint, adds nothing. +pasteable `sel=`, and `decl_only="1"` when the row has no body (a header declaration or an interface contract). Up to 16 rows; +past that `alts_shown=K defs=N next=--defs=NAME` pages the rest. A definition the answer already prints (the bound one, or the +endpoint of a listed ``) is not listed again, and a C prototype no `#include` ties to a definition is not a second candidate +(the rule `from_defs`/`to_defs` use), so an endpoint that binds one definition, or a `*` glob endpoint, adds nothing. diff --git a/src/compactlegend.h b/src/compactlegend.h index cbe2363cd..5c0372544 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -837,7 +837,8 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "unproven_defs", "name unproven_defs=K: same-named definitions a file:name spelling could not tie to that file; not in defs=", true, "name", MapHeaderRead::No, {}, "defs" }, { "shown", "name shown=K offset=M: rows M..M+K of defs=; next= pages the rest", true, "name", MapHeaderRead::No, {}, "defs" }, // also defines offset= { "next", "name next=: the next page, or (defs=0) the single-name call whose refusal names near names", true, "name", MapHeaderRead::No, {}, "defs" }, - { "side", "alts side=from|to of=: the OTHER definitions that endpoint's name binds (the pick is not provably unique): , sel= pastes as that endpoint", true, "alts", MapHeaderRead::No, {}, "path" }, + { "side", "alts side=from|to of=: the definitions that endpoint's name binds that this answer does not already print (the pick is not provably unique): , sel= pastes as that endpoint", true, "alts", MapHeaderRead::No, {}, "path" }, + { "decl_only", "alts d decl_only=1: no body (a header declaration or interface contract), not a second definition", true, "d", MapHeaderRead::No, {}, "path" }, { "alts_shown", "alts alts_shown=K defs=N: K of the other rows shown; next= pages the rest", true, "alts", MapHeaderRead::No, {}, "path" }, // also defines defs= there { "from_p", "from_p=/to_p=: the definitions from= and to= were bound to", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_p= { "from_defs", "from_defs=/to_defs=: definitions of each name, all searched; above 1, qualify file:name", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_defs= diff --git a/src/defattrib.h b/src/defattrib.h index b9cfd706f..d2289e7ca 100644 --- a/src/defattrib.h +++ b/src/defattrib.h @@ -980,15 +980,18 @@ struct PathEndpointBinding // How many definitions an endpoint really names: its groups (a definition with its declarations is one), less the untied // declarations (defGroupUntied) — a prototype the identity rule cannot prove is the same function is no second candidate here, // exactly as it is silent on a 1-hop answer no row ties it to. +inline std::size_t candidateGroupCount( const DefGroups& groups ) +{ + const std::size_t untied = std::size_t( std::count( groups.untied.begin(), groups.untied.end(), char( 1 ) ) ); + return std::max( 1, groups.size() - untied ); +} inline std::size_t endpointDefinitionCount( const IngestResult& ing, std::span defs ) { if( defs.size() < 2 ) { return defs.size(); } - const DefGroups groups = defGroupsOf( ing, defs ); - const std::size_t untied = std::size_t( std::count( groups.untied.begin(), groups.untied.end(), char( 1 ) ) ); - return std::max( 1, groups.size() - untied ); + return candidateGroupCount( defGroupsOf( ing, defs ) ); } // a hop row (i > 0) whose edge into it is a split arm (Q1: on every path, whatever the endpoints' defs=) inline bool pathHopSplitArm( const Graph& g, std::span path, std::size_t i ) noexcept diff --git a/src/defslookup.h b/src/defslookup.h index 6f7114403..0266c6b4c 100644 --- a/src/defslookup.h +++ b/src/defslookup.h @@ -183,7 +183,7 @@ inline DefLookup lookupDefs( const IngestResult& ing, const Graph& g, std::strin } // One definition row: . `compact` (the --path alternatives) -// keeps only what the next step needs: sel=, p=, class=. +// keeps only what the next step needs: sel=, p=, class=, decl_only=. inline void defRowXml( std::string& out, const IngestResult& ing, const DefLookup& L, const DefRow& r, const MemberOwners& owners, bool compact ) { std::vector esc; @@ -207,10 +207,10 @@ inline void defRowXml( std::string& out, const IngestResult& ing, const DefLooku if( !compact ) { out += " in=\"" + std::to_string( r.callers ) + "\""; - if( L.groups.bodyless[ r.group ] ) - { - out += " decl_only=\"1\""; - } + } + if( L.groups.bodyless[ r.group ] ) + { + out += " decl_only=\"1\""; // compact too: a header declaration of the bound definition is no second definition } out += "/>"; } @@ -233,25 +233,57 @@ inline void defRowJson( std::string& out, const IngestResult& ing, const DefLook out += "}"; } -// Is the pick a verb made among these definitions provably unique? Only when one group is left — the resolver's binding of a -// name with several groups is a choice, not a proof (attrib-multidef). -inline bool defPickProvablyUnique( const DefLookup& L ) noexcept { return L.defs() <= 1; } +// Is the pick a verb made among these definitions provably unique? Only when one candidate group is left: the groups less the +// untied C-family prototypes (defattrib.h candidateGroupCount — the rule --path's own endpointDefinitionCount uses, so the +// answer never calls an endpoint bound and unique while lists a prototype as another definition). Several candidates: the +// resolver's binding is a choice, not a proof (attrib-multidef). +inline bool defPickProvablyUnique( const DefLookup& L ) noexcept { return candidateGroupCount( L.groups ) <= 1; } -// --path's inline alternatives for one endpoint (part 5): every OTHER group up to the cap, compact rows, then -// alts_shown=K defs=N next=--defs=NAME when more exist. Empty when the pick is provably unique (0 bytes added). -// `bound`: the definition the answer bound (kNoNode when it bound none: every group is then an alternative). -inline std::string defAltsXml( const IngestResult& ing, const DefLookup& L, NodeId bound, std::string_view side ) +// The definitions a --path answer already prints, as the --path answer's own rows name them: the root's from_p=/to_p= are the +// first pair's, and every printed / row carries its own (pathpairs.h emitPathPairs). Listing one again under +// tells the agent nothing new. +inline std::vector pathPrintedEnds( const PathPairs& pp ) +{ + std::vector ends; + if( pp.active() ) + { + const std::size_t printed = std::min( pp.pairs.size(), kPathPairChains + kPathPairAltRows ); + for( std::size_t i = 0; i < printed; ++i ) + { + if( !pp.pairs[i].chain.empty() ) + { + ends.push_back( pp.pairs[i].chain.front() ); + ends.push_back( pp.pairs[i].chain.back() ); + } + } + } + return ends; +} + +struct PathAlts +{ + std::string xml; + bool declOnly = false; // some printed row is decl_only=1 (its legend reading rides) + bool any() const noexcept { return !xml.empty(); } +}; + +// --path's inline alternatives for one endpoint (part 5): every definition the answer does NOT already show, up to the cap, +// compact rows, then alts_shown=K defs=N next=--defs=NAME when more exist. Empty when the pick is provably unique or +// nothing is left to tell (0 bytes added). Not alternatives: the bound definition, a definition the answer prints as a pair +// endpoint (`printed`), an untied C-family prototype (not a second candidate, as endpointDefinitionCount counts it). +// `bound`: the definition the answer bound (kNoNode when it bound none: every candidate group is then an alternative). +inline void defAltsXml( PathAlts& acc, const IngestResult& ing, const DefLookup& L, NodeId bound, std::string_view side, std::span printed ) { if( defPickProvablyUnique( L ) ) { - return {}; + return; } - std::uint32_t boundGroup = UINT32_MAX; + std::vector skip( L.groups.size(), 0 ); for( std::size_t i = 0; i < L.matches.size(); ++i ) { - if( L.matches[i] == bound ) + if( L.matches[i] == bound || std::find( printed.begin(), printed.end(), L.matches[i] ) != printed.end() ) { - boundGroup = L.groups.ofMatch[i]; + skip[ L.groups.ofMatch[i] ] = 1; } } const MemberOwners owners( ing ); @@ -259,7 +291,7 @@ inline std::string defAltsXml( const IngestResult& ing, const DefLookup& L, Node std::size_t shown = 0, total = 0; for( const DefRow& r : L.rows ) { - if( r.group == boundGroup ) + if( skip[ r.group ] || L.groups.untied[ r.group ] ) { continue; } @@ -267,9 +299,14 @@ inline std::string defAltsXml( const IngestResult& ing, const DefLookup& L, Node if( shown < kDefRowsCap ) { defRowXml( rows, ing, L, r, owners, /*compact=*/true ); + acc.declOnly = acc.declOnly || L.groups.bodyless[ r.group ]; ++shown; } } + if( total == 0 ) + { + return; + } std::vector esc; std::string out = "" + rows + ""; + acc.xml += out + ">" + rows + ""; } // Both --path transports (verbs_navigate.h runPath, mcpverbs.h pathText): the two endpoints' , from= first. A glob endpoint // stands for every fitting definition by design (from_glob=), so it lists none. -struct PathAlts -{ - std::string xml; - bool any() const noexcept { return !xml.empty(); } -}; inline PathAlts pathAltsOf( const IngestResult& ing, const Graph& g, std::string_view srcN, std::string_view dstN, bool srcGlob, bool dstGlob, - NodeId srcBound, NodeId dstBound ) + NodeId srcBound, NodeId dstBound, const PathPairs& pairs ) { - PathAlts out; + PathAlts out; + const std::vector printed = pathPrintedEnds( pairs ); if( !srcGlob ) { - out.xml += defAltsXml( ing, lookupDefs( ing, g, srcN ), srcBound, "from" ); + defAltsXml( out, ing, lookupDefs( ing, g, srcN ), srcBound, "from", printed ); } if( !dstGlob ) { - out.xml += defAltsXml( ing, lookupDefs( ing, g, dstN ), dstBound, "to" ); + defAltsXml( out, ing, lookupDefs( ing, g, dstN ), dstBound, "to", printed ); } return out; } // The legend clause for — present only when an answer carries one. -inline const char* defAltsLegend( bool on ) noexcept +inline std::string defAltsLegend( const PathAlts& alts ) { - return on ? " lists the OTHER definitions the endpoint's name binds (the pick is not provably unique): sel= pastes " - "as that endpoint to bind one alone, p= where it is, class=source|test; alts_shown=K defs=N: K of the other rows shown, " - "next= pages the rest. " - : ""; + if( !alts.any() ) + { + return {}; + } + return std::string( " lists the definitions the endpoint's name binds that this answer does not already print (the pick is " + "not provably unique): sel= pastes as that endpoint to bind one alone, p= where it is, class=source|test; " + "alts_shown=K defs=N: K of the other rows shown, next= pages the rest. " ) + + ( alts.declOnly ? "decl_only=1: no body (a header declaration or interface contract), not a second definition. " : "" ); } } // namespace rw diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 7a4a26d56..dc60e6e00 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -3828,9 +3828,9 @@ inline std::optional pathText( const std::string& root, const std:: const PathTestTwin ptTwin = srcGlob || dstGlob ? PathTestTwin{} : pathTestTwins( ing, g, { srcDefs, from, ptBound.from }, { dstDefs, to, ptBound.to }, ptShown ); // zed-03, as on the CLI const std::vector ptOwners = pathHopOwners( ing, pth ); // PATH-OWNERS, as on the CLI const bool ptHasOwner = ptMarks.owner; - const PathAlts ptAlts = pathAltsOf( ing, g, from, to, srcGlob, dstGlob, ptBound.from ? srcUsed : kNoNode, ptBound.to ? dstUsed : kNoNode ); // D2 part 5, as on the CLI + const PathAlts ptAlts = pathAltsOf( ing, g, from, to, srcGlob, dstGlob, ptBound.from ? srcUsed : kNoNode, ptBound.to ? dstUsed : kNoNode, ptPairs ); // D2 part 5, as on the CLI rw::emitTo( mem, "{}", ( std::string( defAltsLegend( ptAlts.any() ) ) + unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + ptKindLegend + "graph holds none. {}{}{}{}-->{}", ( defAltsLegend( ptAlts ) + unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + ptKindLegend + splitArmLegend( ptHasSplit ) + pathUnboundLegend( !ptBound.from || !ptBound.to ) + memberOwnerLegend( ptHasOwner ) + memberOwnerContainedLegend( ptMarks.ownerContained ) + std::string( pathPairsLegend( ptPairs ) ) + pathOrderLegend( ptPairs, ptTwin ) ).c_str(), ( std::string( toValueRefsLegend( ptToValueRefs.count > 0 ) ) + valueRefsDepthLegend( ptToValueRefs.depthCut.files > 0 ) ).c_str(), diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index f60c9c6fc..505d268a5 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2787,9 +2787,9 @@ std::optional runPath( const MainDispatch& d ) const std::vector pthOwners = rw::pathHopOwners( ing, path ); // PATH-OWNERS: n="Owner.name" per hop const bool pthHasOwner = pthMarks.owner; // D2 part 5 (defslookup.h): the other definitions of an endpoint whose pick is not provably unique, inline - const rw::PathAlts pthAlts = rw::pathAltsOf( ing, g, srcN, dstN, srcGlob, dstGlob, pthBound.from ? srcUsed : rw::kNoNode, pthBound.to ? dstUsed : rw::kNoNode ); + const rw::PathAlts pthAlts = rw::pathAltsOf( ing, g, srcN, dstN, srcGlob, dstGlob, pthBound.from ? srcUsed : rw::kNoNode, pthBound.to ? dstUsed : rw::kNoNode, pthPairs ); rw::emitTo( stdout, "{}", ( std::string( rw::defAltsLegend( pthAlts.any() ) ) + rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Path, pthUnprovenDefs > 0 ) + rw::viaNameLegend( pthHasVia ) + "graph holds none. {}{}{}{}-->{}", ( rw::defAltsLegend( pthAlts ) + rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Path, pthUnprovenDefs > 0 ) + rw::viaNameLegend( pthHasVia ) + rw::viaLinkLegend( pthHasLink ) + rw::anchorMissLegend( pthHasMiss ) + rw::splitArmLegend( pthHasSplit ) + rw::pathUnboundLegend( !pthBound.from || !pthBound.to ) + rw::memberOwnerLegend( pthHasOwner ) + rw::memberOwnerContainedLegend( pthMarks.ownerContained ) + std::string( rw::pathPairsLegend( pthPairs ) ) + rw::pathOrderLegend( pthPairs, pthTwin ) ).c_str(), diff --git a/test/defslookupcheck.sh b/test/defslookupcheck.sh index 3c9379349..c03adfe38 100755 --- a/test/defslookupcheck.sh +++ b/test/defslookupcheck.sh @@ -99,5 +99,24 @@ if grep -q 'reachable="1"' "$TMP/p2.xml" && ! grep -q '"$TMP/p3.xml" 2>/dev/null if grep -q '' "$TMP/p3.xml" && xmllint --noout "$TMP/p3.xml" 2>/dev/null; then ok "P3: 17 others past the cap: alts_shown=16 defs=18 next=--defs=many, well-formed"; else no "P3: $( grep -o ']*>' "$TMP/p3.xml" )"; fi +echo "-- (P4) an untied C prototype is no second candidate: --path prints no (endpointDefinitionCount's rule)" +run --path=orphan,helperA >"$TMP/p4.xml" 2>/dev/null +if grep -q 'from_defs="2"' "$TMP/p4.xml" && ! grep -q ' from_defs=2 yet no (0 bytes)"; else no "P4: $( grep -o '' "$TMP/p4.xml" | head -c 300 ) $( grep -o 'from_defs="[0-9]*"' "$TMP/p4.xml" )"; fi +run --defs=orphan >"$TMP/p4d.xml" 2>/dev/null +if grep -q '' "$TMP/p4d.xml" && grep -q 'decl_only="1"' "$TMP/p4d.xml"; then ok "P4 control: --defs=orphan still lists both groups (the verb answering 'which definitions' is complete), the header decl_only"; else no "P4 control: $( grep -o ' never re-lists a definition the answer prints as a pair endpoint; a tied contract still lists, decl_only rides" +run --path=ping,helperA --legend=full >"$TMP/p5.xml" 2>/dev/null +altsp="$( perl -0ne 'print "$1\n" while /]*>(.*?)<\/alts>/gs' "$TMP/p5.xml" )" +pairp="$( body "$TMP/p5.xml" | grep -o '"; else no "P5: pairs=[$pairp] dup=$dup alts=$altsp"; fi +if [ "$( printf '%s' "$altsp" | grep -o '/dev/null; then ok "P5: --legend=full reads decl_only=1 when a row carries it; well-formed"; else no "P5 legend / well-formedness"; fi +run --path=run,helperA --legend=full >"$TMP/p5b.xml" 2>/dev/null +if grep -q ' with no decl_only row has no decl_only clause (present-only legend)"; else no "P5 control: decl_only text on an answer with no such row"; fi +run --path=ping,helperA >"$TMP/p5c.xml" 2>/dev/null +if grep -q 'alts d decl_only=1: no body' "$TMP/p5c.xml"; then ok "P5: the default (compact) legend reads decl_only=1 on --path"; else no "P5: compact legend lacks the path decl_only reading: $( grep -o '' "$TMP/p5c.xml" | head -c 200 )"; fi + [ "$fail" -eq 0 ] && echo "defslookupcheck: OK" || echo "defslookupcheck: FAILURES above" exit "$fail" diff --git a/test/defslookupfix/proto/orphan.c b/test/defslookupfix/proto/orphan.c new file mode 100644 index 000000000..f4f205bed --- /dev/null +++ b/test/defslookupfix/proto/orphan.c @@ -0,0 +1 @@ +void orphan( int x ) { (void)x; } diff --git a/test/defslookupfix/proto/orphan.h b/test/defslookupfix/proto/orphan.h new file mode 100644 index 000000000..0cde946a8 --- /dev/null +++ b/test/defslookupfix/proto/orphan.h @@ -0,0 +1 @@ +void orphan( int x ); diff --git a/test/defslookupfix/src/ping1.ts b/test/defslookupfix/src/ping1.ts new file mode 100644 index 000000000..738f2f48c --- /dev/null +++ b/test/defslookupfix/src/ping1.ts @@ -0,0 +1,4 @@ +import { helperA } from "./a" +export class PingOne { + ping(): number { return helperA() } +} diff --git a/test/defslookupfix/src/ping2.ts b/test/defslookupfix/src/ping2.ts new file mode 100644 index 000000000..5f06e8534 --- /dev/null +++ b/test/defslookupfix/src/ping2.ts @@ -0,0 +1,4 @@ +import { helperA } from "./a" +export class PingTwo { + ping(): number { return helperA() + 1 } +} diff --git a/test/defslookupfix/src/ping3.ts b/test/defslookupfix/src/ping3.ts new file mode 100644 index 000000000..ff79dad34 --- /dev/null +++ b/test/defslookupfix/src/ping3.ts @@ -0,0 +1,4 @@ +export interface Pinger { ping(): number } +export class PingThree { + ping(): number { return 3 } +} From 925a33a627e36922faaa50cb21918a9663d340c5 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 05:58:11 -0400 Subject: [PATCH 59/95] defslookupcheck: a same-file namesake fixture so "sel= binds that one alone" can fail (F15) The review's picksAlone all_of -> any_of mutant stayed green (21 PASS) and printed the same "proven" sel for two different rows: no fixture held two same-named definitions in ONE file, so file:name always picked alone. test/defslookupfix/src/pair.ts holds P.go and Q.go; arm B2 asserts two DIFFERENT proven sels (never the bare file:name, which binds both) with a control that the bare spelling really is a 2-group table, and arm C re-resolves both sels through --callers. The mutant is shown red in the fix-round report. Co-Authored-By: Claude Sonnet 5.5 --- test/defslookupcheck.sh | 15 +++++++++++++-- test/defslookupfix/src/pair.ts | 6 ++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 test/defslookupfix/src/pair.ts diff --git a/test/defslookupcheck.sh b/test/defslookupcheck.sh index c03adfe38..3407b0f31 100755 --- a/test/defslookupcheck.sh +++ b/test/defslookupcheck.sh @@ -5,6 +5,10 @@ # flag (18 FAIL) and its --path answer carries no . Three NEGATIVES pass vacuously there (no row is a JSON key, every # sel= proven, P2 no ); each was proven able to fail by a mutant on f966224b: the data-Section filter dropped (6 FAIL), # picksAlone always false (4 FAIL), defPickProvablyUnique false at one group (P2 FAIL). +# FIX ROUND 1 (2026-10-28, review F15): the picksAlone all_of -> any_of mutant ("binds this group AND MAYBE OTHERS" counts as +# proven) survived all 21 arms because no fixture held two same-named definitions in ONE file, so file:name always picked alone. +# Arm B2 (src/pair.ts: P.go, Q.go) fails that mutant, as do the (C) re-resolution of its sels. Mutants of the fix round are recorded +# at the end of this header. # Arms: namesakes across files with a test copy and a JSON key (A), a C prototype folded with its definition (B), every # printed sel= re-resolved by another verb to one definition (C), file:name / Owner.name spellings (D), zero found (E), over # the 16-row cap with --offset paging (F), several names in one call (G), the JSON twin spelling every row as the XML does (J), @@ -42,14 +46,21 @@ echo "-- (B) a C prototype and its definition are one group" run --defs=work >"$TMP/b.xml" 2>/dev/null if grep -q '' "$TMP/b.xml" && grep -q 'p="src/work.c:2"' "$TMP/b.xml"; then ok "work: one group, the bodied definition"; else no "work not one group: $( grep -o '"$TMP/b2.xml" 2>/dev/null +selsb2="$( grep -o 'sel="[^"]*"' "$TMP/b2.xml" | sed 's/sel="//; s/"$//' )" +if grep -q '' "$TMP/b2.xml" && [ "$( printf '%s\n' "$selsb2" | sort -u | wc -l | tr -d ' ' )" = 2 ] && ! printf '%s\n' "$selsb2" | grep -qx 'src/pair.ts:go' && ! grep -q 'sel_proven=' "$TMP/b2.xml"; then ok "go: 2 rows, 2 DIFFERENT proven sels (Owner-qualified), never the bare file:name that binds both"; else no "go: $( grep -o ']*>' "$TMP/b2.xml" )"; fi +run --callers=src/pair.ts:go >"$TMP/b2c.xml" 2>/dev/null +if grep -q '/dev/null )" { printf '%s' "$out" | grep -q ']*>' | head -c 200 )"; } done -if [ "$n" -ge 4 ] && [ "$bad" -eq 0 ]; then ok "$n sel= spellings each bind one definition group"; else no "$bad of $n sel= spellings did not bind one definition group"; fi +if [ "$n" -ge 6 ] && [ "$bad" -eq 0 ]; then ok "$n sel= spellings each bind one definition group"; else no "$bad of $n sel= spellings did not bind one definition group"; fi echo "-- (D) spellings the shared resolver accepts" run --defs=src/b.ts:run >"$TMP/d1.xml" 2>/dev/null diff --git a/test/defslookupfix/src/pair.ts b/test/defslookupfix/src/pair.ts new file mode 100644 index 000000000..3e2891e96 --- /dev/null +++ b/test/defslookupfix/src/pair.ts @@ -0,0 +1,6 @@ +export class P { + go(): number { return 1 } +} +export class Q { + go(): number { return 2 } +} From ca7393a1d716edcafb7d36b3154c36d8365a69c2 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 06:02:33 -0400 Subject: [PATCH 60/95] defs: no longer claims sel= binds one alone on a sel_proven=0 row (F7) nunjucks-14's carried sel="nunjucks/src/compiler.js:compile" sel_proven="0" under a legend that said sel= "pastes as that endpoint to bind one alone"; --callers with that sel gives defs=3. The compact sel_proven reading was registered for verb defs only, so --path's answer had no reading for the attribute at all. - defAltsLegend adds ", except on a sel_proven=0 row: no spelling tried binds that one alone, its sel= names the file and owner and may bind more" only when a printed row is unproven (PathAlts.unproven; present-only). - compactlegend.h registers the sel_proven=0 reading for verb path (alts d), beside decl_only. - test arm P6 on a fixture whose two same-line nested functions no spelling picks alone (src/inner.js): the full legend carries the exception, the compact legend the reading, an all-proven answer neither. Red first on the lane head binary (built_from de9d903b2): both P6 reading arms FAIL; green after. Co-Authored-By: Claude Sonnet 5.5 --- src/compactlegend.h | 1 + src/defslookup.h | 7 +++++-- test/defslookupcheck.sh | 8 ++++++++ test/defslookupfix/src/inner.js | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 test/defslookupfix/src/inner.js diff --git a/src/compactlegend.h b/src/compactlegend.h index 5c0372544..663e40d28 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -839,6 +839,7 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "next", "name next=: the next page, or (defs=0) the single-name call whose refusal names near names", true, "name", MapHeaderRead::No, {}, "defs" }, { "side", "alts side=from|to of=: the definitions that endpoint's name binds that this answer does not already print (the pick is not provably unique): , sel= pastes as that endpoint", true, "alts", MapHeaderRead::No, {}, "path" }, { "decl_only", "alts d decl_only=1: no body (a header declaration or interface contract), not a second definition", true, "d", MapHeaderRead::No, {}, "path" }, + { "sel_proven", "alts d sel_proven=0: no spelling tried binds it alone; its sel= names the file and owner and may bind more than that definition", true, "d", MapHeaderRead::No, {}, "path" }, { "alts_shown", "alts alts_shown=K defs=N: K of the other rows shown; next= pages the rest", true, "alts", MapHeaderRead::No, {}, "path" }, // also defines defs= there { "from_p", "from_p=/to_p=: the definitions from= and to= were bound to", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_p= { "from_defs", "from_defs=/to_defs=: definitions of each name, all searched; above 1, qualify file:name", false, "path", MapHeaderRead::No, {}, "path" }, // also defines to_defs= diff --git a/src/defslookup.h b/src/defslookup.h index 0266c6b4c..20fccbc14 100644 --- a/src/defslookup.h +++ b/src/defslookup.h @@ -264,6 +264,7 @@ struct PathAlts { std::string xml; bool declOnly = false; // some printed row is decl_only=1 (its legend reading rides) + bool unproven = false; // some printed row is sel_proven="0": its sel= may bind more than that row (the legend says so) bool any() const noexcept { return !xml.empty(); } }; @@ -300,6 +301,7 @@ inline void defAltsXml( PathAlts& acc, const IngestResult& ing, const DefLookup& { defRowXml( rows, ing, L, r, owners, /*compact=*/true ); acc.declOnly = acc.declOnly || L.groups.bodyless[ r.group ]; + acc.unproven = acc.unproven || !r.selProven; ++shown; } } @@ -343,8 +345,9 @@ inline std::string defAltsLegend( const PathAlts& alts ) return {}; } return std::string( " lists the definitions the endpoint's name binds that this answer does not already print (the pick is " - "not provably unique): sel= pastes as that endpoint to bind one alone, p= where it is, class=source|test; " - "alts_shown=K defs=N: K of the other rows shown, next= pages the rest. " ) + "not provably unique): sel= pastes as that endpoint to bind one alone" ) + + ( alts.unproven ? ", except on a sel_proven=0 row: no spelling tried binds that one alone, its sel= names the file and owner and may bind more" : "" ) + + ", p= where it is, class=source|test; alts_shown=K defs=N: K of the other rows shown, next= pages the rest. " + ( alts.declOnly ? "decl_only=1: no body (a header declaration or interface contract), not a second definition. " : "" ); } diff --git a/test/defslookupcheck.sh b/test/defslookupcheck.sh index 3407b0f31..5e3812369 100755 --- a/test/defslookupcheck.sh +++ b/test/defslookupcheck.sh @@ -129,5 +129,13 @@ if grep -q '"$TMP/p5c.xml" 2>/dev/null if grep -q 'alts d decl_only=1: no body' "$TMP/p5c.xml"; then ok "P5: the default (compact) legend reads decl_only=1 on --path"; else no "P5: compact legend lacks the path decl_only reading: $( grep -o '' "$TMP/p5c.xml" | head -c 200 )"; fi +echo "-- (P6) an row whose sel= is NOT proven says so: the legend never claims it binds one alone (F7)" +run --path=inner,helperA --legend=full >"$TMP/p6.xml" 2>/dev/null +unp="$( grep -o '' "$TMP/p6.xml" )" +if printf '%s' "$unp" | grep -q 'sel_proven="0"' && grep -q 'except on a sel_proven=0 row' "$TMP/p6.xml" && xmllint --noout "$TMP/p6.xml" 2>/dev/null; then ok "P6: a sel_proven=0 alts row -> --legend=full carries the 'except on a sel_proven=0 row' reading; well-formed"; else no "P6: alts=$unp"; fi +run --path=inner,helperA >"$TMP/p6c.xml" 2>/dev/null +if grep -q 'alts d sel_proven=0: no spelling tried' "$TMP/p6c.xml"; then ok "P6: the default (compact) legend reads sel_proven=0 on --path"; else no "P6: compact legend lacks the path sel_proven reading"; fi +if ! grep -q 'sel_proven' "$TMP/p5.xml" && ! grep -q 'sel_proven' "$TMP/p5c.xml"; then ok "P6 control: an whose sels are all proven carries no sel_proven text (present-only, both legends)"; else no "P6 control: sel_proven text on an all-proven "; fi + [ "$fail" -eq 0 ] && echo "defslookupcheck: OK" || echo "defslookupcheck: FAILURES above" exit "$fail" diff --git a/test/defslookupfix/src/inner.js b/test/defslookupfix/src/inner.js new file mode 100644 index 000000000..06db3430b --- /dev/null +++ b/test/defslookupfix/src/inner.js @@ -0,0 +1 @@ +function one() { function inner() { return 1 } return inner() } function two() { function inner() { return 2 } return inner() } From e653ef952f9243377f451c29018b4462f4b7657e Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 06:07:29 -0400 Subject: [PATCH 61/95] defs: next= continues past page 1 (F8) kysely-14 and starlette-14 printed `alts_shown=16 defs=33 next=--defs=execute`, which replays page 1: the same 16 rows the already lists. next= is now `--defs=NAME --offset=M`, M the first sorted row not yet shown (the rows skipped before it are the bound one, printed pair endpoints and untied prototypes, which the answer already has or does not count). Arm P3 follows the continuation and checks that the 16 shown rows, the bound one and the offset page are 18 distinct definitions with 0 repeats. Red first on the lane head binary (built_from de9d903b2): P3 next= FAIL (16 repeats); green after. Co-Authored-By: Claude Sonnet 5.5 --- src/defslookup.h | 10 ++++++---- test/defslookupcheck.sh | 11 ++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/defslookup.h b/src/defslookup.h index 20fccbc14..b38f7dd7a 100644 --- a/src/defslookup.h +++ b/src/defslookup.h @@ -269,7 +269,7 @@ struct PathAlts }; // --path's inline alternatives for one endpoint (part 5): every definition the answer does NOT already show, up to the cap, -// compact rows, then alts_shown=K defs=N next=--defs=NAME when more exist. Empty when the pick is provably unique or +// compact rows, then alts_shown=K defs=N next=--defs=NAME --offset=M when more exist (M: the first row not yet shown). Empty when the pick is provably unique or // nothing is left to tell (0 bytes added). Not alternatives: the bound definition, a definition the answer prints as a pair // endpoint (`printed`), an untied C-family prototype (not a second candidate, as endpointDefinitionCount counts it). // `bound`: the definition the answer bound (kNoNode when it bound none: every candidate group is then an alternative). @@ -289,9 +289,10 @@ inline void defAltsXml( PathAlts& acc, const IngestResult& ing, const DefLookup& } const MemberOwners owners( ing ); std::string rows; - std::size_t shown = 0, total = 0; - for( const DefRow& r : L.rows ) + std::size_t shown = 0, total = 0, nextOffset = 0; + for( std::size_t i = 0; i < L.rows.size(); ++i ) { + const DefRow& r = L.rows[i]; if( skip[ r.group ] || L.groups.untied[ r.group ] ) { continue; @@ -303,6 +304,7 @@ inline void defAltsXml( PathAlts& acc, const IngestResult& ing, const DefLookup& acc.declOnly = acc.declOnly || L.groups.bodyless[ r.group ]; acc.unproven = acc.unproven || !r.selProven; ++shown; + nextOffset = i + 1; // --defs lists the same sorted rows: the ones not shown yet start right after the last one shown } } if( total == 0 ) @@ -314,7 +316,7 @@ inline void defAltsXml( PathAlts& acc, const IngestResult& ing, const DefLookup& if( shown < total ) { out += " alts_shown=\"" + std::to_string( shown ) + "\" defs=\"" + std::to_string( L.defs() ) + "\"" - + nextAttrXml( nextFlag( "--defs=", L.spec ) ); + + nextAttrXml( nextFlag( "--defs=", L.spec ) + " --offset=" + std::to_string( nextOffset ) ); // past page 1, never a replay of it } acc.xml += out + ">" + rows + ""; } diff --git a/test/defslookupcheck.sh b/test/defslookupcheck.sh index 5e3812369..3befad55b 100755 --- a/test/defslookupcheck.sh +++ b/test/defslookupcheck.sh @@ -108,7 +108,16 @@ if printf '%s' "$alts" | grep -q '^' && [ "$( printf run --path=A.run,helperA >"$TMP/p2.xml" 2>/dev/null if grep -q 'reachable="1"' "$TMP/p2.xml" && ! grep -q ' (0 bytes)"; else no "P2: $( head -c 400 "$TMP/p2.xml" )"; fi run --path=many,helperA --legend=full >"$TMP/p3.xml" 2>/dev/null -if grep -q '' "$TMP/p3.xml" && xmllint --noout "$TMP/p3.xml" 2>/dev/null; then ok "P3: 17 others past the cap: alts_shown=16 defs=18 next=--defs=many, well-formed"; else no "P3: $( grep -o ']*>' "$TMP/p3.xml" )"; fi +if grep -q '' "$TMP/p3.xml" && xmllint --noout "$TMP/p3.xml" 2>/dev/null; then ok "P3: 17 others past the cap: alts_shown=16 defs=18 next=--defs=many --offset=16|17, well-formed"; else no "P3: $( grep -o ']*>' "$TMP/p3.xml" )"; fi +# F8: the continuation starts AFTER the rows already shown (it used to replay page 1): the 16 shown + the bound + the offset page = 18 distinct, no repeat +off="$( grep -o ']*next="--defs=many --offset=[0-9]*"' "$TMP/p3.xml" | sed 's/.*--offset=//; s/"$//' )" +run --defs=many --offset="${off:-0}" >"$TMP/p3n.xml" 2>/dev/null +shownp="$( grep -o '' "$TMP/p3.xml" | grep -o 'p="[^"]*"' | sort )" +boundp="$( grep -o ']*from_p="[^"]*"' "$TMP/p3.xml" | sed 's/.*from_p=/p=/' )" +nextp="$( body "$TMP/p3n.xml" | grep -o ']*' | grep -o 'p="[^"]*"' | sort )" +rep="$( printf '%s\n%s\n' "$shownp" "$nextp" | sort | uniq -d | grep -c . )" +cover="$( printf '%s\n%s\n%s\n' "$shownp" "$nextp" "$boundp" | sort -u | grep -c . )" +if [ -n "$off" ] && [ "$rep" = 0 ] && [ "$cover" = 18 ]; then ok "P3 next=: --defs=many --offset=$off adds only rows the did not show (shown 16 + bound + the offset page = 18 distinct, 0 repeats)"; else no "P3 next=: off=$off repeats=$rep distinct=$cover"; fi echo "-- (P4) an untied C prototype is no second candidate: --path prints no (endpointDefinitionCount's rule)" run --path=orphan,helperA >"$TMP/p4.xml" 2>/dev/null From 982306d4fa4da48b642a85948eea0aca2e36155f Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 06:34:30 -0400 Subject: [PATCH 62/95] defslookupcheck: arms for the guards the fix round added; shorter legend wording - P4b: a name with two real C definitions and an untied header prototype lists the two definitions, never the prototype (the row-level untied skip; P4 alone ends at the early "unique" return). - P5 solo: two definitions that both print as pair endpoints leave nothing to tell: no empty . - the legend says "further definitions ..., not printed above" (full and compact), 34 B shorter than the first wording of the fix round on every row. Co-Authored-By: Claude Sonnet 5.5 --- src/compactlegend.h | 2 +- src/defslookup.h | 2 +- test/defslookupcheck.sh | 6 ++++++ test/defslookupfix/proto/twin.h | 1 + test/defslookupfix/proto/twin_a.c | 1 + test/defslookupfix/proto/twin_b.c | 1 + test/defslookupfix/src/solo1.ts | 4 ++++ test/defslookupfix/src/solo2.ts | 4 ++++ 8 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 test/defslookupfix/proto/twin.h create mode 100644 test/defslookupfix/proto/twin_a.c create mode 100644 test/defslookupfix/proto/twin_b.c create mode 100644 test/defslookupfix/src/solo1.ts create mode 100644 test/defslookupfix/src/solo2.ts diff --git a/src/compactlegend.h b/src/compactlegend.h index 663e40d28..0c7ba1b65 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -837,7 +837,7 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "unproven_defs", "name unproven_defs=K: same-named definitions a file:name spelling could not tie to that file; not in defs=", true, "name", MapHeaderRead::No, {}, "defs" }, { "shown", "name shown=K offset=M: rows M..M+K of defs=; next= pages the rest", true, "name", MapHeaderRead::No, {}, "defs" }, // also defines offset= { "next", "name next=: the next page, or (defs=0) the single-name call whose refusal names near names", true, "name", MapHeaderRead::No, {}, "defs" }, - { "side", "alts side=from|to of=: the definitions that endpoint's name binds that this answer does not already print (the pick is not provably unique): , sel= pastes as that endpoint", true, "alts", MapHeaderRead::No, {}, "path" }, + { "side", "alts side=from|to of=: further definitions that endpoint's name binds, not printed above (the pick is not provably unique): , sel= pastes as that endpoint", true, "alts", MapHeaderRead::No, {}, "path" }, { "decl_only", "alts d decl_only=1: no body (a header declaration or interface contract), not a second definition", true, "d", MapHeaderRead::No, {}, "path" }, { "sel_proven", "alts d sel_proven=0: no spelling tried binds it alone; its sel= names the file and owner and may bind more than that definition", true, "d", MapHeaderRead::No, {}, "path" }, { "alts_shown", "alts alts_shown=K defs=N: K of the other rows shown; next= pages the rest", true, "alts", MapHeaderRead::No, {}, "path" }, // also defines defs= there diff --git a/src/defslookup.h b/src/defslookup.h index b38f7dd7a..e13fc05e9 100644 --- a/src/defslookup.h +++ b/src/defslookup.h @@ -346,7 +346,7 @@ inline std::string defAltsLegend( const PathAlts& alts ) { return {}; } - return std::string( " lists the definitions the endpoint's name binds that this answer does not already print (the pick is " + return std::string( " lists further definitions the endpoint's name binds, not printed above (the pick is " "not provably unique): sel= pastes as that endpoint to bind one alone" ) + ( alts.unproven ? ", except on a sel_proven=0 row: no spelling tried binds that one alone, its sel= names the file and owner and may bind more" : "" ) + ", p= where it is, class=source|test; alts_shown=K defs=N: K of the other rows shown, next= pages the rest. " diff --git a/test/defslookupcheck.sh b/test/defslookupcheck.sh index 3befad55b..7e15b98c4 100755 --- a/test/defslookupcheck.sh +++ b/test/defslookupcheck.sh @@ -125,6 +125,10 @@ if grep -q 'from_defs="2"' "$TMP/p4.xml" && ! grep -q '"$TMP/p4d.xml" 2>/dev/null if grep -q '' "$TMP/p4d.xml" && grep -q 'decl_only="1"' "$TMP/p4d.xml"; then ok "P4 control: --defs=orphan still lists both groups (the verb answering 'which definitions' is complete), the header decl_only"; else no "P4 control: $( grep -o '"$TMP/p4b.xml" 2>/dev/null +altsb="$( grep -o '' "$TMP/p4b.xml" )" +if grep -q 'from_defs="3"' "$TMP/p4b.xml" && [ "$( printf '%s' "$altsb" | grep -o ' lists the 2 definitions, never the prototype"; else no "P4b: $altsb"; fi + echo "-- (P5) never re-lists a definition the answer prints as a pair endpoint; a tied contract still lists, decl_only rides" run --path=ping,helperA --legend=full >"$TMP/p5.xml" 2>/dev/null altsp="$( perl -0ne 'print "$1\n" while /]*>(.*?)<\/alts>/gs' "$TMP/p5.xml" )" @@ -133,6 +137,8 @@ dup=0; for pp in $pairp; do printf '%s' "$altsp" | grep -q "p=\"$pp\"" && dup=$( if grep -q 'pairs_reaching="2"' "$TMP/p5.xml" && [ "$( printf '%s\n' "$pairp" | wc -l | tr -d ' ' )" = 2 ] && [ "$dup" -eq 0 ]; then ok "P5: 2 pairs print (ping1, ping2); neither is re-listed under "; else no "P5: pairs=[$pairp] dup=$dup alts=$altsp"; fi if [ "$( printf '%s' "$altsp" | grep -o '/dev/null; then ok "P5: --legend=full reads decl_only=1 when a row carries it; well-formed"; else no "P5 legend / well-formedness"; fi +run --path=solo,helperA >"$TMP/p5d.xml" 2>/dev/null +if grep -q 'pairs_reaching="2"' "$TMP/p5d.xml" && ! grep -q ' nothing left to tell, no empty (0 bytes)"; else no "P5 solo: $( grep -o '"$TMP/p5b.xml" 2>/dev/null if grep -q ' with no decl_only row has no decl_only clause (present-only legend)"; else no "P5 control: decl_only text on an answer with no such row"; fi run --path=ping,helperA >"$TMP/p5c.xml" 2>/dev/null diff --git a/test/defslookupfix/proto/twin.h b/test/defslookupfix/proto/twin.h new file mode 100644 index 000000000..262f9edbb --- /dev/null +++ b/test/defslookupfix/proto/twin.h @@ -0,0 +1 @@ +void twin( int x ); diff --git a/test/defslookupfix/proto/twin_a.c b/test/defslookupfix/proto/twin_a.c new file mode 100644 index 000000000..9ff4227c1 --- /dev/null +++ b/test/defslookupfix/proto/twin_a.c @@ -0,0 +1 @@ +void twin( int x ) { (void)x; } diff --git a/test/defslookupfix/proto/twin_b.c b/test/defslookupfix/proto/twin_b.c new file mode 100644 index 000000000..fc97a3ee9 --- /dev/null +++ b/test/defslookupfix/proto/twin_b.c @@ -0,0 +1 @@ +void twin( int x ) { (void)x; (void)x; } diff --git a/test/defslookupfix/src/solo1.ts b/test/defslookupfix/src/solo1.ts new file mode 100644 index 000000000..c2f0d8e3a --- /dev/null +++ b/test/defslookupfix/src/solo1.ts @@ -0,0 +1,4 @@ +import { helperA } from "./a" +export class SoloOne { + solo(): number { return helperA() } +} diff --git a/test/defslookupfix/src/solo2.ts b/test/defslookupfix/src/solo2.ts new file mode 100644 index 000000000..bd4174223 --- /dev/null +++ b/test/defslookupfix/src/solo2.ts @@ -0,0 +1,4 @@ +import { helperA } from "./a" +export class SoloTwo { + solo(): number { return helperA() + 2 } +} From 77bb261ba29f1a6117ca1fbb718f4bc5beb50c06 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 07:42:57 -0400 Subject: [PATCH 63/95] defs: lintbudgetcheck (two C casts on one line in the fixture) and printf parity re-pin lintbudgetcheck c-style-cast 636 != --match ground truth 637: the fix round's fixture proto/twin_b.c held two (void) casts on one line (the 2026-10-24 trap); the fixture bodies now assign instead of cast. RE-PIN LOG printf_parity.manifest: moved exactly help_all (the --help HONORED-by sentence names --defs, F3); the other 40 labels unchanged. Co-Authored-By: Claude Sonnet 5.5 --- test/defslookupfix/proto/orphan.c | 2 +- test/defslookupfix/proto/twin_a.c | 2 +- test/defslookupfix/proto/twin_b.c | 2 +- test/printf_parity.manifest | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/defslookupfix/proto/orphan.c b/test/defslookupfix/proto/orphan.c index f4f205bed..82397506d 100644 --- a/test/defslookupfix/proto/orphan.c +++ b/test/defslookupfix/proto/orphan.c @@ -1 +1 @@ -void orphan( int x ) { (void)x; } +void orphan( int x ) { x = 1; } diff --git a/test/defslookupfix/proto/twin_a.c b/test/defslookupfix/proto/twin_a.c index 9ff4227c1..5d2ee93f3 100644 --- a/test/defslookupfix/proto/twin_a.c +++ b/test/defslookupfix/proto/twin_a.c @@ -1 +1 @@ -void twin( int x ) { (void)x; } +void twin( int x ) { x = 1; } diff --git a/test/defslookupfix/proto/twin_b.c b/test/defslookupfix/proto/twin_b.c index fc97a3ee9..666f2b907 100644 --- a/test/defslookupfix/proto/twin_b.c +++ b/test/defslookupfix/proto/twin_b.c @@ -1 +1 @@ -void twin( int x ) { (void)x; (void)x; } +void twin( int x ) { x = 2; } diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index 03463f499..be927960a 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -38,5 +38,5 @@ safe_delete 0 16990750bd7c003d73fa9c4ebb85ca8a9a3e6ffe8bf8f9987ed47f5459232403 e verify_layer 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5288c345d7d6e335f88b9c1daa8935db22e1dcf89c0c8bc1f6140d4cb5af0b48 graph_query 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 bfa4319feb9dee09cfbd3991cf6fbd752297e99d14de9e75768820d2a9c8832f callers_limit 0 98a50bcc6b32183d7967ee6af8f9219f083c00bde882f3aff4ebe1f39f4166e4 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help_all 0 ede426d1e2c6afc384c7cfd7115043587e485ee91ee363c120bd7fac7af82f47 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help_all 0 8bb536a5956018df14882d87cc7bc61879711b880a03ad05569322668b272697 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help_one 0 938ff4f8802a8f5d9cb10f9a1f3804ac755be44c8ab00e24e45bfe8255224a21 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 From 5351fbbb54e2b060912de5c6d5bd6bf6ec0f907f Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 07:52:10 -0400 Subject: [PATCH 64/95] forheadrrcheck H3: a follow_of body is not a ranked fold row (WRAPPER-FOLLOW, by design) The ranked rows are still exactly the score top three; a follow body must name a wrapper the fold serves. Co-Authored-By: Claude Opus 5.5 --- test/forheadrrcheck.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/forheadrrcheck.sh b/test/forheadrrcheck.sh index eead9604b..3f0d11f0a 100755 --- a/test/forheadrrcheck.sh +++ b/test/forheadrrcheck.sh @@ -106,7 +106,11 @@ if cmd == "rr": # ON OFF [names] — ON is the round-robin of OFF; fi elif cmd == "fold": # ON OFF — the fold's rows are the score top three (OFF's first three rows), and ON's fold is OFF's t = load(args[0]); m = re.search(r']*>(.*?)', t, re.S) if not m: fail("no ") - got = [attrs(x.group(1)).get("n") for x in re.finditer(r'<(?:b|skip|slice) ([^>]*)>', m.group(1))] + # WRAPPER-FOLLOW (src/hopbody.h): a is a thin wrapper's callee riding after it, not a ranked row — the ranked rows + # are still exactly the score top three; a follow body must name a wrapper the fold serves + pieces = [attrs(x.group(1)) for x in re.finditer(r'<(?:b|skip|slice) ([^>]*)>', m.group(1))] + got = [a.get("n") for a in pieces if "follow_of" not in a] + if any(a["follow_of"] not in got for a in pieces if "follow_of" in a): fail("a follow_of body names no served wrapper") want = [n for p, n in rows(load(args[1]))[:3]] mo = re.search(r']*>.*?', load(args[1]), re.S) if not mo or mo.group(0) != m.group(0): fail("the fold differs from the RIPWIRE_NO_HEAD_RR=1 answer's") From e3040e8b3ec2af3f9678c2236b2a1271e57db605 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 08:35:59 -0400 Subject: [PATCH 65/95] regen: showcase capture 2026-10-28 and COMMANDS.md on the fix-round binary The two new path readings (decl_only, sel_proven) moved the legend dictionary (entries 972 -> 974), which showcasecapturecheck (I) reads as a stale capture; the sample now shows next= with --offset. Recorded on a clean tree with build/ripwire built_from 982306d4f (tests-only commit after). Co-Authored-By: Claude Sonnet 5.5 --- docs/COMMANDS.md | 437 +++-- docs/captures/COMMANDS_showcase_2026-10-28.md | 1471 ++++++++--------- 2 files changed, 947 insertions(+), 961 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 2933ba959..2dfdb502d 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -71,7 +71,7 @@ $ ./build/ripwire . --top-k=5 - + @@ -107,7 +107,7 @@ $ ./build/ripwire . --top-k=5 --no-inventory - + @@ -141,7 +141,7 @@ $ ./build/ripwire . --max-tokens=1500 - + @@ -216,7 +216,7 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - + @@ -253,7 +253,7 @@ _Restore the / sections the --for bundle collapses to a counted s ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --sections=lego,compose - + @@ -280,7 +280,7 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - + @@ -315,7 +315,7 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - + @@ -347,7 +347,7 @@ _Same query with routing forced OFF (plain subtoken+body BM25) — contrast with ``` $ ./build/ripwire . --for="rankGraphTeleport" --no-route - + @@ -379,7 +379,7 @@ _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 - + @@ -410,7 +410,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 - @@ -440,13 +440,13 @@ _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 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] @@ -470,7 +470,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -501,7 +501,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=2040 est_tokens=952 +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=957 -━━ commands.md (relevance 10.971) ━━ [sections: 1 of 166 selected (193 in doc), section-granular; whole doc 602072 B; lines="555-580"; dropped_by_budget=165] [truncated: 1854 of 4257 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base +━━ commands.md (relevance 10.137) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 595804 B; lines="3866-3870"; dropped_by_budget=164] [truncated: 1865 of 7741 bytes] +### `--field-affinity[=STRUCT]` -**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? +**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. -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: +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] -... [17 more line(s); run it to see the whole thing] +(capped: 1 of 2 relevant document files omitted — raise --max-tokens/budget_tokens or narrow the query for 1 more (~2548-byte budget)) ``` ### `--tree` @@ -629,7 +624,7 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - + @@ -734,7 +729,7 @@ $ ./build/ripwire . --no-stable --top-k=3 - + @@ -768,7 +763,7 @@ $ ./build/ripwire . --around=rankGraphTeleport - + @@ -819,7 +814,7 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - + @@ -854,7 +849,7 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - + @@ -888,7 +883,7 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - + @@ -926,15 +921,15 @@ _Names referenced but never defined in-corpus (stdlib/third-party surface). The $ ./build/ripwire . --external-surface - + - + - + - + - + @@ -959,14 +954,14 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - - + + - + @@ -1005,6 +1000,8 @@ $ ./build/ripwire . --defs=main,rankGraphTeleport ... [10 more line(s); run it to see the whole thing] ``` +**Shaped by:** `--limit` + ### `--connect=A,B,C` **Answers:** show how 2..16 symbols relate: the smallest subgraph that joins them minimal connecting subgraph over 2..16 symbols: terminals + fewest joining intermediaries + call edges in TRUE direction (finds the shared-caller join a directed --path can't) [--connect-radius=N (1..12, default 6)] @@ -1016,7 +1013,7 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + @@ -1126,7 +1123,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - @@ -1185,7 +1182,7 @@ _Mid-task situational report for the current git diff — recorded against a CLE $ ./build/ripwire . --situ ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: 70c265cd6 +at: 77bb261ba (0 changed files — working tree is clean, nothing to analyze) ``` @@ -1204,18 +1201,18 @@ _The continuation packet for the NEXT session: disk truth (branch/sha ``` $ ./build/ripwire . --handoff - + - + - - - + + + + ... [1 more line(s); run it to see the whole thing] ``` @@ -1535,14 +1532,14 @@ $ ./build/ripwire . --query="teleport pagerank" --top-k=5 - + - + - + ... [17 more line(s); run it to see the whole thing] @@ -1566,7 +1563,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1602,7 +1599,7 @@ $ ./build/ripwire . --pack-signatures --top-k=10 signature rows. est_tokens=: price as emitted (an upper bound under compact). root=: p= relative to it. pr_iters=N: PageRank iterations. declined=K: K call sites without edges, including inert Elixir quote sites after unquote/unquote_splicing re-entry. external=K: K calls proven … [line truncated: 1465 more bytes on this line] - + @@ -1800,7 +1797,7 @@ $ ./build/ripwire . --metrics --top-k=10 - + @@ -1835,9 +1832,9 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - + + + @@ -1872,16 +1869,16 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - + - + - + - + ... [17 more line(s); run it to see the whole thing] @@ -1900,7 +1897,7 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + @@ -1931,7 +1928,7 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - + @@ -1964,7 +1961,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1999,7 +1996,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -2030,7 +2027,7 @@ _THE single wide-angle quality read: six families in one pass, an eligible/ranke ``` $ ./build/ripwire . --quality-panel - @@ -2067,9 +2064,9 @@ _The local-reasoning lens: to understand this symbol, how much must you know tha ``` $ ./build/ripwire . --context-ratio --limit=8 - - + + @@ -2098,7 +2095,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2133,8 +2130,8 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - + + @@ -2170,7 +2167,7 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - + @@ -2179,7 +2176,7 @@ $ ./build/ripwire . --lint --naming-locals - + @@ -2205,7 +2202,7 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - + @@ -2238,16 +2235,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - + + - - - - + + + ... [22 more line(s); run it to see the whole thing] ``` @@ -2270,16 +2267,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] ``` @@ -2296,7 +2293,7 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + @@ -2325,16 +2322,16 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - + - + - + - + @@ -2363,7 +2360,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2393,7 +2390,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2419,7 +2416,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2446,7 +2443,7 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - + @@ -2455,7 +2452,7 @@ $ ./build/ripwire . --lint - + @@ -2555,7 +2552,7 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - @@ -2564,7 +2561,7 @@ $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + @@ -2669,15 +2666,15 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + + - - + @@ -2700,7 +2697,7 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - ``` @@ -2725,9 +2722,9 @@ _Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels ``` $ ./build/ripwire . --zoom - - + + @@ -2762,16 +2759,16 @@ $ ./build/ripwire . --report # ripwire architecture report -3931 files · 33184 symbols · 31243 edges · 3837 modules (15600 call-graph isolated) +3943 files · 33211 symbols · 31252 edges · 3827 modules (15617 call-graph isolated) Root: `.` -Call-graph isolate provenance: 4128 declaration, 2511 header, 3713 source, 5248 document; 0 connected Louvain singletons +Call-graph isolate provenance: 4131 declaration, 2511 header, 3727 source, 5248 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3837) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1495 symbols -- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 555 symbols -- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 254 symbols +## Modules (call-graph clusters; showing 12 of 3827) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1579 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 556 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 256 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2788,7 +2785,7 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - @@ -2819,17 +2816,17 @@ $ ./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
9807"] + n102["src
9809"] n103["src/infra
1301"] end subgraph sg1 ["test"] n104["test
5595"] - n606["test/legochargefix
546"] - n419["test/fixtures/recallpassage
314"] - n1033["test/slotjoinfix
175"] - n843["test/ownerhopfix/py/pkg
158"] - n356["test/expandmodefix
151"] - n358["test/expandsibsfix
149"] + n607["test/legochargefix
546"] + n420["test/fixtures/recallpassage
314"] + n1034["test/slotjoinfix
175"] + n844["test/ownerhopfix/py/pkg
158"] + n357["test/expandmodefix
151"] + n359["test/expandsibsfix
149"] ... [17 more line(s); run it to see the whole thing] ``` @@ -2848,8 +2845,8 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - + + @@ -2879,7 +2876,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2944,7 +2941,7 @@ _The same clean verdict with what it folded printed back: every stale acknowledg ``` $ ./build/ripwire . --quality-delta --show-stale - @@ -2973,7 +2970,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -2998,7 +2995,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -3023,7 +3020,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 -

@@ -3116,7 +3113,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3146,7 +3143,7 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - @@ -3200,12 +3197,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":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3221,12 +3218,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":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3272,12 +3269,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":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3336,11 +3333,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":2988,"graph_unresolved":15914,"graph_unindexed":275,"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] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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: 532 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":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true}}]} +"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3386,7 +3383,7 @@ _Bare --slice=SYM: the INVENTORY of sliceable locals (), so a calle ``` $ ./build/ripwire . --slice=rankGraphTeleport - + @@ -3423,7 +3420,7 @@ _Forward flow: which statements the seed's value reaches, at the default depth b ``` $ ./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd - + @@ -3504,7 +3501,7 @@ _No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - ``` @@ -3530,11 +3527,11 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + @@ -3609,7 +3606,7 @@ _A second selection, the `fix/` ref family, picked at capture time from the refs ``` $ ./build/ripwire . --stray-content=fix/ - + ``` @@ -3634,7 +3631,7 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + @@ -3670,7 +3667,7 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi - @@ -3705,11 +3702,11 @@ _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] ``` @@ -3735,8 +3732,8 @@ _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 - - + + @@ -3763,8 +3760,8 @@ _The whole hit list asked for by name: under the same 60-row cap HEAD's referenc ``` $ ./build/ripwire . --whereis=rankGraphTeleport --whereis-listing=all - - + + @@ -3795,7 +3792,7 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat $ ./build/ripwire . --flags - + @@ -3876,7 +3873,7 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - @@ -3911,7 +3908,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3945,7 +3942,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3978,8 +3975,8 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + @@ -4012,7 +4009,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -4077,7 +4074,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4237,7 +4234,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - + @@ -4310,13 +4307,13 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - + - - + + - + @@ -4344,13 +4341,13 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - - + - - + + - + @@ -4380,7 +4377,7 @@ $ ./build/ripwire . --skipped - @@ -4468,8 +4465,8 @@ $ ./build/ripwire . --rank-by=churn --top-k=5 - - + + @@ -4500,21 +4497,21 @@ a trailing slash is ignored; absolute paths and '..' are refused). 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._ ``` -$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~9 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3 +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3 - - + + - - - - - - + + + + + + - - + + ... [5 more line(s); run it to see the whole thing] ``` @@ -4577,7 +4574,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4605,7 +4602,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=80dbc95318c95d64 entries=972 +ripwire legend dictionary ripwire.dict/v1 dictv=1bb96ec3e6969a9c entries=974 : 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=) @@ -4658,7 +4655,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -4695,7 +4692,7 @@ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - + @@ -4726,8 +4723,8 @@ $ ./build/ripwire . --map-diff --top-k=5 - - + + @@ -4760,7 +4757,7 @@ $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - + @@ -4810,7 +4807,7 @@ $ ./build/ripwire . --no-cache --top-k=3 - + @@ -4841,7 +4838,7 @@ $ ./build/ripwire . --no-ignore --top-k=3 - + @@ -4877,7 +4874,7 @@ $ ./build/ripwire . --max-file-size=8K --top-k=3 - + @@ -4957,7 +4954,7 @@ $ ./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3 - + @@ -5011,9 +5008,9 @@ _--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":" - + @@ -63,13 +63,13 @@ *Same map, capped to the 5 highest-ranked symbols.* -**wall time: 5.53s** +**wall time: 3.87s** ````` - + @@ -88,7 +88,7 @@ - + @@ -103,13 +103,13 @@ *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.* -**wall time: 8.60s** +**wall time: 3.03s** ````` - + @@ -135,7 +135,7 @@ *NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map rides along with the body you asked for.* -**wall time: 8.51s** +**wall time: 3.10s** ````` @@ -178,7 +178,7 @@ *--top-k=0 with NO payload verb asked for — REFUSES (exit 1) naming the payload verbs, never an empty map.* -**exit code: 1** — **wall time: 1.36s** +**exit code: 1** ````` (empty) @@ -194,13 +194,13 @@ ripwire: --top-k=0 means "no ranked map, payload only" — pass a payload verb ( *SHAPE the map to fit ~1500 tokens (binary-search top-K).* -**wall time: 5.70s** +**wall time: 2.59s** ````` - + @@ -213,7 +213,7 @@ ripwire: --top-k=0 means "no ranked map, payload only" — pass a payload verb ( *GATE form: exit 3 if the map's own est_tokens exceeds the budget (over-budget failure shape).* -**exit code: 3** — **wall time: 5.33s** +**exit code: 3** — **wall time: 3.66s** ````` @@ -230,10 +230,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *The task lens: ranked signatures + quality metrics framed for the task.* -**wall time: 18.75s** +**wall time: 10.57s** ````` - @@ -270,10 +270,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *Name-shaped query: the router picks name-exact BM25 (header says which/why).* -**wall time: 31.62s** +**wall time: 7.27s** ````` - + @@ -310,10 +310,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* -**wall time: 15.45s** +**wall time: 7.59s** ````` - + @@ -329,7 +329,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 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 ) +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… 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] @@ -350,10 +350,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribute) — contrast with the terminal default above.* -**wall time: 13.63s** +**wall time: 7.71s** ````` - + @@ -374,10 +374,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *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: 16.10s** +**wall time: 9.06s** ````` - + @@ -414,10 +414,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *Mention anchoring (default-on): a path and a Symbol literally named in the task get lifted; the header says what anchored.* -**wall time: 15.92s** +**wall time: 9.58s** ````` - @@ -454,10 +454,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *Same task with the anchor disabled — the contrast the flag exists for.* -**wall time: 12.75s** +**wall time: 7.87s** ````` - @@ -494,12 +494,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *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.* -**wall time: 2.26s** +**wall time: 14.59s** ````` - + @@ -512,12 +512,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* -**wall time: 7.54s** +**wall time: 8.99s** ````` - + budget=100 *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: 7.75s** +**wall time: 5.91s** ````` @@ -553,7 +553,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *The honest half of the contract: a task with no ripwire-shaped evidence ABSTAINS with zero commands rather than guessing.* -**wall time: 5.09s** +**wall time: 6.83s** ````` @@ -566,7 +566,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* -**wall time: 4.67s** +**wall time: 2.61s** ````` ripwire recall — "quality delta gating exit codes" — 124 relevant of 305 document files, best-first — total=124 shown=8 capped=1 truncated=6 generated_demoted=3 max_tokens=8000 share_bytes=2468 est_tokens=4165 @@ -595,7 +595,7 @@ refuses by default. -━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.515) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.514) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] ### Root spelling (maintainer-owned; reproduce it once so you recognise it) ```bash @@ -604,40 +604,17 @@ refuses by default. ## `./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 1307034-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.* - -**wall time: 1.13s** +*The directory-as-knowledge-base pattern: --recall pointed at a 1304993-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.971) ━━ [sections: 1 of 166 selected (193 in doc), section-granular; whole doc 602072 B; lines="555-580"; dropped_by_budget=165] [truncated: 1854 of 4257 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base +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=957 -**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? +━━ commands.md (relevance 10.137) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 595804 B; lines="3866-3870"; dropped_by_budget=164] [truncated: 1865 of 7741 bytes] +### `--field-affinity[=STRUCT]` -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: +**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. -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. … +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] (capped: 1 of 2 relevant document files omitted — raise --max-tokens/budget_tokens or narrow the query for 1 more (~2548-byte budget)) ````` @@ -652,11 +629,11 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *File-by-file orientation map (top symbols per file).* -**wall time: 2.94s** +**wall time: 2.10s** ````` - + @@ -692,7 +669,7 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *Self-contained HTML force-directed call graph.* -**wall time: 16.41s** +**wall time: 9.66s** ````` (empty) @@ -701,14 +678,14 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas Artifact written: ````` - 156107 /aux/map2.html + 156254 /aux/map2.html ````` ## `./build/ripwire . --order=stable --top-k=5` *Stable (path/id) emit order — provider KV-cache hits across re-runs.* -**wall time: 3.90s** +**wall time: 3.08s** ````` @@ -732,7 +709,7 @@ Artifact written: - + @@ -753,14 +730,14 @@ Artifact written: *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.* -**wall time: 1.80s** +**wall time: 1.90s** ````` - + @@ -793,14 +770,14 @@ Artifact written: *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.* -**wall time: 2.19s** +**wall time: 2.18s** ````` - + @@ -833,14 +810,14 @@ Artifact written: *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.* -**wall time: 2.77s** +**wall time: 1.98s** ````` - + @@ -873,11 +850,11 @@ Artifact written: *Who calls SYM (1-hop in-edges).* -**wall time: 2.03s** +**wall time: 1.95s** ````` - + @@ -892,12 +869,12 @@ Artifact written: *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.* -**wall time: 3.49s** +**wall time: 3.40s** ````` - + @@ -912,7 +889,7 @@ Artifact written: *Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* -**exit code: 1** — **wall time: 7.03s** +**exit code: 1** — **wall time: 8.29s** ````` @@ -929,11 +906,11 @@ ripwire: --callers symbol not found: DoesNotExist *What SYM calls (1-hop out-edges).* -**wall time: 2.00s** +**wall time: 2.48s** ````` - + @@ -952,11 +929,11 @@ ripwire: --callers symbol not found: DoesNotExist *The resolvable use-sites (call/read/write/import/extends) with file:line; count= is a floor.* -**wall time: 2.85s** +**wall time: 3.62s** ````` - + @@ -973,11 +950,11 @@ ripwire: --callers symbol not found: DoesNotExist *Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* -**wall time: 2.61s** +**wall time: 3.36s** ````` - + @@ -1013,20 +990,20 @@ ripwire: --callers symbol not found: DoesNotExist *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.* -**wall time: 1.97s** +**wall time: 2.68s** ````` - + - + - + - + - + @@ -1034,8 +1011,8 @@ ripwire: --callers symbol not found: DoesNotExist + - @@ -1053,26 +1030,26 @@ ripwire: --callers symbol not found: DoesNotExist *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).* -**wall time: 2.33s** +**wall time: 2.20s** ````` - - - + + + - + - + - + - + @@ -1082,8 +1059,8 @@ ripwire: --callers symbol not found: DoesNotExist + - … [73 more display lines; full output is 5481 bytes on 1 raw line(s)] @@ -1093,17 +1070,17 @@ ripwire: --callers symbol not found: DoesNotExist *Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_defs and resolves the right `main` (was reachable="0").* -**wall time: 1.31s** +**wall time: 1.53s** ````` - - + + - + @@ -1128,7 +1105,7 @@ ripwire: --callers symbol not found: DoesNotExist *Which definitions each name binds, one row per definition group with sel= (the spelling that picks it alone in any verb), class=source|test and in= callers; source first.* -**wall time: 1.92s** +**wall time: 1.98s** ````` @@ -1161,11 +1138,11 @@ ripwire: --callers symbol not found: DoesNotExist *Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* -**wall time: 1.64s** +**wall time: 2.97s** ````` - + @@ -1180,7 +1157,7 @@ ripwire: --callers symbol not found: DoesNotExist *Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* -**wall time: 3.37s** +**wall time: 2.17s** ````` @@ -1220,7 +1197,7 @@ ripwire: --callers symbol not found: DoesNotExist *Markdown docs that name SYM in a backtick (doc<->code edges).* -**wall time: 2.39s** +**wall time: 2.35s** ````` @@ -1239,11 +1216,11 @@ ripwire: --callers symbol not found: DoesNotExist *Test files that transitively reach the changed file.* -**wall time: 2.06s** +**wall time: 2.28s** ````` - @@ -1260,12 +1237,12 @@ ripwire: --callers symbol not found: DoesNotExist *Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below).* -**wall time: 3.27s** +**wall time: 7.50s** ````` ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: 70c265cd6 +at: 77bb261ba (0 changed files — working tree is clean, nothing to analyze) ````` @@ -1273,7 +1250,7 @@ at: 70c265cd6 *Pre-PR gate on a CLEAN tree: no obligations, exit 0.* -**wall time: 4.85s** +**wall time: 8.45s** ````` @@ -1285,7 +1262,7 @@ at: 70c265cd6 *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.* -**wall time: 1.04s** +**wall time: 2.99s** ````` @@ -1468,11 +1445,11 @@ at: 70c265cd6 *The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* -**wall time: 2.46s** +**wall time: 3.95s** ````` -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 @@ -1508,21 +1485,21 @@ at: 70c265cd6 *Raw BM25 ranking (debug lens; --for is the real verb).* -**wall time: 2.04s** +**wall time: 2.87s** ````` - + - + - + @@ -1533,12 +1510,12 @@ at: 70c265cd6 - + - + … [22 more display lines; full output is 3459 bytes on 1 raw line(s)] @@ -1553,10 +1530,10 @@ at: 70c265cd6 *Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* -**wall time: 4.36s** +**wall time: 10.89s** ````` - + @@ -1566,7 +1543,7 @@ at: 70c265cd6 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 ) +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 @@ -1584,8 +1561,8 @@ at: 70c265cd6 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 +std::uint32_t testIterationCeiling() noexcept +inline const char* rankByLegendFor( const char* label ) noexcept … [156 more display lines; full output is 22219 bytes on 72 raw line(s)] ````` @@ -1593,7 +1570,7 @@ at: 70c265cd6 *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.94s** +**wall time: 2.78s** ````` @@ -1601,7 +1578,7 @@ at: 70c265cd6 signature rows. est_tokens=: price as emitted (an upper bound under compact). root=: p= relative to it. pr_iters=N: PageRank iterations. declined=K: K call sites without edges, including inert Elixir quote sites after unquote/unquote_splicing re-entry. external=K: K calls proven … [line truncated: 1465 more bytes on this line] - + @@ -1633,7 +1610,7 @@ at: 70c265cd6 *Control-flow skeleton of one symbol, payload-only via the new --top-k=0.* -**wall time: 1.24s** +**wall time: 4.43s** ````` - + @@ -1941,13 +1918,13 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *File->file dependency graph (god-files, cycles).* -**wall time: 1.09s** +**wall time: 2.04s** ````` - - - + + + @@ -1981,25 +1958,25 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Complexity x recent git churn (maintenance pain).* -**wall time: 6.11s** +**wall time: 8.82s** ````` - + - + - + - + - + @@ -2021,11 +1998,11 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Token-normalized duplicate bodies.* -**wall time: 3.66s** +**wall time: 4.18s** ````` - + @@ -2061,40 +2038,40 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Files that change together in git (hidden coupling).* -**wall time: 6.37s** +**wall time: 6.79s** ````` - + + - - - - + + - - - - - + + + - + + + + + - - + ````` @@ -2103,26 +2080,26 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Hotspots scoped to RECENT churn (the regression lens).* -**wall time: 5.91s** +**wall time: 11.53s** ````` - + - + - + - + - + @@ -2130,25 +2107,25 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan + - -… [13 more display lines; full output is 5132 bytes on 1 raw line(s)] +… [13 more display lines; full output is 5133 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.* -**wall time: 1.21s** +**wall time: 1.67s** ````` - @@ -2176,18 +2153,18 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan -… [1142 more display lines; full output is 168443 bytes on 1 raw line(s)] +… [1143 more display lines; full output is 168584 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint` *Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* -**wall time: 3.75s** +**wall time: 3.27s** ````` - + @@ -2196,14 +2173,14 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - + @@ -2223,7 +2200,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *User lint rules (YAML, ast-grep style) from a directory.* -**wall time: 2.24s** +**wall time: 1.90s** ````` @@ -2250,8 +2227,6 @@ ripwire: AST query did not compile for any grammar: (this_is_not_a_real_node @x *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.* -**wall time: 1.26s** - Input file: ````` @@ -2305,26 +2280,26 @@ The joined finding — past the display cut above, extracted so the join is visi *Cluster the call graph into cohesive modules.* -**wall time: 1.90s** +**wall time: 1.89s** ````` - + + - - + - + @@ -2338,7 +2313,7 @@ The joined finding — past the display cut above, extracted so the join is visi -… [189 more display lines; full output is 16823 bytes on 1 raw line(s)] +… [189 more display lines; full output is 16816 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --zoom` @@ -2349,9 +2324,9 @@ The joined finding — past the display cut above, extracted so the join is visi ````` - - + + @@ -2374,51 +2349,51 @@ The joined finding — past the display cut above, extracted so the join is visi - - + + -… [137 more display lines; full output is 7477 bytes on 1 raw line(s)] +… [137 more display lines; full output is 7479 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.* -**wall time: 1.73s** +**wall time: 1.38s** ````` - - - + + + - + - + - + - + - + - + - + - + - + - + - + - + -… [293 more display lines; full output is 13250 bytes on 1 raw line(s)] +… [289 more display lines; full output is 13105 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --report` @@ -2432,16 +2407,16 @@ The joined finding — past the display cut above, extracted so the join is visi # ripwire architecture report -3931 files · 33184 symbols · 31243 edges · 3837 modules (15600 call-graph isolated) +3943 files · 33211 symbols · 31252 edges · 3827 modules (15617 call-graph isolated) Root: `.` -Call-graph isolate provenance: 4128 declaration, 2511 header, 3713 source, 5248 document; 0 connected Louvain singletons +Call-graph isolate provenance: 4131 declaration, 2511 header, 3727 source, 5248 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3837) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1495 symbols -- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 555 symbols -- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 254 symbols +## Modules (call-graph clusters; showing 12 of 3827) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1579 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 556 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 256 symbols - **docs::`--top-k=N`@COMMANDS.md:59:9108 [pack,allow,insert]** — 182 symbols - **test/ownerhopfix/py/pkg::big_handler_001@broadcast.py:3:72** — 106 symbols - **src::buf@infra/svector.h:125:8942 [insert,find,push]** — 95 symbols @@ -2449,8 +2424,8 @@ Call-graph isolate provenance: 4128 declaration, 2511 header, 3713 source, 5248 - **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,compute,collect]** — 66 symbols - **test::no@estchargecheck.sh:52:4108 [skip,set]** — 63 symbols - **src::child@ingest_valuerefs.h:651:34179 [emit,resolve,open]** — 59 symbols -- **src::active@pathpairs.h:106:7669 [emit,split,resolve]** — 56 symbols - **src::sliceKindIs@slice.h:368:24239 [match,collect,push]** — 48 symbols +- **src::active@pathpairs.h:106:7669 [split,resolve,emit]** — 46 symbols ## God files (most depended-on; showing 10 of 236) - `src/model.h` — 108 dependents @@ -2458,18 +2433,18 @@ Call-graph isolate provenance: 4128 declaration, 2511 header, 3713 source, 5248 - `src/infra/Diagnostics.h` — 65 dependents - `src/graph.h` — 50 dependents - `src/serialize.h` — 49 dependents -… [35 more lines, 3936 bytes total] +… [35 more lines, 3944 bytes total] ````` ## `./build/ripwire . --seams` *Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* -**wall time: 2.82s** +**wall time: 1.31s** ````` - @@ -2505,26 +2480,26 @@ Call-graph isolate provenance: 4128 declaration, 2511 header, 3713 source, 5248 *Module (directory) dependency graph as a Mermaid diagram.* -**wall time: 2.58s** +**wall time: 1.58s** ````` %% 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
9807"] + n102["src
9809"] n103["src/infra
1301"] end subgraph sg1 ["test"] n104["test
5595"] - n606["test/legochargefix
546"] - n419["test/fixtures/recallpassage
314"] - n1033["test/slotjoinfix
175"] - n843["test/ownerhopfix/py/pkg
158"] - n356["test/expandmodefix
151"] - n358["test/expandsibsfix
149"] - n1016["test/rubyschemafix
99"] - n456["test/forbodyfoldfix/py
98"] - n998["test/rubyattrsfix
85"] + n607["test/legochargefix
546"] + n420["test/fixtures/recallpassage
314"] + n1034["test/slotjoinfix
175"] + n844["test/ownerhopfix/py/pkg
158"] + n357["test/expandmodefix
151"] + n359["test/expandsibsfix
149"] + n1017["test/rubyschemafix
99"] + n457["test/forbodyfoldfix/py
98"] + n999["test/rubyattrsfix
85"] end n74["docs
866"] n2[".github
632"] @@ -2545,12 +2520,12 @@ flowchart LR *Bus-factor: recency-weighted author ownership per file.* -**wall time: 7.30s** +**wall time: 6.36s** ````` - - + + @@ -2585,11 +2560,11 @@ flowchart LR *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.* -**wall time: 2.93s** +**wall time: 2.31s** ````` - + ````` @@ -2598,7 +2573,7 @@ flowchart LR *Which symbols a TEST FILE exercises — the reverse direction of --affected.* -**wall time: 3.15s** +**wall time: 1.59s** ````` @@ -2611,11 +2586,11 @@ flowchart LR *Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* -**wall time: 1.14s** +**wall time: 1.26s** ````` - ````` @@ -2624,11 +2599,11 @@ flowchart LR *On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* -**wall time: 26.05s** +**wall time: 28.93s** ````` - ````` @@ -2642,11 +2617,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 33.98s** +**wall time: 36.55s** ````` - @@ -2688,11 +2663,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree).* -**wall time: 19.42s** +**wall time: 22.83s** ````` - @@ -2707,11 +2682,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *No-LLM review-evidence bundle for the working-tree diff (clean tree = empty).* -**wall time: 3.15s** +**wall time: 6.51s** ````` - ````` @@ -2720,59 +2695,55 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 11.57s** +**wall time: 15.86s** ````` - - + - - - - + + - + + + - - - - - - - - - - - - - + - - - - - - + + - + + + + + + + + + + + + +… [19 more display lines; full output is 4650 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: 24.44s** +**wall time: 37.95s** ````` - - + + - + @@ -2784,11 +2755,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Which refs of the `lane/` ref family still hold divergent authored work vs HEAD, with verdicts.* -**wall time: 77.74s** +**wall time: 74.21s** ````` - + @@ -2817,18 +2788,18 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g -… [281 more display lines; full output is 35915 bytes on 1 raw line(s)] +… [296 more display lines; full output is 37602 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --stray-content=fix/` *A second selection, the `fix/` ref family, 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: 6.29s** +**wall time: 11.12s** ````` - + ````` @@ -2836,11 +2807,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 180.37s** +**wall time: 213.65s** ````` - + @@ -2854,29 +2825,29 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g - - + + + - + - -… [33526 more display lines; full output is 4640965 bytes on 1 raw line(s)] +… [33527 more display lines; full output is 4641044 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: 6.44s** +**exit code: 1** — **wall time: 13.37s** ````` (empty) @@ -2893,11 +2864,11 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 431.08s** +**exit code: 2** — **wall time: 574.76s** ````` - @@ -2923,18 +2894,18 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 23.43s** +**wall time: 32.81s** ````` - - + + - - + + - + @@ -2945,7 +2916,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - + ````` @@ -2953,12 +2924,12 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 46.52s** +**wall time: 41.99s** ````` - - + + @@ -2993,11 +2964,11 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 30.39s** +**wall time: 28.27s** ````` - - + + @@ -3045,15 +3016,15 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Same, plus a git-history row (never / removed-by-commit) for names no tree carries.* -**wall time: 28.46s** +**wall time: 27.74s** ````` - - + + - - + + ````` @@ -3061,12 +3032,12 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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.95s** +**wall time: 2.28s** ````` - + @@ -3101,11 +3072,11 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Blast radius of turning ONE gate on: live code, symbols, transitive reach, covering tests.* -**wall time: 2.03s** +**wall time: 1.88s** ````` - + @@ -3139,7 +3110,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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.77s** +**exit code: 1** — **wall time: 1.84s** ````` (empty) @@ -3156,10 +3127,10 @@ ripwire: run `ripwire . --flags` for the gate table *NEW VERB: pre-hoc lane plan — which of 3 parallel worktrees would COLLIDE, before a line is written. JSON on stdout.* -**wall time: 5.25s** +**wall time: 6.98s** ````` -{"v":1,"verb":"plan-lanes","at":"70c265cd6","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":3931,"symbols":33184,"edges":312 … [line truncated: 338 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"77bb261ba","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":3943,"symbols":33211,"edges":312 … [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"}, @@ -3176,12 +3147,12 @@ ripwire: run `ripwire . --flags` for the gate table {"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":693,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":455,"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":5897,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":490,"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":5897,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":491,"tested":0}], "files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":56}, {"p":"./src/docdrift.h","symbols":4,"churn":36,"ccx":14,"hotspot_rank":40}, {"p":"./src/editcheck.h","symbols":1,"churn":61,"ccx":10,"hotspot_rank":43}, {"p":"./src/main.cpp","symbols":2,"churn":455,"ccx":12,"hotspot_rank":5}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":490,"ccx":2,"hotspot_rank":4}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":491,"ccx":2,"hotspot_rank":4}, {"p":"./src/pageview.h","symbols":1,"churn":28,"ccx":2,"hotspot_rank":132}, {"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":26}]},"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] @@ -3196,7 +3167,7 @@ ripwire: run `ripwire . --flags` for the gate table *NEW VERB, explicit form: one line per lane, lane boundaries are the ones you wrote (the defensible mode).* -**wall time: 5.65s** +**wall time: 5.34s** Input file: @@ -3207,10 +3178,10 @@ write regression tests for the new filter ````` ````` -{"v":1,"verb":"plan-lanes","at":"70c265cd6","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3931,"symbols":33184,"edges":31243,"ambiguous":2988,"unresolved":15914},"carve":null,"core":{"files" … [line truncated: 4 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"77bb261ba","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3943,"symbols":33211,"edges":31252,"ambiguous":2988,"unresolved":15925},"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":658,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":490,"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":658,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":491,"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}, @@ -3218,24 +3189,24 @@ write regression tests for the new filter {"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/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":5897,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":490,"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":5897,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":491,"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/docdrift.h","symbols":7,"churn":36,"ccx":60,"hotspot_rank":40}, {"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":107}, -{"p":"./src/mcpverbs.h","symbols":2,"churn":490,"ccx":2,"hotspot_rank":4}, +{"p":"./src/mcpverbs.h","symbols":2,"churn":491,"ccx":2,"hotspot_rank":4}, {"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":65}, {"p":"./src/verbs_change.h","symbols":1,"churn":82,"ccx":4,"hotspot_rank":26}], "symbols_total":3537,"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":5,"notes":[], "execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":12,"files":5,"module_span":5,"max_ccx":38,"sum_ccx":70,"ambiguous_calls":14,"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":136,"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":2953,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":515,"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":3032,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":515,"tested":0}, +{"p":"./src/cli.h","n":"HelpLine","scope":"rw","key":"1489844dbc310b82","id":"./src/cli.h::rw::HelpLine","id_addressable":true,"id_collides_with":0,"l":2953,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":516,"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":3032,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":516,"tested":0}, {"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":136,"l":885,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":18,"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":3097,"ord":0,"overloads":1,"amb":1,"cx":4,"ccx":3,"churn":515,"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":3097,"ord":0,"overloads":1,"amb":1,"cx":4,"ccx":3,"churn":516,"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":3083,"ord":0,"overloads":1,"amb":0,"cx":2,"ccx":1,"churn":515,"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":3083,"ord":0,"overloads":1,"amb":0,"cx":2,"ccx":1,"churn":516,"tested":0}, {"p":"./src/crossref.h","n":"writeWhereisTextCut","scope":"crossref","key":"9a3ef6403f53ce21","id":"./src/crossref.h::crossref::writeWhereisTextCut","id_addressable":true,"id_collides_with":0,"l":4542,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":3,"churn":95,"tested":0}, … [58 more display lines; full output is 18348 bytes on 1 raw line(s)] ````` @@ -3244,7 +3215,7 @@ write regression tests for the new filter *Out-of-range refusal shape for the lane count.* -**exit code: 1** +**exit code: 1** — **wall time: 1.11s** ````` (empty) @@ -3260,9 +3231,11 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out *CPU/GPU contract view of one struct: computed offsets/sizes/padding + mirror check.* +**wall time: 1.84s** + ````` - + @@ -3300,7 +3273,7 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out *The honest refusal (exit 1): Lang is an `enum class`, not a struct — no offsets are fabricated.* -**exit code: 1** — **wall time: 1.21s** +**exit code: 1** — **wall time: 1.15s** ````` (empty) @@ -3316,11 +3289,11 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped *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: 2.97s** +**wall time: 4.03s** ````` - +
@@ -3356,11 +3329,11 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped *The finishable to-do list: docs whose LIVE failing anchors a date-stamp would reclassify.* -**wall time: 2.78s** +**wall time: 5.24s** ````` - + @@ -3426,12 +3399,12 @@ Tail of the same output — the `` section: *Same report, with git history splitting stale mentions into deleted-by-commit vs never-existed.* -**wall time: 3.40s** +**wall time: 6.00s** ````` - - + + @@ -3466,7 +3439,7 @@ Tail of the same output — the `` section: *Map a pasted stack trace onto indexed symbols. CHANGED: in_corpus= now reports the real count (was 0).* -**wall time: 1.18s** +**wall time: 1.94s** Input file: @@ -3520,7 +3493,7 @@ AddressSanitizer:DEADLYSIGNAL *List all field notes (write-side memory) — the committed .ripwire_notes at the repo root, each with the sha/branch it was recorded at.* -**wall time: 1.04s** +**wall time: 1.84s** ````` @@ -3545,7 +3518,7 @@ AddressSanitizer:DEADLYSIGNAL *ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* -**wall time: 1.22s** +**wall time: 2.12s** ````` @@ -3585,7 +3558,7 @@ AddressSanitizer:DEADLYSIGNAL *Fan-out form: one shared core + 3 per-agent slices carved along call-graph communities.* -**wall time: 1.38s** +**wall time: 1.89s** ````` + @@ -3638,7 +3611,7 @@ AddressSanitizer:DEADLYSIGNAL 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 ) +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 @@ -3656,8 +3629,8 @@ AddressSanitizer:DEADLYSIGNAL 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 +std::uint32_t testIterationCeiling() noexcept +inline const char* rankByLegendFor( const char* label ) noexcept … [124 more display lines; full output is 19109 bytes on 10 raw line(s)] ````` @@ -3665,7 +3638,7 @@ AddressSanitizer:DEADLYSIGNAL *Per-file metrics as CodeCharta cc.json.* -**wall time: 3.39s** +**wall time: 5.11s** ````` (empty) @@ -3674,7 +3647,7 @@ AddressSanitizer:DEADLYSIGNAL Artifact written: ````` - 610617 /aux/ripwire2.cc.json + 612188 /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 ````` @@ -3682,7 +3655,7 @@ Artifact written: *One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* -**wall time: 4.19s** +**wall time: 5.58s** Input file: @@ -3697,7 +3670,7 @@ lego:Vehicle - + @@ -3736,17 +3709,17 @@ lego:Vehicle *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: 19.97s** +**exit code: 1** — **wall time: 39.30s** ````` - - + - - + + - + @@ -3799,14 +3772,14 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s *Rank by git change-frequency prior instead of PageRank.* -**wall time: 7.70s** +**wall time: 9.41s** ````` - - + + @@ -3826,31 +3799,31 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s ````` -## `./build/ripwire . --rank-by=churn-decay --since=HEAD~9 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3` +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --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: 11.26s** +**wall time: 10.70s** ````` - - + + - - - - - - + + + + + + - - - - + + + + - + ````` @@ -3874,11 +3847,11 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *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).* -**wall time: 2.33s** +**wall time: 3.23s** ````` - + 0=src/mcpindex.h 1=src/graph.h 2=src/eval.h 3=src/main.cpp 0,1,1,2,3,3,3 @@ -3895,24 +3868,24 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *Flat top-K export for an external reranker.* -**wall time: 5.97s** +**wall time: 8.02s** ````` - - + + 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 ) @@ -3922,7 +3895,7 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *An unknown --format value REFUSES (exit 1), named, with the supported set listed.* -**exit code: 1** +**exit code: 1** — **wall time: 1.10s** ````` (empty) @@ -3938,10 +3911,10 @@ ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidate *Machine-parseable JSON, same content, keys mirror the XML attrs.* -**wall time: 3.37s** +**wall time: 2.87s** ````` -{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1184","sites_at":["src/mcpindex.h:1288"]}, +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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:12016","sites_at":["src/graph.h:12019"]}, {"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:12662","sites_at":["src/graph.h:12713"]}, {"t":"fn","n":"runEval","p":"src/eval.h:171","sites_at":["src/eval.h:328"]}, @@ -3970,15 +3943,15 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Pagination: 3 items, skipping the first 3 (deterministic seams).* -**wall time: 9.86s** +**wall time: 8.43s** ````` - - + + - + ````` @@ -3986,12 +3959,12 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop test paths from the corpus before ranking.* -**wall time: 2.97s** +**wall time: 2.69s** ````` - + @@ -4010,7 +3983,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - + @@ -4026,13 +3999,13 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop matching paths (repeatable) before ranking.* -**wall time: 3.28s** +**wall time: 3.25s** ````` - + @@ -4051,7 +4024,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - + @@ -4066,14 +4039,14 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Full map re-ranked with teleport toward git-changed files — clean tree, so changed=0 and it degrades to the plain map.* -**wall time: 6.69s** +**wall time: 6.79s** ````` - - + + @@ -4098,13 +4071,13 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm cost.* -**wall time: 6.20s** +**wall time: 5.05s** ````` - + @@ -4120,7 +4093,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - + @@ -4138,13 +4111,13 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Explicit incremental cache at a path OUTSIDE the repo (first call writes it).* -**wall time: 5.67s** +**wall time: 5.31s** ````` - + @@ -4160,7 +4133,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - + @@ -4177,20 +4150,20 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m Artifact written: ````` - 29623015 /aux/warm2.ripwirecache + 29639325 /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).* -**wall time: 1.49s** +**wall time: 1.66s** ````` - + @@ -4204,7 +4177,7 @@ Artifact written: - + @@ -4240,13 +4213,13 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Multi-root workspace: ONE merged graph over two roots, paths labeled /.* -**wall time: 4.19s** +**wall time: 4.71s** ````` - + @@ -4274,19 +4247,19 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Self-eval: co-change recall vs BM25.* -**wall time: 24.33s** +**wall time: 22.03s** ````` ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 1.0% 1.2% 2.7% - BM25 14.8% 14.9% 15.9% - BM25sub 12.8% 14.4% 18.4% - BM25body 3.4% 16.9% 23.4% - fused 1.0% 4.1% 11.9% - anchored 3.4% 16.9% 23.4% - same-dir 0.0% 0.6% 6.3% - random 0.1% 0.3% 0.5% <- floor (random ranking over F=3931 files) + ripwire 1.0% 1.2% 2.8% + BM25 13.2% 13.3% 14.0% + BM25sub 11.3% 12.5% 16.2% + BM25body 3.6% 15.6% 22.0% + fused 1.0% 4.2% 10.6% + anchored 3.6% 15.6% 22.0% + same-dir 1.2% 1.8% 7.4% + random 0.1% 0.3% 0.5% <- floor (random ranking over F=3943 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 / @@ -4302,24 +4275,24 @@ ripwire --eval (co-change recovery, averaged over 80 historical commits) *Known-item retrieval eval: MRR + recall@k per ranker per query mode.* -**wall time: 186.02s** +**wall time: 178.83s** ````` -ripwire --eval-retrieval (known-item, 4002 doc-commented symbols; gold is in-corpus by construction) - sample: population=8397 scored=4002 rule=smallest-key CAPPED — a SUBSET, not the population +ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) + sample: population=8408 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.674 53.6% 84.0% 89.4% - subtoken doc-phrase 0.889 86.8% 90.8% 92.1% - name-exact name 0.888 80.5% 95.2% 97.4% - name-exact doc-phrase 0.011 0.4% 1.7% 2.3% - anchored name 0.679 55.1% 83.2% 88.0% - anchored doc-phrase 0.884 86.0% 90.7% 91.4% - routed name 0.889 80.7% 95.3% 97.6% - routed doc-phrase 0.887 86.7% 90.6% 91.8% - note: routing chose name-exact on 3991/4002 NAME queries (a NAME query is always identifier-shaped); + subtoken name 0.675 53.8% 84.0% 89.4% + subtoken doc-phrase 0.887 86.6% 90.7% 92.0% + name-exact name 0.888 80.5% 95.1% 97.3% + name-exact doc-phrase 0.011 0.5% 1.7% 2.4% + anchored name 0.680 55.2% 83.3% 88.0% + anchored doc-phrase 0.883 85.9% 90.5% 91.3% + routed name 0.889 80.7% 95.2% 97.5% + routed doc-phrase 0.886 86.5% 90.5% 91.7% + 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 @@ -4331,7 +4304,7 @@ ripwire --eval-retrieval (known-item, 4002 doc-commented symbols; gold is in-co *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: 67.70s** +**exit code: 3** — **wall time: 75.84s** Input file: @@ -4355,6 +4328,8 @@ arm/orient-map-067-narrow merged *Labelled skill-ROUTING eval over the repo's own skills/ directory (4 hand-labelled prompts).* +**wall time: 1.07s** + Input file: ````` @@ -4395,7 +4370,7 @@ ripwire --eval-skills (skill routing over K=16 candidate skills [ripwire-router 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] +… [19 more lines, 3668 bytes total] ````` ## `./build/ripwire wrap claude` @@ -4441,7 +4416,7 @@ Reach for it BEFORE blind grep + whole-file reads. First call ~1s cold; after th *Version + short build info.* ````` -ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=de9d903b2) +ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=982306d4f) ````` @@ -4453,7 +4428,7 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=de9d *Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost.* -**wall time: 1.21s** +**wall time: 1.86s** ````` @@ -4466,11 +4441,11 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=de9d *The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* -**wall time: 1.43s** +**wall time: 1.56s** ````` - + @@ -4485,7 +4460,7 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=de9d *A seed past the end of the file — the refusal shape for a faulted location.* -**exit code: 1** — **wall time: 1.90s** +**exit code: 1** — **wall time: 1.41s** ````` (empty) @@ -4501,11 +4476,11 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *VERIFY a closed claim in one call: three-valued verdict (confirmed / refuted / not-established) with the evidence rows inline.* -**wall time: 1.52s** +**wall time: 1.86s** ````` - + @@ -4515,11 +4490,11 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A claim that is FALSE — the refuted shape, with the references that refute it.* -**wall time: 1.46s** +**wall time: 1.49s** ````` - + @@ -4536,7 +4511,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A literal-scan absence: refuted only with complete= evidence, never on a partial scan.* -**wall time: 1.34s** +**wall time: 2.12s** ````` @@ -4548,7 +4523,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *An unparseable claim — the refusal names the accepted shapes.* -**exit code: 1** — **wall time: 1.34s** +**exit code: 1** — **wall time: 2.22s** ````` (empty) @@ -4564,11 +4539,11 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *Bare --slice=SYM: the INVENTORY of sliceable locals (), so a caller can pick VAR.* -**wall time: 2.04s** +**wall time: 4.22s** ````` - + @@ -4590,11 +4565,11 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *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: 2.11s** +**wall time: 4.43s** ````` - + @@ -4614,11 +4589,11 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *TRANSITIVE backward value-flow from the seed variable, bounded BFS (depth= disclosed; a cut frontier says flow_truncated=1).* -**wall time: 1.94s** +**wall time: 3.66s** ````` - + @@ -4647,11 +4622,11 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *Forward flow: which statements the seed's value reaches, at the default depth bound.* -**wall time: 2.10s** +**wall time: 3.84s** ````` - + @@ -4692,7 +4667,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *A variable the definition does not bind — the refusal shape, naming the inventory.* -**exit code: 1** — **wall time: 1.07s** +**exit code: 1** — **wall time: 2.43s** ````` (empty) @@ -4724,11 +4699,11 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *The compact legend posture: rows byte-identical, a versioned schema id replaces the repeated explanatory prose — for a many-small-calls loop.* -**wall time: 1.96s** +**wall time: 4.46s** ````` - + @@ -4748,7 +4723,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *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: 2.07s** +**wall time: 2.95s** ````` @@ -4761,7 +4736,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *The ellipsis form over a macro-shaped call site; unsupported= names the families this verb does not serve.* -**wall time: 1.96s** +**wall time: 2.19s** ````` @@ -4801,7 +4776,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *A pattern that collapses to a bare token is REFUSED — never reported as hits=0.* -**exit code: 1** — **wall time: 2.26s** +**exit code: 1** — **wall time: 2.19s** ````` (empty) @@ -4817,7 +4792,7 @@ ripwire: --pattern: this pattern collapses to a single bare token, which is a TE *Boolean grep: hits where BOTH literals share the matched line (--grep-scope=line is the default).* -**wall time: 1.59s** +**wall time: 2.53s** ````` @@ -5113,10 +5088,10 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? *Restore the / sections the --for bundle collapses to a counted stub by default (this is the stub's own next= spelling).* -**wall time: 3.22s** +**wall time: 10.94s** ````` - + @@ -5153,10 +5128,10 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? *Opt OUT of compact conceptual serving: restore the rank-first auto walk (bundle="auto").* -**wall time: 3.11s** +**wall time: 8.20s** ````` - + @@ -5193,16 +5168,16 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? *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: 3.00s** +**wall time: 7.08s** ````` - + 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 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 ) @@ -5218,11 +5193,11 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? 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 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 +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 std::string gitHeadSha( const std::string& root ) inline bool atomicWriteFile( const std::string& path, const std::string& blob ) @@ -5233,16 +5208,16 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? *The same task with doc-mention surfacing OFF — the contrast the flag exists for (no [doc mentions] clause, one fewer row).* -**wall time: 2.76s** +**wall time: 7.70s** ````` - + 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 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 ) @@ -5258,11 +5233,11 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? 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 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 +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 std::string gitHeadSha( const std::string& root ) inline bool atomicWriteFile( const std::string& path, const std::string& blob ) @@ -5273,6 +5248,8 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? *"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.* +**wall time: 1.63s** + ````` disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a CLEAN tree.* -**wall time: 5.28s** +**wall time: 11.17s** ````` - + - + - - - + + + + ````` @@ -5330,19 +5307,18 @@ ripwire: --safe-delete symbol not found: DoesNotExist *The same packet under a hard ceiling: heuristic rows drop tail-first (withheld= disclosed), verified rows never drop.* -**wall time: 5.88s** +**wall time: 11.65s** ````` - - + + - + ````` @@ -5351,13 +5327,15 @@ ripwire: --safe-delete symbol not found: DoesNotExist *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.* +**wall time: 1.20s** + ````` - @@ -5389,11 +5367,13 @@ ripwire: --safe-delete symbol not found: DoesNotExist *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).* +**wall time: 1.51s** + ````` - + @@ -5409,7 +5389,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist - + @@ -5427,13 +5407,13 @@ ripwire: --safe-delete symbol not found: DoesNotExist *--no-stable outside --mcp: what the flag does (or says) when there is no stable-by-default ordering to opt out of.* -**wall time: 1.11s** +**wall time: 2.31s** ````` - + @@ -5449,7 +5429,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist - + @@ -5473,13 +5453,13 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o *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** +**exit code: 4** — **wall time: 1.86s** ````` - + - + pw = biasPrior( g, p ); @@ -5522,10 +5502,12 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o *A command that exits 0: a minimal success record (exit, measured duration, disclosed output tail) and NO bundle — nothing failed, nothing to map.* +**wall time: 2.29s** + ````` - + ````` @@ -5533,12 +5515,12 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o *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.90s** +**exit code: 4** — **wall time: 3.81s** ````` - + ````` @@ -5573,11 +5555,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *THE single wide-angle quality read: six families in one pass, an eligible/ranked shortlist rather than a firehose.* -**wall time: 7.57s** +**wall time: 11.21s** ````` - @@ -5613,11 +5595,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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.52s** +**wall time: 2.86s** ````` - + @@ -5633,11 +5615,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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.* -**wall time: 1.35s** +**wall time: 2.01s** ````` - + @@ -5653,27 +5635,27 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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.40s** +**wall time: 2.17s** ````` - - + + - - - - + + + + - + @@ -5693,11 +5675,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *Per function, the non-local MUTABLE state it can reach (transitively), most writes first — unsound by construction, and the legend says where.* -**wall time: 3.35s** +**wall time: 3.70s** ````` - @@ -5733,11 +5715,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The family join: per function, which of four orthogonal evidence families fire, ranked by how many agree.* -**wall time: 6.58s** +**wall time: 8.33s** ````` - @@ -5756,15 +5738,15 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - + - + - + … [36 more display lines; full output is 7482 bytes on 1 raw line(s)] ````` @@ -5773,11 +5755,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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.75s** +**wall time: 3.32s** ````` - @@ -5813,11 +5795,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The same lens narrowed to ONE struct — the one --layout=Symbol shows the offsets for.* -**wall time: 2.28s** +**wall time: 2.55s** ````` - @@ -5849,10 +5831,12 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The corpus's OWN case-convention vote per (language, kind) group; off-convention names get a mechanical propose= (a suggestion, never a blind rename).* +**wall time: 1.73s** + ````` - - + + @@ -5887,11 +5871,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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: 15.42s** +**wall time: 15.93s** ````` - + @@ -5917,18 +5901,18 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir

-… [167 more display lines; full output is 17115 bytes on 1 raw line(s)] +… [168 more display lines; full output is 17186 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.57s** +**wall time: 4.02s** ````` - + @@ -5937,14 +5921,14 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - + @@ -5964,7 +5948,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The built-in rule registry — one row per rule with sev=/category=/rationale/lang=/since=; no corpus needed.* -**wall time: 1.08s** +**wall time: 2.10s** ````` @@ -6005,7 +5989,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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=80dbc95318c95d64 entries=972 +ripwire legend dictionary ripwire.dict/v1 dictv=1bb96ec3e6969a9c entries=974 : 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=) @@ -6035,24 +6019,24 @@ ripwire.uses/v1 : resolvable use-sites of of=: : 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.defs/v1 : per the parsed definitions the name binds (defs= groups; a prototype with its definition is one): , sel= binds that one alone in any SYM verb, in= distinct callers (a floor); source first, then in=, then path ripwire.path/v1 : one DIRECTED call path from= to to=, each a hop; reachable=0 hops=0 when none -… [943 more display lines; full output is 103495 bytes on 973 raw line(s)] +… [945 more display lines; full output is 103738 bytes on 975 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: 3.11s** +**wall time: 3.98s** ````` - + - + vector<std::vector<double>> @@ -6075,18 +6059,18 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a g_pool[ g_readOf[i] ] g_pool[ g_readOf[i] ] m[ g_pool[i] ] -… [607 more display lines; full output is 73637 bytes on 1 raw line(s)] +… [607 more display lines; full output is 73630 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: 2.88s** +**wall time: 3.06s** ````` - @@ -6095,7 +6079,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a - + @@ -6122,7 +6106,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *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: 3.05s** +**wall time: 3.14s** ````` @@ -6139,7 +6123,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *Pasting that findings_next=: the same rule under a 10x budget counts its true total — no count_capped=, no findings_next=.* -**wall time: 2.52s** +**wall time: 2.66s** ````` @@ -6170,7 +6154,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *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: 3.14s** +**exit code: 1** — **wall time: 2.45s** ````` (empty) @@ -6186,7 +6170,7 @@ ripwire: --lint-select: 'cach-' matches no rule or family (did you mean 'cache-' *A PREFIX with no near miss at all: the refusal points at --lint-catalog instead of guessing.* -**exit code: 1** — **wall time: 2.90s** +**exit code: 1** — **wall time: 2.82s** ````` (empty) @@ -6202,7 +6186,7 @@ ripwire: --lint-select: 'nosuchfamily' matches no rule or family — see --lint- *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.94s** +**wall time: 3.25s** ````` {"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] @@ -6235,13 +6219,13 @@ ripwire: --lint-select: 'nosuchfamily' matches no rule or family — see --lint- {"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}}, -… [13781 more display lines; full output is 1953686 bytes on 1 raw line(s)] +… [13777 more display lines; full output is 1953363 bytes on 1 raw line(s)] ````` Parsed summary of the same SARIF (past the display cut): ````` -sarif 2.1.0 rules= 39 results= 6886 +sarif 2.1.0 rules= 39 results= 6884 ````` ## `./build/ripwire . --lint --sarif --limit=5` @@ -6264,11 +6248,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *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: 9.23s** +**wall time: 11.01s** ````` -

@@ -6279,11 +6263,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *The per-commit scalar: HEAD vs its first parent, with the three separately actionable sub-scores.* -**wall time: 16.12s** +**wall time: 20.31s** ````` -

@@ -6294,14 +6278,14 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *The range form: tree HEAD vs tree HEAD~3.* -**wall time: 15.95s** +**wall time: 20.28s** ````` - - -

-

+ + +

+

````` @@ -6309,11 +6293,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *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: 7.14s** +**wall time: 9.20s** ````` - + @@ -6342,45 +6326,45 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M -… [851 more display lines; full output is 56600 bytes on 1 raw line(s)] +… [852 more display lines; full output is 56666 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: 5.29s** +**wall time: 10.53s** ````` - - + + + + - - - + - + - - - - + + + + @@ -6391,7 +6375,7 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *The HTML graph with the initial colour mode set to community (the page embeds all five modes and keeps a live selector).* -**wall time: 4.98s** +**wall time: 7.80s** ````` (empty) @@ -6400,14 +6384,14 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M Artifact written: ````` - 156112 /aux/map2.html + 156259 /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: 8.31s** +**wall time: 9.94s** ````` (empty) @@ -6416,29 +6400,29 @@ Artifact written: stderr: ````` -ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (29623015 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (60637669 bytes, rich family) +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (29639325 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (60660177 bytes, rich family) ````` Artifact written: ````` - 29623015 /aux/ci_index.lean.ripwirecache - 60637669 /aux/ci_index.rich.ripwirecache - 90260684 total + 29639325 /aux/ci_index.lean.ripwirecache + 60660177 /aux/ci_index.rich.ripwirecache + 90299502 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.* -**wall time: 2.17s** +**wall time: 2.84s** ````` - + @@ -6454,7 +6438,7 @@ Artifact written: - + @@ -6472,13 +6456,13 @@ Artifact written: *Eval-only: a per-call-site census of WHICH mechanism resolved each call, and the canonical id of every surviving target.* -**wall time: 1.87s** +**wall time: 3.27s** ````` - + @@ -6494,7 +6478,7 @@ Artifact written: - + @@ -6519,18 +6503,18 @@ Artifact written: # 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. - 133165 /aux/pin_census.tsv + 133246 /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: 2.90s** +**exit code: 2** — **wall time: 5.14s** ````` - + @@ -6548,11 +6532,11 @@ Artifact written: *The ledger-shaped fixture through the same check (exit 2: one gating card).* -**exit code: 2** — **wall time: 2.65s** +**exit code: 2** — **wall time: 4.09s** ````` - + @@ -6564,17 +6548,17 @@ Artifact written: *--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: 23.62s** +**exit code: 1** — **wall time: 30.97s** ````` - - + - - + + - + @@ -6611,15 +6595,15 @@ Everything below runs with `cwd` = the throwaway clone at `/dirty` (`gi *Situational report for a real diff: blast radius + tests + co-change + forgotten co-change partners.* -**wall time: 14.29s** +**wall time: 16.01s** ````` ripwire situational-awareness — 1 changed file(s), 13 symbols in them root: . -at: 70c265cd6+dirty +at: 77bb261ba+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: 251 symbols across 60 files transitively depend on these changes (showing 8 of 60 files — shown=8 total=60 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=60) - counts_floor=1 graph_ambiguous=2988 graph_unresolved=15914 graph_unindexed=275 (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] + counts_floor=1 graph_ambiguous=2988 graph_unresolved=15925 graph_unindexed=275 (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_for.h (14 dependent symbols) @@ -6635,16 +6619,22 @@ at: 70c265cd6+dirty 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=795 — 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="5518": - (none — 5518 commits were mined and none co-edited a file outside your diff) + [3] co-change — usually edited with these but NOT in your diff (0) window="18mo@HEAD" commits="5526": + (none — 5526 commits were mined and none co-edited a file outside your diff) next: --test-gate ````` +stderr: + +````` +ripwire: cache : over its 2048 MiB budget — evicted 147 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) +````` + ## `./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: 8.78s** +**exit code: 4** — **wall time: 10.45s** ````` @@ -6686,11 +6676,11 @@ at: 70c265cd6+dirty *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: 54.05s** +**exit code: 2** — **wall time: 48.14s** ````` - @@ -6725,7 +6715,6 @@ at: 70c265cd6+dirty stderr: ````` -ripwire: cache : over its 2048 MiB budget — evicted 72 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) 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) ````` @@ -6734,11 +6723,11 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap *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: 27.26s** +**exit code: 2** — **wall time: 29.16s** ````` - @@ -6781,10 +6770,10 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap *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: 23.76s** +**exit code: 2** — **wall time: 25.19s** ````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":167,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"70c265cd6+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] +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":167,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"77bb261ba+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"}, @@ -6828,7 +6817,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap *NEW FLAG: --ack-only matching nothing REFUSES rather than falling back to acking everything.* -**exit code: 1** — **wall time: 19.40s** +**exit code: 1** — **wall time: 17.41s** ````` (empty) @@ -6846,7 +6835,7 @@ ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written *NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* -**wall time: 20.84s** +**wall time: 17.43s** ````` (empty) @@ -6864,11 +6853,11 @@ ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already *Re-run after the partial ack: acked=3, the rest still gate (exit 2).* -**exit code: 2** — **wall time: 27.56s** +**exit code: 2** — **wall time: 25.82s** ````` - @@ -6927,11 +6916,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6943,11 +6932,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row identical.* -**wall time: 7.34s** +**wall time: 7.25s** ````` - @@ -6959,11 +6948,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6999,14 +6988,14 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire ) and selectors take it. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. --> - - + + @@ -7021,7 +7010,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + @@ -7031,11 +7020,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire of ; dup_loc=/dup_pct=. window: shown= total= capped= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). root=: p= relative to it. groups=/type3=: Type-2 and Type-3 group totals over all groups; total= is their sum. exempt_groups=N: groups whose members all sit on fixture/shell-runner paths quality-delta duplication ignores. idiom_groups=/demoted_groups=: groups of one recognized idiom / those quality-delta demotes to minor; floors. clone_groups=N: clusters after merging pairs (rows sharing gid=); a floor under type3_capped=1. total_loc=N: lines of every function body the detector considered; dup_pct= is dup_loc= over it. group exempt=fixture|shell-runner: every member is on such a path; quality-delta duplication ignores it. group idiom=: the recognized shape every member spells: threshold-ladder, switch-name-table, builder-chain. --> - + @@ -7071,12 +7060,12 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire ; v=unknown = no merge base. at=: commit+dirty+shallow. head=: the HEAD commit, bare 9-hex sha (at= adds +dirty). head_ref=: HEAD's branch (HEAD when detached); that branch itself is not scanned. refs=N: local branches scanned (refs/heads only); unmerged + superseded + merged + unknown = refs. blobs=N: distinct git blobs read for the sweep. unmerged=N: refs whose authored work the live line genuinely lacks. superseded=N: refs whose work the live line re-implemented (removed the same base code). merged=N: refs whose work HEAD already has; omitted from the rows. name=: the local branch. tip=: the branch tip commit (9 hex). date=: the tip's committer date, YYYY-MM-DD. files=N: files with stray lines; rows capped at 12, a more element counts the rest (detail=1 lists all). ref superseded=N>: of this ref's stray= lines, those in files the live line re-implemented. unknown=N: refs that could not be analysed (v=unknown, e.g. no merge base); never counted merged. --> - - + + ````` @@ -7091,11 +7080,11 @@ stderr: *CHANGED: --plan surfaces those same refs as an row rather than silently dropping them.* -**wall time: 8.14s** +**wall time: 9.87s** ````` - + ````` @@ -7110,11 +7099,11 @@ stderr: *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: 10.07s** +**wall time: 12.00s** ````` -

@@ -7125,11 +7114,11 @@ stderr: *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: 24.44s** +**wall time: 25.13s** ````` - @@ -7171,7 +7160,7 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 17.02s** +**exit code: 1** — **wall time: 21.17s** ````` (empty) @@ -7191,11 +7180,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr *The continuation packet with a REAL diff: verified changed symbols + blast radius + tests-to-run, then the heuristic rows.* -**wall time: 12.84s** +**wall time: 15.16s** ````` - + @@ -7220,11 +7209,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr - - - - - + + + + + ````` @@ -7233,10 +7222,10 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr *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: 4.01s** +**wall time: 8.22s** ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 keep this branch-free — it sits inside the PageRank sort comparator 70c265cd61f6db9323b3860e35d1dedba212255c lane/defs-lookup +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 keep this branch-free — it sits inside the PageRank sort comparator 77bb261ba29f1a6117ca1fbb718f4bc5beb50c06 lane/defs-lookup ````` stderr: @@ -7250,7 +7239,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort *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.97s** +**wall time: 2.86s** ````` @@ -7263,7 +7252,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort - + @@ -7280,7 +7269,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort *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** — **wall time: 1.91s** +**exit code: 1** — **wall time: 3.64s** ````` (empty) @@ -7297,7 +7286,7 @@ ripwire: --note-add: target 'gitOneLine' is ambiguous — it matches 2 definitio *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** — **wall time: 1.47s** +**exit code: 1** — **wall time: 2.00s** ````` (empty) @@ -7314,17 +7303,17 @@ ripwire: --note-add: target not found: lessByScoreDescIdd (did you mean 'lessByS *The same symbol addressed by its CANONICAL id: already canonical, so nothing is rewritten and both notes land on ONE target.* -**wall time: 2.51s** +**wall time: 9.28s** ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 70c265cd61f6db9323b3860e35d1dedba212255c lane/defs-lookup +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 77bb261ba29f1a6117ca1fbb718f4bc5beb50c06 lane/defs-lookup ````` ## `./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.* -**wall time: 1.90s** +**wall time: 2.79s** ````` @@ -7363,10 +7352,10 @@ src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 chose the flat The element on the same output — past the 30-line display cut above: ````` - + - + ````` @@ -7375,7 +7364,7 @@ The element on the same output — past the 30-line display cut above: *An EMPTY payload refuses — it never implies deletion.* -**exit code: 1** +**exit code: 1** — **wall time: 1.54s** ````` (empty) @@ -7391,7 +7380,7 @@ ripwire: --edit-payload is empty; empty never means delete *Whole-symbol replace without a whole-file read: the payload is the ORIGINAL flat body, so this edit undoes the sandbox's deep-nesting regression. The receipt's span is the POST-edit byte range; replaced_bytes counts the old bytes overwritten.* -**wall time: 11.62s** +**wall time: 14.50s** Input file: @@ -7408,12 +7397,12 @@ inline bool lessByScoreDescId( const std::vector& scores, std::uint32_t a ````` {"applied":"replace_symbol_body","symbol":"lessByScoreDescId","file":"src/infra/sortutil.h","span":{"start":2162,"end":2375},"lines":{"start":49,"end":56},"replaced_bytes":1718,"old_file_bytes":12329,"new_file_bytes":10824,"file_eol":"lf","eol_normalized":false,"trailing_newline_folded":true,"separa … [line truncated: 726 more bytes on this line] -"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -7426,15 +7415,15 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, *The closed loop: contract unchanged (same params, same publicness) after the replace — nothing provably incompatible.* -**wall time: 5.91s** +**wall time: 6.18s** ````` - + - + @@ -7448,16 +7437,16 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, *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: 10.38s** +**wall time: 10.26s** ````` {"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":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -7470,7 +7459,7 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_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: 3.51s** +**wall time: 4.07s** ````` {"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] @@ -7486,19 +7475,19 @@ ripwire edit: applied atomically; receipt carries region, blob_sha (post-check s *Insert BEFORE, with --edit-target-file pinning which same-named definition (here unambiguous — the disambiguator is simply honoured).* -**wall time: 7.52s** +**wall time: 9.59s** ````` {"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":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ````` stderr: @@ -7511,7 +7500,7 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, *An unknown TARGET refuses and leaves every file byte-identical.* -**exit code: 1** — **wall time: 3.10s** +**exit code: 1** — **wall time: 4.23s** ````` (empty) @@ -7527,7 +7516,7 @@ ripwire: --replace-symbol-body: symbol 'DoesNotExist' not found *A versioned multi-edit TRANSACTION preflighted without writing: the receipt shows what each op would read and touch.* -**wall time: 3.19s** +**wall time: 4.17s** Input file: @@ -7545,21 +7534,21 @@ Input file: ````` ````` -{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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] +{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false," … [line truncated: 348 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: 7.57s** +**wall time: 9.88s** ````` -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"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] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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: 532 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":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true}}]} +"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true}}]} ````` ## `./build/ripwire . --edit-plan=/aux/edit_plan.json` @@ -7582,11 +7571,11 @@ ripwire: --edit-plan requires exactly one of --dry-run or --apply *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: 22.49s** +**exit code: 2** — **wall time: 28.75s** ````` - @@ -7629,7 +7618,7 @@ ripwire: --quality-delta gating: 2 preexisting-worse major finding(s); first: du *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: 14.47s** +**exit code: 1** — **wall time: 22.24s** ````` (empty) @@ -7648,7 +7637,7 @@ ripwire: --quality-baseline: this tree already holds 2 gating finding(s) against *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: 21.98s** +**wall time: 25.42s** ````` (empty) @@ -7658,16 +7647,16 @@ 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 32179 per-symbol rows, 33186 indexed symbols total) +ripwire: wrote ./.ripwire_quality_baseline (snapshot of 32203 per-symbol rows, 33213 indexed symbols total) ````` Artifact written: ````` - 5446331 .ripwire_quality_baseline + 5450298 .ripwire_quality_baseline # ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit -producer 3ada5f8adf8c098b8e95cdb1aa98721e542fbba9885d4731d55abb94b303eb41 -head 70c265cd61f6db9323b3860e35d1dedba212255c +producer 83c8665d632378705842bb931cfcf07076001945324c77fa8e2e2e9bcc42d6ff +head 77bb261ba29f1a6117ca1fbb718f4bc5beb50c06 dirty 1 absorbed 2 ccx 67c15011ee8b 0 @@ -7680,11 +7669,11 @@ ccx 6a732f6699b *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: 24.07s** +**wall time: 26.21s** ````` - ````` @@ -7807,57 +7796,57 @@ quality_delta desc_bytes= 742 schema_bytes= 279 required=[] *MCP `for`: always bundle=sigs (never the CLI's compact route), the same ranked signatures as --for.* -**wall time: 3.83s** +**wall time: 4.77s** ````` {"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: 6565 more bytes on this line] -"_index":"[index: files=3931 symbols=33184 hash=5289b4cf]","_fresh":"ok"}} +"_index":"[index: files=3943 symbols=33211 hash=d2a64018]","_fresh":"ok"}} {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"{}", ( unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + ptKindLegend + splitArmLegend( ptHasSplit ) + pathUnboundLegend( !ptBound.from || !ptBound.to ) + memberOwnerLegend( ptHasOwner ) + memberOwnerContainedLegend( ptMarks.ownerContained ) + std::string( pathPairsLegend( ptPairs ) ) + pathOrderLegend( ptPairs, ptTwin ) - + std::string( pathHopBodiesLegend( !ptHopBodies.empty() ) ) + std::string( pathHopNextLegend( !ptHopNext.empty() ) ) ).c_str(), + + std::string( pathHopBodiesLegend( !ptHopBodies.empty() ) ) + std::string( pathHopNextLegend( !ptHopNext.empty() ) ) + + std::string( routeBodyHedgeLegend( ptHopBodyEl.hedged || ptRouteEl.hedged ) ) ).c_str(), ( std::string( toValueRefsLegend( ptToValueRefs.count > 0 ) ) + valueRefsDepthLegend( ptToValueRefs.depthCut.files > 0 ) ).c_str(), ( pathGapsLegend( ptGaps.any(), !ptSinkCallers.empty() ) + pathBridgeLegend( ptBridge ) - + nearSinkLegend( ptNear ) + std::string( pathHandlerRuleLegend( ptHandler ) ) + std::string( pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), + + nearSinkLegend( ptNear ) + std::string( pathHandlerRuleLegend( ptHandler ) ) + std::string( pathRouteBodiesLegend( !ptRouteEl.xml.empty() ) ) + + std::string( pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), graphCountFloorBrief( rw::graphGaugeClauses( g ) ).c_str(), rootRelPathsLegend( ptSingleRoot ) ); rw::emitTo( mem, " pathText( const std::string& root, const std:: pathNearSinkXml( ing, ptNear, ptSingleRoot, ptRootPrefix ), pth.empty() ? pathHandlerRuleXml( ing, ptBackHops->names(), ptHandler, ptSingleRoot, ptRootPrefix ) : std::string() ); emitPathAnswerRows( mem, ing, g, ptPairs, pth, ptOwners, ptSingleRoot, ptRootPrefix ); // the CLI's own row emitter (pathpairs.h / defattrib.h) - rw::emitRaw( mem, ptHopBodies.c_str() ); // PATH-HOP-BODY, as on the CLI + rw::emitRaw( mem, ptHopBodies.c_str() ); // PATH-HOP-BODY, as on the CLI + rw::emitRaw( mem, ptRouteEl.xml.c_str() ); // BODY-ON-ROUTE, as on the CLI rw::emitRaw( mem, "" ); return mcpAnswerText( stream ); // nullopt = the buffer lost bytes (dispatch answers -32603), "" stays not-found } diff --git a/src/pathgaps.h b/src/pathgaps.h index ec3fcf493..e8fefa1a0 100644 --- a/src/pathgaps.h +++ b/src/pathgaps.h @@ -38,6 +38,7 @@ #include "filter.h" // pathTierIndexOver — the near-sink rows' tier key within a depth #include "arch.h" // wildcardMatch — the ONE `*` match, which a glob endpoint reads #include "closures.h" // ClosureIndex — a near-sink row written inside another function's body (why=closure) +#include "hopbody.h" // BODY-ON-ROUTE: RouteBody, routeBodiesXml — the one edge-kind-aware body printer #include "backhops.h" // HOP1: BackwardHops (slot + declined hops of a sink), DeclinedCrossRule, builtinSpelledCall #include "infra/sortutil.h" // svLess: the explicit byte order every string_view sort here takes (portablebuildcheck #6) #include "infra/enumcount.h" // enumCountIsExact — the kNearSinkKindCount / kBridgeViaCount sizes follow their enums @@ -1790,4 +1791,76 @@ inline std::string_view pathHandlerRuleLegend( const PathHandlerRule& hrule ) no return hrule.found() ? kHandlerRuleLegend : std::string_view(); } + +// ── BODY-ON-ROUTE (lane path-hop-body-fix1, textual-11): the bodies of the route a reachable=0 answer already names ────────── +// A reachable=0 answer names a route it never traced: the bridge rows (poster -> queue consumer -> dispatcher) and the handler_rule +// readers (where the handler name is read and dispatched). The question is usually what those functions DO (textual-11: the post +// puts onto _message_queue, the loop takes from it, _on_message dispatches and bubbles to _parent) and the answer printed only names. +// GAIN GATE (the FOR-ROUTE lesson: 25 KB of rows that gained nothing): only symbols the answer already prints on its route, the +// bridge rows within kRouteBodyMaxHops of from= (never to=) and the readers shown; each body under the per-body guard +// (kHopBodyCapBytes); at most kRouteBodyMax bodies, capped="1" + next= past it. HONESTY: the envelope says proven="0"; a body under a +// crossing carries that crossing's via= cands= (one candidate's body, never the proven callee), a reader's via="name-key"; nothing +// here enters reachable=, hops= or any proven count. +inline constexpr std::size_t kRouteBodyMaxHops = 2; // bridge rows 0..2 (from= and the next two), never the to= row +inline constexpr std::size_t kRouteBodyMax = 4; // spew guard: bodies per answer; past it capped="1" next= the first unshown + +inline std::vector pathRouteBodies( const IngestResult& ing, const Graph& g, const PathBridge& br, const PathHandlerRule& hrule ) +{ + std::vector out; + std::vector seen; + const auto add = [ & ]( NodeId n, std::string hedge ) + { + if( n >= ing.symbols.size() || ing.symbols[n].kind == SymKind::ModuleScope || ing.symbols[n].endByte <= ing.symbols[n].sigStartByte + || fold_detail::overlapsServed( ing, n, seen, /*orHolds=*/true ) ) + { + return; + } + seen.push_back( n ); + out.push_back( { n, std::move( hedge ) } ); + }; + if( br.found() && br.hops.size() >= 2 ) + { + const std::size_t last = std::min( kRouteBodyMaxHops, br.hops.size() - 2 ); // the to= row is the last one: never printed + for( std::size_t i = 0; i <= last; ++i ) + { + std::string hedge; + if( i > 0 && br.via[i] != BridgeVia::Edge ) + { + hedge = " via=\"" + std::string( kBridgeViaNames[ std::size_t( br.via[i] ) ] ) + ( bridgeViaIsSlot( br.via[i] ) ? "\" slot_callers=\"" : "\" cands=\"" ) + + std::to_string( br.fan[i] ) + "\""; + } + else if( i > 0 ) + { + hedge = chainHopHedge( ing, g, br.hops[i - 1], br.hops[i] ); // an edge row: by name / link / split / impl, as on a reached chain + } + add( br.hops[i], std::move( hedge ) ); + } + } + if( hrule.found() && hrule.rule != nullptr ) + { + std::size_t readers = 0; + for( const HandlerReader& reader : hrule.rule->readers ) + { + if( readers++ == kHandlerRuleReaders ) + { + break; // exactly the rows pathHandlerRuleXml prints + } + add( reader.fn, " via=\"name-key\"" ); + } + } + return out; +} +inline RouteBodiesXml pathRouteBodiesXml( const IngestResult& ing, std::span bodies, std::string_view rootArg ) +{ + return routeBodiesXml( ing, bodies, "route", " proven=\"0\"", kRouteBodyMax, rootArg ); +} +static_assert( kRouteBodyMaxHops == 2 && kRouteBodyMax == 4 && kHopBodyCapBytes == 4000, "the route clause spells the bounds: re-word it with the knobs" ); +inline std::string_view pathRouteBodiesLegend( bool on ) noexcept +{ + return on ? "bodies of=route proven=0: the bodies of the route this answer names but never traced: the bridge rows within 2 hops of " + "from= (never to=), then the hrd readers; at most 4 (capped=1, next= the first unshown), each whole up to 4000 bytes " + "(truncated=1 past it); a hedge, never in reachable= or hops=. " + : ""; +} + } // namespace rw diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index aad82fa28..a131f538e 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2618,10 +2618,15 @@ std::optional runPathServed( const MainDispatch& d, std::span pthOwners = rw::pathHopOwners( ing, path ); // PATH-OWNERS: n="Owner.name" per hop const bool pthHasOwner = pthMarks.owner; - // PATH-HOP-BODY (src/hopbody.h): a reached chain of <= 3 hops prints its middle hops' bodies; a longer one names the first in next= + // PATH-HOP-BODY (src/hopbody.h): a reached chain of 2 hops prints its middle hop's body (hedged when that hop is no proven + // call); a longer one names the first in next=. BODY-ON-ROUTE (src/pathgaps.h pathRouteBodies): a reachable=0 answer prints + // the bodies of the route it names (bridge rows, handler_rule readers), every one a hedge. ONE printer for both. const std::string_view pthBodyRoot = pthSingleRoot ? std::string_view( cfg.roots[0] ) : std::string_view(); const bool pthBodiesOn = !path.empty() && rw::hopBodiesOn(); - const std::string pthHopBodies = pthBodiesOn ? rw::pathHopBodiesXml( ing, rw::pathMiddleHops( ing, pthShown, served ), pthBodyRoot ) : std::string(); + const rw::RouteBodiesXml pthHopBodyEl = pthBodiesOn ? rw::pathHopBodiesXml( ing, rw::pathMiddleHops( ing, g, pthShown, served ), pthBodyRoot ) : rw::RouteBodiesXml{}; + const rw::RouteBodiesXml pthRouteEl = path.empty() && rw::hopBodiesOn() + ? rw::pathRouteBodiesXml( ing, rw::pathRouteBodies( ing, g, pthBridge, pthHandler ), pthBodyRoot ) : rw::RouteBodiesXml{}; + const std::string& pthHopBodies = pthHopBodyEl.xml; std::string pthRootAttrs = rw::pathTestTwinRootAttrs( ing, rw::pathPairsRootAttrs( ing, pthPairs, pthSingleRoot, pthRootPrefix ).rootAttrs, pthTwin, pthSingleRoot, pthRootPrefix ); const std::string pthHopNext = pthBodiesOn && pthRootAttrs.find( " next=\"" ) == std::string::npos ? rw::pathMiddleHopNext( ing, pthShown, pthBodyRoot ) : std::string(); pthRootAttrs += pthHopNext.empty() ? std::string() : rw::nextAttrXml( pthHopNext ); @@ -2630,10 +2635,12 @@ std::optional runPathServed( const MainDispatch& d, std::span 0 ) ) + rw::valueRefsDepthLegend( pthToValueRefs.depthCut.files > 0 ) ).c_str(), ( rw::pathGapsLegend( pthGaps.any(), !pthSinkCallers.empty() ) + rw::pathBridgeLegend( pthBridge ) - + rw::nearSinkLegend( pthNear ) + std::string( rw::pathHandlerRuleLegend( pthHandler ) ) + std::string( rw::pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), + + rw::nearSinkLegend( pthNear ) + std::string( rw::pathHandlerRuleLegend( pthHandler ) ) + std::string( rw::pathRouteBodiesLegend( !pthRouteEl.xml.empty() ) ) + + std::string( rw::pathGlobLegend( srcGlob || dstGlob ) ) ).c_str(), rw::graphCountFloorBrief( rw::graphGaugeClauses( g ) ).c_str(), rw::rootRelPathsLegend( pthSingleRoot ) ); rw::emitTo( stdout, " runPathServed( const MainDispatch& d, std::spannames(), pthHandler, pthSingleRoot, pthRootPrefix ) : std::string() ); // FE-B via="name", #409 via="link"/anchor="missing", attrib-multidef prov="split", PATH-OWNERS n="Owner.name": one emitter, shared with MCP path_between rw::emitPathAnswerRows( stdout, ing, g, pthPairs, path, pthOwners, pthSingleRoot, pthRootPrefix ); // TRACE-EP: pairs or today's rows, shared with MCP - rw::emitRaw( stdout, pthHopBodies.c_str() ); // PATH-HOP-BODY: after the hop rows they explain ("" when none) + rw::emitRaw( stdout, pthHopBodies.c_str() ); // PATH-HOP-BODY: after the hop rows they explain ("" when none) + rw::emitRaw( stdout, pthRouteEl.xml.c_str() ); // BODY-ON-ROUTE: after the route rows they explain (reachable=0 only) rw::emitTo( stdout, "" ); return 0; } diff --git a/test/forheadrrcheck.sh b/test/forheadrrcheck.sh index 3f0d11f0a..ebfd8b1ec 100755 --- a/test/forheadrrcheck.sh +++ b/test/forheadrrcheck.sh @@ -22,6 +22,7 @@ # rows, more distinct files in the head-8 (a file the OFF head-8 left out enters it), r=1 unchanged, files_first= == the rows whose rank changed, reading present # H2 [P] a C typedef/struct pair (pair): the same, and the fixture exercises the name rule (the model without it differs) # H3 [G] the fold serves the score top three: the first three rows of the RIPWIRE_NO_HEAD_RR=1 answer (mono, pair) +# H3b [P fix1] the loop aggregator dispatchQueuedJobs (mono) is never followed (review F6; red on 5351fbbb) # H4 [P] --json: the rows in the XML ON order, "files_first" present; H4m [P] MCP `for`: the same order, files_first= present # H5 [G] exempt, byte-identical to RIPWIRE_NO_HEAD_RR=1: the name-exact route, --no-route, a question naming src/scheduler.ts, # --token-budget=1200 @@ -110,7 +111,12 @@ elif cmd == "fold": # ON OFF — the fold's rows are the score top three # are still exactly the score top three; a follow body must name a wrapper the fold serves pieces = [attrs(x.group(1)) for x in re.finditer(r'<(?:b|skip|slice) ([^>]*)>', m.group(1))] got = [a.get("n") for a in pieces if "follow_of" not in a] - if any(a["follow_of"] not in got for a in pieces if "follow_of" in a): fail("a follow_of body names no served wrapper") + # a follow names (as Owner.name or bare) the body just before it: a ranked row, an earlier follow (a 2-level chain), or the + # slot owner's body served ahead of the fold (review F6: each of the three is a legitimate wrapper) + slot = [attrs(x.group(1)).get("n") for x in re.finditer(r']*)>', t[:t.find(']*>.*?', load(args[1]), re.S) if not mo or mo.group(0) != m.group(0): fail("the fold differs from the RIPWIRE_NO_HEAD_RR=1 answer's") @@ -168,6 +174,9 @@ run "$TMP/mono.on" "$FIX/mono" 0 --for="$Q_MONO"; run "$TMP/mono.off" "$FIX/mono if ran "$TMP/mono.on" H1 && ran "$TMP/mono.off" "H1 (twin)"; then chk "H1 one file holds most rows: the head names each file first" rr "$TMP/mono.on" "$TMP/mono.off" morefiles chk "H3 mono: the fold serves the score top three" fold "$TMP/mono.on" "$TMP/mono.off" + # fix1 (review F6): dispatchQueuedJobs is a loop aggregator (let n; for … { dispatchJobToWorker(…); n++ } return n), not a thin + # delegator — its one call is not its last statement, so it is never followed (RED on 5351fbbb, where H3 went red in CI) + if grep -q 'follow_of="[^"]*dispatchQueuedJobs"' "$TMP/mono.on"; then no "H3b mono: the loop aggregator dispatchQueuedJobs is followed"; else ok "H3b mono: the loop aggregator dispatchQueuedJobs is never followed"; fi fi run "$TMP/pair.on" "$FIX/pair" 0 --for="$Q_PAIR"; run "$TMP/pair.off" "$FIX/pair" 1 --for="$Q_PAIR" if ran "$TMP/pair.on" H2 && ran "$TMP/pair.off" "H2 (twin)"; then diff --git a/test/hopbodycheck.sh b/test/hopbodycheck.sh index 8da5db877..5ca9e81e1 100644 --- a/test/hopbodycheck.sh +++ b/test/hopbodycheck.sh @@ -38,6 +38,24 @@ # X1 [G] determinism X2 [G] xmllint well-formed X3 [G] no "--" inside a legend comment # I [optional, RIPWIRE_BASE_BIN=] RIPWIRE_NO_HOP_BODY=1 head == the base binary on every question here # +# FIX1 arms (lane path-hop-body-fix1; [M] = RED on the path-hop-body head 5351fbbb / the merge binary, the review's findings): +# H1 [M] a BY-NAME middle hop (P1.process / P2.process): its body carries via="name" cands="2" on the (F1), the hedge clause +# rides; a proven middle hop (P1's dispatch) carries none +# H2 [M] a declined iface call (ts15 m.match) or an undefined call (missingHelperCall) beside the one proven call: never followed (F2) +# H3 [M] a call bound only through a pybind11 binding (amb=, one call site): never followed — pins the ambOut guard (review mutant MA) +# H4 [M] C stores `*out = …` are statement lines: scale_and_store is not a thin wrapper (F3) +# H5 [M] a documented Python delegator is followed (F4), follow_of names the wrapper as Owner.name (F7) +# H6 [M] a class with a field initializer is never a wrapper (F5) +# H7 [M] logic beside one call (computeScoreTotal) and a .filter().map().reduce() pipeline are not delegators (F2/F6) +# H8 [M] a one-line wrapper over a > 4000 B callee: the follow is cut, truncated="1" lines= next= (kills the review's mutant MB) +# H9 [M] the answer-wide follow guard: 3 follow bodies, then naming the first unfollowed callee +# R1 [M] BODY-ON-ROUTE: a reachable=0 poster -> queue -> dispatch answer prints with post_message, +# _process_messages_loop (via="queue" cands="2"), _dispatch_message and the hrd reader _on_message (via="name-key"), CDATA = +# source; never the to= row (on_event); reachable= and hops= unchanged; the of=route clause rides (compact and full) +# R2 [M] the route spew guard: a fifth route body is cut, capped="1" next="--expand=pump.py:45:_trace_message" +# R3 [M] the hop bound: the 4-hop bridge press -> on_event prints its rows 0..2 only (press, post_message, the loop) +# R4 [G] RIPWIRE_NO_HOP_BODY=1: no route element; MCP path_between prints the CLI's route element byte for byte +# # Usage: bash test/hopbodycheck.sh [BIN] | RIPWIRE_BIN=asan/ripwire bash test/hopbodycheck.sh # BIN is $1, else RIPWIRE_BIN, else build/ripwire; the first output line names the binary used. Exits non-zero on any failure. @@ -89,6 +107,33 @@ run "$WEB" --for="$T_LISTEN" >"$TMP/f3b" run "$LOO" --for="$T_LOOSE" >"$TMP/f4" run "$LOO" --callees=handleLooseRequest >"$TMP/f4c" +# FIX1 fixtures (the route ones run from a copy outside test/: a reader under a test path is not a handler_rule reader) +HB="test/hopbodyfix" +cp -R "$HB/route" "$HB/routecap" "$TMP/" +run "$HB/byname" --path=startJ,sinkJ >"$TMP/h1" +run "$HB/byname" --path=startJ,sinkJ --legend=full >"$TMP/h1f" +run "$HB/declined" --for="how does relayWorkItem relay a work item" >"$TMP/h2a" +run "$HB/declined" --callees=relayWorkItem >"$TMP/h2c" +run "$HB/external" --for="how does relayWorkItem relay a work item" >"$TMP/h2b" +run "$HB/ffi" --for="How does relay_transform relay a value?" >"$TMP/h3" +run "$HB/ffi" --callees=relay_transform >"$TMP/h3c" +run "$HB/cstore" --for="how does scale_and_store scale and store" >"$TMP/h4" +run "$HB/pydoc" --for="how does Widget post_message post a message" >"$TMP/h5a" +run "$HB/pydoc" --for="how does Widget post_message_plain post a message" >"$TMP/h5b" +run "$HB/klass" --for="how does PanelHolder compute the panel size for the holder" >"$TMP/h6" +run "$HB/klass" --for="how does ChildRunner launch the runner" >"$TMP/h6b" +run "$HB/logic" --for="how does computeScoreTotal compute the score total" >"$TMP/h7a" +run "$HB/logic" --for="how does pipelineScore score the pipeline items" >"$TMP/h7b" +run "$HB/bigfollow" --for="how does relayWorkItem relay a work item" >"$TMP/h8" +run "$HB/many" --for="How does shipOrder ship an order?" >"$TMP/h9" +run "$HB/many" --for="How does shipOrder ship an order?" --legend=full >"$TMP/h9f" +rrun(){ local d="$1"; shift; ( cd "$TMP/$d" || exit 1; "$BIN" "$TMP/$d" --no-cache "$@" 2>/dev/null ); } +rrun route '--path=post_message,on_*' >"$TMP/r1" +rrun route '--path=post_message,on_*' --legend=full >"$TMP/r1f" +rrun routecap '--path=post_message,on_*' >"$TMP/r2" +rrun route '--path=press,on_event' >"$TMP/r3" +( cd "$TMP/route" && RIPWIRE_NO_HOP_BODY=1 "$BIN" "$TMP/route" --no-cache '--path=post_message,on_*' 2>/dev/null ) >"$TMP/r1.off" + cat > "$TMP/chk.py" <<'PY' import html, re, shlex, subprocess, sys, os T, BIN, ROOT = sys.argv[1], sys.argv[2], sys.argv[3] @@ -183,8 +228,45 @@ check("follow_of" not in off, "(F6) RIPWIRE_NO_HOP_BODY=1: no follow_of= and no strip = re.sub(r']*follow_of="[^"]*"[^>]*>.*?', '', f1, flags=re.S) mask = lambda t: re.sub(r' est_tokens="\d+"', '', re.sub(r'', '', t, flags=re.S)) check(mask(strip) == mask(off), "(F6) the --for answer minus the follow bodies == the RIPWIRE_NO_HOP_BODY=1 answer (payload, est_tokens masked)") +# FIX1 H1-H9: the review's findings +el = hopel(rd("h1")); bs = bodies(el) +check(len(bs) == 1 and bs[0][0].get("n") == "process" and bs[0][0].get("via") == "name" and bs[0][0].get("cands") == "2", "(H1) a by-name middle hop's body carries via=name cands=2: %r" % [b[0] for b in bs]) +check("b via= cands=" in legend(rd("h1")) and "no proven call" in legend(rd("h1f")), "(H1) the hedge clause rides, compact and full") +check(bodies(hopel(rd("p1"))) and "via" not in bodies(hopel(rd("p1")))[0][0] and "b via= cands=" not in legend(rd("p1")), "(H1) a proven middle hop carries no hedge and no clause") +fol = lambda t: [b[0].get("follow_of") for b in bodies(t) if "follow_of" in b[0]] +check('via="declined"' in rd("h2c") and re.search(r']*n="relayWorkItem"', rd("h2a")) and fol(rd("h2a")) == [], "(H2) a declined call beside the proven one: relayWorkItem served, never followed") +check(re.search(r']*n="relayWorkItem"', rd("h2b")) and fol(rd("h2b")) == [], "(H2) an undefined call beside the proven one: never followed") +check('graph_ambiguous="1"' in rd("h3c") and re.search(r']*n="relay_transform"', rd("h3")) and fol(rd("h3")) == [] and not re.search(r']*n="scale_impl"', rd("h3")), "(H3) a binding-only call (amb=) is never followed") +check(re.search(r']*n="scale_and_store"', rd("h4")) and fol(rd("h4")) == [], "(H4) C *out stores count as statements: no follow") +check(fol(rd("h5a")) == ["Widget.post_message"] and fol(rd("h5b")) == ["Widget.post_message_plain"], "(H5) documented and plain Python delegators both followed, follow_of=Owner.name: %r %r" % (fol(rd("h5a")), fol(rd("h5b")))) +check(re.search(r']*n="PanelHolder"', rd("h6")) and fol(rd("h6")) == [], "(H6) a class is never a wrapper") +check(fol(rd("h6b")) == ["ChildRunner.launch"], "(H5/F7) a super-override follow names its wrapper Owner.name: %r" % fol(rd("h6b"))) +check(re.search(r']*n="computeScoreTotal"', rd("h7a")) and fol(rd("h7a")) == [] and fol(rd("h7b")) == [], "(H7) logic beside one call and a builtin pipeline are not delegators: %r %r" % (fol(rd("h7a")), fol(rd("h7b")))) +fb = [b for b in bodies(rd("h8")) if "follow_of" in b[0]]; fa = fb[0][0] if fb else {} +check(fa.get("follow_of") == "relayWorkItem" and fa.get("truncated") == "1" and re.fullmatch(r'1-\d+/\d+', fa.get("lines", "")) and fa.get("next", "").startswith("--expand=src/work.ts:") and len(fb[0][1].encode()) <= 4000, "(H8) the follow of a > 4000 B callee is cut and says so: %r" % {k: fa.get(k) for k in ("follow_of", "truncated", "lines", "next")}) +h9 = rd("h9"); fc = re.search(r']*)/>', h9) +check(len(fol(h9)) == 3 and fc and attrs(fc.group(1)) == {"n": "2", "next": "--expand=src/ship.ts:19:sealOrder2"}, "(H9) 3 follow bodies, then : %r %r" % (fol(h9), fc.group(0) if fc else None)) +check("follow_cut n= next=" in legend(h9) and "follow_cut n= next=" in legend(rd("h9f")), "(H9) the follow_cut reading rides, compact and full") +# FIX1 R1-R4: BODY-ON-ROUTE +routel = lambda t: (re.search(r'', t, re.S) or re.search(r'$', t)).group(0) +r1 = rd("r1"); el = routel(r1); bs = bodies(el); rt = root(r1) +check(rt.get("reachable") == "0" and rt.get("hops") == "0" and re.search(r']*>', r1), "(R1) reachable=0 hops=0 and the bridge stay") +check([b[0].get("n") for b in bs] == ["post_message", "_process_messages_loop", "_dispatch_message", "_on_message"], "(R1) the route bodies in order: %r" % [b[0].get("n") for b in bs]) +check(el.startswith(''), "(R1) envelope proven=0 shown=4 total=4 capped=0") +hed = {b[0].get("n"): (b[0].get("via"), b[0].get("cands")) for b in bs} +check(hed.get("_process_messages_loop") == ("queue", "2") and hed.get("_on_message") == ("name-key", None) and hed.get("post_message") == (None, None) and hed.get("_dispatch_message") == (None, None), "(R1) each body carries its hop's hedge: %r" % hed) +check(all(is_source((b[0], b[1]), os.path.join(T, "route")) for b in bs) and "put_nowait" in el and "self._parent.post_message" in el, "(R1) CDATA = source text (the put, the bubble)") +check('n="on_event"' not in el, "(R1) never the to= row's body") +check("bodies of=route proven=0" in legend(r1) and "bodies of=route proven=0" in legend(rd("r1f")) and "b via= cands=" in legend(r1), "(R1) the of=route and hedge clauses ride, compact and full") +el2 = routel(rd("r2")) +check(el2.startswith('') and len(bodies(el2)) == 4, "(R2) a fifth route body is cut: capped=1, next= the first unshown") +b3 = [b[0].get("n") for b in bodies(routel(rd("r3")))] +check(b3 == ["press", "post_message", "_process_messages_loop"], "(R3) a 4-hop bridge prints rows 0..2 only: %r" % b3) +check(routel(rd("r1.off")) == "" and "bodies of=route" not in legend(rd("r1.off")), "(R4) RIPWIRE_NO_HOP_BODY=1: no route element, no clause") +for f in ("h1", "r1", "r2", "r3"): + check(subprocess.run(["xmllint", "--noout", os.path.join(T, f)], capture_output=True).returncode == 0, "(X2) %s well-formed" % f) # X3 -for f in ("p1", "p1f", "p3f", "p4", "f1", "f1f"): +for f in ("p1", "p1f", "p3f", "p4", "f1", "f1f", "h1f", "r1f", "h9f"): check(all("--" not in c for c in re.findall(r'', rd(f), re.S)), "(X3) %s: no -- inside a legend comment" % f) sys.exit(1 if fails else 0) PY @@ -218,6 +300,24 @@ for i, (a, b, f) in enumerate(cases): sys.exit(1 if bad else 0) PY +# R4: MCP path_between prints the CLI's route element byte for byte +python3 - "$BIN" "$TMP/route" "$TMP" <<'PY' || fail=1 +import json, re, subprocess, sys +binp, d, tmp = sys.argv[1], sys.argv[2], sys.argv[3] +reqs = [{"jsonrpc": "2.0", "id": 0, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "g", "version": "1"}}}, + {"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "path_between", "arguments": {"path": d, "from": "post_message", "to": "on_*"}}}] +p = subprocess.run([binp, "--mcp", "--mcp-legend=inline"], input="".join(json.dumps(r) + "\n" for r in reqs), capture_output=True, text=True, timeout=120, cwd=d) +mcp = "" +for line in p.stdout.splitlines(): + try: o = json.loads(line) + except ValueError: continue + if o.get("id") == 1: mcp = "".join(c.get("text", "") for c in o.get("result", {}).get("content", [])) +el = lambda t: (re.search(r'', t, re.S) or re.search(r'$', t)).group(0) +good = el(mcp) != "" and el(mcp) == el(open(tmp + "/r1").read()) +print(" %s (R4) MCP path_between post_message,on_*: the CLI's route element" % ("PASS" if good else "FAIL")) +sys.exit(0 if good else 1) +PY + # X1 determinism, X2 well-formed run "$WEB" --path=fetch,compose >"$TMP/x1"; run "$FOL" --for="$T_FOLLOW" >"$TMP/x2" if cmp -s "$TMP/p1" "$TMP/x1" && cmp -s "$TMP/f1" "$TMP/x2"; then echo " PASS (X1) deterministic"; else echo " FAIL (X1) two runs differ"; fail=1; fi diff --git a/test/hopbodyfix/bigfollow/src/relay.ts b/test/hopbodyfix/bigfollow/src/relay.ts new file mode 100644 index 000000000..142f85e5d --- /dev/null +++ b/test/hopbodyfix/bigfollow/src/relay.ts @@ -0,0 +1,19 @@ +import { hugeWorker } from "./work"; +export function relayWorkItem(x: number): number { + return hugeWorker(x); +} +export function relayItemCount(a: number): number { + const p = a * 2; + const q = p + 1; + return q; +} +export function workItemRelayMode(a: number): number { + const w = a * 3; + const v = w - 1; + return v; +} +export function itemWorkRelayLimit(a: number): number { + const s = a * 4; + const r = s + 2; + return r; +} diff --git a/test/hopbodyfix/bigfollow/src/work.ts b/test/hopbodyfix/bigfollow/src/work.ts new file mode 100644 index 000000000..9f5badf69 --- /dev/null +++ b/test/hopbodyfix/bigfollow/src/work.ts @@ -0,0 +1,254 @@ +export function hugeWorker(y: number): number { + let acc = y; + acc = acc + 1 * (y % 2); // step 1 of the huge worker body + acc = acc + 2 * (y % 3); // step 2 of the huge worker body + acc = acc + 3 * (y % 4); // step 3 of the huge worker body + acc = acc + 4 * (y % 5); // step 4 of the huge worker body + acc = acc + 5 * (y % 6); // step 5 of the huge worker body + acc = acc + 6 * (y % 7); // step 6 of the huge worker body + acc = acc + 7 * (y % 8); // step 7 of the huge worker body + acc = acc + 8 * (y % 9); // step 8 of the huge worker body + acc = acc + 9 * (y % 10); // step 9 of the huge worker body + acc = acc + 10 * (y % 11); // step 10 of the huge worker body + acc = acc + 11 * (y % 12); // step 11 of the huge worker body + acc = acc + 12 * (y % 13); // step 12 of the huge worker body + acc = acc + 13 * (y % 14); // step 13 of the huge worker body + acc = acc + 14 * (y % 15); // step 14 of the huge worker body + acc = acc + 15 * (y % 16); // step 15 of the huge worker body + acc = acc + 16 * (y % 17); // step 16 of the huge worker body + acc = acc + 17 * (y % 18); // step 17 of the huge worker body + acc = acc + 18 * (y % 19); // step 18 of the huge worker body + acc = acc + 19 * (y % 20); // step 19 of the huge worker body + acc = acc + 20 * (y % 21); // step 20 of the huge worker body + acc = acc + 21 * (y % 22); // step 21 of the huge worker body + acc = acc + 22 * (y % 23); // step 22 of the huge worker body + acc = acc + 23 * (y % 24); // step 23 of the huge worker body + acc = acc + 24 * (y % 25); // step 24 of the huge worker body + acc = acc + 25 * (y % 26); // step 25 of the huge worker body + acc = acc + 26 * (y % 27); // step 26 of the huge worker body + acc = acc + 27 * (y % 28); // step 27 of the huge worker body + acc = acc + 28 * (y % 29); // step 28 of the huge worker body + acc = acc + 29 * (y % 30); // step 29 of the huge worker body + acc = acc + 30 * (y % 31); // step 30 of the huge worker body + acc = acc + 31 * (y % 32); // step 31 of the huge worker body + acc = acc + 32 * (y % 33); // step 32 of the huge worker body + acc = acc + 33 * (y % 34); // step 33 of the huge worker body + acc = acc + 34 * (y % 35); // step 34 of the huge worker body + acc = acc + 35 * (y % 36); // step 35 of the huge worker body + acc = acc + 36 * (y % 37); // step 36 of the huge worker body + acc = acc + 37 * (y % 38); // step 37 of the huge worker body + acc = acc + 38 * (y % 39); // step 38 of the huge worker body + acc = acc + 39 * (y % 40); // step 39 of the huge worker body + acc = acc + 40 * (y % 41); // step 40 of the huge worker body + acc = acc + 41 * (y % 42); // step 41 of the huge worker body + acc = acc + 42 * (y % 43); // step 42 of the huge worker body + acc = acc + 43 * (y % 44); // step 43 of the huge worker body + acc = acc + 44 * (y % 45); // step 44 of the huge worker body + acc = acc + 45 * (y % 46); // step 45 of the huge worker body + acc = acc + 46 * (y % 47); // step 46 of the huge worker body + acc = acc + 47 * (y % 48); // step 47 of the huge worker body + acc = acc + 48 * (y % 49); // step 48 of the huge worker body + acc = acc + 49 * (y % 50); // step 49 of the huge worker body + acc = acc + 50 * (y % 51); // step 50 of the huge worker body + acc = acc + 51 * (y % 52); // step 51 of the huge worker body + acc = acc + 52 * (y % 53); // step 52 of the huge worker body + acc = acc + 53 * (y % 54); // step 53 of the huge worker body + acc = acc + 54 * (y % 55); // step 54 of the huge worker body + acc = acc + 55 * (y % 56); // step 55 of the huge worker body + acc = acc + 56 * (y % 57); // step 56 of the huge worker body + acc = acc + 57 * (y % 58); // step 57 of the huge worker body + acc = acc + 58 * (y % 59); // step 58 of the huge worker body + acc = acc + 59 * (y % 60); // step 59 of the huge worker body + acc = acc + 60 * (y % 61); // step 60 of the huge worker body + acc = acc + 61 * (y % 62); // step 61 of the huge worker body + acc = acc + 62 * (y % 63); // step 62 of the huge worker body + acc = acc + 63 * (y % 64); // step 63 of the huge worker body + acc = acc + 64 * (y % 65); // step 64 of the huge worker body + acc = acc + 65 * (y % 66); // step 65 of the huge worker body + acc = acc + 66 * (y % 67); // step 66 of the huge worker body + acc = acc + 67 * (y % 68); // step 67 of the huge worker body + acc = acc + 68 * (y % 69); // step 68 of the huge worker body + acc = acc + 69 * (y % 70); // step 69 of the huge worker body + acc = acc + 70 * (y % 71); // step 70 of the huge worker body + acc = acc + 71 * (y % 72); // step 71 of the huge worker body + acc = acc + 72 * (y % 73); // step 72 of the huge worker body + acc = acc + 73 * (y % 74); // step 73 of the huge worker body + acc = acc + 74 * (y % 75); // step 74 of the huge worker body + acc = acc + 75 * (y % 76); // step 75 of the huge worker body + acc = acc + 76 * (y % 77); // step 76 of the huge worker body + acc = acc + 77 * (y % 78); // step 77 of the huge worker body + acc = acc + 78 * (y % 79); // step 78 of the huge worker body + acc = acc + 79 * (y % 80); // step 79 of the huge worker body + acc = acc + 80 * (y % 81); // step 80 of the huge worker body + acc = acc + 81 * (y % 82); // step 81 of the huge worker body + acc = acc + 82 * (y % 83); // step 82 of the huge worker body + acc = acc + 83 * (y % 84); // step 83 of the huge worker body + acc = acc + 84 * (y % 85); // step 84 of the huge worker body + acc = acc + 85 * (y % 86); // step 85 of the huge worker body + acc = acc + 86 * (y % 87); // step 86 of the huge worker body + acc = acc + 87 * (y % 88); // step 87 of the huge worker body + acc = acc + 88 * (y % 89); // step 88 of the huge worker body + acc = acc + 89 * (y % 90); // step 89 of the huge worker body + acc = acc + 90 * (y % 91); // step 90 of the huge worker body + acc = acc + 91 * (y % 92); // step 91 of the huge worker body + acc = acc + 92 * (y % 93); // step 92 of the huge worker body + acc = acc + 93 * (y % 94); // step 93 of the huge worker body + acc = acc + 94 * (y % 95); // step 94 of the huge worker body + acc = acc + 95 * (y % 96); // step 95 of the huge worker body + acc = acc + 96 * (y % 97); // step 96 of the huge worker body + acc = acc + 97 * (y % 98); // step 97 of the huge worker body + acc = acc + 98 * (y % 99); // step 98 of the huge worker body + acc = acc + 99 * (y % 100); // step 99 of the huge worker body + acc = acc + 100 * (y % 101); // step 100 of the huge worker body + acc = acc + 101 * (y % 102); // step 101 of the huge worker body + acc = acc + 102 * (y % 103); // step 102 of the huge worker body + acc = acc + 103 * (y % 104); // step 103 of the huge worker body + acc = acc + 104 * (y % 105); // step 104 of the huge worker body + acc = acc + 105 * (y % 106); // step 105 of the huge worker body + acc = acc + 106 * (y % 107); // step 106 of the huge worker body + acc = acc + 107 * (y % 108); // step 107 of the huge worker body + acc = acc + 108 * (y % 109); // step 108 of the huge worker body + acc = acc + 109 * (y % 110); // step 109 of the huge worker body + acc = acc + 110 * (y % 111); // step 110 of the huge worker body + acc = acc + 111 * (y % 112); // step 111 of the huge worker body + acc = acc + 112 * (y % 113); // step 112 of the huge worker body + acc = acc + 113 * (y % 114); // step 113 of the huge worker body + acc = acc + 114 * (y % 115); // step 114 of the huge worker body + acc = acc + 115 * (y % 116); // step 115 of the huge worker body + acc = acc + 116 * (y % 117); // step 116 of the huge worker body + acc = acc + 117 * (y % 118); // step 117 of the huge worker body + acc = acc + 118 * (y % 119); // step 118 of the huge worker body + acc = acc + 119 * (y % 120); // step 119 of the huge worker body + acc = acc + 120 * (y % 121); // step 120 of the huge worker body + acc = acc + 121 * (y % 122); // step 121 of the huge worker body + acc = acc + 122 * (y % 123); // step 122 of the huge worker body + acc = acc + 123 * (y % 124); // step 123 of the huge worker body + acc = acc + 124 * (y % 125); // step 124 of the huge worker body + acc = acc + 125 * (y % 126); // step 125 of the huge worker body + acc = acc + 126 * (y % 127); // step 126 of the huge worker body + acc = acc + 127 * (y % 128); // step 127 of the huge worker body + acc = acc + 128 * (y % 129); // step 128 of the huge worker body + acc = acc + 129 * (y % 130); // step 129 of the huge worker body + acc = acc + 130 * (y % 131); // step 130 of the huge worker body + acc = acc + 131 * (y % 132); // step 131 of the huge worker body + acc = acc + 132 * (y % 133); // step 132 of the huge worker body + acc = acc + 133 * (y % 134); // step 133 of the huge worker body + acc = acc + 134 * (y % 135); // step 134 of the huge worker body + acc = acc + 135 * (y % 136); // step 135 of the huge worker body + acc = acc + 136 * (y % 137); // step 136 of the huge worker body + acc = acc + 137 * (y % 138); // step 137 of the huge worker body + acc = acc + 138 * (y % 139); // step 138 of the huge worker body + acc = acc + 139 * (y % 140); // step 139 of the huge worker body + acc = acc + 140 * (y % 141); // step 140 of the huge worker body + acc = acc + 141 * (y % 142); // step 141 of the huge worker body + acc = acc + 142 * (y % 143); // step 142 of the huge worker body + acc = acc + 143 * (y % 144); // step 143 of the huge worker body + acc = acc + 144 * (y % 145); // step 144 of the huge worker body + acc = acc + 145 * (y % 146); // step 145 of the huge worker body + acc = acc + 146 * (y % 147); // step 146 of the huge worker body + acc = acc + 147 * (y % 148); // step 147 of the huge worker body + acc = acc + 148 * (y % 149); // step 148 of the huge worker body + acc = acc + 149 * (y % 150); // step 149 of the huge worker body + acc = acc + 150 * (y % 151); // step 150 of the huge worker body + acc = acc + 151 * (y % 152); // step 151 of the huge worker body + acc = acc + 152 * (y % 153); // step 152 of the huge worker body + acc = acc + 153 * (y % 154); // step 153 of the huge worker body + acc = acc + 154 * (y % 155); // step 154 of the huge worker body + acc = acc + 155 * (y % 156); // step 155 of the huge worker body + acc = acc + 156 * (y % 157); // step 156 of the huge worker body + acc = acc + 157 * (y % 158); // step 157 of the huge worker body + acc = acc + 158 * (y % 159); // step 158 of the huge worker body + acc = acc + 159 * (y % 160); // step 159 of the huge worker body + acc = acc + 160 * (y % 161); // step 160 of the huge worker body + acc = acc + 161 * (y % 162); // step 161 of the huge worker body + acc = acc + 162 * (y % 163); // step 162 of the huge worker body + acc = acc + 163 * (y % 164); // step 163 of the huge worker body + acc = acc + 164 * (y % 165); // step 164 of the huge worker body + acc = acc + 165 * (y % 166); // step 165 of the huge worker body + acc = acc + 166 * (y % 167); // step 166 of the huge worker body + acc = acc + 167 * (y % 168); // step 167 of the huge worker body + acc = acc + 168 * (y % 169); // step 168 of the huge worker body + acc = acc + 169 * (y % 170); // step 169 of the huge worker body + acc = acc + 170 * (y % 171); // step 170 of the huge worker body + acc = acc + 171 * (y % 172); // step 171 of the huge worker body + acc = acc + 172 * (y % 173); // step 172 of the huge worker body + acc = acc + 173 * (y % 174); // step 173 of the huge worker body + acc = acc + 174 * (y % 175); // step 174 of the huge worker body + acc = acc + 175 * (y % 176); // step 175 of the huge worker body + acc = acc + 176 * (y % 177); // step 176 of the huge worker body + acc = acc + 177 * (y % 178); // step 177 of the huge worker body + acc = acc + 178 * (y % 179); // step 178 of the huge worker body + acc = acc + 179 * (y % 180); // step 179 of the huge worker body + acc = acc + 180 * (y % 181); // step 180 of the huge worker body + acc = acc + 181 * (y % 182); // step 181 of the huge worker body + acc = acc + 182 * (y % 183); // step 182 of the huge worker body + acc = acc + 183 * (y % 184); // step 183 of the huge worker body + acc = acc + 184 * (y % 185); // step 184 of the huge worker body + acc = acc + 185 * (y % 186); // step 185 of the huge worker body + acc = acc + 186 * (y % 187); // step 186 of the huge worker body + acc = acc + 187 * (y % 188); // step 187 of the huge worker body + acc = acc + 188 * (y % 189); // step 188 of the huge worker body + acc = acc + 189 * (y % 190); // step 189 of the huge worker body + acc = acc + 190 * (y % 191); // step 190 of the huge worker body + acc = acc + 191 * (y % 192); // step 191 of the huge worker body + acc = acc + 192 * (y % 193); // step 192 of the huge worker body + acc = acc + 193 * (y % 194); // step 193 of the huge worker body + acc = acc + 194 * (y % 195); // step 194 of the huge worker body + acc = acc + 195 * (y % 196); // step 195 of the huge worker body + acc = acc + 196 * (y % 197); // step 196 of the huge worker body + acc = acc + 197 * (y % 198); // step 197 of the huge worker body + acc = acc + 198 * (y % 199); // step 198 of the huge worker body + acc = acc + 199 * (y % 200); // step 199 of the huge worker body + acc = acc + 200 * (y % 201); // step 200 of the huge worker body + acc = acc + 201 * (y % 202); // step 201 of the huge worker body + acc = acc + 202 * (y % 203); // step 202 of the huge worker body + acc = acc + 203 * (y % 204); // step 203 of the huge worker body + acc = acc + 204 * (y % 205); // step 204 of the huge worker body + acc = acc + 205 * (y % 206); // step 205 of the huge worker body + acc = acc + 206 * (y % 207); // step 206 of the huge worker body + acc = acc + 207 * (y % 208); // step 207 of the huge worker body + acc = acc + 208 * (y % 209); // step 208 of the huge worker body + acc = acc + 209 * (y % 210); // step 209 of the huge worker body + acc = acc + 210 * (y % 211); // step 210 of the huge worker body + acc = acc + 211 * (y % 212); // step 211 of the huge worker body + acc = acc + 212 * (y % 213); // step 212 of the huge worker body + acc = acc + 213 * (y % 214); // step 213 of the huge worker body + acc = acc + 214 * (y % 215); // step 214 of the huge worker body + acc = acc + 215 * (y % 216); // step 215 of the huge worker body + acc = acc + 216 * (y % 217); // step 216 of the huge worker body + acc = acc + 217 * (y % 218); // step 217 of the huge worker body + acc = acc + 218 * (y % 219); // step 218 of the huge worker body + acc = acc + 219 * (y % 220); // step 219 of the huge worker body + acc = acc + 220 * (y % 221); // step 220 of the huge worker body + acc = acc + 221 * (y % 222); // step 221 of the huge worker body + acc = acc + 222 * (y % 223); // step 222 of the huge worker body + acc = acc + 223 * (y % 224); // step 223 of the huge worker body + acc = acc + 224 * (y % 225); // step 224 of the huge worker body + acc = acc + 225 * (y % 226); // step 225 of the huge worker body + acc = acc + 226 * (y % 227); // step 226 of the huge worker body + acc = acc + 227 * (y % 228); // step 227 of the huge worker body + acc = acc + 228 * (y % 229); // step 228 of the huge worker body + acc = acc + 229 * (y % 230); // step 229 of the huge worker body + acc = acc + 230 * (y % 231); // step 230 of the huge worker body + acc = acc + 231 * (y % 232); // step 231 of the huge worker body + acc = acc + 232 * (y % 233); // step 232 of the huge worker body + acc = acc + 233 * (y % 234); // step 233 of the huge worker body + acc = acc + 234 * (y % 235); // step 234 of the huge worker body + acc = acc + 235 * (y % 236); // step 235 of the huge worker body + acc = acc + 236 * (y % 237); // step 236 of the huge worker body + acc = acc + 237 * (y % 238); // step 237 of the huge worker body + acc = acc + 238 * (y % 239); // step 238 of the huge worker body + acc = acc + 239 * (y % 240); // step 239 of the huge worker body + acc = acc + 240 * (y % 241); // step 240 of the huge worker body + acc = acc + 241 * (y % 242); // step 241 of the huge worker body + acc = acc + 242 * (y % 243); // step 242 of the huge worker body + acc = acc + 243 * (y % 244); // step 243 of the huge worker body + acc = acc + 244 * (y % 245); // step 244 of the huge worker body + acc = acc + 245 * (y % 246); // step 245 of the huge worker body + acc = acc + 246 * (y % 247); // step 246 of the huge worker body + acc = acc + 247 * (y % 248); // step 247 of the huge worker body + acc = acc + 248 * (y % 249); // step 248 of the huge worker body + acc = acc + 249 * (y % 250); // step 249 of the huge worker body + acc = acc + 250 * (y % 251); // step 250 of the huge worker body + return acc; +} diff --git a/test/hopbodyfix/byname/src/p.js b/test/hopbodyfix/byname/src/p.js new file mode 100644 index 000000000..3241f4a62 --- /dev/null +++ b/test/hopbodyfix/byname/src/p.js @@ -0,0 +1,17 @@ +function sinkJ(v) { return v; } +class P1 { + process(x) { + const P1_MARK = x + 1; + return sinkJ(P1_MARK); + } +} +class P2 { + process(x) { + const P2_MARK = x + 2; + return sinkJ(P2_MARK); + } +} +function startJ(obj, x) { + return obj.process(x); +} +module.exports = { P1, P2, startJ, sinkJ }; diff --git a/test/hopbodyfix/cstore/src/c.c b/test/hopbodyfix/cstore/src/c.c new file mode 100644 index 000000000..aae26d0d9 --- /dev/null +++ b/test/hopbodyfix/cstore/src/c.c @@ -0,0 +1,29 @@ +int fill_buffer(int *out, int n); +int scale_and_store(int *out, int *aux, int n) +{ + int t = fill_buffer(out, n); + *aux = t * 3; + *out = *out + *aux; + *aux = *aux - 1; + *out = *out * 2; + return *out; +} +int store_scale_factor(int *slot, int factor) +{ + int scaled = factor * 10; + int stored = scaled + 1; + *slot = stored; + return stored; +} +int scale_store_limit(int v) +{ + int lim = v > 100 ? 100 : v; + int lo = lim < 0 ? 0 : lim; + return lo * 2; +} +int store_and_scale_twice(int v) +{ + int a = v * 2; + int b = a * 2; + return a + b; +} diff --git a/test/hopbodyfix/cstore/src/fill.c b/test/hopbodyfix/cstore/src/fill.c new file mode 100644 index 000000000..5923ad18f --- /dev/null +++ b/test/hopbodyfix/cstore/src/fill.c @@ -0,0 +1,6 @@ +int fill_buffer(int *out, int n) +{ + int FILL_MARK = n * 2; + *out = FILL_MARK; + return FILL_MARK; +} diff --git a/test/hopbodyfix/declined/src/m.ts b/test/hopbodyfix/declined/src/m.ts new file mode 100644 index 000000000..5c3271793 --- /dev/null +++ b/test/hopbodyfix/declined/src/m.ts @@ -0,0 +1,10 @@ +export interface Matcher { match(x: number): number; } +export class M1 implements Matcher { match(x: number): number { const k1 = x + 1; return k1 * 2; } } +export class M2 implements Matcher { match(x: number): number { const k2 = x + 2; return k2 * 2; } } +export class M3 implements Matcher { match(x: number): number { const k3 = x + 3; return k3 * 2; } } +export class M4 implements Matcher { match(x: number): number { const k4 = x + 4; return k4 * 2; } } +export class M5 implements Matcher { match(x: number): number { const k5 = x + 5; return k5 * 2; } } +export class M6 implements Matcher { match(x: number): number { const k6 = x + 6; return k6 * 2; } } +export class M7 implements Matcher { match(x: number): number { const k7 = x + 7; return k7 * 2; } } +export class M8 implements Matcher { match(x: number): number { const k8 = x + 8; return k8 * 2; } } +export class M9 implements Matcher { match(x: number): number { const k9 = x + 9; return k9 * 2; } } diff --git a/test/hopbodyfix/declined/src/relay.ts b/test/hopbodyfix/declined/src/relay.ts new file mode 100644 index 000000000..106fc53c0 --- /dev/null +++ b/test/hopbodyfix/declined/src/relay.ts @@ -0,0 +1,21 @@ +import { realWorker } from "./work"; +import { Matcher } from "./m"; +export function relayWorkItem(m: Matcher, x: number): number { + m.match(x); + return realWorker(x); +} +export function relayItemCount(a: number): number { + const p = a * 2; + const q = p + 1; + return q; +} +export function workItemRelayMode(a: number): number { + const w = a * 3; + const v = w - 1; + return v; +} +export function itemWorkRelayLimit(a: number): number { + const s = a * 4; + const r = s + 2; + return r; +} diff --git a/test/hopbodyfix/declined/src/work.ts b/test/hopbodyfix/declined/src/work.ts new file mode 100644 index 000000000..27797ccfd --- /dev/null +++ b/test/hopbodyfix/declined/src/work.ts @@ -0,0 +1,4 @@ +export function realWorker(y: number): number { + const REAL_WORKER_MARK = y * 11; + return REAL_WORKER_MARK; +} diff --git a/test/hopbodyfix/external/src/relay.ts b/test/hopbodyfix/external/src/relay.ts new file mode 100644 index 000000000..5a5bf56d7 --- /dev/null +++ b/test/hopbodyfix/external/src/relay.ts @@ -0,0 +1,20 @@ +import { realWorker } from "./work"; +export function relayWorkItem(x: number, sink: any): number { + missingHelperCall(x); + return realWorker(x); +} +export function relayItemCount(a: number): number { + const p = a * 2; + const q = p + 1; + return q; +} +export function workItemRelayMode(a: number): number { + const w = a * 3; + const v = w - 1; + return v; +} +export function itemWorkRelayLimit(a: number): number { + const s = a * 4; + const r = s + 2; + return r; +} diff --git a/test/hopbodyfix/external/src/work.ts b/test/hopbodyfix/external/src/work.ts new file mode 100644 index 000000000..27797ccfd --- /dev/null +++ b/test/hopbodyfix/external/src/work.ts @@ -0,0 +1,4 @@ +export function realWorker(y: number): number { + const REAL_WORKER_MARK = y * 11; + return REAL_WORKER_MARK; +} diff --git a/test/hopbodyfix/ffi/src/module.cpp b/test/hopbodyfix/ffi/src/module.cpp new file mode 100644 index 000000000..8978a371a --- /dev/null +++ b/test/hopbodyfix/ffi/src/module.cpp @@ -0,0 +1,15 @@ +// hopbodycheck H2: a C++ function Python reaches only through a pybind11 binding alias (a binding edge: amb= "verify in source"). +#include + +namespace py = pybind11; + +double scale_impl( double x ) +{ + double y = x * 2.0 + 1.0; + return y; +} + +PYBIND11_MODULE( ffimod, m ) +{ + m.def( "fast_transform", &scale_impl ); +} diff --git a/test/hopbodyfix/ffi/src/relay.py b/test/hopbodyfix/ffi/src/relay.py new file mode 100644 index 000000000..b2a668e84 --- /dev/null +++ b/test/hopbodyfix/ffi/src/relay.py @@ -0,0 +1,5 @@ +import ffimod + + +def relay_transform(value): + return ffimod.fast_transform(value) diff --git a/test/hopbodyfix/ffi/src/relays.py b/test/hopbodyfix/ffi/src/relays.py new file mode 100644 index 000000000..5355b7a82 --- /dev/null +++ b/test/hopbodyfix/ffi/src/relays.py @@ -0,0 +1,17 @@ +def relay_value_log(value): + print("relay", value) + print("value", value) + return value + + +def relay_value_count(value): + total = 0 + for v in value: + total += 1 + return total + + +def relay_value_check(value): + if value is None: + raise ValueError("relay value") + return value diff --git a/test/hopbodyfix/klass/src/cfg.ts b/test/hopbodyfix/klass/src/cfg.ts new file mode 100644 index 000000000..ca473dda9 --- /dev/null +++ b/test/hopbodyfix/klass/src/cfg.ts @@ -0,0 +1,16 @@ +export function makePanelConfig(): number { + const MAKE_CONFIG_MARK = 3; + return MAKE_CONFIG_MARK * 7; +} +export class BaseRunner { + launch(x: number): number { + const BASE_LAUNCH_MARK = x + 100; + return BASE_LAUNCH_MARK; + } +} +export class OtherRunner { + launch(x: number): number { + const OTHER_LAUNCH_MARK = x + 200; + return OTHER_LAUNCH_MARK; + } +} diff --git a/test/hopbodyfix/klass/src/panel.ts b/test/hopbodyfix/klass/src/panel.ts new file mode 100644 index 000000000..5e10328a2 --- /dev/null +++ b/test/hopbodyfix/klass/src/panel.ts @@ -0,0 +1,34 @@ +import { makePanelConfig, BaseRunner } from "./cfg"; +export class PanelHolder { + size = makePanelConfig(); +} +export class ChildRunner extends BaseRunner { + launch(x: number): number { + return super.launch(x); + } +} +export function panelHolderSize(a: number): number { + const p = a * 2; + const q = p + 1; + return q; +} +export function holderPanelWidth(a: number): number { + const w = a * 3; + const v = w - 1; + return v; +} +export function panelSizeHolder(a: number): number { + const s = a * 4; + const r = s + 2; + return r; +} +export function childRunnerLaunchCount(n: number): number { + const c = n + 1; + const d = c * 2; + return d; +} +export function runnerChildLaunchMode(n: number): number { + const e = n - 1; + const f = e * 3; + return f; +} diff --git a/test/hopbodyfix/logic/src/w.ts b/test/hopbodyfix/logic/src/w.ts new file mode 100644 index 000000000..86dfd4802 --- /dev/null +++ b/test/hopbodyfix/logic/src/w.ts @@ -0,0 +1,39 @@ +export function auditTrail(t: number): void { + const AUDIT_BODY_MARK = t; + console.log("audit", AUDIT_BODY_MARK); +} +export function computeScoreTotal(a: number, b: number): number { + const t = a * b + 7 - (a % 3) * (b % 5); + auditTrail(t); + return t * t + a - b; +} +export function transformItem(x: number): number { + const TRANSFORM_MARK = x * 4; + return TRANSFORM_MARK; +} +export function pipelineScore(items: number[]): number { + return items.filter((x) => x > 0).map((x) => transformItem(x)).reduce((p, q) => p + q, 0); +} +export class BaseRunner { + run(x: number): number { + const BASE_RUN_MARK = x + 100; + return BASE_RUN_MARK; + } +} +export class OtherRunner { + run(x: number): number { + const OTHER_RUN_MARK = x + 200; + return OTHER_RUN_MARK; + } +} +export class ChildRunner extends BaseRunner { + run(x: number): number { + return super.run(x); + } +} +export function makeWidgetConfig(): number { + return 3; +} +export class WidgetHolder { + size = makeWidgetConfig(); +} diff --git a/test/hopbodyfix/many/src/ship.ts b/test/hopbodyfix/many/src/ship.ts new file mode 100644 index 000000000..98dcead5d --- /dev/null +++ b/test/hopbodyfix/many/src/ship.ts @@ -0,0 +1,59 @@ +export function shipOrder1(order: number): number { + return packOrder1(order); +} +export function packOrder1(order: number): number { + return sealOrder1(order); +} +export function sealOrder1(order: number): number { + const SEAL_MARK_1 = order * 11; + const sealed1 = SEAL_MARK_1 + 1; + return sealed1; +} + +export function shipOrder2(order: number): number { + return packOrder2(order); +} +export function packOrder2(order: number): number { + return sealOrder2(order); +} +export function sealOrder2(order: number): number { + const SEAL_MARK_2 = order * 12; + const sealed2 = SEAL_MARK_2 + 2; + return sealed2; +} + +export function shipOrder3(order: number): number { + return packOrder3(order); +} +export function packOrder3(order: number): number { + return sealOrder3(order); +} +export function sealOrder3(order: number): number { + const SEAL_MARK_3 = order * 13; + const sealed3 = SEAL_MARK_3 + 3; + return sealed3; +} + +export function shipOrder4(order: number): number { + return packOrder4(order); +} +export function packOrder4(order: number): number { + return sealOrder4(order); +} +export function sealOrder4(order: number): number { + const SEAL_MARK_4 = order * 14; + const sealed4 = SEAL_MARK_4 + 4; + return sealed4; +} + +export function shipOrder5(order: number): number { + return packOrder5(order); +} +export function packOrder5(order: number): number { + return sealOrder5(order); +} +export function sealOrder5(order: number): number { + const SEAL_MARK_5 = order * 15; + const sealed5 = SEAL_MARK_5 + 5; + return sealed5; +} diff --git a/test/hopbodyfix/pydoc/src/q.py b/test/hopbodyfix/pydoc/src/q.py new file mode 100644 index 000000000..08c5783ef --- /dev/null +++ b/test/hopbodyfix/pydoc/src/q.py @@ -0,0 +1,5 @@ +class Queue: + def put_nowait_item(self, item): + QUEUE_PUT_MARK = item + self.items = [QUEUE_PUT_MARK] + return True diff --git a/test/hopbodyfix/pydoc/src/w.py b/test/hopbodyfix/pydoc/src/w.py new file mode 100644 index 000000000..136dcc1fd --- /dev/null +++ b/test/hopbodyfix/pydoc/src/w.py @@ -0,0 +1,36 @@ +from .q import Queue + + +class Widget: + def __init__(self): + self._queue: Queue = Queue() + + def post_message(self, message): + """Post a message to this widget. + + Args: + message: the message to post. + """ + return self._queue.put_nowait_item(message) + + def post_message_plain(self, message): + return self._queue.put_nowait_item(message) + + +def message_post_count(widget, message): + count = 0 + count += 1 + count *= 2 + return count + + +def post_message_log(message): + text = str(message) + text = text.strip() + return text + + +def widget_message_post(message): + a = len(str(message)) + b = a * 2 + return b diff --git a/test/hopbodyfix/route/case.py b/test/hopbodyfix/route/case.py new file mode 100644 index 000000000..980b8ceb8 --- /dev/null +++ b/test/hopbodyfix/route/case.py @@ -0,0 +1,9 @@ +import re + + +def camel_to_snake(name, _re=re.compile("[a-z][A-Z]")): + def repl(m): + lower, upper = m.group() + return f"{lower}_{upper.lower()}" + + return _re.sub(repl, name).lower() diff --git a/test/hopbodyfix/route/message.py b/test/hopbodyfix/route/message.py new file mode 100644 index 000000000..6ab2d25fa --- /dev/null +++ b/test/hopbodyfix/route/message.py @@ -0,0 +1,14 @@ +from case import camel_to_snake + + +class Message: + namespace = "" + + def __init_subclass__(cls, namespace=None): + super().__init_subclass__() + if namespace is not None: + name = f"{namespace}_{camel_to_snake(cls.__name__)}" + else: + parts = cls.__qualname__.rsplit(".", 2)[-2:] + name = "_".join(camel_to_snake(p) for p in parts) + cls.handler_name = f"on_{name}" diff --git a/test/hopbodyfix/route/pump.py b/test/hopbodyfix/route/pump.py new file mode 100644 index 000000000..3de1f17bf --- /dev/null +++ b/test/hopbodyfix/route/pump.py @@ -0,0 +1,43 @@ +from message import Message as Event +import queue + + +class Pump: + def __init__(self, parent=None): + self._queue = queue.Queue() + self._parent = parent + + def post_message(self, message): + if self._closed: + return False + self._queue.put_nowait(message) + return True + + def _get_message(self): + return self._queue.get() + + def _process_messages_loop(self): + while not self._closed: + message = self._get_message() + self._dispatch_message(message) + + def _dispatch_message(self, message): + if message.no_dispatch: + return + self.on_event(message) + + async def on_event(self, event: Event) -> None: + self._on_message(event) + + def _on_message(self, message): + handler_name = message.handler_name + for method in self._get_dispatch_methods(handler_name, message): + method(message) + if message.bubble and self._parent is not None: + self._parent.post_message(message) + + def _get_dispatch_methods(self, method_name, message): + for cls in self.__class__.__mro__: + method = cls.__dict__.get(method_name) + if method is not None: + yield method.__get__(self, cls) diff --git a/test/hopbodyfix/route/widgets.py b/test/hopbodyfix/route/widgets.py new file mode 100644 index 000000000..c05704316 --- /dev/null +++ b/test/hopbodyfix/route/widgets.py @@ -0,0 +1,53 @@ +from message import Message +from pump import Pump + + +def leaf(x): + return x + + +class Tick(Message): + pass + + +class Button(Pump): + class Pressed(Message): + pass + + def press(self): + self.post_message(self.Pressed()) + + +class Worker(Pump): + class Changed(Message, namespace="worker"): + pass + + def change(self): + self.post_message(Worker.Changed()) + + +class Form(Pump): + def on_button_pressed(self, m): + return leaf(m) + + def on_worker_changed(self, m): + return m + + def on_tick(self, m): + return leaf(m) + + +def log(m): + return m + + +class Clock(Pump): + def tick(self): + self.post_message(Tick()) + + def poke(self): + msg = Tick() + self.post_message(msg) + + def note(self): + log(Tick()) diff --git a/test/hopbodyfix/routecap/case.py b/test/hopbodyfix/routecap/case.py new file mode 100644 index 000000000..980b8ceb8 --- /dev/null +++ b/test/hopbodyfix/routecap/case.py @@ -0,0 +1,9 @@ +import re + + +def camel_to_snake(name, _re=re.compile("[a-z][A-Z]")): + def repl(m): + lower, upper = m.group() + return f"{lower}_{upper.lower()}" + + return _re.sub(repl, name).lower() diff --git a/test/hopbodyfix/routecap/message.py b/test/hopbodyfix/routecap/message.py new file mode 100644 index 000000000..6ab2d25fa --- /dev/null +++ b/test/hopbodyfix/routecap/message.py @@ -0,0 +1,14 @@ +from case import camel_to_snake + + +class Message: + namespace = "" + + def __init_subclass__(cls, namespace=None): + super().__init_subclass__() + if namespace is not None: + name = f"{namespace}_{camel_to_snake(cls.__name__)}" + else: + parts = cls.__qualname__.rsplit(".", 2)[-2:] + name = "_".join(camel_to_snake(p) for p in parts) + cls.handler_name = f"on_{name}" diff --git a/test/hopbodyfix/routecap/pump.py b/test/hopbodyfix/routecap/pump.py new file mode 100644 index 000000000..2f3d210d1 --- /dev/null +++ b/test/hopbodyfix/routecap/pump.py @@ -0,0 +1,47 @@ +from message import Message as Event +import queue + + +class Pump: + def __init__(self, parent=None): + self._queue = queue.Queue() + self._parent = parent + + def post_message(self, message): + if self._closed: + return False + self._queue.put_nowait(message) + return True + + def _get_message(self): + return self._queue.get() + + def _process_messages_loop(self): + while not self._closed: + message = self._get_message() + self._dispatch_message(message) + + def _dispatch_message(self, message): + if message.no_dispatch: + return + self.on_event(message) + + async def on_event(self, event: Event) -> None: + self._on_message(event) + + def _on_message(self, message): + handler_name = message.handler_name + for method in self._get_dispatch_methods(handler_name, message): + method(message) + if message.bubble and self._parent is not None: + self._parent.post_message(message) + + def _get_dispatch_methods(self, method_name, message): + for cls in self.__class__.__mro__: + method = cls.__dict__.get(method_name) + if method is not None: + yield method.__get__(self, cls) + + def _trace_message(self, message): + handler_name = message.handler_name + return handler_name diff --git a/test/hopbodyfix/routecap/widgets.py b/test/hopbodyfix/routecap/widgets.py new file mode 100644 index 000000000..c05704316 --- /dev/null +++ b/test/hopbodyfix/routecap/widgets.py @@ -0,0 +1,53 @@ +from message import Message +from pump import Pump + + +def leaf(x): + return x + + +class Tick(Message): + pass + + +class Button(Pump): + class Pressed(Message): + pass + + def press(self): + self.post_message(self.Pressed()) + + +class Worker(Pump): + class Changed(Message, namespace="worker"): + pass + + def change(self): + self.post_message(Worker.Changed()) + + +class Form(Pump): + def on_button_pressed(self, m): + return leaf(m) + + def on_worker_changed(self, m): + return m + + def on_tick(self, m): + return leaf(m) + + +def log(m): + return m + + +class Clock(Pump): + def tick(self): + self.post_message(Tick()) + + def poke(self): + msg = Tick() + self.post_message(msg) + + def note(self): + log(Tick()) From 4d59a1758d20d6e11ee8bd7671e9817aea9bbc04 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 10:52:34 -0400 Subject: [PATCH 69/95] for: the ranked caller hop's review fixes: language-neutral whole-word error matcher, by-name seeds skipped, message-anchored seed rv-upward-2hop F1-F4 and the seed rule (review section 3): - the error-task matcher reads a whole-word vocabulary (error, exception, panic, raise, throw, fail, crash, segfault, Traceback and inflections) plus error types written as one identifier (DependencyCycleError, dependency_cycle_error); it no longer fires on a "How does ..." explanation question - a seed whose every first-hop caller is matched by name alone (a Go interface method, a duck-typed Python call) is skipped - among the eligible r=1..3 narrow funnels the seed is the first whose own body holds one string literal with two task words; with none, the first eligible row, for a statement only (never a question) - static_assert ties the legend's "r=1..3" / "1 to 3" text to the two constants - gate arms R11-R16 (red on the previous head), fixtures rankgo/rankpy/rankword, limits classes, regenerated docs Co-Authored-By: Claude Sonnet 5.5 --- changelog.d/upward-2hop-ranked-caller-hop.md | 11 +- docs/LIMITS.md | 8 +- docs/limits_classes.tsv | 2 + src/forcallerhop.h | 234 +++++++++++++++++-- src/verbs_for.h | 2 +- test/forcallerhopcheck.sh | 61 +++++ test/forcallerhopfix/rankgo/store.go | 41 ++++ test/forcallerhopfix/rankpy/parsers.py | 25 ++ test/forcallerhopfix/rankword/cmd.c | 33 +++ 9 files changed, 391 insertions(+), 26 deletions(-) create mode 100644 test/forcallerhopfix/rankgo/store.go create mode 100644 test/forcallerhopfix/rankpy/parsers.py create mode 100644 test/forcallerhopfix/rankword/cmd.c diff --git a/changelog.d/upward-2hop-ranked-caller-hop.md b/changelog.d/upward-2hop-ranked-caller-hop.md index e274d952c..204611846 100644 --- a/changelog.d/upward-2hop-ranked-caller-hop.md +++ b/changelog.d/upward-2hop-ranked-caller-hop.md @@ -1,12 +1,13 @@ ### Added — a `--for` task about an error carries the callers chain of the narrow-funnel function it ranks -A `--for` task that names an error (a whole word error/exception/panic/fail/crash, e.g. an error message) but no function now +A `--for` task that names an error (a whole word of error, exception, panic, raise, throw, fail, crash, segfault, Traceback and their inflections, or an error type written as one identifier such as `DependencyCycleError` or `dependency_cycle_error`; not a "How does ..." explanation question) but no function now carries the same `` element the name-exact route carries, marked `by="rank"`, when one of the answer's r=1..3 rows is ONE function (the callers verb's own resolution, exact or no row) with one to three callers: its 1-hop callers, then their -callers as `d="2"` rows (at most 4), `next=` the whole caller radius. The seed is read from the rows `` emitted, a row -whose every caller is a test path is skipped, and the element has its own present-only legend clause saying it is a lead found +callers as `d="2"` rows (at most 4), `next=` the whole caller radius. The seed is read from the rows `` emitted; a row +whose every caller is a test path, or whose every caller was matched by name alone (an interface or duck-typed call), is +skipped; among the rest the seed is the first whose own body holds a string literal with two of the task's words (the function +that raises the message), else the first, and only for a statement, not a question. And the element has its own present-only legend clause saying it is a lead found by rank, not a function the task named. Everything outside the element and its clause is byte-identical to the answer without it (`RIPWIRE_NO_CALLER_HOP=1`); `--json`, `--signatures-only` and an explicit `--token-budget` carry none. Measured on in-sample tasks: three error-message questions (two Go large-repository rows, one C++) now name the chain above the function that -detects the error; it fires on 2 of 163, 1 of 39 and 5 of 56 questions in three in-sample sets, +0.08 %, +0.15 % and +0.35 % -bytes. Gate: `test/forcallerhopcheck.sh` (R arms). +detects the error; it fires on 0 of 163, 1 of 39 and 5 of 56 questions in three in-sample sets (0 %, +0.15 % and +0.35 % bytes). Gate: `test/forcallerhopcheck.sh` (R arms). diff --git a/docs/LIMITS.md b/docs/LIMITS.md index 11f35f2af..38b42e316 100644 --- a/docs/LIMITS.md +++ b/docs/LIMITS.md @@ -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 289 caps are classified -(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 149 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **142 of 289 caps are classified +(50 INDEXING, 46 OUTPUT, 46 BOUNDARY); the remaining 147 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 @@ -390,8 +390,8 @@ Discloses: `capped` | --- | --- | --- | --- | | `kForCallerHopDepth2Rows` | `4` | — | — | | `kForCallerHopRows` | `16` | — | — | -| `kForRankedHopHeadRows` | `3` | — | r=1..3: the emitted head the seed is read from | -| `kForRankedHopMaxCallers` | `3` | — | the narrow-funnel bar: a wider fan-in is the callers verb's page, not a chain | +| `kForRankedHopHeadRows` | `3` | BOUNDARY | r=1..3: the emitted head the seed is read from | +| `kForRankedHopMaxCallers` | `3` | BOUNDARY | the narrow-funnel bar: a wider fan-in is the callers verb's page, not a chain | ### `src/forctorhop.h` diff --git a/docs/limits_classes.tsv b/docs/limits_classes.tsv index aa18a84e6..b2181c021 100644 --- a/docs/limits_classes.tsv +++ b/docs/limits_classes.tsv @@ -41,6 +41,8 @@ kExtendedLengthThresholdUnits BOUNDARY kFieldWalkCap INDEXING kForPageRowsDefault OUTPUT kForPageUnionSymbolCap INDEXING +kForRankedHopHeadRows BOUNDARY +kForRankedHopMaxCallers BOUNDARY kHandoffCochangeRows OUTPUT kHandoffDocRows OUTPUT kHandoffNoteRows OUTPUT diff --git a/src/forcallerhop.h b/src/forcallerhop.h index 9ba2a9ac6..e4b630da7 100644 --- a/src/forcallerhop.h +++ b/src/forcallerhop.h @@ -26,8 +26,10 @@ // --json, --signatures-only, --detail=N and every other route carry none (byte-identical to before). MCP for is not changed. // RIPWIRE_NO_CALLER_HOP (any value) turns it off — the A/B handle, the RIPWIRE_NO_BODY_FOLD precedent. +#include "clones.h" // scanCodeTokens / CodeScanOptions — the ranked seed reads a body's string literals #include "callhierarchy.h" // oneHopCallerRows — the callers verb's rows; rowCallSites / callSitesAttrXml via editcheck.h #include "defattrib.h" // attributeOneHopAnswer — via="name" on a row, as the callers verb decides it +#include "docparse.h" // detail::readWholeFile — the ranked seed reads the candidate's file #include "filter.h" // pathTierOf — the ranked seed's noise gate (every first-hop caller a test or bench path) #include "graph.h" // viaNameAttr #include "infra/namesplit.h" // isIdentChar — the ranked seed's task-word fold @@ -43,9 +45,11 @@ #include #include #include +#include #include #include #include +#include #include namespace rw @@ -147,29 +151,196 @@ inline std::vector forCallerHopTargets( const IngestResult& ing, const G // (ORCH reports/upward-2hop.md Stage 0): cockroach-01 execStmtInAbortedState (r=3) -> execStmt, ninja-06 VerifyDAG (r=2) -> // RecomputeNodeDirty -> RecomputeDirty; on the 60 S1 --for questions the same seed without the error gate fired on 35 rows, mostly // on a narrow-funnel row the question was not about (noise), so the task gate is part of the rule, not a tuning. -// The seed is the FIRST of the rows emitted at r=1..kForRankedHopHeadRows that is ONE function by the callers verb's own +// The seed is one of the rows emitted at r=1..kForRankedHopHeadRows that is ONE function by the callers verb's own // resolution (forCallerHopTargets, the same exact-or-no-row rule) with 1..kForRankedHopMaxCallers 1-hop caller rows; the element is -// renderForCallerHop's, marked by="rank". NOISE GATE: no element when every first-hop caller is a test or bench path (the chain -// already ends there). +// renderForCallerHop's, marked by="rank". NOISE GATES: no element when every first-hop caller is a test or bench path (the chain +// already ends there), or when every first-hop caller was matched by name alone (a Go interface method, a duck-typed Python call: +// the second hop would inherit an unproven receiver unhedged). WHICH eligible row: the first whose own body holds a string literal +// with >= 2 of the task's words (the function that raises the message, fix-1 F5); with none, the first eligible row, for a +// statement only — a question ("Where is X raised?") with no message-bearing row gets no element. inline constexpr std::size_t kForRankedHopHeadRows = 3; // r=1..3: the emitted head the seed is read from inline constexpr std::size_t kForRankedHopMaxCallers = 3; // the narrow-funnel bar: a wider fan-in is the callers verb's page, not a chain +// The ranked legends below state both bars as text ("r=1..3", "1 to 3 callers", "1-3 callers"): a constant edited alone would +// leave the clause stale with no gate noticing, so the text and the constants are tied here (edit both, or neither). +static_assert( kForRankedHopHeadRows == 3 && kForRankedHopMaxCallers == 3, + "kForRankedCallerHopLegend / kForCompactRankedCallerHopLegend state r=1..3 and 1 to 3 callers — edit the clauses with the constants" ); -// The task names an error: a whole word error(s) / exception(s) / panic(s) / fail(s|ed|ure|ures) / crash(es), ASCII case-folded. -inline bool forRankedCallerHopTask( std::string_view task ) +// The task names an error or a failure, in any language's vocabulary, as a WHOLE WORD (ASCII case-folded; "failover" is not "fail", +// "terror" is not an error): error(s)/errored, exception(s), panic(s)/panicked, Traceback, raise(s)/raised, throw(s)/threw/thrown, +// segfault, crash(es)/crashed, fail(s)/failed/failing/failure(s) — or an error TYPE written as one identifier whose last subtoken +// is error/exception (DependencyCycleError, dependency_cycle_error, ValueError, IOException). The words are lowercased AFTER the +// identifier split, so a CamelCase tail is not lost. Not on an EXPLANATION question ("How does X turn an error into a response?"): +// that asks for a mechanism, not for who reaches one error (measured: koa-12, starlette-12). Inflections the table does not list +// (`errored out`, `Err`, `returns nil`) are a floor, in the safe direction: no element. +inline constexpr std::string_view kForErrorWords[] = { + "error", "errors", "errored", "exception", "exceptions", "panic", "panics", "panicked", "traceback", "raise", "raises", "raised", + "throw", "throws", "threw", "thrown", "segfault", "segfaults", "crash", "crashes", "crashed", "fail", "fails", "failed", "failing", + "failure", "failures" }; + +// The task's ident-run words in order, case kept (camel tails are the error-type test's input): every non-identifier byte is a space. +inline std::vector forTaskIdentWords( std::string_view task ) { - static constexpr std::string_view kWords[] = { "error", "errors", "exception", "exceptions", "panic", "panics", "fail", - "fails", "failed", "failure", "failures", "crash", "crashes" }; std::string folded( task ); for( char& c : folded ) { - c = !namesplit::isIdentChar( c ) ? ' ' : ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : c; + c = namesplit::isIdentChar( c ) ? c : ' '; + } + std::vector out; + for( const std::string_view w : splitRouteWords( folded ) ) + { + out.emplace_back( w ); + } + return out; +} + +inline std::string forLowerAscii( std::string_view w ) +{ + std::string out( w ); + for( char& c : out ) + { + c = ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : c; + } + return out; +} + +// "How does …", "Explain …", "Describe …": a question about a mechanism (the first word, case-folded). +inline bool forTaskExplains( std::string_view task ) +{ + const std::vector w = forTaskIdentWords( task ); + if( w.empty() ) + { + return false; } - const std::vector words = splitRouteWords( folded ); - return std::ranges::any_of( words, []( std::string_view w ) { return std::ranges::find( kWords, w ) != std::end( kWords ); } ); + const std::string first = forLowerAscii( w.front() ); + return first == "how" || first == "explain" || first == "describe"; } -inline std::vector forRankedCallerHopTargets( const IngestResult& ing, const Graph& g, std::span rankedHead ) +// Any question: a `?`, or a question-opening first word. The seed fallback (no message-bearing row) is for statements only. +inline bool forTaskIsQuestion( std::string_view task ) { + if( task.find( '?' ) != std::string_view::npos ) + { + return true; + } + const std::vector w = forTaskIdentWords( task ); + if( w.empty() ) + { + return false; + } + const std::string first = forLowerAscii( w.front() ); + static constexpr std::string_view kOpeners[] = { "how", "what", "why", "where", "which", "when", "who", "explain", "describe", "does", "do", "is", "are", "can" }; + return std::ranges::find( kOpeners, std::string_view( first ) ) != std::end( kOpeners ); +} + +inline bool forRankedCallerHopTask( std::string_view task ) +{ + if( forTaskExplains( task ) ) + { + return false; + } + std::vector parts; + for( const std::string& w : forTaskIdentWords( task ) ) + { + const std::string lower = forLowerAscii( w ); + if( std::ranges::find( kForErrorWords, std::string_view( lower ) ) != std::end( kForErrorWords ) ) + { + return true; + } + parts.clear(); + subtokens( w, parts ); // DependencyCycleError / dependency_cycle_error -> [dependency, cycle, error] + if( parts.size() >= 2 && ( parts.back() == "error" || parts.back() == "exception" ) ) + { + return true; + } + } + return false; +} + +// UPWARD-2HOP F5 — THE MESSAGE-ANCHORED SEED. The task's content words: the lowercased subtokens of its identifier runs, two bytes +// or more, no route stopword, no plain English function word, not error/errors (every task here has one). Distinct. +inline std::vector forTaskContentWords( std::string_view task ) +{ + static constexpr std::string_view kPlain[] = { "and", "or", "be", "it", "as", "at", "by", "from", "that", "this", "was", "has", "have", + "into", "when", "why", "who", "can", "error", "errors" }; + std::vector out; + std::vector parts; + for( const std::string& w : forTaskIdentWords( task ) ) + { + parts.clear(); + subtokens( w, parts ); + for( std::string& p : parts ) + { + if( !isRouteStopword( p ) && std::ranges::find( kPlain, std::string_view( p ) ) == std::end( kPlain ) + && std::ranges::find( out, p ) == out.end() ) + { + out.push_back( std::move( p ) ); + } + } + } + return out; +} + +inline constexpr std::size_t kForRankedMessageWords = 2; // task content words ONE string literal of the seed's own body must hold + +// Does [sigStartByte, endByte) of `s` in `src` hold ONE string literal with >= kForRankedMessageWords distinct task content words? +// Read with THE one code scanner (clones.h scanCodeTokens: comments dropped, per-language quote rules); a backtick raw string is not +// read (a floor: such a seed falls to the statement-only fallback). +inline bool forBodyHoldsTaskMessage( const std::string& src, const Symbol& s, const std::vector& words ) +{ + bool found = false; + std::vector parts; + std::vector hit; + scanCodeTokens( src, s.sigStartByte, s.endByte, + CodeScanOptions{ .stripHashComments = usesHashLineComments( s.lang ), .munchMultiByteOperators = false, + .singleQuoteStrings = usesSingleQuoteStrings( s.lang ), .slashComments = s.lang != Lang::Python, + .tripleQuoteStrings = s.lang == Lang::Python }, + [ & ]( std::string_view token, CodeTokenKind kind ) + { + if( found || kind != CodeTokenKind::String ) + { + return; + } + hit.assign( words.size(), 0 ); + std::size_t distinct = 0; + std::size_t at = 0; + while( at < token.size() && !found ) + { + while( at < token.size() && !namesplit::isIdentChar( token[at] ) ) + { + ++at; + } + const std::size_t begin = at; + while( at < token.size() && namesplit::isIdentChar( token[at] ) ) + { + ++at; + } + parts.clear(); + subtokens( token.substr( begin, at - begin ), parts ); + for( const std::string& p : parts ) + { + const auto it = std::ranges::find( words, p ); + if( it != words.end() && hit[ std::size_t( it - words.begin() ) ] == 0 ) + { + hit[ std::size_t( it - words.begin() ) ] = 1; + found = found || ++distinct >= kForRankedMessageWords; + } + } + } + } ); + return found; +} + +// The seed: the first of the rows emitted at r=1..kForRankedHopHeadRows that is ONE function by the callers verb's own +// resolution (forCallerHopTargets) with 1..kForRankedHopMaxCallers first-hop rows, not every one a test or bench path, and not +// every one matched by name alone (F2: the d=2 rows would inherit an unproven receiver — a Go interface method, a duck-typed +// Python call) — and, among those, PREFERRING the first whose own body holds a string literal with >= 2 task words (F5: the +// function that raises the message). With none, today's first eligible row, but only for a statement, never a question. +inline std::vector forRankedCallerHopTargets( const IngestResult& ing, const Graph& g, std::span rankedHead, + std::string_view task ) +{ + const std::vector words = forTaskContentWords( task ); + std::vector fallback; + std::vector> files; // at most kForRankedHopHeadRows files read, once each for( std::size_t i = 0; i < rankedHead.size() && i < kForRankedHopHeadRows; ++i ) { const NodeId id = rankedHead[i]; @@ -194,9 +365,40 @@ inline std::vector forRankedCallerHopTargets( const IngestResult& ing, c { continue; } - return targets; + const OneHopAttribution attr = attributeOneHopAnswer( ing, g, targets, first, /*towardMatches=*/true ); + bool allByName = true; + for( std::size_t k = 0; k < first.size() && allByName; ++k ) + { + allByName = attr.nameOnly( k ); + } + if( allByName ) + { + continue; + } + if( fallback.empty() ) + { + fallback = targets; + } + if( words.size() < kForRankedMessageWords ) + { + continue; + } + for( const NodeId t : targets ) + { + const Symbol& s = ing.symbols[t]; + auto f = std::ranges::find( files, s.fileId, &std::pair::first ); + if( f == files.end() ) + { + files.emplace_back( s.fileId, docparse::detail::readWholeFile( diskPath( ing, s.fileId ) ).value_or( std::string() ) ); + f = std::prev( files.end() ); + } + if( forBodyHoldsTaskMessage( f->second, s, words ) ) + { + return targets; + } + } } - return {}; + return forTaskIsQuestion( task ) ? std::vector{} : fallback; } // The hop's legend clauses, present-only beside the element: the full dialect's and the compact dialect's. No "--": they ride @@ -212,14 +414,14 @@ inline constexpr std::string_view kForCompactCallerHopLegend = // UPWARD-2HOP: the ranked seed's clauses (by="rank" on the element). Self-contained — the named-route clause never rides with them. inline constexpr std::string_view kForRankedCallerHopLegend = - "; callers by=rank of=: the task names an error, not a function, so of= is the first r=1..3 row that is one function with 1 to " - "3 callers (found by rank, a lead to check), and the chain above it follows: s t= n= sc= p=file:line rows are its 1-hop callers " + "; callers by=rank of=: the task names an error, not a function, so of= is an r=1..3 row that is one function with 1 to " + "3 callers (the first whose body holds the task's words in one string, else the first; found by rank, a lead to check), and the chain above it follows: s t= n= sc= p=file:line rows are its 1-hop callers " "(n=Owner.name with no sc= on a member whose owner the index defines; sites_at= their calls of it as file:line, sites_total= when " "that list was cut; via=name: matched by name alone, the receiver unproven), shown= of total=; then d=2 rows, callers of the " "shown callers by rank, d2_shown= of d2=; capped=1 when either hop was cut; counts are floors (resolved calls only); next= lists " "the whole caller radius"; inline constexpr std::string_view kForCompactRankedCallerHopLegend = - "; callers by=rank of=: an error task's first r=1..3 row that is one function with 1-3 callers (a lead by rank): its callers, s t= n= " + "; callers by=rank of=: an error task's r=1..3 row that is one function with 1-3 callers (the first with the task's words in a body string, else the first; a lead by rank): its callers, s t= n= " "sc= p=file:line (n=Owner.name folds sc= on a member; sites_at= its call sites, sites_total= if cut; via=name by name only), " "shown= of total=; d=2 rows: their callers, d2_shown= of d2=; capped=1 if cut; floors; next= the radius"; diff --git a/src/verbs_for.h b/src/verbs_for.h index c0cd78a9c..cdd50d184 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2587,7 +2587,7 @@ inline rw::CallerHopRender rankedCallerHop( const RankedHopPosture& p, const rw: { return held; } - const std::vector ids = rw::forRankedCallerHopTargets( ing, g, shownSigIds ); + const std::vector ids = rw::forRankedCallerHopTargets( ing, g, shownSigIds, cfg.forTask ); return ids.empty() ? held : rw::renderForCallerHop( ing, g, ids, rootArg, /*ranked=*/true ); } diff --git a/test/forcallerhopcheck.sh b/test/forcallerhopcheck.sh index ecd5379e3..24e427209 100755 --- a/test/forcallerhopcheck.sh +++ b/test/forcallerhopcheck.sh @@ -35,6 +35,13 @@ # R4 [G] every first-hop caller a test path: none R5 [G] the same head, no error word: none R6 [G] the narrow funnel at r>3: none # R7 [G] prefix identity vs RIPWIRE_NO_CALLER_HOP=1 (both legends) R8 [P] est_tokens prices it # R9 [G] --json / --signatures-only / --token-budget=3000: byte-identical to the hop-off answer R10 [P] first-hop rows = --callers rows +# R11 [P] fix round 1 (F4): each error word of the language-neutral vocabulary (panicked, Traceback, raises, throws, segfault, crashed, +# failing, ... and an error TYPE as one identifier: DependencyCycleError, dependency_cycle_error, ValueError) fires the hop +# R12 [G] WHOLE WORD: failover, throwaway, terror, ErrorKind, raiser, crashpad, failsafe, errorless carry none (kills a substring match) +# R13 [P] an explanation question (How does ... / Explain ... / Describe ...) carries none; [G] a where-question with a message-bearing row does +# R14 [P] F5: the seed is the narrow funnel whose own body holds a string literal with >= 2 task words (rankword: lookup_word r=3, not +# command_not_found_hook r=1); R15 [P] a question with no message-bearing narrow funnel carries none (a statement keeps R3's fallback) +# R16 [P] F2: a seed whose only first-hop caller is a by-name match (Go interface, Python duck typing) carries none (rankgo, rankpy) # X1 [G] determinism X2 [G] xmllint well-formed X3 [G] no "--" inside a legend comment # # Usage: bash test/forcallerhopcheck.sh [BIN] | RIPWIRE_BIN=asan/ripwire bash test/forcallerhopcheck.sh @@ -320,6 +327,60 @@ sys.exit(0 if first and all(k in verb for k in first) else 1) PY verdict $? "(R10) every first-hop row of the ranked hop is --callers=verify_dag's own row" "(R10) a ranked first-hop row differs from --callers=verify_dag" +# ── R11-R16: UPWARD-2HOP fix round 1 (rv-upward-2hop F1-F3, F5; the error-task matcher F4) ────────────────────────────────────── +# R11-R13 pin the task matcher: a WHOLE WORD of the language-neutral error vocabulary (or an error TYPE written as one identifier), +# not a substring, and not an EXPLANATION question. Each task rides the same fixture whose r=1 narrow funnel is verify_dag, so a +# missing hop is the matcher's verdict, never the route's (the premise is re-checked per task). +rk_hop(){ # $1 task -> prints "R" when verify_dag is r=1 on a ranked route, then "H" when the ranked hop rode + run "$FR" --for="$1" >"$TMP/rk" + grep -q 'route="subtoken+body' "$TMP/rk" && grep -q ']*n="verify_dag"[^>]*r="1"' "$TMP/rk" && printf R + grep -q '= 2 task words (lookup_word: "command %d not +# found"), not the r=1 funnel that merely carries the words in its name (command_not_found_hook). Statement and question alike. +FW="test/forcallerhopfix/rankword" +for q in "command not found error" "Where is the command not found error raised?"; do + run "$FW" --for="$q" >"$TMP/r14" + if grep -q ']*n="command_not_found_hook"[^>]*r="1"' "$TMP/r14" && grep -q ']*n="lookup_word"[^>]*r="3"' "$TMP/r14"; then + grep -q '"$TMP/r15" +if [ -s "$TMP/r15" ] && grep -q ']*n="load_one"[^>]*r="2"' "$TMP/r15" && ! grep -q '"$TMP/r16" + run "test/forcallerhopfix/$d" --callers="$of" >"$TMP/r16c" + if [ -s "$TMP/r16" ] && grep -q 'route="subtoken+body' "$TMP/r16" && grep -q ' via="name"' "$TMP/r16c"; then + ! grep -q ' + +int command_not_found_hook( int w ) +{ + return w + 1; +} + +int hook_user( int w ) +{ + return command_not_found_hook( w ); +} + +int lookup_word( int w ) +{ + if( w < 0 ) + { + fprintf( stderr, "command %d not found\n", w ); + return -1; + } + return 0; +} + +int run_word( int w ) +{ + return lookup_word( w ); +} + +int main( void ) +{ + return hook_user( 1 ) + run_word( 2 ); +} From b8d4382a4a16446c362a0846e05d43745d1ec225 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 11:20:01 -0400 Subject: [PATCH 70/95] path-hop-body fix1: thinDelegate's statement count in its own helper; pathRouteBodies renders its element Quality-delta: thinDelegate complexity 37 -> split (bodyStatementLines), the one-line route wrapper folded into pathRouteBodies, the cstore fixture's second function no longer a clone of a slice-flow fixture. No output change (textual-11 byte-identical, hopbodycheck 93 PASS). Co-Authored-By: Claude Opus 5.5 --- src/hopbody.h | 81 ++++++++++++++++------------------ src/mcpverbs.h | 2 +- src/pathgaps.h | 9 ++-- src/verbs_navigate.h | 2 +- test/hopbodyfix/cstore/src/c.c | 4 +- 5 files changed, 45 insertions(+), 53 deletions(-) diff --git a/src/hopbody.h b/src/hopbody.h index 4850a1ad2..87697862c 100644 --- a/src/hopbody.h +++ b/src/hopbody.h @@ -154,6 +154,41 @@ inline std::string_view pythonDocstringQuote( std::string_view t ) noexcept return {}; } +// The statement lines of a body (isStatementLine), a leading Python docstring not counted (F4: only the FIRST statement can be one); +// counting stops past `stopAbove` (the caller needs no exact count beyond its bound). +inline std::size_t bodyStatementLines( std::string_view body, Lang lang, std::size_t stopAbove ) noexcept +{ + std::size_t statements = 0; + std::string_view docClose; // inside a leading multi-line docstring: its closing quote + bool docAllowed = lang == Lang::Python; + while( !body.empty() && statements <= stopAbove ) + { + const std::size_t nl = body.find( '\n' ); + const std::string_view line = body.substr( 0, nl ); + body = nl == std::string_view::npos ? std::string_view() : body.substr( nl + 1 ); + if( !docClose.empty() ) + { + docClose = line.find( docClose ) != std::string_view::npos ? std::string_view() : docClose; + continue; + } + if( !isStatementLine( line, lang ) ) + { + continue; + } + const std::string_view q = docAllowed ? pythonDocstringQuote( line.substr( line.find_first_not_of( " \t\r" ) ) ) : std::string_view(); + docAllowed = false; + if( q.empty() ) + { + ++statements; + continue; + } + const std::string_view tr = line.substr( line.find_first_not_of( " \t\r" ) ); + const std::size_t open = tr.find( q ); + docClose = q.size() == 3 && tr.find( q, open + q.size() ) == std::string_view::npos ? q : std::string_view(); + } + return statements; +} + // The call sites W's own text holds: every call reference inside its span (resolved, by name, declined, external or undefined alike: // a call the graph holds no edge for is still a call), nested lambdas included, and the start byte of the first. Only a Call-role use // counts (not a read, a type, an inherit, a doc link or a HAS-A member). One scan of ing.references, run last, after the cheap tests. @@ -245,52 +280,14 @@ inline NodeId thinDelegate( const IngestResult& ing, const Graph& g, NodeId id ) { return kNoNode; } - std::string_view body = std::string_view( *text ).substr( s.sigEndByte, s.endByte - s.sigEndByte ); - std::size_t statements = 0; - std::string_view docClose; // inside a leading multi-line Python docstring: its closing quote - bool docAllowed = s.lang == Lang::Python; // only the FIRST statement can be the docstring - while( !body.empty() ) - { - const std::size_t nl = body.find( '\n' ); - const std::string_view line = body.substr( 0, nl ); - body = nl == std::string_view::npos ? std::string_view() : body.substr( nl + 1 ); - if( !docClose.empty() ) - { - if( line.find( docClose ) != std::string_view::npos ) - { - docClose = {}; - } - continue; - } - if( !isStatementLine( line, s.lang ) ) - { - continue; - } - if( docAllowed ) - { - docAllowed = false; - const std::string_view tr = line.substr( line.find_first_not_of( " \t\r" ) ); - if( const std::string_view q = pythonDocstringQuote( tr ); !q.empty() ) - { - const std::size_t open = tr.find( q ); - if( q.size() == 3 && tr.find( q, open + q.size() ) == std::string_view::npos ) - { - docClose = q; - } - continue; - } - } - if( ++statements > kThinBodyLines ) - { - return kNoNode; - } - } - if( statements == 0 ) + const std::string_view body = std::string_view( *text ).substr( s.sigEndByte, s.endByte - s.sigEndByte ); + const std::size_t statements = bodyStatementLines( body, s.lang, kThinBodyLines ); + if( statements == 0 || statements > kThinBodyLines ) { return kNoNode; } const SpanCalls calls = callSitesInSpan( ing, s ); - if( calls.count != 1 || !callIsLastStatement( std::string_view( *text ).substr( s.sigEndByte, s.endByte - s.sigEndByte ), s.sigEndByte, calls.firstByte, s.lang ) ) + if( calls.count != 1 || !callIsLastStatement( body, s.sigEndByte, calls.firstByte, s.lang ) ) { return kNoNode; // F2: one call site of ANY kind in W's text; F6: and it is W's last statement (W hands off, it does not compute) } diff --git a/src/mcpverbs.h b/src/mcpverbs.h index e4c3837bc..e9937d5d0 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -3835,7 +3835,7 @@ inline std::optional pathText( const std::string& root, const std:: // F8: no served set here — the MCP `for` answer's graph-verb block calls this twin by name only, so a middle hop that `for` // answer already carries prints a second copy on MCP (the CLI passes its served bodies: runPathServed); a known, bounded divergence const RouteBodiesXml ptHopBodyEl = ptBodiesOn ? pathHopBodiesXml( ing, pathMiddleHops( ing, g, ptShown, {} ), ptBodyRoot ) : RouteBodiesXml{}; - const RouteBodiesXml ptRouteEl = pth.empty() && hopBodiesOn() ? pathRouteBodiesXml( ing, pathRouteBodies( ing, g, ptBridge, ptHandler ), ptBodyRoot ) : RouteBodiesXml{}; // BODY-ON-ROUTE, as on the CLI + const RouteBodiesXml ptRouteEl = pth.empty() && hopBodiesOn() ? pathRouteBodies( ing, g, ptBridge, ptHandler, ptBodyRoot ) : RouteBodiesXml{}; // BODY-ON-ROUTE, as on the CLI const std::string& ptHopBodies = ptHopBodyEl.xml; std::string ptRootAttrs = pathTestTwinRootAttrs( ing, pathPairsRootAttrs( ing, ptPairs, ptSingleRoot, ptRootPrefix ).rootAttrs, ptTwin, ptSingleRoot, ptRootPrefix ); const std::string ptHopNext = ptBodiesOn && ptRootAttrs.find( " next=\"" ) == std::string::npos ? pathMiddleHopNext( ing, ptShown, ptBodyRoot ) : std::string(); diff --git a/src/pathgaps.h b/src/pathgaps.h index e8fefa1a0..7198251b0 100644 --- a/src/pathgaps.h +++ b/src/pathgaps.h @@ -1804,7 +1804,8 @@ inline std::string_view pathHandlerRuleLegend( const PathHandlerRule& hrule ) no inline constexpr std::size_t kRouteBodyMaxHops = 2; // bridge rows 0..2 (from= and the next two), never the to= row inline constexpr std::size_t kRouteBodyMax = 4; // spew guard: bodies per answer; past it capped="1" next= the first unshown -inline std::vector pathRouteBodies( const IngestResult& ing, const Graph& g, const PathBridge& br, const PathHandlerRule& hrule ) +// The route's element (routeBodiesXml, the one printer); "" when the answer names no route body. +inline RouteBodiesXml pathRouteBodies( const IngestResult& ing, const Graph& g, const PathBridge& br, const PathHandlerRule& hrule, std::string_view rootArg ) { std::vector out; std::vector seen; @@ -1848,11 +1849,7 @@ inline std::vector pathRouteBodies( const IngestResult& ing, const Gr add( reader.fn, " via=\"name-key\"" ); } } - return out; -} -inline RouteBodiesXml pathRouteBodiesXml( const IngestResult& ing, std::span bodies, std::string_view rootArg ) -{ - return routeBodiesXml( ing, bodies, "route", " proven=\"0\"", kRouteBodyMax, rootArg ); + return routeBodiesXml( ing, out, "route", " proven=\"0\"", kRouteBodyMax, rootArg ); } static_assert( kRouteBodyMaxHops == 2 && kRouteBodyMax == 4 && kHopBodyCapBytes == 4000, "the route clause spells the bounds: re-word it with the knobs" ); inline std::string_view pathRouteBodiesLegend( bool on ) noexcept diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index a131f538e..ffcc994db 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2625,7 +2625,7 @@ std::optional runPathServed( const MainDispatch& d, std::span Date: Wed, 28 Oct 2026 11:21:46 -0400 Subject: [PATCH 71/95] path-hop-body fix1: ack the three intended internal contract changes renderFollows (the answer-wide follow budget), isStatementLine (the language for the comment rule) and pathMiddleHops (the graph for the hop hedge): every caller updated in the same change. Co-Authored-By: Claude Opus 5.5 --- .ripwire_quality_acks | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.ripwire_quality_acks b/.ripwire_quality_acks index 55160fb30..29dc78434 100644 --- a/.ripwire_quality_acks +++ b/.ripwire_quality_acks @@ -121,6 +121,7 @@ ack api-surface 8a92173ded649e17 14 cid=f76e97c84ac7c168 by=src/* lane 2 of the ack api-surface 8d58de9bb922f582 4 T1 completeness claims (complete= on grep/whereis): the +1 on streamBlobs is the deliberate DEFAULTED StreamBlobStats* param (null-object sink inside, no per-site null test; every existing caller byte-identical) so whereis can prove its scan exhaustive before claiming; cx/LOC on streamBlobs/computeWhereis/writeWhereisPage/emitGrepReport is the claim computation plus its in-band legend (the honesty text IS the feature); churn=self on those plus grepCollect/dispatchMcpLine is this lane own edit window. Gated red-first by test/completecheck.sh (24 arms, 10 red pre-fix; mutation arms force cap/offset/budget/unreadable-file/regex-mode/oversized-blob and assert the attribute VANISHES); full plain suite green, 21 touched-family gates green under ASan+LSan, determinism x3, xmllint clean ack api-surface 8fa8514c421e478d 4 cid=7826a06c6d682763 now=4 was=3 facet=contract-change p=src/editcheck.h:833 train 30 fcall-forward nit N1 (d62c00e0): ONE trailing span parameter, the graph's keptForwards, so this call-site reader applies the one model.h forwardCounts rule (--uses' own) instead of counting a forward buildGraph dropped; every caller passes g.keptForwards in the same commit; red-first in test/fcallforwardcheck.sh S1-S3. ack api-surface 925094be92085dae 3 cid=714cea1e1b31a1ae A6: rollbackMessage gained a 'cause' parameter one commit after this lane introduced it (57fe5fc). It is a header-inline helper in namespace rw::editplan with two callers, both in the same function in the same file; no consumer outside this lane ever saw the 2-arg form. The parameter is what lets the concurrent-write abort reuse the rollback disposition wording instead of growing a second copy of it. +ack api-surface 92784a33434e383d 2 cid=e19f01572dacdb87 now=2 was=1 facet=contract-change p=src/hopbody.h:121 fix1: intended internal signature changes (graph for the hop hedge, language for the comment rule, the answer-wide follow budget); every caller updated in the same commit ack api-surface 92ac9caf38b8aab0 4 root-relative coverage round (verifier E1-E4 + two gaps the widened gate exposed, 2026-08-19): every gating row here is the SAME three-line pattern every verb in the original root-relative round already pays — a singleRoot bool, a rootPrefix, a rootAttr, and one ternary per path emission (the shape clones/prcontext/situ/mcp-path all carry verbatim). --tree (runStructureText) +8 ccx / +13 LOC and --quality-panel (writePanelReport) +4 ccx / +12 LOC are those lines plus the finding comment; forTaskText and packTaskBundleText are argument threading only. packBodiesJson api-surface 3 to 4 params is a DELIBERATE contract change: a defaulted trailing rootArg, identical in name, position and default to the one packSignatures/packBodies/packLego/packOutline already take, so the emitter family stays one shape and every existing call site is unaffected. churn=self/ambient is this change's own edit window. Payoff: 1340 absolute paths removed from four surfaces (tree 1212, analyze 85, panel 40, mcp-for 3) plus 5 in the pack-task JSON tail that the gate had been scoring on an empty document, and every single-root run now discloses its root exactly once. All red-first in test/rootrelcheck.sh ack api-surface 93848617cae1fe4e 2 cid=a42863f577100fa6 A3: TestRunnerIndex takes the run's crawl root so run= is spelled relative to root= (one absolute root per document, test/rootrelemitcheck.sh ARM 9). The parameter is the fix; every caller passes the root it already holds. ack api-surface 95cc88ca4aab7039 4 cid=25c411d4871bda46 C1 F-06/F-07/F-10 (the listing-paging round): three listing verbs learn to disclose and page their row listings, and every gating row is that one change. api-surface 14 = ONE trailing DEFAULTED parameter each (an int pageOffset, an McpPageArgs window, a SituPageArgs, or the next= invocation a header now carries) on the emitters that must be TOLD their window — writeFlags/writeGate, writeCappedRows/writeCappedList/writeFlip/writeFlipHeader/writeFlipLights, computeFlip, nearestGateNames (which gains its cap and its TOTAL, the disclosure itself), situShowingNote/writeSituation, and the three MCP twins flagsText/flipText/situationDiffJson; additive by construction, every pre-existing call site compiles unchanged, and the alternative — a second capped emitter per verb — is the drift this repo removes rather than adds, because two emitters that disagree about a window can drop the row that IS the answer. verbosity 3 = kDocDriftLegend +20 lines and writeDocDriftPage +10 are the in-band vocabulary a reader needs to read shown_failed=/failed_capped=/failed_total= where they meet it (the rationale and the next= scan were already hoisted OUT of the body into their own function and comment, which took the complexity row to zero and the LOC row from +48 to +10); dispatchMcpLine +12 is two pagedResult wrappers on a pre-existing 1376-line dispatcher this lane adds to rather than creates. complexity 1 = the same dispatcher, +9 on a base of 518. duplication 1 = flagsText | flipText at 110 tokens, down from 131 after the shared mcpRowCap fold; the residual is getIndex + compute + captureXml, the shape EVERY index-backed MCP twin in this file has, and merging two verbs that return different results behind one entry point would be worse code than the clone. short-horizon-churn 19 = this lane's own footprint across cli.h/docdrift.h/darkflags.h/flipimpact.h/situ.h/mcp*, plus cli.h symbols three other lanes touched the same day; none foreign, none thrash. @@ -137,6 +138,7 @@ ack api-surface ab7737f3582352d5 6 cid=47b3fc03a5d497f1 C1 F-06/F-07/F-10 (the l ack api-surface ad50354348e0b35e 7 cid=1ca210dbe9314b90 capture-audit 2026-09-04 wave-1 close: deliberate contract changes, each --edit-check incompatible=0 in its lane report — L4 (lane-L4.md, Symbols whose contract changed): pageDisclosure/computePageDisclosure +collectionCapped (defaulted), packLego +graphCountFloorAttr (defaulted), packConnect/writePrRootOpen/writeTestGateReport/writeTestGateReportJson/memberUsesArm +const Graph& for the M15 gauge; L5: enumerateRefs +filterNameHits out-param (defaulted, three callers source-compatible); L3: emitGrepUnindexed/grepAuxJson +window (the H4 paging fix, caller updated) ack api-surface aeed75863f7b617d 3 --lint reads the corpus ONCE (audit lane B2, second half): astQueryGrouped gains an OPT-IN keptBytesOut, so the walk that already reads every file hands its bytes to the two symbol-level passes that ran after it instead of each re-opening the same ~900 files one at a time on the main thread. The +1 param on astQueryGrouped/namingLensChecks/appendNamingFindings is that deliberate contract and it is DEFAULTED — the --ensemble caller passes nothing and is byte-identical, verified against a pre-binary. Partial by construction and safe by construction: an empty slot (skipped file, or a genuinely empty one) falls through to the caller's own read, which returns the same bytes, so fast and slow paths cannot disagree; a size guard keeps a vector built for another corpus in bounds. Retention is a SINGLE point placed before the tree is built, so no exit can forget it and no branch can keep it twice; workers only ever write distinct pre-sized slots, verified under ASan+UBSan (-fno-sanitize-recover=all, LSan suppressions) clean on both corpora with output still identical. Cost measured honestly: peak RSS 182.5 -> 192.7 MB (+5.6%) for one corpus of text held across the lint block. churn=self on astQueryGrouped/builtInLintCaptures/runLint/lintSymbolLevelChecks/mergeNamingLens is lane B (one day earlier) plus this round own first commit. Frozen-corpus profile: mergeNamingLens 19.5 -> 6.3 ms (the naminglens getBytes row is GONE, 907 calls -> 0), lintSymbolLevelChecks 45.1 -> 37.7 ms, readFile 2335 -> 1168 calls; warm --lint 0.51-0.52s -> 0.42-0.43s. 20 gates green, determinism + xmllint clean ack api-surface b496a273ae1564ef 5 WAVE-2 close (2026-08-19), finding 3 of 3: the 76 remaining gating rows, ONE change. All of them are W2-E's root-relative p= landing (9beaa2c/fccea68/a271e6c/b3fe074 plus the f9108b7 correction), measured for the first time at WAVE granularity. The per-lane acks written during W2-E covered only the correction round's own diff (working-tree-vs-HEAD at that moment), so the original ~30-verb landing was never QD-acked; this ack closes that gap rather than re-accepting anything. Verified by reading the whole 20cdc04..860291c src/main.cpp diff line by line: 282 of 448 added lines match the root-relative predicate directly and every one of the remaining 127 is an existing std::printf rewritten from ing.files[...] to the root-relative rp local, plus four extracted emit helpers (computeDirModules, printJsonSymbolRows, writeOversizeRows, writeDropRows). No unrelated logic rides in. By kind: api-surface 28 = the +1 rootArg/rootPrefix parameter on the emitters that must now be TOLD their root (writeAbiCheck/Ref/Struct, emitColumnar*, packBodies/Deps/Lego/Outline/Signatures[Json], serialize[Json], writeLayout*, packConnect, buildD1Row and the report writers) - defaulted wherever a caller could stay unchanged. complexity 31 and verbosity 16 = the single-root-condition ternary and its guarded root= clause applied per emitter, with no new nesting level and no new control flow beyond that one conditional; the large absolute numbers (runStructureText 207->231, runLint 313->323, runMaintenanceViews 174->190, runCallHierarchy 71->80) are pre-existing dispatcher size the wave adds to, not creates - decomposing them is its own round and is recorded as a wave-2 follow-up. params 1 = writeNonLocalStateReport 4->6, the same contract. The three sibling lanes are individually clean: --quality-delta at f6ec56d..1732fd8 (W2-J), 1732fd8..9a41c74 (W2-K) and 9a41c74..20cdc04 (W2-F) each report gating=0. Full suite green at this head: gates=429 pass=427 skip=2 fail=0, ASan+LSan clean, byte-deterministic, xmllint clean. churn= is unavailable in ref-pair mode by construction (both trees materialized out of the repo), so short-horizon-churn is silent here and that silence is not evidence. +ack api-surface b530db5496337fe8 4 cid=1a7084b3d1af69e4 now=4 was=3 facet=contract-change p=src/hopbody.h:370 fix1: intended internal signature changes (graph for the hop hedge, language for the comment rule, the answer-wide follow budget); every caller updated in the same commit ack api-surface b65c78efe3e73a34 4 WAVE-2 close (2026-08-19), finding 3 of 3: the 76 remaining gating rows, ONE change. All of them are W2-E's root-relative p= landing (9beaa2c/fccea68/a271e6c/b3fe074 plus the f9108b7 correction), measured for the first time at WAVE granularity. The per-lane acks written during W2-E covered only the correction round's own diff (working-tree-vs-HEAD at that moment), so the original ~30-verb landing was never QD-acked; this ack closes that gap rather than re-accepting anything. Verified by reading the whole 20cdc04..860291c src/main.cpp diff line by line: 282 of 448 added lines match the root-relative predicate directly and every one of the remaining 127 is an existing std::printf rewritten from ing.files[...] to the root-relative rp local, plus four extracted emit helpers (computeDirModules, printJsonSymbolRows, writeOversizeRows, writeDropRows). No unrelated logic rides in. By kind: api-surface 28 = the +1 rootArg/rootPrefix parameter on the emitters that must now be TOLD their root (writeAbiCheck/Ref/Struct, emitColumnar*, packBodies/Deps/Lego/Outline/Signatures[Json], serialize[Json], writeLayout*, packConnect, buildD1Row and the report writers) - defaulted wherever a caller could stay unchanged. complexity 31 and verbosity 16 = the single-root-condition ternary and its guarded root= clause applied per emitter, with no new nesting level and no new control flow beyond that one conditional; the large absolute numbers (runStructureText 207->231, runLint 313->323, runMaintenanceViews 174->190, runCallHierarchy 71->80) are pre-existing dispatcher size the wave adds to, not creates - decomposing them is its own round and is recorded as a wave-2 follow-up. params 1 = writeNonLocalStateReport 4->6, the same contract. The three sibling lanes are individually clean: --quality-delta at f6ec56d..1732fd8 (W2-J), 1732fd8..9a41c74 (W2-K) and 9a41c74..20cdc04 (W2-F) each report gating=0. Full suite green at this head: gates=429 pass=427 skip=2 fail=0, ASan+LSan clean, byte-deterministic, xmllint clean. churn= is unavailable in ref-pair mode by construction (both trees materialized out of the repo), so short-horizon-churn is silent here and that silence is not evidence. ack api-surface b689422adfc04435 5 cid=cdb3a6ea16c83186 lift-disclosure round (2026-09-10): applyStructuralExpansion/applySiblingLift's optional *LiftInfo out-param is the disclosure hook itself (api-surface contract-change, purely additive/default-nullptr per G5) - and the 4 short-horizon-churn(self) rows are the necessary --for/--pack-task integration points (computeLensRanking, forLensHeaderText, runForLens, packTaskBundleText) in files under active development; duplication/complexity/verbosity this round introduced were fixed, not acked ack api-surface b6a24afef32a68a8 2 cid=55e7936512b0563d A3: TestRunnerIndex takes the run's crawl root so run= is spelled relative to root= (one absolute root per document, test/rootrelemitcheck.sh ARM 9). The parameter is the fix; every caller passes the root it already holds. @@ -172,6 +174,7 @@ ack api-surface e3c54d39d366fbaa 7 cid=4cf915e4faf1fd4a capture-audit 2026-09-04 ack api-surface e514a69013d0934c 6 cid=aa51679e8cf66000 lane/helptask-precision 2026-09-10, MCP no_route (audit F-R1-07): all eight gating rows are this one change and nothing rides with it. api-surface forTaskText 4->5 and packTaskText 5->6 params: ONE DEFAULTED bool each (noRoute), so every pre-existing call site compiles unchanged and was verified to; the alternative — a second overload per verb — is the clone seam this repo removes rather than adds. complexity +4 on each of the same two: the four !noRoute gates are a MIRROR of verbs_for.h's own four (cfg.noRoute gates the shape demotion, the mention anchor, the co-change prior and the route note), and collapsing them would be the MCP dialect deciding for itself what --no-route means — the exact drift mcpforparitycheck exists to prevent. Both were already far over the ccx bar (55/29) before this change; decomposing forTaskText is its own round. short-horizon-churn churn=self on dispatchMcpLine, kMcpVerbFields, forTaskText and packTaskText is the footprint of having edited four symbols this window already touched. FIXED rather than acked in the same pass: the verbosity row on dispatchMcpLine (1376 -> 1387) is gone — the second hand-rolled five-line boolean accumulate was replaced by ONE guarded boolArg reader that post_check now shares, the rule intArg already states for the numeric fields, netting the dispatcher SMALLER than before. Manifest re-anchored 41300 -> 41650 for obliged schema bytes only: descriptions are BYTE-IDENTICAL at 19632 B ack api-surface e57abf94a3b1f3a4 5 cid=788aa47a3ec3d3f2 C1 F-06/F-07/F-10 (the listing-paging round): three listing verbs learn to disclose and page their row listings, and every gating row is that one change. api-surface 14 = ONE trailing DEFAULTED parameter each (an int pageOffset, an McpPageArgs window, a SituPageArgs, or the next= invocation a header now carries) on the emitters that must be TOLD their window — writeFlags/writeGate, writeCappedRows/writeCappedList/writeFlip/writeFlipHeader/writeFlipLights, computeFlip, nearestGateNames (which gains its cap and its TOTAL, the disclosure itself), situShowingNote/writeSituation, and the three MCP twins flagsText/flipText/situationDiffJson; additive by construction, every pre-existing call site compiles unchanged, and the alternative — a second capped emitter per verb — is the drift this repo removes rather than adds, because two emitters that disagree about a window can drop the row that IS the answer. verbosity 3 = kDocDriftLegend +20 lines and writeDocDriftPage +10 are the in-band vocabulary a reader needs to read shown_failed=/failed_capped=/failed_total= where they meet it (the rationale and the next= scan were already hoisted OUT of the body into their own function and comment, which took the complexity row to zero and the LOC row from +48 to +10); dispatchMcpLine +12 is two pagedResult wrappers on a pre-existing 1376-line dispatcher this lane adds to rather than creates. complexity 1 = the same dispatcher, +9 on a base of 518. duplication 1 = flagsText | flipText at 110 tokens, down from 131 after the shared mcpRowCap fold; the residual is getIndex + compute + captureXml, the shape EVERY index-backed MCP twin in this file has, and merging two verbs that return different results behind one entry point would be worse code than the clone. short-horizon-churn 19 = this lane's own footprint across cli.h/docdrift.h/darkflags.h/flipimpact.h/situ.h/mcp*, plus cli.h symbols three other lanes touched the same day; none foreign, none thrash. ack api-surface e6bdc5187566f3d7 8 cid=615b35e39fe420e4 capture-audit 2026-09-04 wave-1 close: deliberate contract changes, each --edit-check incompatible=0 in its lane report — L4 (lane-L4.md, Symbols whose contract changed): pageDisclosure/computePageDisclosure +collectionCapped (defaulted), packLego +graphCountFloorAttr (defaulted), packConnect/writePrRootOpen/writeTestGateReport/writeTestGateReportJson/memberUsesArm +const Graph& for the M15 gauge; L5: enumerateRefs +filterNameHits out-param (defaulted, three callers source-compatible); L3: emitGrepUnindexed/grepAuxJson +window (the H4 paging fix, caller updated) +ack api-surface e9041801bd6f7cdb 4 cid=734066326027c1cd now=4 was=3 facet=contract-change p=src/forfold.h:382 fix1: intended internal signature changes (graph for the hop hedge, language for the comment rule, the answer-wide follow budget); every caller updated in the same commit ack api-surface ebaa8de80f3b5caa 2 cid=fbf73430469857ac A3 (review of #219), the other half of the TestRunnerIndex ack above: runHintClauseIfRows takes the root-relative predicate as well as the row count, because the clause's ROOT-RELATIVE sentence must answer to the same fact that spells run= itself — a multi-root run declares no root= and correctly keeps an absolute command, so a clause claiming relativity there is a wrong answer, not a cosmetic one. The parameter is the fix and there is deliberately no default: a defaulted false would let a caller silently drop the sentence, which is the silent-wrong-answer shape this lane exists to remove. All ten call sites pass the predicate they already hold (packtask, flipimpact, handoff, partition, prcontext x2, verbs_change x2, situ, testmap). Visible only now because #214 landed the one-parameter form onto main first; once this lane merges, main carries the two-parameter form and the row cannot recur. Proven by runhintcheck 2c/2d (the root-spelling matrix, and multi-root does not claim a relativity it does not have) and prcontextcheck 49 rows, full suite gates=629 pass=627 skip=2 fail=0. ack api-surface ed05ee0357d016f1 4 --lint reads the corpus ONCE (audit lane B2, second half): astQueryGrouped gains an OPT-IN keptBytesOut, so the walk that already reads every file hands its bytes to the two symbol-level passes that ran after it instead of each re-opening the same ~900 files one at a time on the main thread. The +1 param on astQueryGrouped/namingLensChecks/appendNamingFindings is that deliberate contract and it is DEFAULTED — the --ensemble caller passes nothing and is byte-identical, verified against a pre-binary. Partial by construction and safe by construction: an empty slot (skipped file, or a genuinely empty one) falls through to the caller's own read, which returns the same bytes, so fast and slow paths cannot disagree; a size guard keeps a vector built for another corpus in bounds. Retention is a SINGLE point placed before the tree is built, so no exit can forget it and no branch can keep it twice; workers only ever write distinct pre-sized slots, verified under ASan+UBSan (-fno-sanitize-recover=all, LSan suppressions) clean on both corpora with output still identical. Cost measured honestly: peak RSS 182.5 -> 192.7 MB (+5.6%) for one corpus of text held across the lint block. churn=self on astQueryGrouped/builtInLintCaptures/runLint/lintSymbolLevelChecks/mergeNamingLens is lane B (one day earlier) plus this round own first commit. Frozen-corpus profile: mergeNamingLens 19.5 -> 6.3 ms (the naminglens getBytes row is GONE, 907 calls -> 0), lintSymbolLevelChecks 45.1 -> 37.7 ms, readFile 2335 -> 1168 calls; warm --lint 0.51-0.52s -> 0.42-0.43s. 20 gates green, determinism + xmllint clean ack api-surface eea83c3db0f03d69 20 cid=a4f7862584788fbd by=src/* lane 2 of the Graft head-to-head (2026-09-07): packSignatures and packSignaturesJson each gain ONE defaulted trailing out-parameter, the ids of the sigs rows they actually emitted, so the file-grain tail can exclude those files instead of the whole 40-candidate surface (three single-file answers at candidate rank 5/10/5 were served nowhere on rocksdb). Every existing caller is byte-identical; the facet is the deliberate arity change the ack-only help names. | prior: A2 (dropped_positive, 2026-09-03): packSignatures gained one trailing droppedPositiveOut out-param and the post-ladder accounting inside the rank-adaptive branch — same shared-arithmetic reason as its JSON sibling. From 85ad94fb3ce2e9edaa99da824a76fb7e8caadfd8 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 11:27:18 -0400 Subject: [PATCH 72/95] path-hop-body fix1: regenerate the cap inventory (kWrapperFollowMax, kRouteBodyMaxHops, kRouteBodyMax) Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- docs/LIMITS.md | 13 ++++++++----- docs/TUNING.md | 18 +++++++++--------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2e087bb70..fc6040815 100644 --- a/README.md +++ b/README.md @@ -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 292 compile-time caps and 7 ranking parameters. +Another gate derives the cap inventory. The tool has 295 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/docs/LIMITS.md b/docs/LIMITS.md index c23a680b4..a07ee207f 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 | | --- | --- | --- | --- | -| 292 | 114 | 175 | **117** | +| 295 | 114 | 178 | **117** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 292 + 7 is the 299 constants this generator parses out of `src/`. +are not counted as caps, and 295 + 7 is the 302 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 292 caps are classified -(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 152 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **140 of 295 caps are classified +(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 155 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 114 files that declare a cap — the 292 caps counted above, and no parameter. +One table for each of the 114 files that declare a cap — the 295 caps counted above, and no parameter. ### `src/abicheck.h` @@ -516,6 +516,7 @@ Discloses: `capped` | `kHopBodyCapBytes` | `4000` | — | — | | `kPathHopBodyMaxHops` | `2` | — | — | | `kWrapperFollowDepth` | `2` | — | — | +| `kWrapperFollowMax` | `3` | — | — | ### `src/infra/blanktext.h` @@ -883,6 +884,8 @@ Discloses: `gap_syms_capped`, `rows_capped` | `kNearSinkDepth` | `2` | — | proven caller hops back from to= (1 = a direct caller) | | `kNearSinkRows` | `4` | — | rows printed; rows= counts them all, rows_capped="1" says so | | `kPathGapRows` | `3` | — | rows printed; gap_syms= counts them all, gap_syms_capped="1" says so | +| `kRouteBodyMax` | `4` | — | spew guard: bodies per answer; past it capped="1" next= the first unshown | +| `kRouteBodyMaxHops` | `2` | — | bridge rows 0..2 (from= and the next two), never the to= row | ### `src/pathpairs.h` diff --git a/docs/TUNING.md b/docs/TUNING.md index e1d6b28ed..4df2fe6df 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 | | --- | --- | --- | --- | --- | --- | -| 200 | 199 | 112 | 12 | **37** | 75 | +| 203 | 202 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **200 cap -declarations** under **199 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 **203 cap +declarations** under **202 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 199 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **76 declared since the sweep was +every run, which makes them a third population again. Enumerated over the 202 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **79 declared since the sweep was prepared, which no measurement has touched** (`kBackHopWidth`, `kBaseScopeScanCap`, -`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +70 -more). 111 + 12 + 76 = 199, and `emit` refuses to render a partition that does not add up. The +`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +73 +more). 111 + 12 + 79 = 202, 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 200" would be wrong in both halves +declared in `src/` at all: `kSituTestRowsShown`. Quoting "113 of 203" 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 200-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 203-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. From 449261e7714dce3b885df61459e3da3488d7f970 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:01:45 -0400 Subject: [PATCH 73/95] path-hop-body fix1: route bodies stay off inside a --for graph-verb block; language columns; H4b - GAIN GATE from the fire table: the one in-sample fire inside a --for graph-verb block (LARGE-DEV vscode-03, +7.7 KB) named nothing its question asked; runPathServed / pathText take inForBlock and print no route bodies there (R5). - langidentitycheck ratchet: the Lua -- comment and the Python docstring rules read LangFacts columns (dashDashComments, leadingDocstring) instead of two new Lang:: predicates. - H4b: three *p stores before an unrelated callee pin the * comment rule alone (the F3 mutant survived H4). - Gate count 727 -> 728 (the merged gate lists) in README, docs/EVALS.md and the deck. Co-Authored-By: Claude Opus 5.5 --- README.md | 4 ++-- changelog.d/path-route-body.md | 3 ++- docs/EVALS.md | 6 +++--- present/deck5_ripwire_build.js | 6 +++--- src/hopbody.h | 5 +++-- src/langfacts.h | 12 ++++++++++-- src/mcpverbs.h | 8 ++++---- src/verbs_for.h | 4 ++-- src/verbs_navigate.h | 8 +++++--- test/hopbodycheck.sh | 7 +++++++ test/hopbodyfix/cstore/src/c.c | 8 ++++++++ test/hopbodyfix/cstore/src/commit.c | 6 ++++++ 12 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 test/hopbodyfix/cstore/src/commit.c diff --git a/README.md b/README.md index fc6040815..b39e2ca98 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

-727 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +728 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 **727 gate scripts** and is the authoritative list; +`test/regression.sh` names **728 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 diff --git a/changelog.d/path-route-body.md b/changelog.d/path-route-body.md index d55ee9bcd..e1c0d0e9d 100644 --- a/changelog.d/path-route-body.md +++ b/changelog.d/path-route-body.md @@ -5,7 +5,8 @@ consumer, the dispatcher) and the `` readers — now prints those proven="0">`: the bridge rows within 2 hops of `from=` (never `to=`), then the readers, at most 4 bodies (past that `capped="1"` and `next=` the first one left out), each whole up to 4000 bytes. Every body is a hedge: one under a crossing carries that hop's `via=` and `cands=` (`via="queue" cands="3"`), a reader's carries `via="name-key"`, and none of it enters `reachable=` or `hops=`. -`RIPWIRE_NO_HOP_BODY=1` turns it off. +Inside a `--for` answer's graph-verb block it stays off (that answer serves its own bodies; the one in-sample fire there, a +vscode `--for`, added 7.7 KB that named nothing the question asked). `RIPWIRE_NO_HOP_BODY=1` turns it off. Why: textual's `--path=post_message,on_*` named `post_message -> _process_messages_loop -> _dispatch_message` and the reader `_on_message`, but the answer to "how does a posted message reach its handler" lives in their bodies (the queue put, the loop's diff --git a/docs/EVALS.md b/docs/EVALS.md index 4b579042d..e0660006f 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 727 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 728 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 **727 gate scripts**, all of which exist on disk. +naming **728 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 727. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 728. 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/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index e1d3067e2..9e23641a3 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1214,7 +1214,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["727 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["728 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1238,7 +1238,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "727", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "728", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1540,7 +1540,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["192 long flags · 36 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["727 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["728 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 71 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/src/hopbody.h b/src/hopbody.h index 87697862c..0d916b5f5 100644 --- a/src/hopbody.h +++ b/src/hopbody.h @@ -21,6 +21,7 @@ #include "defattrib.h" // edgeSplitArm — the hop row's prov="split", read for a body's hedge #include "graph.h" +#include "langfacts.h" // dashDashComments / leadingDocstring — the statement-line rule's per-language columns #include "lexical.h" // lexicalScanText — the body text the index read #include "model.h" #include "nextverb.h" // nextFlag @@ -127,7 +128,7 @@ inline bool isStatementLine( std::string_view line, Lang lang ) noexcept } const std::string_view t = line.substr( a ); const bool starComment = t == "*" || t.starts_with( "* " ) || t.starts_with( "*\t" ) || t.starts_with( "*/" ); - if( t.starts_with( "//" ) || t.starts_with( "#" ) || t.starts_with( "/*" ) || starComment || ( lang == Lang::Lua && t.starts_with( "--" ) ) ) + if( t.starts_with( "//" ) || t.starts_with( "#" ) || t.starts_with( "/*" ) || starComment || ( langFacts( lang ).dashDashComments && t.starts_with( "--" ) ) ) { return false; } @@ -160,7 +161,7 @@ inline std::size_t bodyStatementLines( std::string_view body, Lang lang, std::si { std::size_t statements = 0; std::string_view docClose; // inside a leading multi-line docstring: its closing quote - bool docAllowed = lang == Lang::Python; + bool docAllowed = langFacts( lang ).leadingDocstring; while( !body.empty() && statements <= stopAbove ) { const std::size_t nl = body.find( '\n' ); diff --git a/src/langfacts.h b/src/langfacts.h index fd8b1cb34..d23c3d329 100644 --- a/src/langfacts.h +++ b/src/langfacts.h @@ -233,6 +233,14 @@ struct LangFacts /// Python parameter `static=False`, a TypeScript literal type `'static'`, a Rust `'static` lifetime, a string default all spell it /// without meaning linkage, and a function in those languages carries no `static` storage class at all. bool staticLinkage = false; + + /// Lua: a line starting `--` is a comment (elsewhere it is a decrement, `--n;`). hopbody.h isStatementLine reads it when it + /// counts a thin delegator's statement lines (lane path-hop-body-fix1, review F3). + bool dashDashComments = false; + + /// Python: a string literal as a function's FIRST statement is its docstring, not code; hopbody.h bodyStatementLines does not + /// count it (lane path-hop-body-fix1, review F4: a documented one-line delegator is still thin). + bool leadingDocstring = false; }; /// java.lang.Object's public methods an interface may redeclare abstract (LangFacts::samIgnoredMembers). @@ -243,7 +251,7 @@ inline constexpr std::array kLangFacts = { { { .lang = Lang::Cpp, .defectShapeNeedsMacroSet = true, .noNestedNamedFunctions = true, .memberSeparator = "::", .transitiveIncludes = true, .writtenModule = WrittenModuleRule::SourceChain, .staticLinkage = true }, { .lang = Lang::Python, .frameworkEntryHooks = true, .closureReceiverDispatch = true, .baseImportEvidence = BaseImportEvidence::PythonImport, - .instanceReceiver = "self", .underscorePrivacy = true }, + .instanceReceiver = "self", .underscorePrivacy = true, .leadingDocstring = true }, { .lang = Lang::TypeScript, .overloadSignatures = true, .structuralTyping = true, .importBoundConstruction = true, .baseImportEvidence = BaseImportEvidence::JsImport, .writtenBindingUndecided = true, .namespaceBodies = true, .objectLiteralMembers = true, .containmentOwners = true }, { .lang = Lang::Go, .structuralTyping = true, .writtenModule = WrittenModuleRule::PackageDir }, @@ -267,7 +275,7 @@ inline constexpr std::array kLangFacts = { { { .lang = Lang::Toml }, { .lang = Lang::Yaml }, { .lang = Lang::Php, .memberSeparator = "::" }, - { .lang = Lang::Lua }, + { .lang = Lang::Lua, .dashDashComments = true }, { .lang = Lang::Elixir, .hasInertQuoteRegions = true }, { .lang = Lang::Dart }, { .lang = Lang::Kotlin, .samFromJavaLambdas = true, .trailingLambdaScopeFns = true, .interfaceReadsAsClass = true, .anonymousImplementors = true, .overloadsWithoutArity = true, diff --git a/src/mcpverbs.h b/src/mcpverbs.h index e9937d5d0..412a4e575 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -2443,7 +2443,7 @@ inline std::string_view mcpPageRankFlags( bool noRoute ) } // defined below; forTaskText's FOR-ROUTE-DECLINED block calls them (the path_between / connect twins) -inline std::optional pathText( const std::string& root, const std::string& from, const std::string& to ); +inline std::optional pathText( const std::string& root, const std::string& from, const std::string& to, bool inForBlock = false ); inline std::string connectText( const std::string& root, const std::vector& symbolSpecs, std::uint32_t radius, std::string& err, RedactCounts* redact ); inline std::string batchCdata( const std::string& s ); // the callers twin's JSON rides as CDATA @@ -2922,7 +2922,7 @@ inline std::optional forTaskText( const std::string& root, const st GraphSubjectOpen opts; if( gsa->form == GraphSubjectForm::Path ) { - verb = pathText( root, gsa->endpoints[0], gsa->endpoints[1] ).value_or( std::string() ); + verb = pathText( root, gsa->endpoints[0], gsa->endpoints[1], /*inForBlock=*/true ).value_or( std::string() ); opts.nameTestHops = graphSubjectNameTestHops( verb ); } else if( gsa->form == GraphSubjectForm::Common ) @@ -3751,7 +3751,7 @@ inline McpAffectedResult affectedText( const std::string& root, std::string_view // resolveFocus + shortestPath, exactly as the CLI --path=A,B. Returns the … XML fragment (with // reachable="0" hops="0" and no children when B is NOT reachable from A — a valid answer, not an error), // or "" (caller → not-found error) when EITHER endpoint fails to resolve. -inline std::optional pathText( const std::string& root, const std::string& from, const std::string& to ) +inline std::optional pathText( const std::string& root, const std::string& from, const std::string& to, bool inForBlock ) { const McpIndex& ix = getIndex( root ); const IngestResult& ing = ix.ing; @@ -3835,7 +3835,7 @@ inline std::optional pathText( const std::string& root, const std:: // F8: no served set here — the MCP `for` answer's graph-verb block calls this twin by name only, so a middle hop that `for` // answer already carries prints a second copy on MCP (the CLI passes its served bodies: runPathServed); a known, bounded divergence const RouteBodiesXml ptHopBodyEl = ptBodiesOn ? pathHopBodiesXml( ing, pathMiddleHops( ing, g, ptShown, {} ), ptBodyRoot ) : RouteBodiesXml{}; - const RouteBodiesXml ptRouteEl = pth.empty() && hopBodiesOn() ? pathRouteBodies( ing, g, ptBridge, ptHandler, ptBodyRoot ) : RouteBodiesXml{}; // BODY-ON-ROUTE, as on the CLI + const RouteBodiesXml ptRouteEl = pth.empty() && !inForBlock && hopBodiesOn() ? pathRouteBodies( ing, g, ptBridge, ptHandler, ptBodyRoot ) : RouteBodiesXml{}; // BODY-ON-ROUTE, as on the CLI const std::string& ptHopBodies = ptHopBodyEl.xml; std::string ptRootAttrs = pathTestTwinRootAttrs( ing, pathPairsRootAttrs( ing, ptPairs, ptSingleRoot, ptRootPrefix ).rootAttrs, ptTwin, ptSingleRoot, ptRootPrefix ); const std::string ptHopNext = ptBodiesOn && ptRootAttrs.find( " next=\"" ) == std::string::npos ? pathMiddleHopNext( ing, ptShown, ptBodyRoot ) : std::string(); diff --git a/src/verbs_for.h b/src/verbs_for.h index a393d96f1..e7834ad68 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2656,7 +2656,7 @@ struct ForLensPassMode }; // the verb sections (verbs_navigate.h) come later in this translation unit; the --for block calls their CLI entry points std::optional runPath( const MainDispatch& d ); -std::optional runPathServed( const MainDispatch& d, std::span served ); +std::optional runPathServed( const MainDispatch& d, std::span served, bool inForBlock ); std::optional runCallHierarchy( const MainDispatch& d ); std::optional runConnect( const MainDispatch& d ); @@ -2699,7 +2699,7 @@ inline void appendSubjectGraphVerbBlock( const MainDispatch& d, std::spanform ) { - case rw::GraphSubjectForm::Path: (void)runPathServed( sd, served ); break; + case rw::GraphSubjectForm::Path: (void)runPathServed( sd, served, /*inForBlock=*/true ); break; case rw::GraphSubjectForm::Callers: (void)runCallHierarchy( sd ); break; case rw::GraphSubjectForm::Common: (void)runConnect( sd ); break; } diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index ffcc994db..af26d701c 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -2479,7 +2479,7 @@ std::optional runExternalSurface( const MainDispatch& d ) // served: bodies the enclosing answer already carries (the --for graph-verb block passes its fold's and slot's); a middle hop among them // prints no second copy (hopbody.h). The bare --path passes none. -std::optional runPathServed( const MainDispatch& d, std::span served ) +std::optional runPathServed( const MainDispatch& d, std::span served, bool inForBlock ) { using namespace rw; const Config& cfg = d.cfg; @@ -2624,7 +2624,9 @@ std::optional runPathServed( const MainDispatch& d, std::span runPathServed( const MainDispatch& d, std::span runPath( const MainDispatch& d ) { - return runPathServed( d, {} ); + return runPathServed( d, {}, /*inForBlock=*/false ); } std::optional runConnect( const MainDispatch& d ) diff --git a/test/hopbodycheck.sh b/test/hopbodycheck.sh index 5ca9e81e1..8456648a2 100644 --- a/test/hopbodycheck.sh +++ b/test/hopbodycheck.sh @@ -54,6 +54,8 @@ # source; never the to= row (on_event); reachable= and hops= unchanged; the of=route clause rides (compact and full) # R2 [M] the route spew guard: a fifth route body is cut, capped="1" next="--expand=pump.py:45:_trace_message" # R3 [M] the hop bound: the 4-hop bridge press -> on_event prints its rows 0..2 only (press, post_message, the loop) +# H4b[M] three `*p =` stores before the one call: 4 statement lines, never followed (pins the * comment rule alone) +# R5 [G] the gain gate: inside a --for answer's graph-verb block the unreached path prints no route bodies # R4 [G] RIPWIRE_NO_HOP_BODY=1: no route element; MCP path_between prints the CLI's route element byte for byte # # Usage: bash test/hopbodycheck.sh [BIN] | RIPWIRE_BIN=asan/ripwire bash test/hopbodycheck.sh @@ -118,6 +120,7 @@ run "$HB/external" --for="how does relayWorkItem relay a work item" >"$TMP/h2b" run "$HB/ffi" --for="How does relay_transform relay a value?" >"$TMP/h3" run "$HB/ffi" --callees=relay_transform >"$TMP/h3c" run "$HB/cstore" --for="how does scale_and_store scale and store" >"$TMP/h4" +run "$HB/cstore" --for="how does prime_then_fill prime the buffers then fill" >"$TMP/h4b" run "$HB/pydoc" --for="how does Widget post_message post a message" >"$TMP/h5a" run "$HB/pydoc" --for="how does Widget post_message_plain post a message" >"$TMP/h5b" run "$HB/klass" --for="how does PanelHolder compute the panel size for the holder" >"$TMP/h6" @@ -132,6 +135,7 @@ rrun route '--path=post_message,on_*' >"$TMP/r1" rrun route '--path=post_message,on_*' --legend=full >"$TMP/r1f" rrun routecap '--path=post_message,on_*' >"$TMP/r2" rrun route '--path=press,on_event' >"$TMP/r3" +rrun route --for="Trace the path from post_message() to on_event()" >"$TMP/r5" ( cd "$TMP/route" && RIPWIRE_NO_HOP_BODY=1 "$BIN" "$TMP/route" --no-cache '--path=post_message,on_*' 2>/dev/null ) >"$TMP/r1.off" cat > "$TMP/chk.py" <<'PY' @@ -238,6 +242,7 @@ check('via="declined"' in rd("h2c") and re.search(r']*n="relayWorkItem"', check(re.search(r']*n="relayWorkItem"', rd("h2b")) and fol(rd("h2b")) == [], "(H2) an undefined call beside the proven one: never followed") check('graph_ambiguous="1"' in rd("h3c") and re.search(r']*n="relay_transform"', rd("h3")) and fol(rd("h3")) == [] and not re.search(r']*n="scale_impl"', rd("h3")), "(H3) a binding-only call (amb=) is never followed") check(re.search(r']*n="scale_and_store"', rd("h4")) and fol(rd("h4")) == [], "(H4) C *out stores count as statements: no follow") +check(re.search(r']*n="prime_then_fill"', rd("h4b")) and fol(rd("h4b")) == [], "(H4b) three *p stores + the call = 4 statement lines: no follow (pins the * rule alone)") check(fol(rd("h5a")) == ["Widget.post_message"] and fol(rd("h5b")) == ["Widget.post_message_plain"], "(H5) documented and plain Python delegators both followed, follow_of=Owner.name: %r %r" % (fol(rd("h5a")), fol(rd("h5b")))) check(re.search(r']*n="PanelHolder"', rd("h6")) and fol(rd("h6")) == [], "(H6) a class is never a wrapper") check(fol(rd("h6b")) == ["ChildRunner.launch"], "(H5/F7) a super-override follow names its wrapper Owner.name: %r" % fol(rd("h6b"))) @@ -262,6 +267,8 @@ el2 = routel(rd("r2")) check(el2.startswith('') and len(bodies(el2)) == 4, "(R2) a fifth route body is cut: capped=1, next= the first unshown") b3 = [b[0].get("n") for b in bodies(routel(rd("r3")))] check(b3 == ["press", "post_message", "_process_messages_loop"], "(R3) a 4-hop bridge prints rows 0..2 only: %r" % b3) +g5 = (re.search(r'', rd("r5"), re.S) or re.search(r'$', rd("r5"))).group(0) +check(g5 != "" and 'reachable="0"' in g5 and routel(g5) == "" and "bodies of=route" not in legend(rd("r5")), "(R5) a --for graph-verb path block prints no route bodies (the gain gate)") check(routel(rd("r1.off")) == "" and "bodies of=route" not in legend(rd("r1.off")), "(R4) RIPWIRE_NO_HOP_BODY=1: no route element, no clause") for f in ("h1", "r1", "r2", "r3"): check(subprocess.run(["xmllint", "--noout", os.path.join(T, f)], capture_output=True).returncode == 0, "(X2) %s well-formed" % f) diff --git a/test/hopbodyfix/cstore/src/c.c b/test/hopbodyfix/cstore/src/c.c index 3c1439252..c85f5c5fb 100644 --- a/test/hopbodyfix/cstore/src/c.c +++ b/test/hopbodyfix/cstore/src/c.c @@ -1,4 +1,5 @@ int fill_buffer(int *out, int n); +int commit_slots(int *out, int n); int scale_and_store(int *out, int *aux, int n) { int t = fill_buffer(out, n); @@ -25,3 +26,10 @@ int store_and_scale_twice(int v) { return ( v * 3 ) - ( v % 7 ); } +int prime_then_fill(int *out, int *aux, int *tag, int n) +{ + *aux = n; + *tag = n + 1; + *out = n + 2; + return commit_slots(out, n); +} diff --git a/test/hopbodyfix/cstore/src/commit.c b/test/hopbodyfix/cstore/src/commit.c new file mode 100644 index 000000000..d57449636 --- /dev/null +++ b/test/hopbodyfix/cstore/src/commit.c @@ -0,0 +1,6 @@ +int commit_slots(int *out, int n) +{ + int COMMIT_MARK = n * 5; + *out = COMMIT_MARK; + return COMMIT_MARK; +} From 8c66c90998e413cf6adc464b574ee8b2201cdd90 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:12:41 -0400 Subject: [PATCH 74/95] for: the ranked seed's string scan shares one set of scanner options; gate verdicts through verdict() langidentitycheck's ratchet counted two new `== Lang::Python` predicates: the string-aware CodeScanOptions that graph.h and the ranked seed both spelled become clones.h stringAwareScanOptions (the compares move, the count is unchanged). gateexitcheck G2: the R11-R13b arms reported through `&& ok || no`; they use verdict(). The rankword fixture gains a one-word literal row so the two-word bar is pinned. Co-Authored-By: Claude Sonnet 5.5 --- src/clones.h | 10 ++++++++++ src/forcallerhop.h | 5 +---- src/graph.h | 5 +---- test/forcallerhopcheck.sh | 14 +++++++------- test/forcallerhopfix/rankword/cmd.c | 1 + 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/clones.h b/src/clones.h index 87971c8ed..61604b507 100644 --- a/src/clones.h +++ b/src/clones.h @@ -207,6 +207,16 @@ struct CodeScanOptions bool tripleQuoteStrings = false; // `"""…"""` / `'''…'''` is one string to its closing triple (Python) }; +// The scanner options for `lang`'s comment and string rules, in ONE place (the two consumers that read string literals spelled the +// same five fields: graph.h identifierTokenCountExceeds, forcallerhop.h forBodyHoldsTaskMessage). Python: `//` is floor division, not a +// comment, and `"""`/`'''` open one triple-quoted string; `#` comments and `'` strings per the two language masks above. +inline CodeScanOptions stringAwareScanOptions( Lang lang, bool munchMultiByteOperators ) noexcept +{ + return CodeScanOptions{ .stripHashComments = usesHashLineComments( lang ), .munchMultiByteOperators = munchMultiByteOperators, + .singleQuoteStrings = usesSingleQuoteStrings( lang ), .slashComments = lang != Lang::Python, + .tripleQuoteStrings = lang == Lang::Python }; +} + template inline void scanCodeTokens( const std::string& src, std::size_t a, std::size_t b, CodeScanOptions options, Sink&& sink ) { diff --git a/src/forcallerhop.h b/src/forcallerhop.h index e4b630da7..15a308b78 100644 --- a/src/forcallerhop.h +++ b/src/forcallerhop.h @@ -290,10 +290,7 @@ inline bool forBodyHoldsTaskMessage( const std::string& src, const Symbol& s, co bool found = false; std::vector parts; std::vector hit; - scanCodeTokens( src, s.sigStartByte, s.endByte, - CodeScanOptions{ .stripHashComments = usesHashLineComments( s.lang ), .munchMultiByteOperators = false, - .singleQuoteStrings = usesSingleQuoteStrings( s.lang ), .slashComments = s.lang != Lang::Python, - .tripleQuoteStrings = s.lang == Lang::Python }, + scanCodeTokens( src, s.sigStartByte, s.endByte, stringAwareScanOptions( s.lang, /*munchMultiByteOperators=*/false ), [ & ]( std::string_view token, CodeTokenKind kind ) { if( found || kind != CodeTokenKind::String ) diff --git a/src/graph.h b/src/graph.h index 1c06a7b9a..a9df4d975 100644 --- a/src/graph.h +++ b/src/graph.h @@ -3211,10 +3211,7 @@ inline bool identifierTokenCountExceeds( const std::string& text, std::string_vi { std::size_t tokens = 0; StringTokenPosition at{ .text = text }; - scanCodeTokens( text, 0, text.size(), - CodeScanOptions{ .stripHashComments = usesHashLineComments( lang ), .munchMultiByteOperators = true, // `->` is one token - .singleQuoteStrings = usesSingleQuoteStrings( lang ), .slashComments = lang != Lang::Python, - .tripleQuoteStrings = lang == Lang::Python }, + scanCodeTokens( text, 0, text.size(), stringAwareScanOptions( lang, /*munchMultiByteOperators=*/true ), // `->` is one token [ & ]( std::string_view token, CodeTokenKind kind ) { if( kind == CodeTokenKind::Identifier || kind == CodeTokenKind::Keyword ) diff --git a/test/forcallerhopcheck.sh b/test/forcallerhopcheck.sh index 24e427209..6fdf02eef 100755 --- a/test/forcallerhopcheck.sh +++ b/test/forcallerhopcheck.sh @@ -40,7 +40,7 @@ # R12 [G] WHOLE WORD: failover, throwaway, terror, ErrorKind, raiser, crashpad, failsafe, errorless carry none (kills a substring match) # R13 [P] an explanation question (How does ... / Explain ... / Describe ...) carries none; [G] a where-question with a message-bearing row does # R14 [P] F5: the seed is the narrow funnel whose own body holds a string literal with >= 2 task words (rankword: lookup_word r=3, not -# command_not_found_hook r=1); R15 [P] a question with no message-bearing narrow funnel carries none (a statement keeps R3's fallback) +# command_not_found_hook r=1; hook_user r=2 holds a ONE-word literal, below the bar); R15 [P] a question with no message-bearing narrow funnel carries none (a statement keeps R3's fallback) # R16 [P] F2: a seed whose only first-hop caller is a by-name match (Go interface, Python duck typing) carries none (rankgo, rankpy) # X1 [G] determinism X2 [G] xmllint well-formed X3 [G] no "--" inside a legend comment # @@ -339,29 +339,29 @@ rk_hop(){ # $1 task -> prints "R" when verify_dag is r=1 on a ranked route, th } for w in panicked panic Traceback raises raise throws throw segfault crashed crash failing failed fail exception errors DependencyCycleError dependency_cycle_error ValueError IOException; do got="$( rk_hop "dependency cycle $w" )" - [ "$got" = RH ] && ok "(R11) [P] the task word '$w' is an error word: the ranked hop rides" || no "(R11) '$w' did not fire the ranked hop (got [$got], want RH)" + [ "$got" = RH ]; verdict $? "(R11) [P] the task word '$w' is an error word: the ranked hop rides" "(R11) '$w' did not fire the ranked hop (got [$got], want RH)" done for w in failover throwaway terror ErrorKind raiser crashpad failsafe errorless; do got="$( rk_hop "dependency cycle $w" )" - [ "$got" = R ] && ok "(R12) [G] '$w' is not a whole error word: no ranked hop (kills a substring match)" || no "(R12) '$w' fired the ranked hop or left the premise (got [$got], want R)" + [ "$got" = R ]; verdict $? "(R12) [G] '$w' is not a whole error word: no ranked hop (kills a substring match)" "(R12) '$w' fired the ranked hop or left the premise (got [$got], want R)" done for q in "How does the dependency cycle error work?" "Explain the dependency cycle error" "Describe how a dependency cycle error is raised"; do got="$( rk_hop "$q" )" - [ "$got" = R ] && ok "(R13) [P] an explanation question gets no ranked hop: $q" || no "(R13) an explanation question fired (or left the premise): $q (got [$got], want R)" + [ "$got" = R ]; verdict $? "(R13) [P] an explanation question gets no ranked hop: $q" "(R13) an explanation question fired (or left the premise): $q (got [$got], want R)" done got="$( rk_hop "Where is the dependency cycle error raised?" )" -[ "$got" = RH ] && ok "(R13b) [G] a where-question whose r=1 row holds the message still rides" || no "(R13b) the where-question lost its hop (got [$got], want RH)" +[ "$got" = RH ]; verdict $? "(R13b) [G] a where-question whose r=1 row holds the message still rides" "(R13b) the where-question lost its hop (got [$got], want RH)" # R14 F5: the seed is the narrow funnel whose OWN body holds a string literal with >= 2 task words (lookup_word: "command %d not # found"), not the r=1 funnel that merely carries the words in its name (command_not_found_hook). Statement and question alike. FW="test/forcallerhopfix/rankword" for q in "command not found error" "Where is the command not found error raised?"; do run "$FW" --for="$q" >"$TMP/r14" - if grep -q ']*n="command_not_found_hook"[^>]*r="1"' "$TMP/r14" && grep -q ']*n="lookup_word"[^>]*r="3"' "$TMP/r14"; then + if grep -q ']*n="\(command_not_found_hook\|hook_user\)"[^>]*r="[12]"' "$TMP/r14" && grep -q ']*n="lookup_word"[^>]*r="3"' "$TMP/r14"; then grep -q ' Date: Wed, 28 Oct 2026 13:47:42 -0400 Subject: [PATCH 75/95] test: the rankword fixture's one-word row also holds "error", pinning that error is not a counted task word Co-Authored-By: Claude Sonnet 5.5 --- test/forcallerhopfix/rankword/cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/forcallerhopfix/rankword/cmd.c b/test/forcallerhopfix/rankword/cmd.c index 93a23bfc1..b89ed76a5 100644 --- a/test/forcallerhopfix/rankword/cmd.c +++ b/test/forcallerhopfix/rankword/cmd.c @@ -9,7 +9,7 @@ int command_not_found_hook( int w ) int hook_user( int w ) { - fputs( "command\n", stderr ); /* ONE task word in a string: below the two-word bar */ + fputs( "error command\n", stderr ); /* ONE task word plus "error" (never a task word): below the two-word bar */ return command_not_found_hook( w ); } From 62e7231c501a0fa3c708234784fac11a19942c39 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:05:12 -0400 Subject: [PATCH 76/95] for: the ranked seed reuses routeLower and splits into small helpers; the Go fixture's saveAll no longer clones a neighbour --quality-delta over the lane range named forLowerAscii a clone of lexical.h routeLower (reuse it), forRankedCallerHopTargets a 30-complexity function (split: forRankedSeedAt, ForRankedSources, forLiteralHoldsTaskWords) and rankgo's saveAll a clone of forbodyslotfix's writeArchive (a different loop shape). No behaviour change: the measured rows and forcallerhopcheck are unchanged. Co-Authored-By: Claude Sonnet 5.5 --- src/forcallerhop.h | 211 ++++++++++++++------------- test/forcallerhopfix/rankgo/store.go | 10 +- 2 files changed, 113 insertions(+), 108 deletions(-) diff --git a/src/forcallerhop.h b/src/forcallerhop.h index 15a308b78..44ad810d2 100644 --- a/src/forcallerhop.h +++ b/src/forcallerhop.h @@ -193,16 +193,6 @@ inline std::vector forTaskIdentWords( std::string_view task ) return out; } -inline std::string forLowerAscii( std::string_view w ) -{ - std::string out( w ); - for( char& c : out ) - { - c = ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : c; - } - return out; -} - // "How does …", "Explain …", "Describe …": a question about a mechanism (the first word, case-folded). inline bool forTaskExplains( std::string_view task ) { @@ -211,7 +201,7 @@ inline bool forTaskExplains( std::string_view task ) { return false; } - const std::string first = forLowerAscii( w.front() ); + const std::string first = routeLower( w.front() ); return first == "how" || first == "explain" || first == "describe"; } @@ -227,7 +217,7 @@ inline bool forTaskIsQuestion( std::string_view task ) { return false; } - const std::string first = forLowerAscii( w.front() ); + const std::string first = routeLower( w.front() ); static constexpr std::string_view kOpeners[] = { "how", "what", "why", "where", "which", "when", "who", "explain", "describe", "does", "do", "is", "are", "can" }; return std::ranges::find( kOpeners, std::string_view( first ) ) != std::end( kOpeners ); } @@ -241,7 +231,7 @@ inline bool forRankedCallerHopTask( std::string_view task ) std::vector parts; for( const std::string& w : forTaskIdentWords( task ) ) { - const std::string lower = forLowerAscii( w ); + const std::string lower = routeLower( w ); if( std::ranges::find( kForErrorWords, std::string_view( lower ) ) != std::end( kForErrorWords ) ) { return true; @@ -282,117 +272,134 @@ inline std::vector forTaskContentWords( std::string_view task ) inline constexpr std::size_t kForRankedMessageWords = 2; // task content words ONE string literal of the seed's own body must hold -// Does [sigStartByte, endByte) of `s` in `src` hold ONE string literal with >= kForRankedMessageWords distinct task content words? -// Read with THE one code scanner (clones.h scanCodeTokens: comments dropped, per-language quote rules); a backtick raw string is not -// read (a floor: such a seed falls to the statement-only fallback). -inline bool forBodyHoldsTaskMessage( const std::string& src, const Symbol& s, const std::vector& words ) +// Does ONE string-literal token hold >= kForRankedMessageWords distinct task content words (identifier runs split into subtokens)? +inline bool forLiteralHoldsTaskWords( std::string_view token, const std::vector& words ) { - bool found = false; std::vector parts; - std::vector hit; + std::vector hit( words.size(), 0 ); + std::size_t distinct = 0; + std::size_t at = 0; + while( at < token.size() ) + { + while( at < token.size() && !namesplit::isIdentChar( token[at] ) ) + { + ++at; + } + const std::size_t begin = at; + while( at < token.size() && namesplit::isIdentChar( token[at] ) ) + { + ++at; + } + parts.clear(); + subtokens( token.substr( begin, at - begin ), parts ); + for( const std::string& p : parts ) + { + const auto it = std::ranges::find( words, p ); + const auto ix = std::size_t( it - words.begin() ); + if( it != words.end() && hit[ix] == 0 ) + { + hit[ix] = 1; + if( ++distinct >= kForRankedMessageWords ) + { + return true; + } + } + } + } + return false; +} + +// Does [sigStartByte, endByte) of `s` in `src` hold such a literal? Read with THE one code scanner (clones.h scanCodeTokens: comments +// dropped, per-language quote rules); a backtick raw string is not read (a floor: such a seed falls to the statement-only fallback). +inline bool forBodyHoldsTaskMessage( const std::string& src, const Symbol& s, const std::vector& words ) +{ + bool found = false; scanCodeTokens( src, s.sigStartByte, s.endByte, stringAwareScanOptions( s.lang, /*munchMultiByteOperators=*/false ), [ & ]( std::string_view token, CodeTokenKind kind ) - { - if( found || kind != CodeTokenKind::String ) - { - return; - } - hit.assign( words.size(), 0 ); - std::size_t distinct = 0; - std::size_t at = 0; - while( at < token.size() && !found ) - { - while( at < token.size() && !namesplit::isIdentChar( token[at] ) ) - { - ++at; - } - const std::size_t begin = at; - while( at < token.size() && namesplit::isIdentChar( token[at] ) ) - { - ++at; - } - parts.clear(); - subtokens( token.substr( begin, at - begin ), parts ); - for( const std::string& p : parts ) - { - const auto it = std::ranges::find( words, p ); - if( it != words.end() && hit[ std::size_t( it - words.begin() ) ] == 0 ) - { - hit[ std::size_t( it - words.begin() ) ] = 1; - found = found || ++distinct >= kForRankedMessageWords; - } - } - } - } ); + { found = found || ( kind == CodeTokenKind::String && forLiteralHoldsTaskWords( token, words ) ); } ); return found; } -// The seed: the first of the rows emitted at r=1..kForRankedHopHeadRows that is ONE function by the callers verb's own -// resolution (forCallerHopTargets) with 1..kForRankedHopMaxCallers first-hop rows, not every one a test or bench path, and not -// every one matched by name alone (F2: the d=2 rows would inherit an unproven receiver — a Go interface method, a duck-typed -// Python call) — and, among those, PREFERRING the first whose own body holds a string literal with >= 2 task words (F5: the -// function that raises the message). With none, today's first eligible row, but only for a statement, never a question. +// The files the seed's bodies are read from: each file once (at most kForRankedHopHeadRows), "" when unreadable (no message found there). +struct ForRankedSources +{ + std::vector> files; + + const std::string& of( const IngestResult& ing, std::uint32_t fileId ) + { + auto f = std::ranges::find( files, fileId, &std::pair::first ); + if( f == files.end() ) + { + files.emplace_back( fileId, docparse::detail::readWholeFile( diskPath( ing, fileId ) ).value_or( std::string() ) ); + f = std::prev( files.end() ); + } + return f->second; + } +}; + +// One r=1..3 head row as a seed: the row's targets (ONE function by the callers verb's own resolution) when it has 1..kForRankedHopMaxCallers +// first-hop rows, not every one a test or bench path (the chain already ends there), not every one matched by name alone (F2: the d=2 rows +// would inherit an unproven receiver — a Go interface method, a duck-typed Python call); else empty. +inline std::vector forRankedSeedAt( const IngestResult& ing, const Graph& g, NodeId id ) +{ + if( id >= ing.symbols.size() || !ReceiverEvidence::callableKind( ing.symbols[id].kind ) ) + { + return {}; + } + const NodeId one[] = { id }; + std::vector targets = forCallerHopTargets( ing, g, one ); + if( targets.empty() ) + { + return {}; + } + const std::vector first = oneHopCallerRows( ing, g, targets ); + if( first.empty() || first.size() > kForRankedHopMaxCallers ) + { + return {}; + } + const bool allTests = std::all_of( first.begin(), first.end(), [ & ]( NodeId c ) + { return pathTierOf( rootRelPath( ing, ing.symbols[c].fileId ) ) == PathTier::TestOrBench; } ); + if( allTests ) + { + return {}; + } + const OneHopAttribution attr = attributeOneHopAnswer( ing, g, targets, first, /*towardMatches=*/true ); + for( std::size_t k = 0; k < first.size(); ++k ) + { + if( !attr.nameOnly( k ) ) + { + return targets; + } + } + return {}; +} + +// The seed: among the eligible rows (forRankedSeedAt) of the rows emitted at r=1..kForRankedHopHeadRows, the first whose own +// body holds a string literal with >= 2 task words (F5: the function that raises the message); with none, the first eligible row, +// but only for a statement, never a question. inline std::vector forRankedCallerHopTargets( const IngestResult& ing, const Graph& g, std::span rankedHead, std::string_view task ) { const std::vector words = forTaskContentWords( task ); std::vector fallback; - std::vector> files; // at most kForRankedHopHeadRows files read, once each + ForRankedSources sources; for( std::size_t i = 0; i < rankedHead.size() && i < kForRankedHopHeadRows; ++i ) { - const NodeId id = rankedHead[i]; - if( id >= ing.symbols.size() || !ReceiverEvidence::callableKind( ing.symbols[id].kind ) ) - { - continue; - } - const NodeId one[] = { id }; - const std::vector targets = forCallerHopTargets( ing, g, one ); + std::vector targets = forRankedSeedAt( ing, g, rankedHead[i] ); if( targets.empty() ) { continue; } - const std::vector first = oneHopCallerRows( ing, g, targets ); - if( first.empty() || first.size() > kForRankedHopMaxCallers ) + const bool holds = words.size() >= kForRankedMessageWords + && std::ranges::any_of( targets, [ & ]( NodeId t ) + { return forBodyHoldsTaskMessage( sources.of( ing, ing.symbols[t].fileId ), ing.symbols[t], words ); } ); + if( holds ) { - continue; - } - const bool allTests = std::all_of( first.begin(), first.end(), [ & ]( NodeId c ) - { return pathTierOf( rootRelPath( ing, ing.symbols[c].fileId ) ) == PathTier::TestOrBench; } ); - if( allTests ) - { - continue; - } - const OneHopAttribution attr = attributeOneHopAnswer( ing, g, targets, first, /*towardMatches=*/true ); - bool allByName = true; - for( std::size_t k = 0; k < first.size() && allByName; ++k ) - { - allByName = attr.nameOnly( k ); - } - if( allByName ) - { - continue; + return targets; } if( fallback.empty() ) { - fallback = targets; - } - if( words.size() < kForRankedMessageWords ) - { - continue; - } - for( const NodeId t : targets ) - { - const Symbol& s = ing.symbols[t]; - auto f = std::ranges::find( files, s.fileId, &std::pair::first ); - if( f == files.end() ) - { - files.emplace_back( s.fileId, docparse::detail::readWholeFile( diskPath( ing, s.fileId ) ).value_or( std::string() ) ); - f = std::prev( files.end() ); - } - if( forBodyHoldsTaskMessage( f->second, s, words ) ) - { - return targets; - } + fallback = std::move( targets ); } } return forTaskIsQuestion( task ) ? std::vector{} : fallback; diff --git a/test/forcallerhopfix/rankgo/store.go b/test/forcallerhopfix/rankgo/store.go index 09567ef8e..a7a8da113 100644 --- a/test/forcallerhopfix/rankgo/store.go +++ b/test/forcallerhopfix/rankgo/store.go @@ -23,13 +23,11 @@ type diskStore struct{} func (d *diskStore) Put(key string) error { return nil } -func saveAll(s Store, keys []string) error { - for _, k := range keys { - if err := s.Put(k); err != nil { - return err - } +func saveAll(s Store, keys []string) (err error) { + for i := 0; i < len(keys) && err == nil; i++ { + err = s.Put(keys[i]) } - return nil + return err } func Import(keys []string) error { From aa13b254caee2f236dd6e844a465db09b50a432e Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:19:15 -0400 Subject: [PATCH 77/95] test: de-flake memguardcheck B6b (readdir-order dependent) and pargatescheck (I control) admission arm (load) memguardcheck (B6b): the second MCP call was find_symbol a_02 against an index cut at crawl entry 30. Which fixture files survive that cut is the filesystem's directory order; when a_02 is outside it the answer is a not-found ERROR, which has no result._memory_stop, so the arm read ''. The follow-up is now grep (a result on any partial index); find_symbol stays as a third probe asserted on the shape it returns. pargatescheck (I control) admission: the read-first control waited 0.5 s, leaving 0.2 s for the probe gate to pass its 0.3 s mark; fails 3/3 at load 9-13 on unmodified main. The control's first wait is 3.0 s; the modelled ordering defect is unchanged. Co-Authored-By: Claude Sonnet 5.5 --- changelog.d/ci-flakes-3-memguard-b6b.md | 14 ++++++++++++++ test/memguardcheck.sh | 20 ++++++++++++++++++-- test/pargatescheck.sh | 7 ++++++- 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 changelog.d/ci-flakes-3-memguard-b6b.md diff --git a/changelog.d/ci-flakes-3-memguard-b6b.md b/changelog.d/ci-flakes-3-memguard-b6b.md new file mode 100644 index 000000000..9f5246f81 --- /dev/null +++ b/changelog.d/ci-flakes-3-memguard-b6b.md @@ -0,0 +1,14 @@ +### Fixed — two flaky gate arms: memguardcheck (B6b) on Linux runners and pargatescheck (I control) on a loaded host + +Neither is a product change; both arms depended on something the gate did not control. + +- `test/memguardcheck.sh` (B6b): the arm sent a `find_symbol a_02` as the second MCP call against an index cut by the crawl + trip at entry 30. Which fixture files survive that cut is the filesystem's directory order (the crawl does not sort before it + stops): on macOS `a/f02.c` is kept; on a Linux runner whose directory order places it later, `a_02` is outside the cut index and + the answer is a not-found error, which carries no `_memory_stop` envelope field, so the arm read an empty string (red on main + and on both train-40 PR runs, ubuntu release shard 4/4, green on rerun). The follow-up call is now `grep`, whose answer is a + result on any partial index, so the arm checks the same thing (a later answer from the same cut index keeps the disclosure) on + every filesystem; the `find_symbol` call stays as a third probe asserted on the shape it returns, with a NOTE line when it fell outside the cut. +- `test/pargatescheck.sh` (I control): the control copy that puts the old "wait, then look at the stop" loop back waited 0.5 s + before looking, leaving 0.2 s for the probe gate to start and pass its 0.3 s mark; at load average 9 to 13 it failed three + runs of three on unmodified main. The control's first wait is now 3.0 s; the ordering defect it models is unchanged. diff --git a/test/memguardcheck.sh b/test/memguardcheck.sh index 0ca7f4c0e..ceef2c96a 100755 --- a/test/memguardcheck.sh +++ b/test/memguardcheck.sh @@ -366,13 +366,29 @@ else fi # (B6) MCP: a soft stop answers with the envelope disclosure, and every answer from that partial index keeps it +# WHICH files survive crawl:30 is the filesystem's readdir order (recursive_directory_iterator, no sort before the +# stop): APFS keeps a/f02.c, ext4's hashed order can keep it or not (a simulation over random orders keeps it +# ~37 % of the time). find_symbol of a name outside the kept files is a JSON-RPC ERROR, which has no +# result._memory_stop at all (CI 40671724898 and #458 runs: B6b "_memory_stop=''" on ubuntu only). So the +# per-answer assertions below use verbs whose answer is a RESULT on ANY partial index (grep, an empty match is +# still an envelope), and the find_symbol probe is asserted only on the shape it actually returned. printf '%s\n' \ "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"grep\",\"arguments\":{\"path\":\"$FX\",\"pattern\":\"a_01\"}}}" \ - "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"find_symbol\",\"arguments\":{\"path\":\"$FX\",\"symbol\":\"a_02\"}}}" \ + "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"grep\",\"arguments\":{\"path\":\"$FX\",\"pattern\":\"a_02\"}}}" \ + "{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"tools/call\",\"params\":{\"name\":\"find_symbol\",\"arguments\":{\"path\":\"$FX\",\"symbol\":\"a_02\"}}}" \ | RIPWIRE_TEST_MEMGUARD=crawl:30 mcp_call "$TMP" --max-memory=64M >"$TMP/b6.out" ms2="$( mcp_field "$TMP/b6.out" 2 _memory_stop )"; ms3="$( mcp_field "$TMP/b6.out" 3 _memory_stop )" case "$ms2" in *"memory guard"*crawl*64M*) ok "(B6) MCP grep under a crawl stop carries _memory_stop";; *) no "(B6) id=2 _memory_stop='${ms2:0:200}' resp: $( head -c 300 "$TMP/b6.out" )";; esac -case "$ms3" in *"memory guard"*) ok "(B6b) the next answer from the same partial index still carries it";; *) no "(B6b) id=3 _memory_stop='${ms3:0:200}'";; esac +case "$ms3" in *"memory guard"*) ok "(B6b) the next answer from the same partial index still carries it";; *) no "(B6b) id=3 _memory_stop='${ms3:0:200}' resp: $( grep '"id":3' "$TMP/b6.out" | head -c 300 )";; esac +# (B6b2) a third verb on the same index: when the symbol is inside the kept files the answer is a result and must carry +# the disclosure; when it is outside them the answer is a not-found ERROR, whose shape (no envelope) is not what +# this arm pins — said on a NOTE line, not a PASS, so a reader sees which shape ran +if [ -n "$( mcp_field "$TMP/b6.out" 4 error )" ]; then + printf ' NOTE (B6b2) find_symbol a_02 fell outside the kept files here (readdir order): a not-found error, no envelope to check\n' +else + ms4="$( mcp_field "$TMP/b6.out" 4 _memory_stop )" + case "$ms4" in *"memory guard"*) ok "(B6b2) find_symbol from the same partial index still carries it";; *) no "(B6b2) id=4 _memory_stop='${ms4:0:200}'";; esac +fi printf '%s\n' "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"grep\",\"arguments\":{\"path\":\"$FX\",\"pattern\":\"a_01\"}}}" \ | mcp_call "$TMP" >"$TMP/b6c.out" if grep -q '_memory_stop' "$TMP/b6c.out"; then diff --git a/test/pargatescheck.sh b/test/pargatescheck.sh index 002a97e6e..878a16d7e 100755 --- a/test/pargatescheck.sh +++ b/test/pargatescheck.sh @@ -688,6 +688,11 @@ PREREVIEW = '''def _stop_group(p): # run_gate() as e442a5d8 had it: the stop was checked only after a STOP_POLL_SEC wait, so a gate admitted as the signal # was recorded ran that long before anything looked. Re-spelled 2026-09-11 against the file-backed capture; the defect is # the ORDER of the two checks, not how the output is collected. +# The first wait is 3.0 s here, not STOP_POLL_SEC (0.5 s): the arm needs the probe gate (bash start + a 0.3 s sleep, then its +# "late" line) to get past its 0.3 s mark INSIDE that wait. At 0.5 s the 0.2 s of slack is eaten by process starts on a loaded host +# (the (I control) admission arm FAILED 3 of 3 runs at load average 9-13 on unmodified main 12056c16, "late recorded: False") and +# the mutant was stopped before it could show its defect. A longer wait changes no ordering: the defect is still +# "wait first, look at the stop second"; the fixed run_gate() checks the stop before any wait and is not affected. READFIRST = '''def run_gate(argv, env, limit): deadline = time.monotonic() + limit fd, capture = tempfile.mkstemp(prefix="ripwire-pargates-capture-", suffix=".out") @@ -697,7 +702,7 @@ READFIRST = '''def run_gate(argv, env, limit): subprocess.Popen(argv, cwd=root, env=env, stdout=fh, stderr=subprocess.STDOUT, start_new_session=True) as p: while True: - if _wait_for(p, max(0.0, min(STOP_POLL_SEC, deadline - time.monotonic()))): + if _wait_for(p, max(0.0, min(3.0, deadline - time.monotonic()))): return p.returncode, _capture_read(capture), "exited" if stop_signal is not None: _stop_group(p) From 5dade5d336b1e93883a6e9046a051410d38c1bce Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:22:25 -0400 Subject: [PATCH 78/95] for: forLiteralHoldsTaskWords reads a literal's words through the task's own ident-run split Co-Authored-By: Claude Sonnet 5.5 --- src/forcallerhop.h | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/forcallerhop.h b/src/forcallerhop.h index 44ad810d2..03369e1ca 100644 --- a/src/forcallerhop.h +++ b/src/forcallerhop.h @@ -278,25 +278,14 @@ inline bool forLiteralHoldsTaskWords( std::string_view token, const std::vector< std::vector parts; std::vector hit( words.size(), 0 ); std::size_t distinct = 0; - std::size_t at = 0; - while( at < token.size() ) + for( const std::string& run : forTaskIdentWords( token ) ) // the same ident-run split the task's words come through { - while( at < token.size() && !namesplit::isIdentChar( token[at] ) ) - { - ++at; - } - const std::size_t begin = at; - while( at < token.size() && namesplit::isIdentChar( token[at] ) ) - { - ++at; - } parts.clear(); - subtokens( token.substr( begin, at - begin ), parts ); + subtokens( run, parts ); for( const std::string& p : parts ) { - const auto it = std::ranges::find( words, p ); - const auto ix = std::size_t( it - words.begin() ); - if( it != words.end() && hit[ix] == 0 ) + const auto ix = std::size_t( std::ranges::find( words, p ) - words.begin() ); + if( ix < words.size() && hit[ix] == 0 ) { hit[ix] = 1; if( ++distinct >= kForRankedMessageWords ) From ba155d13bc0a4d1799d4deb0dd86383e5ba2a526 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:38:13 -0400 Subject: [PATCH 79/95] test: memguardcheck B21 asks whether the stream finished, not how many seconds the refusal took A fixed 4 s bound failed at 5 s on a host at load 13-19 (the shim's own 60 spawned sleeps stretch the 6 s stream too). A join can only end when the stream does, so the shim now writes log-done after its last line and the refusal must return without it. Co-Authored-By: Claude Sonnet 5.5 --- changelog.d/ci-flakes-3-memguard-b6b.md | 8 ++++++-- test/memguardcheck.sh | 12 ++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/changelog.d/ci-flakes-3-memguard-b6b.md b/changelog.d/ci-flakes-3-memguard-b6b.md index 9f5246f81..41f37d064 100644 --- a/changelog.d/ci-flakes-3-memguard-b6b.md +++ b/changelog.d/ci-flakes-3-memguard-b6b.md @@ -1,6 +1,6 @@ -### Fixed — two flaky gate arms: memguardcheck (B6b) on Linux runners and pargatescheck (I control) on a loaded host +### Fixed — three flaky gate arms: memguardcheck (B6b, B21) and pargatescheck (I control) -Neither is a product change; both arms depended on something the gate did not control. +None is a product change; each arm depended on something the gate did not control. - `test/memguardcheck.sh` (B6b): the arm sent a `find_symbol a_02` as the second MCP call against an index cut by the crawl trip at entry 30. Which fixture files survive that cut is the filesystem's directory order (the crawl does not sort before it @@ -12,3 +12,7 @@ Neither is a product change; both arms depended on something the gate did not co - `test/pargatescheck.sh` (I control): the control copy that puts the old "wait, then look at the stop" loop back waited 0.5 s before looking, leaving 0.2 s for the probe gate to start and pass its 0.3 s mark; at load average 9 to 13 it failed three runs of three on unmodified main. The control's first wait is now 3.0 s; the ordering defect it models is unchanged. +- `test/memguardcheck.sh` (B21): the arm required the refusal to return within 4 s of a 6 s git-log stream, but the shim's 60 + spawned sleeps stretch that stream on a busy host and the refusal took 5 s at load average 13 to 19. The arm now asks what a + join would do, which is wait for the stream's end: the shim writes a marker after its last line and the refusal must return + without it. diff --git a/test/memguardcheck.sh b/test/memguardcheck.sh index ceef2c96a..2c7814ddb 100755 --- a/test/memguardcheck.sh +++ b/test/memguardcheck.sh @@ -535,8 +535,11 @@ fi # beside the ingest; a memory-guard refusal used to join that walk's future before exiting, so the exit code waited for # the end of the log. With a git shim whose `log --name-only` (the walk's own stream; its sha-list probe and every other # git call reach the real git) streams one line every 0.1 s for 6 s, the refusal must return well before the stream ends: -# the walker stops at its next line and the join ends there (<= 4 s against the 6 s stream: measured 2 s plain and under -# ASan on a loaded 10-core host; the pre-fix binary took 17-25 s). Fresh TMPDIR, so no cached stream from an earlier arm +# the walker stops at its next line and the join ends there (measured 2 s plain and under ASan on a loaded 10-core host; +# the pre-fix binary took 17-25 s). The verdict is NOT a wall-clock bound: a fixed 4 s failed at 5 s on a host at load +# average 13-19, where the shim's own 60 spawned sleeps stretch the 6 s stream too. A join can only end when the stream +# does, so the shim writes log-done after its last line and the refusal must have returned without it (the abandoned +# walker gets SIGPIPE at its next echo and exits before reaching it); the elapsed seconds stay in the message. Fresh TMPDIR, so no cached stream from an earlier arm # stands in for the walk; the shim records that it served the walk. SHIM="$TMP/shim"; mkdir -p "$SHIM" "$TMP/b21tmp" REALGIT="$( command -v git )" @@ -547,6 +550,7 @@ for a in "\$@"; do : > "$SHIM/log-served" i=0 while [ \$i -lt 60 ]; do echo "shim-line \$i" || exit 0; sleep 0.1; i=\$((i+1)); done + : > "$SHIM/log-done" exit 0 fi done @@ -558,10 +562,10 @@ PATH="$SHIM:$PATH" TMPDIR="$TMP/b21tmp" run_trip crawl:30 "$FXG" --no-cache --fo el=$(( $( date +%s ) - t0 )) if [ ! -e "$SHIM/log-served" ]; then no "(B21) premise: the shim never served a git log --name-only — the history walk did not run against it (rc=$rc)" -elif [ "$rc" = 5 ] && grep -q '^ripwire: .*cannot answer from a partial index' "$TMP/b21.err" && [ "$el" -le 4 ]; then +elif [ "$rc" = 5 ] && grep -q '^ripwire: .*cannot answer from a partial index' "$TMP/b21.err" && [ ! -e "$SHIM/log-done" ]; then ok "(B21) --for over a crawl stop refuses in ${el}s while the shim's log still streams: the walk is abandoned, not joined" else - no "(B21) rc=$rc elapsed=${el}s (the shim's log streams for 6 s; a join waits it out) stderr: $( grep '^ripwire:' "$TMP/b21.err" | head -c 200 )" + no "(B21) rc=$rc elapsed=${el}s stream-finished-before-return=$( [ -e "$SHIM/log-done" ] && echo yes || echo no ) (the shim's log streams for 6 s; a join waits it out) stderr: $( grep '^ripwire:' "$TMP/b21.err" | head -c 200 )" fi # (B16) --query --format=candidates is a report verb, so it refuses a partial index like the rest (pinned: its # root has no header to carry the cut) From d986467d71903c2a3ba68cabe1e8c210f0f36643 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:43:01 -0400 Subject: [PATCH 80/95] path-hop-body fix2: arms R6-R9 pin the edge-row hedge, per-body cap and dedupe; slot body carries slot_fns=; bare-b legend reading Co-Authored-By: Claude Sonnet 5.5 --- src/compactlegend.h | 2 +- src/hopbody.h | 3 +- src/pathgaps.h | 2 +- test/hopbodycheck.sh | 21 +- test/hopbodyfix/routebig/case.py | 9 + test/hopbodyfix/routebig/message.py | 14 + test/hopbodyfix/routebig/pump.py | 683 +++++++++++++++++++++++++ test/hopbodyfix/routebig/widgets.py | 53 ++ test/hopbodyfix/routededupe/case.py | 9 + test/hopbodyfix/routededupe/message.py | 14 + test/hopbodyfix/routededupe/pump.py | 45 ++ test/hopbodyfix/routededupe/widgets.py | 53 ++ test/hopbodyfix/routename/case.py | 9 + test/hopbodyfix/routename/message.py | 14 + test/hopbodyfix/routename/pump.py | 53 ++ test/hopbodyfix/routename/widgets.py | 53 ++ test/hopbodyfix/routeslot/src/ops.c | 14 + 17 files changed, 1047 insertions(+), 4 deletions(-) create mode 100644 test/hopbodyfix/routebig/case.py create mode 100644 test/hopbodyfix/routebig/message.py create mode 100644 test/hopbodyfix/routebig/pump.py create mode 100644 test/hopbodyfix/routebig/widgets.py create mode 100644 test/hopbodyfix/routededupe/case.py create mode 100644 test/hopbodyfix/routededupe/message.py create mode 100644 test/hopbodyfix/routededupe/pump.py create mode 100644 test/hopbodyfix/routededupe/widgets.py create mode 100644 test/hopbodyfix/routename/case.py create mode 100644 test/hopbodyfix/routename/message.py create mode 100644 test/hopbodyfix/routename/pump.py create mode 100644 test/hopbodyfix/routename/widgets.py create mode 100644 test/hopbodyfix/routeslot/src/ops.c diff --git a/src/compactlegend.h b/src/compactlegend.h index bc5660bb4..ed554aa3a 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -869,7 +869,7 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = // BODY-ON-ROUTE (src/pathgaps.h pathRouteBodies): present-only, read only on a reachable=0 path's { "of", "bodies of=route proven=0: bodies of the route this answer names, never traced (bridge rows <=2 hops from from=, never to=; then hrd readers); <=4 (capped=1 next=), each whole to 4000 B; a hedge, never in reachable=/hops=", true, "bodies", MapHeaderRead::No, "route", "path" }, // fix1 F1 + BODY-ON-ROUTE (src/hopbody.h chainHopHedge): present-only, on a path whose hop is no proven call - { "via", "b via= cands=: the hop into this body is no proven call (name: one of cands= same-named defs; a bridge crossing; name-key: a handler_rule reader); a hedge, never the proven callee", true, "b", MapHeaderRead::No, {}, "path" }, + { "via", "b via= cands=: the hop into this body is no proven call (name: one of cands= same-named defs; a bridge crossing, slot_fns=K the functions that field holds; name-key: a handler_rule reader); a hedge, never the proven callee; a b with none is a proven call from the row above, inside a route that stays proven=0", true, "b", MapHeaderRead::No, {}, "path" }, { "prov", "b prov=split: the hop into this body is one arm of an ambiguous pick; a hedge", true, "b", MapHeaderRead::No, {}, "path" }, // ON-NAME-HANDLERS-V2 (src/pathgaps.h kHandlerRuleLegend): present-only, read only on the block and its rows { "handlers", "handler_rule via=name-key base= attr= name= p= posts= classes= handlers= shown=: a posted message reaches a to= handler BY NAME: base's __init_subclass__ writes attr= (p=) as name= (snake = _ between lower and upper, then lower; Owner = enclosing class), read from this repo; posts= calls of from= constructing a subclass whose name fits to=, classes= those classes, handlers= the to= defs matched; next= lists one name's defs; matched, never traced; a hedge", true, "handler_rule", MapHeaderRead::No, {}, "path" }, diff --git a/src/hopbody.h b/src/hopbody.h index 0d916b5f5..3c70df5cd 100644 --- a/src/hopbody.h +++ b/src/hopbody.h @@ -461,7 +461,8 @@ inline std::string_view pathHopBodiesLegend( bool on ) noexcept inline std::string_view routeBodyHedgeLegend( bool on ) noexcept { return on ? "b via= cands= prov=: the hop into this body is no proven call (by name: one of cands= same-named definitions; a link; a " - "split arm; a bridge crossing; a handler_rule reader), so the body is a hedge, never the proven callee. " + "split arm; a bridge crossing, slot_fns= the functions that field holds; a handler_rule reader), so the body is a hedge, never the proven callee; " + "a b with none of these is a proven call from the row above it, inside a route that stays proven=0. " : ""; } inline std::string_view pathHopNextLegend( bool on ) noexcept diff --git a/src/pathgaps.h b/src/pathgaps.h index 7198251b0..01da9b3ed 100644 --- a/src/pathgaps.h +++ b/src/pathgaps.h @@ -1828,7 +1828,7 @@ inline RouteBodiesXml pathRouteBodies( const IngestResult& ing, const Graph& g, if( i > 0 && br.via[i] != BridgeVia::Edge ) { hedge = " via=\"" + std::string( kBridgeViaNames[ std::size_t( br.via[i] ) ] ) + ( bridgeViaIsSlot( br.via[i] ) ? "\" slot_callers=\"" : "\" cands=\"" ) - + std::to_string( br.fan[i] ) + "\""; + + std::to_string( br.fan[i] ) + "\"" + ( bridgeViaIsSlot( br.via[i] ) ? " slot_fns=\"" + std::to_string( br.fwd[i] ) + "\"" : std::string() ); // the bridge row's own widths } else if( i > 0 ) { diff --git a/test/hopbodycheck.sh b/test/hopbodycheck.sh index 8456648a2..72b55531a 100644 --- a/test/hopbodycheck.sh +++ b/test/hopbodycheck.sh @@ -55,6 +55,9 @@ # R2 [M] the route spew guard: a fifth route body is cut, capped="1" next="--expand=pump.py:45:_trace_message" # R3 [M] the hop bound: the 4-hop bridge press -> on_event prints its rows 0..2 only (press, post_message, the loop) # H4b[M] three `*p =` stores before the one call: 4 statement lines, never followed (pins the * comment rule alone) +# R6-R9 [M] FIX2 (review rv-phbf1): R6 a by-name bridge edge row's body carries via=name cands=2 (G1); R7 a > 4000 B route body is cut, +# truncated=1 lines= next= (G2); R8 a reader that is also a bridge row prints once (G3); R9 a slot crossing's body carries slot_fns= +# and the bare-b reading rides (S1, S4) # R5 [G] the gain gate: inside a --for answer's graph-verb block the unreached path prints no route bodies # R4 [G] RIPWIRE_NO_HOP_BODY=1: no route element; MCP path_between prints the CLI's route element byte for byte # @@ -111,7 +114,7 @@ run "$LOO" --callees=handleLooseRequest >"$TMP/f4c" # FIX1 fixtures (the route ones run from a copy outside test/: a reader under a test path is not a handler_rule reader) HB="test/hopbodyfix" -cp -R "$HB/route" "$HB/routecap" "$TMP/" +cp -R "$HB/route" "$HB/routecap" "$HB/routename" "$HB/routebig" "$HB/routededupe" "$HB/routeslot" "$TMP/" run "$HB/byname" --path=startJ,sinkJ >"$TMP/h1" run "$HB/byname" --path=startJ,sinkJ --legend=full >"$TMP/h1f" run "$HB/declined" --for="how does relayWorkItem relay a work item" >"$TMP/h2a" @@ -136,6 +139,11 @@ rrun route '--path=post_message,on_*' --legend=full >"$TMP/r1f" rrun routecap '--path=post_message,on_*' >"$TMP/r2" rrun route '--path=press,on_event' >"$TMP/r3" rrun route --for="Trace the path from post_message() to on_event()" >"$TMP/r5" +rrun routename '--path=post_message,on_*' >"$TMP/r6" +rrun routebig '--path=post_message,on_*' >"$TMP/r7" +rrun routededupe '--path=post_message,on_*' >"$TMP/r8" +rrun routeslot '--path=start_job,sink_write' >"$TMP/r9" +rrun routeslot '--path=start_job,sink_write' --legend=full >"$TMP/r9f" ( cd "$TMP/route" && RIPWIRE_NO_HOP_BODY=1 "$BIN" "$TMP/route" --no-cache '--path=post_message,on_*' 2>/dev/null ) >"$TMP/r1.off" cat > "$TMP/chk.py" <<'PY' @@ -270,6 +278,17 @@ check(b3 == ["press", "post_message", "_process_messages_loop"], "(R3) a 4-hop b g5 = (re.search(r'', rd("r5"), re.S) or re.search(r'$', rd("r5"))).group(0) check(g5 != "" and 'reachable="0"' in g5 and routel(g5) == "" and "bodies of=route" not in legend(rd("r5")), "(R5) a --for graph-verb path block prints no route bodies (the gain gate)") check(routel(rd("r1.off")) == "" and "bodies of=route" not in legend(rd("r1.off")), "(R4) RIPWIRE_NO_HOP_BODY=1: no route element, no clause") +# FIX2 R6-R9 (lane path-hop-body-fix1, review rv-phbf1 G1/G2/G3/S1/S4: each arm is RED on its review mutant) +rb = lambda f: {b[0].get("n"): b[0] for b in bodies(routel(rd(f)))} +b6 = rb("r6") +check(b6.get("dispatch_message", {}).get("via") == "name" and b6.get("dispatch_message", {}).get("cands") == "2", "(R6) a by-name edge row inside the bridge: its route body carries via=name cands=2, never bare (the edge-row hedge): %r" % b6.get("dispatch_message")) +b7 = bodies(routel(rd("r7"))) +check(len(b7) == 4 and all(b[0].get("truncated") == "1" and re.fullmatch(r'1-\d+/\d+', b[0].get("lines", "")) and b[0].get("next", "").startswith("--expand=pump.py:") and len(b[1].encode()) <= 4000 for b in b7) and routel(rd("r7")).startswith(''), "(R7) a route body over 4000 B is cut: truncated=1 lines= next=, <= 4000 B each, capped=1") +b8 = bodies(routel(rd("r8"))); n8 = [b[0].get("n") for b in b8] +check(n8 == ["post_message", "_process_messages_loop", "_dispatch_message", "_on_message"] and len(set(n8)) == len(n8), "(R8) a reader that is also a bridge row prints once and the true reader _on_message is still shown: %r" % n8) +b9 = rb("r9") +check(b9.get("run_fast", {}).get("via") == "slot" and b9.get("run_fast", {}).get("slot_callers") == "1" and b9.get("run_fast", {}).get("slot_fns") == "3", "(R9) a slot-crossing route body carries slot_fns= beside slot_callers= (S1): %r" % b9.get("run_fast")) +check("proven call from the row above" in legend(rd("r9")) and "proven call from the row above" in legend(rd("r9f")), "(R9) the bare-b reading rides beside a hedged route, compact and full (S4)") for f in ("h1", "r1", "r2", "r3"): check(subprocess.run(["xmllint", "--noout", os.path.join(T, f)], capture_output=True).returncode == 0, "(X2) %s well-formed" % f) # X3 diff --git a/test/hopbodyfix/routebig/case.py b/test/hopbodyfix/routebig/case.py new file mode 100644 index 000000000..980b8ceb8 --- /dev/null +++ b/test/hopbodyfix/routebig/case.py @@ -0,0 +1,9 @@ +import re + + +def camel_to_snake(name, _re=re.compile("[a-z][A-Z]")): + def repl(m): + lower, upper = m.group() + return f"{lower}_{upper.lower()}" + + return _re.sub(repl, name).lower() diff --git a/test/hopbodyfix/routebig/message.py b/test/hopbodyfix/routebig/message.py new file mode 100644 index 000000000..6ab2d25fa --- /dev/null +++ b/test/hopbodyfix/routebig/message.py @@ -0,0 +1,14 @@ +from case import camel_to_snake + + +class Message: + namespace = "" + + def __init_subclass__(cls, namespace=None): + super().__init_subclass__() + if namespace is not None: + name = f"{namespace}_{camel_to_snake(cls.__name__)}" + else: + parts = cls.__qualname__.rsplit(".", 2)[-2:] + name = "_".join(camel_to_snake(p) for p in parts) + cls.handler_name = f"on_{name}" diff --git a/test/hopbodyfix/routebig/pump.py b/test/hopbodyfix/routebig/pump.py new file mode 100644 index 000000000..768daa7d5 --- /dev/null +++ b/test/hopbodyfix/routebig/pump.py @@ -0,0 +1,683 @@ +from message import Message as Event +import queue + + +class Pump: + def __init__(self, parent=None): + self._queue = queue.Queue() + self._parent = parent + + def post_message(self, message): + if self._closed: + return False + x0 = len(str(message)) + 0 + x1 = len(str(message)) + 1 + x2 = len(str(message)) + 2 + x3 = len(str(message)) + 3 + x4 = len(str(message)) + 4 + x5 = len(str(message)) + 5 + x6 = len(str(message)) + 6 + x7 = len(str(message)) + 7 + x8 = len(str(message)) + 8 + x9 = len(str(message)) + 9 + x10 = len(str(message)) + 10 + x11 = len(str(message)) + 11 + x12 = len(str(message)) + 12 + x13 = len(str(message)) + 13 + x14 = len(str(message)) + 14 + x15 = len(str(message)) + 15 + x16 = len(str(message)) + 16 + x17 = len(str(message)) + 17 + x18 = len(str(message)) + 18 + x19 = len(str(message)) + 19 + x20 = len(str(message)) + 20 + x21 = len(str(message)) + 21 + x22 = len(str(message)) + 22 + x23 = len(str(message)) + 23 + x24 = len(str(message)) + 24 + x25 = len(str(message)) + 25 + x26 = len(str(message)) + 26 + x27 = len(str(message)) + 27 + x28 = len(str(message)) + 28 + x29 = len(str(message)) + 29 + x30 = len(str(message)) + 30 + x31 = len(str(message)) + 31 + x32 = len(str(message)) + 32 + x33 = len(str(message)) + 33 + x34 = len(str(message)) + 34 + x35 = len(str(message)) + 35 + x36 = len(str(message)) + 36 + x37 = len(str(message)) + 37 + x38 = len(str(message)) + 38 + x39 = len(str(message)) + 39 + x40 = len(str(message)) + 40 + x41 = len(str(message)) + 41 + x42 = len(str(message)) + 42 + x43 = len(str(message)) + 43 + x44 = len(str(message)) + 44 + x45 = len(str(message)) + 45 + x46 = len(str(message)) + 46 + x47 = len(str(message)) + 47 + x48 = len(str(message)) + 48 + x49 = len(str(message)) + 49 + x50 = len(str(message)) + 50 + x51 = len(str(message)) + 51 + x52 = len(str(message)) + 52 + x53 = len(str(message)) + 53 + x54 = len(str(message)) + 54 + x55 = len(str(message)) + 55 + x56 = len(str(message)) + 56 + x57 = len(str(message)) + 57 + x58 = len(str(message)) + 58 + x59 = len(str(message)) + 59 + x60 = len(str(message)) + 60 + x61 = len(str(message)) + 61 + x62 = len(str(message)) + 62 + x63 = len(str(message)) + 63 + x64 = len(str(message)) + 64 + x65 = len(str(message)) + 65 + x66 = len(str(message)) + 66 + x67 = len(str(message)) + 67 + x68 = len(str(message)) + 68 + x69 = len(str(message)) + 69 + x70 = len(str(message)) + 70 + x71 = len(str(message)) + 71 + x72 = len(str(message)) + 72 + x73 = len(str(message)) + 73 + x74 = len(str(message)) + 74 + x75 = len(str(message)) + 75 + x76 = len(str(message)) + 76 + x77 = len(str(message)) + 77 + x78 = len(str(message)) + 78 + x79 = len(str(message)) + 79 + x80 = len(str(message)) + 80 + x81 = len(str(message)) + 81 + x82 = len(str(message)) + 82 + x83 = len(str(message)) + 83 + x84 = len(str(message)) + 84 + x85 = len(str(message)) + 85 + x86 = len(str(message)) + 86 + x87 = len(str(message)) + 87 + x88 = len(str(message)) + 88 + x89 = len(str(message)) + 89 + x90 = len(str(message)) + 90 + x91 = len(str(message)) + 91 + x92 = len(str(message)) + 92 + x93 = len(str(message)) + 93 + x94 = len(str(message)) + 94 + x95 = len(str(message)) + 95 + x96 = len(str(message)) + 96 + x97 = len(str(message)) + 97 + x98 = len(str(message)) + 98 + x99 = len(str(message)) + 99 + x100 = len(str(message)) + 100 + x101 = len(str(message)) + 101 + x102 = len(str(message)) + 102 + x103 = len(str(message)) + 103 + x104 = len(str(message)) + 104 + x105 = len(str(message)) + 105 + x106 = len(str(message)) + 106 + x107 = len(str(message)) + 107 + x108 = len(str(message)) + 108 + x109 = len(str(message)) + 109 + x110 = len(str(message)) + 110 + x111 = len(str(message)) + 111 + x112 = len(str(message)) + 112 + x113 = len(str(message)) + 113 + x114 = len(str(message)) + 114 + x115 = len(str(message)) + 115 + x116 = len(str(message)) + 116 + x117 = len(str(message)) + 117 + x118 = len(str(message)) + 118 + x119 = len(str(message)) + 119 + x120 = len(str(message)) + 120 + x121 = len(str(message)) + 121 + x122 = len(str(message)) + 122 + x123 = len(str(message)) + 123 + x124 = len(str(message)) + 124 + x125 = len(str(message)) + 125 + x126 = len(str(message)) + 126 + x127 = len(str(message)) + 127 + x128 = len(str(message)) + 128 + x129 = len(str(message)) + 129 + x130 = len(str(message)) + 130 + x131 = len(str(message)) + 131 + x132 = len(str(message)) + 132 + x133 = len(str(message)) + 133 + x134 = len(str(message)) + 134 + x135 = len(str(message)) + 135 + x136 = len(str(message)) + 136 + x137 = len(str(message)) + 137 + x138 = len(str(message)) + 138 + x139 = len(str(message)) + 139 + x140 = len(str(message)) + 140 + x141 = len(str(message)) + 141 + x142 = len(str(message)) + 142 + x143 = len(str(message)) + 143 + x144 = len(str(message)) + 144 + x145 = len(str(message)) + 145 + x146 = len(str(message)) + 146 + x147 = len(str(message)) + 147 + x148 = len(str(message)) + 148 + x149 = len(str(message)) + 149 + x150 = len(str(message)) + 150 + x151 = len(str(message)) + 151 + x152 = len(str(message)) + 152 + x153 = len(str(message)) + 153 + x154 = len(str(message)) + 154 + x155 = len(str(message)) + 155 + x156 = len(str(message)) + 156 + x157 = len(str(message)) + 157 + x158 = len(str(message)) + 158 + x159 = len(str(message)) + 159 + self._queue.put_nowait(message) + return True + + def _get_message(self): + return self._queue.get() + + def _process_messages_loop(self): + while not self._closed: + x0 = len(str(message)) + 0 + x1 = len(str(message)) + 1 + x2 = len(str(message)) + 2 + x3 = len(str(message)) + 3 + x4 = len(str(message)) + 4 + x5 = len(str(message)) + 5 + x6 = len(str(message)) + 6 + x7 = len(str(message)) + 7 + x8 = len(str(message)) + 8 + x9 = len(str(message)) + 9 + x10 = len(str(message)) + 10 + x11 = len(str(message)) + 11 + x12 = len(str(message)) + 12 + x13 = len(str(message)) + 13 + x14 = len(str(message)) + 14 + x15 = len(str(message)) + 15 + x16 = len(str(message)) + 16 + x17 = len(str(message)) + 17 + x18 = len(str(message)) + 18 + x19 = len(str(message)) + 19 + x20 = len(str(message)) + 20 + x21 = len(str(message)) + 21 + x22 = len(str(message)) + 22 + x23 = len(str(message)) + 23 + x24 = len(str(message)) + 24 + x25 = len(str(message)) + 25 + x26 = len(str(message)) + 26 + x27 = len(str(message)) + 27 + x28 = len(str(message)) + 28 + x29 = len(str(message)) + 29 + x30 = len(str(message)) + 30 + x31 = len(str(message)) + 31 + x32 = len(str(message)) + 32 + x33 = len(str(message)) + 33 + x34 = len(str(message)) + 34 + x35 = len(str(message)) + 35 + x36 = len(str(message)) + 36 + x37 = len(str(message)) + 37 + x38 = len(str(message)) + 38 + x39 = len(str(message)) + 39 + x40 = len(str(message)) + 40 + x41 = len(str(message)) + 41 + x42 = len(str(message)) + 42 + x43 = len(str(message)) + 43 + x44 = len(str(message)) + 44 + x45 = len(str(message)) + 45 + x46 = len(str(message)) + 46 + x47 = len(str(message)) + 47 + x48 = len(str(message)) + 48 + x49 = len(str(message)) + 49 + x50 = len(str(message)) + 50 + x51 = len(str(message)) + 51 + x52 = len(str(message)) + 52 + x53 = len(str(message)) + 53 + x54 = len(str(message)) + 54 + x55 = len(str(message)) + 55 + x56 = len(str(message)) + 56 + x57 = len(str(message)) + 57 + x58 = len(str(message)) + 58 + x59 = len(str(message)) + 59 + x60 = len(str(message)) + 60 + x61 = len(str(message)) + 61 + x62 = len(str(message)) + 62 + x63 = len(str(message)) + 63 + x64 = len(str(message)) + 64 + x65 = len(str(message)) + 65 + x66 = len(str(message)) + 66 + x67 = len(str(message)) + 67 + x68 = len(str(message)) + 68 + x69 = len(str(message)) + 69 + x70 = len(str(message)) + 70 + x71 = len(str(message)) + 71 + x72 = len(str(message)) + 72 + x73 = len(str(message)) + 73 + x74 = len(str(message)) + 74 + x75 = len(str(message)) + 75 + x76 = len(str(message)) + 76 + x77 = len(str(message)) + 77 + x78 = len(str(message)) + 78 + x79 = len(str(message)) + 79 + x80 = len(str(message)) + 80 + x81 = len(str(message)) + 81 + x82 = len(str(message)) + 82 + x83 = len(str(message)) + 83 + x84 = len(str(message)) + 84 + x85 = len(str(message)) + 85 + x86 = len(str(message)) + 86 + x87 = len(str(message)) + 87 + x88 = len(str(message)) + 88 + x89 = len(str(message)) + 89 + x90 = len(str(message)) + 90 + x91 = len(str(message)) + 91 + x92 = len(str(message)) + 92 + x93 = len(str(message)) + 93 + x94 = len(str(message)) + 94 + x95 = len(str(message)) + 95 + x96 = len(str(message)) + 96 + x97 = len(str(message)) + 97 + x98 = len(str(message)) + 98 + x99 = len(str(message)) + 99 + x100 = len(str(message)) + 100 + x101 = len(str(message)) + 101 + x102 = len(str(message)) + 102 + x103 = len(str(message)) + 103 + x104 = len(str(message)) + 104 + x105 = len(str(message)) + 105 + x106 = len(str(message)) + 106 + x107 = len(str(message)) + 107 + x108 = len(str(message)) + 108 + x109 = len(str(message)) + 109 + x110 = len(str(message)) + 110 + x111 = len(str(message)) + 111 + x112 = len(str(message)) + 112 + x113 = len(str(message)) + 113 + x114 = len(str(message)) + 114 + x115 = len(str(message)) + 115 + x116 = len(str(message)) + 116 + x117 = len(str(message)) + 117 + x118 = len(str(message)) + 118 + x119 = len(str(message)) + 119 + x120 = len(str(message)) + 120 + x121 = len(str(message)) + 121 + x122 = len(str(message)) + 122 + x123 = len(str(message)) + 123 + x124 = len(str(message)) + 124 + x125 = len(str(message)) + 125 + x126 = len(str(message)) + 126 + x127 = len(str(message)) + 127 + x128 = len(str(message)) + 128 + x129 = len(str(message)) + 129 + x130 = len(str(message)) + 130 + x131 = len(str(message)) + 131 + x132 = len(str(message)) + 132 + x133 = len(str(message)) + 133 + x134 = len(str(message)) + 134 + x135 = len(str(message)) + 135 + x136 = len(str(message)) + 136 + x137 = len(str(message)) + 137 + x138 = len(str(message)) + 138 + x139 = len(str(message)) + 139 + x140 = len(str(message)) + 140 + x141 = len(str(message)) + 141 + x142 = len(str(message)) + 142 + x143 = len(str(message)) + 143 + x144 = len(str(message)) + 144 + x145 = len(str(message)) + 145 + x146 = len(str(message)) + 146 + x147 = len(str(message)) + 147 + x148 = len(str(message)) + 148 + x149 = len(str(message)) + 149 + x150 = len(str(message)) + 150 + x151 = len(str(message)) + 151 + x152 = len(str(message)) + 152 + x153 = len(str(message)) + 153 + x154 = len(str(message)) + 154 + x155 = len(str(message)) + 155 + x156 = len(str(message)) + 156 + x157 = len(str(message)) + 157 + x158 = len(str(message)) + 158 + x159 = len(str(message)) + 159 + message = self._get_message() + self._dispatch_message(message) + + def _dispatch_message(self, message): + if message.no_dispatch: + x0 = len(str(message)) + 0 + x1 = len(str(message)) + 1 + x2 = len(str(message)) + 2 + x3 = len(str(message)) + 3 + x4 = len(str(message)) + 4 + x5 = len(str(message)) + 5 + x6 = len(str(message)) + 6 + x7 = len(str(message)) + 7 + x8 = len(str(message)) + 8 + x9 = len(str(message)) + 9 + x10 = len(str(message)) + 10 + x11 = len(str(message)) + 11 + x12 = len(str(message)) + 12 + x13 = len(str(message)) + 13 + x14 = len(str(message)) + 14 + x15 = len(str(message)) + 15 + x16 = len(str(message)) + 16 + x17 = len(str(message)) + 17 + x18 = len(str(message)) + 18 + x19 = len(str(message)) + 19 + x20 = len(str(message)) + 20 + x21 = len(str(message)) + 21 + x22 = len(str(message)) + 22 + x23 = len(str(message)) + 23 + x24 = len(str(message)) + 24 + x25 = len(str(message)) + 25 + x26 = len(str(message)) + 26 + x27 = len(str(message)) + 27 + x28 = len(str(message)) + 28 + x29 = len(str(message)) + 29 + x30 = len(str(message)) + 30 + x31 = len(str(message)) + 31 + x32 = len(str(message)) + 32 + x33 = len(str(message)) + 33 + x34 = len(str(message)) + 34 + x35 = len(str(message)) + 35 + x36 = len(str(message)) + 36 + x37 = len(str(message)) + 37 + x38 = len(str(message)) + 38 + x39 = len(str(message)) + 39 + x40 = len(str(message)) + 40 + x41 = len(str(message)) + 41 + x42 = len(str(message)) + 42 + x43 = len(str(message)) + 43 + x44 = len(str(message)) + 44 + x45 = len(str(message)) + 45 + x46 = len(str(message)) + 46 + x47 = len(str(message)) + 47 + x48 = len(str(message)) + 48 + x49 = len(str(message)) + 49 + x50 = len(str(message)) + 50 + x51 = len(str(message)) + 51 + x52 = len(str(message)) + 52 + x53 = len(str(message)) + 53 + x54 = len(str(message)) + 54 + x55 = len(str(message)) + 55 + x56 = len(str(message)) + 56 + x57 = len(str(message)) + 57 + x58 = len(str(message)) + 58 + x59 = len(str(message)) + 59 + x60 = len(str(message)) + 60 + x61 = len(str(message)) + 61 + x62 = len(str(message)) + 62 + x63 = len(str(message)) + 63 + x64 = len(str(message)) + 64 + x65 = len(str(message)) + 65 + x66 = len(str(message)) + 66 + x67 = len(str(message)) + 67 + x68 = len(str(message)) + 68 + x69 = len(str(message)) + 69 + x70 = len(str(message)) + 70 + x71 = len(str(message)) + 71 + x72 = len(str(message)) + 72 + x73 = len(str(message)) + 73 + x74 = len(str(message)) + 74 + x75 = len(str(message)) + 75 + x76 = len(str(message)) + 76 + x77 = len(str(message)) + 77 + x78 = len(str(message)) + 78 + x79 = len(str(message)) + 79 + x80 = len(str(message)) + 80 + x81 = len(str(message)) + 81 + x82 = len(str(message)) + 82 + x83 = len(str(message)) + 83 + x84 = len(str(message)) + 84 + x85 = len(str(message)) + 85 + x86 = len(str(message)) + 86 + x87 = len(str(message)) + 87 + x88 = len(str(message)) + 88 + x89 = len(str(message)) + 89 + x90 = len(str(message)) + 90 + x91 = len(str(message)) + 91 + x92 = len(str(message)) + 92 + x93 = len(str(message)) + 93 + x94 = len(str(message)) + 94 + x95 = len(str(message)) + 95 + x96 = len(str(message)) + 96 + x97 = len(str(message)) + 97 + x98 = len(str(message)) + 98 + x99 = len(str(message)) + 99 + x100 = len(str(message)) + 100 + x101 = len(str(message)) + 101 + x102 = len(str(message)) + 102 + x103 = len(str(message)) + 103 + x104 = len(str(message)) + 104 + x105 = len(str(message)) + 105 + x106 = len(str(message)) + 106 + x107 = len(str(message)) + 107 + x108 = len(str(message)) + 108 + x109 = len(str(message)) + 109 + x110 = len(str(message)) + 110 + x111 = len(str(message)) + 111 + x112 = len(str(message)) + 112 + x113 = len(str(message)) + 113 + x114 = len(str(message)) + 114 + x115 = len(str(message)) + 115 + x116 = len(str(message)) + 116 + x117 = len(str(message)) + 117 + x118 = len(str(message)) + 118 + x119 = len(str(message)) + 119 + x120 = len(str(message)) + 120 + x121 = len(str(message)) + 121 + x122 = len(str(message)) + 122 + x123 = len(str(message)) + 123 + x124 = len(str(message)) + 124 + x125 = len(str(message)) + 125 + x126 = len(str(message)) + 126 + x127 = len(str(message)) + 127 + x128 = len(str(message)) + 128 + x129 = len(str(message)) + 129 + x130 = len(str(message)) + 130 + x131 = len(str(message)) + 131 + x132 = len(str(message)) + 132 + x133 = len(str(message)) + 133 + x134 = len(str(message)) + 134 + x135 = len(str(message)) + 135 + x136 = len(str(message)) + 136 + x137 = len(str(message)) + 137 + x138 = len(str(message)) + 138 + x139 = len(str(message)) + 139 + x140 = len(str(message)) + 140 + x141 = len(str(message)) + 141 + x142 = len(str(message)) + 142 + x143 = len(str(message)) + 143 + x144 = len(str(message)) + 144 + x145 = len(str(message)) + 145 + x146 = len(str(message)) + 146 + x147 = len(str(message)) + 147 + x148 = len(str(message)) + 148 + x149 = len(str(message)) + 149 + x150 = len(str(message)) + 150 + x151 = len(str(message)) + 151 + x152 = len(str(message)) + 152 + x153 = len(str(message)) + 153 + x154 = len(str(message)) + 154 + x155 = len(str(message)) + 155 + x156 = len(str(message)) + 156 + x157 = len(str(message)) + 157 + x158 = len(str(message)) + 158 + x159 = len(str(message)) + 159 + return + self.on_event(message) + + async def on_event(self, event: Event) -> None: + self._on_message(event) + + def _on_message(self, message): + handler_name = message.handler_name + x0 = len(str(message)) + 0 + x1 = len(str(message)) + 1 + x2 = len(str(message)) + 2 + x3 = len(str(message)) + 3 + x4 = len(str(message)) + 4 + x5 = len(str(message)) + 5 + x6 = len(str(message)) + 6 + x7 = len(str(message)) + 7 + x8 = len(str(message)) + 8 + x9 = len(str(message)) + 9 + x10 = len(str(message)) + 10 + x11 = len(str(message)) + 11 + x12 = len(str(message)) + 12 + x13 = len(str(message)) + 13 + x14 = len(str(message)) + 14 + x15 = len(str(message)) + 15 + x16 = len(str(message)) + 16 + x17 = len(str(message)) + 17 + x18 = len(str(message)) + 18 + x19 = len(str(message)) + 19 + x20 = len(str(message)) + 20 + x21 = len(str(message)) + 21 + x22 = len(str(message)) + 22 + x23 = len(str(message)) + 23 + x24 = len(str(message)) + 24 + x25 = len(str(message)) + 25 + x26 = len(str(message)) + 26 + x27 = len(str(message)) + 27 + x28 = len(str(message)) + 28 + x29 = len(str(message)) + 29 + x30 = len(str(message)) + 30 + x31 = len(str(message)) + 31 + x32 = len(str(message)) + 32 + x33 = len(str(message)) + 33 + x34 = len(str(message)) + 34 + x35 = len(str(message)) + 35 + x36 = len(str(message)) + 36 + x37 = len(str(message)) + 37 + x38 = len(str(message)) + 38 + x39 = len(str(message)) + 39 + x40 = len(str(message)) + 40 + x41 = len(str(message)) + 41 + x42 = len(str(message)) + 42 + x43 = len(str(message)) + 43 + x44 = len(str(message)) + 44 + x45 = len(str(message)) + 45 + x46 = len(str(message)) + 46 + x47 = len(str(message)) + 47 + x48 = len(str(message)) + 48 + x49 = len(str(message)) + 49 + x50 = len(str(message)) + 50 + x51 = len(str(message)) + 51 + x52 = len(str(message)) + 52 + x53 = len(str(message)) + 53 + x54 = len(str(message)) + 54 + x55 = len(str(message)) + 55 + x56 = len(str(message)) + 56 + x57 = len(str(message)) + 57 + x58 = len(str(message)) + 58 + x59 = len(str(message)) + 59 + x60 = len(str(message)) + 60 + x61 = len(str(message)) + 61 + x62 = len(str(message)) + 62 + x63 = len(str(message)) + 63 + x64 = len(str(message)) + 64 + x65 = len(str(message)) + 65 + x66 = len(str(message)) + 66 + x67 = len(str(message)) + 67 + x68 = len(str(message)) + 68 + x69 = len(str(message)) + 69 + x70 = len(str(message)) + 70 + x71 = len(str(message)) + 71 + x72 = len(str(message)) + 72 + x73 = len(str(message)) + 73 + x74 = len(str(message)) + 74 + x75 = len(str(message)) + 75 + x76 = len(str(message)) + 76 + x77 = len(str(message)) + 77 + x78 = len(str(message)) + 78 + x79 = len(str(message)) + 79 + x80 = len(str(message)) + 80 + x81 = len(str(message)) + 81 + x82 = len(str(message)) + 82 + x83 = len(str(message)) + 83 + x84 = len(str(message)) + 84 + x85 = len(str(message)) + 85 + x86 = len(str(message)) + 86 + x87 = len(str(message)) + 87 + x88 = len(str(message)) + 88 + x89 = len(str(message)) + 89 + x90 = len(str(message)) + 90 + x91 = len(str(message)) + 91 + x92 = len(str(message)) + 92 + x93 = len(str(message)) + 93 + x94 = len(str(message)) + 94 + x95 = len(str(message)) + 95 + x96 = len(str(message)) + 96 + x97 = len(str(message)) + 97 + x98 = len(str(message)) + 98 + x99 = len(str(message)) + 99 + x100 = len(str(message)) + 100 + x101 = len(str(message)) + 101 + x102 = len(str(message)) + 102 + x103 = len(str(message)) + 103 + x104 = len(str(message)) + 104 + x105 = len(str(message)) + 105 + x106 = len(str(message)) + 106 + x107 = len(str(message)) + 107 + x108 = len(str(message)) + 108 + x109 = len(str(message)) + 109 + x110 = len(str(message)) + 110 + x111 = len(str(message)) + 111 + x112 = len(str(message)) + 112 + x113 = len(str(message)) + 113 + x114 = len(str(message)) + 114 + x115 = len(str(message)) + 115 + x116 = len(str(message)) + 116 + x117 = len(str(message)) + 117 + x118 = len(str(message)) + 118 + x119 = len(str(message)) + 119 + x120 = len(str(message)) + 120 + x121 = len(str(message)) + 121 + x122 = len(str(message)) + 122 + x123 = len(str(message)) + 123 + x124 = len(str(message)) + 124 + x125 = len(str(message)) + 125 + x126 = len(str(message)) + 126 + x127 = len(str(message)) + 127 + x128 = len(str(message)) + 128 + x129 = len(str(message)) + 129 + x130 = len(str(message)) + 130 + x131 = len(str(message)) + 131 + x132 = len(str(message)) + 132 + x133 = len(str(message)) + 133 + x134 = len(str(message)) + 134 + x135 = len(str(message)) + 135 + x136 = len(str(message)) + 136 + x137 = len(str(message)) + 137 + x138 = len(str(message)) + 138 + x139 = len(str(message)) + 139 + x140 = len(str(message)) + 140 + x141 = len(str(message)) + 141 + x142 = len(str(message)) + 142 + x143 = len(str(message)) + 143 + x144 = len(str(message)) + 144 + x145 = len(str(message)) + 145 + x146 = len(str(message)) + 146 + x147 = len(str(message)) + 147 + x148 = len(str(message)) + 148 + x149 = len(str(message)) + 149 + x150 = len(str(message)) + 150 + x151 = len(str(message)) + 151 + x152 = len(str(message)) + 152 + x153 = len(str(message)) + 153 + x154 = len(str(message)) + 154 + x155 = len(str(message)) + 155 + x156 = len(str(message)) + 156 + x157 = len(str(message)) + 157 + x158 = len(str(message)) + 158 + x159 = len(str(message)) + 159 + for method in self._get_dispatch_methods(handler_name, message): + method(message) + if message.bubble and self._parent is not None: + self._parent.post_message(message) + + def _get_dispatch_methods(self, method_name, message): + for cls in self.__class__.__mro__: + method = cls.__dict__.get(method_name) + if method is not None: + yield method.__get__(self, cls) diff --git a/test/hopbodyfix/routebig/widgets.py b/test/hopbodyfix/routebig/widgets.py new file mode 100644 index 000000000..c05704316 --- /dev/null +++ b/test/hopbodyfix/routebig/widgets.py @@ -0,0 +1,53 @@ +from message import Message +from pump import Pump + + +def leaf(x): + return x + + +class Tick(Message): + pass + + +class Button(Pump): + class Pressed(Message): + pass + + def press(self): + self.post_message(self.Pressed()) + + +class Worker(Pump): + class Changed(Message, namespace="worker"): + pass + + def change(self): + self.post_message(Worker.Changed()) + + +class Form(Pump): + def on_button_pressed(self, m): + return leaf(m) + + def on_worker_changed(self, m): + return m + + def on_tick(self, m): + return leaf(m) + + +def log(m): + return m + + +class Clock(Pump): + def tick(self): + self.post_message(Tick()) + + def poke(self): + msg = Tick() + self.post_message(msg) + + def note(self): + log(Tick()) diff --git a/test/hopbodyfix/routededupe/case.py b/test/hopbodyfix/routededupe/case.py new file mode 100644 index 000000000..980b8ceb8 --- /dev/null +++ b/test/hopbodyfix/routededupe/case.py @@ -0,0 +1,9 @@ +import re + + +def camel_to_snake(name, _re=re.compile("[a-z][A-Z]")): + def repl(m): + lower, upper = m.group() + return f"{lower}_{upper.lower()}" + + return _re.sub(repl, name).lower() diff --git a/test/hopbodyfix/routededupe/message.py b/test/hopbodyfix/routededupe/message.py new file mode 100644 index 000000000..6ab2d25fa --- /dev/null +++ b/test/hopbodyfix/routededupe/message.py @@ -0,0 +1,14 @@ +from case import camel_to_snake + + +class Message: + namespace = "" + + def __init_subclass__(cls, namespace=None): + super().__init_subclass__() + if namespace is not None: + name = f"{namespace}_{camel_to_snake(cls.__name__)}" + else: + parts = cls.__qualname__.rsplit(".", 2)[-2:] + name = "_".join(camel_to_snake(p) for p in parts) + cls.handler_name = f"on_{name}" diff --git a/test/hopbodyfix/routededupe/pump.py b/test/hopbodyfix/routededupe/pump.py new file mode 100644 index 000000000..53fa7288f --- /dev/null +++ b/test/hopbodyfix/routededupe/pump.py @@ -0,0 +1,45 @@ +from message import Message as Event +import queue + + +class Pump: + def __init__(self, parent=None): + self._queue = queue.Queue() + self._parent = parent + + def post_message(self, message): + if self._closed: + return False + self._queue.put_nowait(message) + return True + + def _get_message(self): + return self._queue.get() + + def _process_messages_loop(self): + while not self._closed: + message = self._get_message() + self._dispatch_message(message) + + def _dispatch_message(self, message): + if message.no_dispatch: + return + name = message.handler_name + getattr(self, name)(message) + self.on_event(message) + + async def on_event(self, event: Event) -> None: + self._on_message(event) + + def _on_message(self, message): + handler_name = message.handler_name + for method in self._get_dispatch_methods(handler_name, message): + method(message) + if message.bubble and self._parent is not None: + self._parent.post_message(message) + + def _get_dispatch_methods(self, method_name, message): + for cls in self.__class__.__mro__: + method = cls.__dict__.get(method_name) + if method is not None: + yield method.__get__(self, cls) diff --git a/test/hopbodyfix/routededupe/widgets.py b/test/hopbodyfix/routededupe/widgets.py new file mode 100644 index 000000000..c05704316 --- /dev/null +++ b/test/hopbodyfix/routededupe/widgets.py @@ -0,0 +1,53 @@ +from message import Message +from pump import Pump + + +def leaf(x): + return x + + +class Tick(Message): + pass + + +class Button(Pump): + class Pressed(Message): + pass + + def press(self): + self.post_message(self.Pressed()) + + +class Worker(Pump): + class Changed(Message, namespace="worker"): + pass + + def change(self): + self.post_message(Worker.Changed()) + + +class Form(Pump): + def on_button_pressed(self, m): + return leaf(m) + + def on_worker_changed(self, m): + return m + + def on_tick(self, m): + return leaf(m) + + +def log(m): + return m + + +class Clock(Pump): + def tick(self): + self.post_message(Tick()) + + def poke(self): + msg = Tick() + self.post_message(msg) + + def note(self): + log(Tick()) diff --git a/test/hopbodyfix/routename/case.py b/test/hopbodyfix/routename/case.py new file mode 100644 index 000000000..980b8ceb8 --- /dev/null +++ b/test/hopbodyfix/routename/case.py @@ -0,0 +1,9 @@ +import re + + +def camel_to_snake(name, _re=re.compile("[a-z][A-Z]")): + def repl(m): + lower, upper = m.group() + return f"{lower}_{upper.lower()}" + + return _re.sub(repl, name).lower() diff --git a/test/hopbodyfix/routename/message.py b/test/hopbodyfix/routename/message.py new file mode 100644 index 000000000..6ab2d25fa --- /dev/null +++ b/test/hopbodyfix/routename/message.py @@ -0,0 +1,14 @@ +from case import camel_to_snake + + +class Message: + namespace = "" + + def __init_subclass__(cls, namespace=None): + super().__init_subclass__() + if namespace is not None: + name = f"{namespace}_{camel_to_snake(cls.__name__)}" + else: + parts = cls.__qualname__.rsplit(".", 2)[-2:] + name = "_".join(camel_to_snake(p) for p in parts) + cls.handler_name = f"on_{name}" diff --git a/test/hopbodyfix/routename/pump.py b/test/hopbodyfix/routename/pump.py new file mode 100644 index 000000000..620d3940c --- /dev/null +++ b/test/hopbodyfix/routename/pump.py @@ -0,0 +1,53 @@ +from message import Message as Event +import queue + + +class Pump: + def __init__(self, parent=None): + self._queue = queue.Queue() + self._parent = parent + + def post_message(self, message): + if self._closed: + return False + self._queue.put_nowait(message) + return True + + def _get_message(self): + return self._queue.get() + + def _process_messages_loop(self): + while not self._closed: + message = self._get_message() + self.router.dispatch_message(message) + + def _dispatch_message(self, message): + if message.no_dispatch: + return + self.on_event(message) + + async def on_event(self, event: Event) -> None: + self._on_message(event) + + def _on_message(self, message): + handler_name = message.handler_name + for method in self._get_dispatch_methods(handler_name, message): + method(message) + if message.bubble and self._parent is not None: + self._parent.post_message(message) + + def _get_dispatch_methods(self, method_name, message): + for cls in self.__class__.__mro__: + method = cls.__dict__.get(method_name) + if method is not None: + yield method.__get__(self, cls) + + +class RouterA: + def dispatch_message(self, message): + self.pump.on_event(message) + + +class RouterB: + def dispatch_message(self, message): + return message diff --git a/test/hopbodyfix/routename/widgets.py b/test/hopbodyfix/routename/widgets.py new file mode 100644 index 000000000..c05704316 --- /dev/null +++ b/test/hopbodyfix/routename/widgets.py @@ -0,0 +1,53 @@ +from message import Message +from pump import Pump + + +def leaf(x): + return x + + +class Tick(Message): + pass + + +class Button(Pump): + class Pressed(Message): + pass + + def press(self): + self.post_message(self.Pressed()) + + +class Worker(Pump): + class Changed(Message, namespace="worker"): + pass + + def change(self): + self.post_message(Worker.Changed()) + + +class Form(Pump): + def on_button_pressed(self, m): + return leaf(m) + + def on_worker_changed(self, m): + return m + + def on_tick(self, m): + return leaf(m) + + +def log(m): + return m + + +class Clock(Pump): + def tick(self): + self.post_message(Tick()) + + def poke(self): + msg = Tick() + self.post_message(msg) + + def note(self): + log(Tick()) diff --git a/test/hopbodyfix/routeslot/src/ops.c b/test/hopbodyfix/routeslot/src/ops.c new file mode 100644 index 000000000..d78c39edc --- /dev/null +++ b/test/hopbodyfix/routeslot/src/ops.c @@ -0,0 +1,14 @@ +struct ops { void (*run)(int v); }; +void sink_write(int v); +static void run_fast(int v) { sink_write(v + 1); } +static void run_slow(int v) { int w = v * 2; w = w + 3; sink_write(w); } +static void run_noop(int v) { (void)v; } +static struct ops g_ops_a = { .run = run_fast }; +static struct ops g_ops_b = { .run = run_slow }; +static struct ops g_ops_c = { .run = run_noop }; +struct ops *pick(int k) { return k ? &g_ops_a : (k < 0 ? &g_ops_c : &g_ops_b); } +void start_job(struct ops *o, int v) +{ + o->run(v); +} +void sink_write(int v) { (void)v; } From a72a51241d888fd2255a5e22e4eaa3ffec273904 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:25:52 -0400 Subject: [PATCH 81/95] path-hop-body fix2: generate the R6-R8 route fixtures from the route fixture (no committed clones) Co-Authored-By: Claude Sonnet 5.5 --- test/hopbodycheck.sh | 17 +- test/hopbodyfix/routebig/case.py | 9 - test/hopbodyfix/routebig/message.py | 14 - test/hopbodyfix/routebig/pump.py | 683 ------------------------- test/hopbodyfix/routebig/widgets.py | 53 -- test/hopbodyfix/routededupe/case.py | 9 - test/hopbodyfix/routededupe/message.py | 14 - test/hopbodyfix/routededupe/pump.py | 45 -- test/hopbodyfix/routededupe/widgets.py | 53 -- test/hopbodyfix/routename/case.py | 9 - test/hopbodyfix/routename/message.py | 14 - test/hopbodyfix/routename/pump.py | 53 -- test/hopbodyfix/routename/widgets.py | 53 -- 13 files changed, 16 insertions(+), 1010 deletions(-) delete mode 100644 test/hopbodyfix/routebig/case.py delete mode 100644 test/hopbodyfix/routebig/message.py delete mode 100644 test/hopbodyfix/routebig/pump.py delete mode 100644 test/hopbodyfix/routebig/widgets.py delete mode 100644 test/hopbodyfix/routededupe/case.py delete mode 100644 test/hopbodyfix/routededupe/message.py delete mode 100644 test/hopbodyfix/routededupe/pump.py delete mode 100644 test/hopbodyfix/routededupe/widgets.py delete mode 100644 test/hopbodyfix/routename/case.py delete mode 100644 test/hopbodyfix/routename/message.py delete mode 100644 test/hopbodyfix/routename/pump.py delete mode 100644 test/hopbodyfix/routename/widgets.py diff --git a/test/hopbodycheck.sh b/test/hopbodycheck.sh index 72b55531a..32cf5826f 100644 --- a/test/hopbodycheck.sh +++ b/test/hopbodycheck.sh @@ -114,7 +114,7 @@ run "$LOO" --callees=handleLooseRequest >"$TMP/f4c" # FIX1 fixtures (the route ones run from a copy outside test/: a reader under a test path is not a handler_rule reader) HB="test/hopbodyfix" -cp -R "$HB/route" "$HB/routecap" "$HB/routename" "$HB/routebig" "$HB/routededupe" "$HB/routeslot" "$TMP/" +cp -R "$HB/route" "$HB/routecap" "$HB/routeslot" "$TMP/" run "$HB/byname" --path=startJ,sinkJ >"$TMP/h1" run "$HB/byname" --path=startJ,sinkJ --legend=full >"$TMP/h1f" run "$HB/declined" --for="how does relayWorkItem relay a work item" >"$TMP/h2a" @@ -139,6 +139,21 @@ rrun route '--path=post_message,on_*' --legend=full >"$TMP/r1f" rrun routecap '--path=post_message,on_*' >"$TMP/r2" rrun route '--path=press,on_event' >"$TMP/r3" rrun route --for="Trace the path from post_message() to on_event()" >"$TMP/r5" +# the R6-R8 fixtures are the route fixture with one edit each (so no committed clone): R6 a by-name call (two same-named routers), R7 four bodies +# over 4000 B, R8 the reader _on_message is also reached as a bridge row (the loop calls it by way of _dispatch_message) +python3 - "$TMP" <<'PY' +import os, shutil, sys +T = sys.argv[1]; src = open(os.path.join(T, "route", "pump.py")).read().split("\n") +def make(name, lines): + shutil.copytree(os.path.join(T, "route"), os.path.join(T, name)); open(os.path.join(T, name, "pump.py"), "w").write("\n".join(lines)) +by = [l.replace("self._dispatch_message(message)", "self.router.dispatch_message(message)") for l in src] +make("routename", by + ["", "", "class RouterA:", " def dispatch_message(self, message):", " self.pump.on_event(message)", "", "", "class RouterB:", " def dispatch_message(self, message):", " return message", ""]) +ded = list(src); ded[26:26] = [" name = message.handler_name", " getattr(self, name)(message)"] +make("routededupe", ded) +big = list(src) +for at in (33, 25, 20, 12): big[at:at] = [" x%d = len(str(message)) + %d" % (i, i) for i in range(160)] +make("routebig", big) +PY rrun routename '--path=post_message,on_*' >"$TMP/r6" rrun routebig '--path=post_message,on_*' >"$TMP/r7" rrun routededupe '--path=post_message,on_*' >"$TMP/r8" diff --git a/test/hopbodyfix/routebig/case.py b/test/hopbodyfix/routebig/case.py deleted file mode 100644 index 980b8ceb8..000000000 --- a/test/hopbodyfix/routebig/case.py +++ /dev/null @@ -1,9 +0,0 @@ -import re - - -def camel_to_snake(name, _re=re.compile("[a-z][A-Z]")): - def repl(m): - lower, upper = m.group() - return f"{lower}_{upper.lower()}" - - return _re.sub(repl, name).lower() diff --git a/test/hopbodyfix/routebig/message.py b/test/hopbodyfix/routebig/message.py deleted file mode 100644 index 6ab2d25fa..000000000 --- a/test/hopbodyfix/routebig/message.py +++ /dev/null @@ -1,14 +0,0 @@ -from case import camel_to_snake - - -class Message: - namespace = "" - - def __init_subclass__(cls, namespace=None): - super().__init_subclass__() - if namespace is not None: - name = f"{namespace}_{camel_to_snake(cls.__name__)}" - else: - parts = cls.__qualname__.rsplit(".", 2)[-2:] - name = "_".join(camel_to_snake(p) for p in parts) - cls.handler_name = f"on_{name}" diff --git a/test/hopbodyfix/routebig/pump.py b/test/hopbodyfix/routebig/pump.py deleted file mode 100644 index 768daa7d5..000000000 --- a/test/hopbodyfix/routebig/pump.py +++ /dev/null @@ -1,683 +0,0 @@ -from message import Message as Event -import queue - - -class Pump: - def __init__(self, parent=None): - self._queue = queue.Queue() - self._parent = parent - - def post_message(self, message): - if self._closed: - return False - x0 = len(str(message)) + 0 - x1 = len(str(message)) + 1 - x2 = len(str(message)) + 2 - x3 = len(str(message)) + 3 - x4 = len(str(message)) + 4 - x5 = len(str(message)) + 5 - x6 = len(str(message)) + 6 - x7 = len(str(message)) + 7 - x8 = len(str(message)) + 8 - x9 = len(str(message)) + 9 - x10 = len(str(message)) + 10 - x11 = len(str(message)) + 11 - x12 = len(str(message)) + 12 - x13 = len(str(message)) + 13 - x14 = len(str(message)) + 14 - x15 = len(str(message)) + 15 - x16 = len(str(message)) + 16 - x17 = len(str(message)) + 17 - x18 = len(str(message)) + 18 - x19 = len(str(message)) + 19 - x20 = len(str(message)) + 20 - x21 = len(str(message)) + 21 - x22 = len(str(message)) + 22 - x23 = len(str(message)) + 23 - x24 = len(str(message)) + 24 - x25 = len(str(message)) + 25 - x26 = len(str(message)) + 26 - x27 = len(str(message)) + 27 - x28 = len(str(message)) + 28 - x29 = len(str(message)) + 29 - x30 = len(str(message)) + 30 - x31 = len(str(message)) + 31 - x32 = len(str(message)) + 32 - x33 = len(str(message)) + 33 - x34 = len(str(message)) + 34 - x35 = len(str(message)) + 35 - x36 = len(str(message)) + 36 - x37 = len(str(message)) + 37 - x38 = len(str(message)) + 38 - x39 = len(str(message)) + 39 - x40 = len(str(message)) + 40 - x41 = len(str(message)) + 41 - x42 = len(str(message)) + 42 - x43 = len(str(message)) + 43 - x44 = len(str(message)) + 44 - x45 = len(str(message)) + 45 - x46 = len(str(message)) + 46 - x47 = len(str(message)) + 47 - x48 = len(str(message)) + 48 - x49 = len(str(message)) + 49 - x50 = len(str(message)) + 50 - x51 = len(str(message)) + 51 - x52 = len(str(message)) + 52 - x53 = len(str(message)) + 53 - x54 = len(str(message)) + 54 - x55 = len(str(message)) + 55 - x56 = len(str(message)) + 56 - x57 = len(str(message)) + 57 - x58 = len(str(message)) + 58 - x59 = len(str(message)) + 59 - x60 = len(str(message)) + 60 - x61 = len(str(message)) + 61 - x62 = len(str(message)) + 62 - x63 = len(str(message)) + 63 - x64 = len(str(message)) + 64 - x65 = len(str(message)) + 65 - x66 = len(str(message)) + 66 - x67 = len(str(message)) + 67 - x68 = len(str(message)) + 68 - x69 = len(str(message)) + 69 - x70 = len(str(message)) + 70 - x71 = len(str(message)) + 71 - x72 = len(str(message)) + 72 - x73 = len(str(message)) + 73 - x74 = len(str(message)) + 74 - x75 = len(str(message)) + 75 - x76 = len(str(message)) + 76 - x77 = len(str(message)) + 77 - x78 = len(str(message)) + 78 - x79 = len(str(message)) + 79 - x80 = len(str(message)) + 80 - x81 = len(str(message)) + 81 - x82 = len(str(message)) + 82 - x83 = len(str(message)) + 83 - x84 = len(str(message)) + 84 - x85 = len(str(message)) + 85 - x86 = len(str(message)) + 86 - x87 = len(str(message)) + 87 - x88 = len(str(message)) + 88 - x89 = len(str(message)) + 89 - x90 = len(str(message)) + 90 - x91 = len(str(message)) + 91 - x92 = len(str(message)) + 92 - x93 = len(str(message)) + 93 - x94 = len(str(message)) + 94 - x95 = len(str(message)) + 95 - x96 = len(str(message)) + 96 - x97 = len(str(message)) + 97 - x98 = len(str(message)) + 98 - x99 = len(str(message)) + 99 - x100 = len(str(message)) + 100 - x101 = len(str(message)) + 101 - x102 = len(str(message)) + 102 - x103 = len(str(message)) + 103 - x104 = len(str(message)) + 104 - x105 = len(str(message)) + 105 - x106 = len(str(message)) + 106 - x107 = len(str(message)) + 107 - x108 = len(str(message)) + 108 - x109 = len(str(message)) + 109 - x110 = len(str(message)) + 110 - x111 = len(str(message)) + 111 - x112 = len(str(message)) + 112 - x113 = len(str(message)) + 113 - x114 = len(str(message)) + 114 - x115 = len(str(message)) + 115 - x116 = len(str(message)) + 116 - x117 = len(str(message)) + 117 - x118 = len(str(message)) + 118 - x119 = len(str(message)) + 119 - x120 = len(str(message)) + 120 - x121 = len(str(message)) + 121 - x122 = len(str(message)) + 122 - x123 = len(str(message)) + 123 - x124 = len(str(message)) + 124 - x125 = len(str(message)) + 125 - x126 = len(str(message)) + 126 - x127 = len(str(message)) + 127 - x128 = len(str(message)) + 128 - x129 = len(str(message)) + 129 - x130 = len(str(message)) + 130 - x131 = len(str(message)) + 131 - x132 = len(str(message)) + 132 - x133 = len(str(message)) + 133 - x134 = len(str(message)) + 134 - x135 = len(str(message)) + 135 - x136 = len(str(message)) + 136 - x137 = len(str(message)) + 137 - x138 = len(str(message)) + 138 - x139 = len(str(message)) + 139 - x140 = len(str(message)) + 140 - x141 = len(str(message)) + 141 - x142 = len(str(message)) + 142 - x143 = len(str(message)) + 143 - x144 = len(str(message)) + 144 - x145 = len(str(message)) + 145 - x146 = len(str(message)) + 146 - x147 = len(str(message)) + 147 - x148 = len(str(message)) + 148 - x149 = len(str(message)) + 149 - x150 = len(str(message)) + 150 - x151 = len(str(message)) + 151 - x152 = len(str(message)) + 152 - x153 = len(str(message)) + 153 - x154 = len(str(message)) + 154 - x155 = len(str(message)) + 155 - x156 = len(str(message)) + 156 - x157 = len(str(message)) + 157 - x158 = len(str(message)) + 158 - x159 = len(str(message)) + 159 - self._queue.put_nowait(message) - return True - - def _get_message(self): - return self._queue.get() - - def _process_messages_loop(self): - while not self._closed: - x0 = len(str(message)) + 0 - x1 = len(str(message)) + 1 - x2 = len(str(message)) + 2 - x3 = len(str(message)) + 3 - x4 = len(str(message)) + 4 - x5 = len(str(message)) + 5 - x6 = len(str(message)) + 6 - x7 = len(str(message)) + 7 - x8 = len(str(message)) + 8 - x9 = len(str(message)) + 9 - x10 = len(str(message)) + 10 - x11 = len(str(message)) + 11 - x12 = len(str(message)) + 12 - x13 = len(str(message)) + 13 - x14 = len(str(message)) + 14 - x15 = len(str(message)) + 15 - x16 = len(str(message)) + 16 - x17 = len(str(message)) + 17 - x18 = len(str(message)) + 18 - x19 = len(str(message)) + 19 - x20 = len(str(message)) + 20 - x21 = len(str(message)) + 21 - x22 = len(str(message)) + 22 - x23 = len(str(message)) + 23 - x24 = len(str(message)) + 24 - x25 = len(str(message)) + 25 - x26 = len(str(message)) + 26 - x27 = len(str(message)) + 27 - x28 = len(str(message)) + 28 - x29 = len(str(message)) + 29 - x30 = len(str(message)) + 30 - x31 = len(str(message)) + 31 - x32 = len(str(message)) + 32 - x33 = len(str(message)) + 33 - x34 = len(str(message)) + 34 - x35 = len(str(message)) + 35 - x36 = len(str(message)) + 36 - x37 = len(str(message)) + 37 - x38 = len(str(message)) + 38 - x39 = len(str(message)) + 39 - x40 = len(str(message)) + 40 - x41 = len(str(message)) + 41 - x42 = len(str(message)) + 42 - x43 = len(str(message)) + 43 - x44 = len(str(message)) + 44 - x45 = len(str(message)) + 45 - x46 = len(str(message)) + 46 - x47 = len(str(message)) + 47 - x48 = len(str(message)) + 48 - x49 = len(str(message)) + 49 - x50 = len(str(message)) + 50 - x51 = len(str(message)) + 51 - x52 = len(str(message)) + 52 - x53 = len(str(message)) + 53 - x54 = len(str(message)) + 54 - x55 = len(str(message)) + 55 - x56 = len(str(message)) + 56 - x57 = len(str(message)) + 57 - x58 = len(str(message)) + 58 - x59 = len(str(message)) + 59 - x60 = len(str(message)) + 60 - x61 = len(str(message)) + 61 - x62 = len(str(message)) + 62 - x63 = len(str(message)) + 63 - x64 = len(str(message)) + 64 - x65 = len(str(message)) + 65 - x66 = len(str(message)) + 66 - x67 = len(str(message)) + 67 - x68 = len(str(message)) + 68 - x69 = len(str(message)) + 69 - x70 = len(str(message)) + 70 - x71 = len(str(message)) + 71 - x72 = len(str(message)) + 72 - x73 = len(str(message)) + 73 - x74 = len(str(message)) + 74 - x75 = len(str(message)) + 75 - x76 = len(str(message)) + 76 - x77 = len(str(message)) + 77 - x78 = len(str(message)) + 78 - x79 = len(str(message)) + 79 - x80 = len(str(message)) + 80 - x81 = len(str(message)) + 81 - x82 = len(str(message)) + 82 - x83 = len(str(message)) + 83 - x84 = len(str(message)) + 84 - x85 = len(str(message)) + 85 - x86 = len(str(message)) + 86 - x87 = len(str(message)) + 87 - x88 = len(str(message)) + 88 - x89 = len(str(message)) + 89 - x90 = len(str(message)) + 90 - x91 = len(str(message)) + 91 - x92 = len(str(message)) + 92 - x93 = len(str(message)) + 93 - x94 = len(str(message)) + 94 - x95 = len(str(message)) + 95 - x96 = len(str(message)) + 96 - x97 = len(str(message)) + 97 - x98 = len(str(message)) + 98 - x99 = len(str(message)) + 99 - x100 = len(str(message)) + 100 - x101 = len(str(message)) + 101 - x102 = len(str(message)) + 102 - x103 = len(str(message)) + 103 - x104 = len(str(message)) + 104 - x105 = len(str(message)) + 105 - x106 = len(str(message)) + 106 - x107 = len(str(message)) + 107 - x108 = len(str(message)) + 108 - x109 = len(str(message)) + 109 - x110 = len(str(message)) + 110 - x111 = len(str(message)) + 111 - x112 = len(str(message)) + 112 - x113 = len(str(message)) + 113 - x114 = len(str(message)) + 114 - x115 = len(str(message)) + 115 - x116 = len(str(message)) + 116 - x117 = len(str(message)) + 117 - x118 = len(str(message)) + 118 - x119 = len(str(message)) + 119 - x120 = len(str(message)) + 120 - x121 = len(str(message)) + 121 - x122 = len(str(message)) + 122 - x123 = len(str(message)) + 123 - x124 = len(str(message)) + 124 - x125 = len(str(message)) + 125 - x126 = len(str(message)) + 126 - x127 = len(str(message)) + 127 - x128 = len(str(message)) + 128 - x129 = len(str(message)) + 129 - x130 = len(str(message)) + 130 - x131 = len(str(message)) + 131 - x132 = len(str(message)) + 132 - x133 = len(str(message)) + 133 - x134 = len(str(message)) + 134 - x135 = len(str(message)) + 135 - x136 = len(str(message)) + 136 - x137 = len(str(message)) + 137 - x138 = len(str(message)) + 138 - x139 = len(str(message)) + 139 - x140 = len(str(message)) + 140 - x141 = len(str(message)) + 141 - x142 = len(str(message)) + 142 - x143 = len(str(message)) + 143 - x144 = len(str(message)) + 144 - x145 = len(str(message)) + 145 - x146 = len(str(message)) + 146 - x147 = len(str(message)) + 147 - x148 = len(str(message)) + 148 - x149 = len(str(message)) + 149 - x150 = len(str(message)) + 150 - x151 = len(str(message)) + 151 - x152 = len(str(message)) + 152 - x153 = len(str(message)) + 153 - x154 = len(str(message)) + 154 - x155 = len(str(message)) + 155 - x156 = len(str(message)) + 156 - x157 = len(str(message)) + 157 - x158 = len(str(message)) + 158 - x159 = len(str(message)) + 159 - message = self._get_message() - self._dispatch_message(message) - - def _dispatch_message(self, message): - if message.no_dispatch: - x0 = len(str(message)) + 0 - x1 = len(str(message)) + 1 - x2 = len(str(message)) + 2 - x3 = len(str(message)) + 3 - x4 = len(str(message)) + 4 - x5 = len(str(message)) + 5 - x6 = len(str(message)) + 6 - x7 = len(str(message)) + 7 - x8 = len(str(message)) + 8 - x9 = len(str(message)) + 9 - x10 = len(str(message)) + 10 - x11 = len(str(message)) + 11 - x12 = len(str(message)) + 12 - x13 = len(str(message)) + 13 - x14 = len(str(message)) + 14 - x15 = len(str(message)) + 15 - x16 = len(str(message)) + 16 - x17 = len(str(message)) + 17 - x18 = len(str(message)) + 18 - x19 = len(str(message)) + 19 - x20 = len(str(message)) + 20 - x21 = len(str(message)) + 21 - x22 = len(str(message)) + 22 - x23 = len(str(message)) + 23 - x24 = len(str(message)) + 24 - x25 = len(str(message)) + 25 - x26 = len(str(message)) + 26 - x27 = len(str(message)) + 27 - x28 = len(str(message)) + 28 - x29 = len(str(message)) + 29 - x30 = len(str(message)) + 30 - x31 = len(str(message)) + 31 - x32 = len(str(message)) + 32 - x33 = len(str(message)) + 33 - x34 = len(str(message)) + 34 - x35 = len(str(message)) + 35 - x36 = len(str(message)) + 36 - x37 = len(str(message)) + 37 - x38 = len(str(message)) + 38 - x39 = len(str(message)) + 39 - x40 = len(str(message)) + 40 - x41 = len(str(message)) + 41 - x42 = len(str(message)) + 42 - x43 = len(str(message)) + 43 - x44 = len(str(message)) + 44 - x45 = len(str(message)) + 45 - x46 = len(str(message)) + 46 - x47 = len(str(message)) + 47 - x48 = len(str(message)) + 48 - x49 = len(str(message)) + 49 - x50 = len(str(message)) + 50 - x51 = len(str(message)) + 51 - x52 = len(str(message)) + 52 - x53 = len(str(message)) + 53 - x54 = len(str(message)) + 54 - x55 = len(str(message)) + 55 - x56 = len(str(message)) + 56 - x57 = len(str(message)) + 57 - x58 = len(str(message)) + 58 - x59 = len(str(message)) + 59 - x60 = len(str(message)) + 60 - x61 = len(str(message)) + 61 - x62 = len(str(message)) + 62 - x63 = len(str(message)) + 63 - x64 = len(str(message)) + 64 - x65 = len(str(message)) + 65 - x66 = len(str(message)) + 66 - x67 = len(str(message)) + 67 - x68 = len(str(message)) + 68 - x69 = len(str(message)) + 69 - x70 = len(str(message)) + 70 - x71 = len(str(message)) + 71 - x72 = len(str(message)) + 72 - x73 = len(str(message)) + 73 - x74 = len(str(message)) + 74 - x75 = len(str(message)) + 75 - x76 = len(str(message)) + 76 - x77 = len(str(message)) + 77 - x78 = len(str(message)) + 78 - x79 = len(str(message)) + 79 - x80 = len(str(message)) + 80 - x81 = len(str(message)) + 81 - x82 = len(str(message)) + 82 - x83 = len(str(message)) + 83 - x84 = len(str(message)) + 84 - x85 = len(str(message)) + 85 - x86 = len(str(message)) + 86 - x87 = len(str(message)) + 87 - x88 = len(str(message)) + 88 - x89 = len(str(message)) + 89 - x90 = len(str(message)) + 90 - x91 = len(str(message)) + 91 - x92 = len(str(message)) + 92 - x93 = len(str(message)) + 93 - x94 = len(str(message)) + 94 - x95 = len(str(message)) + 95 - x96 = len(str(message)) + 96 - x97 = len(str(message)) + 97 - x98 = len(str(message)) + 98 - x99 = len(str(message)) + 99 - x100 = len(str(message)) + 100 - x101 = len(str(message)) + 101 - x102 = len(str(message)) + 102 - x103 = len(str(message)) + 103 - x104 = len(str(message)) + 104 - x105 = len(str(message)) + 105 - x106 = len(str(message)) + 106 - x107 = len(str(message)) + 107 - x108 = len(str(message)) + 108 - x109 = len(str(message)) + 109 - x110 = len(str(message)) + 110 - x111 = len(str(message)) + 111 - x112 = len(str(message)) + 112 - x113 = len(str(message)) + 113 - x114 = len(str(message)) + 114 - x115 = len(str(message)) + 115 - x116 = len(str(message)) + 116 - x117 = len(str(message)) + 117 - x118 = len(str(message)) + 118 - x119 = len(str(message)) + 119 - x120 = len(str(message)) + 120 - x121 = len(str(message)) + 121 - x122 = len(str(message)) + 122 - x123 = len(str(message)) + 123 - x124 = len(str(message)) + 124 - x125 = len(str(message)) + 125 - x126 = len(str(message)) + 126 - x127 = len(str(message)) + 127 - x128 = len(str(message)) + 128 - x129 = len(str(message)) + 129 - x130 = len(str(message)) + 130 - x131 = len(str(message)) + 131 - x132 = len(str(message)) + 132 - x133 = len(str(message)) + 133 - x134 = len(str(message)) + 134 - x135 = len(str(message)) + 135 - x136 = len(str(message)) + 136 - x137 = len(str(message)) + 137 - x138 = len(str(message)) + 138 - x139 = len(str(message)) + 139 - x140 = len(str(message)) + 140 - x141 = len(str(message)) + 141 - x142 = len(str(message)) + 142 - x143 = len(str(message)) + 143 - x144 = len(str(message)) + 144 - x145 = len(str(message)) + 145 - x146 = len(str(message)) + 146 - x147 = len(str(message)) + 147 - x148 = len(str(message)) + 148 - x149 = len(str(message)) + 149 - x150 = len(str(message)) + 150 - x151 = len(str(message)) + 151 - x152 = len(str(message)) + 152 - x153 = len(str(message)) + 153 - x154 = len(str(message)) + 154 - x155 = len(str(message)) + 155 - x156 = len(str(message)) + 156 - x157 = len(str(message)) + 157 - x158 = len(str(message)) + 158 - x159 = len(str(message)) + 159 - return - self.on_event(message) - - async def on_event(self, event: Event) -> None: - self._on_message(event) - - def _on_message(self, message): - handler_name = message.handler_name - x0 = len(str(message)) + 0 - x1 = len(str(message)) + 1 - x2 = len(str(message)) + 2 - x3 = len(str(message)) + 3 - x4 = len(str(message)) + 4 - x5 = len(str(message)) + 5 - x6 = len(str(message)) + 6 - x7 = len(str(message)) + 7 - x8 = len(str(message)) + 8 - x9 = len(str(message)) + 9 - x10 = len(str(message)) + 10 - x11 = len(str(message)) + 11 - x12 = len(str(message)) + 12 - x13 = len(str(message)) + 13 - x14 = len(str(message)) + 14 - x15 = len(str(message)) + 15 - x16 = len(str(message)) + 16 - x17 = len(str(message)) + 17 - x18 = len(str(message)) + 18 - x19 = len(str(message)) + 19 - x20 = len(str(message)) + 20 - x21 = len(str(message)) + 21 - x22 = len(str(message)) + 22 - x23 = len(str(message)) + 23 - x24 = len(str(message)) + 24 - x25 = len(str(message)) + 25 - x26 = len(str(message)) + 26 - x27 = len(str(message)) + 27 - x28 = len(str(message)) + 28 - x29 = len(str(message)) + 29 - x30 = len(str(message)) + 30 - x31 = len(str(message)) + 31 - x32 = len(str(message)) + 32 - x33 = len(str(message)) + 33 - x34 = len(str(message)) + 34 - x35 = len(str(message)) + 35 - x36 = len(str(message)) + 36 - x37 = len(str(message)) + 37 - x38 = len(str(message)) + 38 - x39 = len(str(message)) + 39 - x40 = len(str(message)) + 40 - x41 = len(str(message)) + 41 - x42 = len(str(message)) + 42 - x43 = len(str(message)) + 43 - x44 = len(str(message)) + 44 - x45 = len(str(message)) + 45 - x46 = len(str(message)) + 46 - x47 = len(str(message)) + 47 - x48 = len(str(message)) + 48 - x49 = len(str(message)) + 49 - x50 = len(str(message)) + 50 - x51 = len(str(message)) + 51 - x52 = len(str(message)) + 52 - x53 = len(str(message)) + 53 - x54 = len(str(message)) + 54 - x55 = len(str(message)) + 55 - x56 = len(str(message)) + 56 - x57 = len(str(message)) + 57 - x58 = len(str(message)) + 58 - x59 = len(str(message)) + 59 - x60 = len(str(message)) + 60 - x61 = len(str(message)) + 61 - x62 = len(str(message)) + 62 - x63 = len(str(message)) + 63 - x64 = len(str(message)) + 64 - x65 = len(str(message)) + 65 - x66 = len(str(message)) + 66 - x67 = len(str(message)) + 67 - x68 = len(str(message)) + 68 - x69 = len(str(message)) + 69 - x70 = len(str(message)) + 70 - x71 = len(str(message)) + 71 - x72 = len(str(message)) + 72 - x73 = len(str(message)) + 73 - x74 = len(str(message)) + 74 - x75 = len(str(message)) + 75 - x76 = len(str(message)) + 76 - x77 = len(str(message)) + 77 - x78 = len(str(message)) + 78 - x79 = len(str(message)) + 79 - x80 = len(str(message)) + 80 - x81 = len(str(message)) + 81 - x82 = len(str(message)) + 82 - x83 = len(str(message)) + 83 - x84 = len(str(message)) + 84 - x85 = len(str(message)) + 85 - x86 = len(str(message)) + 86 - x87 = len(str(message)) + 87 - x88 = len(str(message)) + 88 - x89 = len(str(message)) + 89 - x90 = len(str(message)) + 90 - x91 = len(str(message)) + 91 - x92 = len(str(message)) + 92 - x93 = len(str(message)) + 93 - x94 = len(str(message)) + 94 - x95 = len(str(message)) + 95 - x96 = len(str(message)) + 96 - x97 = len(str(message)) + 97 - x98 = len(str(message)) + 98 - x99 = len(str(message)) + 99 - x100 = len(str(message)) + 100 - x101 = len(str(message)) + 101 - x102 = len(str(message)) + 102 - x103 = len(str(message)) + 103 - x104 = len(str(message)) + 104 - x105 = len(str(message)) + 105 - x106 = len(str(message)) + 106 - x107 = len(str(message)) + 107 - x108 = len(str(message)) + 108 - x109 = len(str(message)) + 109 - x110 = len(str(message)) + 110 - x111 = len(str(message)) + 111 - x112 = len(str(message)) + 112 - x113 = len(str(message)) + 113 - x114 = len(str(message)) + 114 - x115 = len(str(message)) + 115 - x116 = len(str(message)) + 116 - x117 = len(str(message)) + 117 - x118 = len(str(message)) + 118 - x119 = len(str(message)) + 119 - x120 = len(str(message)) + 120 - x121 = len(str(message)) + 121 - x122 = len(str(message)) + 122 - x123 = len(str(message)) + 123 - x124 = len(str(message)) + 124 - x125 = len(str(message)) + 125 - x126 = len(str(message)) + 126 - x127 = len(str(message)) + 127 - x128 = len(str(message)) + 128 - x129 = len(str(message)) + 129 - x130 = len(str(message)) + 130 - x131 = len(str(message)) + 131 - x132 = len(str(message)) + 132 - x133 = len(str(message)) + 133 - x134 = len(str(message)) + 134 - x135 = len(str(message)) + 135 - x136 = len(str(message)) + 136 - x137 = len(str(message)) + 137 - x138 = len(str(message)) + 138 - x139 = len(str(message)) + 139 - x140 = len(str(message)) + 140 - x141 = len(str(message)) + 141 - x142 = len(str(message)) + 142 - x143 = len(str(message)) + 143 - x144 = len(str(message)) + 144 - x145 = len(str(message)) + 145 - x146 = len(str(message)) + 146 - x147 = len(str(message)) + 147 - x148 = len(str(message)) + 148 - x149 = len(str(message)) + 149 - x150 = len(str(message)) + 150 - x151 = len(str(message)) + 151 - x152 = len(str(message)) + 152 - x153 = len(str(message)) + 153 - x154 = len(str(message)) + 154 - x155 = len(str(message)) + 155 - x156 = len(str(message)) + 156 - x157 = len(str(message)) + 157 - x158 = len(str(message)) + 158 - x159 = len(str(message)) + 159 - for method in self._get_dispatch_methods(handler_name, message): - method(message) - if message.bubble and self._parent is not None: - self._parent.post_message(message) - - def _get_dispatch_methods(self, method_name, message): - for cls in self.__class__.__mro__: - method = cls.__dict__.get(method_name) - if method is not None: - yield method.__get__(self, cls) diff --git a/test/hopbodyfix/routebig/widgets.py b/test/hopbodyfix/routebig/widgets.py deleted file mode 100644 index c05704316..000000000 --- a/test/hopbodyfix/routebig/widgets.py +++ /dev/null @@ -1,53 +0,0 @@ -from message import Message -from pump import Pump - - -def leaf(x): - return x - - -class Tick(Message): - pass - - -class Button(Pump): - class Pressed(Message): - pass - - def press(self): - self.post_message(self.Pressed()) - - -class Worker(Pump): - class Changed(Message, namespace="worker"): - pass - - def change(self): - self.post_message(Worker.Changed()) - - -class Form(Pump): - def on_button_pressed(self, m): - return leaf(m) - - def on_worker_changed(self, m): - return m - - def on_tick(self, m): - return leaf(m) - - -def log(m): - return m - - -class Clock(Pump): - def tick(self): - self.post_message(Tick()) - - def poke(self): - msg = Tick() - self.post_message(msg) - - def note(self): - log(Tick()) diff --git a/test/hopbodyfix/routededupe/case.py b/test/hopbodyfix/routededupe/case.py deleted file mode 100644 index 980b8ceb8..000000000 --- a/test/hopbodyfix/routededupe/case.py +++ /dev/null @@ -1,9 +0,0 @@ -import re - - -def camel_to_snake(name, _re=re.compile("[a-z][A-Z]")): - def repl(m): - lower, upper = m.group() - return f"{lower}_{upper.lower()}" - - return _re.sub(repl, name).lower() diff --git a/test/hopbodyfix/routededupe/message.py b/test/hopbodyfix/routededupe/message.py deleted file mode 100644 index 6ab2d25fa..000000000 --- a/test/hopbodyfix/routededupe/message.py +++ /dev/null @@ -1,14 +0,0 @@ -from case import camel_to_snake - - -class Message: - namespace = "" - - def __init_subclass__(cls, namespace=None): - super().__init_subclass__() - if namespace is not None: - name = f"{namespace}_{camel_to_snake(cls.__name__)}" - else: - parts = cls.__qualname__.rsplit(".", 2)[-2:] - name = "_".join(camel_to_snake(p) for p in parts) - cls.handler_name = f"on_{name}" diff --git a/test/hopbodyfix/routededupe/pump.py b/test/hopbodyfix/routededupe/pump.py deleted file mode 100644 index 53fa7288f..000000000 --- a/test/hopbodyfix/routededupe/pump.py +++ /dev/null @@ -1,45 +0,0 @@ -from message import Message as Event -import queue - - -class Pump: - def __init__(self, parent=None): - self._queue = queue.Queue() - self._parent = parent - - def post_message(self, message): - if self._closed: - return False - self._queue.put_nowait(message) - return True - - def _get_message(self): - return self._queue.get() - - def _process_messages_loop(self): - while not self._closed: - message = self._get_message() - self._dispatch_message(message) - - def _dispatch_message(self, message): - if message.no_dispatch: - return - name = message.handler_name - getattr(self, name)(message) - self.on_event(message) - - async def on_event(self, event: Event) -> None: - self._on_message(event) - - def _on_message(self, message): - handler_name = message.handler_name - for method in self._get_dispatch_methods(handler_name, message): - method(message) - if message.bubble and self._parent is not None: - self._parent.post_message(message) - - def _get_dispatch_methods(self, method_name, message): - for cls in self.__class__.__mro__: - method = cls.__dict__.get(method_name) - if method is not None: - yield method.__get__(self, cls) diff --git a/test/hopbodyfix/routededupe/widgets.py b/test/hopbodyfix/routededupe/widgets.py deleted file mode 100644 index c05704316..000000000 --- a/test/hopbodyfix/routededupe/widgets.py +++ /dev/null @@ -1,53 +0,0 @@ -from message import Message -from pump import Pump - - -def leaf(x): - return x - - -class Tick(Message): - pass - - -class Button(Pump): - class Pressed(Message): - pass - - def press(self): - self.post_message(self.Pressed()) - - -class Worker(Pump): - class Changed(Message, namespace="worker"): - pass - - def change(self): - self.post_message(Worker.Changed()) - - -class Form(Pump): - def on_button_pressed(self, m): - return leaf(m) - - def on_worker_changed(self, m): - return m - - def on_tick(self, m): - return leaf(m) - - -def log(m): - return m - - -class Clock(Pump): - def tick(self): - self.post_message(Tick()) - - def poke(self): - msg = Tick() - self.post_message(msg) - - def note(self): - log(Tick()) diff --git a/test/hopbodyfix/routename/case.py b/test/hopbodyfix/routename/case.py deleted file mode 100644 index 980b8ceb8..000000000 --- a/test/hopbodyfix/routename/case.py +++ /dev/null @@ -1,9 +0,0 @@ -import re - - -def camel_to_snake(name, _re=re.compile("[a-z][A-Z]")): - def repl(m): - lower, upper = m.group() - return f"{lower}_{upper.lower()}" - - return _re.sub(repl, name).lower() diff --git a/test/hopbodyfix/routename/message.py b/test/hopbodyfix/routename/message.py deleted file mode 100644 index 6ab2d25fa..000000000 --- a/test/hopbodyfix/routename/message.py +++ /dev/null @@ -1,14 +0,0 @@ -from case import camel_to_snake - - -class Message: - namespace = "" - - def __init_subclass__(cls, namespace=None): - super().__init_subclass__() - if namespace is not None: - name = f"{namespace}_{camel_to_snake(cls.__name__)}" - else: - parts = cls.__qualname__.rsplit(".", 2)[-2:] - name = "_".join(camel_to_snake(p) for p in parts) - cls.handler_name = f"on_{name}" diff --git a/test/hopbodyfix/routename/pump.py b/test/hopbodyfix/routename/pump.py deleted file mode 100644 index 620d3940c..000000000 --- a/test/hopbodyfix/routename/pump.py +++ /dev/null @@ -1,53 +0,0 @@ -from message import Message as Event -import queue - - -class Pump: - def __init__(self, parent=None): - self._queue = queue.Queue() - self._parent = parent - - def post_message(self, message): - if self._closed: - return False - self._queue.put_nowait(message) - return True - - def _get_message(self): - return self._queue.get() - - def _process_messages_loop(self): - while not self._closed: - message = self._get_message() - self.router.dispatch_message(message) - - def _dispatch_message(self, message): - if message.no_dispatch: - return - self.on_event(message) - - async def on_event(self, event: Event) -> None: - self._on_message(event) - - def _on_message(self, message): - handler_name = message.handler_name - for method in self._get_dispatch_methods(handler_name, message): - method(message) - if message.bubble and self._parent is not None: - self._parent.post_message(message) - - def _get_dispatch_methods(self, method_name, message): - for cls in self.__class__.__mro__: - method = cls.__dict__.get(method_name) - if method is not None: - yield method.__get__(self, cls) - - -class RouterA: - def dispatch_message(self, message): - self.pump.on_event(message) - - -class RouterB: - def dispatch_message(self, message): - return message diff --git a/test/hopbodyfix/routename/widgets.py b/test/hopbodyfix/routename/widgets.py deleted file mode 100644 index c05704316..000000000 --- a/test/hopbodyfix/routename/widgets.py +++ /dev/null @@ -1,53 +0,0 @@ -from message import Message -from pump import Pump - - -def leaf(x): - return x - - -class Tick(Message): - pass - - -class Button(Pump): - class Pressed(Message): - pass - - def press(self): - self.post_message(self.Pressed()) - - -class Worker(Pump): - class Changed(Message, namespace="worker"): - pass - - def change(self): - self.post_message(Worker.Changed()) - - -class Form(Pump): - def on_button_pressed(self, m): - return leaf(m) - - def on_worker_changed(self, m): - return m - - def on_tick(self, m): - return leaf(m) - - -def log(m): - return m - - -class Clock(Pump): - def tick(self): - self.post_message(Tick()) - - def poke(self): - msg = Tick() - self.post_message(msg) - - def note(self): - log(Tick()) From c8b49812b1ae3eaad7b5ac05fcf5cf8903b7780c Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:14:27 -0400 Subject: [PATCH 82/95] train 41: versions above every placeholder, qschemetrip re-pin, generated counts kParserVer 251 -> 259 (rich file tag 260): fn-slot-2's Rust slot records (placeholder 249/250) and py-split-marker's conditional module-scope `?name` record (255/256) change extraction; 259 is above both and above fn-slot-s2's 257/258. kCacheVersion stays 46 and kQSnapCacheScheme stays 24 (no record changes shape; no watched quality.h function moved). quality.h mirror, changelog.d/versions.md (and the session dictionary dictv 981), the qschemetrip RE-PIN LOG entry and pin. Gate count 729, LIMITS (298 caps) and TUNING re-derived once; README's cap sentence follows LIMITS. Co-Authored-By: Claude Opus 5.5 --- README.md | 6 +++--- changelog.d/versions.md | 10 +++++----- docs/EVALS.md | 6 +++--- docs/LIMITS.md | 10 +++++----- docs/TUNING.md | 18 +++++++++--------- present/deck5_ripwire_build.js | 6 +++--- src/ingest_cache.h | 7 ++++++- src/quality.h | 4 +++- test/qschemetrip.hash | 2 +- test/qschemetripcheck.sh | 5 +++++ 10 files changed, 43 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 80ad418f9..e6d3443d1 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
-727 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +729 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 **727 gate scripts** and is the authoritative list; +`test/regression.sh` names **729 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 288 compile-time caps and 7 ranking parameters. +Another gate derives the cap inventory. The tool has 298 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/versions.md b/changelog.d/versions.md index 2ddd3744b..19cb7525c 100644 --- a/changelog.d/versions.md +++ b/changelog.d/versions.md @@ -1,6 +1,6 @@ ### Changed — the versions this change moves, stated once -`kParserVer` 206 → 251 (file tag 252): the Rust written-type receivers carried 202 (203) on their branch and the struct-field +`kParserVer` 206 → 259 (file tag 260): 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; the JS/TS written-binding records carried 218 and the Python rebind-withholding records a 224 placeholder on theirs; train 34 took 226; the Kotlin trailing-lambda capture (#155) @@ -10,11 +10,11 @@ records (a Python queue call's keyword arguments on its call reference) carried train 38 took 235 (236); the receiver typing of TS `#private` fields and of Python annotations and `mod.X( … )` constructors carried a 237 (238) placeholder on its branch and train 39 took 239 (240); the JS/TS naming records (a bare `module.exports` function, a named object literal's binding) carried 238, the name-keyed lookup qualifier 241 (242) and the Go function-valued -slots 247 (248) on their branches, and the Rust slot join carries 249 (250) on its, so 251 sits above every live branch and -pull-request number. The Python conditional module-scope definition record (a `def` or `class` inside an `if`/`try`/`with`/loop -at module scope) carries a 255 (256) placeholder on its branch. A cache +slots 247 (248) on their branches and train 40 took 251 (252); the Rust slot join carried 249 (250) and the Python conditional +module-scope definition record (a `def` or `class` inside an `if`/`try`/`with`/loop at module scope) 255 (256) on theirs, and the +C/C++ slot join carries 257 (258) on its, so 259 sits above every live branch and pull-request number. A cache written by 0.6.8 is re-parsed once. `kCacheVersion` 36 → 46: 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, as is a JS/TS module-scope object literal's binding name, a Kotlin call's trailing-lambda byte (#155) widens the reference record, and a file's parse-health record gains the Rust re-parse count, so a cache written before them is rejected and rebuilt (the branches that added them used 37, 38, 39, 41, 42 and 45). The quality-snapshot scheme moves 22 → 24 (#228: Django and -unittest hooks and closure self-dispatch change the dead set; old snapshots are not used; the branch used 23). The session legend dictionary is `dictv=a3aaaa12b102080c entries=958`. +unittest hooks and closure self-dispatch change the dead set; old snapshots are not used; the branch used 23). The session legend dictionary is `dictv=05b48ed93c807ee2 entries=981`. diff --git a/docs/EVALS.md b/docs/EVALS.md index 4b579042d..6d03c8161 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 727 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 729 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 **727 gate scripts**, all of which exist on disk. +naming **729 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 727. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 729. 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 6a09e429e..e3d188281 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 | | --- | --- | --- | --- | -| 288 | 114 | 170 | **118** | +| 298 | 115 | 180 | **118** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 288 + 7 is the 295 constants this generator parses out of `src/`. +are not counted as caps, and 298 + 7 is the 305 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 288 caps are classified -(50 INDEXING, 46 OUTPUT, 44 BOUNDARY); the remaining 148 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **142 of 298 caps are classified +(50 INDEXING, 46 OUTPUT, 46 BOUNDARY); the remaining 156 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 114 files that declare a cap — the 288 caps counted above, and no parameter. +One table for each of the 115 files that declare a cap — the 298 caps counted above, and no parameter. ### `src/abicheck.h` diff --git a/docs/TUNING.md b/docs/TUNING.md index 685c1efa5..5c05400c6 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 | | --- | --- | --- | --- | --- | --- | -| 196 | 195 | 112 | 12 | **37** | 75 | +| 206 | 205 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **196 cap -declarations** under **195 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 **206 cap +declarations** under **205 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 195 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **72 declared since the sweep was +every run, which makes them a third population again. Enumerated over the 205 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **82 declared since the sweep was prepared, which no measurement has touched** (`kBackHopWidth`, `kBaseScopeScanCap`, -`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +66 -more). 111 + 12 + 72 = 195, and `emit` refuses to render a partition that does not add up. The +`kBridgeMaxHops`, `kCallSitesPerRowCap`, `kCallersBandMaxBytes`, `kChurnMergeBombMaxFiles`, +76 +more). 111 + 12 + 82 = 205, 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 196" would be wrong in both halves +declared in `src/` at all: `kSituTestRowsShown`. Quoting "113 of 206" 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 196-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 206-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/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index 8cf5bf989..36a2ab66a 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1214,7 +1214,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["727 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["729 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1238,7 +1238,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "727", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "729", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1540,7 +1540,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["193 long flags · 36 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["727 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["729 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 71 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/src/ingest_cache.h b/src/ingest_cache.h index 561c48113..1448a99a3 100644 --- a/src/ingest_cache.h +++ b/src/ingest_cache.h @@ -363,7 +363,12 @@ constexpr std::uint32_t kCacheVersion = 46; // 46: train 40 — lane j // (Py `pkg.mod`, TS `./x`, Rust `crate::a::b`/`mod:x`) — // a target FORMAT change → old caches must be rejected. // 4: Include gained a `bool isAngle` (quote/angle) field -constexpr std::uint32_t kParserVer = 255; // bump on any grammar/.scm/extraction change +constexpr std::uint32_t kParserVer = 259; // bump on any grammar/.scm/extraction change + // 259 = 2026-10-28 (train 41: lanes fn-slot-2 (placeholder 249/250; Rust files record the slot join's records: + // function-path arguments of path calls, field stores, registrar parameter facts, `(x.f)( … )` slot calls) and + // py-split-marker (255/256; a Python module-scope def/class inside an if/try/with/loop files a second VarDecl + // `?name`)). Strictly above train 40's 251 (rich 252), every lane placeholder (249/250, 255/256) and + // fn-slot-s2's 257/258 (train 41b takes the next). Rich file tag 260. kCacheVersion stays 46: no record changes shape. // 251 = 2026-10-27 (train 40: lanes js-def-naming (placeholder 238; JS `module.exports = ` named // by its file stem, module-scope `NAME = { … }` binding names), string-keyed (241; Pattern qualifier "t" at a // hasattr test / JS-TS subscript store) and fn-slot (247; Go function-valued slot records)). Strictly above diff --git a/src/quality.h b/src/quality.h index 96a20a26a..18d9e5e77 100644 --- a/src/quality.h +++ b/src/quality.h @@ -2441,7 +2441,9 @@ inline std::string cacheRootKeyHex( const std::string& root ) // not include this header; it relies on ingest.cpp including quality.h (line 13) before ingest_cache.h, and a reorder // that broke that fails the build on the undeclared name rather than passing. constexpr std::uint32_t kIngestCacheVersionMirror = 46; // MUST equal ingest.cpp's kCacheVersion (gated); 46 = train 40 (js-def-naming JsObjectLiteral; its placeholder 45), 44 = train 37b (rust-degraded FileHealth::useBoundsBlanked; its placeholder 42), 43 = train 36 (#155 trailingLambda ref byte; above main 40, #438's placeholder 41, rust-degraded's 42), 40 = train 34 (fc3-js-rebind JsRebind; above main 39), 39 = train 33 (above main 36 and l6-iface 38), 38 = train 32 lane l6-iface fix round 1: LocalBindKind ImplBlock (train renumbers), 37 = train 32 lane l6-iface: LocalBindKind DispatchType (train renumbers), 36 = train 30 (above call-result-typing's and fcall-forward's 33, name-template-dispatch's 35, main's 34), 34 = train 29 (above lane arity-min's and call-result-typing's 33), 33 = train 29 lane arity-min: def record paramsMin (the train renumbers), 33 = lane call-result-typing: RawBind resultIndex + unwrapDepth, 33 = lane fcall-forward: ref `forwarded` byte, 35 = lane name-template-dispatch: RefRole Pattern 11, 32 = train 28: RefRole Subscript 9 + Inert 10, 31 = train 27: lego-transitive's inherit-ref baseBind (the lane's 30), 29 = FE-B ref memberPath/memberCtor + 4 bind kinds, 28 = FE-A ref memberCall/memberRoot, 27 = corrected fnScope values, 26 = function-local def scope span (25 = #157 + #150) -constexpr std::uint32_t kIngestParserVerMirror = 255; // MUST equal ingest.cpp's kParserVer (gated) +constexpr std::uint32_t kIngestParserVerMirror = 259; // MUST equal ingest.cpp's kParserVer (gated) + // 259 = 2026-10-28 (train 41: fn-slot-2 Rust slot records / py-split-marker `?name`, + // placeholders 249, 255; above fn-slot-s2's 257/258) // 251 = 2026-10-27 (train 40: js-def-naming / string-keyed / fn-slot extraction, // placeholders 238, 241, 247; above train 39's 239/240 and fn-slot-2's 249) // 239 = 2026-10-27 (train 39: receiver-fixes typing, placeholder 237/238; above train 38 diff --git a/test/qschemetrip.hash b/test/qschemetrip.hash index f4cc73e4c..c773a1130 100644 --- a/test/qschemetrip.hash +++ b/test/qschemetrip.hash @@ -1 +1 @@ -eb9903e1e451d1655f4f5c104a805d5dfb4ccee8337e3b0a9672ab6da347a051 +7273ed82499a3ea224e7923f2c954067f9c8293e54d04ccae65e02ae518ac92a diff --git a/test/qschemetripcheck.sh b/test/qschemetripcheck.sh index 6541571e4..7e2f5c584 100755 --- a/test/qschemetripcheck.sh +++ b/test/qschemetripcheck.sh @@ -34,6 +34,11 @@ 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-28, train 41 (lanes ci-flakes-3, defs-lookup, py-split-marker, fn-slot-2, path-hop-body-fix1, upward-2hop merged onto main +# 12056c16): RE-DERIVED ONCE with UPDATE_GOLDEN=1 on the composed tree (main's pin f4d5b120… and the py-split-marker and fn-slot-2 +# (127443d5…) branch pins conflicted). Watched text: kParserVer 251 -> 259 (rich 260; above the placeholders 249, 255 and fn-slot-s2's +# 257), quality.h's kIngestParserVerMirror with it. kCacheVersion stays 46 and kQSnapCacheScheme stays 24: no record changes shape +# and no watched quality.h function moved. # 2026-10-27, lane py-split-marker (PY-ALIAS-SPLIT-MARKER, base integration/train-40 1318069a; PLACEHOLDER, the train renumbers): # RE-PINNED ONCE with UPDATE_GOLDEN=1. Watched text: kParserVer 251 -> 255 (rich 256; above fn-slot-2's 249 and train 40's 251/252: # a Python module-scope def/class inside an if/try/with/loop files a second VarDecl `?name`), quality.h's kIngestParserVerMirror with it. From fd75f9c6c37608e127b60fec6776e85da6386a22 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:22:20 -0400 Subject: [PATCH 83/95] train 41: ONE showcase regen from the composed binary (c8b49812) docs/captures/COMMANDS_showcase_2026-10-28.md re-captured in place by test/showcase_capture.py on the clean tree c8b49812 (266 commands, 220 rc 0, 35 , 0 home paths, dictv 981). Clears every lane's showcasecapturecheck (I) stale-capture red. Co-Authored-By: Claude Opus 5.5 --- docs/captures/COMMANDS_showcase_2026-10-28.md | 2833 +++++++++-------- 1 file changed, 1425 insertions(+), 1408 deletions(-) diff --git a/docs/captures/COMMANDS_showcase_2026-10-28.md b/docs/captures/COMMANDS_showcase_2026-10-28.md index 6a724b32d..43879096d 100644 --- a/docs/captures/COMMANDS_showcase_2026-10-28.md +++ b/docs/captures/COMMANDS_showcase_2026-10-28.md @@ -2,8 +2,8 @@ - **Date:** 2026-10-28 (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=982306d4f)` -- **Repo:** the ripwire repo @ `77bb261b` — **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. +- **Version:** `ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=c8b49812b)` +- **Repo:** the ripwire repo @ `c8b49812` — **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 + @@ -23,72 +23,72 @@ *The default ranked symbol map — start here when landing cold in a repo.* -**wall time: 2.62s** +**wall time: 3.34s** ````` - - + + - + + + + + + + + + - + - + - - - - - - - - -… [751 more display lines; full output is 24638 bytes on 1 raw line(s)] +… [750 more display lines; full output is 24662 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --top-k=5` *Same map, capped to the 5 highest-ranked symbols.* -**wall time: 3.87s** +**wall time: 1.99s** ````` - - + + - + + + + + - + - + - - - - - + @@ -96,38 +96,38 @@ -… [68 more display lines; full output is 8605 bytes on 1 raw line(s)] +… [68 more display lines; full output is 8648 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.* -**wall time: 3.03s** +**wall time: 2.15s** ````` - + - + + + + + - + - + - - - - ````` @@ -135,14 +135,14 @@ *NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map rides along with the body you asked for.* -**wall time: 3.10s** +**wall time: 1.82s** ````` - + - + - + @@ -213,33 +213,33 @@ ripwire: --top-k=0 means "no ranked map, payload only" — pass a payload verb ( *GATE form: exit 3 if the map's own est_tokens exceeds the budget (over-budget failure shape).* -**exit code: 3** — **wall time: 3.66s** +**exit code: 3** — **wall time: 1.59s** ````` - + ````` stderr: ````` -ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 +ripwire: --token-budget exceeded: withheld_est_tokens=9940 > 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: 10.57s** +**wall time: 7.20s** ````` - - + 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] @@ -250,37 +250,37 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 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 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 void emitGrepHandleLegend( bool enabled ) inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) -… [127 more display lines; full output is 20016 bytes on 8 raw line(s)] +… [127 more display lines; full output is 19923 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: 7.27s** +**wall time: 3.84s** ````` - + - + 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` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -289,19 +289,19 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 - - + + - - - + + + - + & p, float alpha = 0.85f ) … [28 more display lines; full output is 5863 bytes on 29 raw line(s)] ````` @@ -310,26 +310,26 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* -**wall time: 7.59s** +**wall time: 3.24s** ````` - + - + 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 ) +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 ) +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… 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] @@ -338,29 +338,29 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 inline const McpIndex& getIndex( const std::string& root ) 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 +### 2. Gates for A #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` -inline std::vector<float> anchoredLexicalRank( const Graph& g, const std::vector<float>& lex ) +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +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 ) -… [155 more display lines; full output is 24099 bytes on 72 raw line(s)] +… [155 more display lines; full output is 24097 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: 7.71s** +**wall time: 2.82s** ````` - + - + 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` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -374,10 +374,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *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: 9.06s** +**wall time: 3.75s** ````` - + @@ -387,7 +387,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 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 +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 @@ -407,99 +407,97 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 struct LintRule inline bool hasPytestTable( std::string_view source ) inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [139 more display lines; full output is 19846 bytes on 44 raw line(s)] +… [139 more display lines; full output is 19839 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: 9.58s** +**wall time: 2.93s** ````` - - + 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 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 std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) struct NameCorpusStats inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) -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 +inline bool hasIdentifierShape( std::string_view w ) noexcept +inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) struct LensRanking -struct Bm25Params -inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept +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] -inline constexpr int kLexWeightDoc = 2 +inline constexpr int kLexWeightDoc = 2 +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 std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) +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 ) inline double binomialChoose( std::size_t n, std::size_t k ) struct RecallSectionPick -… [146 more display lines; full output is 21540 bytes on 18 raw line(s)] +… [147 more display lines; full output is 21622 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: 7.87s** +**wall time: 3.11s** ````` - - + 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 std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) struct NameCorpusStats inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) -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 +inline bool hasIdentifierShape( std::string_view w ) noexcept +inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) struct LensRanking -struct Bm25Params -inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept -inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) +struct Bm25Params +inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept +inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) 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] -inline constexpr int kLexWeightDoc = 2 +inline constexpr int kLexWeightDoc = 2 +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 std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) +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 ) inline double binomialChoose( std::size_t n, std::size_t k ) struct RecallSectionPick -… [156 more display lines; full output is 21831 bytes on 18 raw line(s)] +… [156 more display lines; full output is 21826 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.* -**wall time: 14.59s** - ````` - + @@ -512,12 +510,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* -**wall time: 8.99s** +**wall time: 1.43s** ````` - + budget=100 *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: 5.91s** +**wall time: 1.47s** ````` @@ -553,7 +551,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *The honest half of the contract: a task with no ripwire-shaped evidence ABSTAINS with zero commands rather than guessing.* -**wall time: 6.83s** +**wall time: 2.36s** ````` @@ -566,12 +564,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* -**wall time: 2.61s** +**wall time: 4.05s** ````` -ripwire recall — "quality delta gating exit codes" — 124 relevant of 305 document files, best-first — total=124 shown=8 capped=1 truncated=6 generated_demoted=3 max_tokens=8000 share_bytes=2468 est_tokens=4165 +ripwire recall — "quality delta gating exit codes" — 124 relevant of 314 document files, best-first — total=124 shown=8 capped=1 truncated=6 generated_demoted=3 max_tokens=8000 share_bytes=2468 est_tokens=4165 -━━ README.md (relevance 8.063) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 222723 B; lines="2747-2759,2760-2767"; dropped_by_budget=23] +━━ README.md (relevance 8.079) ━━ [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. @@ -595,7 +593,7 @@ refuses by default. -━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.514) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.531) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] ### Root spelling (maintainer-owned; reproduce it once so you recognise it) ```bash @@ -604,17 +602,38 @@ refuses by default. ## `./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 1304993-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.* +*The directory-as-knowledge-base pattern: --recall pointed at a 1297195-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=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 10.137) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 595804 B; lines="3866-3870"; dropped_by_budget=164] [truncated: 1865 of 7741 bytes] -### `--field-affinity[=STRUCT]` +━━ commands.md (relevance 11.020) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 595976 B; lines="557-582"; dropped_by_budget=164] [truncated: 1854 of 4257 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: + +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)) ````` @@ -629,24 +648,24 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *File-by-file orientation map (top symbols per file).* -**wall time: 2.10s** +**wall time: 3.16s** ````` - + - - - - - + + + + + @@ -657,10 +676,10 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas - - - - + + + + … [367 more display lines; full output is 11127 bytes on 1 raw line(s)] ````` @@ -669,7 +688,7 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *Self-contained HTML force-directed call graph.* -**wall time: 9.66s** +**wall time: 10.81s** ````` (empty) @@ -678,14 +697,14 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas Artifact written: ````` - 156254 /aux/map2.html + 156086 /aux/map2.html ````` ## `./build/ripwire . --order=stable --top-k=5` *Stable (path/id) emit order — provider KV-cache hits across re-runs.* -**wall time: 3.08s** +**wall time: 3.04s** ````` @@ -709,7 +728,7 @@ Artifact written: - + @@ -718,7 +737,7 @@ Artifact written: -… [68 more display lines; full output is 8623 bytes on 1 raw line(s)] +… [68 more display lines; full output is 8666 bytes on 1 raw line(s)] ````` @@ -730,14 +749,14 @@ Artifact written: *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.* -**wall time: 1.90s** +**wall time: 4.40s** ````` - + @@ -770,14 +789,14 @@ Artifact written: *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.* -**wall time: 2.18s** +**wall time: 2.69s** ````` - + @@ -810,14 +829,14 @@ Artifact written: *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.* -**wall time: 1.98s** +**wall time: 2.23s** ````` - + @@ -850,14 +869,14 @@ Artifact written: *Who calls SYM (1-hop in-edges).* -**wall time: 1.95s** +**wall time: 1.49s** ````` - + - - + + @@ -869,15 +888,15 @@ Artifact written: *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.* -**wall time: 3.40s** +**wall time: 1.95s** ````` - + - - + + @@ -889,7 +908,7 @@ Artifact written: *Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* -**exit code: 1** — **wall time: 8.29s** +**exit code: 1** — **wall time: 4.15s** ````` @@ -906,22 +925,22 @@ ripwire: --callers symbol not found: DoesNotExist *What SYM calls (1-hop out-edges).* -**wall time: 2.48s** +**wall time: 1.67s** ````` - - + + - - - - - - + + + + + + ````` @@ -929,14 +948,14 @@ ripwire: --callers symbol not found: DoesNotExist *The resolvable use-sites (call/read/write/import/extends) with file:line; count= is a floor.* -**wall time: 3.62s** +**wall time: 1.15s** ````` - + - - + + @@ -950,39 +969,39 @@ ripwire: --callers symbol not found: DoesNotExist *Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* -**wall time: 3.36s** +**wall time: 1.09s** ````` - + - + - + - + - - - + + + - - - - - - - - - - + + + + + + + + + + … [28 more display lines; full output is 3650 bytes on 1 raw line(s)] ````` @@ -990,39 +1009,37 @@ ripwire: --callers symbol not found: DoesNotExist *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.* -**wall time: 2.68s** - ````` - - - + + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - + - + - + + - … [73 more display lines; full output is 5602 bytes on 1 raw line(s)] ````` @@ -1030,38 +1047,36 @@ ripwire: --callers symbol not found: DoesNotExist *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).* -**wall time: 2.20s** - ````` - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + … [73 more display lines; full output is 5481 bytes on 1 raw line(s)] ````` @@ -1070,20 +1085,20 @@ ripwire: --callers symbol not found: DoesNotExist *Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_defs and resolves the right `main` (was reachable="0").* -**wall time: 1.53s** +**wall time: 1.01s** ````` - + - - + + - + @@ -1105,15 +1120,15 @@ ripwire: --callers symbol not found: DoesNotExist *Which definitions each name binds, one row per definition group with sel= (the spelling that picks it alone in any verb), class=source|test and in= callers; source first.* -**wall time: 1.98s** +**wall time: 1.06s** ````` - + - + @@ -1129,7 +1144,7 @@ ripwire: --callers symbol not found: DoesNotExist - + ````` @@ -1138,14 +1153,14 @@ ripwire: --callers symbol not found: DoesNotExist *Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* -**wall time: 2.97s** +**wall time: 1.07s** ````` - + - + @@ -1157,11 +1172,11 @@ ripwire: --callers symbol not found: DoesNotExist *Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* -**wall time: 2.17s** +**wall time: 1.12s** ````` - @@ -1179,8 +1194,8 @@ ripwire: --callers symbol not found: DoesNotExist - - + + @@ -1190,14 +1205,14 @@ ripwire: --callers symbol not found: DoesNotExist -… [70 more display lines; full output is 6872 bytes on 1 raw line(s)] +… [71 more display lines; full output is 6959 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --mentions=rankGraphTeleport` *Markdown docs that name SYM in a backtick (doc<->code edges).* -**wall time: 2.35s** +**wall time: 1.18s** ````` @@ -1216,11 +1231,11 @@ ripwire: --callers symbol not found: DoesNotExist *Test files that transitively reach the changed file.* -**wall time: 2.28s** +**wall time: 1.33s** ````` - @@ -1237,12 +1252,12 @@ ripwire: --callers symbol not found: DoesNotExist *Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below).* -**wall time: 7.50s** +**wall time: 2.74s** ````` ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: 77bb261ba +at: c8b49812b (0 changed files — working tree is clean, nothing to analyze) ````` @@ -1250,11 +1265,11 @@ at: 77bb261ba *Pre-PR gate on a CLEAN tree: no obligations, exit 0.* -**wall time: 8.45s** +**wall time: 4.46s** ````` - ````` @@ -1262,7 +1277,7 @@ at: 77bb261ba *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.* -**wall time: 2.99s** +**wall time: 1.02s** ````` @@ -1445,11 +1456,11 @@ at: 77bb261ba *The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* -**wall time: 3.95s** +**wall time: 1.76s** ````` -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 @@ -1485,21 +1496,21 @@ at: 77bb261ba *Raw BM25 ranking (debug lens; --for is the real verb).* -**wall time: 2.87s** +**wall time: 1.23s** ````` - + - + - + @@ -1510,12 +1521,12 @@ at: 77bb261ba - + - + … [22 more display lines; full output is 3459 bytes on 1 raw line(s)] @@ -1530,10 +1541,10 @@ at: 77bb261ba *Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* -**wall time: 10.89s** +**wall time: 2.54s** ````` - + @@ -1543,53 +1554,55 @@ at: 77bb261ba 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 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 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 path_prepend_hint( std::string_view dir ) inline std::string powerShellSingleQuote( std::string_view s ) noexcept -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] +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 ) std::uint32_t testIterationCeiling() noexcept inline const char* rankByLegendFor( const char* label ) noexcept -… [156 more display lines; full output is 22219 bytes on 72 raw line(s)] +… [156 more display lines; full output is 22220 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: 2.78s** - ````` - - + + - + + + + + - + - + @@ -1599,25 +1612,21 @@ at: 77bb261ba - - - - -… [150 more display lines; full output is 15426 bytes on 1 raw line(s)] +… [151 more display lines; full output is 15392 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.* -**wall time: 4.43s** +**wall time: 1.18s** ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1641,14 +1650,14 @@ at: 77bb261ba *CHANGED: a line range on --outline is now STRIPPED with a stderr note (it used to refuse).* -**wall time: 2.81s** +**wall time: 1.40s** ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1678,14 +1687,14 @@ ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — *Full body + inline callee signatures.* -**wall time: 2.67s** +**wall time: 1.59s** ````` - + - + : 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= lcom4= amp=: callers, callees, cyclomatic/cognitive complexity, lines, parameters, nesting depth, coupled types, class cohesion, 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=). --> - - + + - + + + + + - + - + - + @@ -1907,42 +1920,38 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - - - - -… [98 more display lines; full output is 11226 bytes on 1 raw line(s)] +… [98 more display lines; full output is 11208 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --deps` *File->file dependency graph (god-files, cycles).* -**wall time: 2.04s** +**wall time: 1.12s** ````` - - - - - + + + + + - - + + + - - + - + - + - - + + @@ -1951,68 +1960,68 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan -… [874 more display lines; full output is 23544 bytes on 1 raw line(s)] +… [885 more display lines; full output is 23687 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --hotspots` *Complexity x recent git churn (maintenance pain).* -**wall time: 8.82s** +**wall time: 5.79s** ````` - - + + - - + + - + - - + + - - + + - + - + - + + - -… [14 more display lines; full output is 5765 bytes on 1 raw line(s)] +… [14 more display lines; full output is 5767 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --clones` *Token-normalized duplicate bodies.* -**wall time: 4.18s** +**wall time: 3.87s** ````` - + - + - + @@ -2023,11 +2032,11 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - + @@ -2038,39 +2047,39 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Files that change together in git (hidden coupling).* -**wall time: 6.79s** +**wall time: 4.54s** ````` - - - - + - - + + + + + - - - - - - - + + + - + + + + + - + - + @@ -2080,52 +2089,52 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Hotspots scoped to RECENT churn (the regression lens).* -**wall time: 11.53s** +**wall time: 8.10s** ````` - - - - - - - - + + + + + + + + - - + + + - - + + - - - - - - - + + + + + + - - + + -… [13 more display lines; full output is 5133 bytes on 1 raw line(s)] +… [13 more display lines; full output is 5144 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.* -**wall time: 1.67s** +**wall time: 3.23s** ````` - @@ -2153,19 +2162,19 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan -… [1143 more display lines; full output is 168584 bytes on 1 raw line(s)] +… [1179 more display lines; full output is 173724 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint` *Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* -**wall time: 3.27s** +**wall time: 7.05s** ````` - - + + @@ -2173,34 +2182,34 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - - + + - - - + + + - + - + - -… [682 more display lines; full output is 70695 bytes on 1 raw line(s)] + +… [682 more display lines; full output is 70696 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.90s** +**wall time: 2.90s** ````` @@ -2280,77 +2289,77 @@ The joined finding — past the display cut above, extracted so the join is visi *Cluster the call graph into cohesive modules.* -**wall time: 1.89s** +**wall time: 1.54s** ````` - - - + + - + + - + - + - + - + -… [189 more display lines; full output is 16816 bytes on 1 raw line(s)] +… [190 more display lines; full output is 16945 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=.* -**wall time: 1.92s** +**wall time: 1.89s** ````` - - + + - - + + - + - - + + - + - - + + - - + + - - + + … [137 more display lines; full output is 7479 bytes on 1 raw line(s)] @@ -2360,92 +2369,92 @@ The joined finding — past the display cut above, extracted so the join is visi *The restoring knob: --zoom-levels=N prints N levels (0 = the whole tree); here three, ~11 KB.* -**wall time: 1.38s** +**wall time: 1.71s** ````` - - - + + + - + - + - + - + - + - + - + - + - + - + - + - + -… [289 more display lines; full output is 13105 bytes on 1 raw line(s)] +… [291 more display lines; full output is 13185 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --report` *Architecture summary (modules, god-files, cycles) as markdown.* -**wall time: 1.70s** +**wall time: 1.77s** ````` # ripwire architecture report -3943 files · 33211 symbols · 31252 edges · 3827 modules (15617 call-graph isolated) +4056 files · 33884 symbols · 31822 edges · 3979 modules (15964 call-graph isolated) Root: `.` -Call-graph isolate provenance: 4131 declaration, 2511 header, 3727 source, 5248 document; 0 connected Louvain singletons +Call-graph isolate provenance: 4203 declaration, 2539 header, 3947 source, 5275 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3827) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1579 symbols +## Modules (call-graph clusters; showing 12 of 3979) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1373 symbols - **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 556 symbols -- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 256 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 259 symbols - **docs::`--top-k=N`@COMMANDS.md:59:9108 [pack,allow,insert]** — 182 symbols - **test/ownerhopfix/py/pkg::big_handler_001@broadcast.py:3:72** — 106 symbols -- **src::buf@infra/svector.h:125:8942 [insert,find,push]** — 95 symbols +- **src::buf@infra/svector.h:125:8942 [insert,find,push]** — 96 symbols - **src/infra::fail@os_win32.cpp:63:2553 [open,get,close]** — 89 symbols -- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,compute,collect]** — 66 symbols +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,collect,compute]** — 68 symbols - **test::no@estchargecheck.sh:52:4108 [skip,set]** — 63 symbols -- **src::child@ingest_valuerefs.h:651:34179 [emit,resolve,open]** — 59 symbols +- **src::child@ingest_valuerefs.h:685:36070 [emit,resolve,open]** — 59 symbols +- **src::viaLinkLegend@graphlegend.h:1217:112851 [route,run,split]** — 52 symbols - **src::sliceKindIs@slice.h:368:24239 [match,collect,push]** — 48 symbols -- **src::active@pathpairs.h:106:7669 [split,resolve,emit]** — 46 symbols -## God files (most depended-on; showing 10 of 236) -- `src/model.h` — 108 dependents +## God files (most depended-on; showing 10 of 239) +- `src/model.h` — 110 dependents - `src/infra/emit.h` — 88 dependents - `src/infra/Diagnostics.h` — 65 dependents -- `src/graph.h` — 50 dependents -- `src/serialize.h` — 49 dependents -… [35 more lines, 3944 bytes total] +- `src/graph.h` — 51 dependents +- `src/serialize.h` — 50 dependents +… [35 more lines, 3929 bytes total] ````` ## `./build/ripwire . --seams` *Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* -**wall time: 1.31s** +**wall time: 2.09s** ````` - + @@ -2480,29 +2489,29 @@ Call-graph isolate provenance: 4131 declaration, 2511 header, 3727 source, 5248 *Module (directory) dependency graph as a Mermaid diagram.* -**wall time: 1.58s** +**wall time: 1.81s** ````` %% 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
9809"] + n102["src
10011"] n103["src/infra
1301"] end subgraph sg1 ["test"] - n104["test
5595"] - n607["test/legochargefix
546"] + n104["test
5600"] + n642["test/legochargefix
546"] n420["test/fixtures/recallpassage
314"] - n1034["test/slotjoinfix
175"] - n844["test/ownerhopfix/py/pkg
158"] + n1069["test/slotjoinfix
175"] + n879["test/ownerhopfix/py/pkg
158"] n357["test/expandmodefix
151"] n359["test/expandsibsfix
149"] - n1017["test/rubyschemafix
99"] - n457["test/forbodyfoldfix/py
98"] - n999["test/rubyattrsfix
85"] + n1052["test/rubyschemafix
99"] + n471["test/forbodyfoldfix/py
98"] + n1034["test/rubyattrsfix
85"] end - n74["docs
866"] - n2[".github
632"] + n74["docs
867"] + n2[".github
633"] n0["(root)
606"] subgraph sg5 ["bench"] n4["bench
545"] @@ -2513,19 +2522,19 @@ flowchart LR n43["bench/locbench
107"] n38["bench/headtohead/r3-headroom-2026-08-03
102"] n65["bench/skillrater/results/2026-09-07/arms
100"] -… [19 more lines, 1575 bytes total] +… [19 more lines, 1577 bytes total] ````` ## `./build/ripwire . --owners` *Bus-factor: recency-weighted author ownership per file.* -**wall time: 6.36s** +**wall time: 6.81s** ````` - - + + @@ -2553,18 +2562,18 @@ flowchart LR -… [979 more display lines; full output is 107176 bytes on 1 raw line(s)] +… [981 more display lines; full output is 107432 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.* -**wall time: 2.31s** +**wall time: 1.74s** ````` - + ````` @@ -2573,7 +2582,7 @@ flowchart LR *Which symbols a TEST FILE exercises — the reverse direction of --affected.* -**wall time: 1.59s** +**wall time: 1.07s** ````` @@ -2586,11 +2595,11 @@ flowchart LR *Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* -**wall time: 1.26s** +**wall time: 1.55s** ````` - ````` @@ -2599,11 +2608,11 @@ flowchart LR *On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* -**wall time: 28.93s** +**wall time: 40.67s** ````` - ````` @@ -2617,11 +2626,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 36.55s** +**wall time: 31.92s** ````` - @@ -2646,11 +2655,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g - + + - - -… [140 more display lines; full output is 16170 bytes on 1 raw line(s)] + +… [142 more display lines; full output is 16388 bytes on 1 raw line(s)] ````` stderr: @@ -2663,14 +2672,14 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree).* -**wall time: 22.83s** +**wall time: 31.27s** ````` - - - + + @@ -2682,11 +2691,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *No-LLM review-evidence bundle for the working-tree diff (clean tree = empty).* -**wall time: 6.51s** +**wall time: 6.34s** ````` - ````` @@ -2695,55 +2704,55 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 15.86s** +**wall time: 16.93s** ````` - -: n runner-less rows with equal attrs as ONE row, paths verbatim; a path holding ',' is never grouped, so p= splits into exactly n=; shown=/total= over these rows counts test FILES. est_tokens=: price as emitted (an upper bound under compact). at=: commit+dirty+shallow. root=: p= relative to it. truncated=: what the trim ladder dropped to fit budget_tokens= (budget-floor-exceeded: still over). anchor=merge-base: diffed from merge base(base, HEAD); ref-tip-two-dot = no merge base, two-dot view. base_sha=: the merge-base commit (9 hex) the diff is anchored at. base_moved=N: paths the base ref changed since the fork that this work never touched; excluded. direction=: side reviewed: worktree-since-head, head-since-fork or head-since-ref-tip. skipped_mode_only=N: mode-only (chmod) diffs left out of the changed files; renames stay in. trim_level=0-4: trim ladder step taken to fit budget_tokens= (0 none, 4 counts only); raise token-budget. budget_default=1: the default 8000-token budget applied (no token-budget or max-tokens given). commits=N: this file's commits in window=; 0 = the window could not look, not no partners. partners=N: co-change partners NOT in the diff; rows are its top shown= (the cochange verb lists all). dependents=N: distinct symbols transitively calling this file's symbols (reach set, a FLOOR). files_other=N: non-changed files among those reached; the f rows are its top shown=. authors=N: distinct authors of this file (0 = no git data). bf=1: one author holds over 80% of recency-weighted commits (bus-factor risk). no-ref-work note=: the base ref's tip is the merge base, so it has no work of its own; rows are HEAD's. files=N: changed files in the diff (shown= of them listed). file symbols=: indexed symbols in that changed file. changed-symbols count= / tests count=: that section's full count (shown= of it listed). tests count=: test files reaching this file (shown= of them listed). impact files=: files holding the transitive callers (dependents=). impactf deps=: transitive callers in that file. test run=: the command that runs that test file (run_unknown=1: none derivable). changed-symbols sections=N: doc headings folded into count=, no row each; count minus sections = rows. --> + - - + + + + - + + + - - - - - - - - - + + + - - + + + + - + + + - - - - - - - -… [19 more display lines; full output is 4650 bytes on 1 raw line(s)] + + + + + +… [69 more display lines; full output is 7118 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: 37.95s** +**wall time: 28.99s** ````` - - + + - + @@ -2755,11 +2764,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Which refs of the `lane/` ref family still hold divergent authored work vs HEAD, with verdicts.* -**wall time: 74.21s** +**wall time: 85.99s** ````` - + @@ -2788,18 +2797,18 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g -… [296 more display lines; full output is 37602 bytes on 1 raw line(s)] +… [230 more display lines; full output is 30484 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --stray-content=fix/` *A second selection, the `fix/` ref family, 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: 11.12s** +**wall time: 6.71s** ````` - + ````` @@ -2807,11 +2816,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 213.65s** +**wall time: 180.28s** ````` - + @@ -2824,30 +2833,30 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g - - - + + - - - - -… [33527 more display lines; full output is 4641044 bytes on 1 raw line(s)] + + + + + +… [33522 more display lines; full output is 4640644 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: 13.37s** +**exit code: 1** — **wall time: 6.89s** ````` (empty) @@ -2864,26 +2873,21 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 574.76s** +**exit code: 2** — **wall time: 571.53s** ````` - + - - - - - - + - + @@ -2894,18 +2898,18 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 32.81s** +**wall time: 20.97s** ````` - - - - - + + + + + - + @@ -2916,7 +2920,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - + ````` @@ -2924,15 +2928,15 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 41.99s** +**wall time: 41.65s** ````` - - + + - - + + - - - - - - + + + + + + @@ -2955,20 +2959,20 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - - -… [36 more display lines; full output is 13365 bytes on 1 raw line(s)] + + +… [36 more display lines; full output is 13366 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: 28.27s** +**wall time: 20.15s** ````` - - - - + + + - - - - - - + + + + + + @@ -3006,25 +3010,25 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - - + + -… [35 more display lines; full output is 14378 bytes on 1 raw line(s)] +… [35 more display lines; full output is 14379 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: 27.74s** +**wall time: 40.48s** ````` - - + + - - + + ````` @@ -3032,12 +3036,12 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 2.28s** +**wall time: 2.65s** ````` - + @@ -3065,18 +3069,18 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer -… [424 more display lines; full output is 25917 bytes on 1 raw line(s)] +… [427 more display lines; full output is 26080 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.88s** +**wall time: 2.45s** ````` - + @@ -3110,7 +3114,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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.84s** +**exit code: 1** — **wall time: 2.92s** ````` (empty) @@ -3127,15 +3131,15 @@ ripwire: run `ripwire . --flags` for the gate table *NEW VERB: pre-hoc lane plan — which of 3 parallel worktrees would COLLIDE, before a line is written. JSON on stdout.* -**wall time: 6.98s** +**wall time: 15.43s** ````` -{"v":1,"verb":"plan-lanes","at":"77bb261ba","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":3943,"symbols":33211,"edges":312 … [line truncated: 338 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"c8b49812b","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":4056,"symbols":33884,"edges":318 … [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":658,"id":"./src/mcpverbs.h::rw::docDriftText"}, +{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","l":659,"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":1211,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":10,"churn":61,"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}, @@ -3147,15 +3151,15 @@ ripwire: run `ripwire . --flags` for the gate table {"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":693,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":455,"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":5897,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":491,"tested":0}], -"files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":56}, +{"p":"./src/mcpverbs.h","n":"unknownSubVerbRefusal","scope":"rw","key":"e336d39b0a6addea","id":"./src/mcpverbs.h::rw::unknownSubVerbRefusal","id_addressable":true,"id_collides_with":0,"l":5916,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":498,"tested":0}], +"files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":57}, {"p":"./src/docdrift.h","symbols":4,"churn":36,"ccx":14,"hotspot_rank":40}, {"p":"./src/editcheck.h","symbols":1,"churn":61,"ccx":10,"hotspot_rank":43}, {"p":"./src/main.cpp","symbols":2,"churn":455,"ccx":12,"hotspot_rank":5}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":491,"ccx":2,"hotspot_rank":4}, -{"p":"./src/pageview.h","symbols":1,"churn":28,"ccx":2,"hotspot_rank":132}, -{"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":26}]},"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] +{"p":"./src/mcpverbs.h","symbols":1,"churn":498,"ccx":2,"hotspot_rank":4}, +{"p":"./src/pageview.h","symbols":1,"churn":28,"ccx":2,"hotspot_rank":134}, +{"p":"./src/testmap.h","symbols":1,"churn":61,"ccx":4,"hotspot_rank":35}, +{"p":"./src/verbs_change.h","symbols":1,"churn":82,"ccx":9,"hotspot_rank":27}]},"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/cli.h","n":"inHostActive","scope":"rw","key":"4446994deb74226c","id":"./src/cli.h::rw::inHostActive","id_addressable":true,"id_collides_with":0,"l":4675,"ord":0,"overloads":1," … [line truncated: 46 more bytes on this line] @@ -3167,7 +3171,7 @@ ripwire: run `ripwire . --flags` for the gate table *NEW VERB, explicit form: one line per lane, lane boundaries are the ones you wrote (the defensible mode).* -**wall time: 5.34s** +**wall time: 15.52s** Input file: @@ -3178,10 +3182,10 @@ write regression tests for the new filter ````` ````` -{"v":1,"verb":"plan-lanes","at":"77bb261ba","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3943,"symbols":33211,"edges":31252,"ambiguous":2988,"unresolved":15925},"carve":null,"core":{"files" … [line truncated: 4 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"c8b49812b","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":4056,"symbols":33884,"edges":31822,"ambiguous":3015,"unresolved":15989},"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":658,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":491,"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":659,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":498,"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}, @@ -3189,20 +3193,20 @@ write regression tests for the new filter {"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/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":5897,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":491,"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":5916,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":498,"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/docdrift.h","symbols":7,"churn":36,"ccx":60,"hotspot_rank":40}, {"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":107}, -{"p":"./src/mcpverbs.h","symbols":2,"churn":491,"ccx":2,"hotspot_rank":4}, +{"p":"./src/mcpverbs.h","symbols":2,"churn":498,"ccx":2,"hotspot_rank":4}, {"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":65}, -{"p":"./src/verbs_change.h","symbols":1,"churn":82,"ccx":4,"hotspot_rank":26}], -"symbols_total":3537,"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":[], +{"p":"./src/verbs_change.h","symbols":1,"churn":82,"ccx":4,"hotspot_rank":27}], +"symbols_total":3640,"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":5,"notes":[], "execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":12,"files":5,"module_span":5,"max_ccx":38,"sum_ccx":70,"ambiguous_calls":14,"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":136,"l":279,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":14, … [line truncated: 12 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":140,"l":280,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":15, … [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":2953,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":516,"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":3032,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":516,"tested":0}, -{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":136,"l":885,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":18,"tested":0}, +{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":140,"l":885,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":18,"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":3097,"ord":0,"overloads":1,"amb":1,"cx":4,"ccx":3,"churn":516,"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}, @@ -3215,7 +3219,7 @@ write regression tests for the new filter *Out-of-range refusal shape for the lane count.* -**exit code: 1** — **wall time: 1.11s** +**exit code: 1** ````` (empty) @@ -3231,12 +3235,12 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out *CPU/GPU contract view of one struct: computed offsets/sizes/padding + mirror check.* -**wall time: 1.84s** +**wall time: 2.43s** ````` - - + + @@ -3264,7 +3268,7 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out - + ````` @@ -3273,7 +3277,7 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out *The honest refusal (exit 1): Lang is an `enum class`, not a struct — no offsets are fabricated.* -**exit code: 1** — **wall time: 1.15s** +**exit code: 1** — **wall time: 4.50s** ````` (empty) @@ -3289,11 +3293,11 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped *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: 4.03s** +**wall time: 6.38s** ````` - +
@@ -3312,28 +3316,28 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped - - - + + + - + - -… [244 more display lines; full output is 24817 bytes on 1 raw line(s)] + +… [244 more display lines; full output is 24872 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: 5.24s** +**wall time: 5.47s** ````` - + @@ -3352,17 +3356,17 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped - - - + + + - + - -… [274 more display lines; full output is 26234 bytes on 1 raw line(s)] + +… [274 more display lines; full output is 26289 bytes on 1 raw line(s)] ````` Tail of the same output — the `` section: @@ -3399,12 +3403,12 @@ Tail of the same output — the `` section: *Same report, with git history splitting stale mentions into deleted-by-commit vs never-existed.* -**wall time: 6.00s** +**wall time: 8.69s** ````` - - + + @@ -3423,23 +3427,23 @@ Tail of the same output — the `` section: - - - + + + -… [224 more display lines; full output is 24880 bytes on 1 raw line(s)] +… [224 more display lines; full output is 24935 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).* -**wall time: 1.94s** +**wall time: 2.42s** Input file: @@ -3447,8 +3451,8 @@ 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:11977 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:12018 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:11993 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:12034 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1755 #3 0x102e01a44 in main src/main.cpp:4177 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) @@ -3456,25 +3460,25 @@ AddressSanitizer:DEADLYSIGNAL ````` ````` - + - - + + - + 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)" ); @@ -3493,7 +3497,7 @@ AddressSanitizer:DEADLYSIGNAL *List all field notes (write-side memory) — the committed .ripwire_notes at the repo root, each with the sha/branch it was recorded at.* -**wall time: 1.84s** +**wall time: 1.77s** ````` @@ -3518,7 +3522,7 @@ AddressSanitizer:DEADLYSIGNAL *ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* -**wall time: 2.12s** +**wall time: 1.64s** ````` @@ -3558,7 +3562,7 @@ AddressSanitizer:DEADLYSIGNAL *Fan-out form: one shared core + 3 per-agent slices carved along call-graph communities.* -**wall time: 1.89s** +**wall time: 1.96s** ````` + @@ -3611,34 +3615,34 @@ AddressSanitizer:DEADLYSIGNAL 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 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 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 path_prepend_hint( std::string_view dir ) inline std::string powerShellSingleQuote( std::string_view s ) noexcept -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] +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 ) std::uint32_t testIterationCeiling() noexcept inline const char* rankByLegendFor( const char* label ) noexcept -… [124 more display lines; full output is 19109 bytes on 10 raw line(s)] +… [124 more display lines; full output is 19110 bytes on 10 raw line(s)] ````` ## `./build/ripwire . --export=cc.json:/aux/ripwire2.cc.json` *Per-file metrics as CodeCharta cc.json.* -**wall time: 5.11s** +**wall time: 4.87s** ````` (empty) @@ -3647,7 +3651,7 @@ AddressSanitizer:DEADLYSIGNAL Artifact written: ````` - 612188 /aux/ripwire2.cc.json + 630319 /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 ````` @@ -3655,7 +3659,7 @@ Artifact written: *One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* -**wall time: 5.58s** +**wall time: 10.78s** Input file: @@ -3670,7 +3674,7 @@ lego:Vehicle - + @@ -3683,20 +3687,20 @@ lego:Vehicle 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 +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 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 ) -const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) -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] -static bool cachePathIsDirectory( const std::string& cachePath ) -inline std::string oracleCachePath( const std::string& root, const std::string& headSha ) -HashMap<const TSLanguage*, TSQuery*>& compiledQueryCache() +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) +static bool cachePathIsDirectory( const std::string& cachePath ) +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] +HashMap<const TSLanguage*, TSQuery*>& compiledQueryCache() +inline std::string oracleCachePath( const std::string& root, const std::string& headSha ) … [107 more display lines; full output is 51609 bytes on 1 raw line(s)] ````` @@ -3709,18 +3713,18 @@ lego:Vehicle *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: 39.30s** +**exit code: 1** — **wall time: 58.97s** ````` - - + - - + + - - + @@ -3751,6 +3755,8 @@ ripwire scan: 0 finding(s) in skills/ripwire-orient/SKILL.md *Scan a whole skills directory (exit 2 = CRITICAL, 1 = WARN). Explicit-DIR form only.* +**wall time: 1.63s** + ````` @@ -3772,14 +3778,14 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s *Rank by git change-frequency prior instead of PageRank.* -**wall time: 9.41s** +**wall time: 11.09s** ````` - + - - + + @@ -3788,42 +3794,59 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s - - - - + + + + + ````` -## `./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3` +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~3 --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: 10.70s** +**wall time: 13.64s** ````` - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - + ````` @@ -3847,19 +3870,19 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *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).* -**wall time: 3.23s** +**wall time: 4.30s** ````` - + 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,12016,12662,171,1427,1466,1753 +1184,12032,12678,171,1427,1466,1753 fn,fn,fn,fn,fn,fn,fn 0,0,0,0,0,0,0 -src/mcpindex.h:1288,src/graph.h:12019,src/graph.h:12713,src/eval.h:328,src/main.cpp:1441,src/main.cpp:1480 src/main.cpp:1481 src/main.cpp:1495,src/main.cpp:1873 +src/mcpindex.h:1288,src/graph.h:12035,src/graph.h:12729,src/eval.h:328,src/main.cpp:1441,src/main.cpp:1480 src/main.cpp:1481 src/main.cpp:1495,src/main.cpp:1873 ````` @@ -3868,24 +3891,24 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *Flat top-K export for an external reranker.* -**wall time: 8.02s** +**wall time: 8.98s** ````` - - + + 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 ) @@ -3895,7 +3918,7 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *An unknown --format value REFUSES (exit 1), named, with the supported set listed.* -**exit code: 1** — **wall time: 1.10s** +**exit code: 1** — **wall time: 1.08s** ````` (empty) @@ -3911,12 +3934,12 @@ ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidate *Machine-parseable JSON, same content, keys mirror the XML attrs.* -**wall time: 2.87s** +**wall time: 3.62s** ````` -{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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:12016","sites_at":["src/graph.h:12019"]}, -{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:12662","sites_at":["src/graph.h:12713"]}, +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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:12032","sites_at":["src/graph.h:12035"]}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:12678","sites_at":["src/graph.h:12729"]}, {"t":"fn","n":"runEval","p":"src/eval.h:171","sites_at":["src/eval.h:328"]}, {"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1427","sites_at":["src/main.cpp:1441"]}, {"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1466","sites_at":["src/main.cpp:1480","src/main.cpp:1481","src/main.cpp:1495"]}, @@ -3927,7 +3950,7 @@ ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidate *JSON refusal shape: an unsupported verb refuses loudly instead of silently falling back to XML.* -**exit code: 1** +**exit code: 1** — **wall time: 1.06s** ````` (empty) @@ -3943,13 +3966,13 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Pagination: 3 items, skipping the first 3 (deterministic seams).* -**wall time: 8.43s** +**wall time: 9.89s** ````` - - + + @@ -3959,31 +3982,31 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop test paths from the corpus before ranking.* -**wall time: 2.69s** +**wall time: 1.74s** ````` - - + + + + + + - + - - - - - + @@ -3992,23 +4015,27 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m -… [67 more display lines; full output is 8224 bytes on 1 raw line(s)] +… [67 more display lines; full output is 8267 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --exclude=present --exclude=bench --top-k=5` *Drop matching paths (repeatable) before ranking.* -**wall time: 3.25s** +**wall time: 1.79s** ````` - - + + - + + + + + @@ -4016,15 +4043,11 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - + - - - - - + @@ -4032,38 +4055,38 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m -… [66 more display lines; full output is 8444 bytes on 1 raw line(s)] +… [66 more display lines; full output is 8487 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: 6.79s** +**wall time: 4.14s** ````` - - + + - + + + + + - + - + - - - - ````` @@ -4071,29 +4094,29 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm cost.* -**wall time: 5.05s** +**wall time: 4.39s** ````` - - + + - + + + + + - + - - - - - + @@ -4104,36 +4127,36 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m -… [65 more display lines; full output is 8460 bytes on 1 raw line(s)] +… [65 more display lines; full output is 8503 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: 5.31s** +**wall time: 4.30s** ````` - - + + - + + + + + - + - - - - - + @@ -4144,40 +4167,40 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m -… [65 more display lines; full output is 8460 bytes on 1 raw line(s)] +… [65 more display lines; full output is 8503 bytes on 1 raw line(s)] ````` Artifact written: ````` - 29639325 /aux/warm2.ripwirecache + 30040005 /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).* -**wall time: 1.66s** +**wall time: 1.81s** ````` - + - + - + - + @@ -4186,7 +4209,7 @@ Artifact written: - + @@ -4213,33 +4236,33 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Multi-root workspace: ONE merged graph over two roots, paths labeled /.* -**wall time: 4.71s** +**wall time: 4.34s** ````` - + - + + + + + - + - + - - - - ````` @@ -4247,19 +4270,19 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Self-eval: co-change recall vs BM25.* -**wall time: 22.03s** +**wall time: 17.12s** ````` ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 1.0% 1.2% 2.8% - BM25 13.2% 13.3% 14.0% - BM25sub 11.3% 12.5% 16.2% - BM25body 3.6% 15.6% 22.0% - fused 1.0% 4.2% 10.6% - anchored 3.6% 15.6% 22.0% - same-dir 1.2% 1.8% 7.4% - random 0.1% 0.3% 0.5% <- floor (random ranking over F=3943 files) + ripwire 1.2% 1.3% 9.9% + BM25 13.4% 14.6% 16.1% + BM25sub 9.3% 14.3% 21.6% + BM25body 5.2% 14.2% 20.9% + fused 2.3% 10.3% 18.0% + anchored 4.3% 14.2% 20.9% + same-dir 2.8% 4.7% 10.1% + random 0.1% 0.2% 0.5% <- floor (random ranking over F=4056 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 / @@ -4275,23 +4298,23 @@ ripwire --eval (co-change recovery, averaged over 80 historical commits) *Known-item retrieval eval: MRR + recall@k per ranker per query mode.* -**wall time: 178.83s** +**wall time: 171.44s** ````` ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=8408 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + sample: population=8654 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.675 53.8% 84.0% 89.4% - subtoken doc-phrase 0.887 86.6% 90.7% 92.0% - name-exact name 0.888 80.5% 95.1% 97.3% - name-exact doc-phrase 0.011 0.5% 1.7% 2.4% - anchored name 0.680 55.2% 83.3% 88.0% - anchored doc-phrase 0.883 85.9% 90.5% 91.3% - routed name 0.889 80.7% 95.2% 97.5% - routed doc-phrase 0.886 86.5% 90.5% 91.7% + subtoken name 0.674 53.7% 83.7% 89.2% + subtoken doc-phrase 0.877 85.5% 89.7% 91.0% + name-exact name 0.886 80.2% 95.0% 97.4% + name-exact doc-phrase 0.011 0.5% 1.6% 2.2% + anchored name 0.678 55.0% 83.0% 87.7% + anchored doc-phrase 0.873 84.9% 89.6% 90.3% + routed name 0.887 80.4% 95.1% 97.5% + routed doc-phrase 0.875 85.5% 89.4% 90.6% 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 — @@ -4304,7 +4327,7 @@ ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-co *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: 75.84s** +**exit code: 3** — **wall time: 54.50s** Input file: @@ -4328,8 +4351,6 @@ arm/orient-map-067-narrow merged *Labelled skill-ROUTING eval over the repo's own skills/ directory (4 hand-labelled prompts).* -**wall time: 1.07s** - Input file: ````` @@ -4370,7 +4391,7 @@ ripwire --eval-skills (skill routing over K=16 candidate skills [ripwire-router 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, 3668 bytes total] +… [19 more lines, 3665 bytes total] ````` ## `./build/ripwire wrap claude` @@ -4416,7 +4437,7 @@ Reach for it BEFORE blind grep + whole-file reads. First call ~1s cold; after th *Version + short build info.* ````` -ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=982306d4f) +ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=c8b49812b) ````` @@ -4424,31 +4445,31 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=9823 # navigate — seeds, claims, slices, shapes -## `./build/ripwire . --at=src/graph.h:11977` +## `./build/ripwire . --at=src/graph.h:11993` *Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost.* -**wall time: 1.86s** +**wall time: 1.35s** ````` - - + + ````` -## `./build/ripwire . --callers=@src/graph.h:11977` +## `./build/ripwire . --callers=@src/graph.h:11993` *The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* -**wall time: 1.56s** +**wall time: 1.41s** ````` - + - - + + @@ -4460,7 +4481,7 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=9823 *A seed past the end of the file — the refusal shape for a faulted location.* -**exit code: 1** — **wall time: 1.41s** +**exit code: 1** — **wall time: 1.16s** ````` (empty) @@ -4469,20 +4490,20 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=9823 stderr: ````` -ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 17116 lines — the seed asked for line 999999) +ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 17132 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.* -**wall time: 1.86s** +**wall time: 1.50s** ````` - + - + ````` @@ -4490,14 +4511,14 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A claim that is FALSE — the refuted shape, with the references that refute it.* -**wall time: 1.49s** +**wall time: 1.47s** ````` - + - - + + @@ -4511,7 +4532,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A literal-scan absence: refuted only with complete= evidence, never on a partial scan.* -**wall time: 2.12s** +**wall time: 1.43s** ````` @@ -4523,7 +4544,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *An unparseable claim — the refusal names the accepted shapes.* -**exit code: 1** — **wall time: 2.22s** +**exit code: 1** — **wall time: 1.36s** ````` (empty) @@ -4539,25 +4560,25 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *Bare --slice=SYM: the INVENTORY of sliceable locals (), so a caller can pick VAR.* -**wall time: 4.22s** +**wall time: 2.89s** ````` - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ````` @@ -4565,21 +4586,21 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *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: 4.43s** +**wall time: 3.63s** ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4589,30 +4610,30 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *TRANSITIVE backward value-flow from the seed variable, bounded BFS (depth= disclosed; a cut frontier says flow_truncated=1).* -**wall time: 3.66s** +**wall time: 4.41s** ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + - + pw = biasPrior( g, p );]]> - + & p, float alpha = 0.85f )]]> - + & p, float alpha = 0.85f )]]> @@ -4622,42 +4643,42 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *Forward flow: which statements the seed's value reaches, at the default depth bound.* -**wall time: 3.84s** +**wall time: 4.72s** ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + - + - + - + - + - + 0.0 )]]> - + - + @@ -4667,7 +4688,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *A variable the definition does not bind — the refusal shape, naming the inventory.* -**exit code: 1** — **wall time: 2.43s** +**exit code: 1** — **wall time: 2.29s** ````` (empty) @@ -4699,21 +4720,21 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *The compact legend posture: rows byte-identical, a versioned schema id replaces the repeated explanatory prose — for a many-small-calls loop.* -**wall time: 4.46s** +**wall time: 4.47s** ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4723,12 +4744,12 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *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: 2.95s** +**wall time: 4.12s** ````` rankGraphTeleport( g, std::vector<float>( N, N ? 1.0f / float( N ) : 0.f ), alpha ) +rankGraphTeleport( g, std::vector<float>( N, N ? 1.0f / float( N ) : 0.f ), alpha ) ````` @@ -4736,7 +4757,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *The ellipsis form over a macro-shaped call site; unsupported= names the families this verb does not serve.* -**wall time: 2.19s** +**wall time: 5.50s** ````` @@ -4749,7 +4770,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < 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( 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 @@ -4776,7 +4797,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *A pattern that collapses to a bare token is REFUSED — never reported as hits=0.* -**exit code: 1** — **wall time: 2.19s** +**exit code: 1** — **wall time: 1.71s** ````` (empty) @@ -4792,30 +4813,30 @@ ripwire: --pattern: this pattern collapses to a single bare token, which is a TE *Boolean grep: hits where BOTH literals share the matched line (--grep-scope=line is the default).* -**wall time: 2.53s** +**wall time: 3.51s** ````` @@ -5061,7 +5082,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? 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 +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 @@ -5081,17 +5102,17 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? struct LintRule inline bool hasPytestTable( std::string_view source ) inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [139 more display lines; full output is 19731 bytes on 44 raw line(s)] +… [139 more display lines; full output is 19724 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: 10.94s** +**wall time: 7.87s** ````` - + @@ -5101,7 +5122,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? 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 +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 @@ -5121,17 +5142,17 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? struct LintRule inline bool hasPytestTable( std::string_view source ) inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [146 more display lines; full output is 19782 bytes on 44 raw line(s)] +… [146 more display lines; full output is 19775 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: 8.20s** +**wall time: 8.44s** ````` - + @@ -5141,7 +5162,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? 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 +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 @@ -5161,101 +5182,101 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? struct LintRule inline bool hasPytestTable( std::string_view source ) inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [137 more display lines; full output is 20506 bytes on 57 raw line(s)] +… [137 more display lines; full output is 20499 bytes on 57 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: 7.08s** +**wall time: 9.41s** ````` - + - - + + 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 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 Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes … [line truncated: 5 more bytes on this line] +inline rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::stri… 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 std::string gitHeadSha( const std::string& root ) -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -… [193 more display lines; full output is 22995 bytes on 45 raw line(s)] +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 bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root, bool showStale = false ) +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 std::string gitHeadSha( const std::string& root ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +… [193 more display lines; full output is 23058 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: 7.70s** +**wall time: 5.69s** ````` - + - - + + 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 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 Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes … [line truncated: 5 more bytes on this line] +inline rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::stri… 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 std::string gitHeadSha( const std::string& root ) -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -… [193 more display lines; full output is 22814 bytes on 45 raw line(s)] +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 bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root, bool showStale = false ) +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 std::string gitHeadSha( const std::string& root ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +… [193 more display lines; full output is 22880 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.* -**wall time: 1.63s** +**wall time: 2.02s** ````` - - - + + @@ -5267,7 +5288,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? *Unknown-symbol refusal shape for --safe-delete.* -**exit code: 1** — **wall time: 1.94s** +**exit code: 1** — **wall time: 3.08s** ````` (empty) @@ -5283,22 +5304,22 @@ ripwire: --safe-delete symbol not found: DoesNotExist *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: 11.17s** +**wall time: 15.08s** ````` - + - + - - - + + + + ````` @@ -5307,16 +5328,16 @@ ripwire: --safe-delete symbol not found: DoesNotExist *The same packet under a hard ceiling: heuristic rows drop tail-first (withheld= disclosed), verified rows never drop.* -**wall time: 11.65s** +**wall time: 14.10s** ````` - + - + @@ -5335,7 +5356,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist - @@ -5360,36 +5381,36 @@ ripwire: --safe-delete symbol not found: DoesNotExist -… [458 more display lines; full output is 49347 bytes on 1 raw line(s)] +… [459 more display lines; full output is 49456 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).* -**wall time: 1.51s** +**wall time: 2.20s** ````` - - + + - + + + + + - + - - - - - + @@ -5400,36 +5421,36 @@ ripwire: --safe-delete symbol not found: DoesNotExist -… [65 more display lines; full output is 8460 bytes on 1 raw line(s)] +… [65 more display lines; full output is 8503 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.* -**wall time: 2.31s** +**wall time: 2.80s** ````` - - + + - + + + + + - + - - - - - + @@ -5440,7 +5461,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist -… [65 more display lines; full output is 8460 bytes on 1 raw line(s)] +… [65 more display lines; full output is 8503 bytes on 1 raw line(s)] ````` stderr: @@ -5453,21 +5474,21 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o *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** — **wall time: 1.86s** +**exit code: 4** — **wall time: 2.42s** ````` - + - + const&, float) src/graph.h:11977 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:12018 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:11993 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:12034 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1755 #3 0x102e01a44 in main src/main.cpp:4177 - #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc)]]> pw = biasPrior( g, p ); @@ -5495,19 +5516,19 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o 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] +}]]>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.* -**wall time: 2.29s** +**wall time: 3.44s** ````` - + ````` @@ -5515,12 +5536,12 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o *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: 3.81s** +**exit code: 4** — **wall time: 3.76s** ````` - + ````` @@ -5555,21 +5576,21 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *THE single wide-angle quality read: six families in one pass, an eligible/ranked shortlist rather than a firehose.* -**wall time: 11.21s** +**wall time: 15.53s** ````` - - + - + - + @@ -5578,31 +5599,31 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - - - - - - -… [189 more display lines; full output is 15691 bytes on 1 raw line(s)] + + + + + + +… [188 more display lines; full output is 15661 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: 2.86s** +**wall time: 3.32s** ````` - - + + + - @@ -5615,11 +5636,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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.* -**wall time: 2.01s** +**wall time: 2.56s** ````` - + @@ -5635,51 +5656,51 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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: 2.17s** +**wall time: 3.29s** ````` - - - + + + - + - - - - - - - - - - + + + + + + + + + + - + - - + + - + + - - -… [21 more display lines; full output is 11156 bytes on 1 raw line(s)] + +… [21 more display lines; full output is 11168 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: 3.70s** +**wall time: 7.12s** ````` - @@ -5715,16 +5736,16 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The family join: per function, which of four orthogonal evidence families fire, ranked by how many agree.* -**wall time: 8.33s** +**wall time: 10.83s** ````` - - + @@ -5733,33 +5754,33 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - + - + - + - + -… [36 more display lines; full output is 7482 bytes on 1 raw line(s)] +… [36 more display lines; full output is 7464 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: 3.32s** +**wall time: 5.49s** ````` - @@ -5788,21 +5809,21 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir -… [518 more display lines; full output is 46439 bytes on 1 raw line(s)] +… [514 more display lines; full output is 46163 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: 2.55s** +**wall time: 3.84s** ````` - + - + @@ -5811,7 +5832,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + @@ -5831,51 +5852,51 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The corpus's OWN case-convention vote per (language, kind) group; off-convention names get a mechanical propose= (a suggestion, never a blind rename).* -**wall time: 1.73s** +**wall time: 2.52s** ````` - - - - + + + + - + - + - + - + - + - + -… [13 more display lines; full output is 4430 bytes on 1 raw line(s)] +… [13 more display lines; full output is 4431 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: 15.93s** +**wall time: 23.88s** ````` - + @@ -5887,33 +5908,33 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir

-

-

+

-

+

-

+

+

-… [168 more display lines; full output is 17186 bytes on 1 raw line(s)] +… [171 more display lines; full output is 17431 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: 4.02s** +**wall time: 5.41s** ````` - - + + @@ -5921,34 +5942,34 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - - + + - - - + + + - + - + - -… [639 more display lines; full output is 72718 bytes on 1 raw line(s)] + +… [639 more display lines; full output is 72719 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.* -**wall time: 2.10s** +**wall time: 1.54s** ````` @@ -5989,7 +6010,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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=1bb96ec3e6969a9c entries=974 +ripwire legend dictionary ripwire.dict/v1 dictv=05b48ed93c807ee2 entries=981 : 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=) @@ -6019,24 +6040,24 @@ ripwire.uses/v1 : resolvable use-sites of of=: : 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.defs/v1 : per the parsed definitions the name binds (defs= groups; a prototype with its definition is one): , sel= binds that one alone in any SYM verb, in= distinct callers (a floor); source first, then in=, then path ripwire.path/v1 : one DIRECTED call path from= to to=, each a hop; reachable=0 hops=0 when none -… [945 more display lines; full output is 103738 bytes on 975 raw line(s)] +… [952 more display lines; full output is 105532 bytes on 982 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: 3.98s** +**wall time: 5.43s** ````` - + - - + + - + vector<std::vector<double>> @@ -6059,19 +6080,19 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a g_pool[ g_readOf[i] ] g_pool[ g_readOf[i] ] m[ g_pool[i] ] -… [607 more display lines; full output is 73630 bytes on 1 raw line(s)] +… [607 more display lines; full output is 73635 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.06s** +**wall time: 4.34s** ````` - + @@ -6079,17 +6100,17 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a - + - - + + - + - + @@ -6099,20 +6120,20 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a 20 delta < 0.02 && delta > -0.02 0.02 -… [720 more display lines; full output is 66538 bytes on 1 raw line(s)] +… [720 more display lines; full output is 66458 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: 3.14s** +**wall time: 5.28s** ````` -goto done; +goto done; goto epilogue; goto epilogue; goto epilogue; @@ -6123,13 +6144,13 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *Pasting that findings_next=: the same rule under a 10x budget counts its true total — no count_capped=, no findings_next=.* -**wall time: 2.66s** +**wall time: 5.04s** ````` -goto done; +goto done; goto epilogue; goto epilogue; goto epilogue; @@ -6154,7 +6175,7 @@ ripwire.path/v1 : one DIRECTED call path from= to to=, each a *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.45s** +**exit code: 1** — **wall time: 6.06s** ````` (empty) @@ -6170,7 +6191,7 @@ ripwire: --lint-select: 'cach-' matches no rule or family (did you mean 'cache-' *A PREFIX with no near miss at all: the refusal points at --lint-catalog instead of guessing.* -**exit code: 1** — **wall time: 2.82s** +**exit code: 1** — **wall time: 4.57s** ````` (empty) @@ -6186,7 +6207,7 @@ ripwire: --lint-select: 'nosuchfamily' matches no rule or family — see --lint- *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: 3.25s** +**wall time: 5.10s** ````` {"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] @@ -6219,13 +6240,13 @@ ripwire: --lint-select: 'nosuchfamily' matches no rule or family — see --lint- {"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}}, -… [13777 more display lines; full output is 1953363 bytes on 1 raw line(s)] +… [13933 more display lines; full output is 1976705 bytes on 1 raw line(s)] ````` Parsed summary of the same SARIF (past the display cut): ````` -sarif 2.1.0 rules= 39 results= 6884 +sarif 2.1.0 rules= 39 results= 6962 ````` ## `./build/ripwire . --lint --sarif --limit=5` @@ -6248,11 +6269,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *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: 11.01s** +**wall time: 12.08s** ````` -

@@ -6263,11 +6284,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *The per-commit scalar: HEAD vs its first parent, with the three separately actionable sub-scores.* -**wall time: 20.31s** +**wall time: 29.85s** ````` -

@@ -6278,14 +6299,14 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *The range form: tree HEAD vs tree HEAD~3.* -**wall time: 20.28s** +**wall time: 28.55s** ````` - -

-

+ +

+

````` @@ -6293,11 +6314,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *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: 9.20s** +**wall time: 9.52s** ````` - + @@ -6326,48 +6347,48 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M -… [852 more display lines; full output is 56666 bytes on 1 raw line(s)] +… [845 more display lines; full output is 56219 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: 10.53s** +**wall time: 16.61s** ````` - - - + - - - + + - + + + + + - - + - - - + + + - + ````` @@ -6375,7 +6396,7 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *The HTML graph with the initial colour mode set to community (the page embeds all five modes and keeps a live selector).* -**wall time: 7.80s** +**wall time: 13.26s** ````` (empty) @@ -6384,14 +6405,14 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M Artifact written: ````` - 156259 /aux/map2.html + 156091 /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: 9.94s** +**wall time: 13.68s** ````` (empty) @@ -6400,45 +6421,45 @@ Artifact written: stderr: ````` -ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (29639325 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (60660177 bytes, rich family) +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (30040005 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (61456848 bytes, rich family) ````` Artifact written: ````` - 29639325 /aux/ci_index.lean.ripwirecache - 60660177 /aux/ci_index.rich.ripwirecache - 90299502 total + 30040005 /aux/ci_index.lean.ripwirecache + 61456848 /aux/ci_index.rich.ripwirecache + 91496853 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.* -**wall time: 2.84s** +**wall time: 3.59s** ````` - - + + - + + + + + - + - - - - - + @@ -6449,36 +6470,36 @@ Artifact written: -… [65 more display lines; full output is 8460 bytes on 1 raw line(s)] +… [65 more display lines; full output is 8503 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: 3.27s** +**wall time: 3.08s** ````` - - + + - + + + + + - + - - - - - + @@ -6489,7 +6510,7 @@ Artifact written: -… [65 more display lines; full output is 8460 bytes on 1 raw line(s)] +… [65 more display lines; full output is 8503 bytes on 1 raw line(s)] ````` Artifact written: @@ -6503,18 +6524,18 @@ Artifact written: # 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. - 133246 /aux/pin_census.tsv + 135034 /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: 5.14s** +**exit code: 2** — **wall time: 5.26s** ````` - + @@ -6532,11 +6553,11 @@ Artifact written: *The ledger-shaped fixture through the same check (exit 2: one gating card).* -**exit code: 2** — **wall time: 4.09s** +**exit code: 2** — **wall time: 6.43s** ````` - + @@ -6548,18 +6569,18 @@ Artifact written: *--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: 30.97s** +**exit code: 1** — **wall time: 36.86s** ````` - - + - - + + - - + @@ -6595,20 +6616,20 @@ Everything below runs with `cwd` = the throwaway clone at `/dirty` (`gi *Situational report for a real diff: blast radius + tests + co-change + forgotten co-change partners.* -**wall time: 16.01s** +**wall time: 15.74s** ````` ripwire situational-awareness — 1 changed file(s), 13 symbols in them root: . -at: 77bb261ba+dirty +at: c8b49812b+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: 251 symbols across 60 files transitively depend on these changes (showing 8 of 60 files — shown=8 total=60 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=60) - counts_floor=1 graph_ambiguous=2988 graph_unresolved=15925 graph_unindexed=275 (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] + [1] blast radius: 255 symbols across 60 files transitively depend on these changes (showing 8 of 60 files — shown=8 total=60 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=60) + counts_floor=1 graph_ambiguous=3015 graph_unresolved=15989 graph_unindexed=275 (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_for.h (14 dependent symbols) + src/verbs_for.h (15 dependent symbols) src/verbs_quality.h (14 dependent symbols) - src/verbs_navigate.h (10 dependent symbols) + src/verbs_navigate.h (11 dependent symbols) src/editcheck.h (9 dependent symbols) src/resolve.h (9 dependent symbols) src/graph.h (8 dependent symbols) @@ -6618,57 +6639,51 @@ at: 77bb261ba+dirty 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=795 — 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="5526": - (none — 5526 commits were mined and none co-edited a file outside your diff) + script_gates_unmodelled=797 — 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="5607": + (none — 5607 commits were mined and none co-edited a file outside your diff) next: --test-gate ````` -stderr: - -````` -ripwire: cache : over its 2048 MiB budget — evicted 147 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) -````` - ## `./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: 10.45s** +**exit code: 4** — **wall time: 9.26s** ````` - - + - + - - + + - + + - + - - + ````` @@ -6676,11 +6691,11 @@ ripwire: cache : over its 2048 MiB budget — evicted 147 blob(s) of other *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: 48.14s** +**exit code: 2** — **wall time: 51.03s** ````` - @@ -6709,7 +6724,7 @@ ripwire: cache : over its 2048 MiB budget — evicted 147 blob(s) of other -… [149 more display lines; full output is 18050 bytes on 1 raw line(s)] +… [151 more display lines; full output is 18268 bytes on 1 raw line(s)] ````` stderr: @@ -6723,11 +6738,11 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap *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: 29.16s** +**exit code: 2** — **wall time: 28.78s** ````` - @@ -6756,7 +6771,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap -… [149 more display lines; full output is 23042 bytes on 1 raw line(s)] +… [151 more display lines; full output is 23260 bytes on 1 raw line(s)] ````` stderr: @@ -6770,10 +6785,10 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap *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: 25.19s** +**exit code: 2** — **wall time: 29.41s** ````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":167,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"77bb261ba+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] +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":169,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"c8b49812b+dirty","renames":62,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 240 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"}, @@ -6803,7 +6818,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap {"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"}, -… [146 more display lines; full output is 16644 bytes on 1 raw line(s)] +… [148 more display lines; full output is 16876 bytes on 1 raw line(s)] ````` stderr: @@ -6817,7 +6832,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap *NEW FLAG: --ack-only matching nothing REFUSES rather than falling back to acking everything.* -**exit code: 1** — **wall time: 17.41s** +**exit code: 1** — **wall time: 15.67s** ````` (empty) @@ -6835,7 +6850,7 @@ ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written *NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* -**wall time: 17.43s** +**wall time: 17.99s** ````` (empty) @@ -6853,11 +6868,11 @@ ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already *Re-run after the partial ack: acked=3, the rest still gate (exit 2).* -**exit code: 2** — **wall time: 25.82s** +**exit code: 2** — **wall time: 22.46s** ````` - @@ -6886,7 +6901,7 @@ ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already -… [148 more display lines; full output is 17753 bytes on 1 raw line(s)] +… [150 more display lines; full output is 17971 bytes on 1 raw line(s)] ````` stderr: @@ -6916,11 +6931,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6932,11 +6947,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row identical.* -**wall time: 7.25s** +**wall time: 7.05s** ````` - @@ -6948,18 +6963,18 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - - + - + - + @@ -6988,14 +7003,14 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire ) and selectors take it. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. --> - - + + @@ -7010,7 +7025,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + @@ -7020,21 +7035,21 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire of ; dup_loc=/dup_pct=. window: shown= total= capped= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). root=: p= relative to it. groups=/type3=: Type-2 and Type-3 group totals over all groups; total= is their sum. exempt_groups=N: groups whose members all sit on fixture/shell-runner paths quality-delta duplication ignores. idiom_groups=/demoted_groups=: groups of one recognized idiom / those quality-delta demotes to minor; floors. clone_groups=N: clusters after merging pairs (rows sharing gid=); a floor under type3_capped=1. total_loc=N: lines of every function body the detector considered; dup_pct= is dup_loc= over it. group exempt=fixture|shell-runner: every member is on such a path; quality-delta duplication ignores it. group idiom=: the recognized shape every member spells: threshold-ladder, switch-name-table, builder-chain. --> - + - + - + @@ -7045,11 +7060,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + - + @@ -7060,12 +7075,12 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire ; v=unknown = no merge base. at=: commit+dirty+shallow. head=: the HEAD commit, bare 9-hex sha (at= adds +dirty). head_ref=: HEAD's branch (HEAD when detached); that branch itself is not scanned. refs=N: local branches scanned (refs/heads only); unmerged + superseded + merged + unknown = refs. blobs=N: distinct git blobs read for the sweep. unmerged=N: refs whose authored work the live line genuinely lacks. superseded=N: refs whose work the live line re-implemented (removed the same base code). merged=N: refs whose work HEAD already has; omitted from the rows. name=: the local branch. tip=: the branch tip commit (9 hex). date=: the tip's committer date, YYYY-MM-DD. files=N: files with stray lines; rows capped at 12, a more element counts the rest (detail=1 lists all). ref superseded=N>: of this ref's stray= lines, those in files the live line re-implemented. unknown=N: refs that could not be analysed (v=unknown, e.g. no merge base); never counted merged. --> - - + + ````` @@ -7080,11 +7095,11 @@ stderr: *CHANGED: --plan surfaces those same refs as an row rather than silently dropping them.* -**wall time: 9.87s** +**wall time: 21.85s** ````` - + ````` @@ -7099,11 +7114,11 @@ stderr: *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: 12.00s** +**wall time: 20.94s** ````` -

-

+

... [14 more line(s); run it to see the whole thing] ``` @@ -2130,16 +2135,16 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - - + + + + - + - + - + @@ -2167,8 +2172,8 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - - + + @@ -2176,10 +2181,10 @@ $ ./build/ripwire . --lint --naming-locals - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2202,7 +2207,7 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - + @@ -2235,16 +2240,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - - - - + - - + + + + + - + ... [22 more line(s); run it to see the whole thing] ``` @@ -2267,16 +2272,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] ``` @@ -2293,7 +2298,7 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + @@ -2322,19 +2327,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] ``` @@ -2360,7 +2365,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2390,7 +2395,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2416,7 +2421,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2443,8 +2448,8 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - - + + @@ -2452,10 +2457,10 @@ $ ./build/ripwire . --lint - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2552,8 +2557,8 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + @@ -2561,10 +2566,10 @@ $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2583,7 +2588,7 @@ $ ./build/ripwire . --lint --lint-select=goto --lint-max-per-rule=4 -goto done; +goto done; goto epilogue; goto epilogue; goto epilogue; @@ -2666,15 +2671,15 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - - - + + - + + - + @@ -2697,7 +2702,7 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - ``` @@ -2722,19 +2727,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] ``` @@ -2759,16 +2764,16 @@ $ ./build/ripwire . --report # ripwire architecture report -3943 files · 33211 symbols · 31252 edges · 3827 modules (15617 call-graph isolated) +4056 files · 33884 symbols · 31822 edges · 3979 modules (15964 call-graph isolated) Root: `.` -Call-graph isolate provenance: 4131 declaration, 2511 header, 3727 source, 5248 document; 0 connected Louvain singletons +Call-graph isolate provenance: 4203 declaration, 2539 header, 3947 source, 5275 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3827) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1579 symbols +## Modules (call-graph clusters; showing 12 of 3979) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1373 symbols - **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 556 symbols -- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 256 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 259 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2785,8 +2790,8 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - + @@ -2816,15 +2821,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
9809"] + n102["src
10011"] n103["src/infra
1301"] end subgraph sg1 ["test"] - n104["test
5595"] - n607["test/legochargefix
546"] + n104["test
5600"] + n642["test/legochargefix
546"] n420["test/fixtures/recallpassage
314"] - n1034["test/slotjoinfix
175"] - n844["test/ownerhopfix/py/pkg
158"] + n1069["test/slotjoinfix
175"] + n879["test/ownerhopfix/py/pkg
158"] n357["test/expandmodefix
151"] n359["test/expandsibsfix
149"] ... [17 more line(s); run it to see the whole thing] @@ -2845,8 +2850,8 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - + + @@ -2876,7 +2881,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2941,7 +2946,7 @@ _The same clean verdict with what it folded printed back: every stale acknowledg ``` $ ./build/ripwire . --quality-delta --show-stale - @@ -2970,7 +2975,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -2995,7 +3000,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -3020,7 +3025,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 -

@@ -3113,7 +3118,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3143,10 +3148,10 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - - + + @@ -3197,12 +3202,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":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":797,"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3218,12 +3223,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":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":797,"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3269,12 +3274,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":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":797,"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3333,11 +3338,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":2988,"graph_unresolved":15925,"graph_unindexed":275,"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: 532 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true}}]} +"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3383,19 +3388,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] ``` @@ -3420,17 +3425,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] @@ -3474,10 +3479,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:11977 +$ ./build/ripwire . --at=src/graph.h:11993 - - + + ``` @@ -3501,7 +3506,7 @@ _No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - ``` @@ -3527,11 +3532,11 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + @@ -3606,7 +3611,7 @@ _A second selection, the `fix/` ref family, picked at capture time from the refs ``` $ ./build/ripwire . --stray-content=fix/ - + ``` @@ -3631,7 +3636,7 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + @@ -3667,20 +3672,20 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi - + - - - + + - + -... [7 more line(s); run it to see the whole thing] + +... [2 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -3702,11 +3707,11 @@ _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] ``` @@ -3732,10 +3737,10 @@ _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] @@ -3760,11 +3765,11 @@ _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] @@ -3792,7 +3797,7 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat $ ./build/ripwire . --flags - + @@ -3873,7 +3878,7 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - @@ -3908,7 +3913,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3942,7 +3947,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3975,8 +3980,8 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + @@ -4009,7 +4014,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -4044,8 +4049,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:11977 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:12018 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:11993 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:12034 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1755 #3 0x102e01a44 in main src/main.cpp:4177 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) @@ -4074,7 +4079,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4234,7 +4239,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - + @@ -4307,14 +4312,14 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - + - - + + - - + @@ -4341,14 +4346,14 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - - + - - + + - - + @@ -4377,7 +4382,7 @@ $ ./build/ripwire . --skipped - @@ -4462,11 +4467,11 @@ _Rank by git change-frequency prior instead of PageRank._ ``` $ ./build/ripwire . --rank-by=churn --top-k=5 - + - - + + @@ -4475,8 +4480,8 @@ $ ./build/ripwire . --rank-by=churn --top-k=5 - -... [8 more line(s); run it to see the whole thing] + +... [9 more line(s); run it to see the whole thing] ``` **Shaped by:** `--since`, `--in` @@ -4497,22 +4502,22 @@ a trailing slash is ignored; absolute paths and '..' are refused). 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._ ``` -$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3 +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~3 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3 - - - - - - - - - - - - -... [5 more line(s); run it to see the whole thing] + + + + + + + + + + + + +... [21 more line(s); run it to see the whole thing] ``` **Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` @@ -4574,7 +4579,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4602,7 +4607,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=1bb96ec3e6969a9c entries=974 +ripwire legend dictionary ripwire.dict/v1 dictv=05b48ed93c807ee2 entries=981 : 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=) @@ -4655,12 +4660,12 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - - + @@ -4692,17 +4697,17 @@ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - - + + - + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -4723,17 +4728,17 @@ $ ./build/ripwire . --map-diff --top-k=5 - - + + - + - - - - + + + + ... [9 more line(s); run it to see the whole thing] ``` @@ -4757,17 +4762,17 @@ $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - - + + - + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -4807,17 +4812,17 @@ $ ./build/ripwire . --no-cache --top-k=3 - - + + - + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -4838,17 +4843,17 @@ $ ./build/ripwire . --no-ignore --top-k=3 - - + + - + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -4874,14 +4879,14 @@ $ ./build/ripwire . --max-file-size=8K --top-k=3 - + - + - + @@ -4954,17 +4959,17 @@ $ ./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3 - - + + - + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -5007,10 +5012,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":"', rd(f), re.S)), "(X3) %s: no -- inside a legend comment" % f) From 171c57448e654f12cb11895bb0b99caa90f80a09 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Thu, 29 Oct 2026 00:11:44 -0400 Subject: [PATCH 93/95] test: writtenspellingcheck binds both callees of the bare #dispatch union (CodeRabbit 4408459940) bind only required tsPrivHelper, so a union that dropped the other class's #dispatch still passed. A second bind line now requires tsOtherPrivHelper. Co-Authored-By: Claude Sonnet 5.5 --- test/writtenspellingcheck.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/writtenspellingcheck.sh b/test/writtenspellingcheck.sh index ff9d375cf..47eb972f6 100755 --- a/test/writtenspellingcheck.sh +++ b/test/writtenspellingcheck.sh @@ -65,6 +65,7 @@ bind ts 'RuntimeDriver.#dispatch' tsPrivHelper tsOtherPrivHelper bind ts 'Other.#dispatch' tsOtherPrivHelper tsPrivHelper bind ts 'src/driver/runtime-driver.ts:RuntimeDriver.#dispatch' tsPrivHelper tsOtherPrivHelper bind ts '#dispatch' tsPrivHelper # the bare name: both, as before (a union) +bind ts '#dispatch' tsOtherPrivHelper # ... and the other one too: a union names BOTH definitions' callees (CR 4408459940) refuse ts 'Missing.#dispatch' # no owner of that name refuse ts 'RuntimeDriver.#nope' # no such private member refuse ts 'RuntimeDriver.dispatch' # the '#' is part of the name: the unprefixed spelling names nothing From eee47f91978b40e38160e3196540826b14e22c5f Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I <210578314+joyful-ii-V-I@users.noreply.github.com> Date: Thu, 29 Oct 2026 00:38:20 -0400 Subject: [PATCH 94/95] upward-2hop: a yes/no question without a ? is a question (CodeRabbit 4408459921) forTaskIsQuestion listed does/do/is/are/can but not did/could/should/would/will/was/were/has/have, so 'Did the manifest syntax error ...' with no ? took the statement fallback seed when no body literal matched. The sibling auxiliaries are openers now. Red-first: forcallerhopcheck R15b (nine openers, each premise-checked on the rank fixture) printed a hop on af8fc25a and none here. None of the 163 stored S1 first-call argv starts a --for task with these words and no ?, so no S1 answer can move. Co-Authored-By: Claude Sonnet 5.5 --- src/forcallerhop.h | 3 ++- test/forcallerhopcheck.sh | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/forcallerhop.h b/src/forcallerhop.h index 03369e1ca..2394b9489 100644 --- a/src/forcallerhop.h +++ b/src/forcallerhop.h @@ -218,7 +218,8 @@ inline bool forTaskIsQuestion( std::string_view task ) return false; } const std::string first = routeLower( w.front() ); - static constexpr std::string_view kOpeners[] = { "how", "what", "why", "where", "which", "when", "who", "explain", "describe", "does", "do", "is", "are", "can" }; + static constexpr std::string_view kOpeners[] = { "how", "what", "why", "where", "which", "when", "who", "explain", "describe", "does", "do", "is", "are", "can", + "did", "could", "should", "would", "will", "was", "were", "has", "have" }; // yes/no questions carry no `?` at times return std::ranges::find( kOpeners, std::string_view( first ) ) != std::end( kOpeners ); } diff --git a/test/forcallerhopcheck.sh b/test/forcallerhopcheck.sh index 6fdf02eef..ed28ed322 100755 --- a/test/forcallerhopcheck.sh +++ b/test/forcallerhopcheck.sh @@ -367,6 +367,15 @@ done # R15 F5: with NO message-bearing row, a question gets no element (the fallback is for statements); the statement keeps today's seed (R3). run "$FR" --for="manifest syntax error?" >"$TMP/r15" if [ -s "$TMP/r15" ] && grep -q ']*n="load_one"[^>]*r="2"' "$TMP/r15" && ! grep -q '"$TMP/r15b" + if [ -s "$TMP/r15b" ] && grep -q ']*n="load_one"[^>]*r="2"' "$TMP/r15b"; then + ! grep -q ' Date: Thu, 29 Oct 2026 00:41:09 -0400 Subject: [PATCH 95/95] cli: drop the dead --defs row from kShapingVerbs (CodeRabbit review 5692571864 nitpick) --defs is an honorsPaging() member, so noticeShapingFlagIgnored returns before selectedShapingVerb is read; the table header says a verb holds a row in one table, never both. shapingflagcheck (C) pins both halves: no --defs row in kShapingVerbs, and --defs --max-tokens=200 still refuses without the not-read notice (no behaviour moves). Co-Authored-By: Claude Sonnet 5.5 --- src/cli.h | 1 - test/shapingflagcheck.sh | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/cli.h b/src/cli.h index f7a607a03..e5608b465 100644 --- a/src/cli.h +++ b/src/cli.h @@ -4236,7 +4236,6 @@ inline constexpr ShapingVerb kShapingVerbs[] = { { "--exemplar", nullptr, &Config::exemplar }, { "--around", nullptr, &Config::around }, { "--path", nullptr, &Config::pathSpec }, - { "--defs", nullptr, &Config::defsSpec }, // lane/defs-lookup: a fixed 16-row page (--offset pages), no budget { "--lego", nullptr, &Config::legoType }, { "--report", &Config::report, nullptr }, { "--slice", nullptr, &Config::sliceSpec }, diff --git a/test/shapingflagcheck.sh b/test/shapingflagcheck.sh index b8b8d01ff..e64c241fe 100755 --- a/test/shapingflagcheck.sh +++ b/test/shapingflagcheck.sh @@ -308,6 +308,14 @@ if [ "$nHonor" -ge 3 ]; then ok "(B) $nHonor honouring rows proved to actually && ok "(C) a report/paging verb still REFUSES --top-k and does not also emit the notice" \ || no "(C) --hotspots --top-k=3 exited $rcH with: [$( head -c 200 "$TMP/hot.err" )]" +# CR nitpick (harvest 463, review 5692571864, src/cli.h:4239): --defs is an honorsPaging() member, so it holds no kShapingVerbs row +# (the header's invariant: a verb is in one table, never both). The row was dead — noticeShapingFlagIgnored returns before reading it. +if grep -n -E '^[[:space:]]*\{ "--defs"[[:space:]]*,' "$ROOT/src/cli.h" >/dev/null; then no "(C) --defs holds a kShapingVerbs row while honorsPaging() already owns it"; else ok "(C) --defs is in honorsPaging() only: no kShapingVerbs row"; fi +"$BIN" . --defs=main --max-tokens=200 >/dev/null 2>"$TMP/defs.err"; rcD=$? +{ [ "$rcD" -eq 1 ] && grep -q 'honoring set' "$TMP/defs.err" && ! grep -q 'is not read by' "$TMP/defs.err"; } \ + && ok "(C) --defs --max-tokens=200 still REFUSES and does not also emit the notice" \ + || no "(C) --defs --max-tokens=200 exited $rcD with: [$( head -c 200 "$TMP/defs.err" )]" + # ── (D) the default map and its riders honour both and must stay silent ──────────────────────────────── for probe in "--top-k=3" "--max-tokens=200"; do "$BIN" . "$probe" >"$TMP/d.out" 2>"$TMP/d.err"; rcD=$?

@@ -7114,11 +7129,11 @@ stderr: *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: 25.13s** +**wall time: 51.83s** ````` - @@ -7147,7 +7162,7 @@ stderr: -… [151 more display lines; full output is 18020 bytes on 1 raw line(s)] +… [153 more display lines; full output is 18238 bytes on 1 raw line(s)] ````` stderr: @@ -7160,7 +7175,7 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 21.17s** +**exit code: 1** — **wall time: 34.18s** ````` (empty) @@ -7180,11 +7195,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr *The continuation packet with a REAL diff: verified changed symbols + blast radius + tests-to-run, then the heuristic rows.* -**wall time: 15.16s** +**wall time: 20.42s** ````` - + @@ -7209,11 +7224,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr - - - - - + + + + + ````` @@ -7222,10 +7237,10 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr *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: 8.22s** +**wall time: 8.34s** ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 keep this branch-free — it sits inside the PageRank sort comparator 77bb261ba29f1a6117ca1fbb718f4bc5beb50c06 lane/defs-lookup +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 keep this branch-free — it sits inside the PageRank sort comparator c8b49812b1ae3eaad7b5ac05fcf5cf8903b7780c integration/train-41 ````` stderr: @@ -7239,7 +7254,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort *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: 2.86s** +**wall time: 2.77s** ````` @@ -7252,7 +7267,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort - + @@ -7269,7 +7284,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort *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** — **wall time: 3.64s** +**exit code: 1** — **wall time: 2.16s** ````` (empty) @@ -7286,7 +7301,7 @@ ripwire: --note-add: target 'gitOneLine' is ambiguous — it matches 2 definitio *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** — **wall time: 2.00s** +**exit code: 1** — **wall time: 2.18s** ````` (empty) @@ -7303,20 +7318,20 @@ ripwire: --note-add: target not found: lessByScoreDescIdd (did you mean 'lessByS *The same symbol addressed by its CANONICAL id: already canonical, so nothing is rewritten and both notes land on ONE target.* -**wall time: 9.28s** +**wall time: 6.61s** ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 77bb261ba29f1a6117ca1fbb718f4bc5beb50c06 lane/defs-lookup +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-28 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort c8b49812b1ae3eaad7b5ac05fcf5cf8903b7780c integration/train-41 ````` ## `./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.* -**wall time: 2.79s** +**wall time: 3.56s** ````` - + - + - + @@ -7437,16 +7452,16 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, *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: 10.26s** +**wall time: 12.67s** ````` {"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":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":797,"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -7459,7 +7474,7 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_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: 4.07s** +**wall time: 6.56s** ````` {"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] @@ -7475,19 +7490,19 @@ ripwire edit: applied atomically; receipt carries region, blob_sha (post-check s *Insert BEFORE, with --edit-target-file pinning which same-named definition (here unambiguous — the disambiguator is simply honoured).* -**wall time: 9.59s** +**wall time: 20.31s** ````` {"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":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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":795,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":797,"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ````` stderr: @@ -7500,7 +7515,7 @@ ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, *An unknown TARGET refuses and leaves every file byte-identical.* -**exit code: 1** — **wall time: 4.23s** +**exit code: 1** — **wall time: 7.51s** ````` (empty) @@ -7516,7 +7531,7 @@ ripwire: --replace-symbol-body: symbol 'DoesNotExist' not found *A versioned multi-edit TRANSACTION preflighted without writing: the receipt shows what each op would read and touch.* -**wall time: 4.17s** +**wall time: 9.24s** Input file: @@ -7534,28 +7549,28 @@ Input file: ````` ````` -{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false," … [line truncated: 348 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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: 9.88s** +**wall time: 23.68s** ````` -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2988,"graph_unresolved":15925,"graph_unindexed":275,"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: 532 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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":2988,"graph_unresolved":15925,"graph_unindexed":275,"counts_floor":true}}]} +"graph_ambiguous":3015,"graph_unresolved":15989,"graph_unindexed":275,"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** +**exit code: 1** — **wall time: 4.59s** ````` (empty) @@ -7571,11 +7586,11 @@ ripwire: --edit-plan requires exactly one of --dry-run or --apply *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: 28.75s** +**exit code: 2** — **wall time: 76.98s** ````` - @@ -7604,7 +7619,7 @@ ripwire: --edit-plan requires exactly one of --dry-run or --apply -… [145 more display lines; full output is 17117 bytes on 1 raw line(s)] +… [147 more display lines; full output is 17335 bytes on 1 raw line(s)] ````` stderr: @@ -7618,7 +7633,7 @@ ripwire: --quality-delta gating: 2 preexisting-worse major finding(s); first: du *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: 22.24s** +**exit code: 1** — **wall time: 37.73s** ````` (empty) @@ -7637,7 +7652,7 @@ ripwire: --quality-baseline: this tree already holds 2 gating finding(s) against *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: 25.42s** +**wall time: 38.53s** ````` (empty) @@ -7647,16 +7662,16 @@ 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 32203 per-symbol rows, 33213 indexed symbols total) +ripwire: wrote ./.ripwire_quality_baseline (snapshot of 32860 per-symbol rows, 33886 indexed symbols total) ````` Artifact written: ````` - 5450298 .ripwire_quality_baseline + 5561300 .ripwire_quality_baseline # ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit -producer 83c8665d632378705842bb931cfcf07076001945324c77fa8e2e2e9bcc42d6ff -head 77bb261ba29f1a6117ca1fbb718f4bc5beb50c06 +producer 633cd66b1d7dcb157a0f07192be0a24084a59085643ee69a1f0e4daca636402f +head c8b49812b1ae3eaad7b5ac05fcf5cf8903b7780c dirty 1 absorbed 2 ccx 67c15011ee8b 0 @@ -7669,11 +7684,11 @@ ccx 6a732f6699b *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: 26.21s** +**wall time: 38.19s** ````` - ````` @@ -7686,6 +7701,8 @@ ccx 6a732f6699b *initialize + tools/list: the manifest an agent host loads at session start — every verb's name, description and input schema.* +**wall time: 1.33s** + ````` {"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] @@ -7796,57 +7813,57 @@ quality_delta desc_bytes= 742 schema_bytes= 279 required=[] *MCP `for`: always bundle=sigs (never the CLI's compact route), the same ranked signatures as --for.* -**wall time: 4.77s** +**wall time: 7.60s** ````` {"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: 6565 more bytes on this line] -"_index":"[index: files=3943 symbols=33211 hash=d2a64018]","_fresh":"ok"}} -{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":" - - + + - + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -107,17 +107,17 @@ $ ./build/ripwire . --top-k=5 --no-inventory - + - + - - - - + + + + ... [9 more line(s); run it to see the whole thing] ``` @@ -141,10 +141,10 @@ $ ./build/ripwire . --max-tokens=1500 - + - + @@ -171,7 +171,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` @@ -216,14 +216,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` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -253,7 +253,7 @@ _Restore the / sections the --for bundle collapses to a counted s ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --sections=lego,compose - + @@ -280,14 +280,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` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -315,7 +315,7 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - + @@ -347,13 +347,13 @@ _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 ) @@ -379,7 +379,7 @@ _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 - + @@ -410,11 +410,11 @@ _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 - - + 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() @@ -440,14 +440,14 @@ _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 std::string staleByKindValue( const std::vector<StaleAck>& staleAcks ) +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] ``` @@ -470,7 +470,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -501,7 +501,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=2040 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 10.137) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 595804 B; lines="3866-3870"; dropped_by_budget=164] [truncated: 1865 of 7741 bytes] -### `--field-affinity[=STRUCT]` +━━ commands.md (relevance 11.020) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 595976 B; lines="557-582"; dropped_by_budget=164] [truncated: 1854 of 4257 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` @@ -624,19 +629,19 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - + - - - - - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -729,17 +734,17 @@ $ ./build/ripwire . --no-stable --top-k=3 - - + + - + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -763,7 +768,7 @@ $ ./build/ripwire . --around=rankGraphTeleport - + @@ -814,18 +819,18 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - - + + - - - - - - + + + + + + ``` @@ -849,10 +854,10 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - + - - + + @@ -883,16 +888,16 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - + - + - + - + @@ -920,19 +925,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] ``` @@ -955,16 +960,16 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - + - - + + - + @@ -985,10 +990,10 @@ _Which definitions each name binds, one row per definition group with sel= (the $ ./build/ripwire . --defs=main,rankGraphTeleport - + - + @@ -1013,10 +1018,10 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + - + @@ -1039,7 +1044,7 @@ _Transitive blast radius — everything that reaches SYM. NOW carries shown/capp ``` $ ./build/ripwire . --impact=rankGraphTeleport - @@ -1123,7 +1128,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - @@ -1182,7 +1187,7 @@ _Mid-task situational report for the current git diff — recorded against a CLE $ ./build/ripwire . --situ ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: 77bb261ba +at: c8b49812b (0 changed files — working tree is clean, nothing to analyze) ``` @@ -1201,18 +1206,18 @@ _The continuation packet for the NEXT session: disk truth (branch/sha ``` $ ./build/ripwire . --handoff - + - + - - - + + + + ... [1 more line(s); run it to see the whole thing] ``` @@ -1236,7 +1241,7 @@ _Pre-PR gate on a CLEAN tree: no obligations, exit 0._ ``` $ ./build/ripwire . --test-gate - ``` @@ -1321,16 +1326,16 @@ $ ./build/ripwire . --grep=DISCLOSE --and=cache CDATA text (/ context around it); complete=1 only for an exhaustive literal scan (beside pattern_meta=, of the literal reading only); = off-index. window: shown= capped= (capped=1 cut). hits_capped=1: hits= is a floor. root=: p= relative to it. parse_degraded=1: E … [line truncated: 1494 more bytes on this line] - + - - - - + + + + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1532,14 +1537,14 @@ $ ./build/ripwire . --query="teleport pagerank" --top-k=5 - + - + - + ... [17 more line(s); run it to see the whole thing] @@ -1563,7 +1568,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1599,15 +1604,15 @@ $ ./build/ripwire . --pack-signatures --top-k=10 signature rows. est_tokens=: price as emitted (an upper bound under compact). root=: p= relative to it. pr_iters=N: PageRank iterations. declined=K: K call sites without edges, including inert Elixir quote sites after unquote/unquote_splicing re-entry. external=K: K calls proven … [line truncated: 1465 more bytes on this line] - - + + - + - - - + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1629,7 +1634,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)" ); @@ -1656,11 +1661,11 @@ _NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map ride ``` $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport - + - + of ; resolved callees. window: shown= total= capped= (capped=1 cut). sibs_capped=/inc_capped=: 1 = cut. est_tokens=: price as emitted (an upper bound under compact). root=: p= relative to it. : that callee matched by name alone (receiver unproven); it does NOT mean the edge is fal … [line truncated: 443 more bytes on this line] -: 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= lcom4= amp=: callers, callees, cyclomatic/cognitive complexity, lines, parameters, nesting depth, coupled types, class cohesion, 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=). --> - - + + - + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1832,19 +1837,19 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - - - + + + + + - - + + + - - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1869,17 +1874,17 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - - + + - - + + - + - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1897,17 +1902,17 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + - + - + ... [17 more line(s); run it to see the whole thing] @@ -1928,10 +1933,10 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - - + + + - @@ -1961,7 +1966,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1996,12 +2001,12 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - - + @@ -2027,17 +2032,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] @@ -2064,12 +2069,12 @@ _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] ``` @@ -2095,7 +2100,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2107,7 +2112,7 @@ $ ./build/ripwire . --naming-calibration

@@ -3122,7 +3116,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3152,7 +3146,7 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - @@ -3392,7 +3386,7 @@ _Bare --slice=SYM: the INVENTORY of sliceable locals (), so a calle ``` $ ./build/ripwire . --slice=rankGraphTeleport - + @@ -3429,7 +3423,7 @@ _Forward flow: which statements the seed's value reaches, at the default depth b ``` $ ./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd - + @@ -3509,21 +3503,10 @@ _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` @@ -3547,20 +3530,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` @@ -3630,7 +3609,7 @@ _A second selection, the `fix/` ref family, picked at capture time from the refs ``` $ ./build/ripwire . --stray-content=fix/ - + ``` @@ -3655,7 +3634,7 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + @@ -3691,13 +3670,13 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi - - + @@ -3725,8 +3704,8 @@ _Which ref's tree defines or mentions SYM — HEAD first, then every local branc ``` $ ./build/ripwire . --whereis=rankGraphTeleport - - + + @@ -3755,13 +3734,13 @@ _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 - -. window: shown= total= capped= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). at=: commit+dirty+shallow. next=: the one pasteable follow-up. hits=N: occurrences in HEAD plus every scanned local ref's full tree (the total rows). on-head=1|0: whether HEAD's tree holds it; 0 beside hits = it lives only on a branch. head_labels=index: HEAD kind= from the parsed index; lexical: text heuristic. ref_labels=parsed: another ref's kind= from the index's own tags parse of its blob (one per blob and path; HEAD's own blob takes HEAD's labels); a blob with no line a loose lexical test calls worth parsing is not parsed and its rows read ref; kind=text: such a line no parse read, never a def. unparsed blobs=N: other refs' blobs holding a line worth parsing that no parse read (past the runaway guard, or the parser could not finish); those lines read kind=text; next= lifts the guard. unparsed rows=N: the kind=text rows those blobs gave. more hits=N: rows after this page; page on with offset=next_offset. head_date=: a hit without tip= date= has tip= at=, date= this. --> + - + + ... [24 more line(s); run it to see the whole thing] ``` @@ -3783,14 +3762,14 @@ _The whole hit list asked for by name: under the same 60-row cap HEAD's referenc ``` $ ./build/ripwire . --whereis=rankGraphTeleport --whereis-listing=all - -. window: shown= total= capped= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). at=: commit+dirty+shallow. next=: the one pasteable follow-up. hits=N: occurrences in HEAD plus every scanned local ref's full tree (the total rows). on-head=1|0: whether HEAD's tree holds it; 0 beside hits = it lives only on a branch. head_labels=index: HEAD kind= from the parsed index; lexical: text heuristic. ref_labels=parsed: another ref's kind= from the index's own tags parse of its blob (one per blob and path; HEAD's own blob takes HEAD's labels); a blob with no line a loose lexical test calls worth parsing is not parsed and its rows read ref; kind=text: such a line no parse read, never a def. unparsed blobs=N: other refs' blobs holding a line worth parsing that no parse read (past the runaway guard, or the parser could not finish); those lines read kind=text; next= lifts the guard. unparsed rows=N: the kind=text rows those blobs gave. hit t_bytes=N: t= previews an N-byte line (over 300 B): 200 B at most, its first bytes cut on a UTF-8 boundary plus an ellipsis. t_cut rows=N: rows of this page whose t= is a preview (each carries t_bytes=); next= replays this page (same listing, window, rows) with every line whole. more hits=N: rows after this page; page on with offset=next_offset. head_date=: a hit without tip= date= has tip= at=, date= this. --> + - - + + ... [23 more line(s); run it to see the whole thing] ``` @@ -3932,7 +3911,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3966,7 +3945,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3999,8 +3978,8 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + @@ -4033,7 +4012,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -4098,7 +4077,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4258,7 +4237,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - @@ -4331,13 +4310,13 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - + - - + + - + @@ -4365,13 +4344,13 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - + - - + + - + @@ -4489,8 +4468,8 @@ $ ./build/ripwire . --rank-by=churn --top-k=5 - - + + @@ -4521,20 +4500,20 @@ a trailing slash is ignored; absolute paths and '..' are refused). 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._ ``` -$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3 +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~9 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3 - - + + - + + - - + ... [5 more line(s); run it to see the whole thing] ``` @@ -4598,7 +4577,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4679,7 +4658,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -4744,20 +4723,20 @@ _Full map re-ranked with teleport toward git-changed files — clean tree, so ch ``` $ ./build/ripwire . --map-diff --top-k=5 - + - - - - + + + + - - + + + + + - - - ... [9 more line(s); run it to see the whole thing] ``` @@ -5031,10 +5010,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":" @@ -63,7 +63,7 @@ *Same map, capped to the 5 highest-ranked symbols.* -**wall time: 1.20s** +**wall time: 5.53s** ````` @@ -103,7 +103,7 @@ *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.* -**wall time: 1.18s** +**wall time: 8.60s** ````` @@ -135,7 +135,7 @@ *NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map rides along with the body you asked for.* -**wall time: 1.34s** +**wall time: 8.51s** ````` @@ -178,7 +178,7 @@ *--top-k=0 with NO payload verb asked for — REFUSES (exit 1) naming the payload verbs, never an empty map.* -**exit code: 1** +**exit code: 1** — **wall time: 1.36s** ````` (empty) @@ -194,7 +194,7 @@ ripwire: --top-k=0 means "no ranked map, payload only" — pass a payload verb ( *SHAPE the map to fit ~1500 tokens (binary-search top-K).* -**wall time: 1.23s** +**wall time: 5.70s** ````` @@ -213,7 +213,7 @@ ripwire: --top-k=0 means "no ranked map, payload only" — pass a payload verb ( *GATE form: exit 3 if the map's own est_tokens exceeds the budget (over-budget failure shape).* -**exit code: 3** — **wall time: 1.41s** +**exit code: 3** — **wall time: 5.33s** ````` @@ -230,10 +230,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *The task lens: ranked signatures + quality metrics framed for the task.* -**wall time: 5.04s** +**wall time: 18.75s** ````` - @@ -263,17 +263,17 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 inline std::uint64_t byteHash( const char* data, std::size_t n ) noexcept void emitGrepHandleLegend( bool enabled ) inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) -… [127 more display lines; full output is 20022 bytes on 8 raw line(s)] +… [127 more display lines; full output is 20016 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: 3.29s** +**wall time: 31.62s** ````` - @@ -303,17 +303,17 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 & p, float alpha = 0.85f ) -… [28 more display lines; full output is 5869 bytes on 29 raw line(s)] +… [28 more display lines; full output is 5863 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: 3.07s** +**wall time: 15.45s** ````` - + @@ -343,17 +343,17 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 ### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` 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 ) -… [155 more display lines; full output is 24105 bytes on 72 raw line(s)] +… [155 more display lines; full output is 24099 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: 3.04s** +**wall time: 13.63s** ````` - + @@ -374,10 +374,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *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: 3.60s** +**wall time: 16.10s** ````` - + @@ -407,17 +407,17 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 struct LintRule inline bool hasPytestTable( std::string_view source ) inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [139 more display lines; full output is 19852 bytes on 44 raw line(s)] +… [139 more display lines; full output is 19846 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: 3.06s** +**wall time: 15.92s** ````` - @@ -447,17 +447,17 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 std::optional<int> runEvalViews( const MainDispatch& d ) inline double binomialChoose( std::size_t n, std::size_t k ) struct RecallSectionPick -… [146 more display lines; full output is 21546 bytes on 18 raw line(s)] +… [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: 3.05s** +**wall time: 12.75s** ````` - @@ -487,14 +487,14 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 std::optional<int> runEvalViews( const MainDispatch& d ) inline double binomialChoose( std::size_t n, std::size_t k ) struct RecallSectionPick -… [156 more display lines; full output is 21837 bytes on 18 raw line(s)] +… [156 more display lines; full output is 21831 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.* -**wall time: 1.11s** +**wall time: 2.26s** ````` @@ -512,7 +512,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* -**wall time: 2.12s** +**wall time: 7.54s** ````` - + ripwire '.' --verify='calls(runDefaultMap, rankGraphTeleport)' @@ -553,12 +553,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *The honest half of the contract: a task with no ripwire-shaped evidence ABSTAINS with zero commands rather than guessing.* -**wall time: 1.49s** +**wall time: 5.09s** ````` - + ````` @@ -566,12 +566,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9929 > budget=100 *Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* -**wall time: 1.29s** +**wall time: 4.67s** ````` ripwire recall — "quality delta gating exit codes" — 124 relevant of 305 document files, best-first — total=124 shown=8 capped=1 truncated=6 generated_demoted=3 max_tokens=8000 share_bytes=2468 est_tokens=4165 -━━ README.md (relevance 8.062) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 222723 B; lines="2747-2759,2760-2767"; dropped_by_budget=23] +━━ README.md (relevance 8.063) ━━ [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. @@ -595,7 +595,7 @@ refuses by default. -━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.513) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.515) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] ### Root spelling (maintainer-owned; reproduce it once so you recognise it) ```bash @@ -604,17 +604,40 @@ refuses by default. ## `./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 1297593-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.* +*The directory-as-knowledge-base pattern: --recall pointed at a 1307034-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.* + +**wall time: 1.13s** ````` -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=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 10.971) ━━ [sections: 1 of 166 selected (193 in doc), section-granular; whole doc 602072 B; lines="555-580"; dropped_by_budget=165] [truncated: 1854 of 4257 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? -━━ commands.md (relevance 10.141) ━━ [sections: 1 of 165 selected (193 in doc), section-granular; whole doc 592169 B; lines="3836-3840"; dropped_by_budget=164] [truncated: 1865 of 7741 bytes] -### `--field-affinity[=STRUCT]` +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: -**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. +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. -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] +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)) ````` @@ -629,6 +652,8 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *File-by-file orientation map (top symbols per file).* +**wall time: 2.94s** + ````` @@ -667,7 +692,7 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *Self-contained HTML force-directed call graph.* -**wall time: 3.98s** +**wall time: 16.41s** ````` (empty) @@ -676,14 +701,14 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas Artifact written: ````` - 156113 /aux/map2.html + 156107 /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.32s** +**wall time: 3.90s** ````` @@ -728,6 +753,8 @@ Artifact written: *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.* +**wall time: 1.80s** + ````` @@ -766,6 +793,8 @@ Artifact written: *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.* +**wall time: 2.19s** + ````` @@ -804,6 +833,8 @@ Artifact written: *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.* +**wall time: 2.77s** + ````` @@ -842,6 +873,8 @@ Artifact written: *Who calls SYM (1-hop in-edges).* +**wall time: 2.03s** + ````` @@ -859,7 +892,7 @@ Artifact written: *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.* -**wall time: 1.08s** +**wall time: 3.49s** ````` @@ -879,7 +912,7 @@ Artifact written: *Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* -**exit code: 1** — **wall time: 2.27s** +**exit code: 1** — **wall time: 7.03s** ````` @@ -896,6 +929,8 @@ ripwire: --callers symbol not found: DoesNotExist *What SYM calls (1-hop out-edges).* +**wall time: 2.00s** + ````` @@ -917,7 +952,7 @@ ripwire: --callers symbol not found: DoesNotExist *The resolvable use-sites (call/read/write/import/extends) with file:line; count= is a floor.* -**wall time: 1.38s** +**wall time: 2.85s** ````` @@ -938,7 +973,7 @@ ripwire: --callers symbol not found: DoesNotExist *Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* -**wall time: 1.10s** +**wall time: 2.61s** ````` @@ -978,7 +1013,7 @@ ripwire: --callers symbol not found: DoesNotExist *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.* -**wall time: 1.09s** +**wall time: 1.97s** ````` @@ -1018,6 +1053,8 @@ ripwire: --callers symbol not found: DoesNotExist *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).* +**wall time: 2.33s** + ````` @@ -1056,6 +1093,8 @@ ripwire: --callers symbol not found: DoesNotExist *Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_defs and resolves the right `main` (was reachable="0").* +**wall time: 1.31s** + ````` @@ -1089,7 +1128,7 @@ ripwire: --callers symbol not found: DoesNotExist *Which definitions each name binds, one row per definition group with sel= (the spelling that picks it alone in any verb), class=source|test and in= callers; source first.* -**wall time: 1.01s** +**wall time: 1.92s** ````` @@ -1122,6 +1161,8 @@ ripwire: --callers symbol not found: DoesNotExist *Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* +**wall time: 1.64s** + ````` @@ -1139,7 +1180,7 @@ ripwire: --callers symbol not found: DoesNotExist *Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* -**wall time: 1.02s** +**wall time: 3.37s** ````` @@ -1179,6 +1220,8 @@ ripwire: --callers symbol not found: DoesNotExist *Markdown docs that name SYM in a backtick (doc<->code edges).* +**wall time: 2.39s** + ````` @@ -1196,7 +1239,7 @@ ripwire: --callers symbol not found: DoesNotExist *Test files that transitively reach the changed file.* -**wall time: 1.17s** +**wall time: 2.06s** ````` @@ -1217,46 +1260,24 @@ ripwire: --callers symbol not found: DoesNotExist *Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below).* -**wall time: 4.99s** +**wall time: 3.27s** ````` -ripwire situational-awareness — 1 changed file(s), 25 symbols in them +ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: de9d903b2+dirty - change_set: granularity=file changed_symbols=25 file_symbols=25 files_narrowed=0 files_file_granular=1 diff_hunks=3 diff_hunks_located=0 diff_hunks_unlocated=3 — 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] - decl/def partners (3) not_dependents=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/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: 0 symbols across 0 files transitively depend on these changes - counts_floor=1 graph_ambiguous=2988 graph_unresolved=15914 graph_unindexed=275 (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] - [2] tests to run (0): (none transitively reach these files) - script_gates_unmodelled=795 — 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 (117) window="18mo@HEAD" commits="5516" (showing 8 of 117 files — shown=8 total=117 capped=1; next: --situ --limit=117): - README.md (co-edited in 93% of commits) - docs/EVALS.md (co-edited in 82% of commits) - test/regression.sh (co-edited in 65% of commits) - CHANGELOG.md (co-edited in 29% of commits) - docs/COMMANDS.md (co-edited in 24% of commits) - src/cli.h (co-edited in 22% of commits) - .github/pargates-shard-weights.json (co-edited in 20% of commits) - src/quality.h (co-edited in 18% of commits) - next: --test-gate +at: 70c265cd6 + (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.* -**exit code: 4** — **wall time: 5.40s** +**wall time: 4.85s** ````` - - - - - + + ````` @@ -1264,7 +1285,7 @@ at: de9d903b2+dirty *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.* -**wall time: 1.24s** +**wall time: 1.04s** ````` @@ -1447,7 +1468,7 @@ at: de9d903b2+dirty *The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* -**wall time: 1.75s** +**wall time: 2.46s** ````` @@ -1487,7 +1508,7 @@ at: de9d903b2+dirty *Raw BM25 ranking (debug lens; --for is the real verb).* -**wall time: 1.18s** +**wall time: 2.04s** ````` @@ -1497,11 +1518,11 @@ at: de9d903b2+dirty - + - + @@ -1512,12 +1533,12 @@ at: de9d903b2+dirty - + - + … [22 more display lines; full output is 3459 bytes on 1 raw line(s)] @@ -1532,10 +1553,10 @@ at: de9d903b2+dirty *Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* -**wall time: 2.74s** +**wall time: 4.36s** ````` - + @@ -1563,16 +1584,16 @@ at: de9d903b2+dirty 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 ) -std::uint32_t testIterationCeiling() noexcept -inline const char* rankByLegendFor( const char* label ) noexcept -… [156 more display lines; full output is 22225 bytes on 72 raw line(s)] +inline const char* rankByLegendFor( const char* label ) noexcept +std::uint32_t testIterationCeiling() noexcept +… [156 more display lines; full output is 22219 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.28s** +**wall time: 1.94s** ````` @@ -1612,7 +1633,7 @@ at: de9d903b2+dirty *Control-flow skeleton of one symbol, payload-only via the new --top-k=0.* -**wall time: 1.13s** +**wall time: 1.24s** ````` @@ -1920,7 +1941,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *File->file dependency graph (god-files, cycles).* -**wall time: 1.17s** +**wall time: 1.09s** ````` @@ -1960,12 +1981,12 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Complexity x recent git churn (maintenance pain).* -**wall time: 4.60s** +**wall time: 6.11s** ````` - + @@ -1993,14 +2014,14 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan -… [14 more display lines; full output is 5771 bytes on 1 raw line(s)] +… [14 more display lines; full output is 5765 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --clones` *Token-normalized duplicate bodies.* -**wall time: 3.46s** +**wall time: 3.66s** ````` @@ -2040,37 +2061,37 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Files that change together in git (hidden coupling).* -**wall time: 3.89s** +**wall time: 6.37s** ````` - - - + + - - - + + + + - - + + + - - - + - + + + + - - @@ -2082,11 +2103,11 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Hotspots scoped to RECENT churn (the regression lens).* -**wall time: 5.28s** +**wall time: 5.91s** ````` - + @@ -2115,14 +2136,14 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan -… [13 more display lines; full output is 5138 bytes on 1 raw line(s)] +… [13 more display lines; full output is 5132 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.* -**wall time: 1.02s** +**wall time: 1.21s** ````` @@ -2162,7 +2183,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* -**wall time: 2.97s** +**wall time: 3.75s** ````` @@ -2202,7 +2223,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *User lint rules (YAML, ast-grep style) from a directory.* -**wall time: 1.36s** +**wall time: 2.24s** ````` @@ -2229,6 +2250,8 @@ ripwire: AST query did not compile for any grammar: (this_is_not_a_real_node @x *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.* +**wall time: 1.26s** + Input file: ````` @@ -2282,7 +2305,7 @@ The joined finding — past the display cut above, extracted so the join is visi *Cluster the call graph into cohesive modules.* -**wall time: 1.41s** +**wall time: 1.90s** ````` @@ -2322,7 +2345,7 @@ The joined finding — past the display cut above, extracted so the join is visi *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=.* -**wall time: 1.02s** +**wall time: 1.92s** ````` @@ -2362,6 +2385,8 @@ The joined finding — past the display cut above, extracted so the join is visi *The restoring knob: --zoom-levels=N prints N levels (0 = the whole tree); here three, ~11 KB.* +**wall time: 1.73s** + ````` @@ -2440,7 +2465,7 @@ Call-graph isolate provenance: 4128 declaration, 2511 header, 3713 source, 5248 *Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* -**wall time: 1.34s** +**wall time: 2.82s** ````` @@ -2480,6 +2505,8 @@ Call-graph isolate provenance: 4128 declaration, 2511 header, 3713 source, 5248 *Module (directory) dependency graph as a Mermaid diagram.* +**wall time: 2.58s** + ````` %% ripwire --mermaid: module (directory) dependency graph — node = dir (symbol count), edge = inter-module calls (>= 3). Render at mermaid.live. flowchart LR @@ -2518,11 +2545,11 @@ flowchart LR *Bus-factor: recency-weighted author ownership per file.* -**wall time: 5.13s** +**wall time: 7.30s** ````` - + @@ -2551,14 +2578,14 @@ flowchart LR -… [979 more display lines; full output is 107182 bytes on 1 raw line(s)] +… [979 more display lines; full output is 107176 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.* -**wall time: 1.31s** +**wall time: 2.93s** ````` @@ -2571,7 +2598,7 @@ flowchart LR *Which symbols a TEST FILE exercises — the reverse direction of --affected.* -**wall time: 1.04s** +**wall time: 3.15s** ````` @@ -2584,7 +2611,7 @@ flowchart LR *Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* -**wall time: 1.06s** +**wall time: 1.14s** ````` @@ -2597,11 +2624,11 @@ flowchart LR *On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* -**wall time: 36.95s** +**wall time: 26.05s** ````` - -, 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. --> + ````` @@ -2615,11 +2642,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 20.23s** +**wall time: 33.98s** ````` - -, 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. --> + @@ -2648,7 +2675,7 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g -… [140 more display lines; full output is 15980 bytes on 1 raw line(s)] +… [140 more display lines; full output is 16170 bytes on 1 raw line(s)] ````` stderr: @@ -2661,11 +2688,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree).* -**wall time: 3.97s** +**wall time: 19.42s** ````` - @@ -2680,103 +2707,75 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *No-LLM review-evidence bundle for the working-tree diff (clean tree = empty).* -**wall time: 6.58s** +**wall time: 3.15s** ````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [71 more display lines; full output is 7949 bytes on 1 raw line(s)] + + + ````` ## `./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: 7.41s** +**wall time: 11.57s** ````` - - + - - - - + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - -… [13 more display lines; full output is 4481 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: 20.72s** +**wall time: 24.44s** ````` - - + + - - - - + - - ````` @@ -2785,11 +2784,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Which refs of the `lane/` ref family still hold divergent authored work vs HEAD, with verdicts.* -**wall time: 45.15s** +**wall time: 77.74s** ````` - + @@ -2818,18 +2817,18 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g -… [281 more display lines; full output is 35941 bytes on 1 raw line(s)] +… [281 more display lines; full output is 35915 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --stray-content=fix/` *A second selection, the `fix/` ref family, 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: 5.00s** +**wall time: 6.29s** ````` - + ````` @@ -2837,11 +2836,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 128.45s** +**wall time: 180.37s** ````` - + @@ -2854,9 +2853,9 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g - + - + @@ -2870,14 +2869,14 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g -… [33541 more display lines; full output is 4642024 bytes on 1 raw line(s)] +… [33526 more display lines; full output is 4640965 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: 2.92s** +**exit code: 1** — **wall time: 6.44s** ````` (empty) @@ -2894,17 +2893,17 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 169.62s** +**exit code: 2** — **wall time: 431.08s** ````` - - + @@ -2924,11 +2923,11 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 13.56s** +**wall time: 23.43s** ````` - - + + @@ -2946,7 +2945,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - + ````` @@ -2954,18 +2953,18 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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: 14.96s** +**wall time: 46.52s** ````` - -. window: shown= total= capped= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). at=: commit+dirty+shallow. next=: the one pasteable follow-up. hits=N: occurrences in HEAD plus every scanned local ref's full tree (the total rows). on-head=1|0: whether HEAD's tree holds it; 0 beside hits = it lives only on a branch. head_labels=index: HEAD kind= from the parsed index; lexical: text heuristic. ref_labels=parsed: another ref's kind= from the index's own tags parse of its blob (one per blob and path; HEAD's own blob takes HEAD's labels); a blob with no line a loose lexical test calls worth parsing is not parsed and its rows read ref; kind=text: such a line no parse read, never a def. unparsed blobs=N: other refs' blobs holding a line worth parsing that no parse read (past the runaway guard, or the parser could not finish); those lines read kind=text; next= lifts the guard. unparsed rows=N: the kind=text rows those blobs gave. hit t_bytes=N: t= previews an N-byte line (over 300 B): 200 B at most, its first bytes cut on a UTF-8 boundary plus an ellipsis. t_cut rows=N: rows of this page whose t= is a preview (each carries t_bytes=); next= replays this page (same listing, window, rows) with every line whole. more hits=N: rows after this page; page on with offset=next_offset. head_date=: a hit without tip= date= has tip= at=, date= this. --> + - - - + + @@ -2987,18 +2986,18 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer -… [36 more display lines; full output is 13829 bytes on 1 raw line(s)] +… [36 more display lines; full output is 13365 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: 20.38s** +**wall time: 30.39s** ````` - -. window: shown= total= capped= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). at=: commit+dirty+shallow. next=: the one pasteable follow-up. hits=N: occurrences in HEAD plus every scanned local ref's full tree (the total rows). on-head=1|0: whether HEAD's tree holds it; 0 beside hits = it lives only on a branch. head_labels=index: HEAD kind= from the parsed index; lexical: text heuristic. ref_labels=parsed: another ref's kind= from the index's own tags parse of its blob (one per blob and path; HEAD's own blob takes HEAD's labels); a blob with no line a loose lexical test calls worth parsing is not parsed and its rows read ref; kind=text: such a line no parse read, never a def. unparsed blobs=N: other refs' blobs holding a line worth parsing that no parse read (past the runaway guard, or the parser could not finish); those lines read kind=text; next= lifts the guard. unparsed rows=N: the kind=text rows those blobs gave. more hits=N: rows after this page; page on with offset=next_offset. head_date=: a hit without tip= date= has tip= at=, date= this. --> + - - + + @@ -3039,22 +3038,22 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer -… [35 more display lines; full output is 14842 bytes on 1 raw line(s)] +… [35 more display lines; full output is 14378 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.22s** +**wall time: 28.46s** ````` - - - + + + - + ````` @@ -3062,7 +3061,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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.30s** +**wall time: 1.95s** ````` @@ -3102,7 +3101,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Blast radius of turning ONE gate on: live code, symbols, transitive reach, covering tests.* -**wall time: 1.07s** +**wall time: 2.03s** ````` @@ -3140,7 +3139,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *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.13s** +**exit code: 1** — **wall time: 1.77s** ````` (empty) @@ -3157,10 +3156,10 @@ ripwire: run `ripwire . --flags` for the gate table *NEW VERB: pre-hoc lane plan — which of 3 parallel worktrees would COLLIDE, before a line is written. JSON on stdout.* -**wall time: 4.23s** +**wall time: 5.25s** ````` -{"v":1,"verb":"plan-lanes","at":"de9d903b2+dirty","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":3931,"symbols":33184,"edge … [line truncated: 344 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"70c265cd6","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":3931,"symbols":33184,"edges":312 … [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"}, @@ -3190,14 +3189,14 @@ ripwire: run `ripwire . --flags` for the gate table "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/cli.h","n":"inHostActive","scope":"rw","key":"4446994deb74226c","id":"./src/cli.h::rw::inHostActive","id_addressable":true,"id_collides_with":0,"l":4675,"ord":0,"overloads":1," … [line truncated: 46 more bytes on this line] {"p":"./src/jsrunner.h","n":"looksLikeJsTestFile","scope":"rw::jsrunner","key":"64417bc2d02fc89b","id":"./src/jsrunner.h::rw::jsrunner::looksLikeJsTestFile","id_addressable":true,"id_collides_with":0,"l":511,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":8,"churn":17,"tested":0}, -… [69 more display lines; full output is 20529 bytes on 1 raw line(s)] +… [69 more display lines; full output is 20523 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.62s** +**wall time: 5.65s** Input file: @@ -3208,7 +3207,7 @@ write regression tests for the new filter ````` ````` -{"v":1,"verb":"plan-lanes","at":"de9d903b2+dirty","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3931,"symbols":33184,"edges":31243,"ambiguous":2988,"unresolved":15914},"carve":null,"core":{" … [line truncated: 10 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"70c265cd6","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3931,"symbols":33184,"edges":31243,"ambiguous":2988,"unresolved":15914},"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":658,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":490,"tested":0}, @@ -3238,7 +3237,7 @@ write regression tests for the new filter {"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":3083,"ord":0,"overloads":1,"amb":0,"cx":2,"ccx":1,"churn":515,"tested":0}, {"p":"./src/crossref.h","n":"writeWhereisTextCut","scope":"crossref","key":"9a3ef6403f53ce21","id":"./src/crossref.h::crossref::writeWhereisTextCut","id_addressable":true,"id_collides_with":0,"l":4542,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":3,"churn":95,"tested":0}, -… [58 more display lines; full output is 18354 bytes on 1 raw line(s)] +… [58 more display lines; full output is 18348 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --plan-lanes=99 --task=x` @@ -3301,7 +3300,7 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out *The honest refusal (exit 1): Lang is an `enum class`, not a struct — no offsets are fabricated.* -**exit code: 1** +**exit code: 1** — **wall time: 1.21s** ````` (empty) @@ -3317,11 +3316,11 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped *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.37s** +**wall time: 2.97s** ````` - + @@ -3350,18 +3349,18 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped -… [244 more display lines; full output is 24823 bytes on 1 raw line(s)] +… [244 more display lines; full output is 24817 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.47s** +**wall time: 2.78s** ````` - + @@ -3390,7 +3389,7 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped -… [274 more display lines; full output is 26240 bytes on 1 raw line(s)] +… [274 more display lines; full output is 26234 bytes on 1 raw line(s)] ````` Tail of the same output — the `` section: @@ -3427,12 +3426,12 @@ Tail of the same output — the `` section: *Same report, with git history splitting stale mentions into deleted-by-commit vs never-existed.* -**wall time: 2.18s** +**wall time: 3.40s** ````` - - + + @@ -3460,13 +3459,15 @@ Tail of the same output — the `` section: -… [224 more display lines; full output is 24886 bytes on 1 raw line(s)] +… [224 more display lines; full output is 24880 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).* +**wall time: 1.18s** + Input file: ````` @@ -3519,6 +3520,8 @@ AddressSanitizer:DEADLYSIGNAL *List all field notes (write-side memory) — the committed .ripwire_notes at the repo root, each with the sha/branch it was recorded at.* +**wall time: 1.04s** + ````` @@ -3542,7 +3545,7 @@ AddressSanitizer:DEADLYSIGNAL *ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* -**wall time: 1.01s** +**wall time: 1.22s** ````` @@ -3582,7 +3585,7 @@ AddressSanitizer:DEADLYSIGNAL *Fan-out form: one shared core + 3 per-agent slices carved along call-graph communities.* -**wall time: 1.05s** +**wall time: 1.38s** ````` @@ -3653,16 +3656,16 @@ AddressSanitizer:DEADLYSIGNAL 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 ) -std::uint32_t testIterationCeiling() noexcept -inline const char* rankByLegendFor( const char* label ) noexcept -… [124 more display lines; full output is 19115 bytes on 10 raw line(s)] +inline const char* rankByLegendFor( const char* label ) noexcept +std::uint32_t testIterationCeiling() noexcept +… [124 more display lines; full output is 19109 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.80s** +**wall time: 3.39s** ````` (empty) @@ -3679,7 +3682,7 @@ Artifact written: *One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* -**wall time: 6.54s** +**wall time: 4.19s** Input file: @@ -3694,7 +3697,7 @@ lego:Vehicle - + @@ -3721,7 +3724,7 @@ lego:Vehicle 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 51615 bytes on 1 raw line(s)] +… [107 more display lines; full output is 51609 bytes on 1 raw line(s)] ````` @@ -3733,17 +3736,17 @@ lego:Vehicle *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: 29.04s** +**exit code: 1** — **wall time: 19.97s** ````` - + - - + + - + @@ -3796,14 +3799,14 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s *Rank by git change-frequency prior instead of PageRank.* -**wall time: 10.61s** +**wall time: 7.70s** ````` - - + + @@ -3823,31 +3826,31 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s ````` -## `./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --exclude=changelog.d --in=src --limit=3` +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~9 --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: 17.01s** +**wall time: 11.26s** ````` - - + + - + + - - + - + ````` @@ -3871,7 +3874,7 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *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).* -**wall time: 3.89s** +**wall time: 2.33s** ````` @@ -3892,24 +3895,24 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *Flat top-K export for an external reranker.* -**wall time: 10.01s** +**wall time: 5.97s** ````` - + 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 ) @@ -3935,7 +3938,7 @@ ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidate *Machine-parseable JSON, same content, keys mirror the XML attrs.* -**wall time: 2.65s** +**wall time: 3.37s** ````` {"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2988,"graph_unresolved":15914,"graph_unindexed":275,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1184","sites_at":["src/mcpindex.h:1288"]}, @@ -3951,7 +3954,7 @@ ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidate *JSON refusal shape: an unsupported verb refuses loudly instead of silently falling back to XML.* -**exit code: 1** — **wall time: 1.02s** +**exit code: 1** ````` (empty) @@ -3967,12 +3970,12 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Pagination: 3 items, skipping the first 3 (deterministic seams).* -**wall time: 9.80s** +**wall time: 9.86s** ````` - + @@ -3983,7 +3986,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop test paths from the corpus before ranking.* -**wall time: 2.81s** +**wall time: 2.97s** ````` @@ -4023,7 +4026,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop matching paths (repeatable) before ranking.* -**wall time: 4.32s** +**wall time: 3.28s** ````` @@ -4063,29 +4066,29 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Full map re-ranked with teleport toward git-changed files — clean tree, so changed=0 and it degrades to the plain map.* -**wall time: 9.02s** +**wall time: 6.69s** ````` - + - - - - - - - + + + + - - - - - + + + + + - + + - + + + @@ -4095,7 +4098,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm cost.* -**wall time: 8.93s** +**wall time: 6.20s** ````` @@ -4135,7 +4138,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Explicit incremental cache at a path OUTSIDE the repo (first call writes it).* -**wall time: 9.93s** +**wall time: 5.67s** ````` @@ -4181,7 +4184,7 @@ Artifact written: *Skip files above a size bound before parsing (note the corpus shrink in the header).* -**wall time: 3.33s** +**wall time: 1.49s** ````` @@ -4237,7 +4240,7 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Multi-root workspace: ONE merged graph over two roots, paths labeled /.* -**wall time: 8.81s** +**wall time: 4.19s** ````` @@ -4271,17 +4274,17 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Self-eval: co-change recall vs BM25.* -**wall time: 31.96s** +**wall time: 24.33s** ````` ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 1.0% 1.2% 2.8% - BM25 14.8% 15.0% 16.0% - BM25sub 12.9% 14.4% 18.5% - BM25body 3.4% 15.6% 22.2% - fused 1.1% 4.2% 12.1% - anchored 3.4% 15.6% 22.2% + ripwire 1.0% 1.2% 2.7% + BM25 14.8% 14.9% 15.9% + BM25sub 12.8% 14.4% 18.4% + BM25body 3.4% 16.9% 23.4% + fused 1.0% 4.1% 11.9% + anchored 3.4% 16.9% 23.4% same-dir 0.0% 0.6% 6.3% random 0.1% 0.3% 0.5% <- floor (random ranking over F=3931 files) note: `ripwire` here is the DEFAULT MAP's structural-only PageRank (importance, not @@ -4299,7 +4302,7 @@ ripwire --eval (co-change recovery, averaged over 80 historical commits) *Known-item retrieval eval: MRR + recall@k per ranker per query mode.* -**wall time: 193.63s** +**wall time: 186.02s** ````` ripwire --eval-retrieval (known-item, 4002 doc-commented symbols; gold is in-corpus by construction) @@ -4328,7 +4331,7 @@ ripwire --eval-retrieval (known-item, 4002 doc-commented symbols; gold is in-co *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: 102.98s** +**exit code: 3** — **wall time: 67.70s** Input file: @@ -4352,8 +4355,6 @@ arm/orient-map-067-narrow merged *Labelled skill-ROUTING eval over the repo's own skills/ directory (4 hand-labelled prompts).* -**wall time: 7.17s** - Input file: ````` @@ -4401,8 +4402,6 @@ ripwire --eval-skills (skill routing over K=16 candidate skills [ripwire-router *Print the recipe to wire ripwire into Claude Code as an MCP server.* -**wall time: 1.52s** - ````` # ripwire -> Claude Code (CLI-first) # RECOMMENDED — this agent can run shell commands, so call the CLI directly. It costs @@ -4441,8 +4440,6 @@ Reach for it BEFORE blind grep + whole-file reads. First call ~1s cold; after th *Version + short build info.* -**wall time: 1.14s** - ````` ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=de9d903b2) ````` @@ -4456,7 +4453,7 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=de9d *Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost.* -**wall time: 2.64s** +**wall time: 1.21s** ````` @@ -4469,7 +4466,7 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=de9d *The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* -**wall time: 2.59s** +**wall time: 1.43s** ````` @@ -4488,7 +4485,7 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=de9d *A seed past the end of the file — the refusal shape for a faulted location.* -**exit code: 1** — **wall time: 1.83s** +**exit code: 1** — **wall time: 1.90s** ````` (empty) @@ -4504,7 +4501,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *VERIFY a closed claim in one call: three-valued verdict (confirmed / refuted / not-established) with the evidence rows inline.* -**wall time: 4.48s** +**wall time: 1.52s** ````` @@ -4518,7 +4515,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A claim that is FALSE — the refuted shape, with the references that refute it.* -**wall time: 4.60s** +**wall time: 1.46s** ````` @@ -4539,7 +4536,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A literal-scan absence: refuted only with complete= evidence, never on a partial scan.* -**wall time: 2.26s** +**wall time: 1.34s** ````` @@ -4551,7 +4548,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *An unparseable claim — the refusal names the accepted shapes.* -**exit code: 1** — **wall time: 5.58s** +**exit code: 1** — **wall time: 1.34s** ````` (empty) @@ -4567,11 +4564,11 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *Bare --slice=SYM: the INVENTORY of sliceable locals (), so a caller can pick VAR.* -**wall time: 5.32s** +**wall time: 2.04s** ````` - + @@ -4593,11 +4590,11 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *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: 5.70s** +**wall time: 2.11s** ````` - + @@ -4617,11 +4614,11 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *TRANSITIVE backward value-flow from the seed variable, bounded BFS (depth= disclosed; a cut frontier says flow_truncated=1).* -**wall time: 4.01s** +**wall time: 1.94s** ````` - + @@ -4650,11 +4647,11 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *Forward flow: which statements the seed's value reaches, at the default depth bound.* -**wall time: 6.31s** +**wall time: 2.10s** ````` - + @@ -4695,7 +4692,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *A variable the definition does not bind — the refusal shape, naming the inventory.* -**exit code: 1** — **wall time: 2.49s** +**exit code: 1** — **wall time: 1.07s** ````` (empty) @@ -4727,11 +4724,11 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *The compact legend posture: rows byte-identical, a versioned schema id replaces the repeated explanatory prose — for a many-small-calls loop.* -**wall time: 3.15s** +**wall time: 1.96s** ````` - + @@ -4751,7 +4748,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *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: 2.86s** +**wall time: 2.07s** ````` @@ -4764,7 +4761,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *The ellipsis form over a macro-shaped call site; unsupported= names the families this verb does not serve.* -**wall time: 2.96s** +**wall time: 1.96s** ````` @@ -4804,7 +4801,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *A pattern that collapses to a bare token is REFUSED — never reported as hits=0.* -**exit code: 1** — **wall time: 2.86s** +**exit code: 1** — **wall time: 2.26s** ````` (empty) @@ -4820,7 +4817,7 @@ ripwire: --pattern: this pattern collapses to a single bare token, which is a TE *Boolean grep: hits where BOTH literals share the matched line (--grep-scope=line is the default).* -**wall time: 2.77s** +**wall time: 1.59s** ````` @@ -5109,17 +5106,17 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? struct LintRule inline bool hasPytestTable( std::string_view source ) inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [139 more display lines; full output is 19737 bytes on 44 raw line(s)] +… [139 more display lines; full output is 19731 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: 8.79s** +**wall time: 3.22s** ````` - + @@ -5149,17 +5146,17 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? struct LintRule inline bool hasPytestTable( std::string_view source ) inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [146 more display lines; full output is 19788 bytes on 44 raw line(s)] +… [146 more display lines; full output is 19782 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: 14.89s** +**wall time: 3.11s** ````` - + @@ -5189,17 +5186,17 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? struct LintRule inline bool hasPytestTable( std::string_view source ) inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [137 more display lines; full output is 20512 bytes on 57 raw line(s)] +… [137 more display lines; full output is 20506 bytes on 57 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: 9.56s** +**wall time: 3.00s** ````` - + @@ -5229,17 +5226,17 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) inline std::string gitHeadSha( const std::string& root ) inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -… [193 more display lines; full output is 23001 bytes on 45 raw line(s)] +… [193 more display lines; full output is 22995 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: 6.48s** +**wall time: 2.76s** ````` - + @@ -5269,15 +5266,13 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) inline std::string gitHeadSha( const std::string& root ) inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -… [193 more display lines; full output is 22820 bytes on 45 raw line(s)] +… [193 more display lines; full output is 22814 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.* -**wall time: 2.14s** - ````` disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a CLEAN tree.* -**wall time: 18.24s** +**wall time: 5.28s** ````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [18 more display lines; full output is 2321 bytes on 1 raw line(s)] + + + + + + + + + + + + + ````` ## `./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: 21.53s** +**wall time: 5.88s** ````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [13 more display lines; full output is 2314 bytes on 1 raw line(s)] + + + + + + + + + + ````` ## `./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.* -**wall time: 3.32s** - ````` @@ -5431,8 +5389,6 @@ ripwire: --safe-delete symbol not found: DoesNotExist *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).* -**wall time: 2.35s** - ````` @@ -5471,7 +5427,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist *--no-stable outside --mcp: what the flag does (or says) when there is no stable-by-default ordering to opt out of.* -**wall time: 4.79s** +**wall time: 1.11s** ````` @@ -5517,13 +5473,13 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o *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** — **wall time: 4.45s** +**exit code: 4** ````` - + - + ````` @@ -5579,12 +5533,12 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o *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: 6.00s** +**exit code: 4** — **wall time: 2.90s** ````` - + ````` @@ -5619,11 +5573,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *THE single wide-angle quality read: six families in one pass, an eligible/ranked shortlist rather than a firehose.* -**wall time: 17.10s** +**wall time: 7.57s** ````` - @@ -5652,14 +5606,14 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir -… [189 more display lines; full output is 15697 bytes on 1 raw line(s)] +… [189 more display lines; full output is 15691 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: 2.47s** +**wall time: 1.52s** ````` @@ -5679,7 +5633,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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.* -**wall time: 2.58s** +**wall time: 1.35s** ````` @@ -5699,7 +5653,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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: 3.25s** +**wall time: 1.40s** ````` @@ -5739,7 +5693,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *Per function, the non-local MUTABLE state it can reach (transitively), most writes first — unsound by construction, and the legend says where.* -**wall time: 5.19s** +**wall time: 3.35s** ````` @@ -5779,11 +5733,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The family join: per function, which of four orthogonal evidence families fire, ranked by how many agree.* -**wall time: 11.02s** +**wall time: 6.58s** ````` - @@ -5812,14 +5766,14 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir -… [36 more display lines; full output is 7488 bytes on 1 raw line(s)] +… [36 more display lines; full output is 7482 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: 4.92s** +**wall time: 2.75s** ````` @@ -5859,7 +5813,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The same lens narrowed to ONE struct — the one --layout=Symbol shows the offsets for.* -**wall time: 2.86s** +**wall time: 2.28s** ````` @@ -5895,8 +5849,6 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The corpus's OWN case-convention vote per (language, kind) group; off-convention names get a mechanical propose= (a suggestion, never a blind rename).* -**wall time: 1.78s** - ````` @@ -5935,11 +5887,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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: 18.40s** +**wall time: 15.42s** ````` - + @@ -5965,14 +5917,14 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir