From 305a95571791b4079345be6db84e3ff63e6d05c1 Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Fri, 16 Oct 2026 15:19:24 +0300 Subject: [PATCH 01/28] WIP: caller/callee in markdown --- src/ingest_cache.h | 5 +- src/ingest_docs.h | 109 +++++++++++++++++++++++++++++++++++-- src/ingest_parsepool.h | 1 + src/quality.h | 4 +- test/mdsectioncheck.sh | 7 ++- test/mdsectionfix/guide.md | 4 +- 6 files changed, 122 insertions(+), 8 deletions(-) diff --git a/src/ingest_cache.h b/src/ingest_cache.h index ba885c6c7..1faaabc16 100644 --- a/src/ingest_cache.h +++ b/src/ingest_cache.h @@ -330,7 +330,10 @@ constexpr std::uint32_t kCacheVersion = 34; // 34: train 29 — lane a // (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 = 184; // bump on any grammar/.scm/extraction change +constexpr std::uint32_t kParserVer = 185; // bump on any grammar/.scm/extraction change + // 185 = 2026-10-16 (#409): a cross-file markdown [text](other.md#slug) names the + // target file's heading instead of dropping the fragment. 185, not 184: 184 is + // train 29. Rich file tag 186. kCacheVersion stays 34. // 184 = 2026-10-17 (train 29): strictly above every number a train-29 binary used — lane arity-min 178 // (rich 179), lane call-result-typing 180 (rich 181; carried to train 30), lane go-scope 182 (rich 183); // the other train-29 lanes kept 176. Rich file tag 185. kCacheVersion 34 (see its note). diff --git a/src/ingest_docs.h b/src/ingest_docs.h index 6af4b4bc9..870d96959 100644 --- a/src/ingest_docs.h +++ b/src/ingest_docs.h @@ -40,7 +40,10 @@ namespace // (5) links → edges: [[slug]] (the agent-memory/Obsidian convention) and [text](other.md) / // [label]: other.md → a ref named by the target's stem (the resolve ladder's same-dir preference // lands it on the file node); [text](#anchor) → a ref named by the matching heading in THIS file -// (GitHub slugify; same-file preference wins) — a doc-section→doc-section edge. Dangling links drop. +// (GitHub slugify; same-file preference wins) — a doc-section→doc-section edge. A cross-file +// [text](other.md#anchor) keeps the stem on the ref and parks the slug in qualifier until +// bindMarkdownCrossFileAnchors renames it to that file's heading, or leaves the stem when the +// heading is absent. Dangling links drop. // Determinism: the walk is a preorder over the AST (byte order); pending anchors resolve in byte order. // Names are XML-escaped downstream; CR bytes never enter a name, embedded newlines flatten to spaces. namespace mdtier @@ -284,8 +287,9 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi }; // link-target handling shared by inline links, reference definitions and (via slug) anchors. - // Cross-file: [x](other.md) / [x](../a/other.md#frag) → ref named by the target's STEM (the resolve - // ladder's same-dir preference lands it on other.md's file node, exactly like [[other]]). Anchors: + // Cross-file: [x](other.md) / [x](../a/other.md) → ref named by the target's STEM (the resolve + // ladder's same-dir preference lands it on other.md's file node, exactly like [[other]]). + // [x](other.md#slug) parks the slug in qualifier for bindMarkdownCrossFileAnchors. Anchors: // [x](#slug) → pending, resolved against THIS file's headings once they are all known. std::vector> pendingAnchors; // (refByte, slug) const auto emitLinkTarget = [ & ]( std::string_view target, std::uint32_t refByte ) @@ -309,9 +313,11 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi pendingAnchors.emplace_back( refByte, std::move( slug ) ); return; } + std::string_view anchorSlug; if( const std::size_t frag = target.find( '#' ); frag != std::string_view::npos ) { - target = target.substr( 0, frag ); // cross-file anchor part deferred (stem edge only, disclosed) + anchorSlug = target.substr( frag + 1 ); + target = target.substr( 0, frag ); } const bool isMd = target.size() > 3 && ( target.ends_with( ".md" ) || target.ends_with( ".markdown" ) ); if( !isMd ) @@ -326,6 +332,14 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi RawRef r; r.fileId = fileId; r.startByte = refByte; r.lang = Lang::Markdown; r.isInherit = false; r.name.assign( base ); + if( !anchorSlug.empty() ) + { + r.qualifier.reserve( anchorSlug.size() ); + for( const char c : anchorSlug ) + { + r.qualifier += ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : c; + } + } refs.push_back( std::move( r ) ); } }; @@ -503,6 +517,93 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi } } } + +// Cross-file [text](other.md#slug): emitLinkTarget left the stem in name and the slug in qualifier, +// because the target file's headings are not known while one file is extracted. After the parse pool +// merges every file, rename the ref to that file's heading (first slug match, the same rule as an +// in-file anchor). A missing file or heading leaves the stem edge and clears qualifier, so the +// resolver never sees a markdown qualifier. Same-directory stem wins; otherwise the lowest fileId. +inline void bindMarkdownCrossFileAnchors( const std::vector& files, const std::vector& defs, std::vector& refs ) +{ + const auto parentDir = []( std::string_view path ) noexcept + { + const std::size_t slash = path.find_last_of( '/' ); + return slash == std::string_view::npos ? std::string_view{} : path.substr( 0, slash ); + }; + + const auto appendId = []( std::string& out, std::uint32_t id ) + { + char digits[ 10 ]; + int digitCount = 0; + do { digits[ digitCount++ ] = char( '0' + id % 10u ); id /= 10u; } while( id != 0u ); + while( digitCount > 0 ) { out.push_back( digits[ --digitCount ] ); } + }; + + HashMap> stems; + HashMap headingBySlug; + std::string key; + for( const RawDef& d : defs ) + { + if( d.lang != Lang::Markdown || d.kind != SymKind::Section ) + { + continue; + } + if( d.startByte == 0 && d.bodyByte == 0 ) + { + stems[ d.name ].push_back( d.fileId ); + continue; + } + key.clear(); + appendId( key, d.fileId ); + key.push_back( '#' ); + key.append( mdtier::mdSlugOf( d.name ) ); + headingBySlug.try_emplace( key, d.name ); + } + + for( RawRef& r : refs ) + { + if( r.lang != Lang::Markdown || r.isDocLink || r.qualifier.empty() ) + { + continue; + } + const std::string slug = std::move( r.qualifier ); + r.qualifier.clear(); + const auto stemIt = stems.find( r.name ); + if( stemIt == stems.end() || r.fileId >= files.size() ) + { + continue; + } + const std::string_view refDir = parentDir( files[ r.fileId ] ); + std::uint32_t best = stemIt->second.front(); + bool inDir = false; + for( const std::uint32_t fid : stemIt->second ) + { + if( fid >= files.size() ) + { + continue; + } + const bool sameDir = parentDir( files[ fid ] ) == refDir; + if( sameDir && ( !inDir || fid < best ) ) + { + best = fid; + inDir = true; + } + else if( !inDir && !sameDir && fid < best ) + { + best = fid; + } + } + key.clear(); + appendId( key, best ); + key.push_back( '#' ); + key.append( slug ); + if( const auto hit = headingBySlug.find( key ); hit != headingBySlug.end() ) + { + r.name = hit->second; + } + } +} + } // namespace — ingest_docs.h section of ingest.cpp } // namespace rw diff --git a/src/ingest_parsepool.h b/src/ingest_parsepool.h index f583c78a0..3abf6846f 100644 --- a/src/ingest_parsepool.h +++ b/src/ingest_parsepool.h @@ -914,6 +914,7 @@ inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::st } raw = mergeThreadFacts( tFacts ); + bindMarkdownCrossFileAnchors( result.files, raw.defs, raw.refs ); const bool parseCut = applyMemoryParseCut( result, memWatch, nextFile.load( std::memory_order_relaxed ), nfiles ); // #350 diff --git a/src/quality.h b/src/quality.h index ff3ffd5ba..dfef0ff53 100644 --- a/src/quality.h +++ b/src/quality.h @@ -2336,7 +2336,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 = 34; // MUST equal ingest.cpp's kCacheVersion (gated); 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), 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 = 184; // MUST equal ingest.cpp's kParserVer (gated) +constexpr std::uint32_t kIngestParserVerMirror = 185; // MUST equal ingest.cpp's kParserVer (gated) + // 185 = 2026-10-16 (#409): cross-file markdown #slug binds to that file's + // heading. 185, not 184: 184 is train 29. Rich file tag 186. // 184 = 2026-10-17 (train 29: above arity-min 178/179, call-result-typing // 180/181, go-scope 182/183; see the kParserVer note) // 182 = 2026-10-16 (train 29 L5 go-scope reservation, #403: Go method / diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index c662d070e..a15256a11 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -11,7 +11,8 @@ # canonical id is path::Parent::Child — identity stays PATH-QUALIFIED (the churn-keying # lesson: same heading text across files must never collide); # - markdown links are edges: [text](other.md) / [label]: other.md → doc→doc (file node, the -# wikilink machinery), [text](#anchor) → doc-section→doc-section IN-FILE; `backtick` +# wikilink machinery), [text](#anchor) → doc-section→doc-section IN-FILE, +# [text](other.md#slug) → that file's heading (stem if the heading is absent); `backtick` # mentions stay doc→code and now attribute to their ENCLOSING SECTION; # - fenced/tilde/indented code, html blocks, YAML front-matter and blockquoted headings are # NOT document structure: nothing inside them becomes a symbol, and fenced code is never @@ -76,6 +77,8 @@ guard "$G" '# zqhtmlphantom' 'the html-block decoy heading' guard "$G" '# zqindentphantom' 'the indented-code decoy heading' guard "$G" '> # Quoted Phantom' 'the blockquoted decoy heading' guard "$G" '(partner.md)' 'the inline link to partner.md' +guard "$G" '(partner.md#install-steps)' 'the cross-file heading link' +guard "$G" '(alt.markdown#no-such-heading)' 'the cross-file link whose heading is absent' guard "$G" '(#result-tables)' 'the in-file anchor link' guard "$G" '[[decoy]]' 'the wikilink to decoy' guard "$G" '[zqref]: decoy.md' 'the reference-style link definition' @@ -252,6 +255,8 @@ for m in re.finditer(r']*>(.*?)', xml, re.S): PYEOF edge(){ if grep -qF "$1 -> $2" "$TMP/edges.txt"; then ok "edge: $1 -> $2 ($3)"; else no "edge MISSING: $1 -> $2 ($3)"; fi; } edge "Cache Warm Path" "partner" 'inline [text](partner.md) link, attributed to its section' +edge "Cache Warm Path" "Install Steps" 'cross-file [text](partner.md#install-steps) — that file'\''s heading' +edge "Cache Warm Path" "alt" 'cross-file [text](alt.markdown#no-such-heading) falls back to the file stem' edge "Cache Warm Path" "Result Tables" 'in-file [text](#result-tables) anchor — doc-section→doc-section' edge "Result Tables" "decoy" '[[wikilink]], attributed to its section' edge "Deep Appendix" "decoy" 'reference-style [zqref]: decoy.md definition' diff --git a/test/mdsectionfix/guide.md b/test/mdsectionfix/guide.md index 80a59b31b..0407514ad 100644 --- a/test/mdsectionfix/guide.md +++ b/test/mdsectionfix/guide.md @@ -13,7 +13,9 @@ The orientation body carries zqorientbody as its unique marker token. The warm path body carries zqcachewarmbody and mentions `codeIdentFn` in a backtick. It links to [the partner doc](partner.md) and jumps to -[result tables](#result-tables) further down. An archived copy +[result tables](#result-tables) further down. The install section is +[over there](partner.md#install-steps). A missing anchor stays on the file: +[nowhere](alt.markdown#no-such-heading). An archived copy [lives here][zqref]. ```cpp From 9ee11bffdae341cba8c003d9651b7016f25bec9a Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sat, 17 Oct 2026 23:17:35 +0300 Subject: [PATCH 02/28] make CodeRabbit's happy by fixing cache --- src/ingest_parsepool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ingest_parsepool.h b/src/ingest_parsepool.h index 3abf6846f..04e47d121 100644 --- a/src/ingest_parsepool.h +++ b/src/ingest_parsepool.h @@ -914,7 +914,6 @@ inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::st } raw = mergeThreadFacts( tFacts ); - bindMarkdownCrossFileAnchors( result.files, raw.defs, raw.refs ); const bool parseCut = applyMemoryParseCut( result, memWatch, nextFile.load( std::memory_order_relaxed ), nfiles ); // #350 @@ -956,6 +955,7 @@ inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::st saveCache( std::string( cacheFile ), rootDir, result.files, scan.hash, scan.statSize, scan.statMtime, scan.statCtime, scan.health, raw.defs, raw.refs, raw.incs, raw.binds, raw.ffis, raw.routeDefs, raw.routeUses, raw.constOpens, captureValueUses ); } } + bindMarkdownCrossFileAnchors( result.files, raw.defs, raw.refs ); return raw; } From deff7dfe09294f3119d1a8dfb83c393b55d31b06 Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sun, 18 Oct 2026 11:41:58 +0300 Subject: [PATCH 03/28] mdslug [text](path#slug), update CHANGELOG.md --- CHANGELOG.md | 9 ++ src/graph.h | 115 ++++++++++++++++++++++++++ src/infra/mdslug.h | 31 +++++++ src/ingest.cpp | 1 + src/ingest_cache.h | 6 +- src/ingest_docs.h | 127 ++++------------------------- src/ingest_parsepool.h | 1 - src/quality.h | 5 +- test/mdanchorfix/README.md | 5 ++ test/mdanchorfix/docs/README.md | 5 ++ test/mdanchorfix/docs/a/guide.md | 35 ++++++++ test/mdanchorfix/docs/a/notes.md | 5 ++ test/mdanchorfix/docs/a/tools.py | 2 + test/mdanchorfix/docs/b/dup.md | 9 ++ test/mdanchorfix/docs/b/notes.md | 5 ++ test/mdanchorfix/docs/b/partner.md | 9 ++ test/mdsectioncheck.sh | 40 +++++++++ 17 files changed, 292 insertions(+), 118 deletions(-) create mode 100644 src/infra/mdslug.h create mode 100644 test/mdanchorfix/README.md create mode 100644 test/mdanchorfix/docs/README.md create mode 100644 test/mdanchorfix/docs/a/guide.md create mode 100644 test/mdanchorfix/docs/a/notes.md create mode 100644 test/mdanchorfix/docs/a/tools.py create mode 100644 test/mdanchorfix/docs/b/dup.md create mode 100644 test/mdanchorfix/docs/b/notes.md create mode 100644 test/mdanchorfix/docs/b/partner.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 965c7ee73..64e9e8d5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,15 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Added — markdown links bind the exact heading (#409 item 1, #414) + +A cross-file `[text](path/other.md#slug)` now edges to that file's heading — `--callees` / `--callers` on a doc section +list the sections it links to instead of one file-level edge. The extractor caches the link as written; `buildGraph` joins +the path against the linking file, so `../b/notes.md` never lands on a same-named file in the linker's own directory and a +link to a heading named like one of the linking file's own headings does not bind to the latter. Two headings of the same +slug give one edge (the first). A missing heading falls back to the target file's edge; percent-encoded slugs and GitHub's +`-1` duplicate suffix are not modelled and also fall back. Gate: `test/mdsectioncheck.sh`. + ### Changed — Go methods carry their receiver type as scope (#403, slice 9a) Go methods now carry their receiver type as scope (`sc=`), with `*` and type arguments stripped (`*Stack[T]` → `Stack`), diff --git a/src/graph.h b/src/graph.h index 4c49f1424..6fc83bd12 100644 --- a/src/graph.h +++ b/src/graph.h @@ -26,6 +26,7 @@ #include "externalnames.h" // Phase 5: the committed builtin/stdlib tables behind the external-name veto #include "infra/sortutil.h" // radix edge sorting for large integer-key graph edge lists #include "clones.h" // scanCodeTokens / CodeScanOptions — BuiltinMethodGate::namedBeyondDefinition's code-token count +#include "infra/mdslug.h" // mdSlugOf — bindMarkdownLinks matches a link slug to a heading #include "infra/namesplit.h" // isIdentChar — identifierRunCount, the identifier runs inside a string annotation #include "docparse.h" // detail::readWholeFile — resolveAtSeed reads the seed line's byte range off disk #include "infra/profileScope.h" // PROFILE_SCOPE self-profiling — gated by PROFILE_ENABLED (off unless -DRIPWIRE_PROFILE=ON) @@ -6281,6 +6282,106 @@ inline void fillImplementorBound( Graph& g, const std::vector fileByPath; // every .md/.markdown file, by root-relative path + std::vector headings; // sorted (fileId, slug, startByte, id): the first of a slug is its heading + std::vector wholeFile; // per file: the file-level Section node, kNoNode when none +}; + +inline bool isMarkdownLinkRef( const Reference& r ) noexcept +{ + return r.lang == Lang::Markdown && !r.isDocLink && !r.memberCall && !r.memberPath.empty(); +} + +inline MdLinkIndex buildMdLinkIndex( const IngestResult& ing ) +{ + MdLinkIndex ix; + for( std::uint32_t f = 0; f < ing.files.size(); ++f ) + { + if( ing.files[ f ].ends_with( ".md" ) || ing.files[ f ].ends_with( ".markdown" ) ) + { + ix.fileByPath.try_emplace( ing.files[ f ], f ); + } + } + ix.wholeFile.assign( ing.files.size(), kNoNode ); + for( const Symbol& sym : ing.symbols ) + { + if( sym.lang != Lang::Markdown || sym.kind != SymKind::Section || sym.fileId >= ing.files.size() ) + { + continue; + } + if( sym.sigStartByte == 0 && sym.sigEndByte == 0 ) + { + ix.wholeFile[ sym.fileId ] = sym.id; + } + else + { + ix.headings.push_back( { sym.fileId, sym.sigStartByte, sym.id, mdSlugOf( sym.name ) } ); + } + } + std::sort( ix.headings.begin(), ix.headings.end(), []( const MdLinkIndex::Heading& a, const MdLinkIndex::Heading& b ) + { + return std::tie( a.fileId, a.slug, a.startByte, a.id ) < std::tie( b.fileId, b.slug, b.startByte, b.id ); + } ); + return ix; +} + +// The Section one link names, or kNoNode when its path is not in the tree. +inline NodeId bindOneMarkdownLink( const MdLinkIndex& ix, std::string_view linkerPath, std::string_view written ) +{ + const std::size_t frag = written.find( '#' ); + const std::string_view link = written.substr( 0, frag ); + const std::string_view slug = frag == std::string_view::npos ? std::string_view{} : written.substr( frag + 1 ); + const std::string_view dir = includerDir( linkerPath ); + std::string path( dir ); + if( !dir.empty() ) + { + path.push_back( '/' ); + } + path.append( link ); + path = lexicalNormalize( path ); // empty on an escape above the root → stem ladder + const auto file = path.empty() ? ix.fileByPath.end() : ix.fileByPath.find( path ); + if( file == ix.fileByPath.end() ) + { + return kNoNode; + } + const std::uint32_t target = file->second; + using Key = std::pair; + const auto hit = slug.empty() ? ix.headings.end() + : std::lower_bound( ix.headings.begin(), ix.headings.end(), Key{ target, slug }, + []( const MdLinkIndex::Heading& h, const Key& key ) { return Key{ h.fileId, h.slug } < key; } ); + return ( hit != ix.headings.end() && hit->fileId == target && hit->slug == slug ) ? hit->id : ix.wholeFile[ target ]; +} + +inline std::vector bindMarkdownLinks( const IngestResult& ing ) +{ + std::vector pin; + if( std::none_of( ing.references.begin(), ing.references.end(), isMarkdownLinkRef ) ) + { + return pin; + } + pin.assign( ing.references.size(), kNoNode ); + const MdLinkIndex ix = buildMdLinkIndex( ing ); + for( std::size_t i = 0; i < ing.references.size(); ++i ) + { + const Reference& r = ing.references[ i ]; + if( isMarkdownLinkRef( r ) && r.fileId < ing.files.size() ) + { + pin[ i ] = bindOneMarkdownLink( ix, ing.files[ r.fileId ], r.memberPath ); + } + } + return pin; +} + // B2.2 MINIMUM ARITY ACROSS DECLARATIONS: per symbol, the fewest arguments a call can pass to it — its own paramsMin // (ingest_metrics.h cc_paramShape) lowered to the lowest paramsMin of any DECLARATION with the same name and the same // parameter count. C++ may write a default on a separate prototype only (`int f( int x, int y = 5 );` in a header, @@ -7434,6 +7535,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null }; { PROFILE_SCOPE_DESCRIBE( "buildGraph/3: resolve loop (per reference)" ); + const std::vector mdLinkPin = bindMarkdownLinks( ing ); // empty unless the tree holds a cross-file markdown link for( siteIndex = 0; siteIndex < ing.references.size(); ++siteIndex ) { const Reference& site = ing.references[ siteIndex ]; @@ -7517,6 +7619,19 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null } } + // A cross-file markdown link whose file is in the tree: pinned to the exact heading (or file) bindMarkdownLinks + // found, like a SCIP site — the name ladder (same-dir stem, same-file heading) must not re-choose it. + if( !mdLinkPin.empty() && mdLinkPin[ siteIndex ] != kNoNode ) + { + const NodeId to = mdLinkPin[ siteIndex ]; + tier.clear(); + if( to != r.fromSymbol ) + { + tier.push_back( to ); + } + scipPinned = true; + } + // Java issue #74: the grammar labels both `Widget::makeFn` and `widget::makeFn` // with an identifier receiver. A method-reference capture is therefore admitted only when // repository evidence proves the receiver is a type at this site. Every other receiver is diff --git a/src/infra/mdslug.h b/src/infra/mdslug.h new file mode 100644 index 000000000..de0c8da97 --- /dev/null +++ b/src/infra/mdslug.h @@ -0,0 +1,31 @@ +#pragma once + +// mdslug.h — the ONE GitHub-style anchor slug of a markdown heading name. +// +// Two stages need it and neither can include the other: ingest_docs.h resolves an in-file [text](#slug) at +// extraction, and graph.h resolves a cross-file [text](other.md#slug) once every heading in the repo is a +// symbol. A leaf header keeps them on one definition (a second copy is the clone --quality-delta flags). + +#include +#include + +namespace rw +{ + +// alnum lowered, spaces→'-', '-'/'_' kept, everything else dropped. First match wins on collision +// (GitHub's -1/-2 suffixes and percent-encoded slugs are not modelled; such a link falls back to the +// target file's own edge — disclosed here rather than guessed). +inline std::string mdSlugOf( std::string_view name ) +{ + std::string slug; + slug.reserve( name.size() ); + for( const char c : name ) + { + if( ( c >= 'a' && c <= 'z' ) || ( c >= '0' && c <= '9' ) || c == '_' || c == '-' ) { slug += c; } + else if( c >= 'A' && c <= 'Z' ) { slug += char( c - 'A' + 'a' ); } + else if( c == ' ' || c == '\t' ) { slug += '-'; } + } + return slug; +} + +} // namespace rw diff --git a/src/ingest.cpp b/src/ingest.cpp index bb300fe5f..cddb26107 100644 --- a/src/ingest.cpp +++ b/src/ingest.cpp @@ -192,6 +192,7 @@ extern "C" #include "handlershape.h" // --quality-delta's handler/placeholder shapes — AstWalk::HandlerShapes rides the shared file walk (reads nodeTextOf above) #include "ingest_relations.h" #include "ingest_jsimports.h" +#include "infra/mdslug.h" // mdSlugOf — shared with graph.h (the cross-file link bind) #include "ingest_docs.h" #include "ingest_names.h" #include "ingest_binds.h" diff --git a/src/ingest_cache.h b/src/ingest_cache.h index 1faaabc16..f4a6387bb 100644 --- a/src/ingest_cache.h +++ b/src/ingest_cache.h @@ -331,9 +331,9 @@ constexpr std::uint32_t kCacheVersion = 34; // 34: train 29 — lane a // a target FORMAT change → old caches must be rejected. // 4: Include gained a `bool isAngle` (quote/angle) field constexpr std::uint32_t kParserVer = 185; // bump on any grammar/.scm/extraction change - // 185 = 2026-10-16 (#409): a cross-file markdown [text](other.md#slug) names the - // target file's heading instead of dropping the fragment. 185, not 184: 184 is - // train 29. Rich file tag 186. kCacheVersion stays 34. + // 185 = 2026-10-18 (#409): a cross-file markdown [text](other.md#slug) keeps the link as + // written (RawRef::memberPath, unbound) so graph.h pins the exact file + heading. + // 185, not 184: 184 is train 29. Rich file tag 186. kCacheVersion stays 34. // 184 = 2026-10-17 (train 29): strictly above every number a train-29 binary used — lane arity-min 178 // (rich 179), lane call-result-typing 180 (rich 181; carried to train 30), lane go-scope 182 (rich 183); // the other train-29 lanes kept 176. Rich file tag 185. kCacheVersion 34 (see its note). diff --git a/src/ingest_docs.h b/src/ingest_docs.h index 870d96959..b0eea75d7 100644 --- a/src/ingest_docs.h +++ b/src/ingest_docs.h @@ -41,9 +41,8 @@ namespace // [label]: other.md → a ref named by the target's stem (the resolve ladder's same-dir preference // lands it on the file node); [text](#anchor) → a ref named by the matching heading in THIS file // (GitHub slugify; same-file preference wins) — a doc-section→doc-section edge. A cross-file -// [text](other.md#anchor) keeps the stem on the ref and parks the slug in qualifier until -// bindMarkdownCrossFileAnchors renames it to that file's heading, or leaves the stem when the -// heading is absent. Dangling links drop. +// [text](other.md#anchor) keeps the stem on the ref and the link as written in memberPath; graph.h +// pins it to that exact file's heading (the file node when the heading is absent). Dangling links drop. // Determinism: the walk is a preorder over the AST (byte order); pending anchors resolve in byte order. // Names are XML-escaped downstream; CR bytes never enter a name, embedded newlines flatten to spaces. namespace mdtier @@ -82,21 +81,8 @@ inline std::string mdCleanHeadingText( std::string_view raw ) return name; } -// GitHub anchor slug of a heading name: alnum lowered, spaces→'-', '-'/'_' kept, everything else -// dropped. First match wins on collision (GitHub's -1/-2 suffixes are not modelled; a collided anchor -// resolves to the first heading — disclosed here rather than guessed). -inline std::string mdSlugOf( std::string_view name ) -{ - std::string slug; - slug.reserve( name.size() ); - for( const char c : name ) - { - if( ( c >= 'a' && c <= 'z' ) || ( c >= '0' && c <= '9' ) || c == '_' || c == '-' ) { slug += c; } - else if( c >= 'A' && c <= 'Z' ) { slug += char( c - 'A' + 'a' ); } - else if( c == ' ' || c == '\t' ) { slug += '-'; } - } - return slug; -} +// GitHub anchor slug of a heading name: infra/mdslug.h (shared with graph.h's cross-file link bind). +using rw::mdSlugOf; inline void mdWalk( TSNode node, std::string_view src, bool inQuote, std::uint32_t depth, MdWalkOut& out ) { @@ -289,7 +275,7 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi // link-target handling shared by inline links, reference definitions and (via slug) anchors. // Cross-file: [x](other.md) / [x](../a/other.md) → ref named by the target's STEM (the resolve // ladder's same-dir preference lands it on other.md's file node, exactly like [[other]]). - // [x](other.md#slug) parks the slug in qualifier for bindMarkdownCrossFileAnchors. Anchors: + // The ref also carries the link as written (memberPath) so buildGraph can pin the EXACT file and heading. Anchors: // [x](#slug) → pending, resolved against THIS file's headings once they are all known. std::vector> pendingAnchors; // (refByte, slug) const auto emitLinkTarget = [ & ]( std::string_view target, std::uint32_t refByte ) @@ -313,11 +299,10 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi pendingAnchors.emplace_back( refByte, std::move( slug ) ); return; } - std::string_view anchorSlug; + const std::string_view writtenTarget = target; // path#slug as written, for the exact bind in buildGraph if( const std::size_t frag = target.find( '#' ); frag != std::string_view::npos ) { - anchorSlug = target.substr( frag + 1 ); - target = target.substr( 0, frag ); + target = target.substr( 0, frag ); } const bool isMd = target.size() > 3 && ( target.ends_with( ".md" ) || target.ends_with( ".markdown" ) ); if( !isMd ) @@ -332,12 +317,16 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi RawRef r; r.fileId = fileId; r.startByte = refByte; r.lang = Lang::Markdown; r.isInherit = false; r.name.assign( base ); - if( !anchorSlug.empty() ) + // The link as written (path + optional #slug; the slug is lowered, the path is not). It is a fact of THIS file alone, so it is + // safe in the cache; the (file, slug) heading it names is resolved in buildGraph, where every file's + // headings exist (graph.h bindMarkdownLinks). memberPath is read beside memberCall / compose only. + r.memberPath.assign( writtenTarget ); + if( const std::size_t frag = r.memberPath.find( '#' ); frag != std::string::npos ) { - r.qualifier.reserve( anchorSlug.size() ); - for( const char c : anchorSlug ) + for( std::size_t k = frag + 1; k < r.memberPath.size(); ++k ) { - r.qualifier += ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : c; + char& c = r.memberPath[ k ]; + c = ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : c; } } refs.push_back( std::move( r ) ); @@ -518,92 +507,6 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi } } -// Cross-file [text](other.md#slug): emitLinkTarget left the stem in name and the slug in qualifier, -// because the target file's headings are not known while one file is extracted. After the parse pool -// merges every file, rename the ref to that file's heading (first slug match, the same rule as an -// in-file anchor). A missing file or heading leaves the stem edge and clears qualifier, so the -// resolver never sees a markdown qualifier. Same-directory stem wins; otherwise the lowest fileId. -inline void bindMarkdownCrossFileAnchors( const std::vector& files, const std::vector& defs, std::vector& refs ) -{ - const auto parentDir = []( std::string_view path ) noexcept - { - const std::size_t slash = path.find_last_of( '/' ); - return slash == std::string_view::npos ? std::string_view{} : path.substr( 0, slash ); - }; - - const auto appendId = []( std::string& out, std::uint32_t id ) - { - char digits[ 10 ]; - int digitCount = 0; - do { digits[ digitCount++ ] = char( '0' + id % 10u ); id /= 10u; } while( id != 0u ); - while( digitCount > 0 ) { out.push_back( digits[ --digitCount ] ); } - }; - - HashMap> stems; - HashMap headingBySlug; - std::string key; - for( const RawDef& d : defs ) - { - if( d.lang != Lang::Markdown || d.kind != SymKind::Section ) - { - continue; - } - if( d.startByte == 0 && d.bodyByte == 0 ) - { - stems[ d.name ].push_back( d.fileId ); - continue; - } - key.clear(); - appendId( key, d.fileId ); - key.push_back( '#' ); - key.append( mdtier::mdSlugOf( d.name ) ); - headingBySlug.try_emplace( key, d.name ); - } - - for( RawRef& r : refs ) - { - if( r.lang != Lang::Markdown || r.isDocLink || r.qualifier.empty() ) - { - continue; - } - const std::string slug = std::move( r.qualifier ); - r.qualifier.clear(); - const auto stemIt = stems.find( r.name ); - if( stemIt == stems.end() || r.fileId >= files.size() ) - { - continue; - } - const std::string_view refDir = parentDir( files[ r.fileId ] ); - std::uint32_t best = stemIt->second.front(); - bool inDir = false; - for( const std::uint32_t fid : stemIt->second ) - { - if( fid >= files.size() ) - { - continue; - } - const bool sameDir = parentDir( files[ fid ] ) == refDir; - if( sameDir && ( !inDir || fid < best ) ) - { - best = fid; - inDir = true; - } - else if( !inDir && !sameDir && fid < best ) - { - best = fid; - } - } - key.clear(); - appendId( key, best ); - key.push_back( '#' ); - key.append( slug ); - if( const auto hit = headingBySlug.find( key ); hit != headingBySlug.end() ) - { - r.name = hit->second; - } - } -} - } // namespace — ingest_docs.h section of ingest.cpp } // namespace rw diff --git a/src/ingest_parsepool.h b/src/ingest_parsepool.h index 04e47d121..f583c78a0 100644 --- a/src/ingest_parsepool.h +++ b/src/ingest_parsepool.h @@ -955,7 +955,6 @@ inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::st saveCache( std::string( cacheFile ), rootDir, result.files, scan.hash, scan.statSize, scan.statMtime, scan.statCtime, scan.health, raw.defs, raw.refs, raw.incs, raw.binds, raw.ffis, raw.routeDefs, raw.routeUses, raw.constOpens, captureValueUses ); } } - bindMarkdownCrossFileAnchors( result.files, raw.defs, raw.refs ); return raw; } diff --git a/src/quality.h b/src/quality.h index dfef0ff53..5565adeb0 100644 --- a/src/quality.h +++ b/src/quality.h @@ -2337,8 +2337,9 @@ inline std::string cacheRootKeyHex( const std::string& root ) // that broke that fails the build on the undeclared name rather than passing. constexpr std::uint32_t kIngestCacheVersionMirror = 34; // MUST equal ingest.cpp's kCacheVersion (gated); 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), 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 = 185; // MUST equal ingest.cpp's kParserVer (gated) - // 185 = 2026-10-16 (#409): cross-file markdown #slug binds to that file's - // heading. 185, not 184: 184 is train 29. Rich file tag 186. + // 185 = 2026-10-18 (#409): cross-file markdown links carry their written path#slug + // (memberPath); graph.h pins the exact heading. 185, not 184: 184 is train 29. + // Rich file tag 186. kCacheVersion stays 34. // 184 = 2026-10-17 (train 29: above arity-min 178/179, call-result-typing // 180/181, go-scope 182/183; see the kParserVer note) // 182 = 2026-10-16 (train 29 L5 go-scope reservation, #403: Go method / diff --git a/test/mdanchorfix/README.md b/test/mdanchorfix/README.md new file mode 100644 index 000000000..80c52b68a --- /dev/null +++ b/test/mdanchorfix/README.md @@ -0,0 +1,5 @@ +# Root Readme + +## Languages + +The root languages section. diff --git a/test/mdanchorfix/docs/README.md b/test/mdanchorfix/docs/README.md new file mode 100644 index 000000000..cecfaeee8 --- /dev/null +++ b/test/mdanchorfix/docs/README.md @@ -0,0 +1,5 @@ +# Docs Readme + +## Languages + +The docs-directory readme, not the root one. diff --git a/test/mdanchorfix/docs/a/guide.md b/test/mdanchorfix/docs/a/guide.md new file mode 100644 index 000000000..33d4fe0f3 --- /dev/null +++ b/test/mdanchorfix/docs/a/guide.md @@ -0,0 +1,35 @@ +# Guide + +## Install + +The guide's own install section. + +## Self Collision + +See [partner install](../b/partner.md#install). + +## Path Choice + +See [setup](../b/notes.md#setup). + +## Root Readme Link + +See [languages](../../README.md#languages). + +## Dup Link + +See [notes](../b/dup.md#notes). + +## Func Name + +See [work](../b/partner.md#do_work). + +## Missing Anchor + +See [nothing](../b/partner.md#no-such-heading). + +## Fence Only + +```python +[fenced](../b/partner.md#install) +``` diff --git a/test/mdanchorfix/docs/a/notes.md b/test/mdanchorfix/docs/a/notes.md new file mode 100644 index 000000000..ca62ba09e --- /dev/null +++ b/test/mdanchorfix/docs/a/notes.md @@ -0,0 +1,5 @@ +# Notes Alpha + +## Setup Alpha + +The notes beside the guide. diff --git a/test/mdanchorfix/docs/a/tools.py b/test/mdanchorfix/docs/a/tools.py new file mode 100644 index 000000000..8e33ea784 --- /dev/null +++ b/test/mdanchorfix/docs/a/tools.py @@ -0,0 +1,2 @@ +def do_work(): + return 1 diff --git a/test/mdanchorfix/docs/b/dup.md b/test/mdanchorfix/docs/b/dup.md new file mode 100644 index 000000000..5183d9cd9 --- /dev/null +++ b/test/mdanchorfix/docs/b/dup.md @@ -0,0 +1,9 @@ +# Dup + +## Notes + +First. + +## Notes + +Second. diff --git a/test/mdanchorfix/docs/b/notes.md b/test/mdanchorfix/docs/b/notes.md new file mode 100644 index 000000000..6e790643b --- /dev/null +++ b/test/mdanchorfix/docs/b/notes.md @@ -0,0 +1,5 @@ +# Notes Beta + +## Setup + +The notes in the other directory. diff --git a/test/mdanchorfix/docs/b/partner.md b/test/mdanchorfix/docs/b/partner.md new file mode 100644 index 000000000..5e7a3d4c0 --- /dev/null +++ b/test/mdanchorfix/docs/b/partner.md @@ -0,0 +1,9 @@ +# Partner + +## Install + +The partner install section. + +## do_work + +The partner heading named like a python function. diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index a15256a11..426653c24 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -356,6 +356,46 @@ $BIN "$FIX" --no-cache --mentions=codeIdentFn >"$TMP/men.xml" 2>/dev/null grep -qF 'guide.md' "$TMP/men.xml" && ok "--mentions=codeIdentFn names guide.md (backtick doc→code edge)" \ || no "--mentions=codeIdentFn lost the guide.md mention" +# ═══════════════════════════════════════════════════════════════════════════ +echo +echo "=== cross-file [text](path#slug) binds the EXACT (file, slug) heading, never a bare name ===" +# ═══════════════════════════════════════════════════════════════════════════ +# Each arm reads --callees of ONE linking section and compares the rows' n=@p= (file, no line). The +# fixture is built so a name-only resolver lands on the wrong target: the linking file has its own +# `## Install`; docs/a/notes.md and docs/b/notes.md both exist; docs/README.md shadows the root README. +AFIX="$ROOT/test/mdanchorfix" +[ -d "$AFIX" ] || { echo "no fixture at $AFIX"; exit 2; } +callees(){ # callees DIR SECTION → "n@file" per callee row, sorted, one per line + $BIN "$1" --no-cache --callees="$2" --legend=compact 2>/dev/null \ + | python3 -c ' +import sys, re +for m in re.finditer(r"/dev/null | grep -c 'p="docs/a/tools.py' )" +[ "$n" = "0" ] && ok "the .py do_work has no caller edge from the doc link" || no "the doc link leaked onto the .py function (rows naming tools.py: $n)" + +echo "--- warm == cold after editing ONLY the target file (the cached ref must stay unbound) ---" +cp -R "$AFIX" "$TMP/anchorcopy" +rm -f "$TMP/anchor.cache" +$BIN "$TMP/anchorcopy" --cache="$TMP/anchor.cache" --callees="Self Collision" --legend=compact >/dev/null 2>&1 +sed -i 's/^## Install$/## Install Steps Renamed/' "$TMP/anchorcopy/docs/b/partner.md" +$BIN "$TMP/anchorcopy" --cache="$TMP/anchor.cache" --callees="Self Collision" --legend=compact >"$TMP/ac_warm" 2>/dev/null +$BIN "$TMP/anchorcopy" --no-cache --callees="Self Collision" --legend=compact >"$TMP/ac_cold" 2>/dev/null +if [ -s "$TMP/ac_cold" ] && diff -q "$TMP/ac_cold" "$TMP/ac_warm" >/dev/null; then ok "warm run equals cold run after only the target heading changed"; else no "warm run differs from cold after a target-only edit (a stale bound ref)"; fi +grep -q 'n="partner"' "$TMP/ac_warm" && ok "…and the dead anchor degraded to the partner file edge" || no "the renamed-heading link lost its edge on the warm run" + # ═══════════════════════════════════════════════════════════════════════════ echo echo "=== determinism ×3 and cache transparency on the md fixture ===" From 194a84e798baae95211d5a9cdb2cc6472a3f4bd2 Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sun, 18 Oct 2026 12:04:56 +0300 Subject: [PATCH 04/28] update unit test in test/mdsectioncheck --- test/mdsectioncheck.sh | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index 426653c24..83c4acaca 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -383,8 +383,24 @@ want "$AFIX" "Dup Link" 'Notes@docs/b/dup.md' "#notes over t want "$AFIX" "Func Name" 'do_work@docs/b/partner.md' "a heading named like a .py function binds the section, never the function" want "$AFIX" "Missing Anchor" 'partner@docs/b/partner.md' "#no-such-heading falls back to the file (stem) edge" want "$AFIX" "Fence Only" '' "a link inside a fenced block makes no edge" -n="$( $BIN "$AFIX" --no-cache --callers=do_work --legend=compact 2>/dev/null | grep -c 'p="docs/a/tools.py' )" -[ "$n" = "0" ] && ok "the .py do_work has no caller edge from the doc link" || no "the doc link leaked onto the .py function (rows naming tools.py: $n)" +# --callers lists every matched def in (cross_kind when a heading and a function share the name). +# The edge is the row: it must name the section, never the .py function. +if $BIN "$AFIX" --no-cache --callers=do_work --legend=compact 2>/dev/null | python3 -c ' +import sys, re +xml = sys.stdin.read() +kind = { m.group(1): m.group(2) for m in re.finditer(r"]*)/?>", xml)) +ok = bool(rows) and "fn" in kind.values() and "sec" in kind.values() +for m in rows: + attrs = m.group(1) + p = re.search(r" p=\"([^\"]*)\"", attrs) + of = re.search(r"of_def=\"(\d+)", attrs) + if p and "tools.py" in p.group(1): + ok = False + if of and kind.get(of.group(1)) == "fn": + ok = False +sys.exit(0 if ok else 1) +'; then ok "the .py do_work is a matched def only — the caller edge belongs to the section"; else no "the doc link leaked onto the .py function"; fi echo "--- warm == cold after editing ONLY the target file (the cached ref must stay unbound) ---" cp -R "$AFIX" "$TMP/anchorcopy" From 9464c61d6264838ee12514b458dca7d0a041540c Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sun, 18 Oct 2026 14:33:30 +0300 Subject: [PATCH 05/28] handle missing heading in markdown link. --- src/graph.h | 52 ++++++++++++++++++++++++------- test/mdanchorfix/docs/a/README.md | 3 ++ test/mdanchorfix/docs/a/guide.md | 8 +++++ test/mdanchorfix/docs/b/README.md | 3 ++ test/mdsectioncheck.sh | 4 ++- 5 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 test/mdanchorfix/docs/a/README.md create mode 100644 test/mdanchorfix/docs/b/README.md diff --git a/src/graph.h b/src/graph.h index 6fc83bd12..af00b3565 100644 --- a/src/graph.h +++ b/src/graph.h @@ -6302,36 +6302,66 @@ inline bool isMarkdownLinkRef( const Reference& r ) noexcept return r.lang == Lang::Markdown && !r.isDocLink && !r.memberCall && !r.memberPath.empty(); } -inline MdLinkIndex buildMdLinkIndex( const IngestResult& ing ) +// The file node is the Section extractMarkdown builds with bodyByte 0 over [0, size). assignSymbols then +// leaves sigStart 0 and sigEnd == endByte == the file span (widest is that span, measured over the file's +// own sections). A 0,0 span is a ModuleScope, never this node. Same predicate recall.h uses. A heading +// matches it only when the whole file is that one heading line. +inline bool isMarkdownFileNode( const Symbol& sym, std::uint32_t widestEnd ) noexcept { - MdLinkIndex ix; - for( std::uint32_t f = 0; f < ing.files.size(); ++f ) + return sym.sigStartByte == 0 && sym.sigEndByte >= sym.endByte && sym.endByte >= widestEnd; +} + +inline std::vector markdownWidestEnd( const IngestResult& ing ) +{ + std::vector widest( ing.files.size(), 0 ); + for( const Symbol& sym : ing.symbols ) { - if( ing.files[ f ].ends_with( ".md" ) || ing.files[ f ].ends_with( ".markdown" ) ) + if( sym.lang == Lang::Markdown && sym.kind == SymKind::Section && sym.fileId < ing.files.size() ) { - ix.fileByPath.try_emplace( ing.files[ f ], f ); + widest[ sym.fileId ] = std::max( widest[ sym.fileId ], sym.endByte ); } } + return widest; +} + +// Headings, and each file's own node (the first matching symbol in id order). The file node is not a heading. +inline void indexMarkdownSections( MdLinkIndex& ix, const IngestResult& ing ) +{ ix.wholeFile.assign( ing.files.size(), kNoNode ); + const std::vector widest = markdownWidestEnd( ing ); for( const Symbol& sym : ing.symbols ) { if( sym.lang != Lang::Markdown || sym.kind != SymKind::Section || sym.fileId >= ing.files.size() ) { continue; } - if( sym.sigStartByte == 0 && sym.sigEndByte == 0 ) - { - ix.wholeFile[ sym.fileId ] = sym.id; - } - else + if( isMarkdownFileNode( sym, widest[ sym.fileId ] ) ) { - ix.headings.push_back( { sym.fileId, sym.sigStartByte, sym.id, mdSlugOf( sym.name ) } ); + if( ix.wholeFile[ sym.fileId ] == kNoNode ) + { + ix.wholeFile[ sym.fileId ] = sym.id; + } + continue; } + ix.headings.push_back( { sym.fileId, sym.sigStartByte, sym.id, mdSlugOf( sym.name ) } ); } std::sort( ix.headings.begin(), ix.headings.end(), []( const MdLinkIndex::Heading& a, const MdLinkIndex::Heading& b ) { return std::tie( a.fileId, a.slug, a.startByte, a.id ) < std::tie( b.fileId, b.slug, b.startByte, b.id ); } ); +} + +inline MdLinkIndex buildMdLinkIndex( const IngestResult& ing ) +{ + MdLinkIndex ix; + for( std::uint32_t f = 0; f < ing.files.size(); ++f ) + { + if( ing.files[ f ].ends_with( ".md" ) || ing.files[ f ].ends_with( ".markdown" ) ) + { + ix.fileByPath.try_emplace( ing.files[ f ], f ); + } + } + indexMarkdownSections( ix, ing ); return ix; } diff --git a/test/mdanchorfix/docs/a/README.md b/test/mdanchorfix/docs/a/README.md new file mode 100644 index 000000000..3e93c7af3 --- /dev/null +++ b/test/mdanchorfix/docs/a/README.md @@ -0,0 +1,3 @@ +# A readme + +Same filename as docs/b/README.md. A name-only guess from this directory lands here. diff --git a/test/mdanchorfix/docs/a/guide.md b/test/mdanchorfix/docs/a/guide.md index 33d4fe0f3..e23235868 100644 --- a/test/mdanchorfix/docs/a/guide.md +++ b/test/mdanchorfix/docs/a/guide.md @@ -33,3 +33,11 @@ See [nothing](../b/partner.md#no-such-heading). ```python [fenced](../b/partner.md#install) ``` + +## Missed Readme + +See [gone](../b/README.md#no-such-heading). + +## Bare Readme + +See [the page](../b/README.md). diff --git a/test/mdanchorfix/docs/b/README.md b/test/mdanchorfix/docs/b/README.md new file mode 100644 index 000000000..4c7cbbd29 --- /dev/null +++ b/test/mdanchorfix/docs/b/README.md @@ -0,0 +1,3 @@ +# B readme + +The file a `../b/README.md` link names. It has no heading for the missing slug. diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index 83c4acaca..23ab1632b 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -381,7 +381,9 @@ want "$AFIX" "Path Choice" 'Setup@docs/b/notes.md' "../b/notes.md want "$AFIX" "Root Readme Link" 'Languages@README.md' "../../README.md#languages binds the ROOT README, not docs/README.md" want "$AFIX" "Dup Link" 'Notes@docs/b/dup.md' "#notes over two ## Notes gives ONE edge (first heading wins)" want "$AFIX" "Func Name" 'do_work@docs/b/partner.md' "a heading named like a .py function binds the section, never the function" -want "$AFIX" "Missing Anchor" 'partner@docs/b/partner.md' "#no-such-heading falls back to the file (stem) edge" +want "$AFIX" "Missing Anchor" 'partner@docs/b/partner.md' "#no-such-heading falls back to that file's own node" +want "$AFIX" "Missed Readme" 'README@docs/b/README.md' "../b/README.md#no-such-heading pins docs/b/README.md, not the same-directory README" +want "$AFIX" "Bare Readme" 'README@docs/b/README.md' "a bare ../b/README.md pins that file, not the same-directory README" want "$AFIX" "Fence Only" '' "a link inside a fenced block makes no edge" # --callers lists every matched def in (cross_kind when a heading and a function share the name). # The edge is the row: it must name the section, never the .py function. From 4d8dd119bded39edb0a2a54edf1055c1dfe96632 Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sun, 18 Oct 2026 15:28:30 +0300 Subject: [PATCH 06/28] Mark a markdown link with via="link" when no call shares that edge. A heading that shares a function's name stays in the callers union, and the row says the edge is a link. mdsectioncheck passes. legendcoverage hit the 300-second runner timeout, and quality-delta has not been --- CHANGELOG.md | 4 +- src/compactlegend.h | 4 ++ src/forowner.h | 3 +- src/graph.h | 103 +++++++++++++++++++++++++++++++++++++++ src/graphlegend.h | 12 +++++ src/main.cpp | 12 ++--- src/mcpverbs.h | 19 +++++--- src/serialize.h | 107 ++++++++++++++++++++++++++++++++--------- src/verbs_for.h | 12 ++--- src/verbs_navigate.h | 20 +++++--- test/mdsectioncheck.sh | 22 ++++++++- 11 files changed, 265 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e9e8d5c..8696ecded 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,9 @@ list the sections it links to instead of one file-level edge. The extractor cach the path against the linking file, so `../b/notes.md` never lands on a same-named file in the linker's own directory and a link to a heading named like one of the linking file's own headings does not bind to the latter. Two headings of the same slug give one edge (the first). A missing heading falls back to the target file's edge; percent-encoded slugs and GitHub's -`-1` duplicate suffix are not modelled and also fall back. Gate: `test/mdsectioncheck.sh`. +`-1` duplicate suffix are not modelled and also fall back. A markdown link that no call shares is marked `via="link"` on +`--callers`, `--callees`, `--path`, `--connect`, and on a map or body callee row. `count=` and `cross_kind` stay the same. A +call on the same pair leaves the row unmarked. Gate: `test/mdsectioncheck.sh`. ### Changed — Go methods carry their receiver type as scope (#403, slice 9a) diff --git a/src/compactlegend.h b/src/compactlegend.h index 44a050727..075aace41 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -252,6 +252,7 @@ inline constexpr std::string_view kCompactProsePrefixes[] = ""; } +inline std::string viaLinkLegendComment() +{ + return std::string( ""; +} // …and the MAP's spelling, as its own comment: the same three facts as kViaNameLegend in the fewest bytes (a map prints the // most hedged rows of any answer, and its --max-tokens fit pays for every legend byte in rows), plus x=, the count a merged // row carries (serialize.h writeMapCalleeRows), and where each merged candidate is listed. No `--` inside an XML comment: // the flag is named callees=. inline constexpr std::string_view kMapViaNameLegend = ""; +inline constexpr std::string_view kMapViaLinkLegend = + ""; // …and MCP's spelling: the same sentence as the `via_note` key of an answer one of whose entries carries "via":"name". inline std::string viaNameNoteJson() { diff --git a/src/main.cpp b/src/main.cpp index b2c399d90..03a0148ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2041,7 +2041,7 @@ int runDefaultMap( const MainDispatch& d ) { return {}; } - serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls ); + serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ) ); const rw::MemoryStreamBytes measured = probe.finish(); return measured.isWhole ? std::string( measured.bytes ) : std::string(); }; @@ -2054,7 +2054,7 @@ int runDefaultMap( const MainDispatch& d ) DISCLOSE( maxTokensFit, rw::MapAnnotations::MaxTokensFit::DisclosureWhy::ProbeUnmeasured, "runDefaultMap: open_memstream failed for the --max-tokens fit probe — the map is emitted unshaped and its ceiling unverified" ); return 0; } - serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls ); + serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ) ); const rw::MemoryStreamBytes measured = probe.finish(); if( !measured.isWhole ) { @@ -2444,7 +2444,7 @@ int runDefaultMap( const MainDispatch& d ) { bodiesSection = rw::chargeSection( [ & ]( std::FILE* f ) { expandRecord = EmittedBodies{}; // a re-render starts from empty: the record is what THIS render emitted - packBodies( f, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, g.outNameOnly, cfg.compress, redactPtr, + packBodies( f, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/&expandRecord, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); }, // V1: octocode F2 sibs=/inc= rw::kBytesPerTokenBody ); @@ -2826,7 +2826,7 @@ int runDefaultMap( const MainDispatch& d ) // not as the literal source token, for that same reason (the count is a naive grep over this file). if( !expandNodes.empty() && noteAppliesToBundle ) // !serveWholeFile is this whole branch's precondition (see the if above) { - emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, g.outNameOnly, cfg.compress, redactPtr, + emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); } ); bodiesEmittedEarly = true; @@ -2854,7 +2854,7 @@ int runDefaultMap( const MainDispatch& d ) } else { - serialize( out, ing, rank, g.outOff, g.outTargets, mapTopK, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, &mapEstTokens, payloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls ); + serialize( out, ing, rank, g.outOff, g.outTargets, mapTopK, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, &mapEstTokens, payloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ) ); } } else @@ -2900,7 +2900,7 @@ int runDefaultMap( const MainDispatch& d ) } if( !expandNodes.empty() && !serveWholeFile && !bodiesEmittedEarly ) // #289: already served ahead of the map below { - emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, g.outNameOnly, cfg.compress, redactPtr, + emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); } ); // L3: --expand bodies surface notes; V1: sibs=/inc= } diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 4e8395f26..f3f62ad01 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -454,7 +454,7 @@ inline std::string analyzeToString( const std::string& root, int topK, bool stab // §B4 family, and mcpclidiffcheck is the gate that keeps the two surfaces one. /*ann=*/rw::MapAnnotations{ .prDisclosure = ix.prDisclosure, .codeFirstRows = ix.isCleanWorkingSet, .inventoryTier = ix.isCleanWorkingSet }, /*statsFirstScreen=*/true, anRootArg, &ix.g.locPinOut, ix.g.externalCalls, &ix.g.declinedOut, ix.g.gateDeclinedCalls, - ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls ); } ); + ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls, rw::linkOutPtr( ix.g ) ); } ); } // `rank_by` verb (lane/t10-mcp-coverage): the MCP twin of --rank-by=pagerank|authority|hub|rrf — the SAME @@ -522,7 +522,7 @@ inline std::string rankByText( const std::string& root, std::string_view mode, i /*extraPayloadTokens=*/0, /*ann=*/rw::MapAnnotations{ .rankByLabel = rankByLabel, .prDisclosure = disclosure, .codeFirstRows = rankByLabel == nullptr, .inventoryTier = rankByLabel == nullptr }, // pagerank: the map scope's code-first pick /*statsFirstScreen=*/true, rbRootArg, &ix.g.locPinOut, ix.g.externalCalls, &ix.g.declinedOut, ix.g.gateDeclinedCalls, - ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls ); } ); + ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls, rw::linkOutPtr( ix.g ) ); } ); } // ─── the cross-branch + dark-content MCP twins (`whereis`, `stray_content`, `flags`) ─── @@ -2213,7 +2213,7 @@ inline McpForAssembly assembleMcpFor( const McpForAssemblyInputs& in, std::strin share = forSoftSectionsShare( budgetBytesForTokens( budgetTokens ), mcpHdrXml.size() + atCut + legoStr.size() + composeStr.size() + routeStr.size() ); } mcpSlot = renderForBodySlot( BodySlotInputs{ ing, in.g.outOff, in.g.outTargets, in.g.outNameOnly, lensRank, flRootArg, redact, notesPtr, - /*viaLegendInHead=*/false, /*compress=*/false, &in.g.outProv, /*splitLegendInHead=*/false }, + /*viaLegendInHead=*/false, /*compress=*/false, &in.g.outProv, /*splitLegendInHead=*/false, linkOutPtr( in.g ) }, in.slotOwner, share ); } mcpForSoftRefill( in, renderSigs, sigsStr, mcpSigsCut, &mcpSigsNext, @@ -3637,10 +3637,12 @@ inline std::optional pathText( const std::string& root, const std:: // PATH-GAP: the CLI --path's gap clause, by the same analysis and emitter (src/pathgaps.h). const PathSearchGaps ptGaps = pth.empty() ? pathSearchGaps( ing, g, srcDefs, valueRefIndexOf( ix ) ) : PathSearchGaps{}; const bool ptHasVia = pathHasNameOnlyHop( g, pth ); // FE-B: exactly when a hop of this path is via="name", as on the CLI + const bool ptHasLink = pathHasLinkHop( g, pth ); const PathEndpointBinding ptBound = pathEndpointBinding( g, endpointDefinitionCount( ing, srcDefs ), endpointDefinitionCount( ing, dstDefs ), pth ); // attrib-multidef, as on the CLI const bool ptHasSplit = ptBound.splitHop; rw::emitTo( mem, "{}", ( unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + viaNameLegend( ptHasVia ) + + viaLinkLegend( ptHasLink ) + splitArmLegend( ptHasSplit ) + pathUnboundLegend( !ptBound.from || !ptBound.to ) ).c_str(), ( std::string( toValueRefsLegend( ptToValueRefs.count > 0 ) ) + valueRefsDepthLegend( ptToValueRefs.depthCut.files > 0 ) ).c_str(), pathGapsLegend( ptGaps.any() ), @@ -3661,7 +3663,7 @@ inline std::optional pathText( const std::string& root, const std:: { const Symbol& s = ing.symbols[ pth[i] ]; const std::string_view rp = ptSingleRoot ? sarif::rootRelativeUri( ing.files[ s.fileId ], ptRootPrefix ) : std::string_view( ing.files[ s.fileId ] ); rw::emitTo( mem, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, - viaNameAttr( i > 0 && edgeNameOnly( g, pth[ i - 1 ], pth[ i ] ) ), splitArmAttrXml( pathHopSplitArm( g, pth, i ) ) ); } // FE-B; attrib-multidef: as on the CLI + viaEdgeAttr( i > 0 && edgeNameOnly( g, pth[ i - 1 ], pth[ i ] ), i > 0 && edgeIsLink( g, pth[ i - 1 ], pth[ i ] ) ), splitArmAttrXml( pathHopSplitArm( g, pth, i ) ) ); } // FE-B; a link hop says via="link" rw::emitRaw( mem, "" ); return mcpAnswerText( stream ); // nullopt = the buffer lost bytes (dispatch answers -32603), "" stays not-found } @@ -3925,7 +3927,12 @@ inline void packConnect( std::FILE* out, const IngestResult& ing, const Graph& g { return grp.terminals.size() > 1 && std::any_of( grp.edges.begin(), grp.edges.end(), [ & ]( const ConnectEdge& e ) { return edgeNameOnly( g, e.from, e.to ); } ); } ); - const std::string connectViaLegend = connectHasVia ? "" : std::string(); + const bool connectHasLink = std::any_of( res.groups.begin(), res.groups.end(), [ & ]( const ConnectGroup& grp ) + { + return grp.terminals.size() > 1 && std::any_of( grp.edges.begin(), grp.edges.end(), [ & ]( const ConnectEdge& e ) { return edgeIsLink( g, e.from, e.to ); } ); + } ); + const std::string connectViaLegend = ( connectHasVia ? "" : std::string() ) + + ( connectHasLink ? "" : std::string() ); const std::size_t connectExtraBytes = connectRootAttr.size() + std::strlen( rootRelPathsLegend( !rootArg.empty() ) ) + connectUnindexedLegend.size() + connectUnprovenAttr.size() + connectUnprovenLegend.size() + connectAmbiguousAttr.size() + connectViaLegend.size(); @@ -4077,7 +4084,7 @@ inline void packConnect( std::FILE* out, const IngestResult& ing, const Graph& g { payload.append( "" ); // FE-B: a name-only edge says so + .append( viaEdgeAttr( edgeNameOnly( g, e.from, e.to ), edgeIsLink( g, e.from, e.to ) ) ).append( "/>" ); // FE-B, or via="link" } payload.append( "" ); } diff --git a/src/serialize.h b/src/serialize.h index 761ebe8ad..e70d13d0e 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -2642,10 +2642,11 @@ inline std::uint32_t counterAt( const std::vector* v, NodeId id ) // before. `scratch` is reused across symbols. inline void writeMapCalleeRows( XmlWriter& w, const IngestResult& ing, std::uint32_t from, std::uint32_t to, const std::vector& outTargets, const std::vector* outProv, const std::vector* outNameOnly, std::vector& esc, - std::vector& scratch ) + std::vector& scratch, const std::vector* outLink = nullptr ) { const auto provOf = [ & ]( std::uint32_t e ) -> std::uint8_t { return ( outProv && e < outProv->size() ) ? ( *outProv )[ e ] : 0; }; const auto nameOnly = [ & ]( std::uint32_t e ) { return outNameOnly && e < outNameOnly->size() && ( *outNameOnly )[ e ] != 0; }; + const auto link = [ & ]( std::uint32_t e ) { return outLink && e < outLink->size() && ( *outLink )[ e ] != 0; }; // the row's bytes among via="name" rows: name, prov= const auto rowLess = [ & ]( std::uint32_t x, std::uint32_t y ) { @@ -2688,7 +2689,11 @@ inline void writeMapCalleeRows( XmlWriter& w, const IngestResult& ing, std::uint w.write( "\" prov=\"" ); w.write( provLabel( provOf( e ) ) ); } - if( nameOnly( e ) ) + if( link( e ) ) + { + w.write( "\" via=\"link" ); // a pinned markdown link, and no real call on this pair + } + else if( nameOnly( e ) ) { w.write( "\" via=\"name" ); // FE-B: the same edge bit every other surface reads (viaNameLegendComment) } @@ -2701,6 +2706,27 @@ inline void writeMapCalleeRows( XmlWriter& w, const IngestResult& ing, std::uint } } +// A row under one of `nodes` may carry via="link" when any of their out-edges is a markdown link. Same over-approximation +// as namesOnlyOutAny, asked before the rows are written so the map's first-screen legend can define the attribute. +inline bool linksOutAny( const std::vector& outOff, const std::vector& outLink, + std::span nodes ) noexcept +{ + for( const NodeId n : nodes ) + { + if( std::size_t( n ) + 1 >= outOff.size() ) + { + continue; + } + for( std::uint32_t e = outOff[ n ]; e < outOff[ n + 1 ] && e < outLink.size(); ++e ) + { + if( outLink[ e ] != 0 ) + { + return true; + } + } + } + return false; +} // FE-B: may a row under one of `nodes` carry via="name"? True when any of their out-edges is name-only — an // over-approximation of what a section renders (a block's 16-per-symbol cap may cut that row; a merged overload // row prints one representative's edges), asked BEFORE the rows are written by a surface whose first-screen legend must @@ -2782,7 +2808,8 @@ template inline void writeMapInventoryXml( XmlWriter& w, const IngestResult& ing, const MapInventory& inv, const PathRel& pathRel, const std::vector& outOff, const std::vector& outTargets, const std::vector* outProv, const std::vector* outNameOnly, - std::vector& esc, std::vector& scratch ) + std::vector& esc, std::vector& scratch, + const std::vector* outLink = nullptr ) { EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( "" ); - writeMapCalleeRows( w, ing, outOff[ id ], outOff[ id + 1 ], outTargets, outProv, outNameOnly, esc, scratch ); + writeMapCalleeRows( w, ing, outOff[ id ], outOff[ id + 1 ], outTargets, outProv, outNameOnly, esc, scratch, outLink ); w.write( "" ); } for( const MapInventoryDir& d : inv.dirs ) @@ -2893,7 +2920,10 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto std::size_t stdMemberDeclinedCalls = 0, // of declined=, the Elixir call candidates inside inert quote AST (g.inertDeclinedCalls, #380) → // the kInertDeclinedMapLegend clause, absent when zero. - std::size_t inertDeclinedCalls = 0 ) + std::size_t inertDeclinedCalls = 0, + // Markdown link edges (Graph::outLink) → via="link" on a row and kMapViaLinkLegend. nullptr/empty + // ⇒ the map has no link edge and stays byte-identical. + const std::vector* outLink = nullptr ) { const std::size_t* changedCount = ann.changedCount; const std::string* mapAtStamp = ann.atStamp; @@ -3148,6 +3178,11 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto // and whose element-qualified via/x rows restate it: the --max-tokens fit then prices what is delivered. legend += kMapViaNameLegend; } + if( outLink && !stubbed && !ann.viaLegendStripped + && linksOutAny( outOff, *outLink, std::span( order.data(), std::min( keep, order.size() ) ) ) ) + { + legend += kMapViaLinkLegend; + } // C1 DRIFT FIX (Round C lane B, found by re-reading this header's own output). `precise=` means "how many // out-edges a SCIP index PINNED", and the emitter's own comment below says it is "emitted ONLY under // --scip". Both were true when outProv held only {0, 1}. A4-R5 then added value 2 (an FFI binding edge) @@ -3442,7 +3477,7 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto { if( inventory.active ) { - writeMapInventoryXml( w, ing, inventory, pathRel, outOff, outTargets, outProv, outNameOnly, esc, hedgedScratch ); + writeMapInventoryXml( w, ing, inventory, pathRel, outOff, outTargets, outProv, outNameOnly, esc, hedgedScratch, outLink ); } }; const bool inventoryFirst = !stable && effImportantLast; // --stable's path order wins, as it does for the rows @@ -3663,7 +3698,7 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto } w.write( ">" ); - writeMapCalleeRows( w, ing, outOff[id], outOff[id + 1], outTargets, outProv, outNameOnly, esc, hedgedScratch ); + writeMapCalleeRows( w, ing, outOff[id], outOff[id + 1], outTargets, outProv, outNameOnly, esc, hedgedScratch, outLink ); w.write( "" ); } w.write( "" ); @@ -6544,6 +6579,7 @@ struct CalleeCallsSink // nullptr / empty ⇒ no row is marked. `splitLegendCharged`: as viaLegendCharged, for its comment (kForSplitClause). const std::vector* prov = nullptr; bool* splitLegendCharged = nullptr; + const std::vector* link = nullptr; // Graph::outLink: via="link" on a callee row. nullptr ⇒ none }; // The wrapper, written in front of the rows it describes (they have to be walked before `shown` @@ -6689,17 +6725,18 @@ struct MergedCalleeNameRow std::vector lines; // every definition line of that name; joined ascending at append time bool via = false; // FE-B: these edges are name-only — merged only with rows of the same bit bool split = false; // attrib-multidef Q1: these edges are split arms — likewise + bool link = false; // a markdown link — merged only with other link rows of the same name }; inline void collectCalleeNameRow( std::vector& rows, const Symbol& cs, - std::size_t& used, const CalleeCallsSink& sink, bool via = false, bool split = false ) + std::size_t& used, const CalleeCallsSink& sink, bool via = false, bool split = false, bool link = false ) { char lb[ 16 ]; rw::formatTo( lb, sizeof( lb ), "{}", cs.line ); bool merged = false; for( MergedCalleeNameRow& r : rows ) { - if( r.name == cs.name && r.via == via && r.split == split ) + if( r.name == cs.name && r.via == via && r.split == split && r.link == link ) { r.lines.push_back( cs.line ); merged = true; break; @@ -6707,11 +6744,11 @@ inline void collectCalleeNameRow( std::vector& rows, const } if( !merged ) { - rows.push_back( MergedCalleeNameRow { cs.name, { cs.line }, via, split } ); + rows.push_back( MergedCalleeNameRow { cs.name, { cs.line }, via, split, link } ); } // the comma and the digits a merge appends, or the whole row it opens (FE-B: plus its 11-byte via="name"; Q1: its 13-byte - // prov="split") - used += merged ? std::strlen( lb ) + 1 : cs.name.size() + 16 + ( via ? 11u : 0u ) + ( split ? 13u : 0u ); + // prov="split"; a link row's via="link" is the same 11 bytes) + used += merged ? std::strlen( lb ) + 1 : cs.name.size() + 16 + ( ( via || link ) ? 11u : 0u ) + ( split ? 13u : 0u ); if( sink.recorded ) { sink.recorded->push_back( EmittedBodyCall { cs.name, cs.line, std::string() } ); // §H5: no sig to record @@ -6738,7 +6775,7 @@ inline void appendMergedCalleeNameRows( std::string& callsBody, std::vector& nameOnly; const std::vector* prov = nullptr; bool* splitLegendCharged = nullptr; + const std::vector* link = nullptr; CalleeEdgeBytes( const std::vector& nameOnlyBytes, const std::vector* provBytes = nullptr, // NOLINT: implicit - bool* charged = nullptr ) noexcept - : nameOnly( nameOnlyBytes ), prov( provBytes ), splitLegendCharged( charged ) {} + bool* charged = nullptr, const std::vector* linkBytes = nullptr ) noexcept + : nameOnly( nameOnlyBytes ), prov( provBytes ), splitLegendCharged( charged ), link( linkBytes ) {} }; // …and which of their readings the caller's own first-screen legend already carries (FE-B's via="name", Q1's prov="split"), so // the section neither repeats nor charges them. Built implicitly from the via bit alone, as every pre-Q1 caller passes it. @@ -6786,6 +6824,11 @@ inline CalleeCallsSink withSplitArms( CalleeCallsSink sink, const std::vector* link ) noexcept +{ + sink.link = link; + return sink; +} // attrib-multidef Q1: is the row id → cid a split arm (and not by name alone: that row says via="name")? The first // such row of an answer charges the comment that defines it to `used`, before the row, when the head did not define it. @@ -6837,6 +6880,7 @@ inline void emitCalleeCallsBlock( std::string& out, NodeId id, const std::vector std::string callsBody; std::vector nameRows; // names-only rendering: collected, merged by name, written after the walk int shown = 0; + bool linkLegendCharged = false; for( std::uint32_t k = outOff[id]; k < outOff[id + 1] && shown < int( sink.rowCap ) && used < budgetBytes; ++k ) { const NodeId cid = walk[ k - outOff[id] ]; @@ -6845,13 +6889,20 @@ inline void emitCalleeCallsBlock( std::string& out, NodeId id, const std::vector continue; } const Symbol& cs = ing.symbols[cid]; - const std::string_view viaAttr = viaNameAttr( calleeRowVia( outOff, outTargets, id, cid, used, sink ) ); // FE-B: via="name" or "" + const bool linkRow = sink.link != nullptr && edgeNameOnlyInCsr( outOff, outTargets, *sink.link, id, cid ); + if( linkRow && !linkLegendCharged ) + { + used += viaLinkLegendComment().size(); + linkLegendCharged = true; + } + const bool nameRow = !linkRow && calleeRowVia( outOff, outTargets, id, cid, used, sink ); + const std::string_view viaAttr = viaEdgeAttr( nameRow, linkRow ); const std::string_view splitAttr = calleeRowSplitAttr( outOff, outTargets, id, cid, !viaAttr.empty(), used, sink ); // Q1: prov="split" or "" // COMPACT: the names-only rendering — see collectCalleeNameRow above for what it does and does not do. if( sink.namesOnly ) { - collectCalleeNameRow( nameRows, cs, used, sink, !viaAttr.empty(), !splitAttr.empty() ); + collectCalleeNameRow( nameRows, cs, used, sink, nameRow, !splitAttr.empty(), linkRow ); ++shown; continue; } @@ -7551,8 +7602,8 @@ inline void packBodies( std::FILE* out, const IngestResult& ing, const std::vect // total=/shown=/capped= block — see emitCalleeCallsBlock above; `calleeRank` decides which // rows survive when it CUTS one, which is far from rare here (CalleeCallsSink::rank). emitCalleeCallsBlock( piece, id, outOff, outTargets, ing, contentOf, esc, used, budgetBytes, - withSplitArms( CalleeCallsSink{ redact, record ? &record->calls : nullptr, /*namesOnly=*/false, calleeRank, &outNameOnly, - &viaLegendCharged }, outEdges.prov, &splitLegendCharged ) ); + withLinkBits( withSplitArms( CalleeCallsSink{ redact, record ? &record->calls : nullptr, /*namesOnly=*/false, calleeRank, &outNameOnly, + &viaLegendCharged }, outEdges.prov, &splitLegendCharged ), outEdges.link ) ); const std::string bodyNotes = renderNoteChildren( noteIndex, symbolNoteTarget( noteIndex, ing, s ), esc ); // L3/D5 piece += bodyNotes; used += bodyNotes.size(); // W3-N2: same charge-never-trim rule @@ -7628,6 +7679,10 @@ inline void packBodies( std::FILE* out, const IngestResult& ing, const std::vect { w.write( viaNameLegendComment() ); // FE-B: the same rule, for a row's via="name" } + if( children.find( " via=\"link\"" ) != std::string::npos ) + { + w.write( viaLinkLegendComment() ); + } if( !legendsInHead.split && children.find( " prov=\"split\"" ) != std::string::npos ) { w.write( splitArmCallsLegendComment() ); // attrib-multidef Q1: the same rule, for a row's prov="split" @@ -7726,8 +7781,8 @@ inline OwnerHopRows packOwnerHopRows( const IngestResult& ing, const std::vector } used += rowFixed; emitCalleeCallsBlock( row, id, outOff, outTargets, ing, contentOf, esc, used, budget, - withSplitArms( CalleeCallsSink{ redact, /*recorded=*/nullptr, /*namesOnly=*/true, rank, &outNameOnly, &via, - kForOwnerHopNameGuard, next }, outEdges.prov, outEdges.splitLegendCharged ) ); + withLinkBits( withSplitArms( CalleeCallsSink{ redact, /*recorded=*/nullptr, /*namesOnly=*/true, rank, &outNameOnly, &via, + kForOwnerHopNameGuard, next }, outEdges.prov, outEdges.splitLegendCharged ), outEdges.link ) ); out.xml += row; out.xml += ""; ++out.shown; @@ -7851,7 +7906,7 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector // owner-hop: the leading owner rows first (THE QUESTION'S OWNER ROWS above; nothing when `owners` is null), then the // ranked rows exactly as before, from the node the owners end at, on the budget and via charge the owners leave them - const OwnerHopRows own = packOwnerHopRows( ing, nodes, owners, outOff, outTargets, CalleeEdgeBytes( outNameOnly, outEdges.prov, &splitLegendCharged ), + const OwnerHopRows own = packOwnerHopRows( ing, nodes, owners, outOff, outTargets, CalleeEdgeBytes( outNameOnly, outEdges.prov, &splitLegendCharged, outEdges.link ), redact, rank, rootArg, budgetBytes, viaLegendCharged, pathRel, contentOf, esc ); std::string children = own.xml; std::size_t requestedCount = own.shown; @@ -7903,8 +7958,8 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector // the 1-hop callee signatures — the identical block a body carries, charged against the same // running `used` so the row identity bytes and the edge bytes share one budget. emitCalleeCallsBlock( row, id, outOff, outTargets, ing, contentOf, esc, used, budgetBytes, - withSplitArms( CalleeCallsSink{ redact, /*recorded=*/nullptr, /*namesOnly=*/true, rank, &outNameOnly, &viaLegendCharged }, - outEdges.prov, &splitLegendCharged ) ); + withLinkBits( withSplitArms( CalleeCallsSink{ redact, /*recorded=*/nullptr, /*namesOnly=*/true, rank, &outNameOnly, &viaLegendCharged }, + outEdges.prov, &splitLegendCharged ), outEdges.link ) ); row += ""; children += row; ++shownCount; @@ -7920,6 +7975,10 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector { w.write( viaNameLegendComment() ); // FE-B: exactly when a hop's row carries via="name" } + if( children.find( " via=\"link\"" ) != std::string::npos ) + { + w.write( viaLinkLegendComment() ); + } if( !legendsInHead.split && children.find( " prov=\"split\"" ) != std::string::npos ) { w.write( splitArmCallsLegendComment() ); // attrib-multidef Q1: exactly when a hop's row carries prov="split" diff --git a/src/verbs_for.h b/src/verbs_for.h index 5957b815d..5acd0db66 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -1981,7 +1981,7 @@ ForAutoBodiesResult buildForAutoBodies( const rw::Config& cfg, const rw::IngestR // shell the "budget" branch below already gets for free, rather than a second hand-rolled tag. out.attr = " bundle=\"auto\" bodies=\"0\" reason=\"no_candidates\""; out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packBodies( f, ing, autoBodyIds, /*budgetBytes=*/1, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv ), cfg.compress, redactPtr, + { rw::packBodies( f, ing, autoBodyIds, /*budgetBytes=*/1, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, /*ranges=*/nullptr, /*noteIndex=*/nullptr, nullptr, /*truncateOversizedFirst=*/false, /*withFileContext=*/false, fabRootArg, &lensRank, rw::CalleeLegendsInHead( viaLegendInHead, splitLegendInHead ) ); }, rw::kBytesPerTokenBody ); @@ -2003,7 +2003,7 @@ ForAutoBodiesResult buildForAutoBodies( const rw::Config& cfg, const rw::IngestR const std::size_t autoBodyBudget = std::max( 1, std::min( leftBytes, cfg.packBudgetBytes ) ); rw::EmittedBodies autoEmitted; out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packBodies( f, ing, autoBodyIds, autoBodyBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv ), cfg.compress, redactPtr, + { rw::packBodies( f, ing, autoBodyIds, autoBodyBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, /*ranges=*/nullptr, /*noteIndex=*/nullptr, &autoEmitted, /*truncateOversizedFirst=*/false, /*withFileContext=*/false, fabRootArg, &lensRank, rw::CalleeLegendsInHead( viaLegendInHead, splitLegendInHead ) ); }, rw::kBytesPerTokenBody ); @@ -2409,7 +2409,7 @@ ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::Ingest const rw::HopOwners owners = forHopOwners( cfg, ing, ownerPlan, leftBytes, fcRootArg ); out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packHops( f, ing, hopNodes, hopBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv ), redactPtr, &lensRank, fcRootArg, + { rw::packHops( f, ing, hopNodes, hopBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), redactPtr, &lensRank, fcRootArg, rw::CalleeLegendsInHead( viaLegendInHead, promised.splitLegendInHead ), &owners ); }, // g.outNameOnly also feeds the hop-slot rule (serialize.h noProvenEdge) // MARKUP rate, not the body rate — and this is an honesty choice, not a copy-paste slip. The body // rate (3.80 B/tok) prices SOURCE TEXT; the compact section contains none, only tags, identifiers @@ -3725,7 +3725,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co detailBodyBudget = std::min( detailBodyBudget, std::max( leftBytes, detailFloorBudget ) ); } detailSection = rw::chargeSection( [ & ]( std::FILE* f ) - { packBodies( f, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv ), cfg.compress, redactPtr, + { packBodies( f, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, /*ranges=*/nullptr, notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/false, flRootArg, &lensRank, rw::CalleeLegendsInHead( headerParts.viaPresent, headerParts.splitPresent ) ); }, // L3: --detail bodies surface notes too (part of the --for bundle) rw::kBytesPerTokenBody ); @@ -3779,7 +3779,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co { const std::size_t slotSpent = rowsSpentBytes + plan.attrReserve + enrich.section.xml.size(); // the rows at the cut + the hops slot = rw::renderForBodySlot( rw::BodySlotInputs{ ing, g.outOff, g.outTargets, g.outNameOnly, lensRank, flRootArg, redactPtr, notesPtr, - headerParts.viaPresent, cfg.compress, &g.outProv, headerParts.splitPresent }, + headerParts.viaPresent, cfg.compress, &g.outProv, headerParts.splitPresent, rw::linkOutPtr( g ) }, slotOwner, forSoftTarget ? std::optional( rw::forSoftSectionsShare( bundleBudget, slotSpent ) ) : std::nullopt ); if( slot.served ) @@ -3999,7 +3999,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co // directly at the SAME budget — uncharged for that one run, with an alert, never a fabricated number. if( cfg.detail > 0 ) { - rw::emitChargedSection( stdout, detailSection, [ & ]{ packBodies( stdout, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv ), + rw::emitChargedSection( stdout, detailSection, [ & ]{ packBodies( stdout, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, /*ranges=*/nullptr, notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/false, flRootArg, &lensRank, rw::CalleeLegendsInHead( headerParts.viaPresent, headerParts.splitPresent ) ); } ); diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index dbe2a6366..31073e2bf 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -92,7 +92,8 @@ inline void printJsonSymbolRows( const rw::IngestResult& ing, const std::vector< rw::symTag( s.kind ), rw::jsonStr( s.name ).c_str(), rw::jsonStr( p ).c_str(), s.line, depth ? ",\"d\":" + std::to_string( ( *depth )[ ids[i] ] ) : std::string(), ( testReach && rw::isTestedByReach( ing, *testReach, ids[i] ) ) ? ",\"tested\":true" : "", - ( viaName && i < viaName->size() && ( *viaName )[ i ] != 0 ) ? ",\"via\":\"name\"" : "", // FE-B: the row's hedge + ( viaName && i < viaName->size() && ( *viaName )[ i ] == 2 ) ? ",\"via\":\"link\"" + : ( ( viaName && i < viaName->size() && ( *viaName )[ i ] == 1 ) ? ",\"via\":\"name\"" : "" ), // 2 = markdown link, 1 = name hedge jsonRowKeysAt( rowKeys, i ) ); } } @@ -262,9 +263,11 @@ std::optional runCallHierarchy( const MainDispatch& d ) const bool chHasModScope = anyModuleScopeRow( ing, chPage ) || anyModuleScopeRow( ing, matches ); // FE-B: exactly when a row on THIS page is via="name" bool chHasVia = false; - for( std::size_t i = pw.begin; i < pw.end && !chHasVia; ++i ) + bool chHasLink = false; + for( std::size_t i = pw.begin; i < pw.end && ( !chHasVia || !chHasLink ); ++i ) { - chHasVia = chAttr.nameOnly( i ); // FE-B's via="name": every edge to the matches by name (defattrib.h, one pass) + chHasVia = chHasVia || chAttr.nameOnly( i ); // FE-B's via="name": every edge to the matches by name (defattrib.h, one pass) + chHasLink = chHasLink || oneHopRowIsLink( g, matches, result[ i ], wantCallers ); } rw::emitTo( stdout, "{}{}{}{}{}{}{}-->{}{}", rw::callHierarchyLegendOpen( wantCallers, chNextIsBare, cfg.columnar, chNextIsPage ).c_str(), rw::capLegendClause( rw::computePageDisclosure( pw.end - pw.begin, result.size(), pw.end, @@ -277,6 +280,7 @@ std::optional runCallHierarchy( const MainDispatch& d ) ( std::string( rw::unprovenDefsLegend( chRows.unprovenDefs > 0 ) ) // H1: likewise, exactly when unproven_defs= is there + rw::crossKindLegend( !chRows.crossKind.empty() ) // hono-07: likewise for cross_kind= + rw::viaNameLegend( chHasVia, false, cfg.columnar ) // FE-B: likewise for via="name" + + rw::viaLinkLegend( chHasLink ) + rw::defTableLegend( chAttr.groups.multi(), chListing.cut ) + rw::splitArmLegend( chHasSplit ) ).c_str(), // attrib-multidef: likewise ( std::string( rw::modScopeLegend( chHasModScope ) ) // #60: likewise, exactly when a t="modscope" row is + rw::callSitesAtLegend( chSites.any, chSites.anyCut, rw::kCallSitesPerRowCap ) ).c_str(), // CALLSITE-AT: likewise for sites_at=/sites_total= @@ -381,7 +385,7 @@ std::optional runCallHierarchy( const MainDispatch& d ) std::vector chAtKeys( result.size() ); // CALLSITE-AT: the rows' sites_at/sites_total keys, absent when none for( std::size_t i = pw.begin; i < pw.end; ++i ) { - chVia[ i ] = chAttr.nameOnly( i ) ? 1 : 0; + chVia[ i ] = oneHopRowIsLink( g, matches, result[ i ], wantCallers ) ? 2 : ( chAttr.nameOnly( i ) ? 1 : 0 ); if( chSites.any ) { chAtKeys[ i ] = rw::callSitesKeyJson( chSites.tokens[ i - pw.begin ], chSites.totals[ i - pw.begin ] ); @@ -418,7 +422,7 @@ std::optional runCallHierarchy( const MainDispatch& d ) // already follows everywhere else (serialize.h/verbs_for.h), so an untested row costs 0 bytes. rw::emitTo( stdout, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, macroRoleAttr( s.kind ), rw::isTestedByReach( ing, chTested.testReach, result[i] ) ? " tested=\"1\"" : "", - viaNameAttr( chAttr.nameOnly( i ) ), // FE-B: a name-only edge says so on its row + viaEdgeAttr( chAttr.nameOnly( i ), oneHopRowIsLink( g, matches, result[ i ], wantCallers ) ), rw::splitArmAttrXml( chAttr.splitArm( i ) ), rw::ofDefAttrXml( chAttr.groups, chAttr.tokens, i ), // attrib-multidef chSites.any ? rw::callSitesAttrXml( chSites.tokens[ i - pw.begin ], chSites.totals[ i - pw.begin ] ) : std::string() ); // CALLSITE-AT } @@ -2413,11 +2417,13 @@ std::optional runPath( const MainDispatch& d ) const rw::ToValueRefs pthToValueRefs = path.empty() ? rw::toValueRefs( ing, true, dstDefs, &*pthValueIdx ) : rw::ToValueRefs{}; const rw::PathSearchGaps pthGaps = path.empty() ? rw::pathSearchGaps( ing, g, srcDefs, *pthValueIdx ) : rw::PathSearchGaps{}; const bool pthHasVia = rw::pathHasNameOnlyHop( g, path ); // FE-B: exactly when a hop of THIS path is via="name" + const bool pthHasLink = rw::pathHasLinkHop( g, path ); // attrib-multidef (defattrib.h): from_p=/to_p= only for an endpoint the path bound; prov="split" on a split-arm hop const rw::PathEndpointBinding pthBound = rw::pathEndpointBinding( g, rw::endpointDefinitionCount( ing, srcDefs ), rw::endpointDefinitionCount( ing, dstDefs ), path ); const bool pthHasSplit = pthBound.splitHop; rw::emitTo( stdout, "{}", ( rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Path, pthUnprovenDefs > 0 ) + rw::viaNameLegend( pthHasVia ) + + rw::viaLinkLegend( pthHasLink ) + rw::splitArmLegend( pthHasSplit ) + rw::pathUnboundLegend( !pthBound.from || !pthBound.to ) ).c_str(), ( std::string( rw::toValueRefsLegend( pthToValueRefs.count > 0 ) ) + rw::valueRefsDepthLegend( pthToValueRefs.depthCut.files > 0 ) ).c_str(), rw::pathGapsLegend( pthGaps.any() ), @@ -2445,7 +2451,7 @@ std::optional runPath( const MainDispatch& d ) const std::string_view rp = pthSingleRoot ? rw::sarif::rootRelativeUri( ing.files[ s.fileId ], pthRootPrefix ) : std::string_view( ing.files[ s.fileId ] ); // FE-B: a hop whose edge INTO it is name-only says so on its row (the first row is no hop) rw::emitTo( stdout, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, - viaNameAttr( i > 0 && edgeNameOnly( g, path[ i - 1 ], path[ i ] ) ), rw::splitArmAttrXml( rw::pathHopSplitArm( g, path, i ) ) ); + viaEdgeAttr( i > 0 && edgeNameOnly( g, path[ i - 1 ], path[ i ] ), i > 0 && edgeIsLink( g, path[ i - 1 ], path[ i ] ) ), rw::splitArmAttrXml( rw::pathHopSplitArm( g, path, i ) ) ); } rw::emitTo( stdout, "" ); return 0; @@ -3061,7 +3067,7 @@ std::optional runAround( const MainDispatch& d ) aroundAnn.seed = { ing.symbols[ focus ].name, cfg.aroundDepth, cfg.aroundFanout, definitionCountOfName( ing, focus ), eg.fanoutCut, eg.depthTruncated }; aroundAnn.seed.unprovenDefs = arUnprovenDefs; // H1: unproven_defs= beside defs= on the root, its clause in the map legend - serialize( stdout, ing, rank, g.outOff, g.outTargets, int( eg.nodes.size() ), cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, false, g.outProv.empty() ? nullptr : &g.outProv, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, /*autoOrder=*/false, /*outEstTokens=*/nullptr, aroundCompose.tokens + aroundRoutes.tokens + wrap.tokens, aroundAnn, /*statsFirstScreen=*/false, aroundRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, /*gateDeclinedCalls=*/0, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, /*stdMemberDeclinedCalls=*/0, g.inertDeclinedCalls ); + serialize( stdout, ing, rank, g.outOff, g.outTargets, int( eg.nodes.size() ), cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, false, g.outProv.empty() ? nullptr : &g.outProv, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, /*autoOrder=*/false, /*outEstTokens=*/nullptr, aroundCompose.tokens + aroundRoutes.tokens + wrap.tokens, aroundAnn, /*statsFirstScreen=*/false, aroundRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, /*gateDeclinedCalls=*/0, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, /*stdMemberDeclinedCalls=*/0, g.inertDeclinedCalls, rw::linkOutPtr( g ) ); if( !g.composeEdges.empty() ) { diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index 23ab1632b..8d1e99629 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -391,7 +391,7 @@ if $BIN "$AFIX" --no-cache --callers=do_work --legend=compact 2>/dev/null | pyth import sys, re xml = sys.stdin.read() kind = { m.group(1): m.group(2) for m in re.finditer(r"]*)/?>", xml)) +rows = list(re.finditer(r"]*)/?>", xml)) ok = bool(rows) and "fn" in kind.values() and "sec" in kind.values() for m in rows: attrs = m.group(1) @@ -401,8 +401,26 @@ for m in rows: ok = False if of and kind.get(of.group(1)) == "fn": ok = False + via = re.search(r" via=\"([^\"]*)\"", attrs) + if not via or via.group(1) != "link": + ok = False sys.exit(0 if ok else 1) -'; then ok "the .py do_work is a matched def only — the caller edge belongs to the section"; else no "the doc link leaked onto the .py function"; fi +'; then ok "the .py do_work is a matched def only — the caller edge belongs to the section and is via=link"; else no "the doc link leaked onto the .py function, or the section row lacks via=link"; fi +if $BIN "$AFIX" --no-cache --callers='docs/a/tools.py:do_work' --legend=compact 2>/dev/null | grep -q 'via="link"'; then + no "a real function's callers carry via=link" +else + ok "the .py do_work callers are not marked via=link" +fi +if $BIN "$AFIX" --no-cache --callees='Self Collision' --legend=compact 2>/dev/null | grep -q 'n="Install"[^>]*via="link"'; then + ok "Self Collision's callee Install is via=link" +else + no "Self Collision's callee Install lacks via=link" +fi +if $BIN "$AFIX" --no-cache --json --callees='Self Collision' 2>/dev/null | grep -q '"via":"link"'; then + ok "the JSON callee row spells via=link" +else + no "the JSON callee row lacks via=link" +fi echo "--- warm == cold after editing ONLY the target file (the cached ref must stay unbound) ---" cp -R "$AFIX" "$TMP/anchorcopy" From 403bee476b472320b5b4c00f9f2ad93b362de8ce Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sun, 18 Oct 2026 17:14:58 +0300 Subject: [PATCH 07/28] Mark agent rows with via="link", and pin the link when the crawl root is a dot. find_symbol and find_referencing_symbols omitted the mark. A root of `.` stored `./docs/a.md`, so the lookup missed the file. --- CHANGELOG.md | 3 ++- src/graph.h | 44 ++++++++++++++++++++++++++++++++++++++++-- src/graphlegend.h | 19 ++++++++++++------ src/mcpverbs.h | 13 +++++++++++++ src/verbs_navigate.h | 7 +++++-- test/mdsectioncheck.sh | 41 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8696ecded..2dc2119e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,8 @@ link to a heading named like one of the linking file's own headings does not bin slug give one edge (the first). A missing heading falls back to the target file's edge; percent-encoded slugs and GitHub's `-1` duplicate suffix are not modelled and also fall back. A markdown link that no call shares is marked `via="link"` on `--callers`, `--callees`, `--path`, `--connect`, and on a map or body callee row. `count=` and `cross_kind` stay the same. A -call on the same pair leaves the row unmarked. Gate: `test/mdsectioncheck.sh`. +call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`; the link index normalizes that path, so the pin matches an absolute root. `find_symbol` and `find_referencing_symbols` carry the same mark, and a +`via_link_note` sentence when a row has it. `--json` callers and callees carry that note too. Gate: `test/mdsectioncheck.sh`. ### Changed — Go methods carry their receiver type as scope (#403, slice 9a) diff --git a/src/graph.h b/src/graph.h index 63b8e0313..3e2a56b47 100644 --- a/src/graph.h +++ b/src/graph.h @@ -494,6 +494,46 @@ inline const char* viaEdgeAttr( bool nameOnly, bool link ) noexcept return link ? " via=\"link\"" : viaNameAttr( nameOnly ); } +// The JSON twin of viaEdgeAttr. Link wins. Empty when the row is a plain call. +inline const char* viaJsonAttr( bool nameOnly, bool link ) noexcept +{ + return link ? ",\"via\":\"link\"" : ( nameOnly ? ",\"via\":\"name\"" : "" ); +} + +// oneHopRowIsLink plus the name hedge, and the two answer-level bits the notes read. The branch stays here so +// symbolQueryJson does not grow one. +inline const char* viaJsonForRow( const Graph& g, std::span matches, NodeId row, bool towardMatches, bool nameOnly, + bool& anyName, bool& anyLink ) noexcept +{ + const bool link = oneHopRowIsLink( g, matches, row, towardMatches ); + if( link ) + { + anyLink = true; + } + else if( nameOnly ) + { + anyName = true; + } + return viaJsonAttr( nameOnly, link ); +} + +inline const char* viaJsonForSide( const Graph& g, std::span callerMatches, std::span calleeMatches, NodeId row, + bool callersSide, bool nameOnly, bool& anyName, bool& anyLink ) noexcept +{ + return viaJsonForRow( g, callersSide ? callerMatches : calleeMatches, row, callersSide, nameOnly, anyName, anyLink ); +} + +// Empty unless id is the row the caller just selected. nameOnly stays false: the name fragment is the caller's own bit. +inline const char* linkJsonFrag( const Graph& g, std::span callerMatches, std::span calleeMatches, NodeId id, + NodeId linkRow, bool callersSide, bool& anyName, bool& anyLink ) noexcept +{ + if( id != linkRow ) + { + return ""; + } + return viaJsonForSide( g, callerMatches, calleeMatches, id, callersSide, false, anyName, anyLink ); +} + // The prov= VOCABULARY, spelled once. serialize's XML and serializeJson's JSON must emit identical // words for identical edges — test/mcpclidiffcheck.sh is the gate that says so — and two ternary // chains over the same integers is precisely how two dialects drift apart. Returns "" for 0 (a @@ -6348,7 +6388,7 @@ inline void fillImplementorBound( Graph& g, const std::vector fileByPath; // every .md/.markdown file, by root-relative path + HashMap fileByPath; // every .md/.markdown file, by lexicalNormalize(ing.files). A `.`-rooted crawl stores `./docs/a.md`; the lookup key does not. An absolute root stores the absolute path, and the lookup joins to that same absolute path. std::vector headings; // sorted (fileId, slug, startByte, id): the first of a slug is its heading std::vector wholeFile; // per file: the file-level Section node, kNoNode when none }; @@ -6449,7 +6489,7 @@ inline MdLinkIndex buildMdLinkIndex( const IngestResult& ing ) { if( ing.files[ f ].ends_with( ".md" ) || ing.files[ f ].ends_with( ".markdown" ) ) { - ix.fileByPath.try_emplace( ing.files[ f ], f ); + ix.fileByPath.try_emplace( lexicalNormalize( ing.files[ f ] ), f ); } } indexMarkdownSections( ix, ing ); diff --git a/src/graphlegend.h b/src/graphlegend.h index 88331e93f..8ed52c3f7 100644 --- a/src/graphlegend.h +++ b/src/graphlegend.h @@ -1230,13 +1230,12 @@ inline constexpr std::string_view kMapViaNameLegend = inline constexpr std::string_view kMapViaLinkLegend = ""; // …and MCP's spelling: the same sentence as the `via_note` key of an answer one of whose entries carries "via":"name". -inline std::string viaNameNoteJson() +inline std::string viaFactNoteJson( std::string text, std::string_view from, std::string_view jsonSpelling, std::string_view key ) { - std::string text = kViaNameLegend; - for( std::size_t at = text.find( "via=\"name\"" ); at != std::string::npos; at = text.find( "via=\"name\"", at ) ) + for( std::size_t at = text.find( from ); at != std::string::npos; at = text.find( from, at ) ) { - text.replace( at, 10, "\\\"via\\\":\\\"name\\\"" ); // via="name" -> \"via\":\"name\" inside the JSON string - at += 16; + text.replace( at, from.size(), jsonSpelling ); + at += jsonSpelling.size(); } if( const std::size_t at = text.find( "on a row" ); at != std::string::npos ) { @@ -1246,7 +1245,15 @@ inline std::string viaNameNoteJson() { text.pop_back(); } - return ",\"via_note\":\"" + text + "\""; + return ",\"" + std::string( key ) + "\":\"" + text + "\""; +} +inline std::string viaNameNoteJson() +{ + return viaFactNoteJson( kViaNameLegend, "via=\"name\"", "\\\"via\\\":\\\"name\\\"", "via_note" ); +} +inline std::string viaLinkNoteJson() +{ + return viaFactNoteJson( kViaLinkLegend, "via=\"link\"", "\\\"via\\\":\\\"link\\\"", "via_link_note" ); } // The same fact in --for's two dialects, which are prose strips joined by ';' and ':' rather than XML comment diff --git a/src/mcpverbs.h b/src/mcpverbs.h index f3f62ad01..f3d5887b3 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -754,10 +754,16 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& const HopTestedPartition chTested = computeHopTestedPartition( ing, g, referencingOnly ? calledBy : calls ); bool anyVia = false; // FE-B: an entry carried "via":"name" — the answer then carries via_note, its reading + bool anyLink = false; // an entry carried "via":"link" — the answer then carries via_link_note + const std::span callerMatches( referencingOnly ? chRows.matches : chCallers.matches ); + const std::span calleeMatches( chRows.matches ); + NodeId linkRow = kNoNode; + bool linkToward = false; RowCallSites calledBySites{}; // CALLSITE-AT: the calledBy window's sites (set once the window is known, below) const auto symObj = [ & ]( NodeId id, bool via, const std::string& attrib = std::string() ) -> std::string { anyVia = anyVia || via; + const char* linkFrag = linkJsonFrag( g, callerMatches, calleeMatches, id, linkRow, linkToward, anyVia, anyLink ); const Symbol& s = ing.symbols[id]; // T4: attach the stable content-handle so the agent can `fetch_body{handle}` instead of us re-sending // the body. Names/signatures by default (this verb); bodies by handle on request. @@ -771,6 +777,7 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& + ",\"handle\":\"" + mcpdetail::jsonEscape( handleFor( ix, id ) ) + "\"" + ( rw::isTestedByReach( ing, chTested.testReach, id ) ? ",\"tested\":true" : "" ) + ( via ? ",\"via\":\"name\"" : "" ) // FE-B: the CLI row's via="name", the same edge bit (defattrib.h, one pass) + + linkFrag + attrib // attrib-multidef: the CLI row's of_def= / prov="split" + "}"; }; @@ -786,6 +793,8 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& { if( i != w.begin ) { a += ","; } anySplit = anySplit || at.splitArm( i ); + linkRow = ids[ i ]; + linkToward = callersSide; // CALLSITE-AT: a calledBy row carries its call sites (the CLI --callers row's sites_at=); a calls row never does. a += withRowSites( symObj( ids[i], at.nameOnly( i ), ofDefKeyJson( at.groups, at.tokens, i ) + splitArmKeyJson( at.splitArm( i ) ) ), calledBySites, i - w.begin, callersSide ); @@ -879,6 +888,10 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& { out += rw::viaNameNoteJson(); // FE-B: the reading of "via":"name", only in an answer that carries it } + if( anyLink ) + { + out += rw::viaLinkNoteJson(); + } out += nextFieldJson( splitArmLegend( anySplit ), "split_note" ); // attrib-multidef Q1: likewise for "prov":"split" out += declinedSitesJson( sqDc, referencingOnly, /*mcp=*/true ); // lane declined-sites: the CLI rows, never in calls/calledBy or count out += nextFieldJson( declinedSitesLegend( declinedSitesLegendFlags( sqDc, referencingOnly, sqDcCut ), /*mcp=*/true ), "declined_note" ); // their reading, only when they ride diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index 31073e2bf..efa192b14 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -382,10 +382,12 @@ std::optional runCallHierarchy( const MainDispatch& d ) : std::string() ); rw::emitTo( stdout, ",\"{}\":[", tag ); std::vector chVia( result.size(), 0 ); // FE-B: the same hedge bit the XML rows carry + bool chHasLink = false; std::vector chAtKeys( result.size() ); // CALLSITE-AT: the rows' sites_at/sites_total keys, absent when none for( std::size_t i = pw.begin; i < pw.end; ++i ) { chVia[ i ] = oneHopRowIsLink( g, matches, result[ i ], wantCallers ) ? 2 : ( chAttr.nameOnly( i ) ? 1 : 0 ); + chHasLink = chHasLink || ( chVia[ i ] == 2 ); if( chSites.any ) { chAtKeys[ i ] = rw::callSitesKeyJson( chSites.tokens[ i - pw.begin ], chSites.totals[ i - pw.begin ] ); @@ -393,8 +395,9 @@ std::optional runCallHierarchy( const MainDispatch& d ) chAtKeys[ i ] += rw::ofDefKeyJson( chAttr.groups, chAttr.tokens, i ) + rw::splitArmKeyJson( chAttr.splitArm( i ) ); // attrib-multidef } printJsonSymbolRows( ing, result, pw.begin, pw.end, chRootPrefix, &chTested.testReach, nullptr, &chVia, &chAtKeys ); - rw::emitTo( stdout, "]{}{}}}", rw::declinedSitesJson( chDc, wantCallers, /*mcp=*/false ), // the XML rows, keys = its attribute names - rw::valueRefsJson( ing, chRows.valueRefs, wantCallers, chVr, "vrs", chVrNext ) ); + rw::emitTo( stdout, "]{}{}{}}}", rw::declinedSitesJson( chDc, wantCallers, /*mcp=*/false ), // the XML rows, keys = its attribute names + rw::valueRefsJson( ing, chRows.valueRefs, wantCallers, chVr, "vrs", chVrNext ), + chHasLink ? rw::viaLinkNoteJson() : std::string() ); return 0; } diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index 8d1e99629..a379bd087 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -406,6 +406,11 @@ for m in rows: ok = False sys.exit(0 if ok else 1) '; then ok "the .py do_work is a matched def only — the caller edge belongs to the section and is via=link"; else no "the doc link leaked onto the .py function, or the section row lacks via=link"; fi +if ( cd "$AFIX" && $BIN . --no-cache --callers=do_work --legend=compact ) 2>/dev/null | grep -q 'n="Func Name"[^>]*via="link"'; then + ok "a dot crawl root still pins the markdown link (./docs/... matches docs/...)" +else + no "a dot crawl root dropped the markdown link pin" +fi if $BIN "$AFIX" --no-cache --callers='docs/a/tools.py:do_work' --legend=compact 2>/dev/null | grep -q 'via="link"'; then no "a real function's callers carry via=link" else @@ -421,6 +426,42 @@ if $BIN "$AFIX" --no-cache --json --callees='Self Collision' 2>/dev/null | grep else no "the JSON callee row lacks via=link" fi +if $BIN "$AFIX" --no-cache --json --callers=do_work 2>/dev/null | python3 -c ' +import json, sys +d = json.load(sys.stdin) +rows = d.get("callers", []) +sec = any(r.get("t") == "sec" and r.get("via") == "link" for r in rows) +fn = any(r.get("t") == "fn" and r.get("via") == "link" for r in rows) +sys.exit(0 if sec and d.get("via_link_note") and not fn else 1) +'; then ok "the JSON callers answer marks the section row and carries via_link_note"; else no "the JSON callers answer lacks via_link_note, or the function row is marked via=link"; fi +mcp_text(){ + printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' "$1" \ + | "$BIN" --mcp 2>/dev/null | tail -1 | python3 -c ' +import sys, json +r = json.load(sys.stdin) +print("__ERROR__:" + r["error"].get("message", "") if "error" in r else r["result"]["content"][0]["text"]) +' +} +call(){ printf '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"%s","arguments":%s}}' "$1" "$2"; } +M="$( mcp_text "$( call find_referencing_symbols '{"path":"'"$AFIX"'","symbol":"do_work"}' )" )" +printf '%s' "$M" | python3 -c ' +import json, sys +d = json.load(sys.stdin) +rows = d.get("calledBy", []) +sec = any(r.get("kind") == "sec" and r.get("via") == "link" for r in rows) +fn = any("tools.py" in r.get("file", "") and r.get("via") == "link" for r in rows) +sys.exit(0 if sec and d.get("via_link_note") and not fn else 1) +' && ok "find_referencing_symbols marks the section row via=link and carries via_link_note" \ + || no "find_referencing_symbols lacks via=link on the section row, or marks the .py caller" +M="$( mcp_text "$( call find_symbol '{"path":"'"$AFIX"'","symbol":"Self Collision"}' )" )" +printf '%s' "$M" | python3 -c ' +import json, sys +d = json.load(sys.stdin) +rows = d.get("calls", []) +hit = any(r.get("name") == "Install" and r.get("via") == "link" for r in rows) +sys.exit(0 if hit and d.get("via_link_note") else 1) +' && ok "find_symbol marks Install via=link and carries via_link_note" \ + || no "find_symbol lacks via=link on Install or lacks via_link_note" echo "--- warm == cold after editing ONLY the target file (the cached ref must stay unbound) ---" cp -R "$AFIX" "$TMP/anchorcopy" From dae894baa371667ff1a1dfafe7a8aa9703f0d4f8 Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sun, 18 Oct 2026 17:22:51 +0300 Subject: [PATCH 08/28] fix sed on macOS behavior --- CHANGELOG.md | 3 +-- test/mdsectioncheck.sh | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dc2119e2..1644d0ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,8 +24,7 @@ link to a heading named like one of the linking file's own headings does not bin slug give one edge (the first). A missing heading falls back to the target file's edge; percent-encoded slugs and GitHub's `-1` duplicate suffix are not modelled and also fall back. A markdown link that no call shares is marked `via="link"` on `--callers`, `--callees`, `--path`, `--connect`, and on a map or body callee row. `count=` and `cross_kind` stay the same. A -call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`; the link index normalizes that path, so the pin matches an absolute root. `find_symbol` and `find_referencing_symbols` carry the same mark, and a -`via_link_note` sentence when a row has it. `--json` callers and callees carry that note too. Gate: `test/mdsectioncheck.sh`. +call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. Gate: `test/mdsectioncheck.sh`. ### Changed — Go methods carry their receiver type as scope (#403, slice 9a) diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index a379bd087..f2f46c056 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -467,7 +467,9 @@ echo "--- warm == cold after editing ONLY the target file (the cached ref must s cp -R "$AFIX" "$TMP/anchorcopy" rm -f "$TMP/anchor.cache" $BIN "$TMP/anchorcopy" --cache="$TMP/anchor.cache" --callees="Self Collision" --legend=compact >/dev/null 2>&1 -sed -i 's/^## Install$/## Install Steps Renamed/' "$TMP/anchorcopy/docs/b/partner.md" +sed -i.bak 's/^## Install$/## Install Steps Renamed/' "$TMP/anchorcopy/docs/b/partner.md" +rm -f "$TMP/anchorcopy/docs/b/partner.md.bak" +grep -q '^## Install Steps Renamed$' "$TMP/anchorcopy/docs/b/partner.md" || no "fixture edit did not apply (sed)" $BIN "$TMP/anchorcopy" --cache="$TMP/anchor.cache" --callees="Self Collision" --legend=compact >"$TMP/ac_warm" 2>/dev/null $BIN "$TMP/anchorcopy" --no-cache --callees="Self Collision" --legend=compact >"$TMP/ac_cold" 2>/dev/null if [ -s "$TMP/ac_cold" ] && diff -q "$TMP/ac_cold" "$TMP/ac_warm" >/dev/null; then ok "warm run equals cold run after only the target heading changed"; else no "warm run differs from cold after a target-only edit (a stale bound ref)"; fi From b6bdcc8155234f3c838c996acc2e578250dcb1dd Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sun, 18 Oct 2026 18:38:25 +0300 Subject: [PATCH 09/28] Mark a direct doc caller with via="link" on --safe-delete and --impact. callers= and reaches= still count that row. A real call on the same pair stays unmarked. --- CHANGELOG.md | 2 +- src/graph.h | 6 ++++++ src/mcpverbs.h | 6 ++++-- src/verbs_navigate.h | 42 +++++++++++++++++++++++++++++++++++------- test/mdsectioncheck.sh | 18 ++++++++++++++++++ 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1644d0ac5..c80c69475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ link to a heading named like one of the linking file's own headings does not bin slug give one edge (the first). A missing heading falls back to the target file's edge; percent-encoded slugs and GitHub's `-1` duplicate suffix are not modelled and also fall back. A markdown link that no call shares is marked `via="link"` on `--callers`, `--callees`, `--path`, `--connect`, and on a map or body callee row. `count=` and `cross_kind` stay the same. A -call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. Gate: `test/mdsectioncheck.sh`. +call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. `--safe-delete` and `--impact` mark a direct doc caller `via="link"`. `callers=` and `reaches=` still count it. Gate: `test/mdsectioncheck.sh`. ### Changed — Go methods carry their receiver type as scope (#403, slice 9a) diff --git a/src/graph.h b/src/graph.h index 3e2a56b47..3f7b600c0 100644 --- a/src/graph.h +++ b/src/graph.h @@ -12185,6 +12185,12 @@ inline bool impactRowNameOnly( const std::vector& proven, NodeId n ) noexc return !proven.empty() && n < proven.size() && proven[n] == 0; } +// A depth-1 impact row whose edge to every matched def is a markdown link. The edge runs caller → seed. +inline bool impactDirectLink( const Graph& g, std::span seeds, const std::vector& depth, NodeId n ) noexcept +{ + return n < depth.size() && depth[ n ] == 1 && oneHopRowIsLink( g, seeds, n, true ); +} + struct ImpactFileRow { std::uint32_t fileId = 0; diff --git a/src/mcpverbs.h b/src/mcpverbs.h index f3d5887b3..880d02c8c 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -3174,11 +3174,13 @@ inline std::optional impactText( const std::string& root, const std } const DefTableListing imListing = defTableListing( imAttr.groups, imNamed ); const bool imHasVia = std::any_of( show.begin() + ipw.begin, show.begin() + ipw.end, [ & ]( NodeId n ) { return impactRowNameOnly( proven, n ); } ); + const bool imHasLink = std::any_of( show.begin() + ipw.begin, show.begin() + ipw.end, [ & ]( NodeId n ) { return impactDirectLink( g, seeds, depth, n ); } ); rw::emitTo( mem, "{}{}. {}{}{}{}{}{}{}{}{}{}{}{}-->", kImpactLegendOpen, kPageRaiseCapClause, reach.empty() ? "" : kImpactDepthLegend, // 0.6.5: exactly when d=/by_depth= ride, as on the CLI ( std::string( kImpactImportTierLegend ) + importersNextLegend( !imports.next.empty() ) ).c_str(), // cut-fix E, as on the CLI ( impactTsImportLegend( imports.importsUnresolved, imports.tsconfigUnread ) // #220: exactly when the root carries them, as on the CLI - + viaNameLegend( imHasVia, true ) ).c_str(), // FE-B: exactly when a shown row is via="name", as on the CLI + + viaNameLegend( imHasVia, true ) + + viaLinkLegend( imHasLink ) ).c_str(), kTestedRowLegend, kImpactTestedPartitionLegend, // A6 kTestedLensBlindSpotLegend, // F-02: rides with the partition, byte-identical to the CLI twin unprovenDefsVerbLegend( UnprovenDefsVerb::Impact, unprovenDefs > 0 ).c_str(), // H1: exactly when the root carries unproven_defs=, as on the CLI @@ -3219,7 +3221,7 @@ inline std::optional impactText( const std::string& root, const std // A6: tested="1" only (never a literal 0) — see kTestedRowLegend. 0.6.5: d= as on the CLI row. rw::emitTo( mem, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, depthRunAttrXml( show, depth, i, ipw.begin ), isTestedByReach( ing, impTestReach, show[i] ) ? " tested=\"1\"" : "", - viaNameAttr( impactRowNameOnly( proven, show[i] ) ), imAttr.ofDefXml( show[i] ) ); } // FE-B (D3); attrib-multidef + viaEdgeAttr( impactRowNameOnly( proven, show[i] ), impactDirectLink( g, seeds, depth, show[i] ) ), imAttr.ofDefXml( show[i] ) ); } // the import tier's rows, after the symbol rows and under their own tag — a different unit, so a // different element (see the CLI arm and kImpactImportTierLegend for why they are never one number). emitImportRowsXml( mem, ing, std::span( imports.files ).first( imports.shown ), imRootPrefix, diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index efa192b14..b09d04e7d 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -972,6 +972,7 @@ struct SafeDeleteLegendFlags bool defTable = false; // attrib-multidef: the table + of_def= rows (a multi-group selector) bool defTableCut = false; // ... and the table lists only the groups this page's rows name (defs_listed=) bool splitArms = false; // a row is on this page (a one-group selector's split arm, Q1) + bool linkRows = false; // a row is on this page rw::DeclinedSitesLegendFlags declinedSites{}; // lane declined-sites: what the rows on this answer carry (declinedsites.h) }; @@ -1009,6 +1010,7 @@ inline std::string safeDeleteFloorLegend( const SafeDeleteLegendFlags& flags, st clause += "next= is that uses call. "; // runSafeDelete names it exactly then (sdNextAttr) } clause += rw::defTableLegend( flags.defTable, flags.defTableCut ) + rw::splitArmLegend( flags.splitArms ); // attrib-multidef + clause += rw::viaLinkLegend( flags.linkRows ); return clause; } @@ -1312,9 +1314,11 @@ std::optional runSafeDelete( const MainDispatch& d ) rw::defTableMarkNamed( sdNamed, sdAttr.groups, sdAttr.tokens, sdLw.begin, sdLw.end ); const rw::DefTableListing sdListing = rw::defTableListing( sdAttr.groups, sdNamed ); bool sdHasSplit = false; - for( std::size_t i = sdLw.begin; i < sdLw.end && !sdHasSplit; ++i ) + bool sdHasLink = false; + for( std::size_t i = sdLw.begin; i < sdLw.end && ( !sdHasSplit || !sdHasLink ); ++i ) { - sdHasSplit = sdAttr.splitArm( i ); + sdHasSplit = sdHasSplit || sdAttr.splitArm( i ); + sdHasLink = sdHasLink || rw::oneHopRowIsLink( g, defs, callerIds[ i ], true ); } const std::vector sdDc = rw::declinedSiteViews( ing, g, std::span( sdDeclinedSites ).first( sdDcCut.shown ), /*callersForm=*/true, defs, sdAttr.groups, @@ -1326,6 +1330,7 @@ std::optional runSafeDelete( const MainDispatch& d ) .valueRefs = sdValueRefs.rows.size(), .callersFloor = sdFloor.isFloor, .usesFloor = usesFloor, .hasSitesAt = sdSites.any, .sitesCut = sdSites.anyCut, .valueRefDepthCut = sdValueRefs.depthCut.files > 0, .defTable = sdAttr.groups.multi(), .defTableCut = sdListing.cut, .splitArms = sdHasSplit, + .linkRows = sdHasLink, .declinedSites = rw::declinedSitesLegendFlags( sdDc, /*callersForm=*/true, sdDcCut ) } ); const Symbol& lead = ing.symbols[ defs[0] ]; // resolveAllByNameQualified walks ascending id — defs[0] is the @@ -1367,7 +1372,8 @@ std::optional runSafeDelete( const MainDispatch& d ) { rw::emitTo( stdout, " amb=\"{}\"", g.ambOut[ callerId ] ); // M15: the same COUNT a map row's amb= prints — one meaning, one unit } - rw::emitTo( stdout, "{}{}/>", rw::splitArmAttrXml( sdAttr.splitArm( i ) ), rw::ofDefAttrXml( sdAttr.groups, sdAttr.tokens, i ) ); // attrib-multidef + rw::emitTo( stdout, "{}{}{}/>", rw::splitArmAttrXml( sdAttr.splitArm( i ) ), rw::ofDefAttrXml( sdAttr.groups, sdAttr.tokens, i ), + rw::viaEdgeAttr( false, rw::oneHopRowIsLink( g, defs, callerId, true ) ) ); // a direct markdown link } rw::emitTo( stdout, "{}{}", rw::declinedSitesXml( sdDc, /*callersForm=*/true ), // lane declined-sites: each declined possible caller, after the rows rw::valueRefsXml( ing, sdValueRefs, true, sdVr, "--uses=" + std::string( cfg.safeDeleteSym ) ) ); @@ -2587,6 +2593,7 @@ struct ImpactView const rw::ReachAttribution& attrib; // attrib-multidef (defattrib.h): the definition groups + each reached row's ties const rw::DefTableListing& defListing; // ... and the groups this page's table lists const std::vector& typeOnly; // L7 (typeuse.h ImpactReach::typeOnly): per node, reached only through a type mention; empty when unarmed + std::span seeds; // the matched defs; a depth-1 row's link mark reads the edge caller → seed }; @@ -2612,6 +2619,24 @@ inline std::vector impactViaColumn( const ImpactView& v, std::size_t begin return via; } +// JSON rows: 2 = a direct markdown link, 1 = via="name". The columnar column stays impactViaColumn (1 means name). +inline std::vector impactJsonVia( const ImpactView& v ) +{ + std::vector via( v.show.size(), 0 ); + for( std::size_t i = v.page.begin; i < v.page.end; ++i ) + { + if( rw::impactDirectLink( v.g, v.seeds, v.depth, v.show[ i ] ) ) + { + via[ i ] = 2; + } + else if( rw::impactRowNameOnly( v.provenReach, v.show[ i ] ) ) + { + via[ i ] = 1; + } + } + return via; +} + // --format=columnar (RESEARCH lever 1): same page window, path-table + parallel arrays. // V1-1: a fixed 160-byte buffer truncated mid-attribute on long escaped symbol names (invalid XML, the // F6 class), and this branch hand-rolled shown=/capped= without the paging half — the one @@ -2712,7 +2737,7 @@ int emitImpactJson( const ImpactView& v ) v.pageLimit, v.pageOffset, true, kJsonPageSyntax ), rw::graphCountFloorAttrJson( v.g ).c_str(), // §H4 §3.4 rw::renderDisclosure( v.prD, rw::DiscloseAs::JsonKeys ).c_str() ); // W2-F: ONE keyset - const std::vector via = impactViaColumn( v, v.page.begin, v.page.end ); // FE-B: the rows' hedge, per entry + const std::vector via = impactJsonVia( v ); // 2 = direct markdown link, 1 = via="name" const std::vector keys = impactOfDefKeys( v ); // attrib-multidef: each row's of_def printJsonSymbolRows( v.ing, v.show, v.page.begin, v.page.end, v.rootPrefix, v.testReach, &v.depth, &via, &keys ); rw::emitTo( stdout, "],\"import_reach\":[" ); @@ -2757,7 +2782,8 @@ int emitImpactXml( const ImpactView& v ) rw::emitTo( stdout, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, rw::depthRunAttrXml( v.show, v.depth, i, v.page.begin ), rw::isTestedByReach( v.ing, *v.testReach, v.show[i] ) ? " tested=\"1\"" : "", - viaNameAttr( rw::impactRowNameOnly( v.provenReach, v.show[i] ) ), // FE-B (D3): reached only through a name-only edge + viaEdgeAttr( rw::impactRowNameOnly( v.provenReach, v.show[i] ), + rw::impactDirectLink( v.g, v.seeds, v.depth, v.show[i] ) ), v.attrib.ofDefXml( v.show[i] ) ); // attrib-multidef } rw::emitImportRowsXml( stdout, v.ing, v.importPage, v.rootPrefix, v.importLazyPage ); @@ -2871,7 +2897,9 @@ std::optional runImpact( const MainDispatch& d ) + rw::importersNextLegend( !imports.next.empty() && !cfg.columnar ) ).c_str(), // cut-fix E: exactly when the root carries it ( rw::impactTsImportLegend( imports.importsUnresolved, imports.tsconfigUnread ) // #220: exactly when the root carries them + rw::viaNameLegend( std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, // FE-B: exactly when a shown row is via="name" - [ & ]( NodeId n ) { return rw::impactRowNameOnly( imProven, n ); } ), true, cfg.columnar ) ).c_str(), + [ & ]( NodeId n ) { return rw::impactRowNameOnly( imProven, n ); } ), true, cfg.columnar ) + + rw::viaLinkLegend( std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, + [ & ]( NodeId n ) { return rw::impactDirectLink( g, seeds, imDepth, n ); } ) ) ).c_str(), rw::testedLensLegend( cfg.columnar ), rw::kImpactTestedPartitionLegend, // A6: the columnar form reads its dense column rw::kTestedLensBlindSpotLegend, // F-02: rides with the partition rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Impact, imUnprovenDefs > 0 ).c_str(), // H1: exactly when the root carries unproven_defs= @@ -2894,7 +2922,7 @@ std::optional runImpact( const MainDispatch& d ) imports, importPage, importLazyPage, prD, imSingleRoot, imRootPrefix, imRootAttr, imSingleRoot ? cfg.roots[0] : std::string_view(), cfg.pageLimit, cfg.pageOffset, &imTestReach, imRadiusTested, imRadiusUntested, imDeclinedCalls, imDeclinedIface, g, imDepth, imByDepth, imValueRefs, - imProven, imFiles, imAttr, imListing, imWalk.typeOnly }; + imProven, imFiles, imAttr, imListing, imWalk.typeOnly, seeds }; if( cfg.columnar ) { return emitImpactColumnar( view ); } if( cfg.json ) { return emitImpactJson( view ); } diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index f2f46c056..d5239d971 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -463,6 +463,24 @@ sys.exit(0 if hit and d.get("via_link_note") else 1) ' && ok "find_symbol marks Install via=link and carries via_link_note" \ || no "find_symbol lacks via=link on Install or lacks via_link_note" +# A direct doc caller is via="link" on --safe-delete and --impact. The counts still include it. +mkdir -p "$TMP/runfix/src" "$TMP/runfix/docs" +printf '%s\n' 'def run():' ' return 1' '' 'def main():' ' run()' > "$TMP/runfix/src/app.py" +printf '%s\n' '# API' '' '## run' > "$TMP/runfix/docs/api.md" +printf '%s\n' '# Guide' '' '## Run Notes' '' 'see [run](api.md#run)' > "$TMP/runfix/docs/guide.md" +SD="$( $BIN "$TMP/runfix" --no-cache --safe-delete=run --legend=compact 2>/dev/null )" +printf '%s' "$SD" | grep -q 'callers="2"' \ + && printf '%s' "$SD" | grep -q 'n="Run Notes"[^>]*via="link"' \ + && ! printf '%s' "$SD" | grep -q 'n="main"[^>]*via="link"' \ + && ok "safe-delete marks Run Notes via=link and still counts both callers" \ + || no "safe-delete dropped the link mark or the caller count" +IM="$( $BIN "$TMP/runfix" --no-cache --impact=run --legend=compact 2>/dev/null )" +printf '%s' "$IM" | grep -q 'reaches="2"' \ + && printf '%s' "$IM" | grep -q 'n="Run Notes"[^>]*via="link"' \ + && ! printf '%s' "$IM" | grep -q 'n="main"[^>]*via="link"' \ + && ok "impact marks Run Notes via=link and still counts both rows" \ + || no "impact dropped the link mark or the reach count" + echo "--- warm == cold after editing ONLY the target file (the cached ref must stay unbound) ---" cp -R "$AFIX" "$TMP/anchorcopy" rm -f "$TMP/anchor.cache" From 31e0fc4002c90e8d62e0371a38ba5c5554c94fdf Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sun, 18 Oct 2026 21:28:11 +0300 Subject: [PATCH 10/28] Mark in-file anchors, wikilinks, and root-relative links with via="link". The mark follows the reference. A code call on the same pair stays unmarked, and an unpinned link no longer clears a pinned one. --- CHANGELOG.md | 2 +- src/graph.h | 31 +++++++++++++++++++++---------- test/mdsectioncheck.sh | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c80c69475..c5db32646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ link to a heading named like one of the linking file's own headings does not bin slug give one edge (the first). A missing heading falls back to the target file's edge; percent-encoded slugs and GitHub's `-1` duplicate suffix are not modelled and also fall back. A markdown link that no call shares is marked `via="link"` on `--callers`, `--callees`, `--path`, `--connect`, and on a map or body callee row. `count=` and `cross_kind` stay the same. A -call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. `--safe-delete` and `--impact` mark a direct doc caller `via="link"`. `callers=` and `reaches=` still count it. Gate: `test/mdsectioncheck.sh`. +call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. `--safe-delete` and `--impact` mark a direct doc caller `via="link"`. `callers=` and `reaches=` still count it. An in-file anchor, a wikilink, and a root-relative link are `via="link"` too. The mark follows the reference, so an unpinned link does not clear a pinned one on the same pair. Gate: `test/mdsectioncheck.sh`. ### Changed — Go methods carry their receiver type as scope (#403, slice 9a) diff --git a/src/graph.h b/src/graph.h index 3f7b600c0..b96d9dfde 100644 --- a/src/graph.h +++ b/src/graph.h @@ -6398,17 +6398,16 @@ inline bool isMarkdownLinkRef( const Reference& r ) noexcept return r.lang == Lang::Markdown && !r.isDocLink && !r.memberCall && !r.memberPath.empty(); } -// A pinned markdown link records the pair as a link. Any other site that binds the same pair records a real call, -// and the real call wins when the edge is stamped. -inline void noteMarkdownLinkSite( HashMap& linkEdges, HashMap& realCalls, std::uint64_t ekey, - const std::vector& mdLinkPin, std::size_t siteIndex, const Reference& r ) +// Link-ness is where the reference came from: lang Markdown and not a doc mention. isMarkdownLinkRef also requires a +// path, which an in-file anchor and a wikilink do not have, and a root-relative link can miss the pin. A non-markdown +// site on the same pair is a real call and wins at stamp time. A markdown doc mention is neither. +inline void noteMarkdownLinkSite( HashMap& linkEdges, HashMap& realCalls, std::uint64_t ekey, const Reference& r ) { - const bool linkSite = !mdLinkPin.empty() && siteIndex < mdLinkPin.size() && mdLinkPin[ siteIndex ] != kNoNode && isMarkdownLinkRef( r ); - if( linkSite ) + if( r.lang == Lang::Markdown && !r.isDocLink ) { linkEdges[ ekey ] = 1; } - else + else if( r.lang != Lang::Markdown ) { realCalls[ ekey ] = 1; } @@ -6977,9 +6976,9 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null // so its bucket layout cannot reach output, and it is filled inside a loop // whose per-reference tier work dominates the growth cascade by orders of // magnitude. A guessed reserve would be a made-up number in a hot struct. - HashMap linkEdges; // (from<<32|to) a pinned markdown link bound. A real call on the same pair + HashMap linkEdges; // (from<<32|to) a markdown link bound. A non-markdown call on the same pair // (realCallEdges) drops the mark: the merged edge stays a call. - HashMap realCallEdges; // (from<<32|to) bound by a site that was not a pinned markdown link. + HashMap realCallEdges; // (from<<32|to) bound by a non-markdown site. Empty on a tree with no markdown link. std::vector rule3Out; // reused Rule-3 output buffer (candidates from the single included file) Rule3FileMemo rule3Memo; // Rule 3's answer per (caller file, byName entry): one walk per name per file, not per call std::string qkey; // reused "qualifier::name" buffer for the E#4 canonical lookup (no per-ref alloc) @@ -7700,6 +7699,15 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null { PROFILE_SCOPE_DESCRIBE( "buildGraph/3: resolve loop (per reference)" ); const std::vector mdLinkPin = bindMarkdownLinks( ing ); // empty unless the tree holds a cross-file markdown link + bool anyMarkdownLink = false; + for( const Reference& r : ing.references ) + { + if( r.lang == Lang::Markdown && !r.isDocLink ) + { + anyMarkdownLink = true; + break; + } + } for( siteIndex = 0; siteIndex < ing.references.size(); ++siteIndex ) { const Reference& site = ing.references[ siteIndex ]; @@ -9164,7 +9172,10 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null finalSegmentEdges[ekey] = 1; // remember (from,to) for prov="final-segment" } ( nameOnly ? nameOnlyEdges : provenEdges )[ ekey ] = 1; // FE-B: a proven site wins the merged row (Graph::outNameOnly) - noteMarkdownLinkSite( linkEdges, realCallEdges, ekey, mdLinkPin, siteIndex, r ); + if( anyMarkdownLink ) + { + noteMarkdownLinkSite( linkEdges, realCallEdges, ekey, r ); + } } disposition = CallDisposition::Bound; } diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index d5239d971..ab539890c 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -481,6 +481,23 @@ printf '%s' "$IM" | grep -q 'reaches="2"' \ && ok "impact marks Run Notes via=link and still counts both rows" \ || no "impact dropped the link mark or the reach count" +# In-file anchors, wikilinks, and root-relative links are via="link". A code call on the same name stays unmarked. +mkdir -p "$TMP/kinds/src" "$TMP/kinds/docs" +printf '%s\n' 'def api():' ' return 1' '' 'def main():' ' api()' > "$TMP/kinds/src/app.py" +printf '%s\n' '# api' > "$TMP/kinds/docs/api.md" +printf '%s\n' '# Guide' '' '## Hub' '' '[down](#later)' '' '[a](api.md)' '' '[b](/docs/api.md)' '' '[[api]]' '' '## Later' '' 'notes' > "$TMP/kinds/docs/guide.md" +HUB="$( $BIN "$TMP/kinds" --no-cache --callees=Hub --legend=compact 2>/dev/null )" +printf '%s' "$HUB" | grep -q 'n="Later"[^>]*via="link"' \ + && printf '%s' "$HUB" | grep -q 'n="api"[^>]*via="link"' \ + && ok "in-file, wikilink, and root-relative links are via=link" \ + || no "an in-file, wikilink, or root-relative link lost via=link" +API="$( $BIN "$TMP/kinds" --no-cache --callers=api --legend=compact 2>/dev/null )" +printf '%s' "$API" | grep -q 'n="Hub"[^>]*via="link"' \ + && printf '%s' "$API" | grep -q 'n="main"' \ + && ! printf '%s' "$API" | grep -q 'n="main"[^>]*via="link"' \ + && ok "a code call to api stays unmarked while the doc link stays via=link" \ + || no "the code call was marked via=link, or the doc link lost the mark" + echo "--- warm == cold after editing ONLY the target file (the cached ref must stay unbound) ---" cp -R "$AFIX" "$TMP/anchorcopy" rm -f "$TMP/anchor.cache" From 6f3ffe4438dd3608eed9fd573dc13e6284a0cffe Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Sun, 18 Oct 2026 23:16:12 +0300 Subject: [PATCH 11/28] Stamp anchor="missing" when a markdown slug names no heading. Columnar is 2 for a direct link, and the do_work callers test has a real Python caller. --- CHANGELOG.md | 2 +- src/columnar.h | 22 +++- src/compactlegend.h | 6 +- src/forowner.h | 3 +- src/graph.h | 152 +++++++++++++++++++++++----- src/graphlegend.h | 22 ++++ src/ingest_docs.h | 2 +- src/main.cpp | 12 +-- src/mcpverbs.h | 34 +++++-- src/model.h | 2 + src/serialize.h | 146 +++++++++++++++++++------- src/verbs_for.h | 12 +-- src/verbs_navigate.h | 79 +++++++++++---- test/mdanchorfix/docs/a/use_work.py | 2 + test/mdsectioncheck.sh | 37 +++++-- 15 files changed, 411 insertions(+), 122 deletions(-) create mode 100644 test/mdanchorfix/docs/a/use_work.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c5db32646..9d51fcbe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ link to a heading named like one of the linking file's own headings does not bin slug give one edge (the first). A missing heading falls back to the target file's edge; percent-encoded slugs and GitHub's `-1` duplicate suffix are not modelled and also fall back. A markdown link that no call shares is marked `via="link"` on `--callers`, `--callees`, `--path`, `--connect`, and on a map or body callee row. `count=` and `cross_kind` stay the same. A -call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. `--safe-delete` and `--impact` mark a direct doc caller `via="link"`. `callers=` and `reaches=` still count it. An in-file anchor, a wikilink, and a root-relative link are `via="link"` too. The mark follows the reference, so an unpinned link does not clear a pinned one on the same pair. Gate: `test/mdsectioncheck.sh`. +call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. `--safe-delete` and `--impact` mark a direct doc caller `via="link"`. `callers=` and `reaches=` still count it. An in-file anchor, a wikilink, and a root-relative link are `via="link"` too. The mark follows the reference, so an unpinned link does not clear a pinned one on the same pair. A link whose file is in the tree and whose slug names no heading carries `anchor="missing"`. Columnar `` is `2` for a direct markdown link and `1` for a name-only edge. Gate: `test/mdsectioncheck.sh`. ### Changed — Go methods carry their receiver type as scope (#403, slice 9a) diff --git a/src/columnar.h b/src/columnar.h index 9bcef0e66..ade0c81f4 100644 --- a/src/columnar.h +++ b/src/columnar.h @@ -168,9 +168,9 @@ inline void emitColumnarDepthColumn( std::FILE* out, const std::vector& rw::emitTo( out, "{}", column ); } -// FE-B: the optional dense `` column — 1 where the row's edge is name-only (via="name"), one value per row in row -// order. Present only when some row carries it, so an answer with no hedged row pays 0 bytes; columnarViaField is its -// fields= suffix. +// The optional dense `` column — 1 where the row's edge is name-only (via="name"), 2 where it is a direct markdown +// link (via="link"), one value per row in row order. Present only when some row carries it, so an answer with no such +// row pays 0 bytes; columnarViaField is its fields= suffix. inline bool columnarViaPresent( const std::vector* viaName ) noexcept { return viaName != nullptr && std::any_of( viaName->begin(), viaName->end(), []( char c ) { return c != 0; } ); @@ -179,6 +179,20 @@ inline std::string_view columnarViaField( const std::vector* viaName ) noe { return columnarViaPresent( viaName ) ? ",via" : ""; } +inline void emitColumnarViaColumn( std::FILE* out, const std::vector& via, std::size_t rowCount ) +{ + rw::emitTo( out, "" ); + for( std::size_t i = 0; i < rowCount; ++i ) + { + if( i ) + { + std::fputc( ',', out ); + } + const unsigned char v = ( i < via.size() ) ? static_cast( via[ i ] ) : 0; + std::fputc( v <= 9 ? char( '0' + v ) : '0', out ); + } + rw::emitTo( out, "" ); +} // CALLSITE-AT (2026-10-09): the optional `` column — each row's call sites (editcheck.h rowCallSites), its tokens joined // by a space, XML-escaped with a value comma written , (kColumnarLegend's rule), an empty value for a row with none — @@ -291,7 +305,7 @@ inline void emitColumnarOptionalColumns( std::FILE* out, const IngestResult& ing } if( columnarViaPresent( viaName ) ) { - emitColumnarFlagColumn( out, "via", rows.size(), [ & ]( std::size_t i ) { return i < viaName->size() && ( *viaName )[ i ] != 0; } ); + emitColumnarViaColumn( out, *viaName, rows.size() ); } if( extras.atTokens != nullptr && extras.atTotals != nullptr ) { diff --git a/src/compactlegend.h b/src/compactlegend.h index 075aace41..6f6d6c9ce 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -253,6 +253,7 @@ inline constexpr std::string_view kCompactProsePrefixes[] = ""; inline constexpr std::string_view kMapViaLinkLegend = ""; +inline constexpr std::string_view kMapAnchorMissLegend = + ""; +inline std::string anchorMissLegendComment() +{ + return std::string( ""; +} // …and MCP's spelling: the same sentence as the `via_note` key of an answer one of whose entries carries "via":"name". inline std::string viaFactNoteJson( std::string text, std::string_view from, std::string_view jsonSpelling, std::string_view key ) { @@ -1255,6 +1273,10 @@ inline std::string viaLinkNoteJson() { return viaFactNoteJson( kViaLinkLegend, "via=\"link\"", "\\\"via\\\":\\\"link\\\"", "via_link_note" ); } +inline std::string anchorMissNoteJson() +{ + return viaFactNoteJson( kAnchorMissLegend, "anchor=\"missing\"", "\\\"anchor\\\":\\\"missing\\\"", "anchor_missing_note" ); +} // The same fact in --for's two dialects, which are prose strips joined by ';' and ':' rather than XML comment // clauses — one constant each so the wording cannot drift from kModScopeLegend above. Present-only, on that diff --git a/src/ingest_docs.h b/src/ingest_docs.h index b0eea75d7..d62f0c86f 100644 --- a/src/ingest_docs.h +++ b/src/ingest_docs.h @@ -319,7 +319,7 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi r.name.assign( base ); // The link as written (path + optional #slug; the slug is lowered, the path is not). It is a fact of THIS file alone, so it is // safe in the cache; the (file, slug) heading it names is resolved in buildGraph, where every file's - // headings exist (graph.h bindMarkdownLinks). memberPath is read beside memberCall / compose only. + // headings exist (graph.h bindMarkdownLinks). buildGraph reads this path. A member call still reads it beside memberCall. r.memberPath.assign( writtenTarget ); if( const std::size_t frag = r.memberPath.find( '#' ); frag != std::string::npos ) { diff --git a/src/main.cpp b/src/main.cpp index 03a0148ab..493b34c6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2041,7 +2041,7 @@ int runDefaultMap( const MainDispatch& d ) { return {}; } - serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ) ); + serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ), &g.outAnchorMiss ); const rw::MemoryStreamBytes measured = probe.finish(); return measured.isWhole ? std::string( measured.bytes ) : std::string(); }; @@ -2054,7 +2054,7 @@ int runDefaultMap( const MainDispatch& d ) DISCLOSE( maxTokensFit, rw::MapAnnotations::MaxTokensFit::DisclosureWhy::ProbeUnmeasured, "runDefaultMap: open_memstream failed for the --max-tokens fit probe — the map is emitted unshaped and its ceiling unverified" ); return 0; } - serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ) ); + serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ), &g.outAnchorMiss ); const rw::MemoryStreamBytes measured = probe.finish(); if( !measured.isWhole ) { @@ -2444,7 +2444,7 @@ int runDefaultMap( const MainDispatch& d ) { bodiesSection = rw::chargeSection( [ & ]( std::FILE* f ) { expandRecord = EmittedBodies{}; // a re-render starts from empty: the record is what THIS render emitted - packBodies( f, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, + packBodies( f, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/&expandRecord, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); }, // V1: octocode F2 sibs=/inc= rw::kBytesPerTokenBody ); @@ -2826,7 +2826,7 @@ int runDefaultMap( const MainDispatch& d ) // not as the literal source token, for that same reason (the count is a naive grep over this file). if( !expandNodes.empty() && noteAppliesToBundle ) // !serveWholeFile is this whole branch's precondition (see the if above) { - emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, + emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); } ); bodiesEmittedEarly = true; @@ -2854,7 +2854,7 @@ int runDefaultMap( const MainDispatch& d ) } else { - serialize( out, ing, rank, g.outOff, g.outTargets, mapTopK, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, &mapEstTokens, payloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ) ); + serialize( out, ing, rank, g.outOff, g.outTargets, mapTopK, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, &mapEstTokens, payloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ), &g.outAnchorMiss ); } } else @@ -2900,7 +2900,7 @@ int runDefaultMap( const MainDispatch& d ) } if( !expandNodes.empty() && !serveWholeFile && !bodiesEmittedEarly ) // #289: already served ahead of the map below { - emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, + emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); } ); // L3: --expand bodies surface notes; V1: sibs=/inc= } diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 880d02c8c..5c4612e56 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -454,7 +454,7 @@ inline std::string analyzeToString( const std::string& root, int topK, bool stab // §B4 family, and mcpclidiffcheck is the gate that keeps the two surfaces one. /*ann=*/rw::MapAnnotations{ .prDisclosure = ix.prDisclosure, .codeFirstRows = ix.isCleanWorkingSet, .inventoryTier = ix.isCleanWorkingSet }, /*statsFirstScreen=*/true, anRootArg, &ix.g.locPinOut, ix.g.externalCalls, &ix.g.declinedOut, ix.g.gateDeclinedCalls, - ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls, rw::linkOutPtr( ix.g ) ); } ); + ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls, rw::linkOutPtr( ix.g ), &ix.g.outAnchorMiss ); } ); } // `rank_by` verb (lane/t10-mcp-coverage): the MCP twin of --rank-by=pagerank|authority|hub|rrf — the SAME @@ -522,7 +522,7 @@ inline std::string rankByText( const std::string& root, std::string_view mode, i /*extraPayloadTokens=*/0, /*ann=*/rw::MapAnnotations{ .rankByLabel = rankByLabel, .prDisclosure = disclosure, .codeFirstRows = rankByLabel == nullptr, .inventoryTier = rankByLabel == nullptr }, // pagerank: the map scope's code-first pick /*statsFirstScreen=*/true, rbRootArg, &ix.g.locPinOut, ix.g.externalCalls, &ix.g.declinedOut, ix.g.gateDeclinedCalls, - ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls, rw::linkOutPtr( ix.g ) ); } ); + ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls, rw::linkOutPtr( ix.g ), &ix.g.outAnchorMiss ); } ); } // ─── the cross-branch + dark-content MCP twins (`whereis`, `stray_content`, `flags`) ─── @@ -755,6 +755,7 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& bool anyVia = false; // FE-B: an entry carried "via":"name" — the answer then carries via_note, its reading bool anyLink = false; // an entry carried "via":"link" — the answer then carries via_link_note + bool anyMiss = false; // an entry carried "anchor":"missing" — the answer then carries anchor_missing_note const std::span callerMatches( referencingOnly ? chRows.matches : chCallers.matches ); const std::span calleeMatches( chRows.matches ); NodeId linkRow = kNoNode; @@ -764,6 +765,7 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& { anyVia = anyVia || via; const char* linkFrag = linkJsonFrag( g, callerMatches, calleeMatches, id, linkRow, linkToward, anyVia, anyLink ); + const char* missFrag = anchorMissJsonFrag( g, callerMatches, calleeMatches, id, linkRow, linkToward, anyMiss ); const Symbol& s = ing.symbols[id]; // T4: attach the stable content-handle so the agent can `fetch_body{handle}` instead of us re-sending // the body. Names/signatures by default (this verb); bodies by handle on request. @@ -778,6 +780,7 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& + ( rw::isTestedByReach( ing, chTested.testReach, id ) ? ",\"tested\":true" : "" ) + ( via ? ",\"via\":\"name\"" : "" ) // FE-B: the CLI row's via="name", the same edge bit (defattrib.h, one pass) + linkFrag + + missFrag + attrib // attrib-multidef: the CLI row's of_def= / prov="split" + "}"; }; @@ -892,6 +895,10 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& { out += rw::viaLinkNoteJson(); } + if( anyMiss ) + { + out += rw::anchorMissNoteJson(); + } out += nextFieldJson( splitArmLegend( anySplit ), "split_note" ); // attrib-multidef Q1: likewise for "prov":"split" out += declinedSitesJson( sqDc, referencingOnly, /*mcp=*/true ); // lane declined-sites: the CLI rows, never in calls/calledBy or count out += nextFieldJson( declinedSitesLegend( declinedSitesLegendFlags( sqDc, referencingOnly, sqDcCut ), /*mcp=*/true ), "declined_note" ); // their reading, only when they ride @@ -2226,7 +2233,7 @@ inline McpForAssembly assembleMcpFor( const McpForAssemblyInputs& in, std::strin share = forSoftSectionsShare( budgetBytesForTokens( budgetTokens ), mcpHdrXml.size() + atCut + legoStr.size() + composeStr.size() + routeStr.size() ); } mcpSlot = renderForBodySlot( BodySlotInputs{ ing, in.g.outOff, in.g.outTargets, in.g.outNameOnly, lensRank, flRootArg, redact, notesPtr, - /*viaLegendInHead=*/false, /*compress=*/false, &in.g.outProv, /*splitLegendInHead=*/false, linkOutPtr( in.g ) }, + /*viaLegendInHead=*/false, /*compress=*/false, &in.g.outProv, /*splitLegendInHead=*/false, linkOutPtr( in.g ), &in.g.outAnchorMiss }, in.slotOwner, share ); } mcpForSoftRefill( in, renderSigs, sigsStr, mcpSigsCut, &mcpSigsNext, @@ -3180,7 +3187,7 @@ inline std::optional impactText( const std::string& root, const std ( std::string( kImpactImportTierLegend ) + importersNextLegend( !imports.next.empty() ) ).c_str(), // cut-fix E, as on the CLI ( impactTsImportLegend( imports.importsUnresolved, imports.tsconfigUnread ) // #220: exactly when the root carries them, as on the CLI + viaNameLegend( imHasVia, true ) - + viaLinkLegend( imHasLink ) ).c_str(), + + viaLinkLegend( imHasLink ) + impactAnchorLegendFor( g, seeds, depth, show, ipw.begin, ipw.end ) ).c_str(), kTestedRowLegend, kImpactTestedPartitionLegend, // A6 kTestedLensBlindSpotLegend, // F-02: rides with the partition, byte-identical to the CLI twin unprovenDefsVerbLegend( UnprovenDefsVerb::Impact, unprovenDefs > 0 ).c_str(), // H1: exactly when the root carries unproven_defs=, as on the CLI @@ -3221,7 +3228,7 @@ inline std::optional impactText( const std::string& root, const std // A6: tested="1" only (never a literal 0) — see kTestedRowLegend. 0.6.5: d= as on the CLI row. rw::emitTo( mem, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, depthRunAttrXml( show, depth, i, ipw.begin ), isTestedByReach( ing, impTestReach, show[i] ) ? " tested=\"1\"" : "", - viaEdgeAttr( impactRowNameOnly( proven, show[i] ), impactDirectLink( g, seeds, depth, show[i] ) ), imAttr.ofDefXml( show[i] ) ); } + impactLinkAttrs( g, proven, seeds, depth, show[ i ] ), imAttr.ofDefXml( show[i] ) ); } // the import tier's rows, after the symbol rows and under their own tag — a different unit, so a // different element (see the CLI arm and kImpactImportTierLegend for why they are never one number). emitImportRowsXml( mem, ing, std::span( imports.files ).first( imports.shown ), imRootPrefix, @@ -3653,11 +3660,13 @@ inline std::optional pathText( const std::string& root, const std:: const PathSearchGaps ptGaps = pth.empty() ? pathSearchGaps( ing, g, srcDefs, valueRefIndexOf( ix ) ) : PathSearchGaps{}; const bool ptHasVia = pathHasNameOnlyHop( g, pth ); // FE-B: exactly when a hop of this path is via="name", as on the CLI const bool ptHasLink = pathHasLinkHop( g, pth ); + const bool ptHasMiss = std::adjacent_find( pth.begin(), pth.end(), + [ & ]( NodeId from, NodeId to ) { return edgeAnchorMiss( g, from, to ); } ) != pth.end(); const PathEndpointBinding ptBound = pathEndpointBinding( g, endpointDefinitionCount( ing, srcDefs ), endpointDefinitionCount( ing, dstDefs ), pth ); // attrib-multidef, as on the CLI const bool ptHasSplit = ptBound.splitHop; rw::emitTo( mem, "{}", ( unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + viaNameLegend( ptHasVia ) - + viaLinkLegend( ptHasLink ) + + viaLinkLegend( ptHasLink ) + anchorMissLegend( ptHasMiss ) + splitArmLegend( ptHasSplit ) + pathUnboundLegend( !ptBound.from || !ptBound.to ) ).c_str(), ( std::string( toValueRefsLegend( ptToValueRefs.count > 0 ) ) + valueRefsDepthLegend( ptToValueRefs.depthCut.files > 0 ) ).c_str(), pathGapsLegend( ptGaps.any() ), @@ -3678,7 +3687,8 @@ inline std::optional pathText( const std::string& root, const std:: { const Symbol& s = ing.symbols[ pth[i] ]; const std::string_view rp = ptSingleRoot ? sarif::rootRelativeUri( ing.files[ s.fileId ], ptRootPrefix ) : std::string_view( ing.files[ s.fileId ] ); rw::emitTo( mem, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, - viaEdgeAttr( i > 0 && edgeNameOnly( g, pth[ i - 1 ], pth[ i ] ), i > 0 && edgeIsLink( g, pth[ i - 1 ], pth[ i ] ) ), splitArmAttrXml( pathHopSplitArm( g, pth, i ) ) ); } // FE-B; a link hop says via="link" + std::string( viaEdgeAttr( i > 0 && edgeNameOnly( g, pth[ i - 1 ], pth[ i ] ), i > 0 && edgeIsLink( g, pth[ i - 1 ], pth[ i ] ) ) ) + + anchorMissAttr( i > 0 && edgeAnchorMiss( g, pth[ i - 1 ], pth[ i ] ) ), splitArmAttrXml( pathHopSplitArm( g, pth, i ) ) ); } // FE-B; a link hop says via="link" rw::emitRaw( mem, "" ); return mcpAnswerText( stream ); // nullopt = the buffer lost bytes (dispatch answers -32603), "" stays not-found } @@ -3946,8 +3956,13 @@ inline void packConnect( std::FILE* out, const IngestResult& ing, const Graph& g { return grp.terminals.size() > 1 && std::any_of( grp.edges.begin(), grp.edges.end(), [ & ]( const ConnectEdge& e ) { return edgeIsLink( g, e.from, e.to ); } ); } ); + const bool connectHasMiss = std::any_of( res.groups.begin(), res.groups.end(), [ & ]( const ConnectGroup& grp ) + { + return grp.terminals.size() > 1 && std::any_of( grp.edges.begin(), grp.edges.end(), [ & ]( const ConnectEdge& e ) { return edgeAnchorMiss( g, e.from, e.to ); } ); + } ); const std::string connectViaLegend = ( connectHasVia ? "" : std::string() ) - + ( connectHasLink ? "" : std::string() ); + + ( connectHasLink ? "" : std::string() ) + + ( connectHasMiss ? "" : std::string() ); const std::size_t connectExtraBytes = connectRootAttr.size() + std::strlen( rootRelPathsLegend( !rootArg.empty() ) ) + connectUnindexedLegend.size() + connectUnprovenAttr.size() + connectUnprovenLegend.size() + connectAmbiguousAttr.size() + connectViaLegend.size(); @@ -4099,7 +4114,8 @@ inline void packConnect( std::FILE* out, const IngestResult& ing, const Graph& g { payload.append( "" ); // FE-B, or via="link" + .append( viaEdgeAttr( edgeNameOnly( g, e.from, e.to ), edgeIsLink( g, e.from, e.to ) ) ) + .append( anchorMissAttr( edgeAnchorMiss( g, e.from, e.to ) ) ).append( "/>" ); // FE-B, or via="link" } payload.append( "" ); } diff --git a/src/model.h b/src/model.h index b861763a9..f2deaa3e3 100644 --- a/src/model.h +++ b/src/model.h @@ -822,6 +822,8 @@ struct Reference // FE-B (test/receiverevidencecheck.sh): the rest of a member call's receiver chain, for the receiver-evidence rule // (graph.h ReceiverEvidence). memberPath is the member names BETWEEN memberRoot and the callee, '.'-joined — // "bucket" for `this.bucket.listSchemas()`, "req.raw.headers" for `ctx.req.raw.headers.get()`, "" for `root.m()`. + // A markdown link stores the path and slug as written here (the slug lowercased). memberCall stays false. + // buildGraph reads it (bindMarkdownLinks). // memberCtor is the class a CONSTRUCTED receiver names, final segment — "Reply" for `new Reply( r ).send()`, Go // "Merger" for `(&Merger{}).Len()` — with memberRoot "" (a construction has no root identifier). Both "" for every // other shape and every language that records no member-call shape. diff --git a/src/serialize.h b/src/serialize.h index e70d13d0e..2c22a9a2b 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -2640,9 +2640,16 @@ inline std::uint32_t counterAt( const std::vector* v, NodeId id ) // written ONCE, at its first edge, with x="N": the count, and --callees on the lists each candidate with its file // (graphlegend.h kMapViaNameLegend / compactlegend.h's x row). Every other row, prov="split" arms included, is written as // before. `scratch` is reused across symbols. +inline void writeMapAnchorAttr( XmlWriter& w, const std::vector* bits, std::uint32_t e ) +{ + if( bits != nullptr && e < bits->size() && ( *bits )[ e ] != 0 ) + { + w.write( "\" anchor=\"missing" ); + } +} inline void writeMapCalleeRows( XmlWriter& w, const IngestResult& ing, std::uint32_t from, std::uint32_t to, const std::vector& outTargets, const std::vector* outProv, const std::vector* outNameOnly, std::vector& esc, - std::vector& scratch, const std::vector* outLink = nullptr ) + std::vector& scratch, const std::vector* outLink = nullptr, const std::vector* outAnchor = nullptr ) { const auto provOf = [ & ]( std::uint32_t e ) -> std::uint8_t { return ( outProv && e < outProv->size() ) ? ( *outProv )[ e ] : 0; }; const auto nameOnly = [ & ]( std::uint32_t e ) { return outNameOnly && e < outNameOnly->size() && ( *outNameOnly )[ e ] != 0; }; @@ -2702,14 +2709,15 @@ inline void writeMapCalleeRows( XmlWriter& w, const IngestResult& ing, std::uint w.write( "\" x=\"" ); w.write( std::to_string( repeats ) ); } + writeMapAnchorAttr( w, outAnchor, e ); w.write( "\"/>" ); } } -// A row under one of `nodes` may carry via="link" when any of their out-edges is a markdown link. Same over-approximation -// as namesOnlyOutAny, asked before the rows are written so the map's first-screen legend can define the attribute. -inline bool linksOutAny( const std::vector& outOff, const std::vector& outLink, - std::span nodes ) noexcept +// A row under one of `nodes` may carry the bit when any of their out-edges has it. Asked before the rows are written +// so the map's first-screen legend can define the attribute. outLink and outNameOnly share this scan. +inline bool markedOutAny( const std::vector& outOff, const std::vector& bits, + std::span nodes ) noexcept { for( const NodeId n : nodes ) { @@ -2717,9 +2725,9 @@ inline bool linksOutAny( const std::vector& outOff, const std::ve { continue; } - for( std::uint32_t e = outOff[ n ]; e < outOff[ n + 1 ] && e < outLink.size(); ++e ) + for( std::uint32_t e = outOff[ n ]; e < outOff[ n + 1 ] && e < bits.size(); ++e ) { - if( outLink[ e ] != 0 ) + if( bits[ e ] != 0 ) { return true; } @@ -2727,6 +2735,11 @@ inline bool linksOutAny( const std::vector& outOff, const std::ve } return false; } +inline bool linksOutAny( const std::vector& outOff, const std::vector& outLink, + std::span nodes ) noexcept +{ + return markedOutAny( outOff, outLink, nodes ); +} // FE-B: may a row under one of `nodes` carry via="name"? True when any of their out-edges is name-only — an // over-approximation of what a section renders (a block's 16-per-symbol cap may cut that row; a merged overload // row prints one representative's edges), asked BEFORE the rows are written by a surface whose first-screen legend must @@ -2734,21 +2747,7 @@ inline bool linksOutAny( const std::vector& outOff, const std::ve inline bool namesOnlyOutAny( const std::vector& outOff, const std::vector& outNameOnly, std::span nodes ) noexcept { - for( const NodeId n : nodes ) - { - if( std::size_t( n ) + 1 >= outOff.size() ) - { - continue; - } - for( std::uint32_t e = outOff[ n ]; e < outOff[ n + 1 ] && e < outNameOnly.size(); ++e ) - { - if( outNameOnly[ e ] != 0 ) - { - return true; - } - } - } - return false; + return markedOutAny( outOff, outNameOnly, nodes ); } // attrib-multidef Q1: may a row under one of `nodes` carry prov="split"? True when any of their out-edges is one arm of a @@ -2809,7 +2808,8 @@ inline void writeMapInventoryXml( XmlWriter& w, const IngestResult& ing, const M const std::vector& outOff, const std::vector& outTargets, const std::vector* outProv, const std::vector* outNameOnly, std::vector& esc, std::vector& scratch, - const std::vector* outLink = nullptr ) + const std::vector* outLink = nullptr, + const std::vector* outAnchor = nullptr ) { EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( "" ); - writeMapCalleeRows( w, ing, outOff[ id ], outOff[ id + 1 ], outTargets, outProv, outNameOnly, esc, scratch, outLink ); + writeMapCalleeRows( w, ing, outOff[ id ], outOff[ id + 1 ], outTargets, outProv, outNameOnly, esc, scratch, outLink, outAnchor ); w.write( "" ); } for( const MapInventoryDir& d : inv.dirs ) @@ -2923,7 +2923,8 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto std::size_t inertDeclinedCalls = 0, // Markdown link edges (Graph::outLink) → via="link" on a row and kMapViaLinkLegend. nullptr/empty // ⇒ the map has no link edge and stays byte-identical. - const std::vector* outLink = nullptr ) + const std::vector* outLink = nullptr, + const std::vector* outAnchor = nullptr ) { const std::size_t* changedCount = ann.changedCount; const std::string* mapAtStamp = ann.atStamp; @@ -3183,6 +3184,11 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto { legend += kMapViaLinkLegend; } + if( outAnchor && !stubbed && !ann.viaLegendStripped + && markedOutAny( outOff, *outAnchor, std::span( order.data(), std::min( keep, order.size() ) ) ) ) + { + legend += kMapAnchorMissLegend; + } // C1 DRIFT FIX (Round C lane B, found by re-reading this header's own output). `precise=` means "how many // out-edges a SCIP index PINNED", and the emitter's own comment below says it is "emitted ONLY under // --scip". Both were true when outProv held only {0, 1}. A4-R5 then added value 2 (an FFI binding edge) @@ -3477,7 +3483,7 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto { if( inventory.active ) { - writeMapInventoryXml( w, ing, inventory, pathRel, outOff, outTargets, outProv, outNameOnly, esc, hedgedScratch, outLink ); + writeMapInventoryXml( w, ing, inventory, pathRel, outOff, outTargets, outProv, outNameOnly, esc, hedgedScratch, outLink, outAnchor ); } }; const bool inventoryFirst = !stable && effImportantLast; // --stable's path order wins, as it does for the rows @@ -3698,7 +3704,7 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto } w.write( ">" ); - writeMapCalleeRows( w, ing, outOff[id], outOff[id + 1], outTargets, outProv, outNameOnly, esc, hedgedScratch, outLink ); + writeMapCalleeRows( w, ing, outOff[id], outOff[id + 1], outTargets, outProv, outNameOnly, esc, hedgedScratch, outLink, outAnchor ); w.write( "" ); } w.write( "" ); @@ -6580,6 +6586,7 @@ struct CalleeCallsSink const std::vector* prov = nullptr; bool* splitLegendCharged = nullptr; const std::vector* link = nullptr; // Graph::outLink: via="link" on a callee row. nullptr ⇒ none + const std::vector* anchor = nullptr; // Graph::outAnchorMiss: anchor="missing" on that row. nullptr ⇒ none }; // The wrapper, written in front of the rows it describes (they have to be walked before `shown` @@ -6726,17 +6733,19 @@ struct MergedCalleeNameRow bool via = false; // FE-B: these edges are name-only — merged only with rows of the same bit bool split = false; // attrib-multidef Q1: these edges are split arms — likewise bool link = false; // a markdown link — merged only with other link rows of the same name + bool miss = false; // that link named no heading — merged only with other miss rows of the same name }; inline void collectCalleeNameRow( std::vector& rows, const Symbol& cs, - std::size_t& used, const CalleeCallsSink& sink, bool via = false, bool split = false, bool link = false ) + std::size_t& used, const CalleeCallsSink& sink, bool via = false, bool split = false, bool link = false, + bool miss = false ) { char lb[ 16 ]; rw::formatTo( lb, sizeof( lb ), "{}", cs.line ); bool merged = false; for( MergedCalleeNameRow& r : rows ) { - if( r.name == cs.name && r.via == via && r.split == split && r.link == link ) + if( r.name == cs.name && r.via == via && r.split == split && r.link == link && r.miss == miss ) { r.lines.push_back( cs.line ); merged = true; break; @@ -6744,11 +6753,11 @@ inline void collectCalleeNameRow( std::vector& rows, const } if( !merged ) { - rows.push_back( MergedCalleeNameRow { cs.name, { cs.line }, via, split, link } ); + rows.push_back( MergedCalleeNameRow { cs.name, { cs.line }, via, split, link, miss } ); } // the comma and the digits a merge appends, or the whole row it opens (FE-B: plus its 11-byte via="name"; Q1: its 13-byte // prov="split"; a link row's via="link" is the same 11 bytes) - used += merged ? std::strlen( lb ) + 1 : cs.name.size() + 16 + ( ( via || link ) ? 11u : 0u ) + ( split ? 13u : 0u ); + used += merged ? std::strlen( lb ) + 1 : cs.name.size() + 16 + ( ( via || link ) ? 11u : 0u ) + ( split ? 13u : 0u ) + ( miss ? 17u : 0u ); if( sink.recorded ) { sink.recorded->push_back( EmittedBodyCall { cs.name, cs.line, std::string() } ); // §H5: no sig to record @@ -6776,6 +6785,7 @@ inline void appendMergedCalleeNameRows( std::string& callsBody, std::vector* prov = nullptr; bool* splitLegendCharged = nullptr; const std::vector* link = nullptr; + const std::vector* anchor = nullptr; CalleeEdgeBytes( const std::vector& nameOnlyBytes, const std::vector* provBytes = nullptr, // NOLINT: implicit bool* charged = nullptr, const std::vector* linkBytes = nullptr ) noexcept : nameOnly( nameOnlyBytes ), prov( provBytes ), splitLegendCharged( charged ), link( linkBytes ) {} }; +inline CalleeEdgeBytes withAnchorEdge( CalleeEdgeBytes edges, const std::vector* anchor ) noexcept +{ + edges.anchor = anchor; + return edges; +} // …and which of their readings the caller's own first-screen legend already carries (FE-B's via="name", Q1's prov="split"), so // the section neither repeats nor charges them. Built implicitly from the via bit alone, as every pre-Q1 caller passes it. struct CalleeLegendsInHead @@ -6824,9 +6840,11 @@ inline CalleeCallsSink withSplitArms( CalleeCallsSink sink, const std::vector* link ) noexcept +inline CalleeCallsSink withLinkBits( CalleeCallsSink sink, const std::vector* link, + const std::vector* anchor = nullptr ) noexcept { - sink.link = link; + sink.link = link; + sink.anchor = anchor; return sink; } @@ -6851,6 +6869,48 @@ inline std::string_view calleeRowSplitAttr( const std::vector& ou return split ? std::string_view( " prov=\"split\"" ) : std::string_view(); } +// A markdown link whose slug names no heading. Charges the legend once, marks a merged name row, or +// inserts the attribute into a signature row that already ends at `>`. +inline void applyCalleeAnchorMiss( const CalleeCallsSink& sink, const std::vector& outOff, + const std::vector& outTargets, NodeId id, NodeId cid, bool& legendCharged, + std::size_t& used, std::vector* nameRows, std::string_view name, + bool linkRow, std::string* callsBody ) +{ + const bool miss = sink.anchor != nullptr && edgeNameOnlyInCsr( outOff, outTargets, *sink.anchor, id, cid ); + if( !miss ) + { + return; + } + if( !legendCharged ) + { + used += anchorMissLegendComment().size(); + legendCharged = true; + } + if( nameRows != nullptr ) + { + for( MergedCalleeNameRow& nr : *nameRows ) + { + if( nr.name == name && nr.link == linkRow && !nr.miss ) + { + nr.miss = true; + used += 17; + return; + } + } + return; + } + if( callsBody == nullptr ) + { + return; + } + const std::size_t cut = callsBody->rfind( '>' ); + if( cut != std::string::npos ) + { + callsBody->insert( cut, " anchor=\"missing\"" ); + used += 17; + } +} + // §P10.1: the disclosed block // for one body's 1-hop callee signatures — extracted out of packBodies so the disclosure logic doesn't // inflate packBodies' own complexity/LOC. `total` is outOff[id+1]-outOff[id] — outTargets is deduped-per- @@ -6881,6 +6941,7 @@ inline void emitCalleeCallsBlock( std::string& out, NodeId id, const std::vector std::vector nameRows; // names-only rendering: collected, merged by name, written after the walk int shown = 0; bool linkLegendCharged = false; + bool anchorLegendCharged = false; for( std::uint32_t k = outOff[id]; k < outOff[id + 1] && shown < int( sink.rowCap ) && used < budgetBytes; ++k ) { const NodeId cid = walk[ k - outOff[id] ]; @@ -6895,6 +6956,7 @@ inline void emitCalleeCallsBlock( std::string& out, NodeId id, const std::vector used += viaLinkLegendComment().size(); linkLegendCharged = true; } + applyCalleeAnchorMiss( sink, outOff, outTargets, id, cid, anchorLegendCharged, used, nullptr, {}, false, nullptr ); const bool nameRow = !linkRow && calleeRowVia( outOff, outTargets, id, cid, used, sink ); const std::string_view viaAttr = viaEdgeAttr( nameRow, linkRow ); const std::string_view splitAttr = calleeRowSplitAttr( outOff, outTargets, id, cid, !viaAttr.empty(), used, sink ); // Q1: prov="split" or "" @@ -6903,6 +6965,7 @@ inline void emitCalleeCallsBlock( std::string& out, NodeId id, const std::vector if( sink.namesOnly ) { collectCalleeNameRow( nameRows, cs, used, sink, nameRow, !splitAttr.empty(), linkRow ); + applyCalleeAnchorMiss( sink, outOff, outTargets, id, cid, anchorLegendCharged, used, &nameRows, cs.name, linkRow, nullptr ); ++shown; continue; } @@ -6919,6 +6982,7 @@ inline void emitCalleeCallsBlock( std::string& out, NodeId id, const std::vector } char hb[ 64 ]; rw::formatTo( hb, sizeof( hb ), "\" l=\"{}\"{}{}>", cs.line, viaAttr, splitAttr ); callsBody += "calls : nullptr, /*namesOnly=*/false, calleeRank, &outNameOnly, - &viaLegendCharged }, outEdges.prov, &splitLegendCharged ), outEdges.link ) ); + &viaLegendCharged }, outEdges.prov, &splitLegendCharged ), outEdges.link, outEdges.anchor ) ); const std::string bodyNotes = renderNoteChildren( noteIndex, symbolNoteTarget( noteIndex, ing, s ), esc ); // L3/D5 piece += bodyNotes; used += bodyNotes.size(); // W3-N2: same charge-never-trim rule @@ -7683,6 +7747,10 @@ inline void packBodies( std::FILE* out, const IngestResult& ing, const std::vect { w.write( viaLinkLegendComment() ); } + if( children.find( " anchor=\"missing\"" ) != std::string::npos ) + { + w.write( anchorMissLegendComment() ); + } if( !legendsInHead.split && children.find( " prov=\"split\"" ) != std::string::npos ) { w.write( splitArmCallsLegendComment() ); // attrib-multidef Q1: the same rule, for a row's prov="split" @@ -7782,7 +7850,7 @@ inline OwnerHopRows packOwnerHopRows( const IngestResult& ing, const std::vector used += rowFixed; emitCalleeCallsBlock( row, id, outOff, outTargets, ing, contentOf, esc, used, budget, withLinkBits( withSplitArms( CalleeCallsSink{ redact, /*recorded=*/nullptr, /*namesOnly=*/true, rank, &outNameOnly, &via, - kForOwnerHopNameGuard, next }, outEdges.prov, outEdges.splitLegendCharged ), outEdges.link ) ); + kForOwnerHopNameGuard, next }, outEdges.prov, outEdges.splitLegendCharged ), outEdges.link, outEdges.anchor ) ); out.xml += row; out.xml += ""; ++out.shown; @@ -7906,7 +7974,7 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector // owner-hop: the leading owner rows first (THE QUESTION'S OWNER ROWS above; nothing when `owners` is null), then the // ranked rows exactly as before, from the node the owners end at, on the budget and via charge the owners leave them - const OwnerHopRows own = packOwnerHopRows( ing, nodes, owners, outOff, outTargets, CalleeEdgeBytes( outNameOnly, outEdges.prov, &splitLegendCharged, outEdges.link ), + const OwnerHopRows own = packOwnerHopRows( ing, nodes, owners, outOff, outTargets, withAnchorEdge( CalleeEdgeBytes( outNameOnly, outEdges.prov, &splitLegendCharged, outEdges.link ), outEdges.anchor ), redact, rank, rootArg, budgetBytes, viaLegendCharged, pathRel, contentOf, esc ); std::string children = own.xml; std::size_t requestedCount = own.shown; @@ -7959,7 +8027,7 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector // running `used` so the row identity bytes and the edge bytes share one budget. emitCalleeCallsBlock( row, id, outOff, outTargets, ing, contentOf, esc, used, budgetBytes, withLinkBits( withSplitArms( CalleeCallsSink{ redact, /*recorded=*/nullptr, /*namesOnly=*/true, rank, &outNameOnly, &viaLegendCharged }, - outEdges.prov, &splitLegendCharged ), outEdges.link ) ); + outEdges.prov, &splitLegendCharged ), outEdges.link, outEdges.anchor ) ); row += ""; children += row; ++shownCount; @@ -7979,6 +8047,10 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector { w.write( viaLinkLegendComment() ); } + if( children.find( " anchor=\"missing\"" ) != std::string::npos ) + { + w.write( anchorMissLegendComment() ); + } if( !legendsInHead.split && children.find( " prov=\"split\"" ) != std::string::npos ) { w.write( splitArmCallsLegendComment() ); // attrib-multidef Q1: exactly when a hop's row carries prov="split" diff --git a/src/verbs_for.h b/src/verbs_for.h index 5acd0db66..cea67a1ee 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -1981,7 +1981,7 @@ ForAutoBodiesResult buildForAutoBodies( const rw::Config& cfg, const rw::IngestR // shell the "budget" branch below already gets for free, rather than a second hand-rolled tag. out.attr = " bundle=\"auto\" bodies=\"0\" reason=\"no_candidates\""; out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packBodies( f, ing, autoBodyIds, /*budgetBytes=*/1, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, + { rw::packBodies( f, ing, autoBodyIds, /*budgetBytes=*/1, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, /*ranges=*/nullptr, /*noteIndex=*/nullptr, nullptr, /*truncateOversizedFirst=*/false, /*withFileContext=*/false, fabRootArg, &lensRank, rw::CalleeLegendsInHead( viaLegendInHead, splitLegendInHead ) ); }, rw::kBytesPerTokenBody ); @@ -2003,7 +2003,7 @@ ForAutoBodiesResult buildForAutoBodies( const rw::Config& cfg, const rw::IngestR const std::size_t autoBodyBudget = std::max( 1, std::min( leftBytes, cfg.packBudgetBytes ) ); rw::EmittedBodies autoEmitted; out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packBodies( f, ing, autoBodyIds, autoBodyBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, + { rw::packBodies( f, ing, autoBodyIds, autoBodyBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, /*ranges=*/nullptr, /*noteIndex=*/nullptr, &autoEmitted, /*truncateOversizedFirst=*/false, /*withFileContext=*/false, fabRootArg, &lensRank, rw::CalleeLegendsInHead( viaLegendInHead, splitLegendInHead ) ); }, rw::kBytesPerTokenBody ); @@ -2409,7 +2409,7 @@ ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::Ingest const rw::HopOwners owners = forHopOwners( cfg, ing, ownerPlan, leftBytes, fcRootArg ); out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packHops( f, ing, hopNodes, hopBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), redactPtr, &lensRank, fcRootArg, + { rw::packHops( f, ing, hopNodes, hopBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), redactPtr, &lensRank, fcRootArg, rw::CalleeLegendsInHead( viaLegendInHead, promised.splitLegendInHead ), &owners ); }, // g.outNameOnly also feeds the hop-slot rule (serialize.h noProvenEdge) // MARKUP rate, not the body rate — and this is an honesty choice, not a copy-paste slip. The body // rate (3.80 B/tok) prices SOURCE TEXT; the compact section contains none, only tags, identifiers @@ -3725,7 +3725,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co detailBodyBudget = std::min( detailBodyBudget, std::max( leftBytes, detailFloorBudget ) ); } detailSection = rw::chargeSection( [ & ]( std::FILE* f ) - { packBodies( f, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), cfg.compress, redactPtr, + { packBodies( f, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, /*ranges=*/nullptr, notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/false, flRootArg, &lensRank, rw::CalleeLegendsInHead( headerParts.viaPresent, headerParts.splitPresent ) ); }, // L3: --detail bodies surface notes too (part of the --for bundle) rw::kBytesPerTokenBody ); @@ -3779,7 +3779,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co { const std::size_t slotSpent = rowsSpentBytes + plan.attrReserve + enrich.section.xml.size(); // the rows at the cut + the hops slot = rw::renderForBodySlot( rw::BodySlotInputs{ ing, g.outOff, g.outTargets, g.outNameOnly, lensRank, flRootArg, redactPtr, notesPtr, - headerParts.viaPresent, cfg.compress, &g.outProv, headerParts.splitPresent, rw::linkOutPtr( g ) }, + headerParts.viaPresent, cfg.compress, &g.outProv, headerParts.splitPresent, rw::linkOutPtr( g ), &g.outAnchorMiss }, slotOwner, forSoftTarget ? std::optional( rw::forSoftSectionsShare( bundleBudget, slotSpent ) ) : std::nullopt ); if( slot.served ) @@ -3999,7 +3999,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co // directly at the SAME budget — uncharged for that one run, with an alert, never a fabricated number. if( cfg.detail > 0 ) { - rw::emitChargedSection( stdout, detailSection, [ & ]{ packBodies( stdout, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), + rw::emitChargedSection( stdout, detailSection, [ & ]{ packBodies( stdout, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, /*ranges=*/nullptr, notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/false, flRootArg, &lensRank, rw::CalleeLegendsInHead( headerParts.viaPresent, headerParts.splitPresent ) ); } ); diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index b09d04e7d..06df64eef 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -269,6 +269,11 @@ std::optional runCallHierarchy( const MainDispatch& d ) chHasVia = chHasVia || chAttr.nameOnly( i ); // FE-B's via="name": every edge to the matches by name (defattrib.h, one pass) chHasLink = chHasLink || oneHopRowIsLink( g, matches, result[ i ], wantCallers ); } + bool chHasMiss = false; + for( std::size_t i = pw.begin; i < pw.end && !chHasMiss; ++i ) + { + chHasMiss = oneHopRowIsLink( g, matches, result[ i ], wantCallers, &g.outAnchorMiss ); + } rw::emitTo( stdout, "{}{}{}{}{}{}{}-->{}{}", rw::callHierarchyLegendOpen( wantCallers, chNextIsBare, cfg.columnar, chNextIsPage ).c_str(), rw::capLegendClause( rw::computePageDisclosure( pw.end - pw.begin, result.size(), pw.end, cfg.pageLimit, cfg.pageOffset, chDiscloseCap ).active ), @@ -288,6 +293,14 @@ std::optional runCallHierarchy( const MainDispatch& d ) + rw::valueRefsDepthLegendFor( chRows.valueRefs ) ).c_str(), // exactly when the root carries the depth disclosure rw::rootRelPathsLegend( chSingleRoot ), rw::multiRootTableLegend( ing.rootLabels.size() >= 2 ) ); + if( cfg.columnar && chHasLink ) + { + rw::emitTo( stdout, "", rw::kViaLinkColumnarClause ); + } + if( chHasMiss ) + { + rw::emitTo( stdout, "", rw::kAnchorMissLegend ); + } } // P3 (L7, nextverb.h): ONE follow-up, shared with MCP. Callers preserve SELECTOR unless a narrowed @@ -344,6 +357,13 @@ std::optional runCallHierarchy( const MainDispatch& d ) pageOfDef.push_back( rw::ofDefColumnValue( chAttr.groups, chAttr.tokens, pw.begin + i ) ); } } + for( std::size_t i = 0; i < page.size(); ++i ) + { + if( oneHopRowIsLink( g, matches, result[ pw.begin + i ], wantCallers ) ) + { + pageVia[ i ] = 2; + } + } const std::string chDefRows = rw::defTableXml( ing, chAttr.groups, chListing, chRootPrefix ); emitColumnarSymbolRows( stdout, ing, tag, attr, page, chRootPrefix, &chTested.testReach, nullptr, rw::ColumnarRowExtras{ &pageVia, chSites.any ? &chSites.tokens : nullptr, &chSites.totals, // CALLSITE-AT: sites_at only when a row has sites @@ -393,6 +413,10 @@ std::optional runCallHierarchy( const MainDispatch& d ) chAtKeys[ i ] = rw::callSitesKeyJson( chSites.tokens[ i - pw.begin ], chSites.totals[ i - pw.begin ] ); } chAtKeys[ i ] += rw::ofDefKeyJson( chAttr.groups, chAttr.tokens, i ) + rw::splitArmKeyJson( chAttr.splitArm( i ) ); // attrib-multidef + if( oneHopRowIsLink( g, matches, result[ i ], wantCallers, &g.outAnchorMiss ) ) + { + chAtKeys[ i ] += ",\"anchor\":\"missing\""; + } } printJsonSymbolRows( ing, result, pw.begin, pw.end, chRootPrefix, &chTested.testReach, nullptr, &chVia, &chAtKeys ); rw::emitTo( stdout, "]{}{}{}}}", rw::declinedSitesJson( chDc, wantCallers, /*mcp=*/false ), // the XML rows, keys = its attribute names @@ -425,7 +449,8 @@ std::optional runCallHierarchy( const MainDispatch& d ) // already follows everywhere else (serialize.h/verbs_for.h), so an untested row costs 0 bytes. rw::emitTo( stdout, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, macroRoleAttr( s.kind ), rw::isTestedByReach( ing, chTested.testReach, result[i] ) ? " tested=\"1\"" : "", - viaEdgeAttr( chAttr.nameOnly( i ), oneHopRowIsLink( g, matches, result[ i ], wantCallers ) ), + std::string( viaEdgeAttr( chAttr.nameOnly( i ), oneHopRowIsLink( g, matches, result[ i ], wantCallers ) ) ) + + anchorMissAttr( oneHopRowIsLink( g, matches, result[ i ], wantCallers, &g.outAnchorMiss ) ), rw::splitArmAttrXml( chAttr.splitArm( i ) ), rw::ofDefAttrXml( chAttr.groups, chAttr.tokens, i ), // attrib-multidef chSites.any ? rw::callSitesAttrXml( chSites.tokens[ i - pw.begin ], chSites.totals[ i - pw.begin ] ) : std::string() ); // CALLSITE-AT } @@ -973,6 +998,7 @@ struct SafeDeleteLegendFlags bool defTableCut = false; // ... and the table lists only the groups this page's rows name (defs_listed=) bool splitArms = false; // a row is on this page (a one-group selector's split arm, Q1) bool linkRows = false; // a row is on this page + bool anchorRows = false; // a row is on this page rw::DeclinedSitesLegendFlags declinedSites{}; // lane declined-sites: what the rows on this answer carry (declinedsites.h) }; @@ -1011,6 +1037,7 @@ inline std::string safeDeleteFloorLegend( const SafeDeleteLegendFlags& flags, st } clause += rw::defTableLegend( flags.defTable, flags.defTableCut ) + rw::splitArmLegend( flags.splitArms ); // attrib-multidef clause += rw::viaLinkLegend( flags.linkRows ); + clause += rw::anchorMissLegend( flags.anchorRows ); return clause; } @@ -1315,10 +1342,12 @@ std::optional runSafeDelete( const MainDispatch& d ) const rw::DefTableListing sdListing = rw::defTableListing( sdAttr.groups, sdNamed ); bool sdHasSplit = false; bool sdHasLink = false; - for( std::size_t i = sdLw.begin; i < sdLw.end && ( !sdHasSplit || !sdHasLink ); ++i ) + bool sdHasMiss = false; + for( std::size_t i = sdLw.begin; i < sdLw.end && ( !sdHasSplit || !sdHasLink || !sdHasMiss ); ++i ) { sdHasSplit = sdHasSplit || sdAttr.splitArm( i ); sdHasLink = sdHasLink || rw::oneHopRowIsLink( g, defs, callerIds[ i ], true ); + sdHasMiss = sdHasMiss || rw::oneHopRowIsLink( g, defs, callerIds[ i ], true, &g.outAnchorMiss ); } const std::vector sdDc = rw::declinedSiteViews( ing, g, std::span( sdDeclinedSites ).first( sdDcCut.shown ), /*callersForm=*/true, defs, sdAttr.groups, @@ -1330,7 +1359,7 @@ std::optional runSafeDelete( const MainDispatch& d ) .valueRefs = sdValueRefs.rows.size(), .callersFloor = sdFloor.isFloor, .usesFloor = usesFloor, .hasSitesAt = sdSites.any, .sitesCut = sdSites.anyCut, .valueRefDepthCut = sdValueRefs.depthCut.files > 0, .defTable = sdAttr.groups.multi(), .defTableCut = sdListing.cut, .splitArms = sdHasSplit, - .linkRows = sdHasLink, + .linkRows = sdHasLink, .anchorRows = sdHasMiss, .declinedSites = rw::declinedSitesLegendFlags( sdDc, /*callersForm=*/true, sdDcCut ) } ); const Symbol& lead = ing.symbols[ defs[0] ]; // resolveAllByNameQualified walks ascending id — defs[0] is the @@ -1373,7 +1402,8 @@ std::optional runSafeDelete( const MainDispatch& d ) rw::emitTo( stdout, " amb=\"{}\"", g.ambOut[ callerId ] ); // M15: the same COUNT a map row's amb= prints — one meaning, one unit } rw::emitTo( stdout, "{}{}{}/>", rw::splitArmAttrXml( sdAttr.splitArm( i ) ), rw::ofDefAttrXml( sdAttr.groups, sdAttr.tokens, i ), - rw::viaEdgeAttr( false, rw::oneHopRowIsLink( g, defs, callerId, true ) ) ); // a direct markdown link + std::string( rw::viaEdgeAttr( false, rw::oneHopRowIsLink( g, defs, callerId, true ) ) ) + + rw::anchorMissAttr( rw::oneHopRowIsLink( g, defs, callerId, true, &g.outAnchorMiss ) ) ); // a direct markdown link } rw::emitTo( stdout, "{}{}", rw::declinedSitesXml( sdDc, /*callersForm=*/true ), // lane declined-sites: each declined possible caller, after the rows rw::valueRefsXml( ing, sdValueRefs, true, sdVr, "--uses=" + std::string( cfg.safeDeleteSym ) ) ); @@ -2427,12 +2457,14 @@ std::optional runPath( const MainDispatch& d ) const rw::PathSearchGaps pthGaps = path.empty() ? rw::pathSearchGaps( ing, g, srcDefs, *pthValueIdx ) : rw::PathSearchGaps{}; const bool pthHasVia = rw::pathHasNameOnlyHop( g, path ); // FE-B: exactly when a hop of THIS path is via="name" const bool pthHasLink = rw::pathHasLinkHop( g, path ); + const bool pthHasMiss = std::adjacent_find( path.begin(), path.end(), + [ & ]( rw::NodeId from, rw::NodeId to ) { return rw::edgeAnchorMiss( g, from, to ); } ) != path.end(); // attrib-multidef (defattrib.h): from_p=/to_p= only for an endpoint the path bound; prov="split" on a split-arm hop const rw::PathEndpointBinding pthBound = rw::pathEndpointBinding( g, rw::endpointDefinitionCount( ing, srcDefs ), rw::endpointDefinitionCount( ing, dstDefs ), path ); const bool pthHasSplit = pthBound.splitHop; rw::emitTo( stdout, "{}", ( rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Path, pthUnprovenDefs > 0 ) + rw::viaNameLegend( pthHasVia ) - + rw::viaLinkLegend( pthHasLink ) + + rw::viaLinkLegend( pthHasLink ) + rw::anchorMissLegend( pthHasMiss ) + rw::splitArmLegend( pthHasSplit ) + rw::pathUnboundLegend( !pthBound.from || !pthBound.to ) ).c_str(), ( std::string( rw::toValueRefsLegend( pthToValueRefs.count > 0 ) ) + rw::valueRefsDepthLegend( pthToValueRefs.depthCut.files > 0 ) ).c_str(), rw::pathGapsLegend( pthGaps.any() ), @@ -2460,7 +2492,8 @@ std::optional runPath( const MainDispatch& d ) const std::string_view rp = pthSingleRoot ? rw::sarif::rootRelativeUri( ing.files[ s.fileId ], pthRootPrefix ) : std::string_view( ing.files[ s.fileId ] ); // FE-B: a hop whose edge INTO it is name-only says so on its row (the first row is no hop) rw::emitTo( stdout, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, - viaEdgeAttr( i > 0 && edgeNameOnly( g, path[ i - 1 ], path[ i ] ), i > 0 && edgeIsLink( g, path[ i - 1 ], path[ i ] ) ), rw::splitArmAttrXml( rw::pathHopSplitArm( g, path, i ) ) ); + std::string( viaEdgeAttr( i > 0 && edgeNameOnly( g, path[ i - 1 ], path[ i ] ), i > 0 && edgeIsLink( g, path[ i - 1 ], path[ i ] ) ) ) + + anchorMissAttr( i > 0 && edgeAnchorMiss( g, path[ i - 1 ], path[ i ] ) ), rw::splitArmAttrXml( rw::pathHopSplitArm( g, path, i ) ) ); } rw::emitTo( stdout, "" ); return 0; @@ -2605,21 +2638,18 @@ inline std::vector impactOfDefKeys( const ImpactView& v ) { keys[ i ] = v.attrib.ofDefJson( v.show[ i ] ); } - return keys; -} - -// FE-B: the window's hedge bits in row order (impactRowNameOnly), for the dialects that carry them per entry or per column -inline std::vector impactViaColumn( const ImpactView& v, std::size_t begin, std::size_t end ) -{ - std::vector via( v.show.size(), 0 ); - for( std::size_t i = begin; i < end; ++i ) + for( std::size_t i = v.page.begin; i < v.page.end; ++i ) { - via[ i ] = rw::impactRowNameOnly( v.provenReach, v.show[ i ] ) ? 1 : 0; + if( rw::impactDirectLink( v.g, v.seeds, v.depth, v.show[ i ] ) + && rw::oneHopRowIsLink( v.g, v.seeds, v.show[ i ], true, &v.g.outAnchorMiss ) ) + { + keys[ i ] += ",\"anchor\":\"missing\""; + } } - return via; + return keys; } -// JSON rows: 2 = a direct markdown link, 1 = via="name". The columnar column stays impactViaColumn (1 means name). +// JSON rows: 2 = a direct markdown link, 1 = via="name". The columnar column uses the same codes. inline std::vector impactJsonVia( const ImpactView& v ) { std::vector via( v.show.size(), 0 ); @@ -2682,7 +2712,7 @@ int emitImpactColumnar( const ImpactView& v ) : " lens=\"shown_importers,importers_capped,importers_next\"" ) + rw::renderDisclosure( v.prD, rw::DiscloseAs::XmlAttrs ) // W2-F + rw::nextAttrXml( rw::nextFlag( "--safe-delete=", v.sym ) ); // P3 (L7): the XML root's next=, same set - const std::vector via = impactViaColumn( v, v.page.begin, v.page.end ); + const std::vector via = impactJsonVia( v ); const std::vector rowVia( via.begin() + v.page.begin, via.begin() + v.page.end ); // FE-B: in `rows` order std::vector rowOfDef; // attrib-multidef: of_def per row, only on a multi-group selector for( std::size_t i = v.page.begin; i < v.page.end && v.attrib.groups.multi(); ++i ) @@ -2779,11 +2809,12 @@ int emitImpactXml( const ImpactView& v ) const std::string_view rp = v.singleRoot ? rw::sarif::rootRelativeUri( v.ing.files[ s.fileId ], v.rootPrefix ) : std::string_view( v.ing.files[ s.fileId ] ); // A6: tested="1" only (never a literal 0) — see kTestedRowLegend. 0.6.5: d= the row's hop depth, run-length (graph.h depthRunAttrXml). + const bool imLink = rw::impactDirectLink( v.g, v.seeds, v.depth, v.show[ i ] ); rw::emitTo( stdout, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, rw::depthRunAttrXml( v.show, v.depth, i, v.page.begin ), rw::isTestedByReach( v.ing, *v.testReach, v.show[i] ) ? " tested=\"1\"" : "", - viaEdgeAttr( rw::impactRowNameOnly( v.provenReach, v.show[i] ), - rw::impactDirectLink( v.g, v.seeds, v.depth, v.show[i] ) ), + std::string( viaEdgeAttr( rw::impactRowNameOnly( v.provenReach, v.show[i] ), imLink ) ) + + anchorMissAttr( imLink && rw::oneHopRowIsLink( v.g, v.seeds, v.show[ i ], true, &v.g.outAnchorMiss ) ), v.attrib.ofDefXml( v.show[i] ) ); // attrib-multidef } rw::emitImportRowsXml( stdout, v.ing, v.importPage, v.rootPrefix, v.importLazyPage ); @@ -2899,7 +2930,11 @@ std::optional runImpact( const MainDispatch& d ) + rw::viaNameLegend( std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, // FE-B: exactly when a shown row is via="name" [ & ]( NodeId n ) { return rw::impactRowNameOnly( imProven, n ); } ), true, cfg.columnar ) + rw::viaLinkLegend( std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, - [ & ]( NodeId n ) { return rw::impactDirectLink( g, seeds, imDepth, n ); } ) ) ).c_str(), + [ & ]( NodeId n ) { return rw::impactDirectLink( g, seeds, imDepth, n ); } ) ) + + rw::anchorMissLegend( std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, + [ & ]( NodeId n ) + { return rw::impactDirectLink( g, seeds, imDepth, n ) + && rw::oneHopRowIsLink( g, seeds, n, true, &g.outAnchorMiss ); } ) ) ).c_str(), rw::testedLensLegend( cfg.columnar ), rw::kImpactTestedPartitionLegend, // A6: the columnar form reads its dense column rw::kTestedLensBlindSpotLegend, // F-02: rides with the partition rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Impact, imUnprovenDefs > 0 ).c_str(), // H1: exactly when the root carries unproven_defs= @@ -3098,7 +3133,7 @@ std::optional runAround( const MainDispatch& d ) aroundAnn.seed = { ing.symbols[ focus ].name, cfg.aroundDepth, cfg.aroundFanout, definitionCountOfName( ing, focus ), eg.fanoutCut, eg.depthTruncated }; aroundAnn.seed.unprovenDefs = arUnprovenDefs; // H1: unproven_defs= beside defs= on the root, its clause in the map legend - serialize( stdout, ing, rank, g.outOff, g.outTargets, int( eg.nodes.size() ), cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, false, g.outProv.empty() ? nullptr : &g.outProv, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, /*autoOrder=*/false, /*outEstTokens=*/nullptr, aroundCompose.tokens + aroundRoutes.tokens + wrap.tokens, aroundAnn, /*statsFirstScreen=*/false, aroundRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, /*gateDeclinedCalls=*/0, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, /*stdMemberDeclinedCalls=*/0, g.inertDeclinedCalls, rw::linkOutPtr( g ) ); + serialize( stdout, ing, rank, g.outOff, g.outTargets, int( eg.nodes.size() ), cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, false, g.outProv.empty() ? nullptr : &g.outProv, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, /*autoOrder=*/false, /*outEstTokens=*/nullptr, aroundCompose.tokens + aroundRoutes.tokens + wrap.tokens, aroundAnn, /*statsFirstScreen=*/false, aroundRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, /*gateDeclinedCalls=*/0, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, /*stdMemberDeclinedCalls=*/0, g.inertDeclinedCalls, rw::linkOutPtr( g ), &g.outAnchorMiss ); if( !g.composeEdges.empty() ) { diff --git a/test/mdanchorfix/docs/a/use_work.py b/test/mdanchorfix/docs/a/use_work.py new file mode 100644 index 000000000..9c5c09dce --- /dev/null +++ b/test/mdanchorfix/docs/a/use_work.py @@ -0,0 +1,2 @@ +def use_work(): + return do_work() diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index ab539890c..70980a32e 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -384,6 +384,17 @@ want "$AFIX" "Func Name" 'do_work@docs/b/partner.md' "a heading nam want "$AFIX" "Missing Anchor" 'partner@docs/b/partner.md' "#no-such-heading falls back to that file's own node" want "$AFIX" "Missed Readme" 'README@docs/b/README.md' "../b/README.md#no-such-heading pins docs/b/README.md, not the same-directory README" want "$AFIX" "Bare Readme" 'README@docs/b/README.md' "a bare ../b/README.md pins that file, not the same-directory README" +if $BIN "$AFIX" --no-cache --callees='Missing Anchor' --legend=compact 2>/dev/null | grep -q 'n="partner"[^>]*anchor="missing"' \ + && ! $BIN "$AFIX" --no-cache --callees='Bare Readme' --legend=compact 2>/dev/null | grep -q 'anchor="missing"'; then + ok "a slug the file does not have is anchor=missing, and a bare link is not" +else + no "anchor=missing missed the slug fallback, or a bare link carried it" +fi +if $BIN "$AFIX" --no-cache --format=columnar --callees='Func Name' --legend=compact 2>/dev/null | grep -q '2'; then + ok "columnar via is 2 for a direct markdown link" +else + no "columnar via did not write 2 for the markdown link" +fi want "$AFIX" "Fence Only" '' "a link inside a fenced block makes no edge" # --callers lists every matched def in (cross_kind when a heading and a function share the name). # The edge is the row: it must name the section, never the .py function. @@ -393,28 +404,36 @@ xml = sys.stdin.read() kind = { m.group(1): m.group(2) for m in re.finditer(r"]*)/?>", xml)) ok = bool(rows) and "fn" in kind.values() and "sec" in kind.values() +saw_sec = False +saw_fn = False for m in rows: attrs = m.group(1) p = re.search(r" p=\"([^\"]*)\"", attrs) of = re.search(r"of_def=\"(\d+)", attrs) + via = re.search(r" via=\"([^\"]*)\"", attrs) if p and "tools.py" in p.group(1): ok = False if of and kind.get(of.group(1)) == "fn": - ok = False - via = re.search(r" via=\"([^\"]*)\"", attrs) - if not via or via.group(1) != "link": - ok = False -sys.exit(0 if ok else 1) -'; then ok "the .py do_work is a matched def only — the caller edge belongs to the section and is via=link"; else no "the doc link leaked onto the .py function, or the section row lacks via=link"; fi + saw_fn = True + if via and via.group(1) == "link": + ok = False + else: + if not via or via.group(1) != "link": + ok = False + else: + saw_sec = True +sys.exit(0 if ok and saw_sec and saw_fn else 1) +'; then ok "the section caller stays via=link and the Python caller of do_work is unmarked"; else no "the doc link leaked onto the .py function, or the section row lacks via=link"; fi if ( cd "$AFIX" && $BIN . --no-cache --callers=do_work --legend=compact ) 2>/dev/null | grep -q 'n="Func Name"[^>]*via="link"'; then ok "a dot crawl root still pins the markdown link (./docs/... matches docs/...)" else no "a dot crawl root dropped the markdown link pin" fi -if $BIN "$AFIX" --no-cache --callers='docs/a/tools.py:do_work' --legend=compact 2>/dev/null | grep -q 'via="link"'; then - no "a real function's callers carry via=link" +QUAL=$($BIN "$AFIX" --no-cache --callers='docs/a/tools.py:do_work' --legend=compact 2>/dev/null || true) +if printf '%s' "$QUAL" | grep -q 'count="[1-9]' && printf '%s' "$QUAL" | grep -q 'n="use_work"' && ! printf '%s' "$QUAL" | grep -q 'via="link"'; then + ok "the .py do_work has a real caller and that row is not via=link" else - ok "the .py do_work callers are not marked via=link" + no "the qualified .py do_work callers were empty or carried via=link" fi if $BIN "$AFIX" --no-cache --callees='Self Collision' --legend=compact 2>/dev/null | grep -q 'n="Install"[^>]*via="link"'; then ok "Self Collision's callee Install is via=link" From d91536245c8b7be640f280104d58cb21a1526328 Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Mon, 19 Oct 2026 01:56:17 +0300 Subject: [PATCH 12/28] Fold call, name, and link into one byte per edge. Path, connect, and impact JSON mark a direct markdown link, and a leading-slash link joins the crawl root. --- .ripwire_quality_acks | 1 + CHANGELOG.md | 3 +- src/compactlegend.h | 2 +- src/defattrib.h | 2 +- src/forowner.h | 3 +- src/graph.h | 188 ++++++++++++++++-------- src/graphlegend.h | 11 +- src/ingest_docs.h | 26 +++- src/main.cpp | 12 +- src/mcpverbs.h | 95 ++++++++---- src/pathgaps.h | 6 +- src/serialize.h | 221 +++++++++++++++------------- src/verbs_for.h | 12 +- src/verbs_navigate.h | 324 +++++++++++++++++++++-------------------- test/mdsectioncheck.sh | 19 ++- 15 files changed, 542 insertions(+), 383 deletions(-) diff --git a/.ripwire_quality_acks b/.ripwire_quality_acks index 5bd8199e7..6845830a1 100644 --- a/.ripwire_quality_acks +++ b/.ripwire_quality_acks @@ -88,6 +88,7 @@ ack api-surface 610cc771fd6f10c1 5 cid=86df6969a0d891fb now=5 was=4 facet=contra ack api-surface 61e5df9e1e40ff70 5 cid=e2155dd6082b880a E2 (terminality round A, lane E): +1 defaulted out-param: the receipt's ONE next= is read off the fold it renders (callers 2, incompatible 0) ack api-surface 6302e2e27e23bcde 6 cid=7202bbc7db7cc1da 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 6483016e50837e70 3 cid=4c53bf4b5b7d4a0c degrade disclosure: each read seam and the ref pool now take the DISCLOSE sink whose field the document reads +ack api-surface 6511634f0768c591 5 cid=0f75a00dd52bc21d now=5 was=3 facet=contract-change p=src/graphlegend.h:921 impactRollupLegend carries the importer cut and the type rollup clauses. That signature is the one upstream already ships. Local main still has the three argument form. ack api-surface 654551bf984cc299 5 cid=913136f787574436 P9 the folded edit receipt: runEditVerb gains the postCheck opt-out parameter (defaulted true, so every existing call site is source-compatible) and the line-range + post-check splice; editplan's ensureStage and receipt each gain the root parameter they need to spell one identity (M12's root-relative rule, applied to the sibling it missed) and to run the per-op post-check. The +15 LOC in runEditVerb is the copy-out-before-the-index-rebuild discipline the fold requires — every reference into ing dangles once getIndex re-ingests, and that is stated in the code. ack api-surface 68301548c0560f5d 3 cid=07b87ac0d0ae631a interface-dispatch lane (2026-09-16, test/narrowcheck.sh arms 26-36): Narrower::rule2RecvVarType takes the two inputs class identity needs — the ClassIdentity index built once in buildGraph and the chaUp base-name graph C++ lookup walks for inherited nested types (1 -> 3 parameters). Single call site (graph.h buildGraph Rule 2), updated in the same commit. Every other quality-delta row this lane produced was fixed in code rather than acked: the identity logic moved out of Narrower into IdentityNarrower (Narrower verbosity 409 -> 800 undone), nine over-complex functions split, and five clone rows removed by reusing extent::inSet with kHeadRuleLangs/kExtentClassKinds, symbolsByFileInIdOrder, and a flat include index. ack api-surface 6e58b0a307757079 24 cid=cb5c8aaa7451a632 by=src/* Phase 5 (docs/EVALS.md): the external-name veto (@external, externalnames.h tables, ExternalVeto predicate, import-name bindings at ingest) and the receiver MRO walk (rule1BaseWalk, SuperObj); every gating row is this one change — the four deliberate contract changes (serialize/serializeJson gain externalCalls, captureIncludes gains binds, methodOnTypeOrBases gains skipSelf/unionOnMulti), the ladder's two new steps in buildGraph, the census's tenth mechanism, kParserVer 77, and the two lexical clone false-positives (a one-line binary_search vs a counts total; a key-buffer probe vs a JSON string writer) | prior: Phase 4 lane (lpin= disclosure + localityKey tie-break, 2026-09-03): serialize/serializeJson each gain ONE trailing defaulted locPinOut param (the identical shape every honesty counter took — ambOut/unresolvedOut/bind); classifyPin churn=self is the one-line reroute of its Locality outcome through isLocalityPin so the shipped marker and the census label are the same predicate; runAround churn=self is the one-argument extension at its serialize call, the same edit every serialize caller took (main.cpp x4, mcpverbs analyze). Six duplicated sum/at chains folded into counterTotal/counterAt in the same change; astropy map + census byte-identical before and after that fold. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d51fcbe8..c7abb20c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,8 +23,7 @@ the path against the linking file, so `../b/notes.md` never lands on a same-name link to a heading named like one of the linking file's own headings does not bind to the latter. Two headings of the same slug give one edge (the first). A missing heading falls back to the target file's edge; percent-encoded slugs and GitHub's `-1` duplicate suffix are not modelled and also fall back. A markdown link that no call shares is marked `via="link"` on -`--callers`, `--callees`, `--path`, `--connect`, and on a map or body callee row. `count=` and `cross_kind` stay the same. A -call on the same pair leaves the row unmarked. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. `--safe-delete` and `--impact` mark a direct doc caller `via="link"`. `callers=` and `reaches=` still count it. An in-file anchor, a wikilink, and a root-relative link are `via="link"` too. The mark follows the reference, so an unpinned link does not clear a pinned one on the same pair. A link whose file is in the tree and whose slug names no heading carries `anchor="missing"`. Columnar `` is `2` for a direct markdown link and `1` for a name-only edge. Gate: `test/mdsectioncheck.sh`. +`--callers`, `--callees`, `--path`, `--connect`, and on a map or body callee row. `count=` and `cross_kind` stay the same. A crawl rooted at `.` stores `./docs/a.md`. The link index normalizes that path, so a root of `.` pins the same heading as an absolute root. `find_symbol` and `find_referencing_symbols` mark the link row, and the answer adds `via_link_note` when a row has it. `--json` callers and callees add that note too. `--safe-delete` and `--impact` mark a direct doc caller `via="link"`. `callers=` and `reaches=` still count it. An in-file anchor, a wikilink, and a root-relative link are `via="link"` too. The mark follows the reference, so an unpinned link does not clear a pinned one on the same pair. A link whose file is in the tree and whose slug names no heading carries `anchor="missing"`. Columnar `` is `2` for a direct markdown link and `1` for a name-only edge. Gate: `test/mdsectioncheck.sh`. ### Changed — Go methods carry their receiver type as scope (#403, slice 9a) diff --git a/src/compactlegend.h b/src/compactlegend.h index 6f6d6c9ce..6f08c3ca5 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -583,7 +583,7 @@ inline constexpr CompactCompletenessTerm kCompactCompletenessTerms[] = // and VALUE-qualified (// whose via= lists name), so impact's importer rows never pull it in; the // columnar form names it in fields= like the tested column. { "via", "via=name: the target was matched by name alone (receiver unproven); every by-name candidate in reach is listed; NOT a claim the edge is false", true, "s", MapHeaderRead::No, "name" }, - { "via", "via=link: this edge is a markdown link, not a call. A real call on the same pair stays unmarked", true, "s", MapHeaderRead::No, "link" }, + { "via", "via=link: this edge is a markdown link, not a call", true, "s", MapHeaderRead::No, "link" }, { "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", ": that callee is a markdown link, not a call", true, "c", MapHeaderRead::No, "link" }, diff --git a/src/defattrib.h b/src/defattrib.h index 9587c4ce9..8f88923f8 100644 --- a/src/defattrib.h +++ b/src/defattrib.h @@ -377,7 +377,7 @@ struct RowDefTokens // One out-edge's tie, off its two parallel per-edge bytes (either may be unallocated: a graph with no such edge). inline DefMark defMarkOfEdge( const Graph& g, std::size_t e ) noexcept { - if( e < g.outNameOnly.size() && g.outNameOnly[e] != 0 ) + if( e < g.outNameOnly.size() && edgeKindByte( g.outNameOnly[e] ) == kEdgeName ) { return DefMark::Name; } diff --git a/src/forowner.h b/src/forowner.h index 60b097465..b075b3ae5 100644 --- a/src/forowner.h +++ b/src/forowner.h @@ -391,7 +391,6 @@ struct BodySlotInputs // a split arm reads as one here too; the split reading rides beside the rows unless the header already defines it const std::vector* outProv = nullptr; // Graph::outProv (nullptr: no split labels) bool splitLegendInHead = false; // the header already defines prov="split" - const std::vector* outLink = nullptr; // Graph::outLink (nullptr: no via="link") const std::vector* outAnchor = nullptr; // Graph::outAnchorMiss (nullptr: no anchor="missing") }; inline BodySlotRender renderForBodySlot( const BodySlotInputs& in, ForBodySlotOwner owner, std::optional share ) @@ -400,7 +399,7 @@ inline BodySlotRender renderForBodySlot( const BodySlotInputs& in, ForBodySlotOw BodySlotRender out; const std::optional before = in.redact != nullptr ? std::optional( *in.redact ) : std::nullopt; ChargedSection sec = chargeSection( [ & ]( std::FILE* f ) - { packBodies( f, in.ing, { owner.id }, kForBodySlotMaxBytes, in.outOff, in.outTargets, withAnchorEdge( CalleeEdgeBytes( in.outNameOnly, in.outProv, nullptr, in.outLink ), in.outAnchor ), + { packBodies( f, in.ing, { owner.id }, kForBodySlotMaxBytes, in.outOff, in.outTargets, withAnchorEdge( CalleeEdgeBytes( in.outNameOnly, in.outProv, nullptr ), in.outAnchor ), in.compress, in.redact, /*ranges=*/nullptr, in.notes, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/false, in.rootArg, &in.lensRank, CalleeLegendsInHead( in.viaLegendInHead, in.splitLegendInHead ) ); }, kBytesPerTokenBody ); diff --git a/src/graph.h b/src/graph.h index eda4935d0..73a5f0eb9 100644 --- a/src/graph.h +++ b/src/graph.h @@ -80,11 +80,9 @@ struct Graph // the export (JS/TS); read, not chosen by name // Empty ⇒ no overlay, no FFI/import edge and nothing split: every edge // uniquely resolved, so the whole attribute is absent (omit-at-confident). - std::vector outNameOnly; // FE-B: per-out-edge HEDGE bit, parallel to outTargets — 1 = every call site that bound this - // edge did so by NAME ALONE (no receiver evidence: receiverevidence.h), rendered via="name" - // on every surface; 0 = at least one site proved it. Empty ⇒ no edge is name-only. - std::vector outLink; // per-out-edge markdown-link bit, parallel to outTargets — 1 = every site that bound this - // edge was a pinned [text](path) link and no real call bound the same pair. Empty ⇒ no link edge. + std::vector outNameOnly; // per-out-edge kind, parallel to outTargets. 0 = a call. 1 = every site bound this edge by + // NAME ALONE (receiverevidence.h), rendered via="name". 2 = a markdown link, rendered via="link". + // The high bit on a 2 marks anchor="missing" for the map writer. Empty ⇒ every edge is a plain call. std::vector outAnchorMiss; // parallel to outTargets — 1 = that link edge named a heading the file does not have, so the // target is the file node. Empty ⇒ no such edge. A link with no slug stays 0. std::size_t scipDocsSeen = 0; // # SCIP documents consumed (0 unless --scip); honesty summary @@ -390,21 +388,30 @@ inline LegoClosure legoImplementorClosure( const std::vector return out; } -// FE-B: is the call edge from → to NAME-ONLY, over the raw out-CSR and its parallel hedge bits — bound by name alone at -// every site that bound it, so every surface renders it via="name". false for an edge that does not exist and for empty -// hedge bits (a graph with no name-only edge at all). edgeNameOnly( Graph ) below and serialize.h's block share it. +// Graph::outNameOnly kind byte. The high bit is the map writer's anchor="missing" mark on a link. +inline constexpr std::uint8_t kEdgeName = 1; +inline constexpr std::uint8_t kEdgeLink = 2; +inline constexpr std::uint8_t kEdgeKindMask = 0x7f; + +inline std::uint8_t edgeKindByte( std::uint8_t stored ) noexcept +{ + return std::uint8_t( stored & kEdgeKindMask ); +} + +// FE-B: is the call edge from → to the asked kind, over the raw out-CSR. want is kEdgeName or kEdgeLink. +// false for an edge that does not exist and for an empty kind vector. inline bool edgeNameOnlyInCsr( const std::vector& outOff, const std::vector& outTargets, - const std::vector& nameOnly, NodeId from, NodeId to ) noexcept + const std::vector& nameOnly, NodeId from, NodeId to, std::uint8_t want = kEdgeName ) noexcept { if( nameOnly.empty() || std::size_t( from ) + 1 >= outOff.size() ) { return false; } - EXPECTS( nameOnly.size() == outTargets.size(), "the hedge bit is parallel to outTargets (buildGraph allocates it per edge)" ); + EXPECTS( nameOnly.size() == outTargets.size(), "the kind byte is parallel to outTargets (buildGraph allocates it per edge)" ); const auto b = outTargets.begin() + outOff[ from ]; const auto e = outTargets.begin() + outOff[ std::size_t( from ) + 1 ]; const auto it = std::lower_bound( b, e, to ); // ascending within a source (buildGraph sorts by (from, to)) - return it != e && *it == to && nameOnly[ std::size_t( it - outTargets.begin() ) ] != 0; + return it != e && *it == to && edgeKindByte( nameOnly[ std::size_t( it - outTargets.begin() ) ] ) == want; } inline bool edgeNameOnly( const Graph& g, NodeId from, NodeId to ) noexcept { @@ -426,7 +433,7 @@ inline std::size_t edgeIndex( const Graph& g, NodeId from, NodeId to ) noexcept return ( it != e && *it == to ) ? std::size_t( it - g.outTargets.begin() ) : SIZE_MAX; } -// One per-edge bit vector (outLink, outAnchorMiss). false when that vector was not allocated, or the edge is absent. +// One per-edge bit vector (outAnchorMiss). false when that vector was not allocated, or the edge is absent. inline bool edgeBitSet( const Graph& g, const std::vector& bits, NodeId from, NodeId to ) noexcept { if( bits.empty() ) @@ -440,7 +447,7 @@ inline bool edgeBitSet( const Graph& g, const std::vector& bits, N inline bool edgeIsLink( const Graph& g, NodeId from, NodeId to ) noexcept { - return edgeBitSet( g, g.outLink, from, to ); + return edgeNameOnlyInCsr( g.outOff, g.outTargets, g.outNameOnly, from, to, kEdgeLink ); } inline bool edgeAnchorMiss( const Graph& g, NodeId from, NodeId to ) noexcept @@ -449,12 +456,13 @@ inline bool edgeAnchorMiss( const Graph& g, NodeId from, NodeId to ) noexcept } // Callers (towardMatches): every edge from the row to a matched def is a link. Callees: every edge from a matched def -// to the row is. A row with no such edge is not a link. One real call among them drops the mark. -// bits, when passed, reads that vector instead of outLink (outAnchorMiss: the slug named no heading). +// to the row is. A row with no such edge is not a link. +// bits, when passed, reads that vector (outAnchorMiss: the slug named no heading). Otherwise the kind byte is read. inline bool oneHopRowIsLink( const Graph& g, std::span matches, NodeId row, bool towardMatches, const std::vector* bits = nullptr ) noexcept { - const std::vector& edgeBits = bits != nullptr ? *bits : g.outLink; + const bool onKind = bits == nullptr; + const std::vector& edgeBits = onKind ? g.outNameOnly : *bits; if( edgeBits.empty() ) { return false; @@ -470,7 +478,8 @@ inline bool oneHopRowIsLink( const Graph& g, std::span matches, No continue; } any = true; - if( e >= edgeBits.size() || edgeBits[ e ] == 0 ) + const bool set = e < edgeBits.size() && ( onKind ? edgeKindByte( edgeBits[ e ] ) == kEdgeLink : edgeBits[ e ] != 0 ); + if( !set ) { return false; } @@ -478,16 +487,27 @@ inline bool oneHopRowIsLink( const Graph& g, std::span matches, No return any; } -// FE-B: does any hop of a directed call path (path[i-1] → path[i]) run through a name-only edge? --path and MCP -// path_between ask it once to decide their via="name" legend sentence (one spelling for both transports). -inline bool pathHasNameOnlyHop( const Graph& g, std::span path ) noexcept +inline bool pageHasAnchorMiss( const Graph& g, std::span matches, const std::vector& rows, + std::size_t begin, std::size_t end, bool towardMatches ) noexcept { - return std::adjacent_find( path.begin(), path.end(), [ & ]( NodeId from, NodeId to ) { return edgeNameOnly( g, from, to ); } ) != path.end(); + for( std::size_t i = begin; i < end && i < rows.size(); ++i ) + { + if( oneHopRowIsLink( g, matches, rows[ i ], towardMatches, &g.outAnchorMiss ) ) + { + return true; + } + } + return false; } -inline bool pathHasLinkHop( const Graph& g, std::span path ) noexcept +// Does any hop of a directed path carry this kind byte? kind 1 is via="name", kind 2 is via="link". +// --path and MCP path_between ask it once for the legend sentence. +inline bool pathHasNameOnlyHop( const Graph& g, std::span path, std::uint8_t kind = kEdgeName ) noexcept { - return std::adjacent_find( path.begin(), path.end(), [ & ]( NodeId from, NodeId to ) { return edgeIsLink( g, from, to ); } ) != path.end(); + return std::adjacent_find( path.begin(), path.end(), [ & ]( NodeId from, NodeId to ) + { + return kind == 0 ? edgeAnchorMiss( g, from, to ) : edgeNameOnlyInCsr( g.outOff, g.outTargets, g.outNameOnly, from, to, kind ); + } ) != path.end(); } // FE-B's per-row via="name" (EVERY edge joining a row to the selector's definitions is name-only) is defattrib.h's @@ -501,7 +521,7 @@ inline const char* viaNameAttr( bool nameOnly ) noexcept } inline const std::vector* linkOutPtr( const Graph& g ) noexcept { - return g.outLink.empty() ? nullptr : &g.outLink; + return g.outNameOnly.empty() ? nullptr : &g.outNameOnly; } inline const char* viaEdgeAttr( bool nameOnly, bool link ) noexcept @@ -6435,31 +6455,30 @@ struct MdLinkIndex inline bool isMarkdownLinkRef( const Reference& r ) noexcept { - return r.lang == Lang::Markdown && !r.isDocLink && !r.memberCall && !r.memberPath.empty(); + if( r.lang != Lang::Markdown || r.isDocLink || r.memberCall ) + { + return false; + } + return !r.memberPath.empty(); } // Link-ness is where the reference came from: lang Markdown and not a doc mention. isMarkdownLinkRef also requires a -// path, which an in-file anchor and a wikilink do not have, and a root-relative link can miss the pin. A non-markdown -// site on the same pair is a real call and wins at stamp time. A markdown doc mention is neither. -inline void noteMarkdownLinkSite( HashMap& linkEdges, HashMap& realCalls, std::uint64_t ekey, const Reference& r ) +// path, which an in-file anchor and a wikilink do not have. A markdown doc mention is neither. +inline void noteMarkdownLinkSite( HashMap& linkEdges, std::uint64_t ekey, const Reference& r ) { if( r.lang == Lang::Markdown && !r.isDocLink ) { linkEdges[ ekey ] = 1; } - else if( r.lang != Lang::Markdown ) - { - realCalls[ ekey ] = 1; - } } template -inline bool graphHasMarkdownLink( const std::vector& edges, const HashMap& linkEdges, const HashMap& realCalls ) +inline bool graphHasMarkdownLink( const std::vector& edges, const HashMap& linkEdges ) { for( const Edge& e : edges ) { const std::uint64_t key = ( std::uint64_t( e.from ) << 32 ) | e.to; - if( linkEdges.find( key ) != linkEdges.end() && realCalls.find( key ) == realCalls.end() ) + if( linkEdges.find( key ) != linkEdges.end() ) { return true; } @@ -6467,9 +6486,9 @@ inline bool graphHasMarkdownLink( const std::vector& edges, const HashMap< return false; } -inline std::uint8_t markdownLinkBit( const HashMap& linkEdges, const HashMap& realCalls, std::uint64_t key ) noexcept +inline std::uint8_t markdownLinkBit( const HashMap& linkEdges, std::uint64_t key ) noexcept { - return ( linkEdges.find( key ) != linkEdges.end() && realCalls.find( key ) == realCalls.end() ) ? 1u : 0u; + return linkEdges.find( key ) != linkEdges.end() ? 1u : 0u; } // The file node is the Section extractMarkdown builds with bodyByte 0 over [0, size). assignSymbols then @@ -6543,18 +6562,41 @@ struct MarkdownLinkTarget bool slugMiss = false; }; -inline MarkdownLinkTarget bindOneMarkdownLink( const MdLinkIndex& ix, std::string_view linkerPath, std::string_view written ) +inline MarkdownLinkTarget bindOneMarkdownLink( const MdLinkIndex& ix, std::string_view linkerPath, std::string_view written, + std::string_view crawlRoot ) { const std::size_t frag = written.find( '#' ); const std::string_view link = written.substr( 0, frag ); const std::string_view slug = frag == std::string_view::npos ? std::string_view{} : written.substr( frag + 1 ); - const std::string_view dir = includerDir( linkerPath ); - std::string path( dir ); - if( !dir.empty() ) + std::string path; + if( !link.empty() && link.front() == '/' ) + { + // A leading slash is the crawl root. A multi-root crawl leaves crawlRoot empty, and the stem ladder answers. + if( crawlRoot.empty() ) + { + return {}; + } + path.assign( crawlRoot ); + if( path.empty() || path.back() != '/' ) + { + path.push_back( '/' ); + } + const std::size_t body = link.find_first_not_of( '/' ); + if( body != std::string_view::npos ) + { + path.append( link.substr( body ) ); + } + } + else { - path.push_back( '/' ); + const std::string_view dir = includerDir( linkerPath ); + path.assign( dir ); + if( !dir.empty() ) + { + path.push_back( '/' ); + } + path.append( link ); } - path.append( link ); path = lexicalNormalize( path ); // empty on an escape above the root → stem ladder const auto file = path.empty() ? ix.fileByPath.end() : ix.fileByPath.find( path ); if( file == ix.fileByPath.end() ) @@ -6595,7 +6637,7 @@ inline MdLinkPins bindMarkdownLinks( const IngestResult& ing ) const Reference& r = ing.references[ i ]; if( isMarkdownLinkRef( r ) && r.fileId < ing.files.size() ) { - const MarkdownLinkTarget t = bindOneMarkdownLink( ix, ing.files[ r.fileId ], r.memberPath ); + const MarkdownLinkTarget t = bindOneMarkdownLink( ix, ing.files[ r.fileId ], r.memberPath, ing.crawlRoot ); out.pin[ i ] = t.id; out.slugMiss[ i ] = t.slugMiss ? 1 : 0; } @@ -7037,9 +7079,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null // so its bucket layout cannot reach output, and it is filled inside a loop // whose per-reference tier work dominates the growth cascade by orders of // magnitude. A guessed reserve would be a made-up number in a hot struct. - HashMap linkEdges; // (from<<32|to) a markdown link bound. A non-markdown call on the same pair - // (realCallEdges) drops the mark: the merged edge stays a call. - HashMap realCallEdges; // (from<<32|to) bound by a non-markdown site. Empty on a tree with no markdown link. + HashMap linkEdges; // (from<<32|to) a markdown link bound. The section that holds the link is the edge's from. HashMap anchorMissEdges; // (from<<32|to) a link whose slug named no heading, so the target is the file node. std::vector rule3Out; // reused Rule-3 output buffer (candidates from the single included file) Rule3FileMemo rule3Memo; // Rule 3's answer per (caller file, byName entry): one walk per name per file, not per call @@ -9143,7 +9183,8 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null } } - // drop self-loops BEFORE the split so the confidence mass is conserved over real targets + // drop self-loops BEFORE the split so the confidence mass is conserved over real targets. + // A markdown link whose target is the section that contains the link is dropped here on purpose. std::size_t nReal = 0; for( NodeId c : tier ) { @@ -9238,7 +9279,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null ( nameOnly ? nameOnlyEdges : provenEdges )[ ekey ] = 1; // FE-B: a proven site wins the merged row (Graph::outNameOnly) if( anyMarkdownLink ) { - noteMarkdownLinkSite( linkEdges, realCallEdges, ekey, r ); + noteMarkdownLinkSite( linkEdges, ekey, r ); if( siteIndex < mdSlugMiss.size() && mdSlugMiss[ siteIndex ] != 0 && siteIndex < mdLinkPin.size() && to == mdLinkPin[ siteIndex ] ) { anchorMissEdges[ ekey ] = 1; @@ -9303,22 +9344,23 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null { g.outProv.assign( edges.size(), 0u ); } - if( graphHasMarkdownLink( edges, linkEdges, realCallEdges ) ) - { - g.outLink.assign( edges.size(), 0u ); - } const auto anchorMissed = [ & ]( std::uint64_t k ) { - return markdownLinkBit( linkEdges, realCallEdges, k ) != 0 && anchorMissEdges.contains( k ); + return markdownLinkBit( linkEdges, k ) != 0 && anchorMissEdges.contains( k ); }; if( std::any_of( edges.begin(), edges.end(), [ & ]( const E& e ) { return anchorMissed( ( std::uint64_t( e.from ) << 32 ) | e.to ); } ) ) { g.outAnchorMiss.assign( edges.size(), 0u ); } const EdgeProvenanceSets provSets{ bindingEdges, importEdges, splitEdges, finalSegmentEdges }; - // FE-B: the hedge bit, allocated only when some edge is name-only (empty ⇒ every edge proven, nothing marked anywhere) + // Kind byte, allocated when some edge is name-only or a markdown link. Empty ⇒ every edge is a plain call. const auto hedged = [ & ]( std::uint64_t k ) { return nameOnlyEdges.contains( k ) && !provenEdges.contains( k ); }; - if( std::any_of( edges.begin(), edges.end(), [ & ]( const E& e ) { return hedged( ( std::uint64_t( e.from ) << 32 ) | e.to ); } ) ) + const auto marked = [ & ]( const E& e ) + { + const std::uint64_t k = ( std::uint64_t( e.from ) << 32 ) | e.to; + return hedged( k ) || markdownLinkBit( linkEdges, k ) != 0; + }; + if( std::any_of( edges.begin(), edges.end(), marked ) ) { g.outNameOnly.assign( edges.size(), 0u ); } @@ -9341,11 +9383,17 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null if( !g.outNameOnly.empty() ) { ASSUME( pos < g.outNameOnly.size(), "outNameOnly is allocated one slot per flattened edge" ); - g.outNameOnly[ pos ] = hedged( ( std::uint64_t( e.from ) << 32 ) | e.to ) ? 1u : 0u; - } - if( !g.outLink.empty() ) - { - g.outLink[ pos ] = markdownLinkBit( linkEdges, realCallEdges, ( std::uint64_t( e.from ) << 32 ) | e.to ); + const std::uint64_t key = ( std::uint64_t( e.from ) << 32 ) | e.to; + std::uint8_t kind = 0; + if( markdownLinkBit( linkEdges, key ) != 0 ) + { + kind = anchorMissed( key ) ? std::uint8_t( kEdgeLink | 0x80 ) : kEdgeLink; + } + else if( hedged( key ) ) + { + kind = kEdgeName; + } + g.outNameOnly[ pos ] = kind; } if( !g.outAnchorMiss.empty() ) { @@ -12281,6 +12329,17 @@ inline bool impactDirectLink( const Graph& g, std::span seeds, con { return n < depth.size() && depth[ n ] == 1 && oneHopRowIsLink( g, seeds, n, true ); } +inline std::string impactViaLinkNote( const std::vector& via, std::size_t begin, std::size_t end ) +{ + for( std::size_t i = begin; i < end && i < via.size(); ++i ) + { + if( via[ i ] == 2 ) + { + return viaLinkNoteJson(); + } + } + return {}; +} // via="link" plus anchor="missing" when that direct edge named no heading. The && stays here so the impact emitters do not grow a branch. inline std::string impactLinkAttrs( const Graph& g, const std::vector& proven, std::span seeds, @@ -12294,6 +12353,15 @@ inline std::string impactLinkAttrs( const Graph& g, const std::vector& pro } return attrs; } +inline std::string impactLinkLegendFor( const Graph& g, std::span seeds, const std::vector& depth, + const std::vector& show, std::size_t begin, std::size_t end ) +{ + const bool any = std::any_of( show.begin() + std::ptrdiff_t( begin ), show.begin() + std::ptrdiff_t( end ), [ & ]( NodeId n ) + { + return impactDirectLink( g, seeds, depth, n ); + } ); + return viaLinkLegend( any ); +} inline std::string impactAnchorLegendFor( const Graph& g, std::span seeds, const std::vector& depth, const std::vector& show, std::size_t begin, std::size_t end ) { diff --git a/src/graphlegend.h b/src/graphlegend.h index a5a7a4e46..5a7642be4 100644 --- a/src/graphlegend.h +++ b/src/graphlegend.h @@ -1163,13 +1163,18 @@ inline constexpr const char* kViaNameImpactClause = "An impact row reached only through such an edge carries it too; one that some all-proven path reaches stays plain. "; inline constexpr const char* kViaNameColumnarClause = "format=columnar carries it as the column (1 = via=\"name\"). "; inline constexpr const char* kViaLinkLegend = - "via=\"link\" on a row: the edge is a markdown link, not a call. A real call on the same pair keeps the row unmarked. "; + "via=\"link\" on a row: the edge is a markdown link, not a call. "; inline constexpr const char* kViaLinkColumnarClause = "format=columnar carries it as the column (2 = via=\"link\"). "; inline constexpr const char* kAnchorMissLegend = "anchor=\"missing\" on a row: the markdown link named a heading this file does not have, so the edge is the file. "; inline std::string viaLinkLegend( bool present ) { - return present ? std::string( kViaLinkLegend ) : std::string(); + std::string sentence; + if( present ) + { + sentence.assign( kViaLinkLegend ); + } + return sentence; } inline std::string anchorMissLegend( bool present ) { @@ -1240,7 +1245,7 @@ inline std::string viaLinkLegendComment() inline constexpr std::string_view kMapViaNameLegend = ""; inline constexpr std::string_view kMapViaLinkLegend = - ""; + ""; inline constexpr std::string_view kMapAnchorMissLegend = ""; inline std::string anchorMissLegendComment() diff --git a/src/ingest_docs.h b/src/ingest_docs.h index d62f0c86f..f0e88c4e9 100644 --- a/src/ingest_docs.h +++ b/src/ingest_docs.h @@ -191,6 +191,23 @@ inline void mdWalk( TSNode node, std::string_view src, bool inQuote, std::uint32 } // namespace mdtier +inline void lowercaseMarkdownSlug( std::string& written ) +{ + const std::size_t frag = written.find( '#' ); + if( frag == std::string::npos ) + { + return; + } + for( std::size_t k = frag + 1; k < written.size(); ++k ) + { + char& c = written[ k ]; + if( c >= 'A' && c <= 'Z' ) + { + c = char( c - 'A' + 'a' ); + } + } +} + void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_view stem, TSNode root, std::vector& defs, std::vector& refs ) { @@ -321,14 +338,7 @@ void extractMarkdown( std::uint32_t fileId, std::string_view src, std::string_vi // safe in the cache; the (file, slug) heading it names is resolved in buildGraph, where every file's // headings exist (graph.h bindMarkdownLinks). buildGraph reads this path. A member call still reads it beside memberCall. r.memberPath.assign( writtenTarget ); - if( const std::size_t frag = r.memberPath.find( '#' ); frag != std::string::npos ) - { - for( std::size_t k = frag + 1; k < r.memberPath.size(); ++k ) - { - char& c = r.memberPath[ k ]; - c = ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : c; - } - } + lowercaseMarkdownSlug( r.memberPath ); refs.push_back( std::move( r ) ); } }; diff --git a/src/main.cpp b/src/main.cpp index 493b34c6a..f80d1ee76 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2041,7 +2041,7 @@ int runDefaultMap( const MainDispatch& d ) { return {}; } - serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ), &g.outAnchorMiss ); + serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls ); const rw::MemoryStreamBytes measured = probe.finish(); return measured.isWhole ? std::string( measured.bytes ) : std::string(); }; @@ -2054,7 +2054,7 @@ int runDefaultMap( const MainDispatch& d ) DISCLOSE( maxTokensFit, rw::MapAnnotations::MaxTokensFit::DisclosureWhy::ProbeUnmeasured, "runDefaultMap: open_memstream failed for the --max-tokens fit probe — the map is emitted unshaped and its ceiling unverified" ); return 0; } - serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ), &g.outAnchorMiss ); + serialize( m, ing, rank, g.outOff, g.outTargets, k, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, /*outEstTokens=*/nullptr, extraPayloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls ); const rw::MemoryStreamBytes measured = probe.finish(); if( !measured.isWhole ) { @@ -2444,7 +2444,7 @@ int runDefaultMap( const MainDispatch& d ) { bodiesSection = rw::chargeSection( [ & ]( std::FILE* f ) { expandRecord = EmittedBodies{}; // a re-render starts from empty: the record is what THIS render emitted - packBodies( f, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, + packBodies( f, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr ), &g.outAnchorMiss ), cfg.compress, redactPtr, expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/&expandRecord, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); }, // V1: octocode F2 sibs=/inc= rw::kBytesPerTokenBody ); @@ -2826,7 +2826,7 @@ int runDefaultMap( const MainDispatch& d ) // not as the literal source token, for that same reason (the count is a naive grep over this file). if( !expandNodes.empty() && noteAppliesToBundle ) // !serveWholeFile is this whole branch's precondition (see the if above) { - emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, + emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr ), &g.outAnchorMiss ), cfg.compress, redactPtr, expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); } ); bodiesEmittedEarly = true; @@ -2854,7 +2854,7 @@ int runDefaultMap( const MainDispatch& d ) } else { - serialize( out, ing, rank, g.outOff, g.outTargets, mapTopK, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, &mapEstTokens, payloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls, rw::linkOutPtr( g ), &g.outAnchorMiss ); + serialize( out, ing, rank, g.outOff, g.outTargets, mapTopK, cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, cfg.stable, mapProvPtr, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, mapAutoOrder, &mapEstTokens, payloadTokens, mapAnn, /*statsFirstScreen=*/false, mapRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, g.gateDeclinedCalls, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, g.stdMemberDeclinedCalls, g.inertDeclinedCalls ); } } else @@ -2900,7 +2900,7 @@ int runDefaultMap( const MainDispatch& d ) } if( !expandNodes.empty() && !serveWholeFile && !bodiesEmittedEarly ) // #289: already served ahead of the map below { - emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, + emitSection( bodiesSection, [ & ]{ packBodies( out, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, nullptr, nullptr ), &g.outAnchorMiss ), cfg.compress, redactPtr, expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); } ); // L3: --expand bodies surface notes; V1: sibs=/inc= } diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 5c4612e56..1fd54230c 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -454,7 +454,7 @@ inline std::string analyzeToString( const std::string& root, int topK, bool stab // §B4 family, and mcpclidiffcheck is the gate that keeps the two surfaces one. /*ann=*/rw::MapAnnotations{ .prDisclosure = ix.prDisclosure, .codeFirstRows = ix.isCleanWorkingSet, .inventoryTier = ix.isCleanWorkingSet }, /*statsFirstScreen=*/true, anRootArg, &ix.g.locPinOut, ix.g.externalCalls, &ix.g.declinedOut, ix.g.gateDeclinedCalls, - ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls, rw::linkOutPtr( ix.g ), &ix.g.outAnchorMiss ); } ); + ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls ); } ); } // `rank_by` verb (lane/t10-mcp-coverage): the MCP twin of --rank-by=pagerank|authority|hub|rrf — the SAME @@ -522,7 +522,7 @@ inline std::string rankByText( const std::string& root, std::string_view mode, i /*extraPayloadTokens=*/0, /*ann=*/rw::MapAnnotations{ .rankByLabel = rankByLabel, .prDisclosure = disclosure, .codeFirstRows = rankByLabel == nullptr, .inventoryTier = rankByLabel == nullptr }, // pagerank: the map scope's code-first pick /*statsFirstScreen=*/true, rbRootArg, &ix.g.locPinOut, ix.g.externalCalls, &ix.g.declinedOut, ix.g.gateDeclinedCalls, - ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls, rw::linkOutPtr( ix.g ), &ix.g.outAnchorMiss ); } ); + ix.g.outNameOnly.empty() ? nullptr : &ix.g.outNameOnly, ix.g.stdMemberDeclinedCalls, ix.g.inertDeclinedCalls ); } ); } // ─── the cross-branch + dark-content MCP twins (`whereis`, `stray_content`, `flags`) ─── @@ -723,6 +723,35 @@ inline std::string symbolQueryDefTableJson( const IngestResult& ing, const DefGr return out; } +inline std::string symbolQueryNextField( bool referencingOnly, const PageWindow& pwPrimary, std::size_t rowTotal, const McpPageArgs& page, + std::string_view name, std::string_view nextSelector ) +{ + if( referencingOnly && pwPrimary.end < rowTotal ) + { + return nextFieldJson( callersPageNext( name, page.limit, pwPrimary.end ) ) + + nextFieldJson( nextFlag( "--uses=", nextSelector ), "uses_next" ); + } + return nextFieldJson( nextFlag( referencingOnly ? "--uses=" : "--expand=", nextSelector ) ); +} + +inline std::string symbolQueryNotes( bool anyVia, bool anyLink, bool anyMiss ) +{ + std::string out; + if( anyVia ) + { + out += viaNameNoteJson(); + } + if( anyLink ) + { + out += viaLinkNoteJson(); + } + if( anyMiss ) + { + out += anchorMissNoteJson(); + } + return out; +} + inline std::string symbolQueryJson( const std::string& root, const std::string& name, bool referencingOnly, McpPageArgs page = {} ) { @@ -852,9 +881,7 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& + declinedSiteCutKeyJson( sqDcCut ) // the CLI root's dc_shown= / dc_next=, absent when nothing was cut + declinedIfaceKeyJson( chRows.declinedIface ) // the CLI root's declined_iface= (callers direction only) + stdMemberCallsSpelling( chRows.stdCalls, /*json=*/true ) // the CLI callees answer's line (callees direction only; absent when none) - + ( referencingOnly && pwPrimary.end < rowTotal // L8: a capped callers page names the next page, the uses verb as uses_next (the CLI root's pair) - ? nextFieldJson( callersPageNext( name, page.limit, pwPrimary.end ) ) + nextFieldJson( nextFlag( "--uses=", chNextSelector ), "uses_next" ) - : nextFieldJson( nextFlag( referencingOnly ? "--uses=" : "--expand=", chNextSelector ) ) ); // P3 (L7): the CLI root's next= (mcpattrparitycheck) + + symbolQueryNextField( referencingOnly, pwPrimary, rowTotal, page, name, chNextSelector ); // P3 (L7): the CLI root's next= (mcpattrparitycheck) if( !referencingOnly && chRows.bodylessDefs > 0 ) { out += ",\"bodyless_defs\":" + std::to_string( chRows.bodylessDefs ); @@ -887,18 +914,7 @@ inline std::string symbolQueryJson( const std::string& root, const std::string& { out += ",\"calls\":" + rowArray( calls, pwPrimary, false ); } - if( anyVia ) - { - out += rw::viaNameNoteJson(); // FE-B: the reading of "via":"name", only in an answer that carries it - } - if( anyLink ) - { - out += rw::viaLinkNoteJson(); - } - if( anyMiss ) - { - out += rw::anchorMissNoteJson(); - } + out += symbolQueryNotes( anyVia, anyLink, anyMiss ); out += nextFieldJson( splitArmLegend( anySplit ), "split_note" ); // attrib-multidef Q1: likewise for "prov":"split" out += declinedSitesJson( sqDc, referencingOnly, /*mcp=*/true ); // lane declined-sites: the CLI rows, never in calls/calledBy or count out += nextFieldJson( declinedSitesLegend( declinedSitesLegendFlags( sqDc, referencingOnly, sqDcCut ), /*mcp=*/true ), "declined_note" ); // their reading, only when they ride @@ -2233,7 +2249,7 @@ inline McpForAssembly assembleMcpFor( const McpForAssemblyInputs& in, std::strin share = forSoftSectionsShare( budgetBytesForTokens( budgetTokens ), mcpHdrXml.size() + atCut + legoStr.size() + composeStr.size() + routeStr.size() ); } mcpSlot = renderForBodySlot( BodySlotInputs{ ing, in.g.outOff, in.g.outTargets, in.g.outNameOnly, lensRank, flRootArg, redact, notesPtr, - /*viaLegendInHead=*/false, /*compress=*/false, &in.g.outProv, /*splitLegendInHead=*/false, linkOutPtr( in.g ), &in.g.outAnchorMiss }, + /*viaLegendInHead=*/false, /*compress=*/false, &in.g.outProv, /*splitLegendInHead=*/false, &in.g.outAnchorMiss }, in.slotOwner, share ); } mcpForSoftRefill( in, renderSigs, sigsStr, mcpSigsCut, &mcpSigsNext, @@ -3605,6 +3621,32 @@ inline McpAffectedResult affectedText( const std::string& root, std::string_view return { mcpAnswerText( stream ), false, {}, false }; } +inline std::string pathKindLegend( const Graph& g, std::span path ) +{ + return std::string( viaNameLegend( pathHasNameOnlyHop( g, path ) ) ) + + viaLinkLegend( pathHasNameOnlyHop( g, path, kEdgeLink ) ) + + anchorMissLegend( pathHasNameOnlyHop( g, path, 0 ) ); +} + +inline void emitPathHopRows( std::FILE* mem, const IngestResult& ing, const Graph& g, std::span pth, + bool singleRoot, std::string_view rootPrefix ) +{ + std::vector esc; + for( std::size_t i = 0; i < pth.size(); ++i ) + { + const Symbol& s = ing.symbols[ pth[ i ] ]; + const std::string_view rp = singleRoot ? sarif::rootRelativeUri( ing.files[ s.fileId ], rootPrefix ) + : std::string_view( ing.files[ s.fileId ] ); + const bool name = i > 0 && edgeNameOnly( g, pth[ i - 1 ], pth[ i ] ); + const bool link = i > 0 && edgeIsLink( g, pth[ i - 1 ], pth[ i ] ); + const std::string mark = std::string( viaEdgeAttr( name, link ) ) + + anchorMissAttr( i > 0 && edgeAnchorMiss( g, pth[ i - 1 ], pth[ i ] ) ); + rw::emitTo( mem, "", symTag( s.kind ), + std::string( escapeXml( s.name, esc ) ).c_str(), std::string( escapeXml( rp, esc ) ).c_str(), s.line, + mark.c_str(), splitArmAttrXml( pathHopSplitArm( g, pth, i ) ) ); + } +} + // `path` verb: the shortest directed CALL path from `from` to `to` (does A reach B, and how?). Reuses // 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), @@ -3658,15 +3700,11 @@ inline std::optional pathText( const std::string& root, const std:: const ToValueRefs ptToValueRefs = toValueRefs( ing, pth.empty(), dstDefs, &valueRefIndexOf( ix ) ); // PATH-GAP: the CLI --path's gap clause, by the same analysis and emitter (src/pathgaps.h). const PathSearchGaps ptGaps = pth.empty() ? pathSearchGaps( ing, g, srcDefs, valueRefIndexOf( ix ) ) : PathSearchGaps{}; - const bool ptHasVia = pathHasNameOnlyHop( g, pth ); // FE-B: exactly when a hop of this path is via="name", as on the CLI - const bool ptHasLink = pathHasLinkHop( g, pth ); - const bool ptHasMiss = std::adjacent_find( pth.begin(), pth.end(), - [ & ]( NodeId from, NodeId to ) { return edgeAnchorMiss( g, from, to ); } ) != pth.end(); - const PathEndpointBinding ptBound = pathEndpointBinding( g, endpointDefinitionCount( ing, srcDefs ), endpointDefinitionCount( ing, dstDefs ), pth ); // attrib-multidef, as on the CLI + const std::string ptKindLegend = pathKindLegend( g, pth ); + const PathEndpointBinding ptBound = pathEndpointBinding( g, endpointDefinitionCount( ing, srcDefs ), endpointDefinitionCount( ing, dstDefs ), pth ); // attrib-multidef, as on the CLI const bool ptHasSplit = ptBound.splitHop; rw::emitTo( mem, "{}", ( unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + viaNameLegend( ptHasVia ) - + viaLinkLegend( ptHasLink ) + anchorMissLegend( ptHasMiss ) + "graph holds none. {}{}{}{}-->{}", ( unprovenDefsVerbLegend( UnprovenDefsVerb::Path, unprovenDefs > 0 ) + ptKindLegend + splitArmLegend( ptHasSplit ) + pathUnboundLegend( !ptBound.from || !ptBound.to ) ).c_str(), ( std::string( toValueRefsLegend( ptToValueRefs.count > 0 ) ) + valueRefsDepthLegend( ptToValueRefs.depthCut.files > 0 ) ).c_str(), pathGapsLegend( ptGaps.any() ), @@ -3683,12 +3721,7 @@ inline std::optional pathText( const std::string& root, const std:: ? pathUnreachedXml( ing, ptGaps, ptSingleRoot, ptRootPrefix, { "the connect verb on " + from + "," + to, "uses/impact", "" } ) : PathGapsXml{}; rw::emitTo( mem, "{}>{}", ptUnreached.rootAttrs, ptUnreached.rows ); - for( std::size_t i = 0; i < pth.size(); ++i ) - { const Symbol& s = ing.symbols[ pth[i] ]; - const std::string_view rp = ptSingleRoot ? sarif::rootRelativeUri( ing.files[ s.fileId ], ptRootPrefix ) : std::string_view( ing.files[ s.fileId ] ); - rw::emitTo( mem, "", symTag( s.kind ), ex( s.name ).c_str(), ex( rp ).c_str(), s.line, - std::string( viaEdgeAttr( i > 0 && edgeNameOnly( g, pth[ i - 1 ], pth[ i ] ), i > 0 && edgeIsLink( g, pth[ i - 1 ], pth[ i ] ) ) ) - + anchorMissAttr( i > 0 && edgeAnchorMiss( g, pth[ i - 1 ], pth[ i ] ) ), splitArmAttrXml( pathHopSplitArm( g, pth, i ) ) ); } // FE-B; a link hop says via="link" + emitPathHopRows( mem, ing, g, pth, ptSingleRoot, ptRootPrefix ); 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 5c682abb9..4d002303e 100644 --- a/src/pathgaps.h +++ b/src/pathgaps.h @@ -106,14 +106,14 @@ inline void countUnlistedNamesakes( const IngestResult& ing, const Graph& g, std }; const auto keyEq = []( const Key& a, const Key& b ) { return a.lang == b.lang && a.kind == b.kind && a.name == b.name; }; const auto keyOf = [ & ]( NodeId t ) { const Symbol& s = ing.symbols[t]; return Key{ s.lang, s.kind, s.name }; }; - const auto hasHedge = [ & ]( NodeId u ) { return std::any_of( g.outNameOnly.begin() + g.outOff[u], g.outNameOnly.begin() + g.outOff[u + 1], []( std::uint8_t b ) { return b != 0; } ); }; + const auto hasHedge = [ & ]( NodeId u ) { return std::any_of( g.outNameOnly.begin() + g.outOff[u], g.outNameOnly.begin() + g.outOff[u + 1], []( std::uint8_t b ) { return edgeKindByte( b ) == kEdgeName; } ); }; const auto isSym = [ & ]( std::uint32_t k ) { return g.outTargets[k] < ing.symbols.size(); }; std::vector names; for( const NodeId u : cone ) { for( std::uint32_t k = g.outOff[u]; k < g.outOff[u + 1]; ++k ) { - if( g.outNameOnly[k] != 0 && isSym( k ) ) + if( edgeKindByte( g.outNameOnly[k] ) == kEdgeName && isSym( k ) ) { names.push_back( ing.symbols[ g.outTargets[k] ].name ); } @@ -156,7 +156,7 @@ inline void countUnlistedNamesakes( const IngestResult& ing, const Graph& g, std std::vector hedged; // the targets u reached by name alone for( std::uint32_t k = g.outOff[u]; k < g.outOff[u + 1]; ++k ) { - if( isSym( k ) && g.outNameOnly[k] != 0 ) + if( isSym( k ) && edgeKindByte( g.outNameOnly[k] ) == kEdgeName ) { hedged.push_back( keyOf( g.outTargets[k] ) ); } diff --git a/src/serialize.h b/src/serialize.h index 2c22a9a2b..2bb22ddb9 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -2635,25 +2635,46 @@ inline std::uint32_t counterAt( const std::vector* v, NodeId id ) return ( v && id < v->size() ) ? ( *v )[ id ] : 0u; } -// FE-B: one map 's rows over out-edges [from, to). A via="name" row (matched by name alone) that the symbol would -// print several times byte for byte — N same-named by-name candidates, one row each, carrying only n=/prov=/via= — is -// written ONCE, at its first edge, with x="N": the count, and --callees on the lists each candidate with its file -// (graphlegend.h kMapViaNameLegend / compactlegend.h's x row). Every other row, prov="split" arms included, is written as -// before. `scratch` is reused across symbols. -inline void writeMapAnchorAttr( XmlWriter& w, const std::vector* bits, std::uint32_t e ) +// The row's prov=, via=, x= and anchor="missing" tail. One writer so the row loop stays a scan. +inline void writeMapCalleeAttrs( XmlWriter& w, std::uint8_t prov, bool link, bool nameOnly, std::size_t repeats, bool miss ) { - if( bits != nullptr && e < bits->size() && ( *bits )[ e ] != 0 ) + if( prov != 0 ) + { + w.write( "\" prov=\"" ); + w.write( provLabel( prov ) ); + } + if( link ) + { + w.write( "\" via=\"link" ); + } + else if( nameOnly ) + { + w.write( "\" via=\"name" ); + } + if( repeats > 1 ) + { + w.write( "\" x=\"" ); + w.write( std::to_string( repeats ) ); + } + if( miss ) { w.write( "\" anchor=\"missing" ); } } + +// FE-B: one map 's rows over out-edges [from, to). A via="name" row (matched by name alone) that the symbol would +// print several times byte for byte — N same-named by-name candidates, one row each, carrying only n=/prov=/via= — is +// written ONCE, at its first edge, with x="N": the count, and --callees on the lists each candidate with its file +// (graphlegend.h kMapViaNameLegend / compactlegend.h's x row). Every other row, prov="split" arms included, is written as +// before. `scratch` is reused across symbols. inline void writeMapCalleeRows( XmlWriter& w, const IngestResult& ing, std::uint32_t from, std::uint32_t to, const std::vector& outTargets, const std::vector* outProv, const std::vector* outNameOnly, std::vector& esc, - std::vector& scratch, const std::vector* outLink = nullptr, const std::vector* outAnchor = nullptr ) + std::vector& scratch ) { const auto provOf = [ & ]( std::uint32_t e ) -> std::uint8_t { return ( outProv && e < outProv->size() ) ? ( *outProv )[ e ] : 0; }; - const auto nameOnly = [ & ]( std::uint32_t e ) { return outNameOnly && e < outNameOnly->size() && ( *outNameOnly )[ e ] != 0; }; - const auto link = [ & ]( std::uint32_t e ) { return outLink && e < outLink->size() && ( *outLink )[ e ] != 0; }; + const auto stored = [ & ]( std::uint32_t e ) -> std::uint8_t { return outNameOnly && e < outNameOnly->size() ? ( *outNameOnly )[ e ] : 0; }; + const auto nameOnly = [ & ]( std::uint32_t e ) { return edgeKindByte( stored( e ) ) == kEdgeName; }; + const auto link = [ & ]( std::uint32_t e ) { return edgeKindByte( stored( e ) ) == kEdgeLink; }; // the row's bytes among via="name" rows: name, prov= const auto rowLess = [ & ]( std::uint32_t x, std::uint32_t y ) { @@ -2691,33 +2712,16 @@ inline void writeMapCalleeRows( XmlWriter& w, const IngestResult& ing, std::uint // uniquely resolved (the common case → zero token cost). outProv parallels outTargets exactly. C1, and this is the // whole point of the marker: `amb="K"` on the enclosing says K of this symbol's CALLS were guesses and cannot // say WHICH edges; prov="split" names the arms, and the suspect set becomes the guessed edges and nothing else. - if( provOf( e ) != 0 ) - { - w.write( "\" prov=\"" ); - w.write( provLabel( provOf( e ) ) ); - } - if( link( e ) ) - { - w.write( "\" via=\"link" ); // a pinned markdown link, and no real call on this pair - } - else if( nameOnly( e ) ) - { - w.write( "\" via=\"name" ); // FE-B: the same edge bit every other surface reads (viaNameLegendComment) - } - if( repeats > 1 ) - { - w.write( "\" x=\"" ); - w.write( std::to_string( repeats ) ); - } - writeMapAnchorAttr( w, outAnchor, e ); + writeMapCalleeAttrs( w, provOf( e ), link( e ), nameOnly( e ), repeats, ( stored( e ) & 0x80 ) != 0 ); w.write( "\"/>" ); } } // A row under one of `nodes` may carry the bit when any of their out-edges has it. Asked before the rows are written // so the map's first-screen legend can define the attribute. outLink and outNameOnly share this scan. +// want 0: any nonzero byte. want 0x80: the anchor-missing high bit. Otherwise the kind byte equals want. inline bool markedOutAny( const std::vector& outOff, const std::vector& bits, - std::span nodes ) noexcept + std::span nodes, std::uint8_t want = 0 ) noexcept { for( const NodeId n : nodes ) { @@ -2727,7 +2731,8 @@ inline bool markedOutAny( const std::vector& outOff, const std::v } for( std::uint32_t e = outOff[ n ]; e < outOff[ n + 1 ] && e < bits.size(); ++e ) { - if( bits[ e ] != 0 ) + const bool hit = want == 0 ? bits[ e ] != 0 : ( want == 0x80 ? ( bits[ e ] & 0x80 ) != 0 : edgeKindByte( bits[ e ] ) == want ); + if( hit ) { return true; } @@ -2735,11 +2740,6 @@ inline bool markedOutAny( const std::vector& outOff, const std::v } return false; } -inline bool linksOutAny( const std::vector& outOff, const std::vector& outLink, - std::span nodes ) noexcept -{ - return markedOutAny( outOff, outLink, nodes ); -} // FE-B: may a row under one of `nodes` carry via="name"? True when any of their out-edges is name-only — an // over-approximation of what a section renders (a block's 16-per-symbol cap may cut that row; a merged overload // row prints one representative's edges), asked BEFORE the rows are written by a surface whose first-screen legend must @@ -2747,7 +2747,7 @@ inline bool linksOutAny( const std::vector& outOff, const std::ve inline bool namesOnlyOutAny( const std::vector& outOff, const std::vector& outNameOnly, std::span nodes ) noexcept { - return markedOutAny( outOff, outNameOnly, nodes ); + return markedOutAny( outOff, outNameOnly, nodes, kEdgeName ); } // attrib-multidef Q1: may a row under one of `nodes` carry prov="split"? True when any of their out-edges is one arm of a @@ -2761,7 +2761,7 @@ inline bool splitArmsOutAny( const std::vector& outOff, const std for( std::uint32_t e = ( std::size_t( n ) + 1 < outOff.size() ) ? outOff[ n ] : 0u; std::size_t( n ) + 1 < outOff.size() && e < outOff[ n + 1 ] && e < outProv.size(); ++e ) { - if( outProv[ e ] == 3u && !( e < outNameOnly.size() && outNameOnly[ e ] != 0 ) ) + if( outProv[ e ] == 3u && !( e < outNameOnly.size() && edgeKindByte( outNameOnly[ e ] ) == kEdgeName ) ) { return true; } @@ -2788,7 +2788,7 @@ inline bool hopSlotHasProvenEdge( const std::vector& outOff, cons } ASSUME( outOff[ id + 1 ] <= outNameOnly.size(), "the hedge bits are one per flattened edge (graph.h buildGraph)" ); return !std::all_of( outNameOnly.begin() + outOff[ id ], outNameOnly.begin() + outOff[ id + 1 ], - []( std::uint8_t bit ) { return bit != 0; } ); + []( std::uint8_t bit ) { return edgeKindByte( bit ) == kEdgeName; } ); } // D7 MAP-INVENTORY: whether THIS map carries the inventory tier (mapinventory.h), decided once for both dialects. The @@ -2807,9 +2807,7 @@ template inline void writeMapInventoryXml( XmlWriter& w, const IngestResult& ing, const MapInventory& inv, const PathRel& pathRel, const std::vector& outOff, const std::vector& outTargets, const std::vector* outProv, const std::vector* outNameOnly, - std::vector& esc, std::vector& scratch, - const std::vector* outLink = nullptr, - const std::vector* outAnchor = nullptr ) + std::vector& esc, std::vector& scratch ) { EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( "" ); - writeMapCalleeRows( w, ing, outOff[ id ], outOff[ id + 1 ], outTargets, outProv, outNameOnly, esc, scratch, outLink, outAnchor ); + writeMapCalleeRows( w, ing, outOff[ id ], outOff[ id + 1 ], outTargets, outProv, outNameOnly, esc, scratch ); w.write( "" ); } for( const MapInventoryDir& d : inv.dirs ) @@ -2920,11 +2918,7 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto std::size_t stdMemberDeclinedCalls = 0, // of declined=, the Elixir call candidates inside inert quote AST (g.inertDeclinedCalls, #380) → // the kInertDeclinedMapLegend clause, absent when zero. - std::size_t inertDeclinedCalls = 0, - // Markdown link edges (Graph::outLink) → via="link" on a row and kMapViaLinkLegend. nullptr/empty - // ⇒ the map has no link edge and stays byte-identical. - const std::vector* outLink = nullptr, - const std::vector* outAnchor = nullptr ) + std::size_t inertDeclinedCalls = 0 ) { const std::size_t* changedCount = ann.changedCount; const std::string* mapAtStamp = ann.atStamp; @@ -3179,13 +3173,13 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto // and whose element-qualified via/x rows restate it: the --max-tokens fit then prices what is delivered. legend += kMapViaNameLegend; } - if( outLink && !stubbed && !ann.viaLegendStripped - && linksOutAny( outOff, *outLink, std::span( order.data(), std::min( keep, order.size() ) ) ) ) + if( outNameOnly && !stubbed && !ann.viaLegendStripped + && markedOutAny( outOff, *outNameOnly, std::span( order.data(), std::min( keep, order.size() ) ), kEdgeLink ) ) { legend += kMapViaLinkLegend; } - if( outAnchor && !stubbed && !ann.viaLegendStripped - && markedOutAny( outOff, *outAnchor, std::span( order.data(), std::min( keep, order.size() ) ) ) ) + if( outNameOnly && !stubbed && !ann.viaLegendStripped + && markedOutAny( outOff, *outNameOnly, std::span( order.data(), std::min( keep, order.size() ) ), 0x80 ) ) { legend += kMapAnchorMissLegend; } @@ -3483,7 +3477,7 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto { if( inventory.active ) { - writeMapInventoryXml( w, ing, inventory, pathRel, outOff, outTargets, outProv, outNameOnly, esc, hedgedScratch, outLink, outAnchor ); + writeMapInventoryXml( w, ing, inventory, pathRel, outOff, outTargets, outProv, outNameOnly, esc, hedgedScratch ); } }; const bool inventoryFirst = !stable && effImportantLast; // --stable's path order wins, as it does for the rows @@ -3704,7 +3698,7 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto } w.write( ">" ); - writeMapCalleeRows( w, ing, outOff[id], outOff[id + 1], outTargets, outProv, outNameOnly, esc, hedgedScratch, outLink, outAnchor ); + writeMapCalleeRows( w, ing, outOff[id], outOff[id + 1], outTargets, outProv, outNameOnly, esc, hedgedScratch ); w.write( "" ); } w.write( "" ); @@ -6736,10 +6730,20 @@ struct MergedCalleeNameRow bool miss = false; // that link named no heading — merged only with other miss rows of the same name }; +struct CalleeRowMarks +{ + bool via = false; + bool split = false; + bool link = false; + bool miss = false; +}; inline void collectCalleeNameRow( std::vector& rows, const Symbol& cs, - std::size_t& used, const CalleeCallsSink& sink, bool via = false, bool split = false, bool link = false, - bool miss = false ) + std::size_t& used, const CalleeCallsSink& sink, CalleeRowMarks mark = {} ) { + const bool via = mark.via; + const bool split = mark.split; + const bool link = mark.link; + const bool miss = mark.miss; char lb[ 16 ]; rw::formatTo( lb, sizeof( lb ), "{}", cs.line ); bool merged = false; @@ -6816,8 +6820,8 @@ struct CalleeEdgeBytes const std::vector* link = nullptr; const std::vector* anchor = nullptr; CalleeEdgeBytes( const std::vector& nameOnlyBytes, const std::vector* provBytes = nullptr, // NOLINT: implicit - bool* charged = nullptr, const std::vector* linkBytes = nullptr ) noexcept - : nameOnly( nameOnlyBytes ), prov( provBytes ), splitLegendCharged( charged ), link( linkBytes ) {} + bool* charged = nullptr ) noexcept + : nameOnly( nameOnlyBytes ), prov( provBytes ), splitLegendCharged( charged ) {} }; inline CalleeEdgeBytes withAnchorEdge( CalleeEdgeBytes edges, const std::vector* anchor ) noexcept { @@ -6911,6 +6915,60 @@ inline void applyCalleeAnchorMiss( const CalleeCallsSink& sink, const std::vecto } } +// One of a body's callee block. True when the row counts toward shown=. +template +inline bool appendOneCalleeCall( std::string& callsBody, std::vector& nameRows, NodeId cid, NodeId id, + const std::vector& outOff, const std::vector& outTargets, + const IngestResult& ing, ContentOfFn&& contentOf, std::vector& esc, std::size_t& used, + const CalleeCallsSink& sink, bool& linkLegendCharged, bool& anchorLegendCharged ) +{ + if( cid >= ing.symbols.size() ) + { + return false; + } + const Symbol& cs = ing.symbols[cid]; + const bool linkRow = sink.nameOnly != nullptr && edgeNameOnlyInCsr( outOff, outTargets, *sink.nameOnly, id, cid, kEdgeLink ); + if( linkRow && !linkLegendCharged ) + { + used += viaLinkLegendComment().size(); + linkLegendCharged = true; + } + applyCalleeAnchorMiss( sink, outOff, outTargets, id, cid, anchorLegendCharged, used, nullptr, {}, false, nullptr ); + const bool nameRow = !linkRow && calleeRowVia( outOff, outTargets, id, cid, used, sink ); + const std::string_view viaAttr = viaEdgeAttr( nameRow, linkRow ); + const std::string_view splitAttr = calleeRowSplitAttr( outOff, outTargets, id, cid, !viaAttr.empty(), used, sink ); + if( sink.namesOnly ) + { + collectCalleeNameRow( nameRows, cs, used, sink, CalleeRowMarks{ nameRow, !splitAttr.empty(), linkRow, false } ); + applyCalleeAnchorMiss( sink, outOff, outTargets, id, cid, anchorLegendCharged, used, &nameRows, cs.name, linkRow, nullptr ); + return true; + } + const std::string& csrc = contentOf( cs.fileId ); + if( cs.sigStartByte >= cs.sigEndByte || cs.sigEndByte > csrc.size() ) + { + return false; + } + const std::string sig = cleanSig( csrc.data(), cs.sigStartByte, cs.sigEndByte, sink.redact ); + if( sig.empty() ) + { + return false; + } + char hb[ 64 ]; + rw::formatTo( hb, sizeof( hb ), "\" l=\"{}\"{}{}>", cs.line, viaAttr, splitAttr ); + callsBody += "push_back( EmittedBodyCall { cs.name, cs.line, sig } ); + } + return true; +} + // §P10.1: the disclosed block // for one body's 1-hop callee signatures — extracted out of packBodies so the disclosure logic doesn't // inflate packBodies' own complexity/LOC. `total` is outOff[id+1]-outOff[id] — outTargets is deduped-per- @@ -6924,7 +6982,6 @@ inline void emitCalleeCallsBlock( std::string& out, NodeId id, const std::vector ContentOfFn&& contentOf, std::vector& esc, std::size_t& used, std::size_t budgetBytes, const CalleeCallsSink& sink ) { - RedactCounts* const redact = sink.redact; if( id + 1 >= outOff.size() ) { return; @@ -6945,50 +7002,10 @@ inline void emitCalleeCallsBlock( std::string& out, NodeId id, const std::vector for( std::uint32_t k = outOff[id]; k < outOff[id + 1] && shown < int( sink.rowCap ) && used < budgetBytes; ++k ) { const NodeId cid = walk[ k - outOff[id] ]; - if( cid >= ing.symbols.size() ) + if( appendOneCalleeCall( callsBody, nameRows, cid, id, outOff, outTargets, ing, contentOf, esc, used, sink, + linkLegendCharged, anchorLegendCharged ) ) { - continue; - } - const Symbol& cs = ing.symbols[cid]; - const bool linkRow = sink.link != nullptr && edgeNameOnlyInCsr( outOff, outTargets, *sink.link, id, cid ); - if( linkRow && !linkLegendCharged ) - { - used += viaLinkLegendComment().size(); - linkLegendCharged = true; - } - applyCalleeAnchorMiss( sink, outOff, outTargets, id, cid, anchorLegendCharged, used, nullptr, {}, false, nullptr ); - const bool nameRow = !linkRow && calleeRowVia( outOff, outTargets, id, cid, used, sink ); - const std::string_view viaAttr = viaEdgeAttr( nameRow, linkRow ); - const std::string_view splitAttr = calleeRowSplitAttr( outOff, outTargets, id, cid, !viaAttr.empty(), used, sink ); // Q1: prov="split" or "" - - // COMPACT: the names-only rendering — see collectCalleeNameRow above for what it does and does not do. - if( sink.namesOnly ) - { - collectCalleeNameRow( nameRows, cs, used, sink, nameRow, !splitAttr.empty(), linkRow ); - applyCalleeAnchorMiss( sink, outOff, outTargets, id, cid, anchorLegendCharged, used, &nameRows, cs.name, linkRow, nullptr ); ++shown; - continue; - } - - const std::string& csrc = contentOf( cs.fileId ); - if( cs.sigStartByte >= cs.sigEndByte || cs.sigEndByte > csrc.size() ) - { - continue; - } - const std::string sig = cleanSig( csrc.data(), cs.sigStartByte, cs.sigEndByte, redact ); - if( sig.empty() ) - { - continue; - } - char hb[ 64 ]; rw::formatTo( hb, sizeof( hb ), "\" l=\"{}\"{}{}>", cs.line, viaAttr, splitAttr ); - callsBody += "push_back( EmittedBodyCall { cs.name, cs.line, sig } ); // §H5 } } appendMergedCalleeNameRows( callsBody, nameRows, esc ); // no-op on the signature rendering (nameRows stays empty) @@ -7974,7 +7991,7 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector // owner-hop: the leading owner rows first (THE QUESTION'S OWNER ROWS above; nothing when `owners` is null), then the // ranked rows exactly as before, from the node the owners end at, on the budget and via charge the owners leave them - const OwnerHopRows own = packOwnerHopRows( ing, nodes, owners, outOff, outTargets, withAnchorEdge( CalleeEdgeBytes( outNameOnly, outEdges.prov, &splitLegendCharged, outEdges.link ), outEdges.anchor ), + const OwnerHopRows own = packOwnerHopRows( ing, nodes, owners, outOff, outTargets, withAnchorEdge( CalleeEdgeBytes( outNameOnly, outEdges.prov, &splitLegendCharged ), outEdges.anchor ), redact, rank, rootArg, budgetBytes, viaLegendCharged, pathRel, contentOf, esc ); std::string children = own.xml; std::size_t requestedCount = own.shown; diff --git a/src/verbs_for.h b/src/verbs_for.h index cea67a1ee..75eb11e69 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -1981,7 +1981,7 @@ ForAutoBodiesResult buildForAutoBodies( const rw::Config& cfg, const rw::IngestR // shell the "budget" branch below already gets for free, rather than a second hand-rolled tag. out.attr = " bundle=\"auto\" bodies=\"0\" reason=\"no_candidates\""; out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packBodies( f, ing, autoBodyIds, /*budgetBytes=*/1, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, + { rw::packBodies( f, ing, autoBodyIds, /*budgetBytes=*/1, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr ), &g.outAnchorMiss ), cfg.compress, redactPtr, /*ranges=*/nullptr, /*noteIndex=*/nullptr, nullptr, /*truncateOversizedFirst=*/false, /*withFileContext=*/false, fabRootArg, &lensRank, rw::CalleeLegendsInHead( viaLegendInHead, splitLegendInHead ) ); }, rw::kBytesPerTokenBody ); @@ -2003,7 +2003,7 @@ ForAutoBodiesResult buildForAutoBodies( const rw::Config& cfg, const rw::IngestR const std::size_t autoBodyBudget = std::max( 1, std::min( leftBytes, cfg.packBudgetBytes ) ); rw::EmittedBodies autoEmitted; out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packBodies( f, ing, autoBodyIds, autoBodyBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, + { rw::packBodies( f, ing, autoBodyIds, autoBodyBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr ), &g.outAnchorMiss ), cfg.compress, redactPtr, /*ranges=*/nullptr, /*noteIndex=*/nullptr, &autoEmitted, /*truncateOversizedFirst=*/false, /*withFileContext=*/false, fabRootArg, &lensRank, rw::CalleeLegendsInHead( viaLegendInHead, splitLegendInHead ) ); }, rw::kBytesPerTokenBody ); @@ -2409,7 +2409,7 @@ ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::Ingest const rw::HopOwners owners = forHopOwners( cfg, ing, ownerPlan, leftBytes, fcRootArg ); out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packHops( f, ing, hopNodes, hopBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), redactPtr, &lensRank, fcRootArg, + { rw::packHops( f, ing, hopNodes, hopBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr ), &g.outAnchorMiss ), redactPtr, &lensRank, fcRootArg, rw::CalleeLegendsInHead( viaLegendInHead, promised.splitLegendInHead ), &owners ); }, // g.outNameOnly also feeds the hop-slot rule (serialize.h noProvenEdge) // MARKUP rate, not the body rate — and this is an honesty choice, not a copy-paste slip. The body // rate (3.80 B/tok) prices SOURCE TEXT; the compact section contains none, only tags, identifiers @@ -3725,7 +3725,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co detailBodyBudget = std::min( detailBodyBudget, std::max( leftBytes, detailFloorBudget ) ); } detailSection = rw::chargeSection( [ & ]( std::FILE* f ) - { packBodies( f, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), cfg.compress, redactPtr, + { packBodies( f, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr ), &g.outAnchorMiss ), cfg.compress, redactPtr, /*ranges=*/nullptr, notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/false, flRootArg, &lensRank, rw::CalleeLegendsInHead( headerParts.viaPresent, headerParts.splitPresent ) ); }, // L3: --detail bodies surface notes too (part of the --for bundle) rw::kBytesPerTokenBody ); @@ -3779,7 +3779,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co { const std::size_t slotSpent = rowsSpentBytes + plan.attrReserve + enrich.section.xml.size(); // the rows at the cut + the hops slot = rw::renderForBodySlot( rw::BodySlotInputs{ ing, g.outOff, g.outTargets, g.outNameOnly, lensRank, flRootArg, redactPtr, notesPtr, - headerParts.viaPresent, cfg.compress, &g.outProv, headerParts.splitPresent, rw::linkOutPtr( g ), &g.outAnchorMiss }, + headerParts.viaPresent, cfg.compress, &g.outProv, headerParts.splitPresent, &g.outAnchorMiss }, slotOwner, forSoftTarget ? std::optional( rw::forSoftSectionsShare( bundleBudget, slotSpent ) ) : std::nullopt ); if( slot.served ) @@ -3999,7 +3999,7 @@ std::optional runForLensPass( const MainDispatch& d, rw::LensRanking lr, co // directly at the SAME budget — uncharged for that one run, with an alert, never a fabricated number. if( cfg.detail > 0 ) { - rw::emitChargedSection( stdout, detailSection, [ & ]{ packBodies( stdout, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr, rw::linkOutPtr( g ) ), &g.outAnchorMiss ), + rw::emitChargedSection( stdout, detailSection, [ & ]{ packBodies( stdout, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, rw::withAnchorEdge( rw::CalleeEdgeBytes( g.outNameOnly, &g.outProv, nullptr ), &g.outAnchorMiss ), cfg.compress, redactPtr, /*ranges=*/nullptr, notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, /*withFileContext=*/false, flRootArg, &lensRank, rw::CalleeLegendsInHead( headerParts.viaPresent, headerParts.splitPresent ) ); } ); diff --git a/src/verbs_navigate.h b/src/verbs_navigate.h index 06df64eef..eb1d08304 100644 --- a/src/verbs_navigate.h +++ b/src/verbs_navigate.h @@ -77,6 +77,27 @@ inline std::string_view jsonRowKeysAt( const std::vector* rowKeys, { return rowKeys != nullptr && i < rowKeys->size() ? std::string_view( ( *rowKeys )[ i ] ) : std::string_view(); } +inline char hopViaCode( bool link, bool name ) noexcept +{ + if( link ) + { + return 2; + } + if( name ) + { + return 1; + } + return 0; +} + +inline const char* jsonViaField( const std::vector* via, std::size_t i ) noexcept +{ + if( via == nullptr || i >= via->size() || ( *via )[ i ] == 0 ) + { + return ""; + } + return ( *via )[ i ] == 2 ? ",\"via\":\"link\"" : ",\"via\":\"name\""; +} inline void printJsonSymbolRows( const rw::IngestResult& ing, const std::vector& ids, std::size_t begin, std::size_t end, std::string_view rootPrefix = {}, const std::vector* testReach = nullptr, @@ -92,8 +113,7 @@ inline void printJsonSymbolRows( const rw::IngestResult& ing, const std::vector< rw::symTag( s.kind ), rw::jsonStr( s.name ).c_str(), rw::jsonStr( p ).c_str(), s.line, depth ? ",\"d\":" + std::to_string( ( *depth )[ ids[i] ] ) : std::string(), ( testReach && rw::isTestedByReach( ing, *testReach, ids[i] ) ) ? ",\"tested\":true" : "", - ( viaName && i < viaName->size() && ( *viaName )[ i ] == 2 ) ? ",\"via\":\"link\"" - : ( ( viaName && i < viaName->size() && ( *viaName )[ i ] == 1 ) ? ",\"via\":\"name\"" : "" ), // 2 = markdown link, 1 = name hedge + jsonViaField( viaName, i ), jsonRowKeysAt( rowKeys, i ) ); } } @@ -138,6 +158,99 @@ inline int refuseNotFoundWithAnswer( const rw::IngestResult& ing, const rw::Conf return 1; } +inline int emitCallHierarchyAlt( const rw::Config& cfg, const rw::IngestResult& ing, const rw::Graph& g, const std::vector& result, + const std::vector& matches, const rw::PageWindow& pw, std::string_view sym, bool wantCallers, + std::size_t bodylessDefsCount, const std::string& chUnprovenAttr, const rw::CallHierarchyRows& chRows, + const rw::OneHopAttribution& chAttr, const rw::DefTableListing& chListing, const HopTestedPartition& chTested, + const std::string& chDeclinedAttr, const std::string& chValueRefsAttr, const std::string& chRootAttr, char* pab, + bool chDiscloseCap, const std::string& chNextAttr, const rw::RowCallSites& chSites, const std::string& chRootPrefix, + const char* tag, const std::string& chDcRows, bool chSingleRoot, const rw::VrRender& chVr, const std::string& chHintCall, + bool chNextIsPage, const rw::DeclinedSiteCut& chDcCut, const std::vector& chDc ) +{ + using namespace rw; + std::vector esc; + const auto ex = [ & ]( std::string_view s ) -> std::string { return std::string( escapeXml( s, esc ) ); }; + const std::string chVrNext = "--uses=" + std::string( sym ); + if( cfg.columnar ) + { + std::vector page( result.begin() + pw.begin, result.begin() + pw.end ); + const std::string attr = "of=\"" + ex( sym ) + "\" defs=\"" + std::to_string( matches.size() ) + + "\" count=\"" + std::to_string( result.size() ) + "\"" + + ( !wantCallers && bodylessDefsCount > 0 ? " bodyless_defs=\"" + std::to_string( bodylessDefsCount ) + "\"" : "" ) + + chUnprovenAttr + + rw::crossKindAttrXml( chRows.crossKind ) + + rw::defsListedAttrXml( chAttr.groups, chListing ) + + chTested.xmlAttr + + chDeclinedAttr + + chValueRefsAttr + + chRootAttr + + pageDisclosure( pab, kPageDisclosureCap, pw.end - pw.begin, result.size(), pw.end, + cfg.pageLimit, cfg.pageOffset, chDiscloseCap ) + + rw::graphCountFloorAttrXml( g ) + + chNextAttr; + std::vector pageVia( page.size(), 0 ); + std::vector pageSplit( page.size(), 0 ); + std::vector pageOfDef; + for( std::size_t i = 0; i < page.size(); ++i ) + { + pageVia[ i ] = hopViaCode( oneHopRowIsLink( g, matches, result[ pw.begin + i ], wantCallers ), chAttr.nameOnly( pw.begin + i ) ); + pageSplit[ i ] = chAttr.splitArm( pw.begin + i ) ? 1 : 0; + if( chAttr.groups.multi() ) + { + pageOfDef.push_back( rw::ofDefColumnValue( chAttr.groups, chAttr.tokens, pw.begin + i ) ); + } + } + const std::string chDefRows = rw::defTableXml( ing, chAttr.groups, chListing, chRootPrefix ); + emitColumnarSymbolRows( stdout, ing, tag, attr, page, chRootPrefix, &chTested.testReach, nullptr, + rw::ColumnarRowExtras{ &pageVia, chSites.any ? &chSites.tokens : nullptr, &chSites.totals, + chAttr.groups.multi() ? &pageOfDef : nullptr, &chDefRows, &pageSplit, &chDcRows } ); + return 0; + } + rw::emitTo( stdout, "{{\"of\":\"{}\",\"defs\":{},\"count\":{}", jsonStr( sym ).c_str(), matches.size(), result.size() ); + if( !wantCallers && bodylessDefsCount > 0 ) + { + rw::emitTo( stdout, ",\"bodyless_defs\":{}", bodylessDefsCount ); + } + rw::emitTo( stdout, "{}{}{}", rw::unprovenDefsKeyJson( chRows.unprovenDefs ).c_str(), + rw::crossKindKeyJson( chRows.crossKind ).c_str(), + rw::defTableJson( ing, chAttr.groups, chListing, chRootPrefix, /*mcp=*/false ) ); + if( chSingleRoot ) { rw::emitTo( stdout, ",\"root\":\"{}\"", jsonStr( cfg.roots[0] ).c_str() ); } + rw::emitTo( stdout, ",\"hop_tested\":{},\"hop_untested\":{}{}{}", chTested.tested, chTested.untested, + rw::declinedCallsKeyJson( chRows.declinedCalls ) + rw::declinedSiteCutKeyJson( chDcCut ) + + rw::declinedIfaceKeyJson( chRows.declinedIface ) + + rw::stdMemberCallsSpelling( chRows.stdCalls, /*json=*/true ), + ( rw::valueRefsCountKeyJson( chRows.valueRefs.rows.size() ) + rw::valueRefsDepthKeyJson( ing, chRows.valueRefs.depthCut, chVr ) ) ); + rw::emitTo( stdout, "{}{}{}", pageDisclosure( pab, kPageDisclosureCap, pw.end - pw.begin, result.size(), pw.end, + cfg.pageLimit, cfg.pageOffset, chDiscloseCap, kJsonPageSyntax ), + rw::graphCountFloorAttrJson( g ).c_str(), + chNextIsPage ? rw::nextFieldJson( rw::callersPageNext( sym, cfg.pageLimit, pw.end, false, true ) ) + + rw::nextFieldJson( chHintCall, "uses_next" ) + : std::string() ); + rw::emitTo( stdout, ",\"{}\":[", tag ); + std::vector chVia( result.size(), 0 ); + bool chHasLink = false; + std::vector chAtKeys( result.size() ); + for( std::size_t i = pw.begin; i < pw.end; ++i ) + { + chVia[ i ] = hopViaCode( oneHopRowIsLink( g, matches, result[ i ], wantCallers ), chAttr.nameOnly( i ) ); + chHasLink = chHasLink || ( chVia[ i ] == 2 ); + if( chSites.any ) + { + chAtKeys[ i ] = rw::callSitesKeyJson( chSites.tokens[ i - pw.begin ], chSites.totals[ i - pw.begin ] ); + } + chAtKeys[ i ] += rw::ofDefKeyJson( chAttr.groups, chAttr.tokens, i ) + rw::splitArmKeyJson( chAttr.splitArm( i ) ); + if( oneHopRowIsLink( g, matches, result[ i ], wantCallers, &g.outAnchorMiss ) ) + { + chAtKeys[ i ] += ",\"anchor\":\"missing\""; + } + } + printJsonSymbolRows( ing, result, pw.begin, pw.end, chRootPrefix, &chTested.testReach, nullptr, &chVia, &chAtKeys ); + rw::emitTo( stdout, "]{}{}{}}}", rw::declinedSitesJson( chDc, wantCallers, /*mcp=*/false ), + rw::valueRefsJson( ing, chRows.valueRefs, wantCallers, chVr, "vrs", chVrNext ), + chHasLink ? rw::viaLinkNoteJson() : std::string() ); + return 0; +} + std::optional runCallHierarchy( const MainDispatch& d ) { using namespace rw; @@ -269,11 +382,7 @@ std::optional runCallHierarchy( const MainDispatch& d ) chHasVia = chHasVia || chAttr.nameOnly( i ); // FE-B's via="name": every edge to the matches by name (defattrib.h, one pass) chHasLink = chHasLink || oneHopRowIsLink( g, matches, result[ i ], wantCallers ); } - bool chHasMiss = false; - for( std::size_t i = pw.begin; i < pw.end && !chHasMiss; ++i ) - { - chHasMiss = oneHopRowIsLink( g, matches, result[ i ], wantCallers, &g.outAnchorMiss ); - } + const bool chHasMiss = rw::pageHasAnchorMiss( g, matches, result, pw.begin, pw.end, wantCallers ); rw::emitTo( stdout, "{}{}{}{}{}{}{}-->{}{}", rw::callHierarchyLegendOpen( wantCallers, chNextIsBare, cfg.columnar, chNextIsPage ).c_str(), rw::capLegendClause( rw::computePageDisclosure( pw.end - pw.begin, result.size(), pw.end, cfg.pageLimit, cfg.pageOffset, chDiscloseCap ).active ), @@ -317,113 +426,14 @@ std::optional runCallHierarchy( const MainDispatch& d ) // Reference-as-value round: value_refs= beside count= (never in it), absent at zero; the window after the rows. const rw::VrRender chVr { chSingleRoot, chRootPrefix }; const std::string chValueRefsAttr = ( rw::valueRefsCountAttrXml( chRows.valueRefs.rows.size() ) + rw::valueRefsDepthAttrXml( ing, chRows.valueRefs.depthCut, chVr ) ); // + the depth cut that makes it a floor - const std::string chVrNext = "--uses=" + std::string( sym ); - - // --format=columnar (RESEARCH lever 1): the same page window, re-encoded as a path-table + parallel - // arrays (dedups the repeated per-row markup + paths). Default --format=xml is byte-identical below. - if( cfg.columnar ) + const std::string chVrNext = "--uses=" + std::string( sym ); + if( cfg.columnar || cfg.json ) { - std::vector page( result.begin() + pw.begin, result.begin() + pw.end ); - // §B1.1: composed into a std::string, NEVER a fixed `char attrbuf[]`. `of=` echoes a - // caller-supplied symbol NAME, which is unbounded (a markdown SECTION heading routinely runs - // 200-600 chars), so the old 288-byte buffer truncated it mid-attribute at exit 0 — invalid - // XML past ~185 chars once paging flags widened the string, and at a boundary length a - // well-formed tag whose next_offset=/offset=/limit= had been silently amputated. Same - // composition runImpact already used; the columnar-capable family is exactly - // {callers, callees, uses, impact} and this is the last shared site of the first two. - const std::string attr = "of=\"" + ex( sym ) + "\" defs=\"" + std::to_string( matches.size() ) - + "\" count=\"" + std::to_string( result.size() ) + "\"" - + ( !wantCallers && bodylessDefsCount > 0 ? " bodyless_defs=\"" + std::to_string( bodylessDefsCount ) + "\"" : "" ) - + chUnprovenAttr // H1: the decl→def residue, on BOTH directions - + rw::crossKindAttrXml( chRows.crossKind ) // hono-07: defs of 2+ kinds, beside defs= - + rw::defsListedAttrXml( chAttr.groups, chListing ) // attrib-multidef: a cut definition table says so - + chTested.xmlAttr // A6: hop_tested=/hop_untested=, the same partition on every dialect - + chDeclinedAttr // the tier-3 declines, beside the count they are not in - + chValueRefsAttr // value uses, beside the count they are not in (rows: the XML form) - + chRootAttr // R-E: same root= the XML/JSON branches carry - + pageDisclosure( pab, sizeof( pab ), pw.end - pw.begin, result.size(), pw.end, - cfg.pageLimit, cfg.pageOffset, chDiscloseCap ) - + rw::graphCountFloorAttrXml( g ) // §H4 §3.4 — every dialect carries the marker - + chNextAttr; // P3 (L7): the same next= on the columnar root - std::vector pageVia( page.size(), 0 ); // FE-B: the rows' hedge, a dense column when any row carries it - std::vector pageSplit( page.size(), 0 ); // attrib-multidef Q1: likewise for a one-group answer's split arms - std::vector pageOfDef; // attrib-multidef: of_def per row, only on a multi-group answer - for( std::size_t i = 0; i < page.size(); ++i ) - { - pageVia[ i ] = chAttr.nameOnly( pw.begin + i ) ? 1 : 0; - pageSplit[ i ] = chAttr.splitArm( pw.begin + i ) ? 1 : 0; - if( chAttr.groups.multi() ) - { - pageOfDef.push_back( rw::ofDefColumnValue( chAttr.groups, chAttr.tokens, pw.begin + i ) ); - } - } - for( std::size_t i = 0; i < page.size(); ++i ) - { - if( oneHopRowIsLink( g, matches, result[ pw.begin + i ], wantCallers ) ) - { - pageVia[ i ] = 2; - } - } - const std::string chDefRows = rw::defTableXml( ing, chAttr.groups, chListing, chRootPrefix ); - emitColumnarSymbolRows( stdout, ing, tag, attr, page, chRootPrefix, &chTested.testReach, nullptr, - rw::ColumnarRowExtras{ &pageVia, chSites.any ? &chSites.tokens : nullptr, &chSites.totals, // CALLSITE-AT: sites_at only when a row has sites - chAttr.groups.multi() ? &pageOfDef : nullptr, &chDefRows, &pageSplit, &chDcRows } ); // + the rows verbatim - return 0; + return emitCallHierarchyAlt( cfg, ing, g, result, matches, pw, sym, wantCallers, bodylessDefsCount, chUnprovenAttr, chRows, + chAttr, chListing, chTested, chDeclinedAttr, chValueRefsAttr, chRootAttr, pab, chDiscloseCap, chNextAttr, + chSites, chRootPrefix, tag, chDcRows, chSingleRoot, chVr, chHintCall, chNextIsPage, chDcCut, chDc ); } - // L2: --json — same rows, keys mirror the XML attr names (of/count/offset/limit/t/n/p). - // §A4c: the page disclosure is pageDisclosure()'s JSON row of the syntax table now, not a hand-rolled - // `offset`+`limit` pair — the SAME seven fields the XML tag two lines below carries (discloseCap=false - // for the same reason: these two verbs have no display cap of their own, so un-paged discloses nothing). - if( cfg.json ) - { - rw::emitTo( stdout, "{{\"of\":\"{}\",\"defs\":{},\"count\":{}", jsonStr( sym ).c_str(), matches.size(), result.size() ); - if( !wantCallers && bodylessDefsCount > 0 ) - { - rw::emitTo( stdout, ",\"bodyless_defs\":{}", bodylessDefsCount ); - } - rw::emitTo( stdout, "{}{}{}", rw::unprovenDefsKeyJson( chRows.unprovenDefs ).c_str(), // H1: absent at zero, like its XML twin - rw::crossKindKeyJson( chRows.crossKind ).c_str(), // hono-07: absent unless defs span 2+ kinds - rw::defTableJson( ing, chAttr.groups, chListing, chRootPrefix, /*mcp=*/false ) ); // attrib-multidef: the table - // R-E: the JSON twin of the XML root= below — right after the leading identifying fields. - if( chSingleRoot ) { rw::emitTo( stdout, ",\"root\":\"{}\"", jsonStr( cfg.roots[0] ).c_str() ); } - rw::emitTo( stdout, ",\"hop_tested\":{},\"hop_untested\":{}{}{}", chTested.tested, chTested.untested, - rw::declinedCallsKeyJson( chRows.declinedCalls ) + rw::declinedSiteCutKeyJson( chDcCut ) - + rw::declinedIfaceKeyJson( chRows.declinedIface ) // A6; then the XML root's declined_calls=/dc_shown=/dc_next=/declined_iface= - + rw::stdMemberCallsSpelling( chRows.stdCalls, /*json=*/true ), // and the XML line - ( rw::valueRefsCountKeyJson( chRows.valueRefs.rows.size() ) + rw::valueRefsDepthKeyJson( ing, chRows.valueRefs.depthCut, chVr ) ) ); - rw::emitTo( stdout, "{}{}{}", pageDisclosure( pab, sizeof( pab ), pw.end - pw.begin, result.size(), pw.end, - cfg.pageLimit, cfg.pageOffset, chDiscloseCap, kJsonPageSyntax ), - rw::graphCountFloorAttrJson( g ).c_str(), // §H4 §3.4 — the JSON dialect's spelling of the same marker - // L8: a capped callers page names the next page and the uses verb, as the XML/columnar root and MCP - // find_referencing_symbols do; the page replays --json. Any other answer keeps its JSON bytes. - chNextIsPage ? rw::nextFieldJson( rw::callersPageNext( sym, cfg.pageLimit, pw.end, false, true ) ) - + rw::nextFieldJson( chHintCall, "uses_next" ) - : std::string() ); - rw::emitTo( stdout, ",\"{}\":[", tag ); - std::vector chVia( result.size(), 0 ); // FE-B: the same hedge bit the XML rows carry - bool chHasLink = false; - std::vector chAtKeys( result.size() ); // CALLSITE-AT: the rows' sites_at/sites_total keys, absent when none - for( std::size_t i = pw.begin; i < pw.end; ++i ) - { - chVia[ i ] = oneHopRowIsLink( g, matches, result[ i ], wantCallers ) ? 2 : ( chAttr.nameOnly( i ) ? 1 : 0 ); - chHasLink = chHasLink || ( chVia[ i ] == 2 ); - if( chSites.any ) - { - chAtKeys[ i ] = rw::callSitesKeyJson( chSites.tokens[ i - pw.begin ], chSites.totals[ i - pw.begin ] ); - } - chAtKeys[ i ] += rw::ofDefKeyJson( chAttr.groups, chAttr.tokens, i ) + rw::splitArmKeyJson( chAttr.splitArm( i ) ); // attrib-multidef - if( oneHopRowIsLink( g, matches, result[ i ], wantCallers, &g.outAnchorMiss ) ) - { - chAtKeys[ i ] += ",\"anchor\":\"missing\""; - } - } - printJsonSymbolRows( ing, result, pw.begin, pw.end, chRootPrefix, &chTested.testReach, nullptr, &chVia, &chAtKeys ); - rw::emitTo( stdout, "]{}{}{}}}", rw::declinedSitesJson( chDc, wantCallers, /*mcp=*/false ), // the XML rows, keys = its attribute names - rw::valueRefsJson( ing, chRows.valueRefs, wantCallers, chVr, "vrs", chVrNext ), - chHasLink ? rw::viaLinkNoteJson() : std::string() ); - return 0; - } // §P10.6: defs= = resolved definitions this name matched (matches.size()) — the rows below UNION the // neighbors of every def, which --uses/--impact already disclose and these two verbs silently hid. @@ -2456,9 +2466,8 @@ std::optional runPath( const MainDispatch& d ) const rw::ToValueRefs pthToValueRefs = path.empty() ? rw::toValueRefs( ing, true, dstDefs, &*pthValueIdx ) : rw::ToValueRefs{}; const rw::PathSearchGaps pthGaps = path.empty() ? rw::pathSearchGaps( ing, g, srcDefs, *pthValueIdx ) : rw::PathSearchGaps{}; const bool pthHasVia = rw::pathHasNameOnlyHop( g, path ); // FE-B: exactly when a hop of THIS path is via="name" - const bool pthHasLink = rw::pathHasLinkHop( g, path ); - const bool pthHasMiss = std::adjacent_find( path.begin(), path.end(), - [ & ]( rw::NodeId from, rw::NodeId to ) { return rw::edgeAnchorMiss( g, from, to ); } ) != path.end(); + const bool pthHasLink = rw::pathHasNameOnlyHop( g, path, rw::kEdgeLink ); + const bool pthHasMiss = rw::pathHasNameOnlyHop( g, path, 0 ); // attrib-multidef (defattrib.h): from_p=/to_p= only for an endpoint the path bound; prov="split" on a split-arm hop const rw::PathEndpointBinding pthBound = rw::pathEndpointBinding( g, rw::endpointDefinitionCount( ing, srcDefs ), rw::endpointDefinitionCount( ing, dstDefs ), path ); const bool pthHasSplit = pthBound.splitHop; @@ -2774,8 +2783,9 @@ int emitImpactJson( const ImpactView& v ) rw::emitImportRowsJson( stdout, v.ing, v.importPage, v.rootPrefix, v.importLazyPage ); rw::emitRaw( stdout, "]" ); rw::emitImpactFileRollupJson( stdout, v.ing, v.fileRollup, v.rootPrefix ); // idea #6: present exactly when the XML element is - rw::emitTo( stdout, "{}}}", rw::valueRefsJson( v.ing, v.valueRefs, true, rw::VrRender{ v.singleRoot, v.rootPrefix }, "vrs", - "--uses=" + std::string( v.sym ) ) ); + const std::string imLinkNote = rw::impactViaLinkNote( via, v.page.begin, v.page.end ); + rw::emitTo( stdout, "{}{}}}", imLinkNote.c_str(), rw::valueRefsJson( v.ing, v.valueRefs, true, rw::VrRender{ v.singleRoot, v.rootPrefix }, "vrs", + "--uses=" + std::string( v.sym ) ).c_str() ); return 0; } @@ -2823,6 +2833,44 @@ int emitImpactXml( const ImpactView& v ) return 0; } +inline void emitImpactLegend( const rw::Config& cfg, const rw::IngestResult& ing, const rw::Graph& g, const std::vector& reach, + const std::vector& show, const rw::PageWindow& imPage, const rw::ImpactReach& imWalk, + const rw::ImportTier& imports, const std::vector& seeds, const std::vector& imDepth, + const std::vector& imProven, std::size_t imUnprovenDefs, std::size_t imDeclinedCalls, std::size_t imDeclinedIface, + const rw::ImpactFileRollup& imFiles, const rw::ReachAttribution& imAttr, const rw::DefTableListing& imListing, + const rw::ValueRefRows& imValueRefs, const rw::RankDisclosure& prD ) +{ + if( cfg.json ) + { + return; + } + const bool imHasModScope = anyModuleScopeRow( ing, std::span( show ).subspan( imPage.begin, imPage.end - imPage.begin ) ); + rw::emitTo( stdout, "{}{}. {}{}{}{}{}{}{}{}{}{}{}{}{}-->", rw::kImpactLegendOpen, rw::kPageRaiseCapClause, + reach.empty() ? std::string() + : std::string( rw::impactDepthLegend( cfg.columnar ) ) + + rw::impactByTypeColumnarLegend( cfg.columnar && std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, + [ & ]( rw::NodeId n ) { return n < imWalk.typeOnly.size() && imWalk.typeOnly[ n ] != 0; } ) ), + ( std::string( cfg.columnar ? rw::kImpactImportTierColumnarLegend : rw::kImpactImportTierLegend ) + + rw::importersNextLegend( !imports.next.empty() && !cfg.columnar ) ).c_str(), + ( rw::impactTsImportLegend( imports.importsUnresolved, imports.tsconfigUnread ) + + rw::viaNameLegend( std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, + [ & ]( rw::NodeId n ) { return rw::impactRowNameOnly( imProven, n ); } ), true, cfg.columnar ) + + rw::impactLinkLegendFor( g, seeds, imDepth, show, imPage.begin, imPage.end ) + + rw::impactAnchorLegendFor( g, seeds, imDepth, show, imPage.begin, imPage.end ) ).c_str(), + rw::testedLensLegend( cfg.columnar ), rw::kImpactTestedPartitionLegend, + rw::kTestedLensBlindSpotLegend, + rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Impact, imUnprovenDefs > 0 ).c_str(), + rw::declinedCallsLegendWithGates( imDeclinedCalls > 0, g.gateDeclinedCalls > 0, g.stdMemberDeclinedCalls > 0 ) + rw::declinedSitesImpactPointer( imDeclinedCalls > 0 ), + rw::declinedIfaceLegend( imDeclinedIface > 0 ), + ( std::string( rw::modScopeLegend( imHasModScope ) ) + + rw::impactRollupLegend( !imFiles.rows.empty() && !cfg.columnar, imports.umbrella, rw::impactRollupHedged( imFiles ), + !imports.next.empty() && !cfg.columnar, rw::ImpactRollupTypeClauses{ imFiles.typed, imFiles.typeDefs, imFiles.typeCapped } ) + + rw::defTableLegend( imAttr.groups.multi(), imListing.cut ) ).c_str(), + ( std::string( rw::valueRefsReachLegend( !imValueRefs.rows.empty() ) ) + + rw::valueRefsDepthLegendFor( imValueRefs ) ).c_str(), + rw::graphCountDisclosure( rw::graphGaugeClauses( g ) ).c_str(), rw::renderDisclosure( prD, rw::DiscloseAs::LegendClause ).c_str() ); +} + std::optional runImpact( const MainDispatch& d ) { using namespace rw; @@ -2908,46 +2956,8 @@ std::optional runImpact( const MainDispatch& d ) const rw::ValueRefIndex imVri( ing ); const rw::ValueRefRows imValueRefs = rw::valueRefCallerRows( ing, imVri, seeds ); - if( !cfg.json ) - { // L2: JSON has no comment-node analogue; the XML-only leading doc comment - // §B12.4 in-band (W3FIX): the paging clause comes from pageview.h::kPageRaiseCapClause, which - // carries the limit="0" definition too — rule 7 existed only in --help and that header before. - // §H4 §3.4: opener + the shared floor/counting-unit tail, both from src/graphlegend.h so the MCP - // twin cannot drift from this wording (the §B4 echo-site class). - // LB-H: the import-tier clause is the columnar variant under --format=columnar, because that - // form carries the count without the rows and a reader must be told which shape they hold. - // #60: exactly when a module-scope owner is one of the rows this answer prints — the PAGE, which - // is what `anyModuleScopeRow`'s own contract asks for ("a page of rows, never the corpus"). - const bool imHasModScope = anyModuleScopeRow( ing, std::span( show ).subspan( imPage.begin, imPage.end - imPage.begin ) ); - rw::emitTo( stdout, "{}{}. {}{}{}{}{}{}{}{}{}{}{}{}{}-->", rw::kImpactLegendOpen, rw::kPageRaiseCapClause, - reach.empty() ? std::string() - : std::string( rw::impactDepthLegend( cfg.columnar ) ) // 0.6.5: exactly when rows (d=) and by_depth= exist - + rw::impactByTypeColumnarLegend( cfg.columnar && std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, // L7: exactly when the column rides - [ & ]( NodeId n ) { return n < imWalk.typeOnly.size() && imWalk.typeOnly[ n ] != 0; } ) ), - ( std::string( cfg.columnar ? rw::kImpactImportTierColumnarLegend : rw::kImpactImportTierLegend ) - + rw::importersNextLegend( !imports.next.empty() && !cfg.columnar ) ).c_str(), // cut-fix E: exactly when the root carries it - ( rw::impactTsImportLegend( imports.importsUnresolved, imports.tsconfigUnread ) // #220: exactly when the root carries them - + rw::viaNameLegend( std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, // FE-B: exactly when a shown row is via="name" - [ & ]( NodeId n ) { return rw::impactRowNameOnly( imProven, n ); } ), true, cfg.columnar ) - + rw::viaLinkLegend( std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, - [ & ]( NodeId n ) { return rw::impactDirectLink( g, seeds, imDepth, n ); } ) ) - + rw::anchorMissLegend( std::any_of( show.begin() + imPage.begin, show.begin() + imPage.end, - [ & ]( NodeId n ) - { return rw::impactDirectLink( g, seeds, imDepth, n ) - && rw::oneHopRowIsLink( g, seeds, n, true, &g.outAnchorMiss ); } ) ) ).c_str(), - rw::testedLensLegend( cfg.columnar ), rw::kImpactTestedPartitionLegend, // A6: the columnar form reads its dense column - rw::kTestedLensBlindSpotLegend, // F-02: rides with the partition - rw::unprovenDefsVerbLegend( rw::UnprovenDefsVerb::Impact, imUnprovenDefs > 0 ).c_str(), // H1: exactly when the root carries unproven_defs= - rw::declinedCallsLegendWithGates( imDeclinedCalls > 0, g.gateDeclinedCalls > 0, g.stdMemberDeclinedCalls > 0 ) + rw::declinedSitesImpactPointer( imDeclinedCalls > 0 ), // exactly when the root carries declined_calls= - rw::declinedIfaceLegend( imDeclinedIface > 0 ), // likewise, exactly when declined_iface= is there - ( std::string( rw::modScopeLegend( imHasModScope ) ) // #60: likewise, exactly when a t="modscope" row is - + rw::impactRollupLegend( !imFiles.rows.empty() && !cfg.columnar, imports.umbrella, rw::impactRollupHedged( imFiles ), - !imports.next.empty() && !cfg.columnar, rw::ImpactRollupTypeClauses{ imFiles.typed, imFiles.typeDefs, imFiles.typeCapped } ) // idea #6; fix27; FIX1 R1: the XML root's importers_next= (columnar names it in lens=); L7 - + rw::defTableLegend( imAttr.groups.multi(), imListing.cut ) ).c_str(), // attrib-multidef: exactly when the table rides - ( std::string( rw::valueRefsReachLegend( !imValueRefs.rows.empty() ) ) // exactly when the root carries value_refs= - + rw::valueRefsDepthLegendFor( imValueRefs ) ).c_str(), // ... and the depth disclosure - rw::graphCountDisclosure( rw::graphGaugeClauses( g ) ).c_str(), rw::renderDisclosure( prD, rw::DiscloseAs::LegendClause ).c_str() ); - } + emitImpactLegend( cfg, ing, g, reach, show, imPage, imWalk, imports, seeds, imDepth, imProven, imUnprovenDefs, + imDeclinedCalls, imDeclinedIface, imFiles, imAttr, imListing, imValueRefs, prD ); // P2.1 + §P8 G1: the rank-ordered listing's 40 is a DEFAULT now, not a ceiling — see the §P10.3 note // above runImpact; pageDisclosure emits the ` shown= capped=` bytes this verb used to hand-roll // (src/pageview.h, THE TRUNCATION VOCABULARY, rules 1-3). LB-G: the same NAMED constant @@ -3133,7 +3143,7 @@ std::optional runAround( const MainDispatch& d ) aroundAnn.seed = { ing.symbols[ focus ].name, cfg.aroundDepth, cfg.aroundFanout, definitionCountOfName( ing, focus ), eg.fanoutCut, eg.depthTruncated }; aroundAnn.seed.unprovenDefs = arUnprovenDefs; // H1: unproven_defs= beside defs= on the root, its clause in the map legend - serialize( stdout, ing, rank, g.outOff, g.outTargets, int( eg.nodes.size() ), cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, false, g.outProv.empty() ? nullptr : &g.outProv, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, /*autoOrder=*/false, /*outEstTokens=*/nullptr, aroundCompose.tokens + aroundRoutes.tokens + wrap.tokens, aroundAnn, /*statsFirstScreen=*/false, aroundRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, /*gateDeclinedCalls=*/0, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, /*stdMemberDeclinedCalls=*/0, g.inertDeclinedCalls, rw::linkOutPtr( g ), &g.outAnchorMiss ); + serialize( stdout, ing, rank, g.outOff, g.outTargets, int( eg.nodes.size() ), cfg.mostImportantLast, cfg.metrics, fanInPtr, &g.ambOut, false, g.outProv.empty() ? nullptr : &g.outProv, cboPtr, testedPtr, lcom4Ptr, ampPtr, &g.unresolvedOut, g.bindLabel.empty() ? nullptr : &g.bindLabel, /*autoOrder=*/false, /*outEstTokens=*/nullptr, aroundCompose.tokens + aroundRoutes.tokens + wrap.tokens, aroundAnn, /*statsFirstScreen=*/false, aroundRootArg, &g.locPinOut, g.externalCalls, &g.declinedOut, /*gateDeclinedCalls=*/0, g.outNameOnly.empty() ? nullptr : &g.outNameOnly, /*stdMemberDeclinedCalls=*/0, g.inertDeclinedCalls ); if( !g.composeEdges.empty() ) { diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index 70980a32e..522bf5963 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -499,11 +499,16 @@ printf '%s' "$IM" | grep -q 'reaches="2"' \ && ! printf '%s' "$IM" | grep -q 'n="main"[^>]*via="link"' \ && ok "impact marks Run Notes via=link and still counts both rows" \ || no "impact dropped the link mark or the reach count" +IJ="$( $BIN "$TMP/runfix" --no-cache --impact=run --json 2>/dev/null )" +printf '%s' "$IJ" | grep -q 'via_link_note' \ + && ok "impact json carries via_link_note" \ + || no "impact json omitted via_link_note" # In-file anchors, wikilinks, and root-relative links are via="link". A code call on the same name stays unmarked. -mkdir -p "$TMP/kinds/src" "$TMP/kinds/docs" +mkdir -p "$TMP/kinds/src" "$TMP/kinds/docs" "$TMP/kinds/notes" printf '%s\n' 'def api():' ' return 1' '' 'def main():' ' api()' > "$TMP/kinds/src/app.py" printf '%s\n' '# api' > "$TMP/kinds/docs/api.md" +printf '%s\n' '# decoy' > "$TMP/kinds/notes/api.md" printf '%s\n' '# Guide' '' '## Hub' '' '[down](#later)' '' '[a](api.md)' '' '[b](/docs/api.md)' '' '[[api]]' '' '## Later' '' 'notes' > "$TMP/kinds/docs/guide.md" HUB="$( $BIN "$TMP/kinds" --no-cache --callees=Hub --legend=compact 2>/dev/null )" printf '%s' "$HUB" | grep -q 'n="Later"[^>]*via="link"' \ @@ -516,6 +521,18 @@ printf '%s' "$API" | grep -q 'n="Hub"[^>]*via="link"' \ && ! printf '%s' "$API" | grep -q 'n="main"[^>]*via="link"' \ && ok "a code call to api stays unmarked while the doc link stays via=link" \ || no "the code call was marked via=link, or the doc link lost the mark" +printf '%s' "$HUB" | grep -q 'docs/api.md:[0-9]*"[^>]*via="link"' \ + && ! printf '%s' "$HUB" | grep -q 'notes/api.md' \ + && ok "a leading-slash link joins the crawl root, not the other api.md" \ + || no "the leading-slash link missed docs/api.md or hit the decoy" +PTH="$( $BIN "$TMP/kinds" --no-cache --path=Hub,Later --legend=compact 2>/dev/null )" +printf '%s' "$PTH" | grep -q 'n="Later"[^>]*via="link"' \ + && ok "path marks the Later hop via=link" \ + || no "path lost via=link on the Later hop" +CON="$( $BIN "$TMP/kinds" --no-cache --connect=Hub,Later --legend=compact 2>/dev/null )" +printf '%s' "$CON" | grep -q 'via="link"' \ + && ok "connect marks the Hub to Later edge via=link" \ + || no "connect lost via=link on the Hub to Later edge" echo "--- warm == cold after editing ONLY the target file (the cached ref must stay unbound) ---" cp -R "$AFIX" "$TMP/anchorcopy" From 4438031c9324a9d761e726d697cb2bfbe59b3b7d Mon Sep 17 00:00:00 2001 From: Muayyad Alsadi Date: Mon, 19 Oct 2026 05:41:32 +0300 Subject: [PATCH 13/28] Read a markdown section link from a language fact, so the shared Lang:: count stays under its limit. Re-pin the golden, the printf hashes, and the compact legend sizes for the via="link" rows. --- src/graph.h | 12 ++++++------ src/langfacts.h | 5 ++++- src/serialize.h | 4 ++-- test/compactlegendcheck.sh | 21 ++++++++++++++------- test/compactlegendfix/pre_l1_full/map.xml | 2 +- test/fillordercheck.sh | 4 +++- test/golden.xml | 2 +- test/mdsectioncheck.sh | 2 +- test/printf_parity.manifest | 4 ++-- test/regression.sh | 3 ++- 10 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/graph.h b/src/graph.h index 73a5f0eb9..8091c89c1 100644 --- a/src/graph.h +++ b/src/graph.h @@ -6455,7 +6455,7 @@ struct MdLinkIndex inline bool isMarkdownLinkRef( const Reference& r ) noexcept { - if( r.lang != Lang::Markdown || r.isDocLink || r.memberCall ) + if( !langFacts( r.lang ).linkSections || r.isDocLink || r.memberCall ) { return false; } @@ -6466,7 +6466,7 @@ inline bool isMarkdownLinkRef( const Reference& r ) noexcept // path, which an in-file anchor and a wikilink do not have. A markdown doc mention is neither. inline void noteMarkdownLinkSite( HashMap& linkEdges, std::uint64_t ekey, const Reference& r ) { - if( r.lang == Lang::Markdown && !r.isDocLink ) + if( langFacts( r.lang ).linkSections && !r.isDocLink ) { linkEdges[ ekey ] = 1; } @@ -6505,7 +6505,7 @@ inline std::vector markdownWidestEnd( const IngestResult& ing ) std::vector widest( ing.files.size(), 0 ); for( const Symbol& sym : ing.symbols ) { - if( sym.lang == Lang::Markdown && sym.kind == SymKind::Section && sym.fileId < ing.files.size() ) + if( langFacts( sym.lang ).linkSections && sym.kind == SymKind::Section && sym.fileId < ing.files.size() ) { widest[ sym.fileId ] = std::max( widest[ sym.fileId ], sym.endByte ); } @@ -6520,7 +6520,7 @@ inline void indexMarkdownSections( MdLinkIndex& ix, const IngestResult& ing ) const std::vector widest = markdownWidestEnd( ing ); for( const Symbol& sym : ing.symbols ) { - if( sym.lang != Lang::Markdown || sym.kind != SymKind::Section || sym.fileId >= ing.files.size() ) + if( !langFacts( sym.lang ).linkSections || sym.kind != SymKind::Section || sym.fileId >= ing.files.size() ) { continue; } @@ -7806,7 +7806,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null bool anyMarkdownLink = false; for( const Reference& r : ing.references ) { - if( r.lang == Lang::Markdown && !r.isDocLink ) + if( langFacts( r.lang ).linkSections && !r.isDocLink ) { anyMarkdownLink = true; break; @@ -11331,7 +11331,7 @@ inline FieldUseAnswer collectFieldUseSites( const IngestResult& ing, FieldId fie for( std::uint32_t refIndex = 0; refIndex < ing.references.size(); ++refIndex ) { const Reference& r = ing.references[ refIndex ]; - if( ( r.role != RefRole::Read && r.role != RefRole::Write ) || r.isCompose || r.isDocLink || r.lang == Lang::Markdown ) + if( ( r.role != RefRole::Read && r.role != RefRole::Write ) || r.isCompose || r.isDocLink || langFacts( r.lang ).linkSections ) { continue; } diff --git a/src/langfacts.h b/src/langfacts.h index 7d0105cc2..38b432dfb 100644 --- a/src/langfacts.h +++ b/src/langfacts.h @@ -76,6 +76,9 @@ struct LangFacts /// judged against the base's whole set. A changed C or Objective-C file does not need it, by today's behaviour /// (verbs_quality.h; pinned by test/qddegradecheck.sh (E3)). bool defectShapeNeedsMacroSet = false; + + /// Markdown: a link targets a section, so shared graph code treats this language's sections as link targets (graph.h). + bool linkSections = false; }; /// The rows, one per Lang in enum order. A language says only the facts it has. @@ -87,7 +90,7 @@ inline constexpr std::array kLangFacts = { { { .lang = Lang::Rust }, { .lang = Lang::Swift }, { .lang = Lang::ObjC }, - { .lang = Lang::Markdown }, + { .lang = Lang::Markdown, .linkSections = true }, { .lang = Lang::JavaScript }, { .lang = Lang::Bash }, { .lang = Lang::Java }, diff --git a/src/serialize.h b/src/serialize.h index 2bb22ddb9..f05bcf96d 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -7760,7 +7760,7 @@ inline void packBodies( std::FILE* out, const IngestResult& ing, const std::vect { w.write( viaNameLegendComment() ); // FE-B: the same rule, for a row's via="name" } - if( children.find( " via=\"link\"" ) != std::string::npos ) + if( !viaLegendInHead && children.find( " via=\"link\"" ) != std::string::npos ) { w.write( viaLinkLegendComment() ); } @@ -8060,7 +8060,7 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector { w.write( viaNameLegendComment() ); // FE-B: exactly when a hop's row carries via="name" } - if( children.find( " via=\"link\"" ) != std::string::npos ) + if( !viaLegendInHead && children.find( " via=\"link\"" ) != std::string::npos ) { w.write( viaLinkLegendComment() ); } diff --git a/test/compactlegendcheck.sh b/test/compactlegendcheck.sh index 030edca98..345204da4 100755 --- a/test/compactlegendcheck.sh +++ b/test/compactlegendcheck.sh @@ -119,8 +119,12 @@ elif op == "schema": elif op == "rootattrs": # sorted attribute NAMES of the root, `schema` excluded print( " ".join( sorted( set( re.findall( r'([\w:.-]+)="', m.group( 2 ) ) ) - { "schema" } ) ) if m else "" ) elif op == "payload": # the payload with the root's schema attribute removed — the byte-identity operand - if m: # est_tokens= on the root is the price of the EMITTED bytes — a smaller legend is a smaller price, not a row change - tag = m.group( 0 ); pay = pay.replace( tag, re.sub( r'\sest_tokens="[0-9]*"', "", re.sub( r'\sschema="[^"]*"', "", tag ) ), 1 ) + if m: # schema= is the compact dialect's own id + tag = m.group( 0 ); pay = pay.replace( tag, re.sub( r'\sschema="[^"]*"', "", tag ), 1 ) + # est_tokens= prices the emitted bytes. A legend the compact dialect strips moves that price on the + # document root and on an embedded map root (pack-signatures, pack-top-n). A smaller legend is a + # smaller price, not a row change. + pay = re.sub( r'\sest_tokens="[0-9]*"', "", pay ) sys.stdout.write( pay ) elif op == "legend": sys.stdout.write( "".join( leg ) ) elif op == "prose": # PROSE legend bytes: comments this document carries that the FULL document (argv[3]) does not carry @@ -318,6 +322,8 @@ echo "=== (A-PIN) --legend=full is BYTE-IDENTICAL to the pre-L1 default (pinned # --for file page (rows with sym=), its full legend clause defines them, and est_tokens= prices them, 4111 -> 4500 B. Checked # mechanically: with the element, the tail clause and est_tokens= masked, the new answer equals the previous pin byte for # byte (at= masked). The six other pins are unchanged. +# RE-ANCHORED BY HAND 2026-10-19 (markdown section links), map.xml only: the two fixture callees gain via="link", +# the full legend gains the via="link" comment, and est_tokens= prices them, 901 -> 929. PIN_DIR="$ROOT/test/compactlegendfix/pre_l1_full" # the one normalisation, in python on BOTH sides so no sed dialect decides it (BSD sed appends a final newline, GNU # sed does not): at="…" masked, trailing newlines dropped. @@ -586,12 +592,13 @@ probeFor() # probe; 595 on main 0852bc0f). The probe's contract is unchanged with nothing flagged, so the answer is now the verdict # line (no rows), and the compact legend reads its two new facts: rows_next= (the --limit call listing the callers) and # callers= as the c rows' total (a floor). Nothing else moved. +# RE-PINNED 2026-10-19 (markdown section links): the via=link reading and the deleted same-pair sentence. PIN_TABLE=' ripwire.edit-check/v1 650 636 ripwire.map/v1 1140 1129 -ripwire.map-diff/v1 900 885 -ripwire.pack-signatures/v1 770 759 -ripwire.metrics/v1 1040 1021 +ripwire.map-diff/v1 970 953 +ripwire.pack-signatures/v1 840 827 +ripwire.metrics/v1 1090 1080 ripwire.deps/v1 780 764 ripwire.hotspots/v1 530 518 ripwire.clones/v1 810 799 @@ -632,7 +639,7 @@ ripwire.mentions/v1 260 243 ripwire.affected/v1 840 826 ripwire.verify/v1 440 421 ripwire.help-task/v1 440 429 -ripwire.query/v1 720 707 +ripwire.query/v1 790 775 ripwire.grep/v1 1380 1368 ripwire.match/v1 370 355 ripwire.lego/v1 290 275 @@ -651,7 +658,7 @@ ripwire.whereis/v1 1050 1037 ripwire.community/v1 730 719 ripwire.layout/v1 1220 1203 ripwire.pack-task/v1 990 974 -ripwire.pack-top-n/v1 760 745 +ripwire.pack-top-n/v1 830 813 ripwire.expand/v1 280 265 ripwire.expand-file/v1 510 508 ripwire.for/v1 820 802 diff --git a/test/compactlegendfix/pre_l1_full/map.xml b/test/compactlegendfix/pre_l1_full/map.xml index 01d2de816..6e28ab00c 100644 --- a/test/compactlegendfix/pre_l1_full/map.xml +++ b/test/compactlegendfix/pre_l1_full/map.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/fillordercheck.sh b/test/fillordercheck.sh index b22f252cb..6077b8610 100755 --- a/test/fillordercheck.sh +++ b/test/fillordercheck.sh @@ -132,7 +132,9 @@ OFIX="$( order_of test/fixture )" # RE-PIN 2026-10-04 (train 25): 863 -> 905, the document GREW by its legend and nothing else. The v1 header legend's # unresolved=/external= clauses now state the false-edge rule (+106 B); test/golden.xml was re-recorded beside it and differs # from its previous self by exactly those two clauses and est_tokens=. Order is unchanged (important-first). -{ [ "$EFIX" = "905" ] && [ "$OFIX" = "important-first" ]; } \ +# RE-PIN 2026-10-19 (markdown section links): 905 -> 933. The full map gained the via="link" comment and the two +# fixture edges ([[notes]] and [[related]]); order is unchanged. +{ [ "$EFIX" = "933" ] && [ "$OFIX" = "important-first" ]; } \ && ok "test/fixture (est_tokens=$EFIX) does NOT auto-flip — order=$OFIX (golden neutral)" \ || no "test/fixture unexpectedly changed order or est_tokens (est=$EFIX order=$OFIX)" diff --git a/test/golden.xml b/test/golden.xml index ea21e4313..93862cf78 100644 --- a/test/golden.xml +++ b/test/golden.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/mdsectioncheck.sh b/test/mdsectioncheck.sh index 522bf5963..bf57e12a1 100755 --- a/test/mdsectioncheck.sh +++ b/test/mdsectioncheck.sh @@ -544,7 +544,7 @@ grep -q '^## Install Steps Renamed$' "$TMP/anchorcopy/docs/b/partner.md" || no " $BIN "$TMP/anchorcopy" --cache="$TMP/anchor.cache" --callees="Self Collision" --legend=compact >"$TMP/ac_warm" 2>/dev/null $BIN "$TMP/anchorcopy" --no-cache --callees="Self Collision" --legend=compact >"$TMP/ac_cold" 2>/dev/null if [ -s "$TMP/ac_cold" ] && diff -q "$TMP/ac_cold" "$TMP/ac_warm" >/dev/null; then ok "warm run equals cold run after only the target heading changed"; else no "warm run differs from cold after a target-only edit (a stale bound ref)"; fi -grep -q 'n="partner"' "$TMP/ac_warm" && ok "…and the dead anchor degraded to the partner file edge" || no "the renamed-heading link lost its edge on the warm run" +if grep -q 'n="partner"' "$TMP/ac_warm"; then ok "…and the dead anchor degraded to the partner file edge"; else no "the renamed-heading link lost its edge on the warm run"; fi # ═══════════════════════════════════════════════════════════════════════════ echo diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index c1821a140..c5cfba42e 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -1,4 +1,4 @@ -flagless 0 22b42aea387b3ba54ac0921ce1caed39aea23a4947579eec2276b72ad77330e6 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +flagless 0 b0447db620f0c57726cc5be7851d6fb5dad0b3510eaa0318297ee7aaeee80904 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 lint 0 a3d29cee7631f2904dfeaa947f091d3e73181d325e6ddedd87a68580d5717ff5 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 lint_sarif 0 805e375b06b7755bcc6423607378e557ee82fef787cf09c34ba5c066639f4d7b e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 lint_select 0 740876ae2df8dbdced07c406c223f545e92a719d8bc3dee1cac56a85207f16c2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 @@ -17,7 +17,7 @@ uses 0 5442767b638c8cac9eb0327cbfd5fde73f92fde5b85e2a995670a3982d903711 e3b0c442 path 0 fe032ed241174b3892bd9369cbc3d3b904d9e6f681cc307c27c476b6a28ea59e e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 connect 0 6ceb99d6c5ecb9f57a1b7426da2ed2118705cb7cc6b3d0aa4456b84c0ff715f4 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 grep 0 ea5c66f3062d3ba805f271d18b146ee411f3b132f75d0c77062ecf2eda13f36e e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -pack_signatures 0 91446d0fc143c8d08605da1a3e7430834de105b15f0d2658f675c6bc3bf641a8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +pack_signatures 0 66de39992132b15fdc91e088e5301976e2beb972cb95802441331350983fab25 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 pack_task 0 f3de4fc3a28342cf515fedb8a02de0fea7f149ce8e948d39a7037c955a9eafd1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 arch 2 45b12ab02e3e9f2e32bf92fe0e1cef7b489e70711386e0749d50281743aeeec5 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 seams 0 069a723391d0a5687bdbe23d250a5dc901791fbcc676e5ac7435e9017cc30478 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 diff --git a/test/regression.sh b/test/regression.sh index c8ff2d3c4..61d2399b0 100755 --- a/test/regression.sh +++ b/test/regression.sh @@ -327,7 +327,8 @@ done # 4) golden snapshot — output matches the committed golden (catches ANY unintended output change). # --no-cache so the golden is the canonical cold parse, independent of any TMPDIR cache state. -"$BIN" "$CORPUS" --no-cache >"$TMP/cur" 2>/dev/null +# L1: the CLI default legend is compact; golden.xml is the full legend, so this compare asks for it. +"$BIN" "$CORPUS" --no-cache --legend=full >"$TMP/cur" 2>/dev/null if [ "${UPDATE_GOLDEN:-0}" = "1" ]; then cp "$TMP/cur" "$GOLD"; printf ' WROTE golden (%s B)\n' "$(wc -c <"$GOLD" | tr -d ' ')" elif [ -f "$GOLD" ]; then From 3a3f0a6156e49c1a1f7e33bf845b318c99029c97 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Mon, 19 Oct 2026 01:26:39 -0400 Subject: [PATCH 14/28] chore(versions): kParserVer 206 after the merge with main; re-pin qschemetrip The merge renumbered kParserVer (and quality.h's mirror) to 206, rich file tag 207, above every live number: main 196, train 31 198, and the branch reservations 202 and 204. The PR's own 185 is below main's 196, so it had to move. qschemetripcheck watches those lines; the only watched text that moved is the version, so the hash is re-pinned with a log line. kCacheVersion stays 36. The CHANGELOG versions note says 206 where it said 196. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 15 ++++++++------- test/qschemetrip.hash | 2 +- test/qschemetripcheck.sh | 4 ++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 433b46d04..a97a39456 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,7 +153,7 @@ it now names nothing.) Go's written `*os.File` no longer resolves to an in-tree type that happens to be called `File`. A Java/Kotlin class-name receiver binds an in-tree class only where the caller's file sees it — its package, a single-type import naming it, an on-demand import of its package (`import ext.Util; Util.open()`, `import ext.*; Util.open()`, `Util::open` no longer bind -an in-tree `Util` as proven). `kParserVer` and `kCacheVersion` move (196 and 36 in this release, see the versions note). +an in-tree `Util` as proven). `kParserVer` and `kCacheVersion` move (206 and 36 in this release, see the versions note). ### Fixed — `f.call( … )` and `f.apply( … )` on a proven function are calls of `f` `handleRequest.call( request.server, null, request, reply )` used to be recorded only as a member call named `call`, so @@ -163,7 +163,7 @@ the file declares as a function, or imports from a relative module, now stands f where the resolver proves a function or method: an imported object, const or class (`rpc.call()` on a required module object, `Klass.call( this )`) and a name bound by name alone add nothing. `f.bind( … )` is a value use of `f`, never a call. Floors, shared with the bare call `f()`: a function-local `var f` / `let f` declared after the site does not -shadow it, and a re-assignment `f = …` is not followed. `kParserVer` and `kCacheVersion` move (196 and 36 in this release, see the versions note). +shadow it, and a re-assignment `f = …` is not followed. `kParserVer` and `kCacheVersion` move (206 and 36 in this release, see the versions note). ### Fixed — a method a name-template lookup can name no longer reads `risk="none-found"` `getattr(obj, f"watch_{name}")`, `hasattr`, `cls.__dict__.get(f"on_{n}")` and JS/TS `this['compile' + node.typename]` @@ -177,7 +177,7 @@ each template's candidates (``, first 8, `next=` for the rest) `--safe-delete`, when nothing else was found, it still makes `risk="hedged"` and names its site as a `` row. A template with no literal side at all (`codes[sc + '']`, `f"{m}"`) carries no name and records nothing, like a bare `obj[k]`. Matched by name, never a proven call: `callers=`, `impact_reaches=`, `count=` and the `--dead-code` list are unchanged. The -`--json` and `--format=columnar` roots carry both counts but not the rows; the MCP transport carries neither yet. Cache format and parser version move (the reference role set grew; 36 and 196 in this release, see the versions +`--json` and `--format=columnar` roots carry both counts but not the rows; the MCP transport carries neither yet. Cache format and parser version move (the reference role set grew; 36 and 206 in this release, see the versions note). Gate: `test/safedeletecheck.sh` arms 9–18. ### Changed — `--for` tail rank rows name their file's top symbols @@ -205,7 +205,7 @@ scope only, a Rust `const` / `static` at module, `impl`, trait or `extern` level level; a call in a function-local initializer belongs to the enclosing function. Package-level grouped Go `var ( a = …; b = … )` declarations, which were missed (grouped `const` was already read), are now one symbol per name. On fzf, gotestsum and nfpm (Go), 574 function-local symbols leave the map, no package-level one is lost, and 87 grouped names are added. `kParserVer` moves -(196 in this release, see the versions note). Gate: `test/localdeclcheck.sh`. +(206 in this release, see the versions note). Gate: `test/localdeclcheck.sh`. ### Changed — `--path` hop rows name a member by its owner (`Owner.name`) @@ -452,14 +452,15 @@ server, when the cgroup's charge is the larger reading: restarting the server do ### Changed — the versions this release moves, stated once -`kParserVer` 167 → 196: the Clojure grammar (#349) took 171 on `main` and slice 4a (#400) 173 (its full-use file tag 174); the value-reference walk through +`kParserVer` 167 → 206: the Clojure grammar (#349) took 171 on `main` and slice 4a (#400) 173 (its full-use file tag 174); the value-reference walk through `??`/`||`/`&&`/`or`/`and` carried 173 on its branch, the Ruby/Python subscript references and Ruby's main-object mixins 174 (file tag 175), and the Elixir quote live holes (#380) 145 on theirs; they landed together at 176 (file tag 177). The minimum-arity field (`paramsMin`) carried 178 (file tag 179) on its branch and the Go receiver scope (#403) 182 (file tag 183); those landed at 184 (file tag 185). Call-result typing carried 180 (file tag 181), the `f.call` / `f.apply` forwarders 188 (189), the function-local Go / Rust / GDScript declarations 190 (191) and the name-template references 192 (193) on their -branches, and a branch not in this release 194 (195). This release is 196, above every number a branch build has used, so -no cache such a build wrote is read as this release's; its full-use file tag is 197. +branches, and branches not in this release 194 (195), 198 (199), 202 (203) and 204 (205). The markdown section links (#409) +carried 185 (186) on their branch and were renumbered when it merged. This release is 206, above every number a branch build +has used, so no cache such a build wrote is read as this release's; its full-use file tag is 207. `kCacheVersion` 31 → 36: a reference record's role byte gained two values, the subscript role (9) and the inert role (10, numbered 9 on #380's branch, which predates the subscript role), and the binding kind byte one (Ruby's main-object mixin) (32); a definition record gained `paramsMin`, the fewest arguments a call can pass (33 on its branch, 34 in the train that landed diff --git a/test/qschemetrip.hash b/test/qschemetrip.hash index 7abac5d07..2ccfe2fbb 100644 --- a/test/qschemetrip.hash +++ b/test/qschemetrip.hash @@ -1 +1 @@ -5af7514356283527ea8833c20b3f63578e930bc6e7d7438e6f7b4d4d4f3726b4 +a667ffc55eff871ff6ea06c1068cc5c73c8fe06b5a582beb87dae733548f8a05 diff --git a/test/qschemetripcheck.sh b/test/qschemetripcheck.sh index ed58efa73..7dfa58851 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-19, PR #414 (#409 markdown section links) merged with main 939dbb69: RE-PINNED with UPDATE_GOLDEN=1 (hash +# a667ffc55e…8a05). The only watched text that moved is the kParserVer line and quality.h's mirror: renumbered again at +# merge to 206 (rich file tag 207), above main 196 / 197 and the live branch builds 198, 202 and 204. kCacheVersion stays +# 36 and kQSnapCacheScheme stays 22: the PR keeps a markdown link's written path in an existing field, no record format moved. # 2026-10-18, train 30 (call-result-typing, go-local-var, fcall-forward, name-template-dispatch, tail-sym, path-owners merged # onto main b59b0824): RE-DERIVED ONCE with UPDATE_GOLDEN=1 on the merged tree (hash 5af7514356…3726b4). Watched text: # kParserVer -> 196 (lanes call-result-typing 180 / rich 181, fcall-forward 188 / 189, go-local-var 190 / 191, From 24bcbf89f5ebb11dd5e2e726c23e5e6064d3821e Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Mon, 19 Oct 2026 01:46:02 -0400 Subject: [PATCH 15/28] release: 0.6.8 (version, CHANGELOG section, README release notes) Version 0.6.7 -> 0.6.8 in CMakeLists.txt; the CHANGELOG [Unreleased] section becomes [0.6.8] - 2026-10-19 with a fresh empty [Unreleased] above it, a summary (two grading methods, 0.6.5 -> 0.6.7 -> 0.6.8, credits) and entries for the changes whose lanes merged without one (--for stops when complete + soft --token-budget, the owner body, the startup git check, --pr-context #395, the dangling-reference fences). The closure/bridge "Fixed" entry, which fixed features that exist only in this release, is now an Added entry. The README gets the 0.6.8 release-notes paragraph, the "Latest" line and the Windows snippet version. No logic changes; kParserVer 198 / kCacheVersion 36 unchanged. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 105 +++++++++++++++++++++++++++++++++++++++---------- CMakeLists.txt | 2 +- README.md | 6 ++- 3 files changed, 89 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 400bd6aff..1a5b865a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,67 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +## [0.6.8] — 2026-10-19 + +Four batches of reviewed changes in one release. What a user sees: +- `--for` stops when its answer is complete, opens one owner body for a "how does X work" question, appends the bodies of its top + ranked rows and names eight different files first. +- `--callers` and `--callees` serve up to 50 rows whole (a callers list up to 16,000 B); `--impact` of a type finds the code + that uses it; declined calls are named as `` rows; `--path` says where an unreached path stopped. +- Fewer false edges: Go receiver scope (#403), call-result typing, Rust standard members (#405), minimum arity, + `f.call()` / `f.apply()`, qualified annotations; `--safe-delete` hedges constructed-name lookups instead of `none-found`. +- `--quality-delta=A..B` fits a memory limit (#387); git filter and diff programs are neutralised; Clojure (#349); `--pr-context` is faster. +- Upgrade: caches rebuild once (`kParserVer` 198, `kCacheVersion` 36); re-pin Go quality baselines, acks and notes once (Go entry). +- Measured: on our fixed set of 173 questions over public repositories (10 of them from one repository sealed before any build ran), + graded two ways, a stricter method (a rubric item counts only when the answer names its primary symbol or at least two of its + listed terms) and a more lenient one (any one listed term), the questions answered completely in one call went 50, 83, 98 + (stricter) and 78, 109, 127 (more lenient) for 0.6.5, 0.6.7 and 0.6.8; on the 10 sealed questions, 2, 5, 6 (stricter). The + 0.6.8 figure was measured on the train-28 build of 0.6.8; the later changes in this release were measured only on their own + target questions (release notes). Same machine, mechanical grading. +- Thanks: @LaimaWu (#380, #382), @mpapis (#396, #400, #401, #412), @tonibergholm-codento (#387, #391, #392), @tag-und-nacht (#349), + and @muayyad-alsadi (#409; the markdown-link work in #414 is coming next, and the idea behind the help-wanted issue #417). + +### Changed — `--for` stops when its answer is complete; an explicit `--token-budget` is a soft target + +With no `--token-budget`, a `--for` answer used to grow to a fixed length however complete it already was. It now stops when it is +complete: a 7,500 B stair-step target with a 30,000 B runaway guard that discloses itself when it fires (`over_ceiling="1"`). +An explicit `--token-budget` becomes a soft target: the fixed sections are reserved first, the `` carries page 0 of the file +page (`sym=`, `total=`, `next=`), every cut stays recoverable through `next=`, and the shown rows are a rank prefix at every +budget and root length. Rows kept past the target ride `` and a legend clause. Measured on 30 rows of our +question set: no item lost against the old budget, and 6 of 6 rows registered for it reach their missing item. + +### Changed — an explanatory `--for` question gets one owner body + +For a "how does X …" question the answer now opens ONE body chosen by structure, never by score alone: the symbol the question +names, an entry point, or the member the parser scopes to its class (C++, Rust and Python; TypeScript class members and Go +receiver methods are a named floor). The legend says it is the owner by rule, "not necessarily the subject", and that it is not +a claim to be the whole answer; with no structural evidence there is no owner body. On the 3 rows registered for it, fastify-12 +and ninja-11 reach their missing item and koa-12 does not; on 45 how-does questions the median answer grows by 702 B (14,636 to +15,338 B), and none of the 44 complete rows lost an item. + +### Changed — the startup git check reads `.git` pointer files itself + +Every run started one `git rev-parse` child (269 to 351 ms, median of 7, on four repositories) to find the git directories it +must pin. Pointer files (`.git`, `commondir`) are now read directly; any doubt, such as a NUL byte or a symlinked pointer, falls +back to asking git. The check itself takes 0.10 to 0.24 ms. Output is byte-identical on 33 argv over tmux, hono, textual and this +repository. + +### Changed — `--pr-context` builds its include-coupling table once per call (#395) + +The table depends only on the index but was rebuilt for every changed file and again on every pass of the trim ladder. +`--pr-context=HEAD~30` on this repository: 49.45 s and 45.79 s become 8.66 s and 7.67 s (two interleaved runs each, about 5.8 +times faster); the output is byte-identical on 49 argv. + +### Changed — source builds: lifetime fences on GCC 13+ and Clang (no behaviour change) + +GCC 13 and later build our own targets with `-Werror=dangling-reference` (a reference bound to a call's result that took a +temporary, as in `for( x : makeRow().items() )`, reads a dead object on GCC 13 and 14, which have no C++23 P2718 range-for +lifetime extension); `RW_LIFETIMEBOUND` marks the accessors that return a view of an argument for Clang's `-Wdangling`; +`test/danglingwarncheck.sh` plants each shape per compiler, and `portablebuildcheck` #7 holds the range-for rule. The tree had 0 +such sites on GCC 13.4, 14.4 and 16.2 and on Clang, so the fences found no bug and changed no output (33 of 33 argv +byte-identical). CONTRIBUTING.md now says: never rely on P2718, and use signed integers for quantities. + + ### Changed — `--for`'s first eight rows name up to eight distinct files before any file takes a second row (head round-robin) A routed (subtoken+body) `--for` answer's head was a list of the highest-scoring rows, and one file often took most of it: on @@ -57,27 +118,6 @@ answer (fold, its legend clause and the `` pair) +3.4 KB median (max 4.9 K `next="--callees=…"`, and the `--for --limit=N` file page legend says `n=` counts this task's symbols, not the file's (`--expand` `sibs_total=`). -### Fixed — closure blocks and bridges: three false hedges a hand-check found - -- `post: function _post( … ) { … }` (and `compile: function compile`) is one definition the parser reports twice; the - property is no longer listed as a closure beside the function expression, which takes its place. -- C, C++ and Objective-C have no named nested function, so a function span the parser reports inside a function (htop's - `else if (…) {` read as a function named `if`) is no longer a closure (a `langfacts.h` column, no language compare). -- A `` no longer routes through a test, bench, demo, generated or minified symbol (it may still start or end at - one), and a chain longer than 8 hops is not shown. -- A `` no longer crosses a declined member call spelled like a builtin type's method (`ids.join( "," )`, - `keys.push( k )`, `d.get( k )`, Ruby `each`): the builtin-method gate's per-language tables decide, and on a 42-query - hand-check every such crossing was a false chain. -- `` reads neither such a builtin-spelled declined call nor a wrapper decorator (`@property`, `@classmethod`, - `@overload`: the dead-code rule's own list) as a way into a row. -- By-name hedges (`` `why="name"`, a `` `via="namesake"` crossing) now need a call of the name that - is not spelled like a builtin type's method (`prerequisites.clear`, `self._invalid_css.update( … )`) AND a namesake - whose owner (its class in its file, or its file for a free function) owns something the forward search reached. On the - 24-row near_sink hand-check sample this removes all 5 by-name rejects and no accepted row; it is a hedge filter, not - proof that a dropped namesake is never the one called. - A row whose by-name calls either rule drops says so with `withheld="name:N"` (N callers) and never reads `why="external"`: the - index holds a way in it chose not to count (textual's `Input.render_line`, entered by `widget.render_line( y )`). - ### Added — `--path` offers a hedged bridge (``) and glob endpoints A `reachable="0"` `--path` answer (and MCP `path_between`) now carries `` when a chain @@ -127,6 +167,29 @@ lists the first row's callers. On htop, `--path=ScreenManager_run,LinuxMachine_s `Machine_scan` (the sink's only caller) and the declined call in `checkRecalculation` that could have meant it. Every `reachable="1"` answer is byte-identical; the reachable="0" hint and gap clause are unchanged. +### Added — closure blocks and bridges do not hedge on look-alikes + +Rules for the `` blocks, `` and `` entries above, found by a hand-check of their first build and shipped with them: + +- `post: function _post( … ) { … }` (and `compile: function compile`) is one definition the parser reports twice; the + property is no longer listed as a closure beside the function expression, which takes its place. +- C, C++ and Objective-C have no named nested function, so a function span the parser reports inside a function (htop's + `else if (…) {` read as a function named `if`) is no longer a closure (a `langfacts.h` column, no language compare). +- A `` no longer routes through a test, bench, demo, generated or minified symbol (it may still start or end at + one), and a chain longer than 8 hops is not shown. +- A `` no longer crosses a declined member call spelled like a builtin type's method (`ids.join( "," )`, + `keys.push( k )`, `d.get( k )`, Ruby `each`): the builtin-method gate's per-language tables decide, and on a 42-query + hand-check every such crossing was a false chain. +- `` reads neither such a builtin-spelled declined call nor a wrapper decorator (`@property`, `@classmethod`, + `@overload`: the dead-code rule's own list) as a way into a row. +- By-name hedges (`` `why="name"`, a `` `via="namesake"` crossing) now need a call of the name that + is not spelled like a builtin type's method (`prerequisites.clear`, `self._invalid_css.update( … )`) AND a namesake + whose owner (its class in its file, or its file for a free function) owns something the forward search reached. On the + 24-row near_sink hand-check sample this removes all 5 by-name rejects and no accepted row; it is a hedge filter, not + proof that a dropped namesake is never the one called. + A row whose by-name calls either rule drops says so with `withheld="name:N"` (N callers) and never reads `why="external"`: the + index holds a way in it chose not to count (textual's `Input.render_line`, entered by `widget.render_line( y )`). + ### Changed — Go methods carry their receiver type as scope (#403, slice 9a) Go methods now carry their receiver type as scope (`sc=`), with `*` and type arguments stripped (`*Stack[T]` → `Stack`), diff --git a/CMakeLists.txt b/CMakeLists.txt index ca4639bc8..fe3282dee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.24) # The version string lives in exactly ONE place — this project() call. --version (main.cpp) # reads it via the generated version.h below; test/versioncheck.sh asserts the two never drift. -project(ripwire VERSION 0.6.7 LANGUAGES C CXX) +project(ripwire VERSION 0.6.8 LANGUAGES C CXX) # ---- language standards (C++23 for our own code / C11 for the vendored C dependencies) ---- set(CMAKE_CXX_STANDARD 23) diff --git a/README.md b/README.md index c55e45cb7..b72ba3c5f 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ claim cannot quietly drift. The row-by-row ledger is JavaScript · Java · Ruby · PHP · Lua · Elixir · Dart · Kotlin · GDScript · Clojure · Bash · C# · JSON · TOML · YAML · Markdown — see [language support and limits](#languages). -**Latest: 0.6.7** — more complete answers, faster calls, fewer bytes. [Release notes](#release-notes) · +**Latest: 0.6.8** — more complete first answers and fewer false edges. [Release notes](#release-notes) · [the presentation](present/ripwire-showcase.pdf) · [the changelog](CHANGELOG.md) — with thanks to the contributors named there; this release is largely theirs. @@ -1058,7 +1058,7 @@ report back. The exe is not code-signed. `Expand-Archive` does not pass the down files it extracts, so SmartScreen does not prompt for an exe unpacked this way; no prompt is not a verdict. In PowerShell: ```powershell -$v = "0.6.3"; $a = "ripwire-$v-windows-x64"; $u = "https://github.com/redhat-et/ripwire/releases/download/v$v" +$v = "0.6.8"; $a = "ripwire-$v-windows-x64"; $u = "https://github.com/redhat-et/ripwire/releases/download/v$v" Invoke-WebRequest "$u/$a.zip" -OutFile "$a.zip"; Invoke-WebRequest "$u/$a.zip.sha256" -OutFile "$a.zip.sha256" # extracts only when the SHA-256 matches; on a mismatch it throws and nothing is unpacked if ((Get-FileHash "$a.zip" -Algorithm SHA256).Hash -eq (Get-Content "$a.zip.sha256").Split(" ")[0]) { Expand-Archive "$a.zip" -DestinationPath "$env:LOCALAPPDATA\Programs" } else { throw "SHA-256 mismatch: do not run $a.zip" } @@ -3092,6 +3092,8 @@ terms. ## Release notes +**ripwire 0.6.8 — more complete first answers and fewer false edges.** `--for` stops when its answer is complete, names one owner body for a "how does X work" question, appends the whole bodies of its top ranked rows and puts eight different files in its first eight rows; `--callers` and `--callees` serve up to 50 rows whole (a callers list whole while its rows fit 16,000 B); `--impact` of a type starts from the symbols that name it; and the calls the resolver declined are named as hedged rows. Fewer edges are false: Go methods carry their receiver type, a local bound to a call takes the callee's declared result type (Go, Rust, Java, Kotlin, TypeScript), Rust standard-type members no longer bind an in-tree namesake, and a method a constructed-name lookup can reach is `risk="hedged"` in `--safe-delete`, not `none-found`. On our fixed set of 173 questions, graded two ways (a stricter method and a more lenient one), the questions answered completely in one call went from 50 with 0.6.5 to 83 with 0.6.7 to 98 with 0.6.8 under the stricter method, and from 78 to 109 to 127 under the more lenient one (same machine, mechanical grading; 15 answers better and none worse since 0.6.7; on the 60 development questions the first answers are 19.9% larger). The 0.6.8 figure was measured on the train-28 build of 0.6.8; the later changes in this release were measured only on their own target questions. Clojure is a supported language (thanks @tag-und-nacht, #349). `--quality-delta=A..B` holds one commit's index at a time and answers what it can under a memory limit, and the guard counts a Linux cgroup's real charge (thanks @tonibergholm-codento, #387, #391, #392). Ruby mixins and Rails capture moved onto the shared path (thanks @mpapis, #396, #400, #401, #412), Python imports and Elixir `unquote` calls too (thanks @LaimaWu, #382, #380). Markdown links that bind the exact heading (thanks @muayyad-alsadi, #409, #414) are coming next. Caches are rebuilt once on first run, and Go quality baselines, acks and notes pinned to a method need re-pinning once (see the changelog). + **ripwire 0.6.7 — more complete answers, faster calls, fewer bytes.** Each `--callers` and `--safe-delete` row now names its call sites as `file:line`, so one answer holds the lines to edit (`--callers=escapeXml` on this repository: its first answer shows 40 of 142 callers, every one with its call lines, 79 in all; 0.6.5 showed none). `--lego` lists implementors below a direct one (a two-level From 9c3ab2e6a1b1ce6141cac59989447eff6df5e1fb Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Mon, 19 Oct 2026 02:28:34 -0400 Subject: [PATCH 16/28] docs: the 0.6.8 regeneration from the release build - showcase capture, COMMANDS.md From build/ripwire at 24bcbf89 (the release commit, clean tree, 0.6.8): - docs/captures/COMMANDS_showcase_2026-10-19.md captured with test/showcase_capture.py (265 commands); its version sample reads ripwire 0.6.8 and it records the live legend dictionary (dictv=f1e98faa0aeaa1ee entries=902, unchanged). - docs/COMMANDS.md rebuilt from it (docs/docs_commands_build.py: 188 flags, 172 samples). Co-Authored-By: Claude Opus 5.5 --- docs/COMMANDS.md | 277 +- docs/captures/COMMANDS_showcase_2026-10-19.md | 7854 +++++++++++++++++ 2 files changed, 7990 insertions(+), 141 deletions(-) create mode 100644 docs/captures/COMMANDS_showcase_2026-10-19.md diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 9524d08d4..754c56ec0 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-18.md`), trimmed to the first few lines and +Sample output is lifted from a real recorded run (`docs/captures/COMMANDS_showcase_2026-10-19.md`), trimmed to the first few lines and scrubbed of local paths. It is illustrative, not a golden: run the command yourself for the current shape. @@ -70,7 +70,7 @@ _Same map, capped to the 5 highest-ranked symbols._ $ ./build/ripwire . --top-k=5 - + @@ -106,7 +106,7 @@ _The same capped map without its inventory tier: --top-k=5 leaves files out, so $ ./build/ripwire . --top-k=5 --no-inventory - + @@ -140,7 +140,7 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ $ ./build/ripwire . --max-tokens=1500 - + @@ -215,7 +215,7 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - + @@ -252,7 +252,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 - + @@ -279,7 +279,7 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - + @@ -314,7 +314,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 - + @@ -346,7 +346,7 @@ _Same query with routing forced OFF (plain subtoken+body BM25) — contrast with ``` $ ./build/ripwire . --for="rankGraphTeleport" --no-route - + @@ -378,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 - + @@ -409,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 - @@ -439,7 +439,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 - + @@ -531,7 +531,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" — 114 relevant of 221 document files, best-first — total=114 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2157 est_tokens=4593 -━━ README.md (relevance 8.060) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 220571 B; lines="2747-2759,2760-2767"; dropped_by_budget=23] +━━ README.md (relevance 8.061) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 222662 B; lines="2747-2759,2760-2767"; dropped_by_budget=23] #### 6.2 Exit codes This is the part to wire into a script. @@ -594,25 +594,20 @@ 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 1375243-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 1377030-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=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.996) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 587071 B; lines="558-583"; dropped_by_budget=163] [truncated: 1854 of 4257 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base +━━ commands.md (relevance 10.114) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 588930 B; lines="3838-3842"; dropped_by_budget=163] [truncated: 1865 of 7739 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` @@ -732,7 +727,7 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable $ ./build/ripwire . --no-stable --top-k=3 - + @@ -766,7 +761,7 @@ $ ./build/ripwire . --around=rankGraphTeleport - + @@ -1148,7 +1143,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: 2c35be9f0 +at: 24bcbf89f (0 changed files — working tree is clean, nothing to analyze) ``` @@ -1167,18 +1162,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] ``` @@ -1497,14 +1492,14 @@ $ ./build/ripwire . --query="teleport pagerank" --top-k=5 - + - + - + @@ -1529,7 +1524,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1564,7 +1559,7 @@ $ ./build/ripwire . --pack-signatures --top-k=10 - + @@ -1762,7 +1757,7 @@ _Fan-in/out + complexity annotations on the map._ $ ./build/ripwire . --metrics --top-k=10 - + @@ -1835,7 +1830,7 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - + @@ -2061,7 +2056,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2201,16 +2196,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - + + + + - - - - + ... [22 more line(s); run it to see the whole thing] ``` @@ -2233,16 +2228,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] ``` @@ -2259,7 +2254,7 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + @@ -2288,7 +2283,7 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - + @@ -2297,7 +2292,7 @@ $ ./build/ripwire . --hotspots --since="2 weeks ago" - + @@ -2632,15 +2627,15 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + - + @@ -2663,7 +2658,7 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - ``` @@ -2688,19 +2683,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] ``` @@ -2725,11 +2720,11 @@ $ ./build/ripwire . --report # ripwire architecture report -3090 files · 29471 symbols · 28529 edges · 3474 modules (13221 call-graph isolated) +3090 files · 29477 symbols · 28529 edges · 3474 modules (13227 call-graph isolated) Root: `.` -Call-graph isolate provenance: 3604 declaration, 2112 header, 2755 source, 4750 document; 0 connected Louvain singletons +Call-graph isolate provenance: 3604 declaration, 2112 header, 2755 source, 4756 document; 0 connected Louvain singletons ## Modules (call-graph clusters; showing 12 of 3474) - **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1488 symbols @@ -2811,7 +2806,7 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - + @@ -2907,7 +2902,7 @@ _The same clean verdict with what it folded printed back: every stale acknowledg ``` $ ./build/ripwire . --quality-delta --show-stale - @@ -2936,7 +2931,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -2961,7 +2956,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -2986,7 +2981,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 -

@@ -3079,7 +3074,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3109,7 +3104,7 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - @@ -3349,7 +3344,7 @@ _Bare --slice=SYM: the INVENTORY of sliceable locals (), so a calle ``` $ ./build/ripwire . --slice=rankGraphTeleport - + @@ -3386,7 +3381,7 @@ _Forward flow: which statements the seed's value reaches, at the default depth b ``` $ ./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd - + @@ -3467,7 +3462,7 @@ _No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - ``` @@ -3493,11 +3488,11 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + @@ -3572,7 +3567,7 @@ _Which refs of the `lane/` ref family still hold divergent authored work vs HEAD ``` $ ./build/ripwire . --stray-content=lane/ - + @@ -3606,7 +3601,7 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + @@ -3642,13 +3637,13 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi - - + @@ -3677,11 +3672,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] ``` @@ -3707,8 +3702,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 - - + + @@ -3735,8 +3730,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 - - + + @@ -3883,7 +3878,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3917,7 +3912,7 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + @@ -3950,8 +3945,8 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + @@ -3984,7 +3979,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -4049,7 +4044,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4209,7 +4204,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - + @@ -4282,11 +4277,11 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - + - - + + - + - - + + : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. amb=K: K calls split over several defs. --> - - + + @@ -4472,20 +4467,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~3 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~4 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - - + + - - - - - - - - + + + + + + + + ... [7 more line(s); run it to see the whole thing] ``` @@ -4549,7 +4544,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4665,7 +4660,7 @@ _Drop matching paths (repeatable) before ranking._ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - + @@ -4696,8 +4691,8 @@ _Full map re-ranked with teleport toward git-changed files — clean tree, so ch $ ./build/ripwire . --map-diff --top-k=5 - - + + @@ -4730,7 +4725,7 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - + @@ -4780,7 +4775,7 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co $ ./build/ripwire . --no-cache --top-k=3 - + @@ -4811,7 +4806,7 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses $ ./build/ripwire . --no-ignore --top-k=3 - + @@ -4927,7 +4922,7 @@ _Eval-only: a per-call-site census of WHICH mechanism resolved each call, and th $ ./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3 - + @@ -4982,9 +4977,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":" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [727 more display lines; full output is 23442 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --top-k=5` + +*Same map, capped to the 5 highest-ranked symbols.* + +**wall time: 1.40s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [58 more display lines; full output is 8090 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.32s** + +````` + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.33s** + +````` + + + + + + + + + + +````` + +## `./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.30s** + +````` + + +````` + +stderr: + +````` +ripwire: --token-budget exceeded: withheld_est_tokens=9444 > 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.62s** + +````` + + + + +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 + +persistent in-memory index (parse once, reuse across MCP calls) ---- The MCP server is long-live…struct McpIndex +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] +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +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 std::string reAbsolutize( std::string_view rel, std::string_view root ) +inline CachePathKeys buildCachePathKeys( const std::vector<std::string>& files, std::string_view rootDir ) +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 void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) +inline std::uint64_t byteHash( const char* data, std::size_t n ) noexcept +void emitGrepHandleLegend( bool enabled ) +… [120 more display lines; full output is 19496 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) +````` + +## `./build/ripwire . --for="rankGraphTeleport"` + +*Name-shaped query: the router picks name-exact BM25 (header says which/why).* + +**wall time: 2.80s** + +````` + + + + + +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 ) +{ + 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; + for( const double value : teleport ) + { + teleportMass += value; +… [15 more display lines; full output is 4560 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: 2.97s** + +````` + + + + + +uniform-teleport PageRank (the defaultinline RankedGraph rankGraph( const Graph& g, float alpha = 0.85f ) + +PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quali…inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) +inline ChurnRanking churnRankedGraph( const MainDispatch& d ) + +L10 (2026-09-04): the old wording claimed "the same corpus ranked by pagerank orders differently…inline constexpr const char* kChurnRankLegend = "<!-- rank_by=churn: k= is PageRank re-run with the teleport BIASED by git CHANGE-FREQUENCY over window= " "(a c… +struct RankedGraph +inline std::vector<float> takeRank( RankedGraph ranked, RankDisclosure& disclosureOut ) +inline std::vector<float> biasPrior( const Graph& g, const std::vector<float>& p ) +inline std::vector<float> churnTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, const char* since = "18 months ago& … [line truncated: 20 more bytes on this line] +inline ChurnRanking churnDecayRanking( const MainDispatch& d, const rw::SinceScope& sinceScope, bool isScoped, const char* verbLabel ) +inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) +PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double> rank… +inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = n… +inline std::string didYouMean( const IngestResult& ing, std::string_view name ) +PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span< … [line truncated: 45 more bytes on this line] +inline const McpIndex& getIndex( const std::string& root ) +inline std::vector<float> churnDecayTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, bool* outHasChurnEvidence = n … [line truncated: 12 more bytes on this line] +struct DataSectionsCut +inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) +inline std::vector<float> anchoredLexicalRank( const Graph& g, const std::vector<float>& lex ) +### Fixed — `rank_by` over MCP no longer answers a different ranking than `--rank-by` on … [line truncated: 35 more bytes on this line] +### 2. Gates for A +inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 +#### The convergence disclosure contract +… [149 more display lines; full output is 24027 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.54s** + +````` + + + + + +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: 5.08s** + +````` + + + + +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 +constexpr std::uint32_t kMaxYamlNestDepth = 64u +inline bool hasMainGuard( std::string_view source ) +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +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 ) +enum class DisclosureWhy : std::uint8_t +struct LintRule +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline bool hasPytestTable( std::string_view source ) +… [137 more display lines; full output is 19675 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: 4.89s** + +````` + + + + +The name-exact ranker AS THE RETRIEVAL LENS SERVES IT: whole-name BM25 plus the definition-over-…inline std::vector<float> lexicalScoresNameExactRanked( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) + +P11.12: the interpretive footer for --eval's ranker table, pulled into its own function so the 9…inline void printEvalRankerNote() +inline int runEvalRetrieval( const IngestResult& ing, const Graph& g ) + +calling agent knows to reformulate rather than trust the ranking. Calibrated empirically (2026-0…inline constexpr float kWeakLexicalScoreThreshold = 1.0f +inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) +inline std::string candidatesRootTag( std::size_t keep, std::size_t corpusCount, const CandidateProvenance& prov ) +inline std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) +inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) +inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) +struct NameCorpusStats +inline bool hasIdentifierShape( std::string_view w ) noexcept +struct LensRanking +struct Bm25Params +inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) +inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept +rw::LensRanking computeLensRanking( const MainDispatch& d, std::string_view task, bool compactCandidate = false, bool fullDistribution = false ) // deep-tail: the file-grain tail rea … [line truncated: 65 more bytes on this line] +struct CandidateProvenance +inline constexpr int kLexWeightDoc = 2 +std::optional<int> runEvalViews( const MainDispatch& d ) +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 ) +struct RecallSectionPick +inline double binomialChoose( std::size_t n, std::size_t k ) +… [146 more display lines; full output is 21490 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: 4.20s** + +````` + + + + +The name-exact ranker AS THE RETRIEVAL LENS SERVES IT: whole-name BM25 plus the definition-over-…inline std::vector<float> lexicalScoresNameExactRanked( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) + +P11.12: the interpretive footer for --eval's ranker table, pulled into its own function so the 9…inline void printEvalRankerNote() +inline int runEvalRetrieval( const IngestResult& ing, const Graph& g ) + +calling agent knows to reformulate rather than trust the ranking. Calibrated empirically (2026-0…inline constexpr float kWeakLexicalScoreThreshold = 1.0f +inline std::string candidatesRootTag( std::size_t keep, std::size_t corpusCount, const CandidateProvenance& prov ) +inline std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) +inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) +inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) +struct NameCorpusStats +inline bool hasIdentifierShape( std::string_view w ) noexcept +struct LensRanking +struct Bm25Params +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 ) +inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept +rw::LensRanking computeLensRanking( const MainDispatch& d, std::string_view task, bool compactCandidate = false, bool fullDistribution = false ) // deep-tail: the file-grain tail rea … [line truncated: 65 more bytes on this line] +struct CandidateProvenance +inline constexpr int kLexWeightDoc = 2 +std::optional<int> runEvalViews( const MainDispatch& d ) +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 ) +struct RecallSectionPick +inline double binomialChoose( std::size_t n, std::size_t k ) +… [154 more display lines; full output is 21492 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.26s** + +````` + + + + + + + + + +````` + +## `./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: 5.56s** + +````` + + + + + f, A&&... a ) +{ + // A fixed char[] must arrive as rw::cstr( buf ). printf's %s always meant "bytes to the first NUL"; + // `{}` on a char[N] is a different question that library versions answer differently, and an + // implementation that formats the ARRAY emits the trailing NUL and the uninitialised bytes after it. + // A regex sweep missed sites twice, so the compiler enforces it instead of a reviewer. + // Only a MUTABLE char[N] is rejected. A string literal is const char[N]; every implementation formats + // that as a string, and the hazard here is the reusable buffer that was written short. + static_assert( ( ... && !( std::is_array_v> + && !std::is_const_v>> ) ), + "pass rw::cstr( buf ) for a fixed char buffer: {} on a char[N] is not printf's %s" ); + std::fputs( std::format( f, std::forward( a )... ).c_str(), stream ); +}]]> +````` + +## `./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: 3.74s** + +````` + + + + +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: 3.03s** + +````` + + + + +````` + +## `./build/ripwire . --recall="quality delta gating exit codes"` + +*Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* + +**wall time: 1.49s** + +````` +ripwire recall — "quality delta gating exit codes" — 114 relevant of 221 document files, best-first — total=114 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2157 est_tokens=4593 + +━━ README.md (relevance 8.061) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 222662 B; lines="2747-2759,2760-2767"; dropped_by_budget=23] +#### 6.2 Exit codes + +This is the part to wire into a script. + +| Code | Meaning | +| --- | --- | +| 0 | The command completed. | +| 1 | The command refused the request. A refusal names the reason on stderr. When `--callers`, `--callees`, `--uses`, `--impact` or `--path` refuse a selector that matches no indexed definition, they also print an answer on stdout: the verb's element with `found="0"` and the name to retry with. | +| 2 | A policy gate fired: `--arch` found a layering violation, `--scan-skill` found a CRITICAL, `--quality-delta` found new debt. | +| 3 | The output exceeded the token budget that you set. | +| 4 | `--test-gate` found an open obligation. | +| 5 | The memory guard stopped the run: over `--max-memory` (default 65% of the machine's memory) with no partial answer possible, or a verb other than the default map facing a partial index. One stderr line names the limit and the override. | + +#### 6.3 JSON output + +`--json` emits JSON instead of XML, with keys mirroring the XML attribute names one to one. It is an +allow-list: the default map, `--for`, `--pack-task`, `--callers`, `--callees`, `--impact`, +`--quality-delta`, `--test-gate`, `--metrics`, and `--plan-lanes`, which is JSON-native. Every other +verb refuses on stderr with exit 1 rather than silently falling back to XML, so a verb added tomorrow +refuses by default. + + + +━━ CHANGELOG.md (relevance 7.542) ━━ [sections: 1 of 109 selected (450 in doc), section-granular; whole doc 995204 B; lines="542-557"; dropped_by_budget=108] [truncated: 1597 of 3826 bytes] +### Changed — `--quality-delta=A..B` answers the kinds it can under memory pressure, and exits 6 when that answer has no gating row (#387 follow-up) + +A tripped memory guard in the range form used to give exit 5 and nothing, so on a large repository a four-file change +… [129 more lines, 11714 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 1377030-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.114) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 588930 B; lines="3838-3842"; dropped_by_budget=163] [truncated: 1865 of 7739 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).* + +**wall time: 2.25s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [368 more display lines; full output is 11214 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --html=/aux/map2.html` + +*Self-contained HTML force-directed call graph.* + +**wall time: 4.22s** + +````` +(empty) +````` + +Artifact written: + +````` + 156619 /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.51s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [58 more display lines; full output is 8108 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.* + +**wall time: 1.06s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [160 more display lines; full output is 7666 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.* + +**wall time: 1.96s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [1928 more display lines; full output is 52641 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.* + +**wall time: 2.52s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --callers=rankGraphTeleport` + +*Who calls SYM (1-hop in-edges).* + +**wall time: 2.32s** + +````` + + + + + + + + + + +````` + +## `./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.55s** + +````` + + + + + + + + + + + +````` + +## `./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.89s** + +````` + + +````` + +stderr: + +````` +ripwire: --callers symbol not found: DoesNotExist +````` + +## `./build/ripwire . --callees=rankGraphTeleport` + +*What SYM calls (1-hop out-edges).* + +**wall time: 1.71s** + +````` + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --uses=rankGraphTeleport` + +*The resolvable use-sites (call/read/write/import/extends) with file:line; count= is a floor.* + +**wall time: 1.44s** + +````` + + + + + + + + + + + + +````` + +## `./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: 2.10s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [28 more display lines; full output is 3649 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.54s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5588 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).* + +**wall time: 1.66s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5451 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").* + +**wall time: 1.04s** + +````` + + + + + + + + +````` + +## `./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex` + +*Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* + +**wall time: 1.18s** + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --impact=rankGraphTeleport` + +*Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* + +**wall time: 1.36s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [69 more display lines; full output is 6413 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --mentions=rankGraphTeleport` + +*Markdown docs that name SYM in a backtick (doc<->code edges).* + +**wall time: 1.22s** + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --affected=src/graph.h` + +*Test files that transitively reach the changed file.* + +**wall time: 1.47s** + +````` + + + + + + + + + +````` + +## `./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.31s** + +````` +ripwire situational-awareness — 0 changed file(s), 0 symbols in them +root: . +at: 24bcbf89f + (0 changed files — working tree is clean, nothing to analyze) +````` + +## `./build/ripwire . --test-gate` + +*Pre-PR gate on a CLEAN tree: no obligations, exit 0.* + +**wall time: 5.82s** + +````` + + +````` + +## `./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.42s** + +````` + +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.93s** + +````` + +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: 2.08s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [21 more display lines; full output is 3219 bytes on 1 raw line(s)] +````` + + +--- + +# zoom the detail ladder + +## `./build/ripwire . --for="pagerank power iteration" --detail=2` + +*Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* + +**wall time: 4.16s** + +````` + + + + + +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 +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] +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +handle_t leftmost_leaf() const +inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) +inline std::string doctorWhichRipwire( rw::os::stat_t& st ) +inline std::string doctorPathPrependLine( const std::string& selfPath ) +std::uint32_t testIterationCeiling() noexcept +inline const char* rankByLegendFor( const char* label ) noexcept +… [156 more display lines; full output is 22214 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: 3.06s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [146 more display lines; full output is 14816 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: 2.22s** + +````` + + + + +& 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: 2.44s** + +````` + + + + +& 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: 2.27s** + +````` + + + + +: files the ranked rows show / do not (sum = files=), each named or counted below. : a program entry by its language's convention (C/Go/Rust main, Java/C# static main, a script main its module scope calls, __main__.py), callees as c rows; manifest entries not read. : n= unshown files under dir p=; f= names its code files, the rest (tests, docs, config, test/doc/example dirs) counted. declined=K: K call sites without edges, including inert Elixir quote sites after unquote/unquote_splicing re-entry. external=K: K calls proven outside the tree, no edge. locality_pinned=K: K calls pinned by locality alone (a guess). extent_suspect_syms=K: K defs failed containment, corpus-wide. macro_blanked_files=K: K files indexed from a macro-blanked re-parse. prov=scip|binding|import|split|final-segment: how that edge bound (absent: one unique name); split = one arm of an amb= pick; final-segment = a qualified type matched by last name only. files=/symbols=: files and symbols indexed; edges= distinct call edges; shown= symbols printed, a merged row counting each def; ambiguous= calls split over several defs, corpus-wide; unresolved= calls with in-tree evidence and no edge (every def language-filtered or unreachable, or binding refused); order= rows by rank (important-first, important-last; (auto:fill) = flipped past a size threshold) or by path (stable). skipped_oversize=K: K files over a size ceiling, not indexed. unindexed=ext:N: N text files of that extension no grammar reads (6 extensions at most). unindexed_exts=E: E such extensions in all, the list cut. : a non-test row an indexed test transitively reaches (absent otherwise, never 0). : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. sc=: enclosing scope; the full id is p::sc::n (p= of the row or its ) and selectors take it. amb=K: K calls split over several defs. locals_floor=1: locals= is a floor. role=hub: in= is 8 or more. s in= out= cx= ccx= loc= params= nest= cbo= amp=: callers, callees, cyclomatic/cognitive complexity, lines, parameters, nesting depth, coupled types, callers + co-changed files. ev=/ev_why=: essential complexity (2+: jumps block extract-method; absent: 1) / the jumps behind it, tag:count. ev_floor=1: ev= is a FLOOR; noreturn calls, macro-hidden exits and unresolved gotos are unseen. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. l=N: start line; only on a same-name overload's row, one row per body (bodyless decls fold into overloads=). --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [89 more display lines; full output is 10639 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --deps` + +*File->file dependency graph (god-files, cycles).* + +**wall time: 1.29s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [894 more display lines; full output is 23865 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --hotspots` + +*Complexity x recent git churn (maintenance pain).* + +**wall time: 6.66s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [14 more display lines; full output is 5768 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --clones` + +*Token-normalized duplicate bodies.* + +**wall time: 3.72s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [307 more display lines; full output is 16168 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --cochange` + +*Files that change together in git (hidden coupling).* + +**wall time: 5.20s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --hotspots --since="2 weeks ago"` + +*Hotspots scoped to RECENT churn (the regression lens).* + +**wall time: 25.84s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [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: 3.79s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [805 more display lines; full output is 117170 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint` + +*Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* + +**wall time: 5.98s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [684 more display lines; full output is 70577 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: 9.22s** + +````` + + + + +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: 3.32s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [189 more display lines; full output is 16813 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: 3.98s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [135 more display lines; full output is 7287 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: 3.72s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [265 more display lines; full output is 12022 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --report` + +*Architecture summary (modules, god-files, cycles) as markdown.* + +**wall time: 3.28s** + +````` + + +# ripwire architecture report + +3090 files · 29477 symbols · 28529 edges · 3474 modules (13227 call-graph isolated) + +Root: `.` + +Call-graph isolate provenance: 3604 declaration, 2112 header, 2755 source, 4756 document; 0 connected Louvain singletons + +## Modules (call-graph clusters; showing 12 of 3474) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1488 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 496 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 335 symbols +- **docs::`--top-k=N`@COMMANDS.md:59:9077 [pack,allow,insert]** — 182 symbols +- **test/ownerhopfix/py/pkg::big_handler_001@broadcast.py:3:72** — 106 symbols +- **src/infra::fail@os_win32.cpp:63:2553 [open,get,close]** — 89 symbols +- **test::no@estchargecheck.sh:52:4108 [skip,set]** — 63 symbols +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,compute,expand]** — 53 symbols +- **src::sliceKindIs@slice.h:368:24239 [match,collect,push]** — 48 symbols +- **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [insert,erase,find]** — 44 symbols +- **src::child@ingest_valuerefs.h:387:20429 [emit,open]** — 44 symbols +- **src::tokText@defectshape.h:128:5308 [scan,find,join]** — 41 symbols + +## God files (most depended-on; showing 10 of 221) +- `src/model.h` — 97 dependents +- `src/infra/emit.h` — 87 dependents +- `src/infra/Diagnostics.h` — 63 dependents +- `src/serialize.h` — 43 dependents +- `src/graph.h` — 42 dependents +… [35 more lines, 3956 bytes total] +````` + +## `./build/ripwire . --seams` + +*Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* + +**wall time: 1.54s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [60 more display lines; full output is 9252 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --mermaid` + +*Module (directory) dependency graph as a Mermaid diagram.* + +**wall time: 2.16s** + +````` +%% ripwire --mermaid: module (directory) dependency graph — node = dir (symbol count), edge = inter-module calls (>= 3). Render at mermaid.live. +flowchart LR + subgraph sg0 ["src"] + n101["src
8841"] + n102["src/infra
1294"] + end + subgraph sg1 ["test"] + n103["test
5405"] + n483["test/legochargefix
546"] + n394["test/fixtures/recallpassage
314"] + n564["test/ownerhopfix/py/pkg
158"] + n338["test/expandmodefix
151"] + n340["test/expandsibsfix
149"] + n705["test/rubyschemafix
99"] + n413["test/forbodyfoldfix/py
98"] + n687["test/rubyattrsfix
85"] + n540["test/massfix
77"] + end + n73["docs
847"] + n2[".github
621"] + n0["(root)
605"] + 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"] +… [18 more lines, 1556 bytes total] +````` + +## `./build/ripwire . --owners` + +*Bus-factor: recency-weighted author ownership per file.* + +**wall time: 6.94s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [934 more display lines; full output is 102054 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.59s** + +````` + + + + +````` + +## `./build/ripwire . --exercises=test/regression.sh` + +*Which symbols a TEST FILE exercises — the reverse direction of --affected.* + +**wall time: 1.56s** + +````` + + + +````` + +## `./build/ripwire . --community=0` + +*Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* + +**wall time: 1.62s** + +````` + + + +````` + +## `./build/ripwire . --quality-delta` + +*On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* + +**wall time: 35.72s** + +````` + + +````` + +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: 24.81s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [135 more display lines; full output is 15906 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: 18.19s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*No-LLM review-evidence bundle for the working-tree diff (clean tree = empty).* + +**wall time: 3.45s** + +````` + + + +````` + +## `./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: 8.88s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [32 more display lines; full output is 5917 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: 23.68s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --stray-content=lane/` + +*Which refs of the `lane/` ref family still hold divergent authored work vs HEAD, with verdicts.* + +**wall time: 22.17s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [212 more display lines; full output is 28603 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --stray-content=arm/for-howitworks-067-f2add` + +*A second selection, the one branch `arm/for-howitworks-067-f2add` (no ref family left to select on this checkout), picked at capture time from the refs this checkout really has and never the selection above: merged refs are OMITTED from the rows and counted in merged=; refs sharing no merge base with HEAD (a shallow clone, or a pre-rewrite history) land in unknown= with ok="0" — the counters always reconcile against refs=.* + +**wall time: 3.63s** + +````` + + + + + + + + + + + + + + + + + + +````` + +## `./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: 114.44s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [33520 more display lines; full output is 4640484 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --stray-content=zzzz-no-such-ref --plan` + +*A --plan filter that selects NO ref REFUSES (exit 1) naming the substring — before the wave-3 close this fell through to the '>512 refs match' sentence, and --abi under the same filter answered an empty measurement at exit 0.* + +**exit code: 1** — **wall time: 3.02s** + +````` +(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: 133.36s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 12.53s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 11.08s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [36 more display lines; full output is 13356 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --quality-delta=HEAD~1..HEAD --quality-kinds=complexity,dead-code` + +*A ref range run for two of the twelve kinds only: the named kinds' rows are exactly their rows in the full answer, the header names the others (kinds_skipped=, skipped_why="selector"), and a pass no named kind needs does not run. The replay a memory-stopped range answer hands back (kinds_next=) is this flag. Refused without --quality-delta=A..B.* + +**wall time: 14.93s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [35 more display lines; full output is 14369 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: 17.21s** + +````` + + + + + + + +````` + +## `./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.05s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [388 more display lines; full output is 23815 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --flip: no gate named 'RIPWIRE_ASA' in . (did you mean 'RIPWIRE_ASAN'?) +ripwire: run `ripwire . --flags` for the gate table +````` + +## `./build/ripwire . --plan-lanes=3 --task="add a --since filter to the doc-drift verb and cover it with tests"` + +*NEW VERB: pre-hoc lane plan — which of 3 parallel worktrees would COLLIDE, before a line is written. JSON on stdout.* + +**wall time: 2.10s** + +````` +{"v":1,"verb":"plan-lanes","at":"24bcbf89f","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":3090,"symbols":29477,"edges":285 … [line truncated: 338 more bytes on this line] +"symbols":[{"p":"./src/docdrift.h","n":"DriftResult","scope":"DriftResult","l":286,"id":"./src/docdrift.h::DriftResult::DriftResult"}, +{"p":"./src/docdrift.h","n":"computeDocDrift","scope":"docdrift","l":2441,"id":"./src/docdrift.h::docdrift::computeDocDrift"}, +{"p":"./src/docdrift.h","n":"kDocDriftLegend","scope":"docdrift","l":2739,"id":"./src/docdrift.h::docdrift::kDocDriftLegend"}, +{"p":"./src/docdrift.h","n":"writeDocDriftPage","scope":"docdrift","l":2909,"id":"./src/docdrift.h::docdrift::writeDocDriftPage"}, +{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","l":655,"id":"./src/mcpverbs.h::rw::docDriftText"}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","l":1678,"id":null}]},"lanes":[{"id":"lane-0","task":"add a --since filter to the doc-drift verb and cover it with tests","claims":{"symbols":[{"p":"./src/verbs_change.h","n":"runAbiCheck","scope":"","key":"183da9aaf74a4a92","id":null,"id_addr … [line truncated: 113 more bytes on this line] +{"p":"./src/editcheck.h","n":"editCheckPriceRoot","scope":"rw","key":"33219bb04158a6a8","id":"./src/editcheck.h::rw::editCheckPriceRoot","id_addressable":true,"id_collides_with":0,"l":1202,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":10,"churn":55,"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":1344,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":48,"tested":0}, +{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":3031,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":11,"churn":448,"tested":0}, +{"p":"./src/pageview.h","n":"secondaryCutAttrs","scope":"rw","key":"a10ea8d3bdca1dfb","id":"./src/pageview.h::rw::secondaryCutAttrs","id_addressable":true,"id_collides_with":0,"l":399,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":28,"tested":0}, +{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":691,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":448,"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":5711,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":428,"tested":0}, +{"p":"./src/wrap.h","n":"wrapEmitCliFirst","scope":"rw","key":"f5ef05d9574d9516","id":"./src/wrap.h::rw::wrapEmitCliFirst","id_addressable":true,"id_collides_with":0,"l":699,"ord":0,"overloads":1,"amb":7,"cx":10,"ccx":5,"churn":52,"tested":0}], +"files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":51}, +{"p":"./src/docdrift.h","symbols":4,"churn":36,"ccx":14,"hotspot_rank":35}, +{"p":"./src/editcheck.h","symbols":1,"churn":55,"ccx":10,"hotspot_rank":41}, +{"p":"./src/main.cpp","symbols":2,"churn":448,"ccx":12,"hotspot_rank":4}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":428,"ccx":2,"hotspot_rank":5}, +{"p":"./src/pageview.h","symbols":1,"churn":28,"ccx":2,"hotspot_rank":126}, +{"p":"./src/testmap.h","symbols":1,"churn":48,"ccx":4,"hotspot_rank":39}, +{"p":"./src/verbs_change.h","symbols":1,"churn":80,"ccx":9,"hotspot_rank":23}, +{"p":"./src/wrap.h","symbols":1,"churn":52,"ccx":5,"hotspot_rank":66}]},"blast_radius":{"reaches":49,"files_total":17,"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","./src … [line truncated: 189 more bytes on this line] +"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":49,"module_span":1,"notes":[], +"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"high","rule":"complex-or-wide","basis":"structural-only","signals":{"claims":13,"files":9,"module_span":1,"max_ccx":11,"sum_ccx":66,"ambiguous_calls":13,"blast_reaches":49,"contract_touches":0,"conflicts":0,"untested":49,"tests … [line truncated: 135 more bytes on this line] +… [66 more display lines; full output is 19957 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: 2.47s** + +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":"24bcbf89f","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3090,"symbols":29477,"edges":28529,"ambiguous":2904,"unresolved":14037},"carve":null,"core":{"files" … [line truncated: 4 more bytes on this line] +"symbols":[]},"lanes":[{"id":"lane-0","task":"add a --since filter to the doc-drift verb","claims":{"symbols":[{"p":"./src/docdrift.h","n":"recordUnchecked","scope":"docdrift","key":"12641dab14abc8fd","id":"./src/docdrift.h::docdrift::recordUnchecked","id_addressable":true,"id_collides_with":0,"l":2 … [line truncated: 72 more bytes on this line] +{"p":"./src/docdrift.h","n":"sortWeakGroupsByPath","scope":"docdrift","key":"1944ba506280ff80","id":"./src/docdrift.h::docdrift::sortWeakGroupsByPath","id_addressable":true,"id_collides_with":0,"l":2401,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":36,"tested":0}, +{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","key":"1fa68e8d93c05a59","id":"./src/mcpverbs.h::rw::docDriftText","id_addressable":true,"id_collides_with":0,"l":655,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":428,"tested":0}, +{"p":"./src/cli.h","n":"kViewFlags","scope":"rw","key":"2a2f2487082cc6d8","id":"./src/cli.h::rw::kViewFlags","id_addressable":true,"id_collides_with":0,"l":3326,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":508,"tested":0}, +{"p":"./src/recall.h","n":"docFileMask","scope":"rw","key":"3149a219f599664c","id":"./src/recall.h::rw::docFileMask","id_addressable":true,"id_collides_with":0,"l":110,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":27,"tested":0}, +{"p":"./src/docdrift.h","n":"writeDocDriftPage","scope":"docdrift","key":"380b7de5df1cfd73","id":"./src/docdrift.h::docdrift::writeDocDriftPage","id_addressable":true,"id_collides_with":0,"l":2909,"ord":0,"overloads":1,"amb":10,"cx":14,"ccx":15,"churn":36,"tested":0}, +{"p":"./src/docdrift.h","n":"computeDocDrift","scope":"docdrift","key":"3b19cc3d8996c3b2","id":"./src/docdrift.h::docdrift::computeDocDrift","id_addressable":true,"id_collides_with":0,"l":2441,"ord":0,"overloads":1,"amb":1,"cx":26,"ccx":38,"churn":36,"tested":0}, +{"p":"./src/docdrift.h","n":"DriftResult","scope":"DriftResult","key":"422ab39546b6e0bd","id":"./src/docdrift.h::DriftResult::DriftResult","id_addressable":true,"id_collides_with":0,"l":286,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"tested":0}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","key":"904831e5c0f66b39","id":null,"id_addressable":false,"id_collides_with":0,"l":1678,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":80,"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":5711,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":428,"tested":0}, +{"p":"./src/docdrift.h","n":"kDocDriftLegend","scope":"docdrift","key":"ff2ba74637bbbebf","id":"./src/docdrift.h::docdrift::kDocDriftLegend","id_addressable":true,"id_collides_with":0,"l":2739,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"tested":0}], +"files":[{"p":"./src/cli.h","symbols":1,"churn":508,"ccx":0,"hotspot_rank":6}, +{"p":"./src/docdrift.h","symbols":6,"churn":36,"ccx":54,"hotspot_rank":35}, +{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":103}, +{"p":"./src/mcpverbs.h","symbols":2,"churn":428,"ccx":2,"hotspot_rank":5}, +{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":63}, +{"p":"./src/verbs_change.h","symbols":1,"churn":80,"ccx":4,"hotspot_rank":23}], +"symbols_total":3359,"symbols_capped":true},"blast_radius":{"reaches":13,"files_total":7,"capped":false,"files":["./src/docdrift.h","./src/main.cpp","./src/mcp.h","./src/mcpserver.h","./src/mcpverbs.h","./src/recall.h","./src/verbs_for.h"]},"tests_to_run":[], +"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":13,"module_span":6,"notes":[], +"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":12,"files":6,"module_span":6,"max_ccx":38,"sum_ccx":64,"ambiguous_calls":12,"blast_reaches":13,"contract_touches":1,"conflicts":1,"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":124,"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":2947,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":508,"tested":0}, +{"p":"./src/cli.h","n":"kViewFlags","scope":"rw","key":"2a2f2487082cc6d8","id":"./src/cli.h::rw::kViewFlags","id_addressable":true,"id_collides_with":0,"l":3326,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":508,"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":3026,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":508,"tested":0}, +{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":124,"l":865,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":17,"tested":0}, +{"p":"./src/mcprefusal.h","n":"kMcpValueFields","scope":"rw::mcprefuse","key":"4a7106e488a2aa80","id":"./src/mcprefusal.h::rw::mcprefuse::kMcpValueFields","id_addressable":true,"id_collides_with":0,"l":304,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":47,"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":3091,"ord":0,"overloads":1,"amb":1,"cx":4,"ccx":3,"churn":508,"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}, +… [62 more display lines; full output is 18945 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.22s** + +````` + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + +… [226 more display lines; full output is 23768 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.37s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [252 more display lines; full output is 24966 bytes on 1 raw line(s)] +````` + +Tail of the same output — the `` section: + +````` + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --doc-drift --with-history` + +*Same report, with git history splitting stale mentions into deleted-by-commit vs never-existed.* + +**wall time: 1.12s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [206 more display lines; full output is 23747 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:9673 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:9714 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 + #3 0x102e01a44 in main src/main.cpp:4174 + #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) +==41337==ABORTING +````` + +````` + + + + + + + + + + + +PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] + +uniform-teleport PageRank (the defaultinline RankedGraph rankGraph( const Graph& g, float alpha = 0.85f ) +int runDefaultMap( const MainDispatch& d ) +int main( int argc, char** argv ) + + + +& p, float alpha = 0.85f ) +{ + PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); + const std::vector pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + double teleportMass = 0.0; +… [18 more display lines; full output is 4908 bytes on 29 raw line(s)] +````` + +## `./build/ripwire . --notes` + +*List all field notes (write-side memory) — the committed .ripwire_notes at the repo root, each with the sha/branch it was recorded at.* + +````` + + + + + + + + + + + gate scripts' claim (~line 1305) is NOT enforced — the derived-vs-stated sibling loop here covers docs/EVALS.md only. It drifted 407→451 unnoticed (fixed 2026-08-23). To close: grep both files ('file:line:' parsing) in the gateCountClaims arm.]]> + + + + +````` + +## `./build/ripwire . --pack-task="add a new output format flag to the CLI"` + +*ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* + +````` + + + + +Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept + +claude's `mcp add … -- ripwire --mcp`: the flag goes on the end of that command line, ahead of its newlineinline std::string wrapCliAddPost( const AgentTarget& row, std::string_view toolsArg ) + +ONE recipe path for every agent that can shell out, and the point is that the RECOMMENDATION does not vary by agent — only the MCP alternative does. Before this there were four near-identical CLI-fiinline void wrapEmitCliFirst( const AgentTarget& row, const std::string& token, c … [line truncated: 83 more bytes on this line] + +mcp-tools pass-through. Written into the server command only where its argument shape is known to be one more plain argument: the JSON args arrays (cursor/windsurf/gemini), opencode's command arrayinline bool wrapWritesToolsArg( const AgentTarget& row ) noexcept + +L8 (train 29): the NEXT PAGE of a callers listing — the CLI --callers root's next=, find_referencing_symbols' next and find_symbol's calledBy_next all name this one call, so the three cannot spell tinline std::string callHierarchyPageNext( bool wantCallers, std::string_vi … [line truncated: 98 more bytes on this line] + +F7: a hostile/garbled frame line number (e.g. a fuzzed or truncated trace) can exceed UINT32_MAX; unchecked `v*10+d` wraps mod 2^32 (4294967297 -> 1), which then confidently maps to a REAL line in theinline std::uint32_t toUint( std::string_view s, bool& overflowed ) noexcept + + + + + + + + +… [47 more display lines; full output is 9865 bytes on 38 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.* + +````` + 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 +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] +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +handle_t leftmost_leaf() const +inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) +inline std::string doctorWhichRipwire( rw::os::stat_t& st ) +inline std::string doctorPathPrependLine( const std::string& selfPath ) +std::uint32_t testIterationCeiling() noexcept +inline const char* rankByLegendFor( const char* label ) noexcept +… [124 more display lines; full output is 18953 bytes on 10 raw line(s)] +````` + +## `./build/ripwire . --export=cc.json:/aux/ripwire2.cc.json` + +*Per-file metrics as CodeCharta cc.json.* + +**wall time: 1.61s** + +````` +(empty) +````` + +Artifact written: + +````` + 473799 /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: 4.24s** + +Input file: + +````` +for:incremental cache invalidation +callers:rankGraphTeleport +grep:DISCLOSE +lego:Vehicle +````` + +````` + + + + + + + + + +incremental cache (--cache): per-file content hash + raw facts so a re-run re-parses ONLY c…constexpr std::uint32_t kCacheMagic = 0x4b505443 + +Composed exactly the way every OTHER blob family is (quality.h): one fixed-width identity hex pe…inline std::string spanTierMemoPath( const std::string& diskPath ) + +Ingest the tree at `sha`, materialized out of `root`'s object store. The HEAD side reuses the SA…inline bool ingestCommitTree( const std::string& root, const std::string& sha, const std::vector<std::string>& excludes, std::size_t maxFileBytes, IngestResult&… … [line truncated: 4 more bytes on this line] + +P1-15 — the `_reingest` envelope field for a response whose handling ran an INCREMENTAL pass, …inline bool mcpRefreshedThisRequest( std::uint64_t passesAtEntry ) +struct FileHealth +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::ve … [line truncated: 8 more bytes on this line] +inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… +inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, const CacheLoadStats& cac … [line truncated: 66 more bytes on this line] +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline const McpIndex& getIndex( const std::string& root ) +inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) +struct McpIndex +inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) +IngestResult ingest( const char* rootDir, const std::vector<std::string>& excludeSubstr, std::string_view cacheFile, std::size_t maxFileBytes, bool captureValueUses, std::string_view excludeLabel, bool … [line truncated: 37 more bytes on this line] +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) +static bool cachePathIsDirectory( const std::string& cachePath ) +inline std::string oracleCachePath( const std::string& root, const std::string& headSha ) +inline constexpr std::array<std::string_view, 8> kCacheRuleNames = +… [106 more display lines; full output is 50521 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: 13.14s** + +````` + + + + + + + + + + +````` + + +--- + +# 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: 4.18s** + +````` + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~4 --exclude=test --exclude=docs --exclude=skills --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: 3.05s** + +````` + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --rank-by=bogus --top-k=5` + +*An unknown value REFUSES (exit 1), NAMED, with the supported set listed.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf|churn|churn-decay) +````` + +## `./build/ripwire . --callers=rankGraphTeleport --format=columnar` + +*Columnar output: paths table + parallel arrays, ~15-60% fewer tokens on MANY-row lists — small results can be LARGER (the columnar legend is a fixed cost).* + +````` + + +0=src/mcpindex.h 1=src/graph.h 2=src/eval.h 3=src/main.cpp + +0,1,1,2,3,3,3 +getIndex,rankGraph,anchoredLexicalRank,runEval,churnDecayRanking,churnRankedGraph,runDefaultMap +1184,9712,10358,171,1425,1464,1751 +fn,fn,fn,fn,fn,fn,fn +0,0,0,0,0,0,0 +src/mcpindex.h:1288,src/graph.h:9715,src/graph.h:10409,src/eval.h:328,src/main.cpp:1439,src/main.cpp:1478 src/main.cpp:1479 src/main.cpp:1493,src/main.cpp:1871 + + +````` + +## `./build/ripwire . --for="cache invalidation" --format=candidates --top-k=5` + +*Flat top-K export for an external reranker.* + +**wall time: 2.14s** + +````` + + + +inline std::string spanTierMemoPath( const std::string& diskPath ) + + +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vector<NodeId>& surfaceIds ) + + +### See the map — not just the numbers + + +inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) + + +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) + + +````` + +## `./build/ripwire . --callers=rankGraphTeleport --format=bogus` + +*An unknown --format value REFUSES (exit 1), named, with the supported set listed.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidates) +````` + +## `./build/ripwire . --callers=rankGraphTeleport --json` + +*Machine-parseable JSON, same content, keys mirror the XML attrs.* + +````` +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"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:9712","sites_at":["src/graph.h:9715"]}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:10358","sites_at":["src/graph.h:10409"]}, +{"t":"fn","n":"runEval","p":"src/eval.h:171","sites_at":["src/eval.h:328"]}, +{"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1425","sites_at":["src/main.cpp:1439"]}, +{"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1464","sites_at":["src/main.cpp:1478","src/main.cpp:1479","src/main.cpp:1493"]}, +{"t":"fn","n":"runDefaultMap","p":"src/main.cpp:1751","sites_at":["src/main.cpp:1871"]}]} +````` + +## `./build/ripwire . --hotspots --json` + +*JSON refusal shape: an unsupported verb refuses loudly instead of silently falling back to XML.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --json is not yet supported for --hotspots — supported: the default map, --for, --pack-task, --callers/--callees, --impact, --quality-delta, --test-gate, --metrics, and --plan-lanes which is JSON-native (e.g. ripwire

--callers=SYM --json) +````` + +## `./build/ripwire . --hotspots --limit=3 --offset=3` + +*Pagination: 3 items, skipping the first 3 (deterministic seams).* + +**wall time: 2.69s** + +````` + + + + + + + +````` + +## `./build/ripwire . --ignore-tests --top-k=5` + +*Drop test paths from the corpus before ranking.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [58 more display lines; full output is 7949 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --exclude=present --exclude=bench --top-k=5` + +*Drop matching paths (repeatable) before ranking.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [56 more display lines; full output is 7928 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --map-diff --top-k=5` + +*Full map re-ranked with teleport toward git-changed files — clean tree, so changed=0 and it degrades to the plain map.* + +**wall time: 1.22s** + +````` + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --no-cache --top-k=3` + +*Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm cost.* + +**wall time: 2.49s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [55 more display lines; full output is 7825 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: 2.80s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [55 more display lines; full output is 7825 bytes on 1 raw line(s)] +````` + +Artifact written: + +````` + 26836629 /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).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [17 more display lines; full output is 4215 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --scip=does_not_exist.scip --callers=rankGraphTeleport` + +*SCIP overlay with a missing index REFUSES (exit 1) naming the file — never silently serves the name-based map you named a precision index to improve on (it used to degrade in silence).* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather than serving the name-based map you named a precision index to improve on (generate one with scip-clang/scip-python, or drop --scip) +````` + +## `./build/ripwire src test --top-k=5` + +*Multi-root workspace: ONE merged graph over two roots, paths labeled /.* + +**wall time: 2.72s** + +````` + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --eval` + +*Self-eval: co-change recall vs BM25.* + +**wall time: 20.44s** + +````` +ripwire --eval (co-change recovery, averaged over 80 historical commits) + ranker recall@5 recall@10 recall@20 + ripwire 3.8% 5.5% 10.3% + BM25 10.8% 13.6% 17.4% + BM25sub 17.0% 20.4% 29.9% + BM25body 10.2% 18.8% 30.6% + fused 7.4% 14.1% 26.1% + anchored 10.2% 18.8% 30.6% + same-dir 0.0% 6.4% 10.2% + random 0.2% 0.3% 0.6% <- floor (random ranking over F=3090 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: 147.61s** + +````` +ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) + sample: population=7465 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + (smallest fnv1a64(scope::name) over the population, cut on the key so an identity is never split; + path- and order-independent, but a corpus this size is NOT graded exhaustively — say so when citing it) + ingest: lex=rich (persisted subtoken stats; no per-query corpus re-tokenize) + ranker query-mode MRR recall@1 recall@5 recall@10 + subtoken name 0.680 54.6% 84.6% 89.9% + subtoken doc-phrase 0.908 88.8% 92.8% 93.8% + name-exact name 0.896 81.9% 95.8% 97.2% + name-exact doc-phrase 0.014 0.6% 1.9% 2.9% + anchored name 0.684 55.9% 83.7% 88.5% + anchored doc-phrase 0.904 88.0% 92.7% 93.5% + routed name 0.898 82.0% 96.0% 97.4% + routed doc-phrase 0.907 88.8% 92.6% 93.5% + note: routing chose name-exact on 3988/4000 NAME queries (a NAME query is always identifier-shaped); + the confidence gate routes doc-phrase queries to name-exact ONLY when EVERY content word names a symbol + (or an explicit camel/snake token appears) AND every matched name is specific enough to anchor on — + a common name (many definitions, or a subtoken carried by many symbol names) declines the route — so + conceptual prose falls back to subtoken+body; routed tracks the better ranker on BOTH modes + (routed==name-exact on name, ~=subtoken+body on doc-phrase). +````` + +## `./build/ripwire . --eval-stray=/aux/stray_labels2.tsv` + +*Labelled verdict-accuracy eval for --stray-content — three labels over REAL local refs (names resolved at capture time); exit 3 when accuracy is under the floor. Read got= against v= in the stray-content run: a ref the verb could not analyse (unknown) must never be credited as a merged hit.* + +**exit code: 3** — **wall time: 24.08s** + +Input file: + +````` +# refverdict labels for --eval-stray (the first three local branches, resolved at capture time; a missing branch is padded with a nonexistent name on purpose) +arm/for-howitworks-067-f2add merged +arm/for-howitworks-067-placebo unmerged +arm/orient-map-067-narrow merged +````` + +````` + + + + + + +````` + +## `./build/ripwire skills --eval-skills=/aux/skills_labels2.tsv` + +*Labelled skill-ROUTING eval over the repo's own skills/ directory (4 hand-labelled prompts).* + +Input file: + +````` +orient in an unfamiliar codebase fast ripwire-orient judged +who calls this function and what is the blast radius ripwire-navigate judged +plan parallel worktrees so the lanes do not collide ripwire-change-check judged +what is the weather in Paris none neg +````` + +````` +ripwire --eval-skills (skill routing over K=16 candidate skills [ripwire-router excluded]; 3 positive + 1 negative prompts; corpus '/aux/skills_labels2.tsv'; split test=4 dev=0) + arm hit@1 hit@2 mrr sep-auc fire/abstain@ORACLE-th (upper bound) + overlap 66.7% 66.7% 0.689 0.000 50.0% (th=-1.000) + name 33.3% 66.7% 0.537 0.667 50.0% (th=0.000) + bm25-desc 66.7% 66.7% 0.689 0.667 75.0% (th=2.865) + bm25-full 33.3% 66.7% 0.611 1.000 50.0% (th=0.426) + for-routed 33.3% 33.3% 0.556 1.000 50.0% (th=0.663) + random 6.2% 12.5% 0.211 0.500 <- floor (uniform-random ranking; auc 0.5 by definition) + provenance hit@1 (bm25-desc): router 0/0, desc 0/0, judged 2/3 (desc rows quote the descriptions - expect them easiest; judged is the honest number) + judged-only hit@1 per arm: overlap 2/3, name 1/3, bm25-desc 2/3, bm25-full 1/3, for-routed 1/3 + router-magnet: with ripwire-router ADMITTED as a candidate it takes top-1 on 1/3 positive prompts (bm25-desc arm) - why it is excluded above + per-skill (bm25-desc): name / permitted-rows / won / pos-fires / false-fires / neg-fires + ripwire-before-you-build 0 0 1 1 0 + ripwire-change-check 1 0 0 0 0 + ripwire-find-bug 0 0 0 0 0 + ripwire-fresh-eyes 0 0 0 0 1 + ripwire-graph-query 0 0 0 0 0 + ripwire-handoff 0 0 0 0 0 + ripwire-layers 0 0 0 0 0 + ripwire-mcp 0 0 0 0 0 + ripwire-navigate 1 1 1 0 0 + ripwire-opt-remarks 0 0 0 0 0 + ripwire-orient 1 1 1 0 0 + ripwire-perf-target 0 0 0 0 0 + ripwire-quality-bar 0 0 0 0 0 + ripwire-reuse-first 0 0 0 0 0 + ripwire-security-scan 0 0 0 0 0 + ripwire-write-tests 0 0 0 0 0 + misses (overlap): + line 3 want=ripwire-change-check got=ripwire-before-you-build "plan parallel worktrees so the lanes do not collide" +… [19 more lines, 3665 bytes total] +````` + +## `./build/ripwire wrap claude` + +*Print the recipe to wire ripwire into Claude Code as an MCP server.* + +````` +# ripwire -> Claude Code (CLI-first) +# RECOMMENDED — this agent can run shell commands, so call the CLI directly. It costs +# nothing until you invoke it, and it reads CLAUDE.md, so the paste block below IS the wiring: +ripwire . --for="" +# +# ...every follow-up call answers with the compact legend by default (terse definitions of only the +# attributes the answer carries; the payload is byte-identical either way). Add --legend=full when a +# definition's reasoning is needed -- `ripwire --help` carries the measured saving (one place, +# gate-held) -- this line does not repeat it, because two copies of a number is one that goes stale: +# ripwire . --callers=SYM --legend=full +# +# ALTERNATIVE — register the MCP server instead, for a warm index across calls: +claude mcp add ripwire -- ripwire --mcp +# verbs the agent can then call mid-task (33 total): +# read: analyze, rank_by, find_symbol, find_referencing_symbols, grep, cochange, memory_recall, situational_awareness, mentions, for, lego, owners, fetch_body, batch, flags, doc_drift, slice +# flagship reflex: exemplar, quality_delta, quality_baseline, impact, uses, affected, path_between, connect, explore, from_trace, edit_check, whereis, stray_content +# edit: replace_symbol_body, insert_before_symbol, insert_after_symbol +bash skills/install.sh # deploy to ${CLAUDE_CONFIG_DIR:-~/.claude}/skills (drift-gated) +bash skills/install.sh --hook # RECOMMENDED: advisory Read/Grep -> ripwire CLI nudge + session primer (opt-in, never blocks) +# +# context wiring — a binary on PATH is invisible to an agent until its rules file says when +# to reach for it. Paste the block below into CLAUDE.md: +# --- paste into CLAUDE.md --- +## ripwire — deterministic codebase maps (on PATH as `ripwire`) +Reach for it BEFORE blind grep + whole-file reads. First call ~1s cold; after that warm, ~0.1s. +- Orient on a task: `ripwire --for=""` — ranked, quality-annotated + signatures. Paste symbol/file names from the issue verbatim; named mentions get anchored. +- One task: `--pack-task="" --legend=compact`; before parallel agents: `--plan-lanes=N --task=""`, then read `lanes[].execution`. +- Have a stack trace / build error: `ripwire --from-trace=FILE --legend=compact` (`-` = stdin) — + paste the error, don't paraphrase it into a query. +… [14 more lines, 4058 bytes total] +````` + +## `./build/ripwire --version` + +*Version + short build info.* + +````` +ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=24bcbf89f) +````` + + +--- + +# navigate — seeds, claims, slices, shapes + +## `./build/ripwire . --at=src/graph.h:9673` + +*Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost.* + +````` + + + + +````` + +## `./build/ripwire . --callers=@src/graph.h:9673` + +*The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --at=src/graph.h:999999` + +*A seed past the end of the file — the refusal shape for a faulted location.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 13961 lines — the seed asked for line 999999) +````` + +## `./build/ripwire . --verify="calls(runDefaultMap, rankGraphTeleport)"` + +*VERIFY a closed claim in one call: three-valued verdict (confirmed / refuted / not-established) with the evidence rows inline.* + +````` + + + + + +````` + +## `./build/ripwire . --verify="unused(rankGraphTeleport)"` + +*A claim that is FALSE — the refuted shape, with the references that refute it.* + +````` + + + + + + + + + + + + +````` + +## `./build/ripwire . --verify="contains(src/graph.h, \"no such literal anywhere\")"` + +*A literal-scan absence: refuted only with complete= evidence, never on a partial scan.* + +````` + + + +````` + +## `./build/ripwire . --verify="frobnicate(x)"` + +*An unparseable claim — the refusal names the accepted shapes.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. The claim language is CLOSED; the shapes are: calls(A, B) does A transitively call B · uses(SYM) is SYM referenced anywhere · unused(SYM) is SYM referenced nowhere · contains(FILE, "LITERAL") do FILE's bytes contain t … [line truncated: 218 more bytes on this line] +````` + +## `./build/ripwire . --slice=rankGraphTeleport` + +*Bare --slice=SYM: the INVENTORY of sliceable locals (), so a caller can pick VAR.* + +````` + + + + + + + + + + + + + + + + + +````` + +## `./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).* + +````` + + + + + + + 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).* + +````` + + + + + + + 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.* + +````` + + + + + + + teleport( pw.begin(), pw.end() );]]> + + + + + + + + + + + + + + + + + + + + + 0.0 )]]> + + + + + + + + +````` + +## `./build/ripwire . --slice=rankGraphTeleport:nosuchvar` + +*A variable the definition does not bind — the refusal shape, naming the inventory.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --slice: no occurrence of 'nosuchvar' in rankGraphTeleport — sliceable locals: alpha, g, p, pw, N, teleport, rankDouble, run, teleportMass, value, inverseMass, r (bare --slice=rankGraphTeleport lists them with first-def lines) +````` + +## `./build/ripwire . --slice-depth=3` + +*--slice-depth without --slice-flow is refused loudly rather than silently ignored.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire --slice=parseArgs:argIndex --slice-flow=fwd --slice-depth=4) +````` + +## `./build/ripwire . --slice=rankGraphTeleport:teleport --legend=compact` + +*The compact legend posture: rows byte-identical, a versioned schema id replaces the repeated explanatory prose — for a many-small-calls loop.* + +````` + + + + + + + teleport( pw.begin(), pw.end() );]]> + + + + + + + + +````` + +## `./build/ripwire . --pattern='rankGraphTeleport($A, $B, $C)'` + +*Structural search written in CODE: $NAME binds one node; grammars=/shapes= disclose what the pattern became per grammar (a 3-argument call shape — the 2-argument spelling has no call site in this repo and correctly reports hits=0).* + +**wall time: 1.31s** + +````` + +rankGraphTeleport( g, std::vector<float>( N, N ? 1.0f / float( N ) : 0.f ), alpha ) + +````` + +## `./build/ripwire . --pattern='DISCLOSE(...)'` + +*The ellipsis form over a macro-shaped call site; unsupported= names the families this verb does not serve.* + +**wall time: 1.29s** + +````` + +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 17832 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --pattern='x'` + +*A pattern that collapses to a bare token is REFUSED — never reported as hits=0.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --pattern: this pattern collapses to a single bare token, which is a TEXT search, not a structural one — a clean parse is not enough (a bare word parses fine in most grammars and means nothing structural). Give it a shape (foo($X), $A + $B, if ($C) { ... }) or use the grep flag for litera … [line truncated: 176 more bytes on this line] +````` + +## `./build/ripwire . --grep=DISCLOSE --and=cache` + +*Boolean grep: hits where BOTH literals share the matched line (--grep-scope=line is the default).* + +````` + + + +inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) +The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u +TSTree* parseTree( TSParser* parser, std::string_view src ) + +Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() +struct FileHealth +constexpr std::array<LangEntry, 57> kLangTable = +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar…struct SliceScan +enum class AstWalk : std::uint8_t +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +std::vector<LocalNameFact> collectGatedLocalNames( std::string_view defBytes, std::uint32_t defStartLine, Lang lang ) +inline ModuleScan scanModule( std::string_view source, std::string_view path ) +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +constexpr std::uint32_t kMaxYamlNestDepth = 64u +inline bool hasMainGuard( std::string_view source ) +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +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 ) +enum class DisclosureWhy : std::uint8_t +struct LintRule +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline bool hasPytestTable( std::string_view source ) +… [137 more display lines; full output is 19560 bytes on 44 raw line(s)] +````` + +## `./build/ripwire . --for="tree-sitter parse of a source file" --sections=lego,compose` + +*Restore the / sections the --for bundle collapses to a counted stub by default (this is the stub's own next= spelling).* + +**wall time: 1.20s** + +````` + + + + +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 +constexpr std::uint32_t kMaxYamlNestDepth = 64u +inline bool hasMainGuard( std::string_view source ) +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +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 ) +enum class DisclosureWhy : std::uint8_t +struct LintRule +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline bool hasPytestTable( std::string_view source ) +… [148 more display lines; full output is 19893 bytes on 44 raw line(s)] +````` + +## `./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies` + +*Opt OUT of compact conceptual serving: restore the rank-first auto walk (bundle="auto").* + +**wall time: 1.28s** + +````` + + + + +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 +constexpr std::uint32_t kMaxYamlNestDepth = 64u +inline bool hasMainGuard( std::string_view source ) +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +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 ) +enum class DisclosureWhy : std::uint8_t +struct LintRule +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline bool hasPytestTable( std::string_view source ) +… [133 more display lines; full output is 20249 bytes on 55 raw line(s)] +````` + +## `./build/ripwire . --for="quality delta acks ledger rubber stamp"` + +*Doc-mention surfacing (default ON): a markdown doc naming a top-resolved symbol in a backtick rides in below that symbol — the legend's [doc mentions: …] clause says it fired.* + +**wall time: 1.28s** + +````` + + + + + +L2 stale acks in the range form. A changed-files answer holds no whole target tree to check the …std::vector<rw::quality::StaleAck> refPairStaleAcks( const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const RefPairDelta& refs ) +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +inline const char* kAcksFile = ".ripwire_quality_acks" +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +inline 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& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes … [line truncated: 5 more bytes on this line] +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] +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +inline std::string qualityAcksPath( const std::string& root ) +struct QualityDeltaOutcome +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string gitHeadSha( const std::string& root ) +inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +… [192 more display lines; full output is 22804 bytes on 45 raw line(s)] +````` + +## `./build/ripwire . --for="quality delta acks ledger rubber stamp" --no-doc-mention` + +*The same task with doc-mention surfacing OFF — the contrast the flag exists for (no [doc mentions] clause, one fewer row).* + +**wall time: 1.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 const char* kAcksFile = ".ripwire_quality_acks" +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& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes … [line truncated: 5 more bytes on this line] +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] +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +inline std::string qualityAcksPath( const std::string& root ) +struct QualityDeltaOutcome +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string gitHeadSha( const std::string& root ) +inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +… [192 more display lines; full output is 22656 bytes on 45 raw line(s)] +````` + +## `./build/ripwire . --safe-delete=rankGraphTeleport` + +*"Can I delete this?" — callers + transitive impact + every use site + how much of the radius is tested, composed in ONE call; risk= names what was found, never a verdict.* + +````` + + + + + + + + + +````` + +## `./build/ripwire . --safe-delete=DoesNotExist` + +*Unknown-symbol refusal shape for --safe-delete.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --safe-delete symbol not found: DoesNotExist +````` + +## `./build/ripwire . --handoff` + +*The continuation packet for the NEXT session: disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a CLEAN tree.* + +**wall time: 2.21s** + +````` + + + + + + + + + + + + + +````` + +## `./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: 2.10s** + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --skipped` + +*WHY a file is not in the index (oversize / excluded / unsupported-ext / gitignored) and which indexed files it cannot vouch for (degraded-parse, minified-suspect), plus the per-language census.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [432 more display lines; full output is 46000 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).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [55 more display lines; full output is 7825 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [55 more display lines; full output is 7825 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: 1.18s** + +````` + + + + + + const&, float) src/graph.h:9673 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:9714 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 + #3 0x102e01a44 in main src/main.cpp:4174 + #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc)]]> pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + double teleportMass = 0.0; + for( const double value : teleport ) + { + teleportMass += value; + } + if( teleportMass > 0.0 ) + { + const double inverseMass = 1.0 / teleportMass; + for( double& value : teleport ) + { + value *= inverseMass; + } + } + run = pageRankDouble( g.inEdges, g.wOutDeg, teleport, rankDouble, PageRankConfig{ .alpha = double( alpha ) } ); + } + std::vector r( N, 0.f ); + std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); + return { std::move( r ), run.iterationCount, run.hasConverged }; +}]]>inline std::vector<float> biasPrior( const Graph& g, const std::vector<float>& p )#define PROFILE_SCOPE_DESCRIBE( desc )#define PROFILE_SCOPE_DESCR … [line truncated: 288 more bytes on this line] +````` + +## `./build/ripwire . --run-trace="true"` + +*A command that exits 0: a minimal success record (exit, measured duration, disclosed output tail) and NO bundle — nothing failed, nothing to map.* + +````` + + + + +````` + +## `./build/ripwire . --run-trace="sleep 30" --run-timeout=2` + +*A command still running at the cap: its process group is killed and the run reports timed_out=1 — an honest timeout, never an empty success.* + +**exit code: 4** — **wall time: 2.69s** + +````` + + + + +````` + +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: 8.39s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [188 more display lines; full output is 15723 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --biggest-first --limit=8` + +*Per-function size ranking, largest Halstead volume/token entropy/lines first — a size proxy, not a readability order (withdrawn, docs/EVALS.md §8) — a RANKING lens, not a grade.* + +**wall time: 1.73s** + +````` + + + + + + + + + + + +````` + +## `./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: 1.30s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --context-ratio --limit=8` + +*The local-reasoning lens: to understand this symbol, how much must you know that is NOT in front of you (ent_ratio= edge share, read_ratio= token-weighted).* + +**wall time: 1.15s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [21 more display lines; full output is 11234 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --nonlocal-state --limit=8` + +*Per function, the non-local MUTABLE state it can reach (transitively), most writes first — unsound by construction, and the legend says where.* + +**wall time: 2.97s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [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: 4.45s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [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: 1.61s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [507 more display lines; full output is 45435 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --field-affinity=Symbol` + +*The same lens narrowed to ONE struct — the one --layout=Symbol shows the offsets for.* + +**wall time: 1.49s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --naming-consistency --limit=8` + +*The corpus's OWN case-convention vote per (language, kind) group; off-convention names get a mechanical propose= (a suggestion, never a blind rename).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 4358 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: 11.71s** + +````` + + + + + + + + + + + +

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

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

+

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

+

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

+

+ +````` + +## `./build/ripwire . --cochange --cochange-groups` + +*Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none of which it depends on" — a greedy cover, disclosed as greedy.* + +**wall time: 4.92s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [825 more display lines; full output is 54831 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --cochange --cochange-recur=2` + +*Only pairs whose co-change RECURS in 2+ sub-windows of the mined window (sub_windows= is the denominator) — a one-off sprint stops reading like a structural defect.* + +**wall time: 4.67s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 4.96s** + +````` +(empty) +````` + +Artifact written: + +````` + 156624 /aux/map2.html +````` + +## `./build/ripwire . --index-out=/aux/ci_index` + +*CI generate-and-exit: cold-parse and write BOTH committable cache families (lean + rich), no map on stdout.* + +**wall time: 6.02s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (26836629 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (55414420 bytes, rich family) +````` + +Artifact written: + +````` + 26836629 /aux/ci_index.lean.ripwirecache + 55414420 /aux/ci_index.rich.ripwirecache + 82251049 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: 1.62s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [55 more display lines; full output is 7825 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3` + +*Eval-only: a per-call-site census of WHICH mechanism resolved each call, and the canonical id of every surviving target.* + +**wall time: 1.75s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [55 more display lines; full output is 7825 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. + 123164 /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: 3.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: 2.81s** + +````` + + + + + + + +````` + +## `./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: 21.43s** + +````` + + + + + + + + + + + + + +````` + +## `./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: 8.72s** + +````` +ripwire situational-awareness — 1 changed file(s), 13 symbols in them +root: . +at: 24bcbf89f+dirty + [1] blast radius: 235 symbols across 55 files transitively depend on these changes (showing 8 of 55 files — shown=8 total=55 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=55) + counts_floor=1 graph_ambiguous=2904 graph_unresolved=14037 graph_unindexed=266 (files no grammar in this build could read at all) (the map header's own gauges) — every count above is a FLOOR, never a total: call edges are name-based, so dynamic dispatch, callbacks and macros can be missing … [line truncated: 45 more bytes on this line] + src/mcpverbs.h (33 dependent symbols) + src/quality.h (15 dependent symbols) + src/verbs_quality.h (14 dependent symbols) + src/verbs_for.h (12 dependent symbols) + src/editcheck.h (9 dependent symbols) + src/resolve.h (9 dependent symbols) + src/verbs_navigate.h (9 dependent symbols) + src/graph.h (8 dependent symbols) + [2] tests to run (5) order=evidence: [changed] you edited it, [partner] named after a changed file, then hops asc (1 = direct); "(n): a, b" = n runner-less files sharing that evidence; a (run: …) is relative to root: + test/verify_radix.cpp [hops=1] (run: bash test/greptiercheck.sh) + test/adaptivecutshapefix/adaptive_cut_shape_test.cpp [hops=2] (run: bash test/adaptivecutshapecheck.sh) + test/includeprecise_unit.cpp [hops=2] (run: bash test/includeprecisecheck.sh) + test/verify_csr.cpp [hops=2] (run: bash test/a9disclosurecheck.sh) + test/rustimport_unit.cpp [hops=4] (run: bash test/rustimportprecisecheck.sh) + script_gates_unmodelled=767 — 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="4924": + (none — 4924 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: 6.16s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 37.47s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [144 more display lines; full output is 17786 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: 20.73s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [144 more display lines; full output is 22778 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: 21.24s** + +````` +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":162,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"24bcbf89f+dirty","renames":60,"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"}, +… [141 more display lines; full output is 16369 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: 9.35s** + +````` +(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, 215 re-derived, 19 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 accepted, but ./.ripwire_quality_acks was not in canonical form (ack provenance backfill / legacy rows) and has been re-serialised +````` + +## `./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: 8.37s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: ack provenance backfill — 0 clone row(s) reconstructed from the current tree, 215 re-derived, 19 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: 12.78s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [143 more display lines; full output is 17489 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: 1.56s** + +````` + + + + + + +````` + +## `./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: 1.83s** + +````` + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*The review-evidence bundle with an actual changed file.* + +**wall time: 3.90s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [80 more display lines; full output is 8067 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --map-diff --top-k=5` + +*The map re-ranked with a teleport toward the changed file (changed=1 here, not 0).* + +**wall time: 2.49s** + +````` + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --clones` + +*The duplicated helper the sandbox edit introduced shows up as a clone group.* + +**wall time: 2.28s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [307 more display lines; full output is 16168 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: 1.85s** + +````` + + + + + +````` + +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: 2.96s** + +````` + + + + +````` + +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: 4.52s** + +````` + + +

+

+ +````` + +## `./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: 11.76s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [146 more display lines; full output is 17756 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: 7.84s** + +````` +(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: 3.57s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --note-add="lessByScoreDescId: keep this branch-free — it sits inside the PageRank sort comparator"` + +*Pin a field note (write-side memory) to a symbol; committed to .ripwire_notes in the sandbox. The BARE name is resolved through the same resolver the read verbs use and stored as the canonical id — the rewrite is echoed on stderr, because a silent one is not a disclosure.* + +**wall time: 1.12s** + +````` +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 keep this branch-free — it sits inside the PageRank sort comparator 24bcbf89f5ebb11dd5e2e726c23e5e6064d3821e release/0.6.8-prep +````` + +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.)* + +````` + + + + + + + + + + + + + + + + gate scripts' claim (~line 1305) is NOT enforced — the derived-vs-stated sibling loop here covers docs/EVALS.md only. It drifted 407→451 unnoticed (fixed 2026-08-23). To close: grep both files ('file:line:' parsing) in the gateCountClaims arm.]]> + + + + +````` + +## `./build/ripwire . --note-add="gitOneLine: which one?"` + +*Two definitions carry this name, so the write REFUSES rather than pick one: a note keys ONE canonical id, and an ambiguous selector is refused, never silently narrowed. Every candidate is named, with a runnable retry.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --note-add: tip: notes that say "chose X over Y because Z" surface better — consider adding the why +ripwire: --note-add: target 'gitOneLine' is ambiguous — it matches 2 definitions in 2 distinct contracts, and a note keys ONE canonical id (it would surface on one of them and look absent on the rest). Qualify one: ./src/handoff.h:gitOneLine, ./src/quality.h:gitOneLine — e.g. --note-add="./src/h … [line truncated: 33 more bytes on this line] +````` + +## `./build/ripwire . --note-add="lessByScoreDescIdd: typo"` + +*A name that resolves to nothing is refused with the read verbs' own did-you-mean — never written as a dead note.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --note-add: tip: notes that say "chose X over Y because Z" surface better — consider adding the why +ripwire: --note-add: target not found: lessByScoreDescIdd (did you mean 'lessByScoreDescId'?) — a note keys the canonical id a read verb resolves; to note a FILE instead, pass a path (one with a '/' or an extension), which may name a file that does not exist yet +````` + +## `./build/ripwire . --note-add="src/infra/sortutil.h::rw::sortutil::lessByScoreDescId: chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort"` + +*The same symbol addressed by its CANONICAL id: already canonical, so nothing is rewritten and both notes land on ONE target.* + +````` +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 24bcbf89f5ebb11dd5e2e726c23e5e6064d3821e release/0.6.8-prep +````` + +## `./build/ripwire . --expand=lessByScoreDescId --top-k=0` + +*Both notes riding along with the symbol's body — the elements follow the body, past the display cut, so they are extracted below.* + +````` + + + + + + + + + + + + + +````` + +## `./build/ripwire . --insert-after-symbol=lessByScoreDescId --edit-payload=/aux/payload_note.h` + +*Insert immediately AFTER one uniquely-resolved definition; replaced_bytes=0 because the insert verbs never overwrite. The receipt carries the folded post-edit verification (lines=, edit_check, tests_to_run) so the loop closes in one call.* + +**wall time: 3.69s** + +````` +{"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":2904,"graph_unresolved":14037,"graph_unindexed":266,"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":767,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +````` + +stderr: + +````` +ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, tests_to_run; next: bash test/greptiercheck.sh +````` + +## `./build/ripwire . --insert-after-symbol=lessByScoreDescId --edit-payload=/aux/payload_note.h --no-post-check` + +*The opt-out: the same insert with the folded verification skipped — lines= still rides (it is free), edit_check/tests_to_run do not, and the two pasteable commands stay on stderr.* + +**wall time: 1.66s** + +````` +{"applied":"insert_after_symbol","symbol":"lessByScoreDescId","file":"src/infra/sortutil.h","span":{"start":2375,"end":2463},"lines":{"start":56,"end":58},"replaced_bytes":0,"old_file_bytes":10912,"new_file_bytes":11000,"file_eol":"lf","eol_normalized":false,"trailing_newline_folded":true,"separator … [line truncated: 572 more bytes on this line] +````` + +stderr: + +````` +ripwire edit: applied atomically; receipt carries region, blob_sha (post-check skipped); next: --edit-check=src/infra/sortutil.h:lessByScoreDescId +````` + +## `./build/ripwire . --insert-before-symbol=nonNegativeFloatDescKey --edit-payload=/aux/payload_note.h --edit-target-file=src/infra/sortutil.h` + +*Insert BEFORE, with --edit-target-file pinning which same-named definition (here unambiguous — the disambiguator is simply honoured).* + +**wall time: 4.00s** + +````` +{"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":2904,"graph_unresolved":14037,"graph_unindexed":266,"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":767,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"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: 1.69s** + +````` +(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: 1.64s** + +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":2904,"graph_unresolved":14037,"graph_unindexed":266,"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: 3.67s** + +````` +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"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":2904,"graph_unresolved":14037,"graph_unindexed":266,"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: 11.87s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [140 more display lines; full output is 16853 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: 7.26s** + +````` +(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: 10.60s** + +````` +(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 28583 per-symbol rows, 29479 indexed symbols total) +````` + +Artifact written: + +````` + 4846583 .ripwire_quality_baseline +# ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit +producer 69b60a9a324ba73ba0983d32f9ffa6a6994d8d5008b316df6779a722847d0c2e +head 24bcbf89f5ebb11dd5e2e726c23e5e6064d3821e +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: 11.74s** + +````` + + +````` + + +--- + +# 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: 867 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= 47450 (~tokens at 4 bytes/token: 11862 ) +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= 833 schema_bytes= 193 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: 867 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= 14191 (~tokens at 4 bytes/token: 3547 ) +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= 833 schema_bytes= 193 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: 1.70s** + +````` +{"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: 6519 more bytes on this line] +"_index":"[index: files=3090 symbols=29477 hash=46268bd9]","_fresh":"ok"}} +{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":" - - + + @@ -106,7 +106,7 @@ _The same capped map without its inventory tier: --top-k=5 leaves files out, so $ ./build/ripwire . --top-k=5 --no-inventory - + @@ -140,7 +140,7 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ $ ./build/ripwire . --max-tokens=1500 - + @@ -170,7 +170,7 @@ _GATE form: exit 3 if the map's own est_tokens exceeds the budget (over-budget f ``` $ ./build/ripwire . --token-budget=100 - + ``` **Shaped by:** `--top-k`, `--max-tokens`, `--for`, `--sections`, `--recall`, `--handoff`, `--pr-context`, `--from-trace` @@ -215,11 +215,11 @@ _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` @@ -252,7 +252,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 - + @@ -279,11 +279,11 @@ _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` @@ -314,7 +314,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 - + @@ -346,16 +346,16 @@ _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] ``` @@ -378,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 - + @@ -409,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 - @@ -439,15 +439,15 @@ _The same task with doc-mention surfacing OFF — the contrast the flag exists f ``` $ ./build/ripwire . --for="quality delta acks ledger rubber stamp" --no-doc-mention - + - + L2 stale acks in the range form. A changed-files answer holds no whole target tree to check the …std::vector<rw::quality::StaleAck> refPairStaleAcks( const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const RefPairDelta& refs ) -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) -inline const char* kAcksFile = ".ripwire_quality_acks" -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +inline const char* kAcksFile = ".ripwire_quality_acks" +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] ``` @@ -469,7 +469,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -500,7 +500,7 @@ _The repo's best-in-class instance to imitate before writing new code (picked by $ ./build/ripwire . --exemplar="format byte sizes for humans" - + f, A&&... a ) @@ -529,9 +529,9 @@ _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" — 114 relevant of 221 document files, best-first — total=114 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2157 est_tokens=4593 +ripwire recall — "quality delta gating exit codes" — 114 relevant of 230 document files, best-first — total=114 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2157 est_tokens=4593 -━━ README.md (relevance 8.061) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 222662 B; lines="2747-2759,2760-2767"; dropped_by_budget=23] +━━ README.md (relevance 8.053) ━━ [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. @@ -594,20 +594,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 1377030-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 1362493-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.114) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 588930 B; lines="3838-3842"; dropped_by_budget=163] [truncated: 1865 of 7739 bytes] -### `--field-affinity[=STRUCT]` +━━ commands.md (relevance 11.005) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 588880 B; lines="556-581"; dropped_by_budget=163] [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` @@ -623,7 +628,7 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - + @@ -727,8 +732,8 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable $ ./build/ripwire . --no-stable --top-k=3 - - + + @@ -761,8 +766,8 @@ $ ./build/ripwire . --around=rankGraphTeleport - - + + @@ -813,18 +818,18 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - - + + - - - - - - + + + + + + ``` @@ -848,10 +853,10 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - + - - + + @@ -882,16 +887,16 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - + - + - + - + @@ -919,17 +924,17 @@ _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] @@ -954,12 +959,12 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - + - + ``` @@ -976,10 +981,10 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + - + @@ -1086,7 +1091,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - @@ -1143,7 +1148,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: 24bcbf89f +at: 9b86a581e (0 changed files — working tree is clean, nothing to analyze) ``` @@ -1162,18 +1167,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] ``` @@ -1197,7 +1202,7 @@ _Pre-PR gate on a CLEAN tree: no obligations, exit 0._ ``` $ ./build/ripwire . --test-gate - ``` @@ -1222,7 +1227,7 @@ _Literal trigram-indexed search. Each hit carries its MATCHED line as the $ ./build/ripwire . --grep=DISCLOSE - + - + - + @@ -1524,7 +1529,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1559,8 +1564,8 @@ $ ./build/ripwire . --pack-signatures --top-k=10 - - + + @@ -1590,7 +1595,7 @@ $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 signature rows. est_tokens=: price as emitted (an upper bound under compact). root=: p= relative to it. --> - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1617,11 +1622,11 @@ _NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map ride ``` $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport - + - + + - - + + @@ -1793,9 +1798,9 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - + + + @@ -1830,17 +1835,17 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - - - - - - + + + + + + - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1858,17 +1863,17 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + - + - + ... [17 more line(s); run it to see the whole thing] @@ -1889,13 +1894,13 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - - + + - + - + @@ -1922,7 +1927,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1957,18 +1962,18 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1988,17 +1993,17 @@ _THE single wide-angle quality read: six families in one pass, an eligible/ranke ``` $ ./build/ripwire . --quality-panel - + - + - - + + - + ... [17 more line(s); run it to see the whole thing] @@ -2025,13 +2030,13 @@ _The local-reasoning lens: to understand this symbol, how much must you know tha ``` $ ./build/ripwire . --context-ratio --limit=8 - - - - - - + + + + + + ... [23 more line(s); run it to see the whole thing] ``` @@ -2056,7 +2061,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2091,10 +2096,10 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - - + + + + @@ -2128,7 +2133,7 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - + @@ -2137,10 +2142,10 @@ $ ./build/ripwire . --lint --naming-locals - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2163,7 +2168,7 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - + @@ -2196,16 +2201,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - - - - + - - + + + + - + + ... [22 more line(s); run it to see the whole thing] ``` @@ -2228,16 +2233,16 @@ _Only pairs whose co-change RECURS in 2+ sub-windows of the mined window (sub_wi ``` $ ./build/ripwire . --cochange --cochange-recur=2 - + - - - + + + - + ... [22 more line(s); run it to see the whole thing] ``` @@ -2254,7 +2259,7 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + @@ -2283,18 +2288,18 @@ _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] ``` @@ -2321,7 +2326,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2351,7 +2356,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2377,7 +2382,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2404,7 +2409,7 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - + @@ -2413,10 +2418,10 @@ $ ./build/ripwire . --lint - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2513,7 +2518,7 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - @@ -2522,10 +2527,10 @@ $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2627,15 +2632,15 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + - + - + @@ -2658,7 +2663,7 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - ``` @@ -2683,19 +2688,19 @@ _Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels ``` $ ./build/ripwire . --zoom - - + + - + - - + + - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2720,16 +2725,16 @@ $ ./build/ripwire . --report # ripwire architecture report -3090 files · 29477 symbols · 28529 edges · 3474 modules (13227 call-graph isolated) +3102 files · 29592 symbols · 28717 edges · 3499 modules (13251 call-graph isolated) Root: `.` -Call-graph isolate provenance: 3604 declaration, 2112 header, 2755 source, 4756 document; 0 connected Louvain singletons +Call-graph isolate provenance: 3612 declaration, 2119 header, 2754 source, 4766 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3474) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1488 symbols -- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 496 symbols -- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 335 symbols +## Modules (call-graph clusters; showing 12 of 3499) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1517 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 497 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 333 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2746,10 +2751,10 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - - + + - + @@ -2777,17 +2782,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"] - n101["src
8841"] - n102["src/infra
1294"] + n101["src
8906"] + n102["src/infra
1296"] end subgraph sg1 ["test"] - n103["test
5405"] + n103["test
5412"] n483["test/legochargefix
546"] n394["test/fixtures/recallpassage
314"] - n564["test/ownerhopfix/py/pkg
158"] + n568["test/ownerhopfix/py/pkg
158"] n338["test/expandmodefix
151"] n340["test/expandsibsfix
149"] - n705["test/rubyschemafix
99"] + n709["test/rubyschemafix
99"] ... [17 more line(s); run it to see the whole thing] ``` @@ -2806,17 +2811,17 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - + + - + - + ... [17 more line(s); run it to see the whole thing] @@ -2837,7 +2842,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2902,7 +2907,7 @@ _The same clean verdict with what it folded printed back: every stale acknowledg ``` $ ./build/ripwire . --quality-delta --show-stale - @@ -2931,7 +2936,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -2956,7 +2961,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -2981,7 +2986,7 @@ _A ref range run for two of the twelve kinds only: the named kinds' rows are exa ``` $ ./build/ripwire . --quality-delta=HEAD~1..HEAD --quality-kinds=complexity,dead-code -

-

-

+

+

-… [146 more display lines; full output is 15445 bytes on 1 raw line(s)] +… [146 more display lines; full output is 15431 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.19s** +**wall time: 2.21s** ````` - + @@ -5837,27 +5758,27 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - + - + - + - -… [641 more display lines; full output is 72715 bytes on 1 raw line(s)] + +… [640 more display lines; full output is 72652 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint-catalog` @@ -5903,7 +5824,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=f1e98faa0aeaa1ee entries=902 +ripwire legend dictionary ripwire.dict/v1 dictv=30173b0aa2d06473 entries=908 : 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=) @@ -5933,14 +5854,14 @@ 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.path/v1 : one DIRECTED call path from= to to=, each a hop; reachable=0 hops=0 when none ripwire.connect/v1 : minimal joining subgraph: groups, terminals, joins, edges, -… [873 more display lines; full output is 93387 bytes on 903 raw line(s)] +… [879 more display lines; full output is 93890 bytes on 909 raw line(s)] ````` ## `./build/ripwire . --lint --lint-select=cache-` *Run ONLY one rule family; the root carries selected="K of N" so a filtered zero is never confusable with an unfiltered one.* -**wall time: 2.57s** +**wall time: 1.80s** ````` @@ -5973,18 +5894,18 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal g_pool[ g_readOf[i] ] g_pool[ g_readOf[i] ] m[ g_pool[i] ] -… [608 more display lines; full output is 73382 bytes on 1 raw line(s)] +… [608 more display lines; full output is 73393 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.40s** +**wall time: 1.77s** ````` - @@ -5993,17 +5914,17 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal - + - + - + @@ -6013,14 +5934,14 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal 20 delta < 0.02 && delta > -0.02 0.02 -… [725 more display lines; full output is 66260 bytes on 1 raw line(s)] +… [725 more display lines; full output is 66267 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint --lint-select=goto --lint-max-per-rule=4` *A rule that spends its own match budget (here 4, set BELOW the default 5000 so the floor shows on this repo): goto carries count_capped="1", its count= is a FLOOR, and the root names the call that counts the rest — findings_next= re-runs only the floored rules under a 10x budget.* -**wall time: 2.39s** +**wall time: 1.70s** ````` @@ -6037,7 +5958,7 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal *Pasting that findings_next=: the same rule under a 10x budget counts its true total — no count_capped=, no findings_next=.* -**wall time: 2.44s** +**wall time: 1.81s** ````` @@ -6068,7 +5989,7 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal *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.51s** +**exit code: 1** — **wall time: 1.76s** ````` (empty) @@ -6084,7 +6005,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.29s** +**exit code: 1** — **wall time: 1.74s** ````` (empty) @@ -6100,7 +6021,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.63s** +**wall time: 1.77s** ````` {"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] @@ -6133,13 +6054,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}}, -… [12747 more display lines; full output is 1802685 bytes on 1 raw line(s)] +… [12761 more display lines; full output is 1804753 bytes on 1 raw line(s)] ````` Parsed summary of the same SARIF (past the display cut): ````` -sarif 2.1.0 rules= 39 results= 6369 +sarif 2.1.0 rules= 39 results= 6376 ````` ## `./build/ripwire . --lint --sarif --limit=5` @@ -6162,11 +6083,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: 7.50s** +**wall time: 3.69s** ````` -

@@ -6177,11 +6098,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: 14.24s** +**wall time: 7.95s** ````` -

@@ -6192,14 +6113,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: 13.84s** +**wall time: 7.84s** ````` - -

-

+ +

+

````` @@ -6207,11 +6128,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: 4.92s** +**wall time: 1.74s** ````` - + @@ -6229,10 +6150,10 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M - + - + @@ -6240,27 +6161,27 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M -… [825 more display lines; full output is 54831 bytes on 1 raw line(s)] +… [829 more display lines; full output is 55101 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --cochange --cochange-recur=2` *Only pairs whose co-change RECURS in 2+ sub-windows of the mined window (sub_windows= is the denominator) — a one-off sprint stops reading like a structural defect.* -**wall time: 4.67s** +**wall time: 1.69s** ````` - + - - - + + + - + @@ -6269,18 +6190,18 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M - + - + - - - - + + + + ````` @@ -6289,7 +6210,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.96s** +**wall time: 1.67s** ````` (empty) @@ -6298,14 +6219,14 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M Artifact written: ````` - 156624 /aux/map2.html + 156583 /aux/map2.html ````` ## `./build/ripwire . --index-out=/aux/ci_index` *CI generate-and-exit: cold-parse and write BOTH committable cache families (lean + rich), no map on stdout.* -**wall time: 6.02s** +**wall time: 3.70s** ````` (empty) @@ -6314,29 +6235,27 @@ Artifact written: stderr: ````` -ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (26836629 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (55414420 bytes, rich family) +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (26929702 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (55610059 bytes, rich family) ````` Artifact written: ````` - 26836629 /aux/ci_index.lean.ripwirecache - 55414420 /aux/ci_index.rich.ripwirecache - 82251049 total + 26929702 /aux/ci_index.lean.ripwirecache + 55610059 /aux/ci_index.rich.ripwirecache + 82539761 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: 1.62s** - ````` - - + + @@ -6350,7 +6269,7 @@ Artifact written: - + @@ -6363,20 +6282,18 @@ Artifact written: -… [55 more display lines; full output is 7825 bytes on 1 raw line(s)] +… [55 more display lines; full output is 7834 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3` *Eval-only: a per-call-site census of WHICH mechanism resolved each call, and the canonical id of every surviving target.* -**wall time: 1.75s** - ````` - - + + @@ -6390,7 +6307,7 @@ Artifact written: - + @@ -6403,7 +6320,7 @@ Artifact written: -… [55 more display lines; full output is 7825 bytes on 1 raw line(s)] +… [55 more display lines; full output is 7834 bytes on 1 raw line(s)] ````` Artifact written: @@ -6417,18 +6334,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. - 123164 /aux/pin_census.tsv + 123569 /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: 3.08s** +**exit code: 2** ````` - + @@ -6446,11 +6363,11 @@ Artifact written: *The ledger-shaped fixture through the same check (exit 2: one gating card).* -**exit code: 2** — **wall time: 2.81s** +**exit code: 2** ````` - + @@ -6462,18 +6379,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: 21.43s** +**exit code: 1** — **wall time: 5.16s** ````` - - + - - + + - - + @@ -6509,14 +6426,14 @@ 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: 8.72s** +**wall time: 4.95s** ````` ripwire situational-awareness — 1 changed file(s), 13 symbols in them root: . -at: 24bcbf89f+dirty +at: 9b86a581e+dirty [1] blast radius: 235 symbols across 55 files transitively depend on these changes (showing 8 of 55 files — shown=8 total=55 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=55) - counts_floor=1 graph_ambiguous=2904 graph_unresolved=14037 graph_unindexed=266 (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=2911 graph_unresolved=14058 graph_unindexed=266 (files no grammar in this build could read at all) (the map header's own gauges) — every count above is a FLOOR, never a total: call edges are name-based, so dynamic dispatch, callbacks and macros can be missing … [line truncated: 45 more bytes on this line] src/mcpverbs.h (33 dependent symbols) src/quality.h (15 dependent symbols) src/verbs_quality.h (14 dependent symbols) @@ -6532,8 +6449,8 @@ at: 24bcbf89f+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=767 — 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="4924": - (none — 4924 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="4957": + (none — 4957 commits were mined and none co-edited a file outside your diff) next: --test-gate ````` @@ -6541,11 +6458,11 @@ at: 24bcbf89f+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: 6.16s** +**exit code: 4** — **wall time: 4.09s** ````` - @@ -6556,25 +6473,25 @@ at: 24bcbf89f+dirty - - + + - + - + - - - + + + ````` @@ -6583,11 +6500,11 @@ at: 24bcbf89f+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: 37.47s** +**exit code: 2** — **wall time: 22.47s** ````` - @@ -6616,7 +6533,7 @@ at: 24bcbf89f+dirty -… [144 more display lines; full output is 17786 bytes on 1 raw line(s)] +… [146 more display lines; full output is 17933 bytes on 1 raw line(s)] ````` stderr: @@ -6630,11 +6547,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: 20.73s** +**exit code: 2** — **wall time: 11.09s** ````` - @@ -6663,7 +6580,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap -… [144 more display lines; full output is 22778 bytes on 1 raw line(s)] +… [146 more display lines; full output is 22925 bytes on 1 raw line(s)] ````` stderr: @@ -6677,10 +6594,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: 21.24s** +**exit code: 2** — **wall time: 10.97s** ````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":162,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"24bcbf89f+dirty","renames":60,"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":164,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"9b86a581e+dirty","renames":60,"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"}, @@ -6710,7 +6627,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"}, -… [141 more display lines; full output is 16369 bytes on 1 raw line(s)] +… [143 more display lines; full output is 16522 bytes on 1 raw line(s)] ````` stderr: @@ -6724,7 +6641,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: 9.35s** +**exit code: 1** — **wall time: 6.92s** ````` (empty) @@ -6734,7 +6651,7 @@ 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, 215 re-derived, 19 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 provenance backfill — 0 clone row(s) reconstructed from the current tree, 214 re-derived, 20 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 56 left legacy (member set not found cloning here — a floor, not proof it is gone), 1311 ineligible (no cu … [line truncated: 200 more bytes on this line] ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing accepted, but ./.ripwire_quality_acks was not in canonical form (ack provenance backfill / legacy rows) and has been re-serialised ````` @@ -6742,7 +6659,7 @@ ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing accepte *NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* -**wall time: 8.37s** +**wall time: 6.96s** ````` (empty) @@ -6752,7 +6669,7 @@ 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, 215 re-derived, 19 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 provenance backfill — 0 clone row(s) reconstructed from the current tree, 214 re-derived, 20 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 56 left legacy (member set not found cloning here — a floor, not proof it is gone), 1311 ineligible (no cu … [line truncated: 200 more bytes on this line] ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already acked) → ./.ripwire_quality_acks ````` @@ -6760,11 +6677,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: 12.78s** +**exit code: 2** — **wall time: 10.92s** ````` - @@ -6793,7 +6710,7 @@ ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already -… [143 more display lines; full output is 17489 bytes on 1 raw line(s)] +… [145 more display lines; full output is 17636 bytes on 1 raw line(s)] ````` stderr: @@ -6823,11 +6740,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6839,11 +6756,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row identical.* -**wall time: 1.83s** +**wall time: 1.57s** ````` - @@ -6855,11 +6772,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6895,13 +6812,13 @@ 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. --> - - + + @@ -6926,21 +6843,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. --> - + - + - + @@ -6951,27 +6868,27 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + - + -… [307 more display lines; full output is 16168 bytes on 1 raw line(s)] +… [308 more display lines; full output is 16216 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: 1.85s** +**wall time: 1.79s** ````` - - + + ````` @@ -6986,11 +6903,11 @@ stderr: *CHANGED: --plan surfaces those same refs as an row rather than silently dropping them.* -**wall time: 2.96s** +**wall time: 2.70s** ````` - + ````` @@ -7005,11 +6922,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: 4.52s** +**wall time: 4.21s** ````` -

@@ -7020,11 +6937,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: 11.76s** +**wall time: 11.07s** ````` - @@ -7053,7 +6970,7 @@ stderr: -… [146 more display lines; full output is 17756 bytes on 1 raw line(s)] +… [148 more display lines; full output is 17903 bytes on 1 raw line(s)] ````` stderr: @@ -7066,7 +6983,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: 7.84s** +**exit code: 1** — **wall time: 6.94s** ````` (empty) @@ -7086,11 +7003,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: 3.57s** +**wall time: 3.31s** ````` - + @@ -7115,11 +7032,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr - - - - - + + + + + ````` @@ -7128,10 +7045,8 @@ 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: 1.12s** - ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 keep this branch-free — it sits inside the PageRank sort comparator 24bcbf89f5ebb11dd5e2e726c23e5e6064d3821e release/0.6.8-prep +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 keep this branch-free — it sits inside the PageRank sort comparator 9b86a581e734ed8bc4a17a91adbed21dae2ee3e2 release/0.6.8-prep ````` stderr: @@ -7156,7 +7071,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort - + @@ -7208,7 +7123,7 @@ 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.* ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 24bcbf89f5ebb11dd5e2e726c23e5e6064d3821e release/0.6.8-prep +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 9b86a581e734ed8bc4a17a91adbed21dae2ee3e2 release/0.6.8-prep ````` ## `./build/ripwire . --expand=lessByScoreDescId --top-k=0` @@ -7252,10 +7167,10 @@ src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 chose the flat The element on the same output — past the 30-line display cut above: ````` - + - + ````` @@ -7280,7 +7195,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: 5.70s** +**wall time: 5.28s** Input file: @@ -7297,12 +7212,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":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":767,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -7315,15 +7230,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: 1.80s** +**wall time: 1.75s** ````` - + - + @@ -7337,16 +7252,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: 3.69s** +**wall time: 3.55s** ````` {"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":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":767,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -7359,7 +7274,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: 1.66s** +**wall time: 1.57s** ````` {"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] @@ -7375,19 +7290,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: 4.00s** +**wall time: 3.86s** ````` {"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":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":767,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ````` stderr: @@ -7400,7 +7315,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: 1.69s** +**exit code: 1** — **wall time: 1.59s** ````` (empty) @@ -7416,7 +7331,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: 1.64s** +**wall time: 1.61s** Input file: @@ -7434,21 +7349,21 @@ Input file: ````` ````` -{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"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":2911,"graph_unresolved":14058,"graph_unindexed":266,"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: 3.67s** +**wall time: 3.50s** ````` -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"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":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true}}]} +"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true}}]} ````` ## `./build/ripwire . --edit-plan=/aux/edit_plan.json` @@ -7471,11 +7386,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: 11.87s** +**exit code: 2** — **wall time: 11.22s** ````` - @@ -7504,7 +7419,7 @@ ripwire: --edit-plan requires exactly one of --dry-run or --apply -… [140 more display lines; full output is 16853 bytes on 1 raw line(s)] +… [142 more display lines; full output is 17000 bytes on 1 raw line(s)] ````` stderr: @@ -7518,7 +7433,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: 7.26s** +**exit code: 1** — **wall time: 6.82s** ````` (empty) @@ -7537,7 +7452,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: 10.60s** +**wall time: 10.14s** ````` (empty) @@ -7547,16 +7462,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 28583 per-symbol rows, 29479 indexed symbols total) +ripwire: wrote ./.ripwire_quality_baseline (snapshot of 28697 per-symbol rows, 29594 indexed symbols total) ````` Artifact written: ````` - 4846583 .ripwire_quality_baseline + 4866558 .ripwire_quality_baseline # ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit -producer 69b60a9a324ba73ba0983d32f9ffa6a6994d8d5008b316df6779a722847d0c2e -head 24bcbf89f5ebb11dd5e2e726c23e5e6064d3821e +producer 531c5fcf91da1ecfd745364ca7f07fe6b827e81fccd473c3db06df832bb5e15d +head 9b86a581e734ed8bc4a17a91adbed21dae2ee3e2 dirty 1 absorbed 2 ccx 67c15011ee8b 0 @@ -7569,11 +7484,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: 11.74s** +**wall time: 10.83s** ````` - ````` @@ -7696,86 +7611,82 @@ quality_delta desc_bytes= 833 schema_bytes= 193 required=[] *MCP `for`: always bundle=sigs (never the CLI's compact route), the same ranked signatures as --for.* -**wall time: 1.70s** +**wall time: 1.48s** ````` {"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: 6519 more bytes on this line] -"_index":"[index: files=3090 symbols=29477 hash=46268bd9]","_fresh":"ok"}} +{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":" - + @@ -106,7 +106,7 @@ _The same capped map without its inventory tier: --top-k=5 leaves files out, so $ ./build/ripwire . --top-k=5 --no-inventory - + @@ -140,7 +140,7 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ $ ./build/ripwire . --max-tokens=1500 - + @@ -215,11 +215,11 @@ _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` @@ -252,7 +252,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 - + @@ -279,11 +279,11 @@ _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` @@ -314,7 +314,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 - + @@ -346,16 +346,16 @@ _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 ) - + 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] ``` @@ -378,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 - + @@ -409,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 - @@ -439,7 +439,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 +447,7 @@ $ ./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 const char* kAcksFile = ".ripwire_quality_acks" -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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] ``` @@ -469,7 +469,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -500,7 +500,7 @@ _The repo's best-in-class instance to imitate before writing new code (picked by $ ./build/ripwire . --exemplar="format byte sizes for humans" - + f, A&&... a ) @@ -594,25 +594,20 @@ 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 1362493-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 1359757-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=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.005) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 588880 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.114) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 589140 B; lines="3838-3842"; dropped_by_budget=163] [truncated: 1865 of 7738 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` @@ -732,7 +727,7 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable $ ./build/ripwire . --no-stable --top-k=3 - + @@ -766,7 +761,7 @@ $ ./build/ripwire . --around=rankGraphTeleport - + @@ -818,18 +813,18 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - - + + - - - - - - + + + + + + ``` @@ -853,10 +848,10 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - + - - + + @@ -887,13 +882,13 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - + - + - + @@ -925,10 +920,10 @@ _Names referenced but never defined in-corpus (stdlib/third-party surface). The $ ./build/ripwire . --external-surface - + - + @@ -959,12 +954,12 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - + - + ``` @@ -981,10 +976,10 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + - + @@ -1091,7 +1086,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - @@ -1148,7 +1143,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: 9b86a581e +at: eb541409e (0 changed files — working tree is clean, nothing to analyze) ``` @@ -1167,18 +1162,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] ``` @@ -1314,7 +1309,7 @@ _Drop every hit in a file that ALSO contains the --not literal anywhere (file sc $ ./build/ripwire . --grep=DISCLOSE --not=test --grep-scope=file - + - + - + @@ -1529,7 +1524,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1564,7 +1559,7 @@ $ ./build/ripwire . --pack-signatures --top-k=10 - + @@ -1595,7 +1590,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)" ); @@ -1626,7 +1621,7 @@ $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport - + : files the ranked rows show / do not (sum = files=), each named or counted below. : a program entry by its language's convention (C/Go/Rust main, Java/C# static main, a script main its module scope calls, __main__.py), callees as c rows; manifest entries not read. : n= unshown files under dir p=; f= names its code files, the rest (tests, docs, config, test/doc/example dirs) counted. declined=K: K call sites without edges, including inert Elixir quote sites after unquote/unquote_splicing re-entry. external=K: K calls proven outside the tree, no edge. locality_pinned=K: K calls pinned by locality alone (a guess). extent_suspect_syms=K: K defs failed containment, corpus-wide. macro_blanked_files=K: K files indexed from a macro-blanked re-parse. prov=scip|binding|import|split|final-segment: how that edge bound (absent: one unique name); split = one arm of an amb= pick; final-segment = a qualified type matched by last name only. files=/symbols=: files and symbols indexed; edges= distinct call edges; shown= symbols printed, a merged row counting each def; ambiguous= calls split over several defs, corpus-wide; unresolved= calls with in-tree evidence and no edge (every def language-filtered or unreachable, or binding refused); order= rows by rank (important-first, important-last; (auto:fill) = flipped past a size threshold) or by path (stable). skipped_oversize=K: K files over a size ceiling, not indexed. unindexed=ext:N: N text files of that extension no grammar reads (6 extensions at most). unindexed_exts=E: E such extensions in all, the list cut. : a non-test row an indexed test transitively reaches (absent otherwise, never 0). : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. sc=: enclosing scope; the full id is p::sc::n (p= of the row or its ) and selectors take it. amb=K: K calls split over several defs. locals_floor=1: locals= is a floor. role=hub: in= is 8 or more. s in= out= cx= ccx= loc= params= nest= cbo= amp=: callers, callees, cyclomatic/cognitive complexity, lines, parameters, nesting depth, coupled types, callers + co-changed files. ev=/ev_why=: essential complexity (2+: jumps block extract-method; absent: 1) / the jumps behind it, tag:count. ev_floor=1: ev= is a FLOOR; noreturn calls, macro-hidden exits and unresolved gotos are unseen. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. l=N: start line; only on a same-name overload's row, one row per body (bodyless decls fold into overloads=). --> - + @@ -1835,9 +1830,9 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - - - + + + @@ -1863,7 +1858,7 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + @@ -1894,8 +1889,8 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - - + + @@ -1927,7 +1922,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1962,7 +1957,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -1993,17 +1988,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] @@ -2030,10 +2025,10 @@ _The local-reasoning lens: to understand this symbol, how much must you know tha ``` $ ./build/ripwire . --context-ratio --limit=8 - - + @@ -2061,7 +2056,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2096,8 +2091,8 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - + + @@ -2168,7 +2163,7 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - + @@ -2201,16 +2196,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - - + - + + + - - - + + ... [22 more line(s); run it to see the whole thing] ``` @@ -2233,7 +2228,7 @@ _Only pairs whose co-change RECURS in 2+ sub-windows of the mined window (sub_wi ``` $ ./build/ripwire . --cochange --cochange-recur=2 - + @@ -2259,7 +2254,7 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + @@ -2288,9 +2283,9 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - - - + + + @@ -2632,8 +2627,8 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + @@ -2663,7 +2658,7 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - ``` @@ -2688,15 +2683,15 @@ _Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels ``` $ ./build/ripwire . --zoom - - + + - - + + @@ -2725,14 +2720,14 @@ $ ./build/ripwire . --report # ripwire architecture report -3102 files · 29592 symbols · 28717 edges · 3499 modules (13251 call-graph isolated) +3102 files · 29597 symbols · 28731 edges · 3498 modules (13251 call-graph isolated) Root: `.` Call-graph isolate provenance: 3612 declaration, 2119 header, 2754 source, 4766 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3499) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1517 symbols +## Modules (call-graph clusters; showing 12 of 3498) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1519 symbols - **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 497 symbols - **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 333 symbols ... [17 more line(s); run it to see the whole thing] @@ -2751,10 +2746,10 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - + - + @@ -2782,11 +2777,11 @@ $ ./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"] - n101["src
8906"] - n102["src/infra
1296"] + n101["src
8908"] + n102["src/infra
1297"] end subgraph sg1 ["test"] - n103["test
5412"] + n103["test
5414"] n483["test/legochargefix
546"] n394["test/fixtures/recallpassage
314"] n568["test/ownerhopfix/py/pkg
158"] @@ -2811,7 +2806,7 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - + @@ -2842,7 +2837,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2907,7 +2902,7 @@ _The same clean verdict with what it folded printed back: every stale acknowledg ``` $ ./build/ripwire . --quality-delta --show-stale - @@ -2936,7 +2931,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -2961,7 +2956,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - ``` @@ -2986,7 +2981,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 -

@@ -3079,7 +3074,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3109,10 +3104,10 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - - + + @@ -3163,12 +3158,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":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"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":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3184,12 +3179,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":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"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":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3235,12 +3230,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":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"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":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3299,11 +3294,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":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":2911,"graph_unresolved":14064,"graph_unindexed":266,"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":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true}}]} +"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3349,19 +3344,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] ``` @@ -3386,17 +3381,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] @@ -3440,10 +3435,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:10126 +$ ./build/ripwire . --at=src/graph.h:10134 - - + + ``` @@ -3467,7 +3462,7 @@ _No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - ``` @@ -3493,11 +3488,11 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + @@ -3572,7 +3567,7 @@ _Which refs of the `lane/` ref family still hold divergent authored work vs HEAD ``` $ ./build/ripwire . --stray-content=lane/ - + @@ -3606,7 +3601,7 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + @@ -3642,7 +3637,7 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi - @@ -3677,10 +3672,10 @@ _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] @@ -3707,9 +3702,9 @@ _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 - - - + + + @@ -3735,10 +3730,10 @@ _The whole hit list asked for by name: under the same 60-row cap HEAD's referenc ``` $ ./build/ripwire . --whereis=rankGraphTeleport --whereis-listing=all - - + + - + @@ -3848,7 +3843,7 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - @@ -3883,15 +3878,15 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + - - + + - + - + ... [20 more line(s); run it to see the whole thing] ``` @@ -3917,15 +3912,15 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + - - + + - + - + ... [20 more line(s); run it to see the whole thing] ``` @@ -3950,16 +3945,16 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + - - + + - + - + ... [20 more line(s); run it to see the whole thing] ``` @@ -3984,7 +3979,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -4019,8 +4014,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:10126 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10167 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:10134 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10175 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 #3 0x102e01a44 in main src/main.cpp:4174 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) @@ -4049,7 +4044,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4209,7 +4204,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - + @@ -4282,11 +4277,11 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - + - - + + - + - - + + @@ -4439,8 +4434,8 @@ _Rank by git change-frequency prior instead of PageRank._ $ ./build/ripwire . --rank-by=churn --top-k=5 - - + + @@ -4472,22 +4467,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~3 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~6 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - - - - - - - - - - - - -... [23 more line(s); run it to see the whole thing] + + + + + + + + + + + + +... [4 more line(s); run it to see the whole thing] ``` **Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` @@ -4549,7 +4544,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4629,7 +4624,7 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - @@ -4665,7 +4660,7 @@ _Drop matching paths (repeatable) before ranking._ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - + @@ -4696,8 +4691,8 @@ _Full map re-ranked with teleport toward git-changed files — clean tree, so ch $ ./build/ripwire . --map-diff --top-k=5 - - + + @@ -4730,7 +4725,7 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - + @@ -4780,7 +4775,7 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co $ ./build/ripwire . --no-cache --top-k=3 - + @@ -4811,7 +4806,7 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses $ ./build/ripwire . --no-ignore --top-k=3 - + @@ -4847,7 +4842,7 @@ _Skip files above a size bound before parsing (note the corpus shrink in the hea $ ./build/ripwire . --max-file-size=8K --top-k=3 - + @@ -4927,7 +4922,7 @@ _Eval-only: a per-call-site census of WHICH mechanism resolved each call, and th $ ./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3 - + @@ -4981,10 +4976,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,10 +63,12 @@ *Same map, capped to the 5 highest-ranked symbols.* +**wall time: 1.57s** + ````` - + @@ -101,10 +103,12 @@ *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.44s** + ````` - + @@ -129,12 +133,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: 1.60s** + ````` - + @@ -202,7 +210,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** +**exit code: 3** — **wall time: 2.02s** ````` @@ -219,10 +227,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 *The task lens: ranked signatures + quality metrics framed for the task.* -**wall time: 3.02s** +**wall time: 10.00s** ````` - @@ -234,7 +242,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 persistent in-memory index (parse once, reuse across MCP calls) ---- The MCP server is long-live…struct McpIndex 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::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] inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) @@ -259,14 +267,14 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 *Name-shaped query: the router picks name-exact BM25 (header says which/why).* -**wall time: 1.16s** +**wall time: 5.96s** ````` - + - + 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` @@ -277,7 +285,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -299,23 +307,23 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 *Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* -**wall time: 1.16s** +**wall time: 4.21s** ````` - + - + 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 ) @@ -325,10 +333,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span< … [line truncated: 45 more bytes on this line] inline const McpIndex& getIndex( const std::string& root ) inline std::vector<float> churnDecayTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, bool* outHasChurnEvidence = n … [line truncated: 12 more bytes on this line] -struct DataSectionsCut +struct DataSectionsCut inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) -inline std::vector<float> anchoredLexicalRank( const Graph& g, const std::vector<float>& lex ) -### Fixed — `rank_by` over MCP no longer answers a different ranking than `--rank-by` on … [line truncated: 35 more bytes on this line] +inline std::vector<float> anchoredLexicalRank( const Graph& g, const std::vector<float>& lex ) +### Fixed — `rank_by` over MCP no longer answers a different ranking than `--rank-by` on … [line truncated: 35 more bytes on this line] ### 2. Gates for A inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 #### The convergence disclosure contract @@ -339,14 +347,14 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 *T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribute) — contrast with the terminal default above.* -**wall time: 1.15s** +**wall time: 5.58s** ````` - + - + 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` @@ -363,10 +371,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > 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: 1.14s** +**wall time: 4.77s** ````` - + @@ -394,7 +402,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 inline void runParseWorker( ParsePoolShared& sh, unsigned t ) enum class DisclosureWhy : std::uint8_t struct LintRule -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) inline bool hasPytestTable( std::string_view source ) … [137 more display lines; full output is 19683 bytes on 44 raw line(s)] ````` @@ -403,10 +411,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > 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: 1.22s** +**wall time: 3.82s** ````` - @@ -418,7 +426,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 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::string candidatesRootTag( std::size_t keep, std::size_t corpusCount, const CandidateProvenance& prov ) inline std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) struct NameCorpusStats @@ -429,7 +437,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept 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 +struct CandidateProvenance inline constexpr int kLexWeightDoc = 2 std::optional<int> runEvalViews( const MainDispatch& d ) inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) @@ -443,10 +451,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 *Same task with the anchor disabled — the contrast the flag exists for.* -**wall time: 1.21s** +**wall time: 2.84s** ````` - @@ -457,7 +465,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 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::string candidatesRootTag( std::size_t keep, std::size_t corpusCount, const CandidateProvenance& prov ) inline std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) struct NameCorpusStats @@ -469,7 +477,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept 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 +struct CandidateProvenance inline constexpr int kLexWeightDoc = 2 std::optional<int> runEvalViews( const MainDispatch& d ) inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) @@ -486,7 +494,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 ````` - + @@ -499,10 +507,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 *The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* +**wall time: 2.18s** + ````` - + f, A&&... a ) @@ -524,6 +534,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > 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: 1.96s** + ````` @@ -538,6 +550,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 *The honest half of the contract: a task with no ripwire-shaped evidence ABSTAINS with zero commands rather than guessing.* +**wall time: 2.40s** + ````` @@ -549,6 +563,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=9415 > budget=100 *Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* +**wall time: 1.46s** + ````` ripwire recall — "quality delta gating exit codes" — 114 relevant of 230 document files, best-first — total=114 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2157 est_tokens=4593 @@ -576,7 +592,7 @@ refuses by default. -━━ CHANGELOG.md (relevance 7.541) ━━ [sections: 1 of 109 selected (451 in doc), section-granular; whole doc 997708 B; lines="554-569"; dropped_by_budget=108] [truncated: 1597 of 3826 bytes] +━━ CHANGELOG.md (relevance 7.541) ━━ [sections: 1 of 109 selected (451 in doc), section-granular; whole doc 997808 B; lines="554-569"; dropped_by_budget=108] [truncated: 1597 of 3826 bytes] ### Changed — `--quality-delta=A..B` answers the kinds it can under memory pressure, and exits 6 when that answer has no gating row (#387 follow-up) A tripped memory guard in the range form used to give exit 5 and nothing, so on a large repository a four-file change @@ -585,38 +601,17 @@ A tripped memory guard in the range form used to give exit 5 and nothing, so on ## `./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 1362493-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 1359757-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 +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.005) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 588880 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.114) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 589140 B; lines="3838-3842"; dropped_by_budget=163] [truncated: 1865 of 7738 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: - -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)) ````` @@ -669,7 +664,7 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *Self-contained HTML force-directed call graph.* -**wall time: 1.76s** +**wall time: 4.25s** ````` (empty) @@ -685,6 +680,8 @@ Artifact written: *Stable (path/id) emit order — provider KV-cache hits across re-runs.* +**wall time: 1.11s** + ````` @@ -732,7 +729,7 @@ Artifact written: - + @@ -770,8 +767,8 @@ Artifact written: - - + + @@ -797,7 +794,7 @@ Artifact written: -… [1919 more display lines; full output is 52380 bytes on 1 raw line(s)] +… [1920 more display lines; full output is 52411 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --around=rankGraphTeleport --around-fanout=4` @@ -808,7 +805,7 @@ Artifact written: - + @@ -853,10 +850,10 @@ Artifact written: ````` - + - - + + @@ -871,10 +868,10 @@ Artifact written: ````` - + - - + + @@ -886,7 +883,7 @@ Artifact written: *Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* -**exit code: 1** +**exit code: 1** — **wall time: 2.04s** ````` @@ -905,18 +902,18 @@ ripwire: --callers symbol not found: DoesNotExist ````` - - + + - - - - - - + + + + + + ````` @@ -924,12 +921,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: 1.20s** + ````` - + - - + + @@ -943,15 +942,17 @@ ripwire: --callers symbol not found: DoesNotExist *Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* +**wall time: 1.01s** + ````` - + - + - + @@ -984,10 +985,10 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + - + @@ -1022,14 +1023,14 @@ ripwire: --callers symbol not found: DoesNotExist ````` - - + + - + @@ -1057,14 +1058,16 @@ 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.10s** + ````` - + - + ````` @@ -1074,10 +1077,10 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + - + @@ -1109,8 +1112,8 @@ ripwire: --callers symbol not found: DoesNotExist - - + + @@ -1127,6 +1130,8 @@ ripwire: --callers symbol not found: DoesNotExist *Markdown docs that name SYM in a backtick (doc<->code edges).* +**wall time: 1.18s** + ````` @@ -1144,9 +1149,11 @@ ripwire: --callers symbol not found: DoesNotExist *Test files that transitively reach the changed file.* +**wall time: 1.24s** + ````` - @@ -1161,10 +1168,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: 2.44s** + ````` ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: 9b86a581e +at: eb541409e (0 changed files — working tree is clean, nothing to analyze) ````` @@ -1172,7 +1181,7 @@ at: 9b86a581e *Pre-PR gate on a CLEAN tree: no obligations, exit 0.* -**wall time: 2.72s** +**wall time: 4.88s** ````` @@ -1184,6 +1193,8 @@ at: 9b86a581e *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.08s** + ````` @@ -1359,7 +1374,7 @@ at: 9b86a581e *The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* -**wall time: 1.03s** +**wall time: 1.90s** ````` @@ -1399,18 +1414,20 @@ at: 9b86a581e *Raw BM25 ranking (debug lens; --for is the real verb).* +**wall time: 1.63s** + ````` - + - + - + @@ -1421,12 +1438,12 @@ at: 9b86a581e - + - + @@ -1442,10 +1459,10 @@ at: 9b86a581e *Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* -**wall time: 1.19s** +**wall time: 3.62s** ````` - + @@ -1456,14 +1473,14 @@ at: 9b86a581e 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… +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 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] @@ -1474,7 +1491,7 @@ at: 9b86a581e inline std::string doctorWhichRipwire( rw::os::stat_t& st ) inline std::string doctorPathPrependLine( const std::string& selfPath ) std::uint32_t testIterationCeiling() noexcept -inline const char* rankByLegendFor( const char* label ) noexcept +inline const char* rankByLegendFor( const char* label ) noexcept … [156 more display lines; full output is 22218 bytes on 72 raw line(s)] ````` @@ -1482,12 +1499,14 @@ at: 9b86a581e *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.54s** + ````` - + @@ -1520,12 +1539,14 @@ at: 9b86a581e *Control-flow skeleton of one symbol, payload-only via the new --top-k=0.* +**wall time: 1.41s** + ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1549,12 +1570,14 @@ at: 9b86a581e *CHANGED: a line range on --outline is now STRIPPED with a stderr note (it used to refuse).* +**wall time: 1.51s** + ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1584,12 +1607,14 @@ ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — *Full body + inline callee signatures.* +**wall time: 1.29s** + ````` - + : files the ranked rows show / do not (sum = files=), each named or counted below. : a program entry by its language's convention (C/Go/Rust main, Java/C# static main, a script main its module scope calls, __main__.py), callees as c rows; manifest entries not read. : n= unshown files under dir p=; f= names its code files, the rest (tests, docs, config, test/doc/example dirs) counted. declined=K: K call sites without edges, including inert Elixir quote sites after unquote/unquote_splicing re-entry. external=K: K calls proven outside the tree, no edge. locality_pinned=K: K calls pinned by locality alone (a guess). extent_suspect_syms=K: K defs failed containment, corpus-wide. macro_blanked_files=K: K files indexed from a macro-blanked re-parse. prov=scip|binding|import|split|final-segment: how that edge bound (absent: one unique name); split = one arm of an amb= pick; final-segment = a qualified type matched by last name only. files=/symbols=: files and symbols indexed; edges= distinct call edges; shown= symbols printed, a merged row counting each def; ambiguous= calls split over several defs, corpus-wide; unresolved= calls with in-tree evidence and no edge (every def language-filtered or unreachable, or binding refused); order= rows by rank (important-first, important-last; (auto:fill) = flipped past a size threshold) or by path (stable). skipped_oversize=K: K files over a size ceiling, not indexed. unindexed=ext:N: N text files of that extension no grammar reads (6 extensions at most). unindexed_exts=E: E such extensions in all, the list cut. : a non-test row an indexed test transitively reaches (absent otherwise, never 0). : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. sc=: enclosing scope; the full id is p::sc::n (p= of the row or its ) and selectors take it. amb=K: K calls split over several defs. locals_floor=1: locals= is a floor. role=hub: in= is 8 or more. s in= out= cx= ccx= loc= params= nest= cbo= amp=: callers, callees, cyclomatic/cognitive complexity, lines, parameters, nesting depth, coupled types, callers + co-changed files. ev=/ev_why=: essential complexity (2+: jumps block extract-method; absent: 1) / the jumps behind it, tag:count. ev_floor=1: ev= is a FLOOR; noreturn calls, macro-hidden exits and unresolved gotos are unseen. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. l=N: start line; only on a same-name overload's row, one row per body (bodyless decls fold into overloads=). --> - + @@ -1812,6 +1847,8 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *File->file dependency graph (god-files, cycles).* +**wall time: 1.23s** + ````` @@ -1850,14 +1887,14 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Complexity x recent git churn (maintenance pain).* -**wall time: 1.84s** +**wall time: 4.77s** ````` - - - + + + @@ -1890,11 +1927,11 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Token-normalized duplicate bodies.* -**wall time: 2.18s** +**wall time: 3.00s** ````` - + @@ -1919,7 +1956,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + @@ -1930,39 +1967,39 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Files that change together in git (hidden coupling).* -**wall time: 1.73s** +**wall time: 4.10s** ````` - - + - + + + - + + + + + + + - - - - - - - + + - - - + @@ -1972,13 +2009,13 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Hotspots scoped to RECENT churn (the regression lens).* -**wall time: 1.67s** +**wall time: 4.47s** ````` - - - + + + @@ -1998,7 +2035,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + @@ -2012,6 +2049,8 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Enforce layering rules (exit 2 on violation) — run against the repo's own test fixture rules.* +**wall time: 1.04s** + ````` @@ -2050,7 +2089,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: 1.75s** +**wall time: 2.29s** ````` @@ -2090,6 +2129,8 @@ 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.21s** + ````` @@ -2170,8 +2211,8 @@ The joined finding — past the display cut above, extracted so the join is visi ````` - + @@ -2199,7 +2240,7 @@ The joined finding — past the display cut above, extracted so the join is visi -… [189 more display lines; full output is 16880 bytes on 1 raw line(s)] +… [189 more display lines; full output is 16887 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --zoom` @@ -2208,15 +2249,15 @@ The joined finding — past the display cut above, extracted so the join is visi ````` - - + + - - + + @@ -2246,12 +2287,12 @@ The joined finding — past the display cut above, extracted so the join is visi ````` - - - + + + - + @@ -2287,14 +2328,14 @@ The joined finding — past the display cut above, extracted so the join is visi # ripwire architecture report -3102 files · 29592 symbols · 28717 edges · 3499 modules (13251 call-graph isolated) +3102 files · 29597 symbols · 28731 edges · 3498 modules (13251 call-graph isolated) Root: `.` Call-graph isolate provenance: 3612 declaration, 2119 header, 2754 source, 4766 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3499) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1517 symbols +## Modules (call-graph clusters; showing 12 of 3498) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1519 symbols - **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 497 symbols - **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 333 symbols - **docs::`--top-k=N`@COMMANDS.md:59:9077 [pack,allow,insert]** — 181 symbols @@ -2322,10 +2363,10 @@ Call-graph isolate provenance: 3612 declaration, 2119 header, 2754 source, 4766 ````` - + - + @@ -2362,11 +2403,11 @@ Call-graph isolate provenance: 3612 declaration, 2119 header, 2754 source, 4766 %% ripwire --mermaid: module (directory) dependency graph — node = dir (symbol count), edge = inter-module calls (>= 3). Render at mermaid.live. flowchart LR subgraph sg0 ["src"] - n101["src
8906"] - n102["src/infra
1296"] + n101["src
8908"] + n102["src/infra
1297"] end subgraph sg1 ["test"] - n103["test
5412"] + n103["test
5414"] n483["test/legochargefix
546"] n394["test/fixtures/recallpassage
314"] n568["test/ownerhopfix/py/pkg
158"] @@ -2396,11 +2437,11 @@ flowchart LR *Bus-factor: recency-weighted author ownership per file.* -**wall time: 1.70s** +**wall time: 3.53s** ````` - + @@ -2438,7 +2479,7 @@ flowchart LR ````` - + ````` @@ -2460,7 +2501,7 @@ flowchart LR ````` - ````` @@ -2469,11 +2510,11 @@ flowchart LR *On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* -**wall time: 17.11s** +**wall time: 19.70s** ````` - ````` @@ -2487,11 +2528,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: 13.25s** +**wall time: 19.69s** ````` - @@ -2533,14 +2574,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: 9.23s** +**wall time: 3.54s** ````` - - - + + @@ -2552,9 +2593,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: 2.31s** + ````` - ````` @@ -2563,55 +2606,52 @@ 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: 2.92s** +**wall time: 6.25s** ````` - - + - - + + + + - + + + - - - - - - - - - - - - - - - - - - - - - - -… [34 more display lines; full output is 6038 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: 11.26s** +**wall time: 15.28s** ````` - - + + - + @@ -2623,11 +2663,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: 13.87s** +**wall time: 26.66s** ````` - + @@ -2663,11 +2703,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *A second selection, the one branch `arm/for-howitworks-067-f2add` (no ref family left to select on this checkout), picked at capture time from the refs this checkout really has and never the selection above: merged refs are OMITTED from the rows and counted in merged=; refs sharing no merge base with HEAD (a shallow clone, or a pre-rewrite history) land in unknown= with ok="0" — the counters always reconcile against refs=.* -**wall time: 1.57s** +**wall time: 4.75s** ````` - + @@ -2690,11 +2730,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: 70.46s** +**wall time: 98.83s** ````` - + @@ -2723,20 +2763,14 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g -… [32411 more display lines; full output is 4509091 bytes on 1 raw line(s)] -````` - -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) +… [32413 more display lines; full output is 4509251 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: 1.42s** +**exit code: 1** — **wall time: 3.76s** ````` (empty) @@ -2753,11 +2787,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: 46.14s** +**exit code: 2** — **wall time: 204.22s** ````` - @@ -2770,7 +2804,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - + @@ -2793,21 +2827,21 @@ 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: 6.82s** +**wall time: 13.66s** ````` - - - - + + + + - + @@ -2823,14 +2857,14 @@ 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: 6.82s** +**wall time: 14.15s** ````` - - + + - + @@ -2840,12 +2874,12 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - - - - - - + + + + + + @@ -2863,11 +2897,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: 8.38s** +**wall time: 14.04s** ````` - - - + + + @@ -2891,12 +2925,12 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - - - - - - + + + + + + @@ -2915,12 +2949,12 @@ 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: 12.51s** +**wall time: 23.31s** ````` - - + + @@ -2931,6 +2965,8 @@ 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.82s** + ````` @@ -2969,6 +3005,8 @@ 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.70s** + ````` @@ -3005,7 +3043,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** +**exit code: 1** — **wall time: 1.46s** ````` (empty) @@ -3022,10 +3060,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: 1.80s** +**wall time: 5.53s** ````` -{"v":1,"verb":"plan-lanes","at":"9b86a581e","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":3102,"symbols":29592,"edges":287 … [line truncated: 338 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"eb541409e","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":3102,"symbols":29597,"edges":287 … [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"}, @@ -3043,7 +3081,7 @@ ripwire: run `ripwire . --flags` for the gate table {"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":691,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":451,"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/wrap.h","n":"wrapEmitCliFirst","scope":"rw","key":"f5ef05d9574d9516","id":"./src/wrap.h::rw::wrapEmitCliFirst","id_addressable":true,"id_collides_with":0,"l":699,"ord":0,"overloads":1,"amb":7,"cx":10,"ccx":5,"churn":52,"tested":0}], -"files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":52}, +"files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":53}, {"p":"./src/docdrift.h","symbols":4,"churn":36,"ccx":14,"hotspot_rank":35}, {"p":"./src/editcheck.h","symbols":1,"churn":55,"ccx":10,"hotspot_rank":41}, {"p":"./src/main.cpp","symbols":2,"churn":451,"ccx":12,"hotspot_rank":4}, @@ -3062,7 +3100,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: 1.80s** +**wall time: 5.16s** Input file: @@ -3073,7 +3111,7 @@ write regression tests for the new filter ````` ````` -{"v":1,"verb":"plan-lanes","at":"9b86a581e","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3102,"symbols":29592,"edges":28717,"ambiguous":2911,"unresolved":14058},"carve":null,"core":{"files" … [line truncated: 4 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"eb541409e","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3102,"symbols":29597,"edges":28731,"ambiguous":2911,"unresolved":14064},"carve":null,"core":{"files" … [line truncated: 4 more bytes on this line] "symbols":[]},"lanes":[{"id":"lane-0","task":"add a --since filter to the doc-drift verb","claims":{"symbols":[{"p":"./src/docdrift.h","n":"recordUnchecked","scope":"docdrift","key":"12641dab14abc8fd","id":"./src/docdrift.h::docdrift::recordUnchecked","id_addressable":true,"id_collides_with":0,"l":2 … [line truncated: 72 more bytes on this line] {"p":"./src/docdrift.h","n":"sortWeakGroupsByPath","scope":"docdrift","key":"1944ba506280ff80","id":"./src/docdrift.h::docdrift::sortWeakGroupsByPath","id_addressable":true,"id_collides_with":0,"l":2401,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":36,"tested":0}, {"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","key":"1fa68e8d93c05a59","id":"./src/mcpverbs.h::rw::docDriftText","id_addressable":true,"id_collides_with":0,"l":655,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":435,"tested":0}, @@ -3126,6 +3164,8 @@ 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.06s** + ````` @@ -3166,7 +3206,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.12s** ````` (empty) @@ -3182,76 +3222,80 @@ 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.98s** + ````` - +
- - + + - + - + - - + + - - + + - + -… [227 more display lines; full output is 23872 bytes on 1 raw line(s)] +… [227 more display lines; full output is 23874 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: 2.95s** + ````` - + - - + + - + - + - - + + - - + + - + -… [253 more display lines; full output is 25070 bytes on 1 raw line(s)] +… [253 more display lines; full output is 25072 bytes on 1 raw line(s)] ````` Tail of the same output — the `` section: @@ -3288,40 +3332,40 @@ Tail of the same output — the `` section: *Same report, with git history splitting stale mentions into deleted-by-commit vs never-existed.* -**wall time: 1.01s** +**wall time: 2.63s** ````` - - + + - - + + - + - + - - + + - - + + -… [207 more display lines; full output is 23851 bytes on 1 raw line(s)] +… [207 more display lines; full output is 23853 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --from-trace=-` @@ -3334,8 +3378,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:10126 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10167 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:10134 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10175 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 #3 0x102e01a44 in main src/main.cpp:4174 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) @@ -3343,25 +3387,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)" ); @@ -3403,6 +3447,8 @@ AddressSanitizer:DEADLYSIGNAL *ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* +**wall time: 1.07s** + ````` @@ -3479,10 +3527,10 @@ AddressSanitizer:DEADLYSIGNAL *Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges.* -**wall time: 1.15s** +**wall time: 3.46s** ````` - + @@ -3493,14 +3541,14 @@ AddressSanitizer:DEADLYSIGNAL 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… +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 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] @@ -3511,7 +3559,7 @@ AddressSanitizer:DEADLYSIGNAL inline std::string doctorWhichRipwire( rw::os::stat_t& st ) inline std::string doctorPathPrependLine( const std::string& selfPath ) std::uint32_t testIterationCeiling() noexcept -inline const char* rankByLegendFor( const char* label ) noexcept +inline const char* rankByLegendFor( const char* label ) noexcept … [124 more display lines; full output is 18961 bytes on 10 raw line(s)] ````` @@ -3519,7 +3567,7 @@ AddressSanitizer:DEADLYSIGNAL *Per-file metrics as CodeCharta cc.json.* -**wall time: 1.40s** +**wall time: 2.50s** ````` (empty) @@ -3536,7 +3584,7 @@ Artifact written: *One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* -**wall time: 3.31s** +**wall time: 4.36s** Input file: @@ -3551,7 +3599,7 @@ lego:Vehicle - + @@ -3565,7 +3613,7 @@ lego:Vehicle 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::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 ) @@ -3581,12 +3629,6 @@ lego:Vehicle … [106 more display lines; full output is 50524 bytes on 1 raw line(s)] ````` -stderr: - -````` -ripwire: cache : over its 2048 MiB budget — evicted 94 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) -````` - --- @@ -3596,15 +3638,15 @@ ripwire: cache : over its 2048 MiB budget — evicted 94 blob(s) of other r *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: 5.19s** +**exit code: 1** — **wall time: 21.49s** ````` - - + - - + + : 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. --> - - + + @@ -3685,49 +3727,30 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s ````` -## `./build/ripwire . --rank-by=churn-decay --since=HEAD~3 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3` +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~6 --exclude=test --exclude=docs --exclude=skills --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: 1.25s** +**wall time: 4.94s** ````` - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - + + + + - + ````` @@ -3751,17 +3774,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: 1.03s** + ````` - + 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,10165,10811,171,1425,1464,1751 +1184,10173,10819,171,1425,1464,1751 fn,fn,fn,fn,fn,fn,fn 0,0,0,0,0,0,0 -src/mcpindex.h:1288,src/graph.h:10168,src/graph.h:10862,src/eval.h:328,src/main.cpp:1439,src/main.cpp:1478 src/main.cpp:1479 src/main.cpp:1493,src/main.cpp:1871 +src/mcpindex.h:1288,src/graph.h:10176,src/graph.h:10870,src/eval.h:328,src/main.cpp:1439,src/main.cpp:1478 src/main.cpp:1479 src/main.cpp:1493,src/main.cpp:1871 ````` @@ -3770,24 +3795,24 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *Flat top-K export for an external reranker.* -**wall time: 1.14s** +**wall time: 2.78s** ````` - - + + 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 ) @@ -3813,10 +3838,12 @@ ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidate *Machine-parseable JSON, same content, keys mirror the XML attrs.* +**wall time: 1.16s** + ````` -{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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:10165","sites_at":["src/graph.h:10168"]}, -{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:10811","sites_at":["src/graph.h:10862"]}, +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"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:10173","sites_at":["src/graph.h:10176"]}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:10819","sites_at":["src/graph.h:10870"]}, {"t":"fn","n":"runEval","p":"src/eval.h:171","sites_at":["src/eval.h:328"]}, {"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1425","sites_at":["src/main.cpp:1439"]}, {"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1464","sites_at":["src/main.cpp:1478","src/main.cpp:1479","src/main.cpp:1493"]}, @@ -3843,12 +3870,12 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Pagination: 3 items, skipping the first 3 (deterministic seams).* -**wall time: 1.69s** +**wall time: 3.38s** ````` - + @@ -3859,10 +3886,12 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop test paths from the corpus before ranking.* +**wall time: 1.13s** + ````` - + @@ -3897,10 +3926,12 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop matching paths (repeatable) before ranking.* +**wall time: 1.37s** + ````` - + @@ -3935,11 +3966,13 @@ 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: 2.23s** + ````` - - + + @@ -3963,12 +3996,12 @@ 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: 1.84s** +**wall time: 2.79s** ````` - + @@ -4003,12 +4036,12 @@ 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: 1.93s** +**wall time: 3.02s** ````` - + @@ -4042,7 +4075,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m Artifact written: ````` - 26929702 /aux/warm2.ripwirecache + 26937586 /aux/warm2.ripwirecache ````` ## `./build/ripwire . --max-file-size=8K --top-k=3` @@ -4052,7 +4085,7 @@ Artifact written: ````` - + @@ -4103,12 +4136,12 @@ 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: 1.87s** +**wall time: 1.30s** ````` - + @@ -4135,18 +4168,18 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Self-eval: co-change recall vs BM25.* -**wall time: 18.29s** +**wall time: 21.02s** ````` ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 3.4% 6.4% 13.1% - BM25 10.4% 13.0% 13.8% - BM25sub 17.9% 22.3% 31.1% - BM25body 13.8% 20.2% 35.6% - fused 9.1% 18.8% 24.9% - anchored 13.8% 20.2% 35.6% - same-dir 0.0% 5.6% 8.8% + ripwire 3.2% 6.2% 12.7% + BM25 9.7% 12.1% 12.8% + BM25sub 17.3% 21.6% 30.0% + BM25body 14.0% 19.9% 35.1% + fused 8.8% 18.2% 24.1% + anchored 14.0% 19.9% 35.1% + same-dir 0.0% 5.3% 8.5% random 0.2% 0.3% 0.6% <- floor (random ranking over F=3102 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 / @@ -4163,11 +4196,11 @@ ripwire --eval (co-change recovery, averaged over 80 historical commits) *Known-item retrieval eval: MRR + recall@k per ranker per query mode.* -**wall time: 137.96s** +**wall time: 147.90s** ````` -ripwire --eval-retrieval (known-item, 4002 doc-commented symbols; gold is in-corpus by construction) - sample: population=7485 scored=4002 rule=smallest-key CAPPED — a SUBSET, not the population +ripwire --eval-retrieval (known-item, 4004 doc-commented symbols; gold is in-corpus by construction) + sample: population=7488 scored=4004 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) @@ -4176,11 +4209,11 @@ ripwire --eval-retrieval (known-item, 4002 doc-commented symbols; gold is in-co subtoken doc-phrase 0.909 88.9% 92.8% 93.8% name-exact name 0.897 81.9% 95.9% 97.3% name-exact doc-phrase 0.014 0.6% 1.9% 2.9% - anchored name 0.685 56.0% 83.8% 88.5% + anchored name 0.685 56.1% 83.8% 88.5% anchored doc-phrase 0.904 88.1% 92.8% 93.5% routed name 0.899 82.1% 96.1% 97.5% routed doc-phrase 0.907 88.8% 92.6% 93.5% - note: routing chose name-exact on 3990/4002 NAME queries (a NAME query is always identifier-shaped); + note: routing chose name-exact on 3992/4004 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 @@ -4192,7 +4225,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: 18.15s** +**exit code: 3** — **wall time: 39.99s** Input file: @@ -4302,7 +4335,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=9b86a581e) +ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=eb541409e) ````` @@ -4310,27 +4343,31 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=9b86 # navigate — seeds, claims, slices, shapes -## `./build/ripwire . --at=src/graph.h:10126` +## `./build/ripwire . --at=src/graph.h:10134` *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.20s** + ````` - - + + ````` -## `./build/ripwire . --callers=@src/graph.h:10126` +## `./build/ripwire . --callers=@src/graph.h:10134` *The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* +**wall time: 1.06s** + ````` - + - - + + @@ -4351,18 +4388,20 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=9b86 stderr: ````` -ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 14462 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 14470 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.05s** + ````` - + - + ````` @@ -4372,10 +4411,10 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. ````` - + - - + + @@ -4399,7 +4438,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** +**exit code: 1** — **wall time: 1.20s** ````` (empty) @@ -4415,23 +4454,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: 2.27s** + ````` - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ````` @@ -4439,19 +4480,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: 2.29s** + ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4461,28 +4504,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: 2.61s** + ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + - + pw = biasPrior( g, p );]]> - + & p, float alpha = 0.85f )]]> - + & p, float alpha = 0.85f )]]> @@ -4492,40 +4537,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: 1.89s** + ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + - + - + - + - + - + 0.0 )]]> - + - + @@ -4567,19 +4614,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: 1.45s** + ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4589,12 +4638,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: 1.12s** +**wall time: 1.64s** ````` 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 ) ````` @@ -4602,7 +4651,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.17s** +**wall time: 1.89s** ````` @@ -4699,7 +4748,7 @@ ripwire: --pattern: this pattern collapses to a single bare token, which is a TE ````` @@ -4933,7 +4982,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? inline void runParseWorker( ParsePoolShared& sh, unsigned t ) enum class DisclosureWhy : std::uint8_t struct LintRule -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) inline bool hasPytestTable( std::string_view source ) … [137 more display lines; full output is 19568 bytes on 44 raw line(s)] ````` @@ -4942,10 +4991,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: 1.16s** +**wall time: 2.93s** ````` - + @@ -4973,7 +5022,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? inline void runParseWorker( ParsePoolShared& sh, unsigned t ) enum class DisclosureWhy : std::uint8_t struct LintRule -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) inline bool hasPytestTable( std::string_view source ) … [148 more display lines; full output is 19901 bytes on 44 raw line(s)] ````` @@ -4982,10 +5031,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: 1.16s** +**wall time: 2.22s** ````` - + @@ -5013,7 +5062,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? inline void runParseWorker( ParsePoolShared& sh, unsigned t ) enum class DisclosureWhy : std::uint8_t struct LintRule -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) inline bool hasPytestTable( std::string_view source ) … [133 more display lines; full output is 20257 bytes on 55 raw line(s)] ````` @@ -5022,10 +5071,10 @@ 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: 1.11s** +**wall time: 2.07s** ````` - + @@ -5033,7 +5082,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? L2 stale acks in the range form. A changed-files answer holds no whole target tree to check the …std::vector<rw::quality::StaleAck> refPairStaleAcks( const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const RefPairDelta& refs ) inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) inline const char* kAcksFile = ".ripwire_quality_acks" -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +inline 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& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes … [line truncated: 5 more bytes on this line] inline const char* kNotesFile = ".ripwire_notes" @@ -5062,10 +5111,10 @@ 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: 1.15s** +**wall time: 2.12s** ````` - + @@ -5073,7 +5122,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? L2 stale acks in the range form. A changed-files answer holds no whole target tree to check the …std::vector<rw::quality::StaleAck> refPairStaleAcks( const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const RefPairDelta& refs ) inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) inline const char* kAcksFile = ".ripwire_quality_acks" -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +inline 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& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes … [line truncated: 5 more bytes on this line] inline const char* kNotesFile = ".ripwire_notes" @@ -5104,10 +5153,10 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? ````` - - - + + @@ -5135,22 +5184,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: 2.11s** +**wall time: 5.14s** ````` - + - + - - - + + + + ````` @@ -5159,18 +5208,17 @@ 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: 1.97s** +**wall time: 5.22s** ````` - + - + @@ -5217,10 +5265,12 @@ 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.02s** + ````` - + @@ -5255,10 +5305,12 @@ 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.17s** + ````` - + @@ -5299,18 +5351,18 @@ 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.16s** ````` - + - + const&, float) src/graph.h:10126 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10167 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:10134 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10175 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 #3 0x102e01a44 in main src/main.cpp:4174 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc)]]> 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: 1.13s** + ````` - + ````` @@ -5359,12 +5413,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.51s** +**exit code: 4** — **wall time: 2.91s** ````` - + ````` @@ -5399,21 +5453,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: 4.38s** +**wall time: 6.36s** ````` - - + - + @@ -5439,10 +5493,12 @@ 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.02s** + ````` - - + + @@ -5459,7 +5515,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir ````` - + @@ -5477,21 +5533,21 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir ````` - - + - - - - + + + + - + @@ -5501,7 +5557,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + @@ -5513,11 +5569,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: 2.27s** +**wall time: 2.40s** ````` - @@ -5553,11 +5609,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: 2.44s** +**wall time: 2.83s** ````` - @@ -5570,11 +5626,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - + @@ -5593,11 +5649,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: 1.19s** +**wall time: 1.29s** ````` - @@ -5633,19 +5689,19 @@ 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: 1.24s** +**wall time: 1.30s** ````` - - - - + + + @@ -5672,8 +5728,8 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir ````` - - + + @@ -5688,7 +5744,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + @@ -5708,11 +5764,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: 9.81s** +**wall time: 9.95s** ````` - + @@ -5734,7 +5790,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir

-

+

@@ -5745,7 +5801,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *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: 2.21s** +**wall time: 2.20s** ````` @@ -5861,7 +5917,7 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal *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: 1.80s** +**wall time: 1.72s** ````` @@ -5901,7 +5957,7 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal *DROP two families, applied after selection; the raw select=/ignore= you passed rides on the root.* -**wall time: 1.77s** +**wall time: 1.78s** ````` @@ -5941,7 +5997,7 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal *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: 1.70s** +**wall time: 1.82s** ````` @@ -5958,7 +6014,7 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal *Pasting that findings_next=: the same rule under a 10x budget counts its true total — no count_capped=, no findings_next=.* -**wall time: 1.81s** +**wall time: 1.80s** ````` @@ -6005,7 +6061,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: 1.74s** +**exit code: 1** — **wall time: 1.79s** ````` (empty) @@ -6021,7 +6077,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: 1.77s** +**wall time: 1.78s** ````` {"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] @@ -6083,11 +6139,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: 3.69s** +**wall time: 3.97s** ````` -

@@ -6098,14 +6154,14 @@ 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: 7.95s** +**wall time: 8.01s** ````` - - -

-

+ + +

+

````` @@ -6113,14 +6169,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: 7.84s** +**wall time: 7.97s** ````` - -

-

+ +

+

````` @@ -6128,11 +6184,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: 1.74s** +**wall time: 1.76s** ````` - + @@ -6168,11 +6224,11 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M *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: 1.69s** +**wall time: 1.61s** ````` - + @@ -6191,7 +6247,7 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M - + @@ -6210,7 +6266,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: 1.67s** +**wall time: 1.57s** ````` (empty) @@ -6226,7 +6282,7 @@ Artifact written: *CI generate-and-exit: cold-parse and write BOTH committable cache families (lean + rich), no map on stdout.* -**wall time: 3.70s** +**wall time: 4.11s** ````` (empty) @@ -6235,16 +6291,16 @@ Artifact written: stderr: ````` -ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (26929702 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (55610059 bytes, rich family) +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (26937586 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (55620806 bytes, rich family) ````` Artifact written: ````` - 26929702 /aux/ci_index.lean.ripwirecache - 55610059 /aux/ci_index.rich.ripwirecache - 82539761 total + 26937586 /aux/ci_index.lean.ripwirecache + 55620806 /aux/ci_index.rich.ripwirecache + 82558392 total ````` ## `./build/ripwire . --cache=/aux/ci_index.lean.ripwirecache --top-k=3` @@ -6254,7 +6310,7 @@ Artifact written: ````` - + @@ -6292,7 +6348,7 @@ Artifact written: ````` - + @@ -6334,7 +6390,7 @@ 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. - 123569 /aux/pin_census.tsv + 123608 /aux/pin_census.tsv ````` ## `./build/ripwire . --plan-lint=test/planlintfix/wave.md` @@ -6345,7 +6401,7 @@ Artifact written: ````` - + @@ -6367,7 +6423,7 @@ Artifact written: ````` - + @@ -6379,15 +6435,15 @@ 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: 5.16s** +**exit code: 1** — **wall time: 6.41s** ````` - - + - - + + + untested blast radius ; exit 4 while either exists. window: total= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). 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. declined_calls=K: K call sites left unbound (no evidence chose one def), in no count or row. 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. run_first=N: the first N t rows have the most direct evidence (changed/partner/hops=1, else nearest hops=); not a skip list. 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. ccx_bar=N: the cognitive-complexity bar a u row's ccx= is read against. 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). --> @@ -6474,17 +6530,17 @@ at: 9b86a581e+dirty - + - + - - + + @@ -6500,11 +6556,11 @@ at: 9b86a581e+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: 22.47s** +**exit code: 2** — **wall time: 24.97s** ````` - @@ -6547,11 +6603,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: 11.09s** +**exit code: 2** — **wall time: 12.51s** ````` - @@ -6594,10 +6650,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: 10.97s** +**exit code: 2** — **wall time: 12.45s** ````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":164,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"9b86a581e+dirty","renames":60,"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":164,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"eb541409e+dirty","renames":60,"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"}, @@ -6641,7 +6697,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: 6.92s** +**exit code: 1** — **wall time: 7.46s** ````` (empty) @@ -6659,7 +6715,7 @@ ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing accepte *NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* -**wall time: 6.96s** +**wall time: 7.66s** ````` (empty) @@ -6677,11 +6733,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: 10.92s** +**exit code: 2** — **wall time: 11.55s** ````` - @@ -6740,11 +6796,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6756,11 +6812,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row identical.* -**wall time: 1.57s** +**wall time: 1.72s** ````` - @@ -6772,11 +6828,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6812,13 +6868,13 @@ 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. --> - - + + @@ -6843,11 +6899,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. --> - + @@ -6872,7 +6928,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + @@ -6883,12 +6939,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. --> - - + + ````` @@ -6903,11 +6959,11 @@ stderr: *CHANGED: --plan surfaces those same refs as an row rather than silently dropping them.* -**wall time: 2.70s** +**wall time: 3.01s** ````` - + ````` @@ -6922,11 +6978,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: 4.21s** +**wall time: 4.60s** ````` -

@@ -6937,11 +6993,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: 11.07s** +**wall time: 11.73s** ````` - @@ -6983,7 +7039,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: 6.94s** +**exit code: 1** — **wall time: 7.19s** ````` (empty) @@ -7003,11 +7059,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: 3.31s** +**wall time: 3.62s** ````` - + @@ -7032,11 +7088,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr - - - - - + + + + + ````` @@ -7046,7 +7102,7 @@ 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.* ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 keep this branch-free — it sits inside the PageRank sort comparator 9b86a581e734ed8bc4a17a91adbed21dae2ee3e2 release/0.6.8-prep +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 keep this branch-free — it sits inside the PageRank sort comparator eb541409e8fbff63c10f6e4192661251ec56de3c release/0.6.8-prep ````` stderr: @@ -7071,7 +7127,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort - + @@ -7123,7 +7179,7 @@ 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.* ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 9b86a581e734ed8bc4a17a91adbed21dae2ee3e2 release/0.6.8-prep +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort eb541409e8fbff63c10f6e4192661251ec56de3c release/0.6.8-prep ````` ## `./build/ripwire . --expand=lessByScoreDescId --top-k=0` @@ -7167,10 +7223,10 @@ src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-19 chose the flat The element on the same output — past the 30-line display cut above: ````` - + - + ````` @@ -7195,7 +7251,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: 5.28s** +**wall time: 5.90s** Input file: @@ -7212,17 +7268,18 @@ 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":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"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":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: ````` +ripwire: cache : over its 2048 MiB budget — evicted 61 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 ````` @@ -7230,15 +7287,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: 1.75s** +**wall time: 1.93s** ````` - + - + @@ -7252,16 +7309,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: 3.55s** +**wall time: 3.90s** ````` {"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":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"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":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -7274,7 +7331,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: 1.57s** +**wall time: 1.76s** ````` {"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] @@ -7290,19 +7347,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: 3.86s** +**wall time: 4.17s** ````` {"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":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"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":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ````` stderr: @@ -7315,7 +7372,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: 1.59s** +**exit code: 1** — **wall time: 1.76s** ````` (empty) @@ -7331,7 +7388,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: 1.61s** +**wall time: 1.77s** Input file: @@ -7349,21 +7406,21 @@ Input file: ````` ````` -{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":2911,"graph_unresolved":14064,"graph_unindexed":266,"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: 3.50s** +**wall time: 3.91s** ````` -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":2911,"graph_unresolved":14064,"graph_unindexed":266,"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":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true}}]} +"graph_ambiguous":2911,"graph_unresolved":14064,"graph_unindexed":266,"counts_floor":true}}]} ````` ## `./build/ripwire . --edit-plan=/aux/edit_plan.json` @@ -7386,11 +7443,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: 11.22s** +**exit code: 2** — **wall time: 12.24s** ````` - @@ -7433,7 +7490,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: 6.82s** +**exit code: 1** — **wall time: 7.25s** ````` (empty) @@ -7452,7 +7509,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: 10.14s** +**wall time: 10.66s** ````` (empty) @@ -7462,16 +7519,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 28697 per-symbol rows, 29594 indexed symbols total) +ripwire: wrote ./.ripwire_quality_baseline (snapshot of 28702 per-symbol rows, 29599 indexed symbols total) ````` Artifact written: ````` - 4866558 .ripwire_quality_baseline + 4867476 .ripwire_quality_baseline # ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit -producer 531c5fcf91da1ecfd745364ca7f07fe6b827e81fccd473c3db06df832bb5e15d -head 9b86a581e734ed8bc4a17a91adbed21dae2ee3e2 +producer d48904697b489e550db3aa8caeb4115883b8b80eafebf4c4079a7682d33a8c59 +head eb541409e8fbff63c10f6e4192661251ec56de3c dirty 1 absorbed 2 ccx 67c15011ee8b 0 @@ -7484,11 +7541,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: 10.83s** +**wall time: 11.53s** ````` - ````` @@ -7611,38 +7668,38 @@ quality_delta desc_bytes= 833 schema_bytes= 193 required=[] *MCP `for`: always bundle=sigs (never the CLI's compact route), the same ranked signatures as --for.* -**wall time: 1.48s** +**wall time: 1.53s** ````` {"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: 6523 more bytes on this line] -"_index":"[index: files=3102 symbols=29592 hash=a4e5b4b0]","_fresh":"ok"}} +{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"

@@ -3074,7 +3079,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3104,10 +3109,10 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - - + + @@ -3158,12 +3163,12 @@ $ ./build/ripwire . --insert-before-symbol=nonNegativeFloatDescKey --edit-payloa {"n":"benchAdaptive","p":"bench/bench_radix_ab.cpp:157","l":[162]}, {"n":"radixSortNonNegativeFloatsDesc","p":"src/infra/sortutil.h:105","l":[114]}, {"n":"radixSortByScoreDescId","p":"src/infra/sortutil.h:120","l":[180]}], -"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":767,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3179,12 +3184,12 @@ _Insert immediately AFTER one uniquely-resolved definition; replaced_bytes=0 bec ``` $ ./build/ripwire . --insert-after-symbol=lessByScoreDescId --edit-payload=/aux/payload_note.h {"applied":"insert_after_symbol","symbol":"lessByScoreDescId","file":"src/infra/sortutil.h","span":{"start":2375,"end":2463},"lines":{"start":56,"end":58},"replaced_bytes":0,"old_file_bytes":10824,"new_file_bytes":10912,"file_eol":"lf","eol_normalized":false,"trailing_newline_folded":true,"separator … [line truncated: 652 more bytes on this line] -"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":767,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3230,12 +3235,12 @@ $ ./build/ripwire . --insert-before-symbol=nonNegativeFloatDescKey --edit-payloa {"n":"benchAdaptive","p":"bench/bench_radix_ab.cpp:157","l":[162]}, {"n":"radixSortNonNegativeFloatsDesc","p":"src/infra/sortutil.h:105","l":[114]}, {"n":"radixSortByScoreDescId","p":"src/infra/sortutil.h:120","l":[180]}], -"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":767,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":767,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3294,11 +3299,11 @@ _The same plan committed: per-file locks, re-verify-before-write, atomic rename, ``` $ ./build/ripwire . --edit-plan=/aux/edit_plan.json --apply -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"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":2911,"graph_unresolved":14058,"graph_unindexed":266,"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":2904,"graph_unresolved":14037,"graph_unindexed":266,"counts_floor":true}}]} +"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3344,19 +3349,19 @@ _Bare --slice=SYM: the INVENTORY of sliceable locals (), so a calle ``` $ ./build/ripwire . --slice=rankGraphTeleport - - - - - - - - - - - - - + + + + + + + + + + + + + ... [3 more line(s); run it to see the whole thing] ``` @@ -3381,17 +3386,17 @@ _Forward flow: which statements the seed's value reaches, at the default depth b ``` $ ./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + ... [22 more line(s); run it to see the whole thing] @@ -3435,10 +3440,10 @@ $ ./build/ripwire . --slice-depth=3 _Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost._ ``` -$ ./build/ripwire . --at=src/graph.h:9673 +$ ./build/ripwire . --at=src/graph.h:10126 - - + + ``` @@ -3462,7 +3467,7 @@ _No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - ``` @@ -3488,11 +3493,11 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + @@ -3567,7 +3572,7 @@ _Which refs of the `lane/` ref family still hold divergent authored work vs HEAD ``` $ ./build/ripwire . --stray-content=lane/ - + @@ -3601,19 +3606,19 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + - + + - ... [17 more line(s); run it to see the whole thing] ``` @@ -3637,20 +3642,20 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi - - - + + + + + + + + + - - - - - - - -... [16 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):** @@ -3672,11 +3677,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] ``` @@ -3702,10 +3707,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] @@ -3730,11 +3735,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] @@ -3762,7 +3767,7 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat $ ./build/ripwire . --flags - + @@ -3843,7 +3848,7 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - @@ -3878,15 +3883,15 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + - - + + - - - + + + ... [20 more line(s); run it to see the whole thing] ``` @@ -3912,15 +3917,15 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + - - + + - - - + + + ... [20 more line(s); run it to see the whole thing] ``` @@ -3945,16 +3950,16 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + - - + + - - - + + + ... [20 more line(s); run it to see the whole thing] ``` @@ -3979,7 +3984,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -4014,8 +4019,8 @@ $ ./build/ripwire . --from-trace=- AddressSanitizer:DEADLYSIGNAL ================================================================= ==41337==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000018 (pc 0x000102f4a1c8 bp 0x00016d2f1a40 sp 0x00016d2f19e0 T0) - #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:9673 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:9714 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:10126 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10167 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 #3 0x102e01a44 in main src/main.cpp:4174 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) @@ -4044,7 +4049,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4134,7 +4139,7 @@ _ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to ``` $ ./build/ripwire . --pack-task="add a new output format flag to the CLI" - + + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept @@ -4171,11 +4176,11 @@ _Fan-out form: one shared core + 3 per-agent slices carved along call-graph comm ``` $ ./build/ripwire . --pack-task="add a new output format flag to the CLI" --partition=3 - wraps a ; bundle role=core|partition i= symbols= modules= bytes= tokens=: one agent's ctx, symbols= ids assigned, bytes= its size; tokens= = est_tokens= = bytes/2.36 (flat, the densest rate; the inner ctx est_tokens= is language-weighted). Inner ctx task= root= budget_tokens= dropped_positive=: the task, p= base, the slice's token target, ranked candidates its budget cut. of_top=: rows ranked (far) / bodies (callers); s rel=caller|callee shared=: 1-hop edge, bodies reached (absent at 1). window: shown= total= capped= (capped=1 cut). run_unknown=1: no runner derivable (a guess would be worse). est_tokens=: price as emitted (an upper bound under compact). : rank N in this ranking, rows in r= order. : cyclomatic/cognitive complexity. : N callers in the index (absent: not measured). route=: the ranker: name-exact(X) = the task names symbol X (anchors: its evidence), subtoken+body = conceptual BM25 (:broad = 1-2 plain words, plain rg may also win; :declined(...) = a name hit refused as a common name). sc=: enclosing scope; the full id is p::sc::n (p= of the row or its ) and selectors take it. next=: the one pasteable follow-up. pure=1: const/constexpr signature (Swift: non-mutating) and no transitive side effect found; a hint. partitions=/requested=: partitions carved / asked for. modules=/split=: call-graph groups found / cuts forced to split one. core_symbols=/surface=: ids in the shared core / core plus the assignable remainder. budget_per_agent_tokens=: one agent's budget, core_budget_tokens= plus partition_budget_tokens=. total_bytes=N: bytes of all bundles together. overlap_mean=/overlap_max=: pairwise Jaccard over the ids partitions name, before trimming. shared_symbols=/union_symbols=: ids two or more partitions name / ids any partition names. core_overlap=: share of the core surface a partition reaches anyway. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. --> - + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept @@ -4204,7 +4209,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - + @@ -4277,14 +4282,14 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - + - - + + - - + @@ -4311,14 +4316,14 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - - + - - + + - - + @@ -4346,7 +4351,7 @@ $ ./build/ripwire . --skipped - @@ -4432,19 +4437,19 @@ _Rank by git change-frequency prior instead of PageRank._ ``` $ ./build/ripwire . --rank-by=churn --top-k=5 - + - - + + - + - + - + ... [7 more line(s); run it to see the whole thing] ``` @@ -4467,22 +4472,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 --in=src --limit=3 +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~3 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - - - - - - - - - - - - -... [7 more line(s); run it to see the whole thing] + + + + + + + + + + + + +... [23 more line(s); run it to see the whole thing] ``` **Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` @@ -4544,7 +4549,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4572,7 +4577,7 @@ _The session legend dictionary the MCP server serves as ripwire://legend-dict/fu ``` $ ./build/ripwire . --legend-dict -ripwire legend dictionary ripwire.dict/v1 dictv=f1e98faa0aeaa1ee entries=902 +ripwire legend dictionary ripwire.dict/v1 dictv=30173b0aa2d06473 entries=908 : 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=) @@ -4624,18 +4629,18 @@ _The family join: per function, which of four orthogonal evidence families fire, ``` $ ./build/ripwire . --ensemble --limit=8 - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -4660,8 +4665,8 @@ _Drop matching paths (repeatable) before ranking._ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - - + + @@ -4691,8 +4696,8 @@ _Full map re-ranked with teleport toward git-changed files — clean tree, so ch $ ./build/ripwire . --map-diff --top-k=5 - - + + @@ -4725,8 +4730,8 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - - + + @@ -4775,8 +4780,8 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co $ ./build/ripwire . --no-cache --top-k=3 - - + + @@ -4806,8 +4811,8 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses $ ./build/ripwire . --no-ignore --top-k=3 - - + + @@ -4842,8 +4847,8 @@ _Skip files above a size bound before parsing (note the corpus shrink in the hea $ ./build/ripwire . --max-file-size=8K --top-k=3 - - + + @@ -4922,8 +4927,8 @@ _Eval-only: a per-call-site census of WHICH mechanism resolved each call, and th $ ./build/ripwire . --pin-census=/aux/pin_census.tsv --top-k=3 - - + + @@ -4976,10 +4981,10 @@ _--mcp-legend=inline: every answer of the stdio session carries its own legend ( ``` $ ./build/ripwire '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whereis","arguments":{"path":".","symbol":"rankGraphTeleport"}}}' '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"whereis","arguments":{"path":".","symbol":"computeOnePairOverlap"}}}' | ./build/ripwire --mcp --mcp-legend=inline {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"ripwire","version":"1.0"},"capabilities":{"tools":{},"resources":{}},"instructions":"Map before reading files. Start a new task with explore; use from_trace for an error; use impact plus uses before changing a sym … [line truncated: 735 more bytes on this line] -{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":" - - + + @@ -45,7 +45,7 @@ - + @@ -56,20 +56,18 @@ -… [727 more display lines; full output is 23442 bytes on 1 raw line(s)] +… [722 more display lines; full output is 23369 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --top-k=5` *Same map, capped to the 5 highest-ranked symbols.* -**wall time: 1.40s** - ````` - - + + @@ -86,7 +84,7 @@ - + @@ -96,19 +94,17 @@ -… [58 more display lines; full output is 8090 bytes on 1 raw line(s)] +… [58 more display lines; full output is 8099 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.32s** - ````` - + @@ -133,14 +129,12 @@ *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.33s** - ````` - + - + @@ -210,77 +202,71 @@ 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.30s** +**exit code: 3** ````` - + ````` stderr: ````` -ripwire: --token-budget exceeded: withheld_est_tokens=9444 > budget=100 +ripwire: --token-budget exceeded: withheld_est_tokens=9415 > 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.62s** +**wall time: 3.02s** ````` - - + 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 persistent in-memory index (parse once, reuse across MCP calls) ---- The MCP server is long-live…struct McpIndex 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::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] +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] inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) 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 std::string reAbsolutize( std::string_view rel, std::string_view root ) -inline CachePathKeys buildCachePathKeys( const std::vector<std::string>& files, std::string_view rootDir ) +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 std::string reAbsolutize( std::string_view rel, std::string_view root ) +inline CachePathKeys buildCachePathKeys( const std::vector<std::string>& files, std::string_view rootDir ) 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 void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) inline std::uint64_t byteHash( const char* data, std::size_t n ) noexcept void emitGrepHandleLegend( bool enabled ) -… [120 more display lines; full output is 19496 bytes on 1 raw line(s)] -````` - -stderr: - -````` -ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) +… [121 more display lines; full output is 19577 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --for="rankGraphTeleport"` *Name-shaped query: the router picks name-exact BM25 (header says which/why).* -**wall time: 2.80s** +**wall time: 1.16s** ````` - + - + 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` @@ -291,7 +277,7 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -306,61 +292,61 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r for( const double value : teleport ) { teleportMass += value; -… [15 more display lines; full output is 4560 bytes on 29 raw line(s)] +… [15 more display lines; full output is 4564 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: 2.97s** +**wall time: 1.16s** ````` - + - + 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… -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 ) +struct RankedGraph +inline std::vector<float> takeRank( RankedGraph ranked, RankDisclosure& disclosureOut ) +inline std::vector<float> biasPrior( const Graph& g, const std::vector<float>& p ) +inline std::vector<float> churnTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, const char* since = "18 months ago& … [line truncated: 20 more bytes on this line] +inline ChurnRanking churnDecayRanking( const MainDispatch& d, const rw::SinceScope& sinceScope, bool isScoped, const char* verbLabel ) +inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double> rank… -inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = n… +inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = n… inline std::string didYouMean( const IngestResult& ing, std::string_view name ) PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span< … [line truncated: 45 more bytes on this line] inline const McpIndex& getIndex( const std::string& root ) -inline std::vector<float> churnDecayTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, bool* outHasChurnEvidence = n … [line truncated: 12 more bytes on this line] -struct DataSectionsCut +inline std::vector<float> churnDecayTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, bool* outHasChurnEvidence = n … [line truncated: 12 more bytes on this line] +struct DataSectionsCut inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) -inline std::vector<float> anchoredLexicalRank( const Graph& g, const std::vector<float>& lex ) -### Fixed — `rank_by` over MCP no longer answers a different ranking than `--rank-by` on … [line truncated: 35 more bytes on this line] +inline std::vector<float> anchoredLexicalRank( const Graph& g, const std::vector<float>& lex ) +### Fixed — `rank_by` over MCP no longer answers a different ranking than `--rank-by` on … [line truncated: 35 more bytes on this line] ### 2. Gates for A -inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 +inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 #### The convergence disclosure contract -… [149 more display lines; full output is 24027 bytes on 72 raw line(s)] +… [149 more display lines; full output is 24042 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.54s** +**wall time: 1.15s** ````` - + - + 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` @@ -377,10 +363,10 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r *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: 5.08s** +**wall time: 1.14s** ````` - + @@ -390,7 +376,7 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r 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 @@ -405,22 +391,22 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r inline bool hasMainGuard( std::string_view source ) SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) 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 ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) enum class DisclosureWhy : std::uint8_t struct LintRule -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) inline bool hasPytestTable( std::string_view source ) -… [137 more display lines; full output is 19675 bytes on 44 raw line(s)] +… [137 more display lines; full output is 19683 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: 4.89s** +**wall time: 1.22s** ````` - @@ -432,20 +418,20 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r 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::string candidatesRootTag( std::size_t keep, std::size_t corpusCount, const CandidateProvenance& prov ) inline std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) -inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) -struct NameCorpusStats +struct NameCorpusStats +inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) inline bool hasIdentifierShape( std::string_view w ) noexcept struct LensRanking struct Bm25Params inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept -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 +rw::LensRanking computeLensRanking( const MainDispatch& d, std::string_view task, bool compactCandidate = false, bool fullDistribution = false ) // deep-tail: the file-grain tail rea … [line truncated: 65 more bytes on this line] +struct CandidateProvenance inline constexpr int kLexWeightDoc = 2 -std::optional<int> runEvalViews( const MainDispatch& d ) +std::optional<int> runEvalViews( const MainDispatch& d ) 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 ) struct RecallSectionPick @@ -457,10 +443,10 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r *Same task with the anchor disabled — the contrast the flag exists for.* -**wall time: 4.20s** +**wall time: 1.21s** ````` - @@ -471,21 +457,21 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r 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::string candidatesRootTag( std::size_t keep, std::size_t corpusCount, const CandidateProvenance& prov ) inline std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) -inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) -struct NameCorpusStats +struct NameCorpusStats +inline int runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) inline bool hasIdentifierShape( std::string_view w ) noexcept struct LensRanking struct Bm25Params inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) inline double bm25ImpactBound( double idf, double T, const Bm25Params& p ) noexcept -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 +rw::LensRanking computeLensRanking( const MainDispatch& d, std::string_view task, bool compactCandidate = false, bool fullDistribution = false ) // deep-tail: the file-grain tail rea … [line truncated: 65 more bytes on this line] +struct CandidateProvenance inline constexpr int kLexWeightDoc = 2 -std::optional<int> runEvalViews( const MainDispatch& d ) +std::optional<int> runEvalViews( const MainDispatch& d ) 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 ) struct RecallSectionPick @@ -497,12 +483,10 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r *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.26s** - ````` - + @@ -515,12 +499,10 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r *The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* -**wall time: 5.56s** - ````` - + f, A&&... a ) @@ -542,8 +524,6 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r *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: 3.74s** - ````` @@ -558,8 +538,6 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r *The honest half of the contract: a task with no ripwire-shaped evidence ABSTAINS with zero commands rather than guessing.* -**wall time: 3.03s** - ````` @@ -571,12 +549,10 @@ ripwire: cache : over its 2048 MiB budget — evicted 36 blob(s) of other r *Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* -**wall time: 1.49s** - ````` -ripwire recall — "quality delta gating exit codes" — 114 relevant of 221 document files, best-first — total=114 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2157 est_tokens=4593 +ripwire recall — "quality delta gating exit codes" — 114 relevant of 230 document files, best-first — total=114 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2157 est_tokens=4593 -━━ README.md (relevance 8.061) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 222662 B; lines="2747-2759,2760-2767"; dropped_by_budget=23] +━━ README.md (relevance 8.053) ━━ [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. @@ -600,7 +576,7 @@ refuses by default. -━━ CHANGELOG.md (relevance 7.542) ━━ [sections: 1 of 109 selected (450 in doc), section-granular; whole doc 995204 B; lines="542-557"; dropped_by_budget=108] [truncated: 1597 of 3826 bytes] +━━ CHANGELOG.md (relevance 7.541) ━━ [sections: 1 of 109 selected (451 in doc), section-granular; whole doc 997708 B; lines="554-569"; dropped_by_budget=108] [truncated: 1597 of 3826 bytes] ### Changed — `--quality-delta=A..B` answers the kinds it can under memory pressure, and exits 6 when that answer has no gating row (#387 follow-up) A tripped memory guard in the range form used to give exit 5 and nothing, so on a large repository a four-file change @@ -609,17 +585,38 @@ A tripped memory guard in the range form used to give exit 5 and nothing, so on ## `./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 1377030-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 1362493-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.114) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 588930 B; lines="3838-3842"; dropped_by_budget=163] [truncated: 1865 of 7739 bytes] -### `--field-affinity[=STRUCT]` +━━ commands.md (relevance 11.005) ━━ [sections: 1 of 164 selected (192 in doc), section-granular; whole doc 588880 B; lines="556-581"; dropped_by_budget=163] [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)) ````` @@ -634,11 +631,9 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *File-by-file orientation map (top symbols per file).* -**wall time: 2.25s** - ````` - + @@ -667,14 +662,14 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas -… [368 more display lines; full output is 11214 bytes on 1 raw line(s)] +… [368 more display lines; full output is 11234 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --html=/aux/map2.html` *Self-contained HTML force-directed call graph.* -**wall time: 4.22s** +**wall time: 1.76s** ````` (empty) @@ -683,15 +678,13 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas Artifact written: ````` - 156619 /aux/map2.html + 156578 /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.51s** - ````` @@ -712,7 +705,7 @@ Artifact written: - + @@ -723,7 +716,7 @@ Artifact written: -… [58 more display lines; full output is 8108 bytes on 1 raw line(s)] +… [58 more display lines; full output is 8117 bytes on 1 raw line(s)] ````` @@ -735,14 +728,12 @@ 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.06s** - ````` - - + + @@ -768,21 +759,19 @@ Artifact written: -… [160 more display lines; full output is 7666 bytes on 1 raw line(s)] +… [163 more display lines; full output is 7764 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.* -**wall time: 1.96s** - ````` - - + + @@ -808,20 +797,18 @@ Artifact written: -… [1928 more display lines; full output is 52641 bytes on 1 raw line(s)] +… [1919 more display lines; full output is 52380 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.* -**wall time: 2.52s** - ````` - + @@ -864,14 +851,12 @@ Artifact written: *Who calls SYM (1-hop in-edges).* -**wall time: 2.32s** - ````` - + - - + + @@ -883,15 +868,13 @@ 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.55s** - ````` - + - - + + @@ -903,7 +886,7 @@ Artifact written: *Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* -**exit code: 1** — **wall time: 2.89s** +**exit code: 1** ````` @@ -920,22 +903,20 @@ ripwire: --callers symbol not found: DoesNotExist *What SYM calls (1-hop out-edges).* -**wall time: 1.71s** - ````` - - + + - - - - - - + + + + + + ````` @@ -943,14 +924,12 @@ 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.44s** - ````` - + - - + + @@ -964,79 +943,75 @@ ripwire: --callers symbol not found: DoesNotExist *Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* -**wall time: 2.10s** - ````` - + - + - + - + - + - + - - - - - - - - - - -… [28 more display lines; full output is 3649 bytes on 1 raw line(s)] + + + + + + + + + + +… [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.54s** - ````` - - + + - - - - + + + + - + - - + + - + - - + + - + … [73 more display lines; full output is 5588 bytes on 1 raw line(s)] ````` @@ -1044,56 +1019,52 @@ 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: 1.66s** - ````` - - - - - + + + + + - - - - - - + + + + + + - + - - + + - -… [73 more display lines; full output is 5451 bytes on 1 raw line(s)] + +… [73 more display lines; full output is 5454 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").* -**wall time: 1.04s** - ````` - + - + ````` @@ -1101,14 +1072,12 @@ ripwire: --callers symbol not found: DoesNotExist *Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* -**wall time: 1.18s** - ````` - + - + @@ -1120,8 +1089,6 @@ ripwire: --callers symbol not found: DoesNotExist *Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* -**wall time: 1.36s** - ````` - - + + - + -… [69 more display lines; full output is 6413 bytes on 1 raw line(s)] +… [69 more display lines; full output is 6414 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --mentions=rankGraphTeleport` *Markdown docs that name SYM in a backtick (doc<->code edges).* -**wall time: 1.22s** - ````` @@ -1179,11 +1144,9 @@ ripwire: --callers symbol not found: DoesNotExist *Test files that transitively reach the changed file.* -**wall time: 1.47s** - ````` - @@ -1198,12 +1161,10 @@ 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.31s** - ````` ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: 24bcbf89f +at: 9b86a581e (0 changed files — working tree is clean, nothing to analyze) ````` @@ -1211,11 +1172,11 @@ at: 24bcbf89f *Pre-PR gate on a CLEAN tree: no obligations, exit 0.* -**wall time: 5.82s** +**wall time: 2.72s** ````` - ````` @@ -1223,12 +1184,10 @@ at: 24bcbf89f *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.42s** - ````` @@ -1406,11 +1359,11 @@ at: 24bcbf89f *The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* -**wall time: 1.93s** +**wall time: 1.03s** ````` -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 @@ -1446,20 +1399,18 @@ at: 24bcbf89f *Raw BM25 ranking (debug lens; --for is the real verb).* -**wall time: 2.08s** - ````` - + - + - + @@ -1470,12 +1421,12 @@ at: 24bcbf89f - + - + @@ -1491,10 +1442,10 @@ at: 24bcbf89f *Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* -**wall time: 4.16s** +**wall time: 1.19s** ````` - + @@ -1504,42 +1455,40 @@ at: 24bcbf89f 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::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 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] inline std::string powerShellSingleQuote( std::string_view s ) noexcept inline std::string path_prepend_hint( std::string_view dir ) handle_t leftmost_leaf() const -inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) +inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) inline std::string doctorWhichRipwire( rw::os::stat_t& st ) inline std::string doctorPathPrependLine( const std::string& selfPath ) std::uint32_t testIterationCeiling() noexcept -inline const char* rankByLegendFor( const char* label ) noexcept -… [156 more display lines; full output is 22214 bytes on 72 raw line(s)] +inline const char* rankByLegendFor( const char* label ) noexcept +… [156 more display lines; full output is 22218 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: 3.06s** - ````` - - + + @@ -1555,7 +1504,7 @@ at: 24bcbf89f - + @@ -1564,21 +1513,19 @@ at: 24bcbf89f -… [146 more display lines; full output is 14816 bytes on 1 raw line(s)] +… [145 more display lines; full output is 15018 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: 2.22s** - ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1602,14 +1549,12 @@ at: 24bcbf89f *CHANGED: a line range on --outline is now STRIPPED with a stderr note (it used to refuse).* -**wall time: 2.44s** - ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1639,14 +1584,12 @@ ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — *Full body + inline callee signatures.* -**wall time: 2.27s** - ````` - + - + : files the ranked rows show / do not (sum = files=), each named or counted below. : a program entry by its language's convention (C/Go/Rust main, Java/C# static main, a script main its module scope calls, __main__.py), callees as c rows; manifest entries not read. : n= unshown files under dir p=; f= names its code files, the rest (tests, docs, config, test/doc/example dirs) counted. declined=K: K call sites without edges, including inert Elixir quote sites after unquote/unquote_splicing re-entry. external=K: K calls proven outside the tree, no edge. locality_pinned=K: K calls pinned by locality alone (a guess). extent_suspect_syms=K: K defs failed containment, corpus-wide. macro_blanked_files=K: K files indexed from a macro-blanked re-parse. prov=scip|binding|import|split|final-segment: how that edge bound (absent: one unique name); split = one arm of an amb= pick; final-segment = a qualified type matched by last name only. files=/symbols=: files and symbols indexed; edges= distinct call edges; shown= symbols printed, a merged row counting each def; ambiguous= calls split over several defs, corpus-wide; unresolved= calls with in-tree evidence and no edge (every def language-filtered or unreachable, or binding refused); order= rows by rank (important-first, important-last; (auto:fill) = flipped past a size threshold) or by path (stable). skipped_oversize=K: K files over a size ceiling, not indexed. unindexed=ext:N: N text files of that extension no grammar reads (6 extensions at most). unindexed_exts=E: E such extensions in all, the list cut. : a non-test row an indexed test transitively reaches (absent otherwise, never 0). : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. sc=: enclosing scope; the full id is p::sc::n (p= of the row or its ) and selectors take it. amb=K: K calls split over several defs. locals_floor=1: locals= is a floor. role=hub: in= is 8 or more. s in= out= cx= ccx= loc= params= nest= cbo= amp=: callers, callees, cyclomatic/cognitive complexity, lines, parameters, nesting depth, coupled types, callers + co-changed files. ev=/ev_why=: essential complexity (2+: jumps block extract-method; absent: 1) / the jumps behind it, tag:count. ev_floor=1: ev= is a FLOOR; noreturn calls, macro-hidden exits and unresolved gotos are unseen. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. l=N: start line; only on a same-name overload's row, one row per body (bodyless decls fold into overloads=). --> - - + + @@ -1866,26 +1799,24 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + -… [89 more display lines; full output is 10639 bytes on 1 raw line(s)] +… [89 more display lines; full output is 10648 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --deps` *File->file dependency graph (god-files, cycles).* -**wall time: 1.29s** - ````` - - - + + + @@ -1899,16 +1830,16 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - + - + - + @@ -1919,30 +1850,30 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Complexity x recent git churn (maintenance pain).* -**wall time: 6.66s** +**wall time: 1.84s** ````` - - - - - - + + + + + + - - + + - - + + - + @@ -1959,21 +1890,21 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Token-normalized duplicate bodies.* -**wall time: 3.72s** +**wall time: 2.18s** ````` - + - + - + @@ -1984,56 +1915,56 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - + -… [307 more display lines; full output is 16168 bytes on 1 raw line(s)] +… [308 more display lines; full output is 16216 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --cochange` *Files that change together in git (hidden coupling).* -**wall time: 5.20s** +**wall time: 1.73s** ````` - - - - + - - + + + + - + + + - - + - + - - - + + + - + ````` @@ -2041,52 +1972,50 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Hotspots scoped to RECENT churn (the regression lens).* -**wall time: 25.84s** +**wall time: 1.67s** ````` - - - - - + + + + + - - + + - - + + - + + - - - + + + - -… [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: 3.79s** - ````` - @@ -2114,18 +2043,18 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan -… [805 more display lines; full output is 117170 bytes on 1 raw line(s)] +… [809 more display lines; full output is 117717 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint` *Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* -**wall time: 5.98s** +**wall time: 1.75s** ````` - + @@ -2134,10 +2063,10 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - + @@ -2148,21 +2077,19 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - -… [684 more display lines; full output is 70577 bytes on 1 raw line(s)] + +… [685 more display lines; full output is 70655 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: 9.22s** - ````` @@ -2241,174 +2168,164 @@ The joined finding — past the display cut above, extracted so the join is visi *Cluster the call graph into cohesive modules.* -**wall time: 3.32s** - ````` - + - + - + - + - + - + -… [189 more display lines; full output is 16813 bytes on 1 raw line(s)] +… [189 more display lines; full output is 16880 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: 3.98s** - ````` - - - - + + + - - + + - - + + - - - - - + + - - + + -… [135 more display lines; full output is 7287 bytes on 1 raw line(s)] + + + + +… [135 more display lines; full output is 7371 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: 3.72s** - ````` - - - + + + - + - + - + - + - + - + - + - + - + - + - + - + -… [265 more display lines; full output is 12022 bytes on 1 raw line(s)] +… [263 more display lines; full output is 12069 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --report` *Architecture summary (modules, god-files, cycles) as markdown.* -**wall time: 3.28s** - ````` # ripwire architecture report -3090 files · 29477 symbols · 28529 edges · 3474 modules (13227 call-graph isolated) +3102 files · 29592 symbols · 28717 edges · 3499 modules (13251 call-graph isolated) Root: `.` -Call-graph isolate provenance: 3604 declaration, 2112 header, 2755 source, 4756 document; 0 connected Louvain singletons +Call-graph isolate provenance: 3612 declaration, 2119 header, 2754 source, 4766 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 3474) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1488 symbols -- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 496 symbols -- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 335 symbols -- **docs::`--top-k=N`@COMMANDS.md:59:9077 [pack,allow,insert]** — 182 symbols +## Modules (call-graph clusters; showing 12 of 3499) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1517 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 497 symbols +- **src::DISCLOSE@infra/Diagnostics.h:616:43369 [read,compute,resolve]** — 333 symbols +- **docs::`--top-k=N`@COMMANDS.md:59:9077 [pack,allow,insert]** — 181 symbols - **test/ownerhopfix/py/pkg::big_handler_001@broadcast.py:3:72** — 106 symbols - **src/infra::fail@os_win32.cpp:63:2553 [open,get,close]** — 89 symbols - **test::no@estchargecheck.sh:52:4108 [skip,set]** — 63 symbols -- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,compute,expand]** — 53 symbols +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,compute,expand]** — 55 symbols - **src::sliceKindIs@slice.h:368:24239 [match,collect,push]** — 48 symbols - **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [insert,erase,find]** — 44 symbols - **src::child@ingest_valuerefs.h:387:20429 [emit,open]** — 44 symbols - **src::tokText@defectshape.h:128:5308 [scan,find,join]** — 41 symbols -## God files (most depended-on; showing 10 of 221) +## God files (most depended-on; showing 10 of 222) - `src/model.h` — 97 dependents - `src/infra/emit.h` — 87 dependents - `src/infra/Diagnostics.h` — 63 dependents - `src/serialize.h` — 43 dependents - `src/graph.h` — 42 dependents -… [35 more lines, 3956 bytes total] +… [35 more lines, 3954 bytes total] ````` ## `./build/ripwire . --seams` *Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* -**wall time: 1.54s** - ````` - - + + - + @@ -2434,37 +2351,35 @@ Call-graph isolate provenance: 3604 declaration, 2112 header, 2755 source, 4756 -… [60 more display lines; full output is 9252 bytes on 1 raw line(s)] +… [60 more display lines; full output is 9253 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --mermaid` *Module (directory) dependency graph as a Mermaid diagram.* -**wall time: 2.16s** - ````` %% ripwire --mermaid: module (directory) dependency graph — node = dir (symbol count), edge = inter-module calls (>= 3). Render at mermaid.live. flowchart LR subgraph sg0 ["src"] - n101["src
8841"] - n102["src/infra
1294"] + n101["src
8906"] + n102["src/infra
1296"] end subgraph sg1 ["test"] - n103["test
5405"] + n103["test
5412"] n483["test/legochargefix
546"] n394["test/fixtures/recallpassage
314"] - n564["test/ownerhopfix/py/pkg
158"] + n568["test/ownerhopfix/py/pkg
158"] n338["test/expandmodefix
151"] n340["test/expandsibsfix
149"] - n705["test/rubyschemafix
99"] + n709["test/rubyschemafix
99"] n413["test/forbodyfoldfix/py
98"] - n687["test/rubyattrsfix
85"] + n691["test/rubyattrsfix
85"] n540["test/massfix
77"] end n73["docs
847"] n2[".github
621"] - n0["(root)
605"] + n0["(root)
606"] subgraph sg5 ["bench"] n4["bench
545"] n5["bench/agentloop
331"] @@ -2474,28 +2389,28 @@ flowchart LR n43["bench/locbench
107"] n38["bench/headtohead/r3-headroom-2026-08-03
102"] n65["bench/skillrater/results/2026-09-07/arms
100"] -… [18 more lines, 1556 bytes total] +… [19 more lines, 1572 bytes total] ````` ## `./build/ripwire . --owners` *Bus-factor: recency-weighted author ownership per file.* -**wall time: 6.94s** +**wall time: 1.70s** ````` - - + + - + - + @@ -2514,18 +2429,16 @@ flowchart LR -… [934 more display lines; full output is 102054 bytes on 1 raw line(s)] +… [941 more display lines; full output is 102656 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.59s** - ````` - + ````` @@ -2534,8 +2447,6 @@ flowchart LR *Which symbols a TEST FILE exercises — the reverse direction of --affected.* -**wall time: 1.56s** - `````
````` @@ -2560,11 +2469,11 @@ flowchart LR *On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* -**wall time: 35.72s** +**wall time: 17.11s** ````` - ````` @@ -2578,11 +2487,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: 24.81s** +**wall time: 13.25s** ````` - @@ -2611,7 +2520,7 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g -… [135 more display lines; full output is 15906 bytes on 1 raw line(s)] +… [137 more display lines; full output is 16053 bytes on 1 raw line(s)] ````` stderr: @@ -2624,14 +2533,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: 18.19s** +**wall time: 9.23s** ````` - - - + + @@ -2643,11 +2552,9 @@ 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.45s** - ````` - ````` @@ -2656,55 +2563,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: 8.88s** +**wall time: 2.92s** ````` - - + - + - + - + - + + - - + - - - + + + -… [32 more display lines; full output is 5917 bytes on 1 raw line(s)] +… [34 more display lines; full output is 6038 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: 23.68s** +**wall time: 11.26s** ````` - - + + - + @@ -2716,11 +2623,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: 22.17s** +**wall time: 13.87s** ````` - + @@ -2749,32 +2656,32 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g -… [212 more display lines; full output is 28603 bytes on 1 raw line(s)] +… [239 more display lines; full output is 31494 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --stray-content=arm/for-howitworks-067-f2add` *A second selection, the one branch `arm/for-howitworks-067-f2add` (no ref family left to select on this checkout), picked at capture time from the refs this checkout really has and never the selection above: merged refs are OMITTED from the rows and counted in merged=; refs sharing no merge base with HEAD (a shallow clone, or a pre-rewrite history) land in unknown= with ok="0" — the counters always reconcile against refs=.* -**wall time: 3.63s** +**wall time: 1.57s** ````` - - + + - + - + ````` @@ -2783,27 +2690,29 @@ 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: 114.44s** +**wall time: 70.46s** ````` - + - + + - + + + - - + @@ -2814,16 +2723,20 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g - - -… [33520 more display lines; full output is 4640484 bytes on 1 raw line(s)] +… [32411 more display lines; full output is 4509091 bytes on 1 raw line(s)] +````` + +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 . --stray-content=zzzz-no-such-ref --plan` *A --plan filter that selects NO ref REFUSES (exit 1) naming the substring — before the wave-3 close this fell through to the '>512 refs match' sentence, and --abi under the same filter answered an empty measurement at exit 0.* -**exit code: 1** — **wall time: 3.02s** +**exit code: 1** — **wall time: 1.42s** ````` (empty) @@ -2840,18 +2753,29 @@ 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: 133.36s** +**exit code: 2** — **wall time: 46.14s** ````` - - + + + + + + + + + + + + + - - + + @@ -2860,40 +2784,30 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - - - - - - - - - - - - - + + +… [13 more display lines; full output is 3078 bytes on 1 raw line(s)] ````` ## `./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: 12.53s** +**wall time: 6.82s** ````` - - - - - + + + + + - + - + @@ -2901,7 +2815,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - + ````` @@ -2909,15 +2823,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: 11.08s** +**wall time: 6.82s** ````` - - + + - - + + - - - - - - - + + + + + + + @@ -2940,20 +2854,20 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - - -… [36 more display lines; full output is 13356 bytes on 1 raw line(s)] + + +… [36 more display lines; full output is 13361 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --quality-delta=HEAD~1..HEAD --quality-kinds=complexity,dead-code` *A ref range run for two of the twelve kinds only: the named kinds' rows are exactly their rows in the full answer, the header names the others (kinds_skipped=, skipped_why="selector"), and a pass no named kind needs does not run. The replay a memory-stopped range answer hands back (kinds_next=) is this flag. Refused without --quality-delta=A..B.* -**wall time: 14.93s** +**wall time: 8.38s** ````` - - - - + + + - - - - - - - + + + + + + + @@ -2991,25 +2905,25 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - - + + -… [35 more display lines; full output is 14369 bytes on 1 raw line(s)] +… [35 more display lines; full output is 14374 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: 17.21s** +**wall time: 12.51s** ````` - - + + - - + + ````` @@ -3017,12 +2931,10 @@ 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.05s** - ````` - + @@ -3059,7 +2971,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer ````` - + @@ -3110,10 +3022,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: 2.10s** +**wall time: 1.80s** ````` -{"v":1,"verb":"plan-lanes","at":"24bcbf89f","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":3090,"symbols":29477,"edges":285 … [line truncated: 338 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"9b86a581e","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":3102,"symbols":29592,"edges":287 … [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"}, @@ -3126,31 +3038,31 @@ ripwire: run `ripwire . --flags` for the gate table {"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":1344,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":48,"tested":0}, -{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":3031,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":11,"churn":448,"tested":0}, +{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":3031,"ord":0,"overloads":1,"amb":0,"cx":8,"ccx":11,"churn":451,"tested":0}, {"p":"./src/pageview.h","n":"secondaryCutAttrs","scope":"rw","key":"a10ea8d3bdca1dfb","id":"./src/pageview.h::rw::secondaryCutAttrs","id_addressable":true,"id_collides_with":0,"l":399,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":28,"tested":0}, -{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":691,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":448,"tested":0}, +{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":691,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":451,"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":5711,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":428,"tested":0}, {"p":"./src/wrap.h","n":"wrapEmitCliFirst","scope":"rw","key":"f5ef05d9574d9516","id":"./src/wrap.h::rw::wrapEmitCliFirst","id_addressable":true,"id_collides_with":0,"l":699,"ord":0,"overloads":1,"amb":7,"cx":10,"ccx":5,"churn":52,"tested":0}], -"files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":51}, +"files":[{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":52}, {"p":"./src/docdrift.h","symbols":4,"churn":36,"ccx":14,"hotspot_rank":35}, {"p":"./src/editcheck.h","symbols":1,"churn":55,"ccx":10,"hotspot_rank":41}, -{"p":"./src/main.cpp","symbols":2,"churn":448,"ccx":12,"hotspot_rank":4}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":428,"ccx":2,"hotspot_rank":5}, -{"p":"./src/pageview.h","symbols":1,"churn":28,"ccx":2,"hotspot_rank":126}, +{"p":"./src/main.cpp","symbols":2,"churn":451,"ccx":12,"hotspot_rank":4}, +{"p":"./src/pageview.h","symbols":1,"churn":28,"ccx":2,"hotspot_rank":127}, {"p":"./src/testmap.h","symbols":1,"churn":48,"ccx":4,"hotspot_rank":39}, {"p":"./src/verbs_change.h","symbols":1,"churn":80,"ccx":9,"hotspot_rank":23}, {"p":"./src/wrap.h","symbols":1,"churn":52,"ccx":5,"hotspot_rank":66}]},"blast_radius":{"reaches":49,"files_total":17,"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","./src … [line truncated: 189 more bytes on this line] "tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":49,"module_span":1,"notes":[], -"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"high","rule":"complex-or-wide","basis":"structural-only","signals":{"claims":13,"files":9,"module_span":1,"max_ccx":11,"sum_ccx":66,"ambiguous_calls":13,"blast_reaches":49,"contract_touches":0,"conflicts":0,"untested":49,"tests … [line truncated: 135 more bytes on this line] -… [66 more display lines; full output is 19957 bytes on 1 raw line(s)] +"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"high","rule":"complex-or-wide","basis":"structural-only","signals":{"claims":12,"files":8,"module_span":1,"max_ccx":11,"sum_ccx":64,"ambiguous_calls":13,"blast_reaches":49,"contract_touches":0,"conflicts":0,"untested":49,"tests … [line truncated: 135 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":"kViewFlags","scope":"rw","key":"2a2f2487082cc6d8","id":"./src/cli.h::rw::kViewFlags","id_addressable":true,"id_collides_with":0,"l":3326,"ord":0,"overloads":1,"amb" … [line truncated: 42 more bytes on this line] +{"p":"./src/main.cpp","n":"kJsonRideAlongFlags","scope":"","key":"2a90806fa5a580c1","id":null,"id_addressable":false,"id_collides_with":0,"l":3243,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":451,"tested":0}, +… [72 more display lines; full output is 20559 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: 2.47s** +**wall time: 1.80s** Input file: @@ -3161,10 +3073,10 @@ write regression tests for the new filter ````` ````` -{"v":1,"verb":"plan-lanes","at":"24bcbf89f","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3090,"symbols":29477,"edges":28529,"ambiguous":2904,"unresolved":14037},"carve":null,"core":{"files" … [line truncated: 4 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"9b86a581e","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":3102,"symbols":29592,"edges":28717,"ambiguous":2911,"unresolved":14058},"carve":null,"core":{"files" … [line truncated: 4 more bytes on this line] "symbols":[]},"lanes":[{"id":"lane-0","task":"add a --since filter to the doc-drift verb","claims":{"symbols":[{"p":"./src/docdrift.h","n":"recordUnchecked","scope":"docdrift","key":"12641dab14abc8fd","id":"./src/docdrift.h::docdrift::recordUnchecked","id_addressable":true,"id_collides_with":0,"l":2 … [line truncated: 72 more bytes on this line] {"p":"./src/docdrift.h","n":"sortWeakGroupsByPath","scope":"docdrift","key":"1944ba506280ff80","id":"./src/docdrift.h::docdrift::sortWeakGroupsByPath","id_addressable":true,"id_collides_with":0,"l":2401,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":36,"tested":0}, -{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","key":"1fa68e8d93c05a59","id":"./src/mcpverbs.h::rw::docDriftText","id_addressable":true,"id_collides_with":0,"l":655,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":428,"tested":0}, +{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","key":"1fa68e8d93c05a59","id":"./src/mcpverbs.h::rw::docDriftText","id_addressable":true,"id_collides_with":0,"l":655,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":435,"tested":0}, {"p":"./src/cli.h","n":"kViewFlags","scope":"rw","key":"2a2f2487082cc6d8","id":"./src/cli.h::rw::kViewFlags","id_addressable":true,"id_collides_with":0,"l":3326,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":508,"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}, @@ -3172,17 +3084,17 @@ write regression tests for the new filter {"p":"./src/docdrift.h","n":"DriftResult","scope":"DriftResult","key":"422ab39546b6e0bd","id":"./src/docdrift.h::DriftResult::DriftResult","id_addressable":true,"id_collides_with":0,"l":286,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"tested":0}, {"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","key":"904831e5c0f66b39","id":null,"id_addressable":false,"id_collides_with":0,"l":1678,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":80,"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":5711,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":428,"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":5758,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":435,"tested":0}, {"p":"./src/docdrift.h","n":"kDocDriftLegend","scope":"docdrift","key":"ff2ba74637bbbebf","id":"./src/docdrift.h::docdrift::kDocDriftLegend","id_addressable":true,"id_collides_with":0,"l":2739,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"tested":0}], "files":[{"p":"./src/cli.h","symbols":1,"churn":508,"ccx":0,"hotspot_rank":6}, {"p":"./src/docdrift.h","symbols":6,"churn":36,"ccx":54,"hotspot_rank":35}, {"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":103}, -{"p":"./src/mcpverbs.h","symbols":2,"churn":428,"ccx":2,"hotspot_rank":5}, +{"p":"./src/mcpverbs.h","symbols":2,"churn":435,"ccx":2,"hotspot_rank":5}, {"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":63}, {"p":"./src/verbs_change.h","symbols":1,"churn":80,"ccx":4,"hotspot_rank":23}], -"symbols_total":3359,"symbols_capped":true},"blast_radius":{"reaches":13,"files_total":7,"capped":false,"files":["./src/docdrift.h","./src/main.cpp","./src/mcp.h","./src/mcpserver.h","./src/mcpverbs.h","./src/recall.h","./src/verbs_for.h"]},"tests_to_run":[], -"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":13,"module_span":6,"notes":[], -"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":12,"files":6,"module_span":6,"max_ccx":38,"sum_ccx":64,"ambiguous_calls":12,"blast_reaches":13,"contract_touches":1,"conflicts":1,"untested":13,"t … [line truncated: 139 more bytes on this line] +"symbols_total":3358,"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":7,"notes":[], +"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":12,"files":6,"module_span":7,"max_ccx":38,"sum_ccx":64,"ambiguous_calls":12,"blast_reaches":13,"contract_touches":1,"conflicts":1,"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":124,"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":2947,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":508,"tested":0}, {"p":"./src/cli.h","n":"kViewFlags","scope":"rw","key":"2a2f2487082cc6d8","id":"./src/cli.h::rw::kViewFlags","id_addressable":true,"id_collides_with":0,"l":3326,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":508,"tested":0}, @@ -3216,7 +3128,7 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out ````` - + @@ -3270,80 +3182,76 @@ 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.22s** - ````` - +
- - + + - - - + + + - - + + - - - + + + - - - -… [226 more display lines; full output is 23768 bytes on 1 raw line(s)] + + + +… [227 more display lines; full output is 23872 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.37s** - ````` - + - - + + - - - + + + - - + + - - - + + + - - - -… [252 more display lines; full output is 24966 bytes on 1 raw line(s)] + + + +… [253 more display lines; full output is 25070 bytes on 1 raw line(s)] ````` Tail of the same output — the `` section: @@ -3380,40 +3288,40 @@ Tail of the same output — the `` section: *Same report, with git history splitting stale mentions into deleted-by-commit vs never-existed.* -**wall time: 1.12s** +**wall time: 1.01s** ````` - - + + - - + + - - - + + + - - + + - - - + + + -… [206 more display lines; full output is 23747 bytes on 1 raw line(s)] +… [207 more display lines; full output is 23851 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --from-trace=-` @@ -3426,8 +3334,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:9673 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:9714 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:10126 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10167 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 #3 0x102e01a44 in main src/main.cpp:4174 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) @@ -3435,25 +3343,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)" ); @@ -3465,7 +3373,7 @@ AddressSanitizer:DEADLYSIGNAL if( N ) { double teleportMass = 0.0; -… [18 more display lines; full output is 4908 bytes on 29 raw line(s)] +… [18 more display lines; full output is 4915 bytes on 29 raw line(s)] ````` ## `./build/ripwire . --notes` @@ -3496,7 +3404,7 @@ AddressSanitizer:DEADLYSIGNAL *ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* ````` - + + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept @@ -3514,7 +3422,7 @@ AddressSanitizer:DEADLYSIGNAL 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] F7: a hostile/garbled frame line number (e.g. a fuzzed or truncated trace) can exceed UINT32_MAX; unchecked `v*10+d` wraps mod 2^32 (4294967297 -> 1), which then confidently maps to a REAL line in theinline std::uint32_t toUint( std::string_view s, bool& overflowed ) noexcept @@ -3526,7 +3434,7 @@ AddressSanitizer:DEADLYSIGNAL -… [47 more display lines; full output is 9865 bytes on 38 raw line(s)] +… [47 more display lines; full output is 10054 bytes on 38 raw line(s)] ````` ## `./build/ripwire . --pack-task="add a new output format flag to the CLI" --partition=3` @@ -3534,11 +3442,11 @@ AddressSanitizer:DEADLYSIGNAL *Fan-out form: one shared core + 3 per-agent slices carved along call-graph communities.* ````` - 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 @@ -3564,17 +3472,17 @@ AddressSanitizer:DEADLYSIGNAL { post.insert( post.size() - 1, " " + std::string( toolsArg ) ); } -… [104 more display lines; full output is 24273 bytes on 111 raw line(s)] +… [104 more display lines; full output is 24321 bytes on 111 raw line(s)] ````` ## `./build/ripwire . --for="pagerank power iteration" --with-graph` *Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges.* -**wall time: 1.48s** +**wall time: 1.15s** ````` - + @@ -3584,34 +3492,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 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::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 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] inline std::string powerShellSingleQuote( std::string_view s ) noexcept inline std::string path_prepend_hint( std::string_view dir ) handle_t leftmost_leaf() const -inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) +inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) inline std::string doctorWhichRipwire( rw::os::stat_t& st ) inline std::string doctorPathPrependLine( const std::string& selfPath ) std::uint32_t testIterationCeiling() noexcept -inline const char* rankByLegendFor( const char* label ) noexcept -… [124 more display lines; full output is 18953 bytes on 10 raw line(s)] +inline const char* rankByLegendFor( const char* label ) noexcept +… [124 more display lines; full output is 18961 bytes on 10 raw line(s)] ````` ## `./build/ripwire . --export=cc.json:/aux/ripwire2.cc.json` *Per-file metrics as CodeCharta cc.json.* -**wall time: 1.61s** +**wall time: 1.40s** ````` (empty) @@ -3620,7 +3528,7 @@ AddressSanitizer:DEADLYSIGNAL Artifact written: ````` - 473799 /aux/ripwire2.cc.json + 475571 /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 ````` @@ -3628,7 +3536,7 @@ Artifact written: *One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* -**wall time: 4.24s** +**wall time: 3.31s** Input file: @@ -3643,7 +3551,7 @@ lego:Vehicle - + @@ -3656,21 +3564,27 @@ 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 -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… +struct FileHealth +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::ve … [line truncated: 8 more bytes on this line] +inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, const CacheLoadStats& cac … [line truncated: 66 more bytes on this line] inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) inline const McpIndex& getIndex( const std::string& root ) inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) struct McpIndex inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) -IngestResult ingest( const char* rootDir, const std::vector<std::string>& excludeSubstr, std::string_view cacheFile, std::size_t maxFileBytes, bool captureValueUses, std::string_view excludeLabel, bool … [line truncated: 37 more bytes on this line] -const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) +IngestResult ingest( const char* rootDir, const std::vector<std::string>& excludeSubstr, std::string_view cacheFile, std::size_t maxFileBytes, bool captureValueUses, std::string_view excludeLabel, bool … [line truncated: 37 more bytes on this line] +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) static bool cachePathIsDirectory( const std::string& cachePath ) inline std::string oracleCachePath( const std::string& root, const std::string& headSha ) inline constexpr std::array<std::string_view, 8> kCacheRuleNames = -… [106 more display lines; full output is 50521 bytes on 1 raw line(s)] +… [106 more display lines; full output is 50524 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: cache : over its 2048 MiB budget — evicted 94 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) ````` @@ -3682,18 +3596,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: 13.14s** +**exit code: 1** — **wall time: 5.19s** ````` - - + - - + + - - + @@ -3745,59 +3659,75 @@ 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: 4.18s** +**wall time: 1.70s** ````` - + - - + + - + - + - + - - - + + + ````` -## `./build/ripwire . --rank-by=churn-decay --since=HEAD~4 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3` +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~3 --exclude=test --exclude=docs --exclude=skills --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: 3.05s** +**wall time: 1.25s** ````` - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - + ````` @@ -3823,15 +3753,15 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf ````` - + 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,9712,10358,171,1425,1464,1751 +1184,10165,10811,171,1425,1464,1751 fn,fn,fn,fn,fn,fn,fn 0,0,0,0,0,0,0 -src/mcpindex.h:1288,src/graph.h:9715,src/graph.h:10409,src/eval.h:328,src/main.cpp:1439,src/main.cpp:1478 src/main.cpp:1479 src/main.cpp:1493,src/main.cpp:1871 +src/mcpindex.h:1288,src/graph.h:10168,src/graph.h:10862,src/eval.h:328,src/main.cpp:1439,src/main.cpp:1478 src/main.cpp:1479 src/main.cpp:1493,src/main.cpp:1871 ````` @@ -3840,24 +3770,24 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *Flat top-K export for an external reranker.* -**wall time: 2.14s** +**wall time: 1.14s** ````` - - + + 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 ) @@ -3884,9 +3814,9 @@ ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidate *Machine-parseable JSON, same content, keys mirror the XML attrs.* ````` -{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2904,"graph_unresolved":14037,"graph_unindexed":266,"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:9712","sites_at":["src/graph.h:9715"]}, -{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:10358","sites_at":["src/graph.h:10409"]}, +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":2911,"graph_unresolved":14058,"graph_unindexed":266,"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:10165","sites_at":["src/graph.h:10168"]}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:10811","sites_at":["src/graph.h:10862"]}, {"t":"fn","n":"runEval","p":"src/eval.h:171","sites_at":["src/eval.h:328"]}, {"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1425","sites_at":["src/main.cpp:1439"]}, {"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1464","sites_at":["src/main.cpp:1478","src/main.cpp:1479","src/main.cpp:1493"]}, @@ -3913,14 +3843,14 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Pagination: 3 items, skipping the first 3 (deterministic seams).* -**wall time: 2.69s** +**wall time: 1.69s** ````` - - - + + + ````` @@ -3930,10 +3860,10 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop test paths from the corpus before ranking.* ````` - + - - + + @@ -3946,11 +3876,12 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - - + + + - + @@ -3959,8 +3890,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - -… [58 more display lines; full output is 7949 bytes on 1 raw line(s)] +… [59 more display lines; full output is 7832 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --exclude=present --exclude=bench --top-k=5` @@ -3970,8 +3900,8 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m ````` - - + + @@ -3988,7 +3918,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - + @@ -3998,20 +3928,18 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m -… [56 more display lines; full output is 7928 bytes on 1 raw line(s)] +… [56 more display lines; full output is 7937 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --map-diff --top-k=5` *Full map re-ranked with teleport toward git-changed files — clean tree, so changed=0 and it degrades to the plain map.* -**wall time: 1.22s** - ````` - - + + @@ -4035,13 +3963,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: 2.49s** +**wall time: 1.84s** ````` - - + + @@ -4055,7 +3983,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - + @@ -4068,20 +3996,20 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m -… [55 more display lines; full output is 7825 bytes on 1 raw line(s)] +… [55 more display lines; full output is 7834 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: 2.80s** +**wall time: 1.93s** ````` - - + + @@ -4095,7 +4023,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m - + @@ -4108,13 +4036,13 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m -… [55 more display lines; full output is 7825 bytes on 1 raw line(s)] +… [55 more display lines; full output is 7834 bytes on 1 raw line(s)] ````` Artifact written: ````` - 26836629 /aux/warm2.ripwirecache + 26929702 /aux/warm2.ripwirecache ````` ## `./build/ripwire . --max-file-size=8K --top-k=3` @@ -4124,8 +4052,8 @@ Artifact written: ````` - - + + @@ -4138,7 +4066,7 @@ Artifact written: - + @@ -4152,7 +4080,7 @@ Artifact written: -… [17 more display lines; full output is 4215 bytes on 1 raw line(s)] +… [17 more display lines; full output is 4224 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --scip=does_not_exist.scip --callers=rankGraphTeleport` @@ -4175,12 +4103,12 @@ 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: 2.72s** +**wall time: 1.87s** ````` - + @@ -4193,10 +4121,10 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t - + - + @@ -4207,19 +4135,19 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Self-eval: co-change recall vs BM25.* -**wall time: 20.44s** +**wall time: 18.29s** ````` ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 3.8% 5.5% 10.3% - BM25 10.8% 13.6% 17.4% - BM25sub 17.0% 20.4% 29.9% - BM25body 10.2% 18.8% 30.6% - fused 7.4% 14.1% 26.1% - anchored 10.2% 18.8% 30.6% - same-dir 0.0% 6.4% 10.2% - random 0.2% 0.3% 0.6% <- floor (random ranking over F=3090 files) + ripwire 3.4% 6.4% 13.1% + BM25 10.4% 13.0% 13.8% + BM25sub 17.9% 22.3% 31.1% + BM25body 13.8% 20.2% 35.6% + fused 9.1% 18.8% 24.9% + anchored 13.8% 20.2% 35.6% + same-dir 0.0% 5.6% 8.8% + random 0.2% 0.3% 0.6% <- floor (random ranking over F=3102 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 / @@ -4235,24 +4163,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: 147.61s** +**wall time: 137.96s** ````` -ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=7465 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population +ripwire --eval-retrieval (known-item, 4002 doc-commented symbols; gold is in-corpus by construction) + sample: population=7485 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.680 54.6% 84.6% 89.9% - subtoken doc-phrase 0.908 88.8% 92.8% 93.8% - name-exact name 0.896 81.9% 95.8% 97.2% + subtoken name 0.680 54.6% 84.6% 90.0% + subtoken doc-phrase 0.909 88.9% 92.8% 93.8% + name-exact name 0.897 81.9% 95.9% 97.3% name-exact doc-phrase 0.014 0.6% 1.9% 2.9% - anchored name 0.684 55.9% 83.7% 88.5% - anchored doc-phrase 0.904 88.0% 92.7% 93.5% - routed name 0.898 82.0% 96.0% 97.4% + anchored name 0.685 56.0% 83.8% 88.5% + anchored doc-phrase 0.904 88.1% 92.8% 93.5% + routed name 0.899 82.1% 96.1% 97.5% routed doc-phrase 0.907 88.8% 92.6% 93.5% - note: routing chose name-exact on 3988/4000 NAME queries (a NAME query is always identifier-shaped); + note: routing chose name-exact on 3990/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 @@ -4264,7 +4192,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: 24.08s** +**exit code: 3** — **wall time: 18.15s** Input file: @@ -4374,7 +4302,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=24bcbf89f) +ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=9b86a581e) ````` @@ -4382,27 +4310,27 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=24bc # navigate — seeds, claims, slices, shapes -## `./build/ripwire . --at=src/graph.h:9673` +## `./build/ripwire . --at=src/graph.h:10126` *Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost.* ````` - - + + ````` -## `./build/ripwire . --callers=@src/graph.h:9673` +## `./build/ripwire . --callers=@src/graph.h:10126` *The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* ````` - + - - + + @@ -4423,7 +4351,7 @@ ripwire 0.6.8 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=24bc stderr: ````` -ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 13961 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 14462 lines — the seed asked for line 999999) ````` ## `./build/ripwire . --verify="calls(runDefaultMap, rankGraphTeleport)"` @@ -4432,9 +4360,9 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. ````` - + - + ````` @@ -4444,10 +4372,10 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. ````` - + - - + + @@ -4489,21 +4417,21 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ````` @@ -4513,17 +4441,17 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4535,26 +4463,26 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + - + pw = biasPrior( g, p );]]> - + & p, float alpha = 0.85f )]]> - + & p, float alpha = 0.85f )]]> @@ -4566,38 +4494,38 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + - + - + - + - + - + 0.0 )]]> - + - + @@ -4641,17 +4569,17 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4661,12 +4589,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: 1.31s** +**wall time: 1.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 ) ````` @@ -4674,7 +4602,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.29s** +**wall time: 1.17s** ````` @@ -4735,23 +4663,23 @@ ripwire: --pattern: this pattern collapses to a single bare token, which is a TE CDATA text (/ context around it); complete=1 only for an exhaustive literal scan; = off-index. window: shown= capped= (capped=1 cut). hits_capped=1: hits= is a floor. root=: p= relative to it. parse_degraded=1: ERROR nodes in that parse. next=: the one pasteable f … [line truncated: 1338 more bytes on this line] - + - - - - + + + + - + - - - + + + - + - + cache) failed — old cache preserved" );]]> @@ -4901,7 +4829,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? ````` @@ -4987,7 +4915,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 @@ -5002,22 +4930,22 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? inline bool hasMainGuard( std::string_view source ) SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) 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 ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) enum class DisclosureWhy : std::uint8_t struct LintRule -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) inline bool hasPytestTable( std::string_view source ) -… [137 more display lines; full output is 19560 bytes on 44 raw line(s)] +… [137 more display lines; full output is 19568 bytes on 44 raw line(s)] ````` ## `./build/ripwire . --for="tree-sitter parse of a source file" --sections=lego,compose` *Restore the / sections the --for bundle collapses to a counted stub by default (this is the stub's own next= spelling).* -**wall time: 1.20s** +**wall time: 1.16s** ````` - + @@ -5027,7 +4955,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 @@ -5042,22 +4970,22 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? inline bool hasMainGuard( std::string_view source ) SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) 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 ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) enum class DisclosureWhy : std::uint8_t struct LintRule -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) inline bool hasPytestTable( std::string_view source ) -… [148 more display lines; full output is 19893 bytes on 44 raw line(s)] +… [148 more display lines; full output is 19901 bytes on 44 raw line(s)] ````` ## `./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies` *Opt OUT of compact conceptual serving: restore the rank-first auto walk (bundle="auto").* -**wall time: 1.28s** +**wall time: 1.16s** ````` - + @@ -5067,7 +4995,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 @@ -5082,30 +5010,30 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? inline bool hasMainGuard( std::string_view source ) SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) 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 ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) enum class DisclosureWhy : std::uint8_t struct LintRule -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) inline bool hasPytestTable( std::string_view source ) -… [133 more display lines; full output is 20249 bytes on 55 raw line(s)] +… [133 more display lines; full output is 20257 bytes on 55 raw line(s)] ````` ## `./build/ripwire . --for="quality delta acks ledger rubber stamp"` *Doc-mention surfacing (default ON): a markdown doc naming a top-resolved symbol in a backtick rides in below that symbol — the legend's [doc mentions: …] clause says it fired.* -**wall time: 1.28s** +**wall time: 1.11s** ````` - + - + L2 stale acks in the range form. A changed-files answer holds no whole target tree to check the …std::vector<rw::quality::StaleAck> refPairStaleAcks( const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const RefPairDelta& refs ) -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) -inline const char* kAcksFile = ".ripwire_quality_acks" -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +inline const char* kAcksFile = ".ripwire_quality_acks" +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& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes … [line truncated: 5 more bytes on this line] inline const char* kNotesFile = ".ripwire_notes" @@ -5117,16 +5045,16 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? 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] DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -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 +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +inline std::string qualityAcksPath( const std::string& root ) +struct QualityDeltaOutcome std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -inline std::string gitHeadSha( const std::string& root ) -inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string gitHeadSha( const std::string& root ) +inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) … [192 more display lines; full output is 22804 bytes on 45 raw line(s)] ````` @@ -5134,18 +5062,18 @@ 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: 1.41s** +**wall time: 1.15s** ````` - + - + L2 stale acks in the range form. A changed-files answer holds no whole target tree to check the …std::vector<rw::quality::StaleAck> refPairStaleAcks( const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const RefPairDelta& refs ) -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) -inline const char* kAcksFile = ".ripwire_quality_acks" -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +inline const char* kAcksFile = ".ripwire_quality_acks" +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& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes … [line truncated: 5 more bytes on this line] inline const char* kNotesFile = ".ripwire_notes" @@ -5157,17 +5085,17 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? 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] DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -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 +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +inline std::string qualityAcksPath( const std::string& root ) +struct QualityDeltaOutcome std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -inline std::string gitHeadSha( const std::string& root ) -inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) -… [192 more display lines; full output is 22656 bytes on 45 raw line(s)] +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string gitHeadSha( const std::string& root ) +inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +… [192 more display lines; full output is 22624 bytes on 45 raw line(s)] ````` ## `./build/ripwire . --safe-delete=rankGraphTeleport` @@ -5176,13 +5104,13 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? ````` - - - + + - + ````` @@ -5207,22 +5135,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: 2.21s** +**wall time: 2.11s** ````` - + - + - - - + + + + ````` @@ -5231,19 +5159,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: 2.10s** +**wall time: 1.97s** ````` - - + + - + ````` @@ -5257,7 +5184,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist - @@ -5293,8 +5220,8 @@ ripwire: --safe-delete symbol not found: DoesNotExist ````` - - + + @@ -5308,7 +5235,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist - + @@ -5321,7 +5248,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist -… [55 more display lines; full output is 7825 bytes on 1 raw line(s)] +… [55 more display lines; full output is 7834 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --no-stable --top-k=3` @@ -5331,8 +5258,8 @@ ripwire: --safe-delete symbol not found: DoesNotExist ````` - - + + @@ -5346,7 +5273,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist - + @@ -5359,7 +5286,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist -… [55 more display lines; full output is 7825 bytes on 1 raw line(s)] +… [55 more display lines; full output is 7834 bytes on 1 raw line(s)] ````` stderr: @@ -5372,21 +5299,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.18s** +**exit code: 4** ````` - + - + const&, float) src/graph.h:9673 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:9714 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:10126 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:10167 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1753 #3 0x102e01a44 in main src/main.cpp:4174 - #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc)]]> pw = biasPrior( g, p ); @@ -5414,7 +5341,7 @@ 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_DESCR … [line truncated: 288 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"` @@ -5424,7 +5351,7 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o ````` - + ````` @@ -5432,12 +5359,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.69s** +**exit code: 4** — **wall time: 2.51s** ````` - + ````` @@ -5472,28 +5399,28 @@ 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: 8.39s** +**wall time: 4.38s** ````` - + - + - - + + - + - + @@ -5502,27 +5429,25 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + -… [188 more display lines; full output is 15723 bytes on 1 raw line(s)] +… [188 more display lines; full output is 15672 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --biggest-first --limit=8` *Per-function size ranking, largest Halstead volume/token entropy/lines first — a size proxy, not a readability order (withdrawn, docs/EVALS.md §8) — a RANKING lens, not a grade.* -**wall time: 1.73s** - ````` - - + + - + - + @@ -5532,11 +5457,9 @@ 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.30s** - ````` - + @@ -5552,51 +5475,49 @@ 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.15s** - ````` - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + - + - - - - - -… [21 more display lines; full output is 11234 bytes on 1 raw line(s)] + + + + + +… [21 more display lines; full output is 11235 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --nonlocal-state --limit=8` *Per function, the non-local MUTABLE state it can reach (transitively), most writes first — unsound by construction, and the legend says where.* -**wall time: 2.97s** +**wall time: 2.27s** ````` - @@ -5632,38 +5553,38 @@ 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: 4.45s** +**wall time: 2.44s** ````` - + - + - + - + - + - + - + - + - + … [36 more display lines; full output is 7488 bytes on 1 raw line(s)] ````` @@ -5672,11 +5593,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: 1.61s** +**wall time: 1.19s** ````` - @@ -5705,26 +5626,26 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir -… [507 more display lines; full output is 45435 bytes on 1 raw line(s)] +… [509 more display lines; full output is 45573 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --field-affinity=Symbol` *The same lens narrowed to ONE struct — the one --layout=Symbol shows the offsets for.* -**wall time: 1.49s** +**wall time: 1.24s** ````` - + - + - - - + + + @@ -5751,10 +5672,10 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir ````` - - - - + + + + @@ -5767,7 +5688,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + @@ -5787,11 +5708,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: 11.71s** +**wall time: 9.81s** ````` - + @@ -5813,22 +5734,22 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir