From 09ebd07012e94da2072062f84fe309e39b9eb63f Mon Sep 17 00:00:00 2001 From: Michal Papis Date: Fri, 25 Sep 2026 12:32:06 +0200 Subject: [PATCH 001/170] feat(ruby): db/schema.rb columns mint Section defs (Rails schema columns) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Rails schema is recognized BY CONTENT, never by path: a Ruby file whose tree holds a `create_table "x", … do |t| … end` call (the call's do_block carries one bare block parameter) is a rendered schema. Each `t. "name"` / `t. :name` in a table block mints ONE SymKind::Section def at Lang::Ruby — the data-kind slot a doc heading or YAML key already occupies, now with call edges: Rails-generated attribute uses (`product.price`) bind to the column and reach the call graph + PageRank on every Rails corpus (accepted, disclosed in CHANGELOG + tags.scm). model.h's Section wording changes with it ("isolated in the graph" was true only while every Section was a markdown heading/YAML key; the Ruby ones admit edges). The id rule, the four real-world spellings (pinned by the gate): - no id column/spelling -> implicit `id` def at the table-name string (no `id` token exists; anchor is per-table unique, so the multi-def floor id on N tables -> defs="N" holds) - `id: false` -> no id def - `id: :uuid` (simple_symbol value) -> `id` def at the pair key (the uuid is a TYPE of the id column, never a column of its own — Rails docs; type not modelled) - `primary_key: "x"` -> def named the string instead `t.timestamps` mints created_at AND updated_at (anchored at the method token's edges). The DSL CALLS keep their reference posture (string/datetime/create_table stay external). MIGRATIONS are excluded by a class/module-nest gate: a rendered schema's tables live at file level or under Schema[].define — never inside a class body — so a MIGRATION's class-wrapped create_table (string- or symbol-named) mints nothing. That is what makes the content gating honest: the capture is content-addressable everywhere, but the rendered schema stays the ONE source of column names — indexing a migration beside its schema would double every def and its PageRank weight. add_column/remove_column/ change_column are argument data and read nowhere, so a column added then dropped never registers. All four interference points are pinned by the three migration fixtures. Disclosed floors, each pinned by an arm: t.index/references/belongs_to/polymorphic name no column; a symbol-named table is a migration spelling, not a schema surface; string fragments (`where("price > ?")`) stay opaque; duplicate columns across tables stay SEPARATE defs. The picker splits honestly: `rec.name`/rich receivers are graph_ambiguous, `self.name` in the defining class pins its own def. Gate: test/rubyschemacheck.sh, 32 arms, ALL PASS on build/ and asan/; registered in test/regression.sh same-commit (per repo rule). Fixture test/rubyschemafix: schema text from a real, running Rails 8.1 app's `bin/rails db:schema:dump` (verified db:schema:load-able), original domain tables scrubbed to spike_* names; two spellings no dump emits are restored by hand and marked in-file (id: :uuid, which that dump actually failed on, and a literal t.timestamps); three migration fixtures pin the interference floors. Before-evidence recorded: on the pre-change binary every column read defs="0" external="1"; --uses=name was defs="9" and now reads defs="19" external="0" with binding rows. kParserVer is 123 on this branch (carried 122 pre-rebase; train-20's .astro work took 122, so this capture sits at 123 — renumber again at merge if main has moved on), with the quality.h mirror bumped in the same diff. No record layout change (kCacheVersion 25 / kQSnapCacheScheme 14 stay). Tags.scm, CHANGELOG and the fixture's USECASES.md carry the disclosures; gatecount re-pinned (649 -> 650 on this base) and qschemetrip re-pinned in the same commit. Verification on this head (rebased onto train-20 b343b988 / 0.6.4): determinism byte-identical + xmllint clean; ASan+LSan repo sweep clean (g1fresh + cachefuzz ALL PASS, both binaries built from one tree); quality-delta vs origin/main regressions="0" gating="0"; local battery ALL PASS (rubyschemacheck 32 ×2 binaries, rubyattrscheck 47, rubysettercheck, manifest, loopconservation, gatecount 650, qschemetrip, versioncheck, qackorigin, qackconcurrency, ackonly); full suite alone in leap-build: gates=665 pass=655 skip=4 fail=6 — four environment/upstream (crossdirinclude + legendcoverage: documented -j6 timeouts on this laptop, each proven passing standalone ~253s/~187s; htmlrendercheck: known HOME artifact; regexcheck: the train-19-oracle arm needs rg, absent from the container) and TWO self-inflicted on the first rebase draft (the regression.sh absorb list was merged with a `;;` spelling error: manifestcheck "regression.sh does not parse" + loopconservation's zoomcheck-vanish) — fixed, both pass standalone on this committed head. This message states the verified numbers. AI-assistance note: developed with AI assistance (DeepSeek V4 Flash and Qwen 3.8 Flash models) under the author's direct supervision; code, fixture provenance and every claim in this commit were reviewed and verified by the author before the commit. --- .ripwire_quality_acks | 4 +- CHANGELOG.md | 80 ++++++ README.md | 4 +- docs/EVALS.md | 6 +- present/deck5_ripwire_build.js | 6 +- queries/ruby/tags.scm | 13 + src/ingest_cache.h | 20 +- src/ingest_names.h | 328 ++++++++++++++++++++++ src/ingest_sidecap.h | 1 + src/model.h | 4 +- src/quality.h | 5 +- test/qschemetrip.hash | 2 +- test/regression.sh | 2 +- test/rubyschemacheck.sh | 156 ++++++++++ test/rubyschemafix/USECASES.md | 55 ++++ test/rubyschemafix/all_four.rb | 14 + test/rubyschemafix/column_consumers.rb | 16 ++ test/rubyschemafix/column_yaml.rb | 6 + test/rubyschemafix/config/spike_names.yml | 5 + test/rubyschemafix/consumer_ambiguous.rb | 11 + test/rubyschemafix/consumer_locality.rb | 12 + test/rubyschemafix/floor_case.rb | 12 + test/rubyschemafix/id_default.rb | 6 + test/rubyschemafix/id_false.rb | 8 + test/rubyschemafix/id_renamed.rb | 6 + test/rubyschemafix/id_uuid.rb | 6 + test/rubyschemafix/migration_columns.rb | 13 + test/rubyschemafix/migration_string.rb | 14 + test/rubyschemafix/migration_symbol.rb | 11 + test/rubyschemafix/pair_def_column.rb | 18 ++ test/rubyschemafix/quote_double.rb | 6 + test/rubyschemafix/quote_single.rb | 6 + test/rubyschemafix/quote_symbol.rb | 6 + test/rubyschemafix/schema.rb | 83 ++++++ test/rubyschemafix/single_column.rb | 6 + test/rubyschemafix/timestamps.rb | 6 + test/rubyschemafix/triple_plain.rb | 14 + test/rubyschemafix/triple_reversed.rb | 13 + test/rubyschemafix/triple_super.rb | 15 + 39 files changed, 984 insertions(+), 15 deletions(-) create mode 100755 test/rubyschemacheck.sh create mode 100644 test/rubyschemafix/USECASES.md create mode 100644 test/rubyschemafix/all_four.rb create mode 100644 test/rubyschemafix/column_consumers.rb create mode 100644 test/rubyschemafix/column_yaml.rb create mode 100644 test/rubyschemafix/config/spike_names.yml create mode 100644 test/rubyschemafix/consumer_ambiguous.rb create mode 100644 test/rubyschemafix/consumer_locality.rb create mode 100644 test/rubyschemafix/floor_case.rb create mode 100644 test/rubyschemafix/id_default.rb create mode 100644 test/rubyschemafix/id_false.rb create mode 100644 test/rubyschemafix/id_renamed.rb create mode 100644 test/rubyschemafix/id_uuid.rb create mode 100644 test/rubyschemafix/migration_columns.rb create mode 100644 test/rubyschemafix/migration_string.rb create mode 100644 test/rubyschemafix/migration_symbol.rb create mode 100644 test/rubyschemafix/pair_def_column.rb create mode 100644 test/rubyschemafix/quote_double.rb create mode 100644 test/rubyschemafix/quote_single.rb create mode 100644 test/rubyschemafix/quote_symbol.rb create mode 100644 test/rubyschemafix/schema.rb create mode 100644 test/rubyschemafix/single_column.rb create mode 100644 test/rubyschemafix/timestamps.rb create mode 100644 test/rubyschemafix/triple_plain.rb create mode 100644 test/rubyschemafix/triple_reversed.rb create mode 100644 test/rubyschemafix/triple_super.rb diff --git a/.ripwire_quality_acks b/.ripwire_quality_acks index 347d9f931..e41d3ae6a 100644 --- a/.ripwire_quality_acks +++ b/.ripwire_quality_acks @@ -433,7 +433,7 @@ ack complexity 5ec38fbd414fa4d4 56 cid=35883c203ba33e28 lane/tc-sliceat MCP half ack complexity 61e5df9e1e40ff70 19 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 complexity 639de1c3670999f9 69 cid=bbf7c6fe9dbbe217 wave-3 close, H7 hosts: runCrossRef hosts the --plan and --stray-content refusal sites the fix routes through the shared sentence (verify-wave2 lanes edited it days earlier — the churn is the fix's, self) | prior: capture-audit 2026-09-04 wave-1 close, lane L5 (refusal population, lane-L5.md) + lane L0 H13: guard code and its reasoning, not accidental growth — H6 file-list refusal (writeSituation/dispatchMcpLine/runChangeViews), H7 empty-selection refusals (runCrossRef flags/stray-content, evalStray badRefs, writeWhereisPage line-seed + near-miss), M7 named-file inputs + M8 --since validated once before any verb (main), M9 edit-verb refusals (runCliEdit/nearestNames/resolveOneForEdit), M20 seed disclosure (serialize + MapAnnotations::SeedDisclosure, packLego defs=, packConnect terminal defs=), F10/F14 empty list items (runPath/packConnect). L5 left these un-acked on purpose (shared-ledger race, H10); acked at close against the lane's own ec5e3c3 measurement ack complexity 685ade09a168535e 30 T1 completeness claims (complete= on grep/whereis): the +1 on streamBlobs is the deliberate DEFAULTED StreamBlobStats* param (null-object sink inside, no per-site null test; every existing caller byte-identical) so whereis can prove its scan exhaustive before claiming; cx/LOC on streamBlobs/computeWhereis/writeWhereisPage/emitGrepReport is the claim computation plus its in-band legend (the honesty text IS the feature); churn=self on those plus grepCollect/dispatchMcpLine is this lane own edit window. Gated red-first by test/completecheck.sh (24 arms, 10 red pre-fix; mutation arms force cap/offset/budget/unreadable-file/regex-mode/oversized-blob and assert the attribute VANISHES); full plain suite green, 21 touched-family gates green under ASan+LSan, determinism x3, xmllint clean -ack complexity 6979fa794f2a5154 382 cid=8249597fd315bfac ruby-attr class-DSL lane (review round; kParserVer 119->120): duplication/new-clone elixirArguments|elixirTarget is token-shape coincidence only - elixirTarget is the shared fieldIdentifierText primitive plus a null guard, elixirArguments reads a different field with no identifier gate; no shared logic to lift. captureTagsFacts +1cx/+4verb is the two-line gated call site of the inline-visibility lift. +ack complexity 6979fa794f2a5154 386 cid=820a0c985a597ef1 now=386 was=385 p=src/ingest_sidecap.h:1565 ruby schema-column capture (kParserVer 121->122): captureTagsFacts +1cx/+4verb is the one-line gated call site of the schema walk (captureRubySchemaDefs, defined in ingest_names.h beside the attribute-DSL lane); the capture's own helpers are under every bar. Phantom rows on untouched files come from a stale baseline index (reported separately). | prior: ruby-attr class-DSL lane (review round; kParserVer 119->120): duplication/new-clone elixirArguments|elixirTarget is token-shape coincidence only - elixirTarget is the shared fieldIdentifierText primitive plus a null guard, elixirArguments reads a different field with no identifier gate; no shared logic to lift. captureTagsFacts +1cx/+4verb is the two-line gated call site of the inline-visibility lift. ack complexity 6a38d70787d09e64 28 cid=f6a0c09dfc153ece Kotlin port (PR #126): the leading whitespace sample is walked by codepoint (jsonesc::utf8SeqLen) so invalid UTF-8 folds into errNodes/errBytes, deduplicated against the ERROR spans tree-sitter recovery already counted; without it a garbage byte run swallowed by recovery reads as a healthy file in --skipped. ack complexity 6e58b0a307757079 85 WAVE-2 close (2026-08-19), finding 3 of 3: the 76 remaining gating rows, ONE change. All of them are W2-E's root-relative p= landing (9beaa2c/fccea68/a271e6c/b3fe074 plus the f9108b7 correction), measured for the first time at WAVE granularity. The per-lane acks written during W2-E covered only the correction round's own diff (working-tree-vs-HEAD at that moment), so the original ~30-verb landing was never QD-acked; this ack closes that gap rather than re-accepting anything. Verified by reading the whole 20cdc04..860291c src/main.cpp diff line by line: 282 of 448 added lines match the root-relative predicate directly and every one of the remaining 127 is an existing std::printf rewritten from ing.files[...] to the root-relative rp local, plus four extracted emit helpers (computeDirModules, printJsonSymbolRows, writeOversizeRows, writeDropRows). No unrelated logic rides in. By kind: api-surface 28 = the +1 rootArg/rootPrefix parameter on the emitters that must now be TOLD their root (writeAbiCheck/Ref/Struct, emitColumnar*, packBodies/Deps/Lego/Outline/Signatures[Json], serialize[Json], writeLayout*, packConnect, buildD1Row and the report writers) - defaulted wherever a caller could stay unchanged. complexity 31 and verbosity 16 = the single-root-condition ternary and its guarded root= clause applied per emitter, with no new nesting level and no new control flow beyond that one conditional; the large absolute numbers (runStructureText 207->231, runLint 313->323, runMaintenanceViews 174->190, runCallHierarchy 71->80) are pre-existing dispatcher size the wave adds to, not creates - decomposing them is its own round and is recorded as a wave-2 follow-up. params 1 = writeNonLocalStateReport 4->6, the same contract. The three sibling lanes are individually clean: --quality-delta at f6ec56d..1732fd8 (W2-J), 1732fd8..9a41c74 (W2-K) and 9a41c74..20cdc04 (W2-F) each report gating=0. Full suite green at this head: gates=429 pass=427 skip=2 fail=0, ASan+LSan clean, byte-deterministic, xmllint clean. churn= is unavailable in ref-pair mode by construction (both trees materialized out of the repo), so short-horizon-churn is silent here and that silence is not evidence. ack complexity 7051b3950aaf4c14 35 cid=7919a3d486bba416 lane B1 cap disclosure: doc_mentions_capped= is contracted to be PROVABLE, never guessed, and that proof is the +6/+16. It is the in-loop test that a doc below its anchor's lift target was the one a cap turned away, plus docLiftWasRefused for the other half of the total cap, which ends the OUTER loop over anchors already consulted. A one-line flag would instead say 'there might be more', which is the fabricated fact in the other direction. @@ -1556,7 +1556,7 @@ ack verbosity 6031be13b40a1b6f 205 cid=58496e25cbf67431 round ec5e3c3..HEAD, the ack verbosity 6302e2e27e23bcde 64 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 verbosity 639de1c3670999f9 185 cid=ffcb81de788c329a 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. | prior: wave-3 close, H7 hosts: runCrossRef hosts the --plan and --stray-content refusal sites the fix routes through the shared sentence (verify-wave2 lanes edited it days earlier — the churn is the fix's, self) ack verbosity 685ade09a168535e 96 cid=124bb29c88050ae1 Same three sites as the complexity rows and the same reason: LOC grew where a disclosure or a real computation landed (dispatchMcpLine +71 over five pagedResult arms and the legend refusal; symbolQueryJson +26 for the attributes the CLI twin has always carried). None of it is repetition a helper would absorb. | prior: T1 completeness claims (complete= on grep/whereis): the +1 on streamBlobs is the deliberate DEFAULTED StreamBlobStats* param (null-object sink inside, no per-site null test; every existing caller byte-identical) so whereis can prove its scan exhaustive before claiming; cx/LOC on streamBlobs/computeWhereis/writeWhereisPage/emitGrepReport is the claim computation plus its in-band legend (the honesty text IS the feature); churn=self on those plus grepCollect/dispatchMcpLine is this lane own edit window. Gated red-first by test/completecheck.sh (24 arms, 10 red pre-fix; mutation arms force cap/offset/budget/unreadable-file/regex-mode/oversized-blob and assert the attribute VANISHES); full plain suite green, 21 touched-family gates green under ASan+LSan, determinism x3, xmllint clean -ack verbosity 6979fa794f2a5154 418 cid=8249597fd315bfac ruby-attr class-DSL lane (review round; kParserVer 119->120): duplication/new-clone elixirArguments|elixirTarget is token-shape coincidence only - elixirTarget is the shared fieldIdentifierText primitive plus a null guard, elixirArguments reads a different field with no identifier gate; no shared logic to lift. captureTagsFacts +1cx/+4verb is the two-line gated call site of the inline-visibility lift. +ack verbosity 6979fa794f2a5154 420 cid=820a0c985a597ef1 now=420 was=416 p=src/ingest_sidecap.h:1565 ruby schema-column capture (kParserVer 121->122): captureTagsFacts +1cx/+4verb is the one-line gated call site of the schema walk (captureRubySchemaDefs, defined in ingest_names.h beside the attribute-DSL lane); the capture's own helpers are under every bar. Phantom rows on untouched files come from a stale baseline index (reported separately). | prior: ruby-attr class-DSL lane (review round; kParserVer 119->120): duplication/new-clone elixirArguments|elixirTarget is token-shape coincidence only - elixirTarget is the shared fieldIdentifierText primitive plus a null guard, elixirArguments reads a different field with no identifier gate; no shared logic to lift. captureTagsFacts +1cx/+4verb is the two-line gated call site of the inline-visibility lift. ack verbosity 6a38d70787d09e64 74 cid=f6a0c09dfc153ece Kotlin port (PR #126): the leading whitespace sample is walked by codepoint (jsonesc::utf8SeqLen) so invalid UTF-8 folds into errNodes/errBytes, deduplicated against the ERROR spans tree-sitter recovery already counted; without it a garbage byte run swallowed by recovery reads as a healthy file in --skipped. ack verbosity 6acbcaa854eada23 289 cid=e5c009ae9a0d8393 preloaded-corpus hoist: lexicalScores/lexicalScoresTiered gain one optional defaulted preloadedFileText param so a caller scoring many queries against one tree reads the corpus once instead of per call (--eval-retrieval was ~11.8M file opens/run, 48% of its CPU in the kernel). api-surface rows ARE the intended additive change; short-horizon-churn is this session's own edits to those two functions, not instability. Scores proven byte-identical on an identical tree. ack verbosity 7004309695fb79f1 265 2026-08-15 harvest wave-level pass (orchestrator): 12-lane wave measured as one delta vs origin/main 4b9386c per verifier finding 6. All 21 gating rows triaged individually: emitGrepReport/grepHitsJson/runCallHierarchy/runDefaultMap/collectSources/printUsage/Config/runMcpHttp = feature absorption by design (grouping+boolean+corpus disclosure, file-root, bodyless_defs+legend, estimator guard, new flags), each converged and gate-verified at lane level; short-horizon-churn rows = single-wave multi-lane edits of shared hubs, process artifact; sym=main rows are main.cpp::main growth mislabeled to analyze.py by the bare-name canonId collision (path-qualified keying fix d593de3 still unpushed). emitGrepReport cx 25->63 flagged as W2 split candidate in PLAN round record. diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa9e58d6..25897b27d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,48 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli --- +## [Unreleased] + +### Added — a Ruby `db/schema.rb` renders its columns as Section definitions + +A Rails schema is recognized BY CONTENT, never by path: a Ruby file whose tree holds a +`create_table "x", … do |t| … end` call — the call's do-block carries one block parameter — is a +rendered schema, wherever it lives. Each `t. "name"` / `t. :name` in a table block mints +ONE `SymKind::Section` def at `Lang::Ruby` — the data-kind slot a doc heading or YAML key already +occupies (model.h's wording changes with it: a Section is no longer "isolated in the graph"; the Ruby +ones admit call edges) — span = the name token, both quote spellings and the `:symbol` form. +Rails-generated attribute uses (`product.price`) then BIND to the column and leave the external +surface; because Call refs are language-gated, not kind-gated, columns receive real call edges and +PageRank weight on every Rails corpus (accepted churn, disclosed here). The id rule covers the four +grounded spellings: no id column/spelling → an implicit `id` def (the anchor is the table-name string +— no `id` token exists there); `id: false` → no id def; `id: :uuid` → an `id` def at the pair key +(the uuid is a TYPE of the id column, never a column of its own; the type is not modelled); +`primary_key: "x"` → a def named the string instead. `t.timestamps` mints `created_at` AND +`updated_at` (the DSL names them literally, symmetric with the id rule) — the two defs anchor at the +method token's edges. The DSL CALLS keep their reference posture exactly like the attr family: +`string`/`datetime`/`create_table` stay external-surface names. Duplicate columns across tables stay +SEPARATE defs (same name, distinct identity bytes): `id` on N tables is `defs="N"`, and the map row +merges them with `overloads`, never hiding the multiplicity. MIGRATIONS never interfere: a +class-wrapped create_table — the migration shape, string- or symbol-named — is refused by a +class/module-nest gate (a rendered schema's tables live at file level or under `Schema[].define`, never +inside a class body), so the rendered `db/schema.rb` stays the ONE source of column names and indexing +the migration beside its schema cannot double the defs (or their PageRank weight); `add_column` / +`remove_column` / `change_column` are argument data and read nowhere — a column added then dropped +never registers (pinned by the three migration fixtures). kParserVer 122 → 123 (extraction facts +changed, renumbered on rebase because train-20's `.astro` work took 122; no record layout change — +kCacheVersion and kQSnapCacheScheme stay). +Gate: `test/rubyschemacheck.sh` on `test/rubyschemafix/` — schema text from a real, running Rails +8.1 app's `bin/rails db:schema:dump` (verified `db:schema:load`-able), original domain tables scrubbed +to `spike_*` names; two spellings no dump emits are restored by hand and marked in-file (`id: :uuid`, +which that dump actually failed on, and a literal `t.timestamps`). Stated capture floors, each pinned +by a gate arm: `t.index` / `t.references` / `t.belongs_to` / `t.polymorphic` name no column; a +symbol-named table (`create_table :users`) is a migration spelling, not a schema surface; `where("price +> ?")` string fragments stay opaque; the picker splits honestly (`rec.name` on a plain local and a rich +`X.new.name` receiver are `graph_ambiguous`, `self.name` inside the defining class pins its own def). +The before-evidence: on the pre-change binary every column was `defs=0 external=1` — the fixture's +`--uses=name` read `defs="9"` (what attrs/defs/yaml already made) with the schema columns invisible; it +now reads `defs="19" external="0"` with `count` rows binding. + ## [0.6.4] — 2026-09-25 ### Added — Astro (`.astro`) frontmatter is indexed on the TypeScript grammar (#320, #67) @@ -769,6 +811,44 @@ block are not unwrapped either (each defines real methods at runtime); and only define — a quoted (`:"x"`/`:'x'`), string, or splat/`%i[]` argument stays an honest nothing (Ruby defines those methods; ripwire does not capture them). Every floor is stated and pinned, never silent. +### Added — a Ruby `db/schema.rb` renders its columns as Section definitions + +A Rails schema is recognized BY CONTENT, never by path: a Ruby file whose tree holds a +`create_table "x", … do |t| … end` call — the call's do-block carries one block parameter — is a rendered +schema, wherever it lives. Each `t. "name"` / `t. :name` in a table block mints ONE +`SymKind::Section` def at `Lang::Ruby` — the data-kind slot a doc heading or YAML key already occupies +(model.h's wording changes with it: a Section is no longer "isolated in the graph"; the Ruby ones admit +call edges) — span = the name token, both quote spellings and the `:symbol` form. Rails-generated +attribute uses (`product.price`) then D-BIND to the column and leave the external surface; because Call +refs are language-gated, not kind-gated, columns receive real call edges and PageRank weight on every +Rails corpus (accepted churn, disclosed here). The id rule covers the four grounded spellings: no id +column/spelling → an implicit `id` def (the anchor is the table-name string — no `id` token exists +there); `id: false` → no id def; `id: :uuid` → an `id` def at the pair key (the uuid TYPE is not +modelled); `primary_key: "x"` → a def named the string instead. `t.timestamps` mints `created_at` AND +`updated_at` (the DSL names them literally, symmetric with the id rule) — the two defs anchor at the +method token's edges. The DSL CALLS keep their reference posture exactly like the attr family: +`string`/`datetime`/`create_table` stay external-surface names. Duplicate columns across tables stay +SEPARATE defs (same name, distinct identity bytes): `id` on N tables is `defs="N"`, and the map row +merges them with `overloads`, never hiding the multiplicity. MIGRATIONS never interfere: a +class-wrapped create_table — the migration shape, string- or symbol-named — is refused by a +class/module-nest gate (a rendered schema's tables live at file level or under `Schema[].define`, never +inside a class body), so the rendered `db/schema.rb` stays the ONE source of column names and indexing +the migration beside its schema cannot double the defs (or their PageRank weight); `add_column` / +`remove_column` / `change_column` are argument data and read nowhere — a column added then dropped +never registers (pinned by the three migration fixtures). kParserVer 121 → 122 (extraction facts +changed; no record layout change — kCacheVersion and kQSnapCacheScheme stay). +Gate: `test/rubyschemacheck.sh` on `test/rubyschemafix/` — schema text byte-faithful to a real, running +Rails 8.1 app's dump (verified `db:schema:load`-able), scrubbed to `spike_*` tables; two spellings no +dump ever emits are restored by hand and marked in-file (`id: :uuid`, which that dump actually failed +on, and a literal `t.timestamps`). Stated capture floors, each pinned by a gate arm: `t.index` / +`t.references` / `t.belongs_to` / `t.polymorphic` name no column; a symbol-named table +(`create_table :users`) is a migration spelling, not a schema surface; `where("price > ?")` string +fragments stay opaque; the picker splits honestly (`rec.name` on a plain local and a rich `X.new.name` +receiver are `graph_ambiguous`, `self.name` inside the defining class pins its own def). The before- +evidence: on the pre-change binary every column was `defs=0 external=1` — the fixture's `--uses=name` +read `defs="9"` (what attrs/defs/yaml already made) with the schema columns invisible; it now reads +`defs="19" external="0"` with `count` rows binding. + ## [0.6.2] — 2026-09-21 ### Added — Microsoft's `cl.exe` builds the tree, so both Windows front ends compile and both gate diff --git a/README.md b/README.md index 054c60c6e..0d2ae7a3a 100644 --- a/README.md +++ b/README.md @@ -1948,9 +1948,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published ### In the tests
-649 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +650 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures -`test/regression.sh` names **649 gate scripts** and is the authoritative list; +`test/regression.sh` names **650 gate scripts** and is the authoritative list; `python3 test/pargates.py . ./build/ripwire -j 6` runs the same set in parallel. On top of them sit the contracts that do not fit a unit test: two runs byte-identical, warm output identical to cold, output that pipes clean through `xmllint --noout`, a sanitizer build with `-fno-sanitize-recover=all`, and a diff --git a/docs/EVALS.md b/docs/EVALS.md index 93c2f83e0..b8e883901 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 649 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 650 gate scripts plus the determinism, cache-transparency and golden contracts. | | **`--quality-delta`** | `src/quality.h` | Ten measured code-quality failure modes, reported only where a change made them worse. | ### The labeling protocol (why the held-out eval is allowed to disagree with the ranker) @@ -5837,7 +5837,7 @@ copy here would be exactly the dialect divergence that gate exists to catch. Com tags, wrap, stable-order defaults), seven individually invoked standalone gates (`g1freshcheck`, `skillscan`, `htmlexport`, `compresscheck`, `handoffcheck`, `releaseinstallcheck`, `taskroutecheck`), and a single loop -naming **649 gate scripts**, all of which exist on disk. +naming **650 gate scripts**, all of which exist on disk. `python3 test/pargates.py . ./build/ripwire -j 6` runs the same scripts in parallel so a full verification fits in one sitting. It does not modify `regression.sh`. @@ -6849,7 +6849,7 @@ Listed because the reason is more useful than the silence. shipped**. See `bench/locbench/anchorhop_calib.json`. The mention anchor's reproducible numbers are the ablations in §4. - **A single round gate-count.** Two in-tree numbers disagree (`test/pargates.py`'s docstring says - ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 649. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 650. The loop is the authority; the stale docstrings are a known drift. Since 2026-09-10 the number is not written by hand anywhere: `docs/gatecount_build.py` derives it from the loop and rewrites every published site, `test/gatecountcheck.sh` fails if any of them drifts, and `test/manifestcheck.sh` diff --git a/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index 187b2ea97..d32ba5cc7 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1123,7 +1123,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["649 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["650 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1147,7 +1147,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "649", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "650", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1397,7 +1397,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["183 long flags · 34 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["649 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["650 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 71 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/queries/ruby/tags.scm b/queries/ruby/tags.scm index 87e8027d9..050dcc71b 100644 --- a/queries/ruby/tags.scm +++ b/queries/ruby/tags.scm @@ -77,3 +77,16 @@ ; raises in a class and in a module) and defines nothing. (call method: (identifier) @name) @reference.call +; Parser version 122 (test/rubyschemacheck.sh), the Rails SCHEMA capture: a file whose tree holds a +; `create_table "x", … do |t| … end` call is a rendered db/schema.rb (content-gated — path plays no +; part), and each `t. "name"` / `t. :name` in the table block mints ONE data-kind def +; (SymKind::Section at Lang::Ruby, see model.h) via the C++ side-capture in ingest_names.h, span = the +; name token — Rails-generated attribute uses (`product.price`) then bind to the column and enter the +; call graph. The four id spellings and `t.timestamps` also name columns (implicit `id` / +; `id: false` / `id: :uuid` / `primary_key: "x"` / created_at+updated_at). The DSL CALLS stay +; references like this one (`string`, `datetime`, `create_table` remain external). Stated floors: +; `t.index`/`references`/`belongs_to`/`polymorphic` name no column; duplicate columns across tables +; stay SEPARATE defs (`id` on N tables -> defs="N"); `where("price > ?")` fragments stay opaque. A +; MIGRATION never contributes: a class-wrapped create_table (string- or symbol-named) is refused by +; the class/module-nest gate — the schema is the ONE source of column names; add_column/remove_column/ +; change_column are argument data and read nowhere (a column added then dropped never registers). diff --git a/src/ingest_cache.h b/src/ingest_cache.h index 08d55f59e..e87a97a41 100644 --- a/src/ingest_cache.h +++ b/src/ingest_cache.h @@ -279,7 +279,25 @@ constexpr std::uint32_t kCacheVersion = 25; // 25: #150 AND #157 (trai // (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 = 122; // bump on any grammar/.scm/extraction change +constexpr std::uint32_t kParserVer = 123; // bump on any grammar/.scm/extraction change + // 123 = 2026-09-25 (Rails schema-column capture, + // test/rubyschemacheck.sh, unreleased; carried 122 on the + // branch and renumbered 123 on rebase, because train-20's + // .astro work (below) took 122): a Ruby file whose + // tree holds a `create_table "x", … do |t| … end` call is a + // rendered db/schema.rb BY CONTENT, and every + // `t. "name"` / `t. :name` in the table block + // mints a SymKind::Section def (the data-kind slot, same + // as a doc heading / YAML key) at Lang::Ruby — so + // `record.price` call refs bind to the column and reach + // the call graph (a Rails corpus gains call edges + + // PageRank weight on every column, row). Also: the + // four-spelling id rule (implicit / id: false / id: :uuid / + // primary_key: "x") and `t.timestamps` -> created_at + + // updated_at. MIGRATIONS contribute nothing (class/module- + // nest gate). No record layout change: kCacheVersion stays, + // kQSnapCacheScheme stays. A 121/122 cache written before + // this round differs only in the schema defs it lacks. // 122 = 2026-09-25 (#320/#67, test/astrocheck.sh): .astro joins // kLangTable on the TypeScript grammar, parsed through ONE // included range over its `---` frontmatter; blank lines diff --git a/src/ingest_names.h b/src/ingest_names.h index 43ee9c23b..f86a8d0c2 100644 --- a/src/ingest_names.h +++ b/src/ingest_names.h @@ -1199,6 +1199,334 @@ inline void captureRubyAttrDefs( TSNode root, std::uint32_t fileId, std::string_ } } + +// ─── parser version 122 (test/rubyschemacheck.sh): RUBY db/schema.rb COLUMNS ARE Section DEFS ─── +// A schema file is recognized BY CONTENT, never by path: any Ruby file whose tree holds a +// `create_table "x", … do |t| … end` call — the call's `block:` field is a do_block whose `parameters:` +// field is a block_parameters carrying ONE bare identifier — is a rendered-schema document (the block +// is the create_table call's OWN do_block, found by named-child kind because the shared NodeField set +// has no Block enumerator). Path plays no part: a schema dumped inside `ActiveRecord::Schema[8.1] +// .define … do`, a migration's class-level table, or a plain top-level one all look identical to this +// walk — the container does not matter. The file signal below is a contains-"create_table" reject like +// the attribute-family capture's contains-"attr" reject; the semantic gate is the AST shape. +// +// Each `t. "name"` / `t. :name` receiver-CALL in the table's do-block body mints ONE +// SymKind::Section def at Lang::Ruby — the same data-kind slot as a YAML key / doc heading (model.h; +// note the wording change there: a Section is no longer "isolated in the graph" once these are +// Lang::Ruby and admit call edges) — with span = the name token and name = the literal (both quote +// spellings via stringLiteralText, or a simple_symbol's text minus ':'). The DSL CALLS keep their +// external-surface reference posture exactly like the attr family: no def named `string`/`datetime` +// appears. `t.timestamps` (a receiver call with NO arguments field) mints `created_at` AND `updated_at` +// — the DSL names them literally, symmetric with the id rule; the two defs anchor at the method +// token's start/end bytes, since no literal name tokens exist in the source. +// +// Non-column DSL — t.index / t.references / t.belongs_to / t.polymorphic — names no column and defines +// nothing (stated floor: `t.references :owner` would otherwise mint a phantom "owner" column). A column +// call whose receiver is not a bare identifier (`registry.string`, a receiver-less `string`) is not a +// column; a string named by a leading-comment-skipped FIRST argument is (the attribute-DSL comment posture). +// +// The id rule — the four grounded spellings, all pinned by test/rubyschemacheck.sh: +// – no column named `id`, no `id:` pair, no primary_key pair → an implicit `id` def anchored at +// the TABLE-NAME string's content start (no `id` token exists; the anchor is per-table unique, so +// the multi-def floor — `id` on N tables → --uses=id defs="N" — holds naturally) +// – `id: false` (the pair VALUE is the anonymous `false` token) → no id def (a key-less table) +// – `id: :uuid` (pair value a simple_symbol) → `id` def AT THE PAIR KEY (the key's +// text minus its trailing ':'; the uuid TYPE is not modelled — stated floor) +// – `primary_key: "x"` (pair value a string) → a def named the string's content +// instead of the implicit `id` +// Duplicate column NAMES across tables stay SEPARATE defs (distinct nameByte identities — the dedup +// ladder only folds same-byte captures). +inline constexpr std::array kRubySchemaNonColumns = { "index", "references", "belongs_to", "polymorphic" }; + +// The FIRST non-comment named child of an argument_list — the attribute-DSL comment posture ("attribute( # note +// then :x"): a comment is an n-ary extra, never an argument. Null when the list is all comments/empty. +inline TSNode rubyFirstNonCommentArg( TSNode args, std::string_view /*src*/ ) noexcept +{ + ChildCursor cursor( args ); + TSNode first{}; + bool got = false; + forEachNamedChild( args, cursor.cur, [ & ]( TSNode c ) + { + if( kindIs( ts_node_type( c ), "comment" ) ) + { + return true; // skip the comment train, keep looking + } + first = c; + got = true; + return false; // stop at the first real argument + } ); + return got ? first : TSNode{}; +} + +// A pair node's KEY text: a hash_key_symbol carries its trailing ':' (the whole `force:` token) — strip +// it so the id/primary_key comparisons see the bare word. A key without the colon (already bare) passes +// through unchanged. +inline std::string_view rubyPairKeyText( TSNode pair, std::string_view src ) noexcept +{ + const TSNode key = fieldChild( pair, NodeField::Key ); + std::string_view t = nodeTextOf( key, src ); + if( !t.empty() && t.back() == ':' ) + { + t.remove_suffix( 1 ); + } + return t; +} + +// One `t. ` receiver-call inside a table block: a Section def per the rules above. The +// `sawId` out-param records an explicit `id` column so the implicit rule can see it. +inline void rubySchemaColumnCall( TSNode c, std::uint32_t fileId, std::string_view src, std::vector& defs, bool& sawId ) +{ + if( !kindIs( ts_node_type( c ), "call" ) ) + { + return; + } + const TSNode recv = fieldChild( c, NodeField::Receiver ); + if( ts_node_is_null( recv ) || !kindIs( ts_node_type( recv ), "identifier" ) ) + { + return; // receiver-less or receiver-qualified: not a column on the block parameter + } + const std::string_view m = fieldIdentifierText( c, NodeField::Method, src ); + if( m.empty() ) + { + return; + } + const std::uint32_t line = ts_node_start_point( c ).row + 1; + if( m == "timestamps" && ts_node_is_null( fieldChild( c, NodeField::Arguments ) ) ) + { + const TSNode meth = fieldChild( c, NodeField::Method ); + const std::uint32_t s = ts_node_start_byte( meth ), e = ts_node_end_byte( meth ); + RawDef d; + d.fileId = fileId; d.line = line; d.startByte = s; d.endByte = e; d.loc = 1; + d.kind = SymKind::Section; d.lang = Lang::Ruby; + d.nameByte = s; d.name = "created_at"; defs.push_back( d ); // the two anchors are the + d.nameByte = e; d.name = "updated_at"; defs.push_back( d ); // method token's edges — no literal tokens exist + return; + } + if( std::find( kRubySchemaNonColumns.begin(), kRubySchemaNonColumns.end(), m ) != kRubySchemaNonColumns.end() ) + { + return; // t.index / references / belongs_to / polymorphic name no column (stated floor) + } + const TSNode args = fieldChild( c, NodeField::Arguments ); + if( ts_node_is_null( args ) ) + { + return; + } + const TSNode nameNode = rubyFirstNonCommentArg( args, src ); + if( ts_node_is_null( nameNode ) ) + { + return; + } + const std::string lit = stringLiteralText( nameNode, src ); // "" when not a string node + std::string_view txt = lit; + if( txt.empty() && kindIs( ts_node_type( nameNode ), "simple_symbol" ) ) + { + txt = nodeTextOf( nameNode, src ); + if( txt.size() > 1 && txt.front() == ':' ) + { + txt.remove_prefix( 1 ); + } + } + if( txt.empty() ) + { + return; // `""` / `: ` defensive: an empty name is no column + } + const std::uint32_t s = ts_node_start_byte( nameNode ), e = ts_node_end_byte( nameNode ); + RawDef d; + d.fileId = fileId; d.line = line; d.startByte = s; d.endByte = e; d.loc = 1; + d.kind = SymKind::Section; d.lang = Lang::Ruby; d.nameByte = s + 1; // content start — past the quote or ':' + d.name = std::string( txt ); + if( d.name == "id" ) + { + sawId = true; + } + defs.push_back( d ); +} + +// One create_table call: the schema-file gate plus the whole table's defs (name, options, columns, id). +inline void rubySchemaTableCall( TSNode n, std::uint32_t fileId, std::string_view src, std::vector& defs ) +{ + // Gate 0: a rendered schema's tables live at file level or under `ActiveRecord::Schema[].define … do` + // — NEVER inside a class/module body. A MIGRATION's `create_table "users" … do |t|` (class-wrapped, + // often string-named) is the same AST shape, and without this gate it would mint the SAME columns as + // the schema that migration created — double-counting every added column on every corpus with both + // files indexed. `add_column`/`remove_column` are not read anywhere (a column added then dropped never + // registers; pinned by the migration fixtures), but the create_table of a real migration must not + // double its schema either. Excluding at least one `class`/`module` ancestor keeps the capture + // content-gated (still no path heuristic): a migration file next to the schema contributes ZERO defs. + // Checked FIRST — the cheapest gate — before the argument/block walks. + { + for( TSNode p = ts_node_parent( n ); !ts_node_is_null( p ); p = ts_node_parent( p ) ) + { + const std::string_view k( ts_node_type( p ) ); + if( k == "class" || k == "module" ) // a locals distilled walk — no cross-helper dependency + { + return; + } + } + } + const TSNode args = fieldChild( n, NodeField::Arguments ); + if( ts_node_is_null( args ) ) + { + return; + } + // Gate 1: a STRING table name as the first argument (a symbol-named table is a migration spelling, + // not a rendered-schema shape — stated floor; no name node → no schema surface either). + const TSNode tableName = rubyFirstNonCommentArg( args, src ); + if( ts_node_is_null( tableName ) || !kindIs( ts_node_type( tableName ), "string" ) ) + { + return; + } + // Gate 2: the call's OWN do_block, and its parameters: field carries ONE bare identifier (the `|t|`). + TSNode block{}; + { + ChildCursor cursor( n ); + forEachNamedChild( n, cursor.cur, [ & ]( TSNode c ) + { + if( kindIs( ts_node_type( c ), "do_block" ) ) + { + block = c; + return false; + } + return true; + } ); + } + if( ts_node_is_null( block ) ) + { + return; + } + const TSNode params = fieldChild( block, NodeField::Parameters ); + if( ts_node_is_null( params ) ) + { + return; // `create_table "x" do … end` with no handle — not the schema shape + } + { + ChildCursor cursor( params ); + TSNode only{}; + std::size_t named = 0; + forEachNamedChild( params, cursor.cur, [ & ]( TSNode c ) { only = c; ++named; return true; } ); + if( named != 1 || !kindIs( ts_node_type( only ), "identifier" ) ) + { + return; // multi-parameter or non-identifier handle — not a rendered schema's do |t| + } + } + // Options: the id/primary_key pairs among the call's arguments. + bool idFalse = false, primaryKey = false; + TSNode idKey{}, pkValue{}; + { + ChildCursor cursor( args ); + forEachNamedChild( args, cursor.cur, [ & ]( TSNode a ) + { + if( !kindIs( ts_node_type( a ), "pair" ) ) + { + return true; + } + const std::string_view k = rubyPairKeyText( a, src ); + const TSNode v = fieldChild( a, NodeField::Value ); + if( k == "id" ) + { + if( kindIs( ts_node_type( v ), "false" ) ) + { + idFalse = true; + } + else if( kindIs( ts_node_type( v ), "simple_symbol" ) ) + { + idKey = fieldChild( a, NodeField::Key ); + } + } + else if( k == "primary_key" ) + { + primaryKey = true; + pkValue = v; + } + return true; + } ); + } + // The columns: the do_block's body: field (its body_statement) — walk its named children directly + // (a fresh cursor; the outer walks cannot be reused mid-flight, tschildren.h). + bool sawIdColumn = false; + const TSNode body = fieldChild( block, NodeField::Body ); + if( !ts_node_is_null( body ) ) + { + ChildCursor cursor( body ); + forEachNamedChild( body, cursor.cur, [ & ]( TSNode c ) + { + rubySchemaColumnCall( c, fileId, src, defs, sawIdColumn ); + return true; + } ); + } + // The id rule, in the grounded order: an explicit id column wins; then the pairs decide. + if( sawIdColumn || idFalse ) + { + return; + } + if( primaryKey && !ts_node_is_null( pkValue ) && kindIs( ts_node_type( pkValue ), "string" ) ) + { + const std::string name = stringLiteralText( pkValue, src ); + if( name.empty() ) + { + return; + } + const std::uint32_t s = ts_node_start_byte( pkValue ), e = ts_node_end_byte( pkValue ); + RawDef d; + d.fileId = fileId; d.line = ts_node_start_point( pkValue ).row + 1; d.startByte = s; d.endByte = e; d.loc = 1; + d.kind = SymKind::Section; d.lang = Lang::Ruby; d.nameByte = s + 1; d.name = name; + defs.push_back( d ); + return; + } + if( !ts_node_is_null( idKey ) ) + { + const std::uint32_t s = ts_node_start_byte( idKey ), e = ts_node_end_byte( idKey ); + RawDef d; + d.fileId = fileId; d.line = ts_node_start_point( idKey ).row + 1; d.startByte = s; d.endByte = e; d.loc = 1; + d.kind = SymKind::Section; d.lang = Lang::Ruby; d.nameByte = s; d.name = "id"; + defs.push_back( d ); + return; + } + // Implicit `id` — anchored at the table-name string's content start (a stable per-table byte). + { + const std::uint32_t s = ts_node_start_byte( tableName ), e = ts_node_end_byte( tableName ); + RawDef d; + d.fileId = fileId; d.line = ts_node_start_point( tableName ).row + 1; d.startByte = s; d.endByte = e; d.loc = 1; + d.kind = SymKind::Section; d.lang = Lang::Ruby; d.nameByte = s + 1; d.name = "id"; + defs.push_back( d ); + } +} + +// The schema walk: ONE pass over the whole tree (the same explicit-stack shape as captureRubyAttrDefs — +// root's width is file-controlled, never index it, O(C²)). Every create_table-with-handle call in the +// file mints its table's defs; anything that is not such a call contributes nothing. +inline void captureRubySchemaDefs( TSNode root, std::uint32_t fileId, std::string_view src, std::vector& defs ) +{ + if( src.find( "create_table" ) == std::string_view::npos ) // file signal: the gate name must appear + { + return; + } + ChildCursor cursor( root ); + std::vector kids; + kids.reserve( 64 ); + std::vector stack; + stack.reserve( 64 ); + collectChildren( root, cursor.cur, kids ); // root's width is file-controlled — never index it (O(C²)) + for( std::size_t i = kids.size(); i > 0; --i ) + { + stack.push_back( kids[i - 1] ); + } + while( !stack.empty() ) + { + const TSNode n = stack.back(); + stack.pop_back(); + if( kindIs( ts_node_type( n ), "call" ) && fieldIdentifierText( n, NodeField::Method, src ) == "create_table" ) + { + rubySchemaTableCall( n, fileId, src, defs ); + } + collectChildren( n, cursor.cur, kids ); + for( std::size_t i = kids.size(); i > 0; --i ) + { + stack.push_back( kids[i - 1] ); + } + } +} + // F5: a Swift LOCAL binding — `let a = f()` / `var b = ...` inside a function/closure body — parses to the // same `property_declaration` node as a real stored/computed MEMBER property, so the @definition.var pattern // captures it as a spurious top-level `var` symbol AND (being the nearest enclosing symbol above the body's diff --git a/src/ingest_sidecap.h b/src/ingest_sidecap.h index e5275babf..8bfc93204 100644 --- a/src/ingest_sidecap.h +++ b/src/ingest_sidecap.h @@ -2321,6 +2321,7 @@ void captureTagsFacts( TSQueryCursor* cursor, const LangEntry& le, std::uint32_t if( le.lang == Lang::Ruby ) { captureRubyAttrDefs( root, fileId, src, defs ); + captureRubySchemaDefs( root, fileId, src, defs ); } } diff --git a/src/model.h b/src/model.h index 46ddfb50a..b6909530d 100644 --- a/src/model.h +++ b/src/model.h @@ -82,7 +82,9 @@ inline const char* symTag( SymKind k ) noexcept case SymKind::Struct: return "struct"; case SymKind::Interface: return "iface"; case SymKind::Var: return "var"; - case SymKind::Section: return "sec"; // markdown heading (doc structure; isolated in the graph) + case SymKind::Section: return "sec"; // data-kind defs: doc headings, JSON/YAML keys, and Ruby db/schema.rb + // columns (parser 122) — the Ruby ones admit call edges, so a Section + // is "isolated" only when its language is not the caller's (best-effort) case SymKind::Macro: return "macro"; // #define (disclosed-degraded: replacement text, not a parsed body) case SymKind::Field: return "field"; // member variable (id=path::Owner::field; use-sites via --uses=Owner.field) case SymKind::Other: return "other"; diff --git a/src/quality.h b/src/quality.h index fd7b6bd53..2c315f500 100644 --- a/src/quality.h +++ b/src/quality.h @@ -2102,7 +2102,10 @@ 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 = 25; // MUST equal ingest.cpp's kCacheVersion (gated); 25 = #157 + #150 -constexpr std::uint32_t kIngestParserVerMirror = 122; // MUST equal ingest.cpp's kParserVer (gated) +constexpr std::uint32_t kIngestParserVerMirror = 123; // MUST equal ingest.cpp's kParserVer (gated) + // 123 = 2026-09-25 (Rails schema-column capture, see kParserVer note; + // carried 122 on the branch, renumbered on rebase after train-20's .astro + // work took 122; kIngestCacheVersionMirror stays 25) // 122 = 2026-09-25 (#320/#67, Astro frontmatter, see kParserVer note; // kIngestCacheVersionMirror stays 25) // 121 = 2026-09-24 (#310, Ruby attr DSL, see kParserVer note) diff --git a/test/qschemetrip.hash b/test/qschemetrip.hash index 6ed85a87b..c9a87e8c2 100644 --- a/test/qschemetrip.hash +++ b/test/qschemetrip.hash @@ -1 +1 @@ -98afcd66a22ccaaa58bb671b517c58484ba382eb0f6755dd7f526c2123ebfc64 +3250f2dc0c1ca4798555e6535d22ff45d76b043fe79ab02dd6dd38fdad4fd249 diff --git a/test/regression.sh b/test/regression.sh index 82cecd82f..9880306b2 100755 --- a/test/regression.sh +++ b/test/regression.sh @@ -277,7 +277,7 @@ else RIPWIRE_BIN="$BIN" bash "$ROOT/test/codexdoctorcheck.sh" 2>&1 | sed 's/^/ | /' fi # retired: cacheexclkeycheck — the per-configuration auto-cache key it pinned is a registered NEGATIVE (docs/EVALS.md, "The auto-cache key ignores --exclude", RUN 2026-09-03: a 158K-file root with >= 12 gate configurations thrashed the 2 GiB sweep); the retry design keeps ONE superset blob per root and will bring its own gate -for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agentsurfacecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck astqueryregexcheck astrocheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck buildtypestampcheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachereservecheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck ceilingverdictcheck chacheck chaconecheck chainguardcheck chainidcheck childwalkscalecheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck cppbenchcheck cppoperatorcheck cppqualcheck cpptmplscopecheck crashsweepcheck crawlescapecheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck declinecheck declinedlistcheck decltodefcheck deeptailcheck defaultceilingcheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck diagnoticecheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck elixirnamearitycheck elixirsemanticcheck emitescapecheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck enumtablecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandbodyfirstcheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check extentcheck externalvetocheck fficheck fieldaffinitycheck fieldidcheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnptrcheck forautobodycheck forblowupcheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forhdrshapecheck forlenscheck formatgatecheck formaxtokenscheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forsectioncollapsecheck forwidencheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck gdscriptcheck genrecallcheck gitenvhermeticcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck hazardpatterncheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactimportcheck impactpartitioncheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javamethodrefcheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck jsxcallcheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legendrefcheck legobundlecheck legocheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck listingpagingcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck macroreparsecheck manifestcheck mapdiffcheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstdiolinecapcheck mcpstrictschemacheck mcptoolprunecheck mcptranchecheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck noaliascheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck notesdegradecheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck osswitchcheck oswin32logiccheck outlinecheck overbudgetcommentcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck ppdeadrolescheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck preprocdeadscalecheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pymodulealiascheck pyshapecheck qackconcurrencycheck qackorigincheck qbaselineproducercheck qchurncheck qchurnmemocheck qddialscheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qsnapproducercheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recalltablecheck recalltotalcheck receiptpostcheck recentscopecheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexguardcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck rootspellingcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyargcheck rubyattrscheck rubyconstcheck rubymetricscheck rubyrecvcheck rubyrecvnarrowcheck rubyrequirecheck rubyscopecheck rubysettercheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck scroundtripcheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcheckcheck selfcontainedcheck shadowcheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sidecarsymlinkcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck situshapecheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skipclassifycheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck stdqualcheck strkerncheck structlayoutcheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck tempfilesymlinkcheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck traceasanlinearcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do +for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agentsurfacecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck astqueryregexcheck astrocheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck buildtypestampcheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachereservecheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck ceilingverdictcheck chacheck chaconecheck chainguardcheck chainidcheck childwalkscalecheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck cppbenchcheck cppoperatorcheck cppqualcheck cpptmplscopecheck crashsweepcheck crawlescapecheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck declinecheck declinedlistcheck decltodefcheck deeptailcheck defaultceilingcheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck diagnoticecheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck elixirnamearitycheck elixirsemanticcheck emitescapecheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck enumtablecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandbodyfirstcheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check extentcheck externalvetocheck fficheck fieldaffinitycheck fieldidcheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnptrcheck forautobodycheck forblowupcheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forhdrshapecheck forlenscheck formatgatecheck formaxtokenscheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forsectioncollapsecheck forwidencheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck gdscriptcheck genrecallcheck gitenvhermeticcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck hazardpatterncheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactimportcheck impactpartitioncheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javamethodrefcheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck jsxcallcheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legendrefcheck legobundlecheck legocheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck listingpagingcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck macroreparsecheck manifestcheck mapdiffcheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstdiolinecapcheck mcpstrictschemacheck mcptoolprunecheck mcptranchecheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck noaliascheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck notesdegradecheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck osswitchcheck oswin32logiccheck outlinecheck overbudgetcommentcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck ppdeadrolescheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck preprocdeadscalecheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pymodulealiascheck pyshapecheck qackconcurrencycheck qackorigincheck qbaselineproducercheck qchurncheck qchurnmemocheck qddialscheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qsnapproducercheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recalltablecheck recalltotalcheck receiptpostcheck recentscopecheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexguardcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck rootspellingcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyargcheck rubyattrscheck rubyconstcheck rubymetricscheck rubyrecvcheck rubyrecvnarrowcheck rubyrequirecheck rubyschemacheck rubyscopecheck rubysettercheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck scroundtripcheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcheckcheck selfcontainedcheck shadowcheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sidecarsymlinkcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck situshapecheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skipclassifycheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck stdqualcheck strkerncheck structlayoutcheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck tempfilesymlinkcheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck traceasanlinearcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do [ -f "$ROOT/test/$_g.sh" ] || continue if RIPWIRE_BIN="$BIN" bash "$ROOT/test/$_g.sh" >/dev/null 2>&1; then ok "absorb gate ($_g.sh)" diff --git a/test/rubyschemacheck.sh b/test/rubyschemacheck.sh new file mode 100755 index 000000000..ebdfa87c9 --- /dev/null +++ b/test/rubyschemacheck.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# rubyschemacheck.sh — parser version 122 gate: RUBY db/schema.rb COLUMNS ARE Section DEFS. The Rails +# schema capture: a Ruby file whose tree holds a `create_table "x", … do |t| … end` call is a rendered +# schema BY CONTENT (no path heuristic), and each `t. "name"` / `t. :name` receiver-call in +# the table block mints ONE SymKind::Section def at Lang::Ruby — the same data-kind slot as a doc +# heading / YAML key (model.h) — with span = the name token. Rails-generated attribute uses +# (`product.price`, pinned here as explicit-receiver model calls) then bind to the column def set and +# reach the call graph and PageRank: the "admission consequence" the PR body discloses. The DSL CALLS +# keep their reference posture exactly like the attr family (--uses=string/define/timestamps stay +# defs=0 external=1). The four id spellings and t.timestamps name columns too, per the rules below. +# +# Fixture test/rubyschemafix — schema text from a real, running Rails 8.1 app's `bin/rails +# db:schema:dump` (verified db:schema:load-able), with the original domain tables scrubbed to spike_* +# names; two spellings no dump ever emits are restored BY HAND and marked in-file: `id: :uuid` (the +# dump itself notes "Could not dump … Unknown type 'uuid'") and a literal `t.timestamps` (rendered +# schemas expand it to t.datetime pairs) — runtime-semantics and shape notes in USECASES.md beside the +# fixture. +# name the 9 `t.string "name"` columns (→ defs=19 with the 9 attr/def defs + the yaml key) +# id the multi-def floor: EVERY table carries its key — 14 (12 implicit + id: :uuid + …) +# event_id `primary_key: "event_id"` renames the key off `id` +# ref `id: false` table's only column; NO id def there +# created_at/updated_at both the rendered t.datetime pair AND the literal t.timestamps call +# quote_symbol t.string :name + t.references :owner (floor) + t.index ["name"] (floor) +# migration_string/migration_symbol/migration_columns the migration INTERFERENCE floors: a +# class-wrapped create_table (string- OR symbol-named) mints NOTHING — the schema is the only +# source of column names, indexing a migration beside its schema would double every def and its +# PageRank weight — and add_column/remove_column are argument data, never defs (a column added +# then dropped never registers) +# floor_case where("name = ?") — a string fragment stays opaque, never a def or a use +# all_four / pair_def_column / triple_* the def+attr+column(+yaml) collision matrix; the picker +# styles: `self.name` inside PairDefColumn pins its own def (locality), `rec.name` on a local and +# `SingleColumn.new.name` on a rich receiver split over the whole def set (graph_ambiguous) +# config/spike_names.yml the cross-language name key (counted, never edgeable) +# +# Usage: test/rubyschemacheck.sh | RIPWIRE_BIN=asan/ripwire test/rubyschemacheck.sh +# Exit: 0 = clean · 1 = an arm failed · 2 = usage / missing prerequisite + +set -u +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" +[ "${BIN#/}" = "$BIN" ] && BIN="$ROOT/$BIN" +FIX="$ROOT/test/rubyschemafix" +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT +fail=0 +ok(){ printf ' PASS %s\n' "$*" || { fail=1; printf ' FAIL could not write the PASS line for: %s\n' "$*"; }; return 0; } +no(){ printf ' FAIL %s\n' "$*"; fail=1; } + +[ -x "$BIN" ] || { echo "no ripwire binary at $BIN — build first (cmake --build build -j)"; exit 2; } +[ -d "$FIX" ] || { echo "no test/rubyschemafix — fixture missing"; exit 2; } +echo "rubyschemacheck: BIN=$BIN FIX=$FIX" + +useshead(){ "$BIN" "$FIX" --uses="$1" --no-cache 2>/dev/null | grep -oE "of=\"[^\"]*\" defs=\"[0-9]+\" external=\"[0-9]+\" count=\"[0-9]+\"" | head -1; } +undefinable(){ "$BIN" "$FIX" --uses="$1" --no-cache 2>&1 | grep -q "matched no indexed definition"; } +callershead(){ "$BIN" "$FIX" --callers="$1" --no-cache 2>/dev/null | grep -oE "of=\"[^\"]*\" defs=\"[0-9]+\" count=\"[0-9]+\"" | head -1; } + +# ── 1. CAPTURE: columns are Section defs; the id rule; the timestamps pair ────────────────────────── +[ "$( useshead name )" = 'of="name" defs="19" external="0" count="3"' ] \ + && ok 'capture: name — 9 columns + 9 attr/def defs + 1 yaml key = 19; only the 3 consumers use it (the where() fragment is opaque)' \ + || no "capture: name: $( useshead name )" +[ "$( useshead id )" = 'of="id" defs="14" external="0" count="1"' ] \ + && ok 'capture: id — the multi-def floor: 12 implicit + the id: :uuid pair + the id-pair… 14 keys on 14 tables (only id_false and the renamed table have none)' \ + || no "capture: id: $( useshead id )" +[ "$( useshead event_id )" = 'of="event_id" defs="1" external="0" count="0"' ] \ + && ok 'capture: `primary_key: "event_id"` mints the renamed key def, and NO implicit id beside it' \ + || no "capture: event_id: $( useshead event_id )" +[ "$( useshead ref )" = 'of="ref" defs="1" external="0" count="0"' ] \ + && ok 'capture: the `id: false` table'"'"'s own column mints; the table has NO id def' \ + || no "capture: ref: $( useshead ref )" +[ "$( useshead created_at )" = 'of="created_at" defs="2" external="0" count="1"' ] \ + && ok 'capture: created_at — the rendered t.datetime column AND the literal t.timestamps call each mint one; the consumer call reaches them' \ + || no "capture: created_at: $( useshead created_at )" +[ "$( useshead updated_at )" = 'of="updated_at" defs="2" external="0" count="0"' ] \ + && ok 'capture: updated_at — both spellings def; no consumer calls it (floor proof the count is per-row, not padded)' \ + || no "capture: updated_at: $( useshead updated_at )" + +# ── 2. FLOORS: the DSL calls stay references; non-column DSL names no column ─────────────────────── +undefinable owner \ + && ok 'floor: t.references :owner names no column — owner stays undefinable (references is the reference, its arg is data)' \ + || no 'floor: owner — t.references minted a def' +[ "$( useshead index )" = 'of="index" defs="0" external="1" count="1"' ] \ + && ok 'floor: t.index ["name"] — the CALL is an external reference (DSL posture), the array arg defines nothing' \ + || no "floor: index: $( useshead index )" +undefinable email \ + && ok 'migration: a CLASS-wrapped string-named create_table ("spike_migrated") mints nothing — the same columns the migration created are already minted from the rendered schema; indexing both would DOUBLE the defs+PageRank' \ + || no 'migration: email — a class-wrapped (string-named) migration table minted a def' +undefinable widget_name \ + && ok 'migration: a symbol-named migration table (create_table :spike_widgets) mints nothing — both the class-nest and the string-name gates refuse' \ + || no 'migration: widget_name — a symbol-named migration table minted a def' +undefinable bla \ + && ok 'migration: add_column "bla" is argument data — the DSL call is a reference, its args are not defs (only the schema is the source of names)' \ + || no 'migration: bla — add_column minted a def' +undefinable gonna_die \ + && ok 'migration: a column added then REMOVED never registers (remove_column is read nowhere)' \ + || no 'migration: gonna_die — a removed column minted a def' +for dsl in string define datetime timestamps create_table; do + [ "$( useshead "$dsl" | grep -oE 'defs="[0-9]+" external="[0-9]+"' )" = 'defs="0" external="1"' ] \ + && ok "posture: the DSL call \`$dsl\` itself is still no def (reference capture posture)" \ + || no "posture: $dsl gained a def — the DSL call must stay a reference: $( useshead "$dsl" )" +done + +# ── 3. BINDING: model call sites reach the column def set (the admission consequence) ─────────────── +[ "$( callershead name )" = 'of="name" defs="19" count="3"' ] \ + && ok 'bind: the 3 name call sites reach the 19-def set — columns admit call edges (the Lang::Ruby admission)' \ + || no "bind: name callers: $( callershead name )" +[ "$( callershead id )" = 'of="id" defs="14" count="1"' ] \ + && ok 'bind: `IdDefault.new.id` sits on the 14-table implicit-id def set' \ + || no "bind: id callers: $( callershead id )" +[ "$( callershead created_at )" = 'of="created_at" defs="2" count="1"' ] \ + && ok 'bind: `Timestamps.new.created_at` reaches BOTH created_at defs (rendered column + literal call)' \ + || no "bind: created_at callers: $( callershead created_at )" + +# ── 4. AMBIGUITY + LOCALITY: the resolver splits honestly, pins on evidence ───────────────────────── +"$BIN" "$FIX" --uses=name --no-cache 2>/dev/null >"$TMP/uses_name" +grep -q 'of="name" defs="19" external="0" count="3"' "$TMP/uses_name" \ + && grep -q 'graph_ambiguous="4"' "$TMP/uses_name" \ + && ok 'ambiguity: the name graph_ambiguous gauge counts exactly the 4 splitting sites (rich/new/rec receivers + id + created_at), never a silent pin' \ + || no 'ambiguity: name gauge/rows wrong' +grep -q 'p="consumer_ambiguous.rb:8"' "$TMP/uses_name" \ + && ok 'ambiguous arm: rec.name on a plain local SPLITS over the whole def set (its row is in the gauge)' \ + || no 'ambiguous arm: consumer_ambiguous.rb:8 row missing' +grep -q 'p="pair_def_column.rb:15"' "$TMP/uses_name" \ + && ok 'locality arm: self.name inside PairDefColumn pins the in-class def — its row exists and is NOT in the ambiguous gauge' \ + || no 'locality arm: pair_def_column.rb:15 row missing' +grep -q 'p="column_consumers.rb:6"' "$TMP/uses_name" \ + && ok 'binding arm: SingleColumn.new.name is a real use row (explicit model receiver)' \ + || no 'binding arm: column_consumers.rb:6 row missing' + +# ── 5. MAP KINDS: the defs carry t="sec" with the DSL row family merged by name ───────────────────── +"$BIN" "$FIX" --no-cache 2>/dev/null >"$TMP/map" +grep -q '"$TMP/m1" 2>/dev/null +"$BIN" "$FIX" --no-cache >"$TMP/m2" 2>/dev/null +if cmp -s "$TMP/m1" "$TMP/m2"; then ok "deterministic (two --no-cache runs byte-identical)"; else no "non-deterministic"; fi +"$BIN" "$FIX" --cache="$TMP/c.bin" >"$TMP/cold" 2>/dev/null +"$BIN" "$FIX" --cache="$TMP/c.bin" >"$TMP/warm" 2>/dev/null +if cmp -s "$TMP/cold" "$TMP/warm"; then ok "warm == cold (schema defs survive the cache round-trip)"; else no "warm != cold"; fi +if command -v xmllint >/dev/null 2>&1; then + if xmllint --noout "$TMP/m1" 2>/dev/null; then ok "xml well-formed"; else no "xml malformed"; fi +else + ok "xml well-formed (xmllint absent — skipped)" +fi + +[ "$fail" -eq 0 ] && echo "ALL PASS" || { echo "SOME CHECKS FAILED"; exit 1; } \ No newline at end of file diff --git a/test/rubyschemafix/USECASES.md b/test/rubyschemafix/USECASES.md new file mode 100644 index 000000000..f686490e9 --- /dev/null +++ b/test/rubyschemafix/USECASES.md @@ -0,0 +1,55 @@ +# rubyschemafix — fixture fact sheet (the Rails schema-column capture) + +What this fixture is, and which claims in it are runtime-proven vs stated floors. + +## Provenance + +`schema.rb` is the rendered schema of a real, running Rails 8.1 application: its `bin/rails +db:schema:dump` output, with the original domain tables and all identifying markers stripped and the +remaining tables renamed to `spike_*`. The dump was verified to load back into that application +(`bin/rails db:schema:load` succeeded), so the shape vocabulary here — `force: :cascade`, `id: false`, +`primary_key:`, the option-pair spellings, the string/symbol column spellings, `null: false` — comes +from a working Rails install, not from a hand-invented guess. + +Two spellings no real dump ever emits are restored BY HAND in `schema.rb` and marked in-file: + +- `spike_id_uuids` with `id: :uuid` — the source app's dump FAILED for this table ("Could not dump + table … Unknown type 'uuid' for column 'id'": the dump host lacked the uuid extension). The table's + shape is the standard Rails rendering for a uuid key; the `id: :uuid` pair is the spelling ripwire's + id rule reads (the pair KEY names `id`, the uuid TYPE is not modelled — stated floor). +- `spike_timestamps_literal` with a bare `t.timestamps` — rendered schemas expand it into the two + `t.datetime` pairs (present above it as `spike_timestamps`); the DSL CALL itself exists only in + migrations. The column names it mints (`created_at`, `updated_at`) are part of Active Record's + documented timestamps contract. + +The model files mirror the source application's runtime shapes (`self.table_name`, `attribute`, +`def name`; a quoted `where("name = ?")` in `floor_case.rb` is the runtime pattern of a raw SQL +fragment). They are indexed as text; nothing here executes at gate time. + +## What the gate pins (all measured on the binary, per the repo's honesty rule) + +| Query | Pinned answer | Why | +|---|---|---| +| `--uses=name` | defs=19 external=0 count=3 | 9 columns + 9 attr/def defs + 1 yaml key; only the 3 consumers use it — the `where("name = ?")` fragment stays opaque | +| `--uses=id` | defs=14 external=0 count=1 | the multi-def floor: EVERY table carries its key (12 implicit, `id: :uuid`, and the explicit `id: false`-less ones); only `id: false` + the renamed table have none | +| `--uses=created_at` | defs=2 | the rendered `t.datetime "created_at"` column AND the literal `t.timestamps` call | +| `--uses=event_id` | defs=1 | `primary_key: "event_id"` renames the key off `id` | +| `--callers=created_at` | count=1 | `Timestamps.new.created_at` reaches BOTH defs — the Lang::Ruby admission consequence | +| `graph_ambiguous` | 4 on `name` | the rich/new/rec receiver sites split; `self.name` in PairDefColumn pins (0) | + +## Stated floors (silence is stated, never silent) + +- `t.references :owner` / `t.belongs_to` / `t.polymorphic` / `t.index` name no column (the fixture + spells `references` + `index` in `spike_quote_symbol`; `owner` stays undefinable). +- A symbol-named table (`create_table :users`) is a migration spelling, not a rendered-schema shape — + no name-string node ⇒ no schema surface. +- Duplicate column names across tables stay SEPARATE defs (`id` on N tables → defs=N); the map row + merges them by name with `overloads`, never hiding the multiplicity. +- MIGRATIONS never interfere (the three migration fixtures, all pinned undefinable): a class-wrapped + `create_table "x"` / `create_table :x` — the migration shape — is refused by the class/module-nest + gate, so the schema is the ONE source of column names and indexing a migration beside its schema + cannot double any def; `add_column` / `remove_column` / `change_column` are argument data and read + nowhere — a column added then dropped (`gonna_die`) never registers. +- `where("price > ?")` string fragments are opaque to any static extractor. +- The DSL calls (`string`, `datetime`, `create_table`, …) stay external-surface references — no def + named `string` appears; only the NAME args gain defs (same posture as the attr family). \ No newline at end of file diff --git a/test/rubyschemafix/all_four.rb b/test/rubyschemafix/all_four.rb new file mode 100644 index 000000000..423054b34 --- /dev/null +++ b/test/rubyschemafix/all_four.rb @@ -0,0 +1,14 @@ +# all-four arm: def + attribute + column in ONE model, and the yaml source in +# config/spike_names.yml. This is the canonical collision; runtime dispatch winner +# is the def, the static picker order (def > attr > column > yaml) is pinned in C. +module Spike + class AllFour < ApplicationRecord + self.table_name = "spike_all_fours" + + attribute :name + + def name + "def" + end + end +end diff --git a/test/rubyschemafix/column_consumers.rb b/test/rubyschemafix/column_consumers.rb new file mode 100644 index 000000000..e90901908 --- /dev/null +++ b/test/rubyschemafix/column_consumers.rb @@ -0,0 +1,16 @@ +# consumer arm: EXPLICIT model receivers — `Spike::SingleColumn.new.name` reaches the +# column def set by name (the sameRoot+langCompatible admission the plan calls out); +# `Spike::IdDefault.new.id` sits on the multi-table implicit id floor (14 id defs). +module Spike + def self.single_name + SingleColumn.new.name + end + + def self.default_id + IdDefault.new.id + end + + def self.literal_stamp + Timestamps.new.created_at + end +end diff --git a/test/rubyschemafix/column_yaml.rb b/test/rubyschemafix/column_yaml.rb new file mode 100644 index 000000000..f794405e8 --- /dev/null +++ b/test/rubyschemafix/column_yaml.rb @@ -0,0 +1,6 @@ +# pair arm (column, yaml): column only; the yaml source lives in config/spike_names.yml. +module Spike + class ColumnYaml < ApplicationRecord + self.table_name = "spike_column_yamls" + end +end diff --git a/test/rubyschemafix/config/spike_names.yml b/test/rubyschemafix/config/spike_names.yml new file mode 100644 index 000000000..9e2e762a9 --- /dev/null +++ b/test/rubyschemafix/config/spike_names.yml @@ -0,0 +1,5 @@ +# spike_names.yml — the cross-language Section def for `name`. +# The all-four collision (def + attribute + column + yaml) in all_four.rb; the YAML +# key is counted, never edgeable (Ruby<->Yaml is not langCompatible). +name: + display: "Spike" \ No newline at end of file diff --git a/test/rubyschemafix/consumer_ambiguous.rb b/test/rubyschemafix/consumer_ambiguous.rb new file mode 100644 index 000000000..ffe21c986 --- /dev/null +++ b/test/rubyschemafix/consumer_ambiguous.rb @@ -0,0 +1,11 @@ +# consumer arm, spam: a call site with NO locality evidence — `rec.name` where rec is +# an ordinary local. Every in-repo def named `name` is a candidate (the 9 columns, the +# attr Vars, the method defs, the yaml key); the resolver SPLITS and says so (amb=K), +# never silently picks. The call edges land on the def set, not on nothing. +module Spike + class ConsumerAmbiguous < ApplicationRecord + def render_label( rec ) + rec.name + end + end +end \ No newline at end of file diff --git a/test/rubyschemafix/consumer_locality.rb b/test/rubyschemafix/consumer_locality.rb new file mode 100644 index 000000000..ab7aed9f7 --- /dev/null +++ b/test/rubyschemafix/consumer_locality.rb @@ -0,0 +1,12 @@ +# consumer arm, locality-resolved: the call site sits inside PairDefColumn, whose own +# `def name` is in the same file — the resolver's locality tier pins THAT def, so the +# row is amb=0, not split over the columns and yaml key. +module Spike + class PairDefColumnConsumer < ApplicationRecord + self.table_name = "spike_pair_def_columns" + + def current_name + name + end + end +end \ No newline at end of file diff --git a/test/rubyschemafix/floor_case.rb b/test/rubyschemafix/floor_case.rb new file mode 100644 index 000000000..1e8dc422f --- /dev/null +++ b/test/rubyschemafix/floor_case.rb @@ -0,0 +1,12 @@ +# floor arm: a raw SQL string fragment. The `name` inside it is opaque to any +# static extractor — this is a DISCLOSED floor (no def, no use), proven only that +# the query runs. +module Spike + class FloorCase < ApplicationRecord + self.table_name = "spike_single_columns" + + def self.find_named( value ) + where( "name = ?", value ).to_a + end + end +end diff --git a/test/rubyschemafix/id_default.rb b/test/rubyschemafix/id_default.rb new file mode 100644 index 000000000..55f9e7ad3 --- /dev/null +++ b/test/rubyschemafix/id_default.rb @@ -0,0 +1,6 @@ +# id arm: implicit default id. +module Spike + class IdDefault < ApplicationRecord + self.table_name = "spike_id_defaults" + end +end diff --git a/test/rubyschemafix/id_false.rb b/test/rubyschemafix/id_false.rb new file mode 100644 index 000000000..732241389 --- /dev/null +++ b/test/rubyschemafix/id_false.rb @@ -0,0 +1,8 @@ +# id arm: create_table carried `id: false` — there is NO id column; the model +# declares its own primary key. +module Spike + class IdFalse < ApplicationRecord + self.table_name = "spike_id_false" + self.primary_key = "ref" + end +end diff --git a/test/rubyschemafix/id_renamed.rb b/test/rubyschemafix/id_renamed.rb new file mode 100644 index 000000000..08c02c84c --- /dev/null +++ b/test/rubyschemafix/id_renamed.rb @@ -0,0 +1,6 @@ +# id arm: `t.primary_key "event_id"` renames the key off `id`. +module Spike + class IdRenamed < ApplicationRecord + self.table_name = "spike_id_renamed" + end +end diff --git a/test/rubyschemafix/id_uuid.rb b/test/rubyschemafix/id_uuid.rb new file mode 100644 index 000000000..d0d21eaaa --- /dev/null +++ b/test/rubyschemafix/id_uuid.rb @@ -0,0 +1,6 @@ +# id arm: `id: :uuid` — the key is present, still named `id`, uuid type. +module Spike + class IdUuid < ApplicationRecord + self.table_name = "spike_id_uuids" + end +end diff --git a/test/rubyschemafix/migration_columns.rb b/test/rubyschemafix/migration_columns.rb new file mode 100644 index 000000000..c70a1fdbd --- /dev/null +++ b/test/rubyschemafix/migration_columns.rb @@ -0,0 +1,13 @@ +# migration arm, column lifecycle: an OLD migration adds "bla", a LATER one removes it — +# only the rendered schema is the source of column names. `add_column` / `remove_column` +# are not read anywhere (the DSL call is a reference, its arguments are data), so a column +# added then dropped never registers, and a column that STILL exists is registered by the +# schema, not by the migration that added it. `bla` and `gonna_die` stay undefinable. +class MoreColumns < ActiveRecord::Migration[8.1] + def change + add_column :spike_single_columns, "bla", :string + change_column :spike_single_columns, :ref, :text + add_column :spike_single_columns, "gonna_die", :string + remove_column :spike_single_columns, "gonna_die" + end +end \ No newline at end of file diff --git a/test/rubyschemafix/migration_string.rb b/test/rubyschemafix/migration_string.rb new file mode 100644 index 000000000..2176b45a6 --- /dev/null +++ b/test/rubyschemafix/migration_string.rb @@ -0,0 +1,14 @@ +# migration arm, STRING-named table: a real migration wraps its create_table in a class body. +# The schema capture must NOT mint from it — the same columns the migration created are already +# minted from the rendered db/schema.rb, and indexing both would DOUBLE the defs (and the +# PageRank weight) on every corpus that carries migrations. The suppression is the +# class/module-nest gate: a rendered schema's tables live at file level or under +# ActiveRecord::Schema[].define, never inside a class. `email` here stays undefinable; the +# table's implicit id is NOT added to the id multi-def floor (the gate pins both). +class CreateUsers < ActiveRecord::Migration[8.1] + def change + create_table "spike_migrated" do |t| + t.string "email" + end + end +end \ No newline at end of file diff --git a/test/rubyschemafix/migration_symbol.rb b/test/rubyschemafix/migration_symbol.rb new file mode 100644 index 000000000..5345d1766 --- /dev/null +++ b/test/rubyschemafix/migration_symbol.rb @@ -0,0 +1,11 @@ +# migration arm, SYMBOL-named table: the other common spelling. Already floored by the +# string-name gate (a symbol-named create_table is a migration spelling, not a rendered +# schema), kept as a fixture so the floor is pinned from BOTH sides — the class-nest gate +# and the name-string gate both refuse, and the widget columns stay undefinable. +class CreateWidgets < ActiveRecord::Migration[8.1] + def change + create_table :spike_widgets do |t| + t.string "widget_name" + end + end +end \ No newline at end of file diff --git a/test/rubyschemafix/pair_def_column.rb b/test/rubyschemafix/pair_def_column.rb new file mode 100644 index 000000000..bc7fd9547 --- /dev/null +++ b/test/rubyschemafix/pair_def_column.rb @@ -0,0 +1,18 @@ +# pair arm (def, column): the class def shadows the generated attribute reader. +module Spike + class PairDefColumn < ApplicationRecord + self.table_name = "spike_pair_def_columns" + + def name + "def" + end + + # locality-resolved arm: a `self.name` read at class level, in the class whose own + # `def name` sits in this same file — the receiver pins the enclosing class, so + # the resolver picks THAT def (amb=0), never splitting over the schema column or + # the yaml key. + def self.lookup + self.name + end + end +end diff --git a/test/rubyschemafix/quote_double.rb b/test/rubyschemafix/quote_double.rb new file mode 100644 index 000000000..824666252 --- /dev/null +++ b/test/rubyschemafix/quote_double.rb @@ -0,0 +1,6 @@ +# quote-spelling arm: column declared t.string "name" (double quotes). +module Spike + class QuoteDouble < ApplicationRecord + self.table_name = "spike_quote_double" + end +end diff --git a/test/rubyschemafix/quote_single.rb b/test/rubyschemafix/quote_single.rb new file mode 100644 index 000000000..2b7cf31f4 --- /dev/null +++ b/test/rubyschemafix/quote_single.rb @@ -0,0 +1,6 @@ +# quote-spelling arm: column declared `t.string 'name'` (single quotes). +module Spike + class QuoteSingle < ApplicationRecord + self.table_name = "spike_quote_single" + end +end diff --git a/test/rubyschemafix/quote_symbol.rb b/test/rubyschemafix/quote_symbol.rb new file mode 100644 index 000000000..22cdf94ff --- /dev/null +++ b/test/rubyschemafix/quote_symbol.rb @@ -0,0 +1,6 @@ +# quote-spelling arm: column declared t.string :name (symbol). +module Spike + class QuoteSymbol < ApplicationRecord + self.table_name = "spike_quote_symbol" + end +end diff --git a/test/rubyschemafix/schema.rb b/test/rubyschemafix/schema.rb new file mode 100644 index 000000000..c6ae07a30 --- /dev/null +++ b/test/rubyschemafix/schema.rb @@ -0,0 +1,83 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.1].define(version: 2000_01_01_000001) do + create_table "spike_all_fours", force: :cascade do |t| + t.string "name" + end + + create_table "spike_column_yamls", force: :cascade do |t| + t.string "name" + end + + create_table "spike_id_defaults", force: :cascade do |t| + end + + create_table "spike_id_false", id: false, force: :cascade do |t| + t.string "ref", null: false + end + + create_table "spike_id_renamed", primary_key: "event_id", force: :cascade do |t| + end + +# Could not dump table "spike_id_uuids" because of following StandardError +# Unknown type 'uuid' for column 'id' +# Restored by hand: the standard Rails rendering for a uuid table — `id: :uuid` (the value is a +# simple_symbol), the spelling the capture's id rule reads. + create_table "spike_id_uuids", id: :uuid, force: :cascade do |t| + end + + create_table "spike_pair_attr_columns", force: :cascade do |t| + t.string "name" + end + + create_table "spike_pair_def_columns", force: :cascade do |t| + t.string "name" + end + + create_table "spike_plain", force: :cascade do |t| + end + + create_table "spike_quote_double", force: :cascade do |t| + t.string "name" + end + + create_table "spike_quote_single", force: :cascade do |t| + t.string "name" + end + +create_table "spike_quote_symbol", force: :cascade do |t| + t.string :name + t.references :owner + t.index ["name"], name: "idx_spike" + end + + create_table "spike_single_columns", force: :cascade do |t| + t.string "name" + end + + create_table "spike_timestamps", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + +# Migration spelling, added by hand: the rendered dump expands `t.timestamps` into the two datetime +# columns above; the DSL CALL itself (the rule under test) names both columns literally. + create_table "spike_timestamps_literal", force: :cascade do |t| + t.timestamps + end + + create_table "spike_triples", force: :cascade do |t| + t.string "name" + end + +end diff --git a/test/rubyschemafix/single_column.rb b/test/rubyschemafix/single_column.rb new file mode 100644 index 000000000..1c89b79d4 --- /dev/null +++ b/test/rubyschemafix/single_column.rb @@ -0,0 +1,6 @@ +# single-source arm: column `name` only (no def, no attr). +module Spike + class SingleColumn < ApplicationRecord + self.table_name = "spike_single_columns" + end +end diff --git a/test/rubyschemafix/timestamps.rb b/test/rubyschemafix/timestamps.rb new file mode 100644 index 000000000..59fd081ba --- /dev/null +++ b/test/rubyschemafix/timestamps.rb @@ -0,0 +1,6 @@ +# timestamps arm: `t.timestamps` yields created_at / updated_at columns. +module Spike + class Timestamps < ApplicationRecord + self.table_name = "spike_timestamps" + end +end diff --git a/test/rubyschemafix/triple_plain.rb b/test/rubyschemafix/triple_plain.rb new file mode 100644 index 000000000..d920b8458 --- /dev/null +++ b/test/rubyschemafix/triple_plain.rb @@ -0,0 +1,14 @@ +# triple arm, spelling (a): plain `def name` alongside `attribute :name` +# and a real `name` column. Ruby lookup is by ancestry — the class-level def +# wins over the module-generated attribute reader regardless of declaration order. +module Spike + class TriplePlain < ApplicationRecord + self.table_name = "spike_triples" + + attribute :name + + def name + "def" + end + end +end diff --git a/test/rubyschemafix/triple_reversed.rb b/test/rubyschemafix/triple_reversed.rb new file mode 100644 index 000000000..c9a2881ec --- /dev/null +++ b/test/rubyschemafix/triple_reversed.rb @@ -0,0 +1,13 @@ +# triple arm, spelling (a) reversed: `def name` declared BEFORE `attribute :name`. +# Same winner as triple_plain — proof that lookup is by ancestry, not declaration order. +module Spike + class TripleReversed < ApplicationRecord + self.table_name = "spike_triples" + + def name + "def" + end + + attribute :name + end +end diff --git a/test/rubyschemafix/triple_super.rb b/test/rubyschemafix/triple_super.rb new file mode 100644 index 000000000..8f58e4612 --- /dev/null +++ b/test/rubyschemafix/triple_super.rb @@ -0,0 +1,15 @@ +# triple arm, spelling (b): `def name; super; end` + `attribute :name` + +# column. Bare `super` reaches the generated attribute reader, which reads the +# column-backed @attributes store. There is no separate "column method" — the +# column IS the storage. Expected chain: def -> super -> generated attr reader -> column. +module Spike + class TripleSuper < ApplicationRecord + self.table_name = "spike_triples" + + attribute :name + + def name + super + end + end +end From 24b34abcbeb31fea0ff2eccb8fcf54f960de898c Mon Sep 17 00:00:00 2001 From: Michal Papis Date: Sat, 26 Sep 2026 19:33:53 +0200 Subject: [PATCH 002/170] fix(ruby): CodeRabbit review round on the schema capture (#339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four findings, addressed: 1. CHANGELOG: the rebase auto-merge had left a STALE DUPLICATE of the schema entry inside [0.6.3] carrying the pre-rebase text ("D-BIND", "byte-faithful", "kParserVer 121 → 122") — an unreleased feature was claimed in a released section with the wrong parser history. Deleted; the [Unreleased] copy (the truthful 122 → 123 wording) is the only one left. 2. Receiver binding: a column call now must be ON the block parameter the create_table call bound (`t` in a rendered do |t|) — previously ANY bare identifier receiver was accepted, so `helper.string "x"` minted a phantom column. rubySchemaColumnCall takes the parameter's name from the table walk and refuses a different receiver. New negative fixture + gate arm (`helper.string "unbound_column"` stays undefinable). 3. Gate map-kind arms are now scoped INSIDE the schema.rb file block (the yaml Section key can no longer satisfy them) and assert the overloads counts (name overloads="9"). 4. Locality fixture: consumer_locality.rb claimed to demonstrate the locality pin from a DIFFERENT class than the one defining `name`. The genuine arm — `self.name` inside PairDefColumn (its own file) — was already pinned at pair_def_column.rb:15 by the gate; the standalone file added nothing (its bare `name` call contributes no row) and is deleted. The bare-name-with-no- evidence floor stays covered by the count arms. Gate test/rubyschemacheck.sh: 34 arms, ALL PASS on build/ and asan/; qschemetrip re-pinned; kParserVer stays 123 (unreleased; the receiver floor is folded into its note — a 122/123 cache written before this round differs only there). Verification: deterministic byte-identical runs + xmllint clean; the full local battery green (rubyschemacheck 34 x2 binaries, rubyattrscheck, rubysettercheck, manifest, loopconservation, gatecount, qschemetrip, versioncheck, g1fresh, cachefuzz, qackorigin, qackconcurrency, ackonly); ASan + LSan sweep clean; quality-delta vs origin/main regressions="0" gating="0". AI-assistance note: developed with AI assistance (DeepSeek V4 Flash and Qwen 3.8 Flash models) under the author's direct supervision; every change here was reviewed and verified by the author. --- CHANGELOG.md | 38 ------------------------- src/ingest_cache.h | 11 +++++-- src/ingest_names.h | 14 +++++---- test/rubyschemacheck.sh | 28 ++++++++++++++---- test/rubyschemafix/USECASES.md | 4 ++- test/rubyschemafix/consumer_locality.rb | 12 -------- test/rubyschemafix/schema.rb | 1 + 7 files changed, 43 insertions(+), 65 deletions(-) delete mode 100644 test/rubyschemafix/consumer_locality.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 25897b27d..be7037916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -811,44 +811,6 @@ block are not unwrapped either (each defines real methods at runtime); and only define — a quoted (`:"x"`/`:'x'`), string, or splat/`%i[]` argument stays an honest nothing (Ruby defines those methods; ripwire does not capture them). Every floor is stated and pinned, never silent. -### Added — a Ruby `db/schema.rb` renders its columns as Section definitions - -A Rails schema is recognized BY CONTENT, never by path: a Ruby file whose tree holds a -`create_table "x", … do |t| … end` call — the call's do-block carries one block parameter — is a rendered -schema, wherever it lives. Each `t. "name"` / `t. :name` in a table block mints ONE -`SymKind::Section` def at `Lang::Ruby` — the data-kind slot a doc heading or YAML key already occupies -(model.h's wording changes with it: a Section is no longer "isolated in the graph"; the Ruby ones admit -call edges) — span = the name token, both quote spellings and the `:symbol` form. Rails-generated -attribute uses (`product.price`) then D-BIND to the column and leave the external surface; because Call -refs are language-gated, not kind-gated, columns receive real call edges and PageRank weight on every -Rails corpus (accepted churn, disclosed here). The id rule covers the four grounded spellings: no id -column/spelling → an implicit `id` def (the anchor is the table-name string — no `id` token exists -there); `id: false` → no id def; `id: :uuid` → an `id` def at the pair key (the uuid TYPE is not -modelled); `primary_key: "x"` → a def named the string instead. `t.timestamps` mints `created_at` AND -`updated_at` (the DSL names them literally, symmetric with the id rule) — the two defs anchor at the -method token's edges. The DSL CALLS keep their reference posture exactly like the attr family: -`string`/`datetime`/`create_table` stay external-surface names. Duplicate columns across tables stay -SEPARATE defs (same name, distinct identity bytes): `id` on N tables is `defs="N"`, and the map row -merges them with `overloads`, never hiding the multiplicity. MIGRATIONS never interfere: a -class-wrapped create_table — the migration shape, string- or symbol-named — is refused by a -class/module-nest gate (a rendered schema's tables live at file level or under `Schema[].define`, never -inside a class body), so the rendered `db/schema.rb` stays the ONE source of column names and indexing -the migration beside its schema cannot double the defs (or their PageRank weight); `add_column` / -`remove_column` / `change_column` are argument data and read nowhere — a column added then dropped -never registers (pinned by the three migration fixtures). kParserVer 121 → 122 (extraction facts -changed; no record layout change — kCacheVersion and kQSnapCacheScheme stay). -Gate: `test/rubyschemacheck.sh` on `test/rubyschemafix/` — schema text byte-faithful to a real, running -Rails 8.1 app's dump (verified `db:schema:load`-able), scrubbed to `spike_*` tables; two spellings no -dump ever emits are restored by hand and marked in-file (`id: :uuid`, which that dump actually failed -on, and a literal `t.timestamps`). Stated capture floors, each pinned by a gate arm: `t.index` / -`t.references` / `t.belongs_to` / `t.polymorphic` name no column; a symbol-named table -(`create_table :users`) is a migration spelling, not a schema surface; `where("price > ?")` string -fragments stay opaque; the picker splits honestly (`rec.name` on a plain local and a rich `X.new.name` -receiver are `graph_ambiguous`, `self.name` inside the defining class pins its own def). The before- -evidence: on the pre-change binary every column was `defs=0 external=1` — the fixture's `--uses=name` -read `defs="9"` (what attrs/defs/yaml already made) with the schema columns invisible; it now reads -`defs="19" external="0"` with `count` rows binding. - ## [0.6.2] — 2026-09-21 ### Added — Microsoft's `cl.exe` builds the tree, so both Windows front ends compile and both gate diff --git a/src/ingest_cache.h b/src/ingest_cache.h index e87a97a41..0dea6f4d8 100644 --- a/src/ingest_cache.h +++ b/src/ingest_cache.h @@ -295,9 +295,14 @@ constexpr std::uint32_t kParserVer = 123; // bump on any grammar/.sc // four-spelling id rule (implicit / id: false / id: :uuid / // primary_key: "x") and `t.timestamps` -> created_at + // updated_at. MIGRATIONS contribute nothing (class/module- - // nest gate). No record layout change: kCacheVersion stays, - // kQSnapCacheScheme stays. A 121/122 cache written before - // this round differs only in the schema defs it lacks. + // nest gate). Review round at 123 (unreleased): a column + // call's receiver must BE the block parameter — a + // different bare-identifier receiver (`helper.string "x"`) + // names nothing; a 122/123 cache written before that + // round differs only there. No record layout change: + // kCacheVersion stays, kQSnapCacheScheme stays. A 121/122 + // cache written before this round differs only in the + // schema defs it lacks. // 122 = 2026-09-25 (#320/#67, test/astrocheck.sh): .astro joins // kLangTable on the TypeScript grammar, parsed through ONE // included range over its `---` frontmatter; blank lines diff --git a/src/ingest_names.h b/src/ingest_names.h index f86a8d0c2..cbd694741 100644 --- a/src/ingest_names.h +++ b/src/ingest_names.h @@ -1273,17 +1273,19 @@ inline std::string_view rubyPairKeyText( TSNode pair, std::string_view src ) noe } // One `t. ` receiver-call inside a table block: a Section def per the rules above. The -// `sawId` out-param records an explicit `id` column so the implicit rule can see it. -inline void rubySchemaColumnCall( TSNode c, std::uint32_t fileId, std::string_view src, std::vector& defs, bool& sawId ) +// RECEIVER must BE the block parameter the create_table call bound (`t` in a rendered schema's do |t|); +// any other bare identifier (`helper.string "x"`) is somebody's own helper call and names no column. +// The `sawId` out-param records an explicit `id` column so the implicit rule can see it. +inline void rubySchemaColumnCall( TSNode c, std::uint32_t fileId, std::string_view src, std::string_view receiverName, std::vector& defs, bool& sawId ) { if( !kindIs( ts_node_type( c ), "call" ) ) { return; } const TSNode recv = fieldChild( c, NodeField::Receiver ); - if( ts_node_is_null( recv ) || !kindIs( ts_node_type( recv ), "identifier" ) ) + if( ts_node_is_null( recv ) || !kindIs( ts_node_type( recv ), "identifier" ) || nodeTextOf( recv, src ) != receiverName ) { - return; // receiver-less or receiver-qualified: not a column on the block parameter + return; // receiver-less, receiver-qualified, or a DIFFERENT bare name: not a column on the block parameter } const std::string_view m = fieldIdentifierText( c, NodeField::Method, src ); if( m.empty() ) @@ -1345,6 +1347,7 @@ inline void rubySchemaColumnCall( TSNode c, std::uint32_t fileId, std::string_vi // One create_table call: the schema-file gate plus the whole table's defs (name, options, columns, id). inline void rubySchemaTableCall( TSNode n, std::uint32_t fileId, std::string_view src, std::vector& defs ) { + std::string_view receiverName; // the block parameter's name (`t`); empty until Gate 2 passes // Gate 0: a rendered schema's tables live at file level or under `ActiveRecord::Schema[].define … do` // — NEVER inside a class/module body. A MIGRATION's `create_table "users" … do |t|` (class-wrapped, // often string-named) is the same AST shape, and without this gate it would mint the SAME columns as @@ -1408,6 +1411,7 @@ inline void rubySchemaTableCall( TSNode n, std::uint32_t fileId, std::string_vie { return; // multi-parameter or non-identifier handle — not a rendered schema's do |t| } + receiverName = nodeTextOf( only, src ); // the `|t|` spelling — column calls must use it } // Options: the id/primary_key pairs among the call's arguments. bool idFalse = false, primaryKey = false; @@ -1450,7 +1454,7 @@ inline void rubySchemaTableCall( TSNode n, std::uint32_t fileId, std::string_vie ChildCursor cursor( body ); forEachNamedChild( body, cursor.cur, [ & ]( TSNode c ) { - rubySchemaColumnCall( c, fileId, src, defs, sawIdColumn ); + rubySchemaColumnCall( c, fileId, src, receiverName, defs, sawIdColumn ); return true; } ); } diff --git a/test/rubyschemacheck.sh b/test/rubyschemacheck.sh index ebdfa87c9..4606b6f0b 100755 --- a/test/rubyschemacheck.sh +++ b/test/rubyschemacheck.sh @@ -20,7 +20,9 @@ # event_id `primary_key: "event_id"` renames the key off `id` # ref `id: false` table's only column; NO id def there # created_at/updated_at both the rendered t.datetime pair AND the literal t.timestamps call -# quote_symbol t.string :name + t.references :owner (floor) + t.index ["name"] (floor) +# quote_symbol t.string :name + t.references :owner (floor) + t.index ["name"] (floor) + +# helper.string "unbound_column" — a column call on a DIFFERENT bare identifier +# names nothing (the receiver must be the block parameter, `t`) # migration_string/migration_symbol/migration_columns the migration INTERFERENCE floors: a # class-wrapped create_table (string- OR symbol-named) mints NOTHING — the schema is the only # source of column names, indexing a migration beside its schema would double every def and its @@ -92,6 +94,9 @@ undefinable bla \ undefinable gonna_die \ && ok 'migration: a column added then REMOVED never registers (remove_column is read nowhere)' \ || no 'migration: gonna_die — a removed column minted a def' +undefinable unbound_column \ + && ok 'receiver: a column call on a DIFFERENT bare identifier (`helper.string "unbound_column"`) names no column — the receiver must BE the block parameter (`t`) the create_table call bound' \ + || no 'receiver: unbound_column — a non-t receiver minted a def' for dsl in string define datetime timestamps create_table; do [ "$( useshead "$dsl" | grep -oE 'defs="[0-9]+" external="[0-9]+"' )" = 'defs="0" external="1"' ] \ && ok "posture: the DSL call \`$dsl\` itself is still no def (reference capture posture)" \ @@ -127,16 +132,27 @@ grep -q 'p="column_consumers.rb:6"' "$TMP/uses_name" \ # ── 5. MAP KINDS: the defs carry t="sec" with the DSL row family merged by name ───────────────────── "$BIN" "$FIX" --no-cache 2>/dev/null >"$TMP/map" -grep -q '"$TMP/schema_block" <<'PYEOF' +import re, sys +xml = open(sys.argv[1], encoding='utf-8').read() +m = re.search(r']*>(.*?)', xml, re.S) +sys.stdout.write(m.group(1) if m else "") +PYEOF +grep -q ' ?")` string fragments are opaque to any static extractor. - The DSL calls (`string`, `datetime`, `create_table`, …) stay external-surface references — no def - named `string` appears; only the NAME args gain defs (same posture as the attr family). \ No newline at end of file + named `string` appears; only the NAME args gain defs (same posture as the attr family). +- A column call's receiver must BE the block parameter: `helper.string "unbound_column"` (a different + bare identifier) names no column — the gate accepts only `t.` (the `|t|` handle) forms. \ No newline at end of file diff --git a/test/rubyschemafix/consumer_locality.rb b/test/rubyschemafix/consumer_locality.rb deleted file mode 100644 index ab7aed9f7..000000000 --- a/test/rubyschemafix/consumer_locality.rb +++ /dev/null @@ -1,12 +0,0 @@ -# consumer arm, locality-resolved: the call site sits inside PairDefColumn, whose own -# `def name` is in the same file — the resolver's locality tier pins THAT def, so the -# row is amb=0, not split over the columns and yaml key. -module Spike - class PairDefColumnConsumer < ApplicationRecord - self.table_name = "spike_pair_def_columns" - - def current_name - name - end - end -end \ No newline at end of file diff --git a/test/rubyschemafix/schema.rb b/test/rubyschemafix/schema.rb index c6ae07a30..fbbd12348 100644 --- a/test/rubyschemafix/schema.rb +++ b/test/rubyschemafix/schema.rb @@ -59,6 +59,7 @@ t.string :name t.references :owner t.index ["name"], name: "idx_spike" + helper.string "unbound_column" end create_table "spike_single_columns", force: :cascade do |t| From 37f20f31955ec18ecf554780b9f635467cba665d Mon Sep 17 00:00:00 2001 From: Michal Papis Date: Sun, 27 Sep 2026 01:05:55 +0200 Subject: [PATCH 003/170] =?UTF-8?q?fix(ruby):=20maintainer=20round=20on=20?= =?UTF-8?q?the=20schema=20capture=20=E2=80=94=20definitions=20only=20(#339?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Response to joyful-ii-V-I's review. The design change: schema columns are now DEFINITIONS ONLY. The "admission consequence" (columns reaching the call graph + PageRank) is retracted after the maintainer measured that untyped receivers bind unrelated calls to columns (`response.code` against a `code` column) and that a 200-table app's map was taken over by columns (196/200 rows; declines 292->1324). Columns still answer --uses/--grep/--whereis and appear in the map as defs. Required, all addressed: 1. Definitions-only: buildGraph/1d's byName fill skips SymKind::Section && Lang::Ruby (the same table feeds call binding AND common-name damping, so both consequences disappear at one seam, mirroring contextratio.h's isMeasurableKind). Gate §3 is now defs-only arms: callers=id -> defs=14 count=0, callers=created_at -> defs=2 count=0, a false-binding arm (a column-ONLY name, ref, gains no caller), and callers=name -> defs=19 count=3 (the METHOD/attr defs' edges only). model.h's sec comment is back to "no call edges"; CHANGELOG/tags.scm reworded. The ambiguity gauge drops 4 -> 2 (the id/created_at column sites left the gauge), pinned. 2. Shared walker instead of acking the duplication: captureRubyAttrDefs and captureRubySchemaDefs are now ONE pre-order walk (captureRubyDefs) dispatching both lanes per call node, with the per-file signals preserved (quality-delta duplication row must be gone — verified on the range, below). 3. Constraint floors: check_constraint / exclusion_constraint / unique_constraint added to kRubySchemaNonColumns — `t.check_constraint "price > 0", name: ...` no longer mints n="price > 0" (real dumps were measured); pinned by a fixture + undefinable arm. 4. kParserVer lands at 126 (123 is reserved for #325, 124 in flight, 125 queued for #338; the merge commit sets the final number). Mirror 126 in the same diff. 5. Version-stale "122" comments made number-free: tags.scm, the ingest_names.h lane header, model.h, rubyschemacheck.sh header, both .ripwire_quality_acks reasons. Optional, all addressed: composite/symbol primary_key now mints NO implicit id (an array-valued key has no id; the rendered columns are the block's own t. lines) pinned by a composite-key fixture + arms, with the id defs=14 arm as the regression guard; spike_quote_single now spells t.string 'name' so the single-quote path is pinned; the lane header no longer claims "the container does not matter" (Gate 0 refuses class/module-nested tables); CHANGELOG "before" corrected to defs=10 (9 attr/def defs + the yaml key, per the maintainer's measurement). The lpin locality row is moot under definitions-only — answered on the review thread. Nice-to-have, all addressed: rubyFirstNonCommentArg's unused src parameter dropped; CHANGELOG floor lines for references/belongs_to (no _id), create_join_table, and the Rails 8 queue_/cache_/cable_schema.rb content-match. Gate test/rubyschemacheck.sh: 39 arms, ALL PASS on build/ and asan/. Verification: determinism byte-identical + xmllint; full battery green (rubyattrscheck 47, rubysettercheck, manifest, loopconservation, gatecount, qschemetrip re-pinned, versioncheck, g1fresh, cachefuzz, qackorigin, qackconcurrency, ackonly); ASan + LSan sweep clean; quality-delta on the maintainer's range b343b988..HEAD: the captureRubyAttrDefs|captureRubySchemaDefs duplication row is GONE (shared walker) and gating=0 (verified after this commit). AI-assistance note: developed with AI assistance (DeepSeek V4 Flash and Qwen 3.8 Flash models) under the author's direct supervision; every change here was reviewed and verified by the author. --- .ripwire_quality_acks | 4 +- CHANGELOG.md | 61 +++++++++-------- queries/ruby/tags.scm | 22 ++++--- src/graph.h | 9 +++ src/ingest_cache.h | 55 +++++++++------- src/ingest_names.h | 91 ++++++++++++-------------- src/ingest_sidecap.h | 3 +- src/model.h | 4 +- src/quality.h | 7 +- test/qschemetrip.hash | 2 +- test/rubyschemacheck.sh | 59 +++++++++++------ test/rubyschemafix/USECASES.md | 4 +- test/rubyschemafix/column_consumers.rb | 7 +- test/rubyschemafix/migration_string.rb | 4 +- test/rubyschemafix/schema.rb | 8 ++- 15 files changed, 192 insertions(+), 148 deletions(-) diff --git a/.ripwire_quality_acks b/.ripwire_quality_acks index e41d3ae6a..9a1b497b8 100644 --- a/.ripwire_quality_acks +++ b/.ripwire_quality_acks @@ -433,7 +433,7 @@ ack complexity 5ec38fbd414fa4d4 56 cid=35883c203ba33e28 lane/tc-sliceat MCP half ack complexity 61e5df9e1e40ff70 19 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 complexity 639de1c3670999f9 69 cid=bbf7c6fe9dbbe217 wave-3 close, H7 hosts: runCrossRef hosts the --plan and --stray-content refusal sites the fix routes through the shared sentence (verify-wave2 lanes edited it days earlier — the churn is the fix's, self) | prior: capture-audit 2026-09-04 wave-1 close, lane L5 (refusal population, lane-L5.md) + lane L0 H13: guard code and its reasoning, not accidental growth — H6 file-list refusal (writeSituation/dispatchMcpLine/runChangeViews), H7 empty-selection refusals (runCrossRef flags/stray-content, evalStray badRefs, writeWhereisPage line-seed + near-miss), M7 named-file inputs + M8 --since validated once before any verb (main), M9 edit-verb refusals (runCliEdit/nearestNames/resolveOneForEdit), M20 seed disclosure (serialize + MapAnnotations::SeedDisclosure, packLego defs=, packConnect terminal defs=), F10/F14 empty list items (runPath/packConnect). L5 left these un-acked on purpose (shared-ledger race, H10); acked at close against the lane's own ec5e3c3 measurement ack complexity 685ade09a168535e 30 T1 completeness claims (complete= on grep/whereis): the +1 on streamBlobs is the deliberate DEFAULTED StreamBlobStats* param (null-object sink inside, no per-site null test; every existing caller byte-identical) so whereis can prove its scan exhaustive before claiming; cx/LOC on streamBlobs/computeWhereis/writeWhereisPage/emitGrepReport is the claim computation plus its in-band legend (the honesty text IS the feature); churn=self on those plus grepCollect/dispatchMcpLine is this lane own edit window. Gated red-first by test/completecheck.sh (24 arms, 10 red pre-fix; mutation arms force cap/offset/budget/unreadable-file/regex-mode/oversized-blob and assert the attribute VANISHES); full plain suite green, 21 touched-family gates green under ASan+LSan, determinism x3, xmllint clean -ack complexity 6979fa794f2a5154 386 cid=820a0c985a597ef1 now=386 was=385 p=src/ingest_sidecap.h:1565 ruby schema-column capture (kParserVer 121->122): captureTagsFacts +1cx/+4verb is the one-line gated call site of the schema walk (captureRubySchemaDefs, defined in ingest_names.h beside the attribute-DSL lane); the capture's own helpers are under every bar. Phantom rows on untouched files come from a stale baseline index (reported separately). | prior: ruby-attr class-DSL lane (review round; kParserVer 119->120): duplication/new-clone elixirArguments|elixirTarget is token-shape coincidence only - elixirTarget is the shared fieldIdentifierText primitive plus a null guard, elixirArguments reads a different field with no identifier gate; no shared logic to lift. captureTagsFacts +1cx/+4verb is the two-line gated call site of the inline-visibility lift. +ack complexity 6979fa794f2a5154 386 cid=820a0c985a597ef1 now=386 was=385 p=src/ingest_sidecap.h:1565 ruby schema-column capture (the Rails schema capture round): captureTagsFacts +1cx/+4verb is the one-line gated call site of the schema walk (captureRubySchemaDefs, defined in ingest_names.h beside the attribute-DSL lane); the capture's own helpers are under every bar. Phantom rows on untouched files come from a stale baseline index (reported separately). | prior: ruby-attr class-DSL lane (review round; kParserVer 119->120): duplication/new-clone elixirArguments|elixirTarget is token-shape coincidence only - elixirTarget is the shared fieldIdentifierText primitive plus a null guard, elixirArguments reads a different field with no identifier gate; no shared logic to lift. captureTagsFacts +1cx/+4verb is the two-line gated call site of the inline-visibility lift. ack complexity 6a38d70787d09e64 28 cid=f6a0c09dfc153ece Kotlin port (PR #126): the leading whitespace sample is walked by codepoint (jsonesc::utf8SeqLen) so invalid UTF-8 folds into errNodes/errBytes, deduplicated against the ERROR spans tree-sitter recovery already counted; without it a garbage byte run swallowed by recovery reads as a healthy file in --skipped. ack complexity 6e58b0a307757079 85 WAVE-2 close (2026-08-19), finding 3 of 3: the 76 remaining gating rows, ONE change. All of them are W2-E's root-relative p= landing (9beaa2c/fccea68/a271e6c/b3fe074 plus the f9108b7 correction), measured for the first time at WAVE granularity. The per-lane acks written during W2-E covered only the correction round's own diff (working-tree-vs-HEAD at that moment), so the original ~30-verb landing was never QD-acked; this ack closes that gap rather than re-accepting anything. Verified by reading the whole 20cdc04..860291c src/main.cpp diff line by line: 282 of 448 added lines match the root-relative predicate directly and every one of the remaining 127 is an existing std::printf rewritten from ing.files[...] to the root-relative rp local, plus four extracted emit helpers (computeDirModules, printJsonSymbolRows, writeOversizeRows, writeDropRows). No unrelated logic rides in. By kind: api-surface 28 = the +1 rootArg/rootPrefix parameter on the emitters that must now be TOLD their root (writeAbiCheck/Ref/Struct, emitColumnar*, packBodies/Deps/Lego/Outline/Signatures[Json], serialize[Json], writeLayout*, packConnect, buildD1Row and the report writers) - defaulted wherever a caller could stay unchanged. complexity 31 and verbosity 16 = the single-root-condition ternary and its guarded root= clause applied per emitter, with no new nesting level and no new control flow beyond that one conditional; the large absolute numbers (runStructureText 207->231, runLint 313->323, runMaintenanceViews 174->190, runCallHierarchy 71->80) are pre-existing dispatcher size the wave adds to, not creates - decomposing them is its own round and is recorded as a wave-2 follow-up. params 1 = writeNonLocalStateReport 4->6, the same contract. The three sibling lanes are individually clean: --quality-delta at f6ec56d..1732fd8 (W2-J), 1732fd8..9a41c74 (W2-K) and 9a41c74..20cdc04 (W2-F) each report gating=0. Full suite green at this head: gates=429 pass=427 skip=2 fail=0, ASan+LSan clean, byte-deterministic, xmllint clean. churn= is unavailable in ref-pair mode by construction (both trees materialized out of the repo), so short-horizon-churn is silent here and that silence is not evidence. ack complexity 7051b3950aaf4c14 35 cid=7919a3d486bba416 lane B1 cap disclosure: doc_mentions_capped= is contracted to be PROVABLE, never guessed, and that proof is the +6/+16. It is the in-loop test that a doc below its anchor's lift target was the one a cap turned away, plus docLiftWasRefused for the other half of the total cap, which ends the OUTER loop over anchors already consulted. A one-line flag would instead say 'there might be more', which is the fabricated fact in the other direction. @@ -1556,7 +1556,7 @@ ack verbosity 6031be13b40a1b6f 205 cid=58496e25cbf67431 round ec5e3c3..HEAD, the ack verbosity 6302e2e27e23bcde 64 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 verbosity 639de1c3670999f9 185 cid=ffcb81de788c329a 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. | prior: wave-3 close, H7 hosts: runCrossRef hosts the --plan and --stray-content refusal sites the fix routes through the shared sentence (verify-wave2 lanes edited it days earlier — the churn is the fix's, self) ack verbosity 685ade09a168535e 96 cid=124bb29c88050ae1 Same three sites as the complexity rows and the same reason: LOC grew where a disclosure or a real computation landed (dispatchMcpLine +71 over five pagedResult arms and the legend refusal; symbolQueryJson +26 for the attributes the CLI twin has always carried). None of it is repetition a helper would absorb. | prior: T1 completeness claims (complete= on grep/whereis): the +1 on streamBlobs is the deliberate DEFAULTED StreamBlobStats* param (null-object sink inside, no per-site null test; every existing caller byte-identical) so whereis can prove its scan exhaustive before claiming; cx/LOC on streamBlobs/computeWhereis/writeWhereisPage/emitGrepReport is the claim computation plus its in-band legend (the honesty text IS the feature); churn=self on those plus grepCollect/dispatchMcpLine is this lane own edit window. Gated red-first by test/completecheck.sh (24 arms, 10 red pre-fix; mutation arms force cap/offset/budget/unreadable-file/regex-mode/oversized-blob and assert the attribute VANISHES); full plain suite green, 21 touched-family gates green under ASan+LSan, determinism x3, xmllint clean -ack verbosity 6979fa794f2a5154 420 cid=820a0c985a597ef1 now=420 was=416 p=src/ingest_sidecap.h:1565 ruby schema-column capture (kParserVer 121->122): captureTagsFacts +1cx/+4verb is the one-line gated call site of the schema walk (captureRubySchemaDefs, defined in ingest_names.h beside the attribute-DSL lane); the capture's own helpers are under every bar. Phantom rows on untouched files come from a stale baseline index (reported separately). | prior: ruby-attr class-DSL lane (review round; kParserVer 119->120): duplication/new-clone elixirArguments|elixirTarget is token-shape coincidence only - elixirTarget is the shared fieldIdentifierText primitive plus a null guard, elixirArguments reads a different field with no identifier gate; no shared logic to lift. captureTagsFacts +1cx/+4verb is the two-line gated call site of the inline-visibility lift. +ack verbosity 6979fa794f2a5154 420 cid=820a0c985a597ef1 now=420 was=416 p=src/ingest_sidecap.h:1565 ruby schema-column capture (the Rails schema capture round): captureTagsFacts +1cx/+4verb is the one-line gated call site of the schema walk (captureRubySchemaDefs, defined in ingest_names.h beside the attribute-DSL lane); the capture's own helpers are under every bar. Phantom rows on untouched files come from a stale baseline index (reported separately). | prior: ruby-attr class-DSL lane (review round; kParserVer 119->120): duplication/new-clone elixirArguments|elixirTarget is token-shape coincidence only - elixirTarget is the shared fieldIdentifierText primitive plus a null guard, elixirArguments reads a different field with no identifier gate; no shared logic to lift. captureTagsFacts +1cx/+4verb is the two-line gated call site of the inline-visibility lift. ack verbosity 6a38d70787d09e64 74 cid=f6a0c09dfc153ece Kotlin port (PR #126): the leading whitespace sample is walked by codepoint (jsonesc::utf8SeqLen) so invalid UTF-8 folds into errNodes/errBytes, deduplicated against the ERROR spans tree-sitter recovery already counted; without it a garbage byte run swallowed by recovery reads as a healthy file in --skipped. ack verbosity 6acbcaa854eada23 289 cid=e5c009ae9a0d8393 preloaded-corpus hoist: lexicalScores/lexicalScoresTiered gain one optional defaulted preloadedFileText param so a caller scoring many queries against one tree reads the corpus once instead of per call (--eval-retrieval was ~11.8M file opens/run, 48% of its CPU in the kernel). api-surface rows ARE the intended additive change; short-horizon-churn is this session's own edits to those two functions, not instability. Scores proven byte-identical on an identical tree. ack verbosity 7004309695fb79f1 265 2026-08-15 harvest wave-level pass (orchestrator): 12-lane wave measured as one delta vs origin/main 4b9386c per verifier finding 6. All 21 gating rows triaged individually: emitGrepReport/grepHitsJson/runCallHierarchy/runDefaultMap/collectSources/printUsage/Config/runMcpHttp = feature absorption by design (grouping+boolean+corpus disclosure, file-root, bodyless_defs+legend, estimator guard, new flags), each converged and gate-verified at lane level; short-horizon-churn rows = single-wave multi-lane edits of shared hubs, process artifact; sym=main rows are main.cpp::main growth mislabeled to analyze.py by the bare-name canonId collision (path-qualified keying fix d593de3 still unpushed). emitGrepReport cx 25->63 flagged as W2 split candidate in PLAN round record. diff --git a/CHANGELOG.md b/CHANGELOG.md index be7037916..8b1e2fb49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,39 +21,48 @@ A Rails schema is recognized BY CONTENT, never by path: a Ruby file whose tree h `create_table "x", … do |t| … end` call — the call's do-block carries one block parameter — is a rendered schema, wherever it lives. Each `t. "name"` / `t. :name` in a table block mints ONE `SymKind::Section` def at `Lang::Ruby` — the data-kind slot a doc heading or YAML key already -occupies (model.h's wording changes with it: a Section is no longer "isolated in the graph"; the Ruby -ones admit call edges) — span = the name token, both quote spellings and the `:symbol` form. -Rails-generated attribute uses (`product.price`) then BIND to the column and leave the external -surface; because Call refs are language-gated, not kind-gated, columns receive real call edges and -PageRank weight on every Rails corpus (accepted churn, disclosed here). The id rule covers the four -grounded spellings: no id column/spelling → an implicit `id` def (the anchor is the table-name string -— no `id` token exists there); `id: false` → no id def; `id: :uuid` → an `id` def at the pair key -(the uuid is a TYPE of the id column, never a column of its own; the type is not modelled); -`primary_key: "x"` → a def named the string instead. `t.timestamps` mints `created_at` AND -`updated_at` (the DSL names them literally, symmetric with the id rule) — the two defs anchor at the -method token's edges. The DSL CALLS keep their reference posture exactly like the attr family: -`string`/`datetime`/`create_table` stay external-surface names. Duplicate columns across tables stay -SEPARATE defs (same name, distinct identity bytes): `id` on N tables is `defs="N"`, and the map row -merges them with `overloads`, never hiding the multiplicity. MIGRATIONS never interfere: a +occupies (model.h: a Section's isolation is a language property; the Ruby ones are still no call +edges) — span = the name token, both quote spellings and the `:symbol` form. Rails-generated attribute +uses (`product.price`) then resolve to the column as a definition: it appears in `--uses`, `--grep`, +`--whereis` and the map. THE COLUMNS ARE DEFINITIONS ONLY, by maintainer decision: buildGraph's +byName skips Section-Ruby, so a column admits NO call edge and no PageRank weight — an untyped +`response.code` must not become a table's caller, and 200 `name` columns must not damp a model's real +`def name` (both measured at review; edges may return behind an evidence rule, e.g. a receiver proven +to be the owning model). The id rule covers the grounded spellings: no id column/spelling → an +implicit `id` def (the anchor is the table-name string — no `id` token exists there); `id: false` → +no id def; `id: :uuid` → an `id` def at the pair key (the uuid is a TYPE of the id column, never a +column of its own; the type is not modelled); `primary_key: "x"` → a def named the string instead. +An ARRAY or symbol `primary_key` (a composite key, or a hand-written migration) mints NO implicit id — +the rendered composite columns are the block's own explicit `t.` lines. `t.timestamps` mints +`created_at` AND `updated_at` (the DSL names them literally, symmetric with the id rule) — the two +defs anchor at the method token's edges. The DSL CALLS keep their reference posture exactly like the +attr family: `string`/`datetime`/`create_table` stay external-surface names. Duplicate columns across +tables stay SEPARATE defs (same name, distinct identity bytes): `id` on N tables is `defs="N"`, and +the map row merges them with `overloads`, never hiding the multiplicity. MIGRATIONS never interfere: a class-wrapped create_table — the migration shape, string- or symbol-named — is refused by a class/module-nest gate (a rendered schema's tables live at file level or under `Schema[].define`, never inside a class body), so the rendered `db/schema.rb` stays the ONE source of column names and indexing -the migration beside its schema cannot double the defs (or their PageRank weight); `add_column` / -`remove_column` / `change_column` are argument data and read nowhere — a column added then dropped -never registers (pinned by the three migration fixtures). kParserVer 122 → 123 (extraction facts -changed, renumbered on rebase because train-20's `.astro` work took 122; no record layout change — -kCacheVersion and kQSnapCacheScheme stay). +the migration beside its schema cannot double any def; `add_column` / `remove_column` / +`change_column` are argument data and read nowhere — a column added then dropped never registers +(pinned by the three migration fixtures). Rails 8's `db/queue_schema.rb`, `db/cache_schema.rb` and +`db/cable_schema.rb` are matched by the same content gate. kParserVer lands at 126 (extraction facts +changed; carried 122 pre-rebase and renumbered for the train-20 `.astro` collision, then to 126 at +review — 123 is reserved for #325, 124 in flight, 125 queued for #338; the merge commit sets the final +number; no record layout change — kCacheVersion and kQSnapCacheScheme stay). Gate: `test/rubyschemacheck.sh` on `test/rubyschemafix/` — schema text from a real, running Rails 8.1 app's `bin/rails db:schema:dump` (verified `db:schema:load`-able), original domain tables scrubbed to `spike_*` names; two spellings no dump emits are restored by hand and marked in-file (`id: :uuid`, which that dump actually failed on, and a literal `t.timestamps`). Stated capture floors, each pinned -by a gate arm: `t.index` / `t.references` / `t.belongs_to` / `t.polymorphic` name no column; a -symbol-named table (`create_table :users`) is a migration spelling, not a schema surface; `where("price -> ?")` string fragments stay opaque; the picker splits honestly (`rec.name` on a plain local and a rich -`X.new.name` receiver are `graph_ambiguous`, `self.name` inside the defining class pins its own def). -The before-evidence: on the pre-change binary every column was `defs=0 external=1` — the fixture's -`--uses=name` read `defs="9"` (what attrs/defs/yaml already made) with the schema columns invisible; it -now reads `defs="19" external="0"` with `count` rows binding. +by a gate arm: `t.index` / `t.references` / `t.belongs_to` / `t.polymorphic` / `t.check_constraint` / +`t.exclusion_constraint` / `t.unique_constraint` name no column (a `t.references`/`t.belongs_to` mints +no `_id`, and a `create_join_table` mints nothing — raws dumps render these as explicit columns); +a column call must be ON the block parameter (`helper.string "x"` names nothing); a symbol-named table +(`create_table :users`) is a migration spelling, not a schema surface; `where("price > ?")` string +fragments stay opaque; the picker splits honestly (`rec.name` on a plain local and a rich `X.new.name` +receiver are `graph_ambiguous`, `self.name` inside the defining class pins its own def). The +before-evidence: on the pre-change binary every column was `defs=0 external=1` — the fixture's +`--uses=name` read `defs="10"` (9 attr/def defs plus the YAML key) with the schema columns invisible; it +now reads `defs="19" external="0"` and the column uses resolve to definitions only. ## [0.6.4] — 2026-09-25 diff --git a/queries/ruby/tags.scm b/queries/ruby/tags.scm index 050dcc71b..7f24a27a3 100644 --- a/queries/ruby/tags.scm +++ b/queries/ruby/tags.scm @@ -77,16 +77,20 @@ ; raises in a class and in a module) and defines nothing. (call method: (identifier) @name) @reference.call -; Parser version 122 (test/rubyschemacheck.sh), the Rails SCHEMA capture: a file whose tree holds a +; The Rails schema capture (test/rubyschemacheck.sh): a file whose tree holds a ; `create_table "x", … do |t| … end` call is a rendered db/schema.rb (content-gated — path plays no ; part), and each `t. "name"` / `t. :name` in the table block mints ONE data-kind def ; (SymKind::Section at Lang::Ruby, see model.h) via the C++ side-capture in ingest_names.h, span = the -; name token — Rails-generated attribute uses (`product.price`) then bind to the column and enter the -; call graph. The four id spellings and `t.timestamps` also name columns (implicit `id` / -; `id: false` / `id: :uuid` / `primary_key: "x"` / created_at+updated_at). The DSL CALLS stay -; references like this one (`string`, `datetime`, `create_table` remain external). Stated floors: -; `t.index`/`references`/`belongs_to`/`polymorphic` name no column; duplicate columns across tables -; stay SEPARATE defs (`id` on N tables -> defs="N"); `where("price > ?")` fragments stay opaque. A -; MIGRATION never contributes: a class-wrapped create_table (string- or symbol-named) is refused by -; the class/module-nest gate — the schema is the ONE source of column names; add_column/remove_column/ +; name token — Rails-generated attribute uses (`product.price`) resolve to the column as a def. +; DEFINITIONS ONLY by maintainer decision: the defs answer --uses/--grep/--whereis, but buildGraph's +; byName skips Section-Ruby, so columns admit NO call edges and no PageRank weight (untyped-receiver +; binding and map takeover were measured; edges may return behind an evidence rule). The four id +; spellings and `t.timestamps` also name columns (implicit `id` / `id: false` / `id: :uuid` / +; `primary_key: "x"` / created_at+updated_at); an ARRAY or symbol primary_key (composite key) mints no +; implicit id. The DSL CALLS stay references like this one (`string`, `datetime`, `create_table` remain +; external). Stated floors: `t.index`/`references`/`belongs_to`/`polymorphic`/`check_constraint`/ +; `exclusion_constraint`/`unique_constraint` name no column; duplicate columns across tables stay +; SEPARATE defs (`id` on N tables -> defs="N"); `where("price > ?")` fragments stay opaque. A MIGRATION +; never contributes: a class-wrapped create_table (string- or symbol-named) is refused by the +; class/module-nest gate — the schema is the ONE source of column names; add_column/remove_column/ ; change_column are argument data and read nowhere (a column added then dropped never registers). diff --git a/src/graph.h b/src/graph.h index a6fe00bb0..d6cb76128 100644 --- a/src/graph.h +++ b/src/graph.h @@ -2238,6 +2238,15 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null PROFILE_SCOPE_DESCRIBE( "buildGraph/1d: byName (name -> def ids)" ); for( const Symbol& s : ing.symbols ) { + if( s.kind == SymKind::Section && s.lang == Lang::Ruby ) + { + continue; // definitions-only (#339): a Ruby schema column answers --uses/--grep/--whereis as + // a def, but admits NO call edge — the byName table feeds both call binding and the + // common-name damping, so an untyped `response.code` must not become a column's + // caller, and 200 `name` columns must not damp a model's real `def name`. + // Mirrors contextratio.h's isMeasurableKind treatment of Sections. Edges may come + // later behind an evidence rule (receiver proven to be the owning model). + } byName[ s.name ].push_back( s.id ); } } diff --git a/src/ingest_cache.h b/src/ingest_cache.h index 0dea6f4d8..2e0b7a20f 100644 --- a/src/ingest_cache.h +++ b/src/ingest_cache.h @@ -279,30 +279,37 @@ constexpr std::uint32_t kCacheVersion = 25; // 25: #150 AND #157 (trai // (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 = 123; // bump on any grammar/.scm/extraction change - // 123 = 2026-09-25 (Rails schema-column capture, - // test/rubyschemacheck.sh, unreleased; carried 122 on the - // branch and renumbered 123 on rebase, because train-20's - // .astro work (below) took 122): a Ruby file whose - // tree holds a `create_table "x", … do |t| … end` call is a - // rendered db/schema.rb BY CONTENT, and every - // `t. "name"` / `t. :name` in the table block - // mints a SymKind::Section def (the data-kind slot, same - // as a doc heading / YAML key) at Lang::Ruby — so - // `record.price` call refs bind to the column and reach - // the call graph (a Rails corpus gains call edges + - // PageRank weight on every column, row). Also: the - // four-spelling id rule (implicit / id: false / id: :uuid / - // primary_key: "x") and `t.timestamps` -> created_at + - // updated_at. MIGRATIONS contribute nothing (class/module- - // nest gate). Review round at 123 (unreleased): a column - // call's receiver must BE the block parameter — a - // different bare-identifier receiver (`helper.string "x"`) - // names nothing; a 122/123 cache written before that - // round differs only there. No record layout change: - // kCacheVersion stays, kQSnapCacheScheme stays. A 121/122 - // cache written before this round differs only in the - // schema defs it lacks. +constexpr std::uint32_t kParserVer = 126; // bump on any grammar/.scm/extraction change + // 126 = the Rails schema-column capture + // (test/rubyschemacheck.sh, unreleased; reviewed round at + // 123/126 — carried 122 pre-rebase, renumbered once for the + // train-20 .astro collision and again to 126 at the + // maintainer's request: 123 is reserved for #325, 124 in + // flight, 125 queued for #338; the MERGE COMMIT sets the + // final number): a Ruby file whose tree holds a + // `create_table "x", … do |t| … end` call is a rendered + // db/schema.rb BY CONTENT, and every `t. "name"` / + // `t. :name` in the table block mints a + // SymKind::Section def (the data-kind slot, same as a doc + // heading / YAML key) at Lang::Ruby — DEFINITIONS ONLY: + // the defs answer --uses/--grep/--whereis, but + // buildGraph's byName skips Section-Ruby, so columns + // admit NO call edges and no PageRank weight (the + // "admission consequence" planned earlier was retracted + // after the maintainer measured unrelated-call binding + // and map takeover; edges may return behind an evidence + // rule). Also: the four-spelling id rule (implicit / + // id: false / id: :uuid / primary_key: "x"; an array or + // symbol primary_key suppresses the implicit id — a + // composite key has no id), `t.timestamps` -> created_at + // + updated_at, constraint DSL floors (check/ + // exclusion/unique_constraint name no column), the + // review-round receiver floor (a column call must be on + // the block parameter), and MIGRATIONS contribute + // nothing (class/module-nest gate). No record layout + // change: kCacheVersion stays, kQSnapCacheScheme stays. + // A 122/123/124/125 cache differs only in the schema + // defs and Section-Ruby edges it lacks. // 122 = 2026-09-25 (#320/#67, test/astrocheck.sh): .astro joins // kLangTable on the TypeScript grammar, parsed through ONE // included range over its `---` frontmatter; blank lines diff --git a/src/ingest_names.h b/src/ingest_names.h index cbd694741..dad39bf6a 100644 --- a/src/ingest_names.h +++ b/src/ingest_names.h @@ -1163,9 +1163,18 @@ inline void captureRubyAttrDefsCall( TSNode n, std::uint32_t fileId, std::string } ); } -inline void captureRubyAttrDefs( TSNode root, std::uint32_t fileId, std::string_view src, std::vector& defs ) -{ - if( src.find( "attr" ) == std::string_view::npos ) // file signal: every family name contains "attr" +// ONE pre-order walk of the whole tree dispatching BOTH Ruby def-side lanes — the class-level +// attribute DSL and the rendered-schema column capture — from the same explicit stack (the duplication +// the maintainer's quality-delta flagged: two byte-identical walkers). The per-file signals (contains +// "attr" / contains "create_table") decide which dispatch fires before the walk starts, exactly like +// each lane's former early-out. Everything else in each lane is unchanged. +// The schema table helper is defined with its lane below (this walker lives with the attr lane). +inline void rubySchemaTableCall( TSNode n, std::uint32_t fileId, std::string_view src, std::vector& defs ); +inline void captureRubyDefs( TSNode root, std::uint32_t fileId, std::string_view src, std::vector& defs ) +{ + const bool wantAttr = src.find( "attr" ) != std::string_view::npos; // every family name contains "attr" + const bool wantSchema = src.find( "create_table" ) != std::string_view::npos; // the schema gate name must appear + if( !wantAttr && !wantSchema ) { return; } @@ -1185,10 +1194,17 @@ inline void captureRubyAttrDefs( TSNode root, std::uint32_t fileId, std::string_ stack.pop_back(); if( kindIs( ts_node_type( n ), "call" ) ) { - const std::string_view fam = rubyNamedDirective( n, src, kRubyAttrFamilyNames ); - if( !fam.empty() && rubyAttrAtClassBodyLevel( n, src ) ) + if( wantAttr ) + { + const std::string_view fam = rubyNamedDirective( n, src, kRubyAttrFamilyNames ); + if( !fam.empty() && rubyAttrAtClassBodyLevel( n, src ) ) + { + captureRubyAttrDefsCall( n, fileId, src, fam, defs ); + } + } + if( wantSchema && fieldIdentifierText( n, NodeField::Method, src ) == "create_table" ) { - captureRubyAttrDefsCall( n, fileId, src, fam, defs ); + rubySchemaTableCall( n, fileId, src, defs ); } } collectChildren( n, cursor.cur, kids ); @@ -1200,15 +1216,18 @@ inline void captureRubyAttrDefs( TSNode root, std::uint32_t fileId, std::string_ } -// ─── parser version 122 (test/rubyschemacheck.sh): RUBY db/schema.rb COLUMNS ARE Section DEFS ─── +// ─── the Rails schema capture (test/rubyschemacheck.sh): RUBY db/schema.rb COLUMNS ARE Section DEFS ─── // A schema file is recognized BY CONTENT, never by path: any Ruby file whose tree holds a // `create_table "x", … do |t| … end` call — the call's `block:` field is a do_block whose `parameters:` // field is a block_parameters carrying ONE bare identifier — is a rendered-schema document (the block // is the create_table call's OWN do_block, found by named-child kind because the shared NodeField set -// has no Block enumerator). Path plays no part: a schema dumped inside `ActiveRecord::Schema[8.1] -// .define … do`, a migration's class-level table, or a plain top-level one all look identical to this -// walk — the container does not matter. The file signal below is a contains-"create_table" reject like -// the attribute-family capture's contains-"attr" reject; the semantic gate is the AST shape. +// has no Block enumerator). Path plays no part: a rendered dump sits at file level or under +// `ActiveRecord::Schema[].define … do`. WHAT A WALK OF THE FILE IS NOT: a MIGRATION wraps its +// create_table inside a class/module body, and rubySchemaTableCall's Gate 0 refuses every +// class/module-nested table — so the container DOES matter exactly there (the migration shape +// contributes ZERO defs, pinned by the three migration fixtures). The file signal below is a +// contains-"create_table" reject like the attribute-family capture's contains-"attr" reject; the +// semantic gate is the AST shape. // // Each `t. "name"` / `t. :name` receiver-CALL in the table's do-block body mints ONE // SymKind::Section def at Lang::Ruby — the same data-kind slot as a YAML key / doc heading (model.h; @@ -1236,11 +1255,11 @@ inline void captureRubyAttrDefs( TSNode root, std::uint32_t fileId, std::string_ // instead of the implicit `id` // Duplicate column NAMES across tables stay SEPARATE defs (distinct nameByte identities — the dedup // ladder only folds same-byte captures). -inline constexpr std::array kRubySchemaNonColumns = { "index", "references", "belongs_to", "polymorphic" }; +inline constexpr std::array kRubySchemaNonColumns = { "index", "references", "belongs_to", "polymorphic", "check_constraint", "exclusion_constraint", "unique_constraint" }; // The FIRST non-comment named child of an argument_list — the attribute-DSL comment posture ("attribute( # note // then :x"): a comment is an n-ary extra, never an argument. Null when the list is all comments/empty. -inline TSNode rubyFirstNonCommentArg( TSNode args, std::string_view /*src*/ ) noexcept +inline TSNode rubyFirstNonCommentArg( TSNode args ) noexcept { ChildCursor cursor( args ); TSNode first{}; @@ -1313,7 +1332,7 @@ inline void rubySchemaColumnCall( TSNode c, std::uint32_t fileId, std::string_vi { return; } - const TSNode nameNode = rubyFirstNonCommentArg( args, src ); + const TSNode nameNode = rubyFirstNonCommentArg( args ); if( ts_node_is_null( nameNode ) ) { return; @@ -1374,7 +1393,7 @@ inline void rubySchemaTableCall( TSNode n, std::uint32_t fileId, std::string_vie } // Gate 1: a STRING table name as the first argument (a symbol-named table is a migration spelling, // not a rendered-schema shape — stated floor; no name node → no schema surface either). - const TSNode tableName = rubyFirstNonCommentArg( args, src ); + const TSNode tableName = rubyFirstNonCommentArg( args ); if( ts_node_is_null( tableName ) || !kindIs( ts_node_type( tableName ), "string" ) ) { return; @@ -1486,6 +1505,13 @@ inline void rubySchemaTableCall( TSNode n, std::uint32_t fileId, std::string_vie defs.push_back( d ); return; } + // A primary_key pair with a NON-string value (composite `["a","b"]`, hand-written `:symbol`): the + // key is NOT an `id` and the rendered columns are the block's own explicit `t.` lines (already + // minted above) — suppress the implicit id entirely (the composite case is pinned by the fixture). + if( primaryKey ) + { + return; + } // Implicit `id` — anchored at the table-name string's content start (a stable per-table byte). { const std::uint32_t s = ts_node_start_byte( tableName ), e = ts_node_end_byte( tableName ); @@ -1496,40 +1522,7 @@ inline void rubySchemaTableCall( TSNode n, std::uint32_t fileId, std::string_vie } } -// The schema walk: ONE pass over the whole tree (the same explicit-stack shape as captureRubyAttrDefs — -// root's width is file-controlled, never index it, O(C²)). Every create_table-with-handle call in the -// file mints its table's defs; anything that is not such a call contributes nothing. -inline void captureRubySchemaDefs( TSNode root, std::uint32_t fileId, std::string_view src, std::vector& defs ) -{ - if( src.find( "create_table" ) == std::string_view::npos ) // file signal: the gate name must appear - { - return; - } - ChildCursor cursor( root ); - std::vector kids; - kids.reserve( 64 ); - std::vector stack; - stack.reserve( 64 ); - collectChildren( root, cursor.cur, kids ); // root's width is file-controlled — never index it (O(C²)) - for( std::size_t i = kids.size(); i > 0; --i ) - { - stack.push_back( kids[i - 1] ); - } - while( !stack.empty() ) - { - const TSNode n = stack.back(); - stack.pop_back(); - if( kindIs( ts_node_type( n ), "call" ) && fieldIdentifierText( n, NodeField::Method, src ) == "create_table" ) - { - rubySchemaTableCall( n, fileId, src, defs ); - } - collectChildren( n, cursor.cur, kids ); - for( std::size_t i = kids.size(); i > 0; --i ) - { - stack.push_back( kids[i - 1] ); - } - } -} + // F5: a Swift LOCAL binding — `let a = f()` / `var b = ...` inside a function/closure body — parses to the // same `property_declaration` node as a real stored/computed MEMBER property, so the @definition.var pattern diff --git a/src/ingest_sidecap.h b/src/ingest_sidecap.h index 8bfc93204..d6cd97722 100644 --- a/src/ingest_sidecap.h +++ b/src/ingest_sidecap.h @@ -2320,8 +2320,7 @@ void captureTagsFacts( TSQueryCursor* cursor, const LangEntry& le, std::uint32_t // same defs window, so the lex build and cache round-trip treat these defs like captured ones. if( le.lang == Lang::Ruby ) { - captureRubyAttrDefs( root, fileId, src, defs ); - captureRubySchemaDefs( root, fileId, src, defs ); + captureRubyDefs( root, fileId, src, defs ); } } diff --git a/src/model.h b/src/model.h index b6909530d..7ae8c1a46 100644 --- a/src/model.h +++ b/src/model.h @@ -83,8 +83,8 @@ inline const char* symTag( SymKind k ) noexcept case SymKind::Interface: return "iface"; case SymKind::Var: return "var"; case SymKind::Section: return "sec"; // data-kind defs: doc headings, JSON/YAML keys, and Ruby db/schema.rb - // columns (parser 122) — the Ruby ones admit call edges, so a Section - // is "isolated" only when its language is not the caller's (best-effort) + // columns — the Ruby ones are definitions only, NO call edges + // (graph.h buildGraph's byName skips them; see isMeasurableKind) case SymKind::Macro: return "macro"; // #define (disclosed-degraded: replacement text, not a parsed body) case SymKind::Field: return "field"; // member variable (id=path::Owner::field; use-sites via --uses=Owner.field) case SymKind::Other: return "other"; diff --git a/src/quality.h b/src/quality.h index 2c315f500..49c377971 100644 --- a/src/quality.h +++ b/src/quality.h @@ -2102,10 +2102,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 = 25; // MUST equal ingest.cpp's kCacheVersion (gated); 25 = #157 + #150 -constexpr std::uint32_t kIngestParserVerMirror = 123; // MUST equal ingest.cpp's kParserVer (gated) - // 123 = 2026-09-25 (Rails schema-column capture, see kParserVer note; - // carried 122 on the branch, renumbered on rebase after train-20's .astro - // work took 122; kIngestCacheVersionMirror stays 25) +constexpr std::uint32_t kIngestParserVerMirror = 126; // MUST equal ingest.cpp's kParserVer (gated) + // 126 = the Rails schema-column capture (see kParserVer note; + // definitions only — no Section-Ruby call edges; kIngestCacheVersionMirror stays 25) // 122 = 2026-09-25 (#320/#67, Astro frontmatter, see kParserVer note; // kIngestCacheVersionMirror stays 25) // 121 = 2026-09-24 (#310, Ruby attr DSL, see kParserVer note) diff --git a/test/qschemetrip.hash b/test/qschemetrip.hash index c9a87e8c2..3eb6cd3cd 100644 --- a/test/qschemetrip.hash +++ b/test/qschemetrip.hash @@ -1 +1 @@ -3250f2dc0c1ca4798555e6535d22ff45d76b043fe79ab02dd6dd38fdad4fd249 +4d698fff67c26f99118e2f03e02d558a93ceee11c4462cb9fe1981652bd21a1d diff --git a/test/rubyschemacheck.sh b/test/rubyschemacheck.sh index 4606b6f0b..4967ef9b7 100755 --- a/test/rubyschemacheck.sh +++ b/test/rubyschemacheck.sh @@ -1,13 +1,15 @@ #!/usr/bin/env bash -# rubyschemacheck.sh — parser version 122 gate: RUBY db/schema.rb COLUMNS ARE Section DEFS. The Rails -# schema capture: a Ruby file whose tree holds a `create_table "x", … do |t| … end` call is a rendered -# schema BY CONTENT (no path heuristic), and each `t. "name"` / `t. :name` receiver-call in +# rubyschemacheck.sh — the Rails schema capture gate: RUBY db/schema.rb COLUMNS ARE Section DEFS. A +# Ruby file whose tree holds a `create_table "x", … do |t| … end` call is a rendered schema BY CONTENT +# (no path heuristic), and each `t. "name"` / `t. :name` receiver-call in # the table block mints ONE SymKind::Section def at Lang::Ruby — the same data-kind slot as a doc # heading / YAML key (model.h) — with span = the name token. Rails-generated attribute uses -# (`product.price`, pinned here as explicit-receiver model calls) then bind to the column def set and -# reach the call graph and PageRank: the "admission consequence" the PR body discloses. The DSL CALLS -# keep their reference posture exactly like the attr family (--uses=string/define/timestamps stay -# defs=0 external=1). The four id spellings and t.timestamps name columns too, per the rules below. +# (`product.price`, pinned here as explicit-receiver model calls) resolve to the column as a def — +# DEFINITIONS ONLY by maintainer decision: the defs answer --uses/--grep/--whereis but admit NO call +# edges and no PageRank weight (graph.h buildGraph's byName skips Section-Ruby; §3 pins that). The DSL +# CALLS keep their reference posture exactly like the attr family (--uses=string/define/timestamps +# stay defs=0 external=1). The four id spellings and t.timestamps name columns too, per the rules +# below; a composite/symbol primary_key mints no implicit id. # # Fixture test/rubyschemafix — schema text from a real, running Rails 8.1 app's `bin/rails # db:schema:dump` (verified db:schema:load-able), with the original domain tables scrubbed to spike_* @@ -26,7 +28,7 @@ # migration_string/migration_symbol/migration_columns the migration INTERFERENCE floors: a # class-wrapped create_table (string- OR symbol-named) mints NOTHING — the schema is the only # source of column names, indexing a migration beside its schema would double every def and its -# PageRank weight — and add_column/remove_column are argument data, never defs (a column added +# — and add_column/remove_column are argument data, never defs (a column added # then dropped never registers) # floor_case where("name = ?") — a string fragment stays opaque, never a def or a use # all_four / pair_def_column / triple_* the def+attr+column(+yaml) collision matrix; the picker @@ -74,6 +76,15 @@ callershead(){ "$BIN" "$FIX" --callers="$1" --no-cache 2>/dev/null | grep -oE "o [ "$( useshead updated_at )" = 'of="updated_at" defs="2" external="0" count="0"' ] \ && ok 'capture: updated_at — both spellings def; no consumer calls it (floor proof the count is per-row, not padded)' \ || no "capture: updated_at: $( useshead updated_at )" +[ "$( useshead origin )" = 'of="origin" defs="1" external="0" count="0"' ] \ + && ok 'capture: a COMPOSITE primary_key (["origin","destination"]) mints the rendered columns — and NO implicit id (the id defs=14 arm above is the regression guard)' \ + || no "capture: origin: $( useshead origin )" +[ "$( useshead destination )" = 'of="destination" defs="1" external="0" count="0"' ] \ + && ok 'capture: the composite key'"'"'s second column mints too' \ + || no "capture: destination: $( useshead destination )" +undefinable 'price > 0' \ + && ok "floor: t.check_constraint \"price > 0\", name: … — a constraint EXPRESSION names no column (would otherwise mint n=\"price > 0\")" \ + || no 'floor: price > 0 — a check_constraint expression minted a def' # ── 2. FLOORS: the DSL calls stay references; non-column DSL names no column ─────────────────────── undefinable owner \ @@ -103,22 +114,25 @@ for dsl in string define datetime timestamps create_table; do || no "posture: $dsl gained a def — the DSL call must stay a reference: $( useshead "$dsl" )" done -# ── 3. BINDING: model call sites reach the column def set (the admission consequence) ─────────────── +# ── 3. DEFINITIONS ONLY: columns admit NO call edges (maintainer decision, graph.h buildGraph byName) ── +[ "$( callershead id )" = 'of="id" defs="14" count="0"' ] \ + && ok 'defs-only: `IdDefault.new.id` matches the 14 defs but has NO caller row — columns take no edges' \ + || no "defs-only: id callers: $( callershead id )" +[ "$( callershead created_at )" = 'of="created_at" defs="2" count="0"' ] \ + && ok 'defs-only: `Timestamps.new.created_at` matches both defs but carries no call edge' \ + || no "defs-only: created_at callers: $( callershead created_at )" +[ "$( callershead ref )" = 'of="ref" defs="1" count="0"' ] \ + && ok "defs-only false-binding: a COLUMN-ONLY name (ref) gains no caller from an unrelated 'obj.ref' style site — pre-change it did bind (untyped receivers)" \ + || no "defs-only: ref callers: $( callershead ref )" [ "$( callershead name )" = 'of="name" defs="19" count="3"' ] \ - && ok 'bind: the 3 name call sites reach the 19-def set — columns admit call edges (the Lang::Ruby admission)' \ - || no "bind: name callers: $( callershead name )" -[ "$( callershead id )" = 'of="id" defs="14" count="1"' ] \ - && ok 'bind: `IdDefault.new.id` sits on the 14-table implicit-id def set' \ - || no "bind: id callers: $( callershead id )" -[ "$( callershead created_at )" = 'of="created_at" defs="2" count="1"' ] \ - && ok 'bind: `Timestamps.new.created_at` reaches BOTH created_at defs (rendered column + literal call)' \ - || no "bind: created_at callers: $( callershead created_at )" + && ok 'defs-only: the 3 name caller rows come from the METHOD/attr defs only — the 9 columns contribute zero edges to the 19-def set' \ + || no "defs-only: name callers: $( callershead name )" # ── 4. AMBIGUITY + LOCALITY: the resolver splits honestly, pins on evidence ───────────────────────── "$BIN" "$FIX" --uses=name --no-cache 2>/dev/null >"$TMP/uses_name" grep -q 'of="name" defs="19" external="0" count="3"' "$TMP/uses_name" \ - && grep -q 'graph_ambiguous="4"' "$TMP/uses_name" \ - && ok 'ambiguity: the name graph_ambiguous gauge counts exactly the 4 splitting sites (rich/new/rec receivers + id + created_at), never a silent pin' \ + && grep -q 'graph_ambiguous="2"' "$TMP/uses_name" \ + && ok 'ambiguity: the name gauge counts ONLY the 2 sites that still split over real (method/attr) defs — the id/created_at column sites left the gauge once columns stopped taking edges' \ || no 'ambiguity: name gauge/rows wrong' grep -q 'p="consumer_ambiguous.rb:8"' "$TMP/uses_name" \ && ok 'ambiguous arm: rec.name on a plain local SPLITS over the whole def set (its row is in the gauge)' \ @@ -126,9 +140,9 @@ grep -q 'p="consumer_ambiguous.rb:8"' "$TMP/uses_name" \ grep -q 'p="pair_def_column.rb:15"' "$TMP/uses_name" \ && ok 'locality arm: self.name inside PairDefColumn pins the in-class def — its row exists and is NOT in the ambiguous gauge' \ || no 'locality arm: pair_def_column.rb:15 row missing' -grep -q 'p="column_consumers.rb:6"' "$TMP/uses_name" \ +grep -q 'p="column_consumers.rb:7"' "$TMP/uses_name" \ && ok 'binding arm: SingleColumn.new.name is a real use row (explicit model receiver)' \ - || no 'binding arm: column_consumers.rb:6 row missing' + || no 'binding arm: column_consumers.rb:7 row missing' # ── 5. MAP KINDS: the defs carry t="sec" with the DSL row family merged by name ───────────────────── "$BIN" "$FIX" --no-cache 2>/dev/null >"$TMP/map" @@ -152,6 +166,9 @@ grep -q ' 0", name: "check_positive" + end + + create_table "spike_composite_keys", primary_key: ["origin", "destination"], force: :cascade do |t| + t.string "origin" + t.string "destination" end create_table "spike_single_columns", force: :cascade do |t| From 221afc08562d5c9b0c00d0a499f71bf6b6a9f751 Mon Sep 17 00:00:00 2001 From: Michal Papis Date: Sun, 27 Sep 2026 11:39:08 +0200 Subject: [PATCH 004/170] =?UTF-8?q?ci:=20empty=20=E2=80=94=20re-probe=20th?= =?UTF-8?q?e=20macOS=20Release=20trace-guard=20(B1)=20outcome=20on=20an=20?= =?UTF-8?q?unchanged=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 3fac666f4ea84429a018668c7b03a18443631ae5 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 09:50:18 +0000 Subject: [PATCH 005/170] =?UTF-8?q?feat(paging):=20the=20budgeted-bundle?= =?UTF-8?q?=20candidate=20page=20(for=20/=20pack-task)=20+=20explore=20lim?= =?UTF-8?q?it=20parity=20=E2=80=94=20PoC=20for=20issue=20#294?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(truncvocabcheck): arm (H) — the budgeted-bundle continuation contract (issue #294), red vs the pre-change binary Gate-first for the paging PoC (upstream redhat-et issue #294): the arm is written BEFORE the code and is RED on the pre-feature binary, which is the intended state (runtracecheck.sh discipline). It greens when the byte-budgeted --for/--pack-task bundle accepts --offset and carries the pageview quintet. SCOPE CORRECTED BY MEASUREMENT (this is the discovery that changes issue #294): - --for=TASK --limit=N (+offset=M) ALREADY pages: the file-grain widening page, full pageview quintet, owned by forwidencheck — not re-gated here (§6.4). - The MCP 'for' verb serves that widening page today (limit=5 → ); MCP 'explore' refuses limit entirely. - --for --token-budget=N --offset=M REFUSES: the byte-budgeted compact bundle is the actual open gap — cut disclosed (over_ce ceiling= reason=) with no continuation handle. Arm (H) guards exactly that, plus the offset=0 byte-identity regression floor (acceptance criterion 2). Arms: quintet presence on paged budgeted roots (for_budget_full red on the ignored-offset=0 answer; for_budget_page/pack_budget_page red on the refusal), capped 0|1 bit, shown<=total coherence, the byte-identity floor, and a mutation control (a bare paged root with no has_more=/next_offset= IS detected). Existing sweep assertions untouched: the (F) vocabulary arm and its mutation control still PASS on the same run. * test(listingpagingcheck): arm (G) — the budgeted-bundle continuation seam (issue #294), red vs the pre-change binary Gate-first, second arm of the paging PoC. The seam contract for the budget-mode continuation once it exists: --offset=N --limit=M must be the EXACT continuation of the previous page over the ranked candidate set — page[0:3]+page[3:6] == page[0:6] in candidates, rank order, no gap, no overlap — the same contract arm (A) holds for rows, measured on the sig rows of the paged budgeted bundle. RED on the pre-feature binary, intended (today --for --token-budget refuses --offset/--limit; the arm records the refusal). Mutation control: an overlapping fabricated page pair IS detected — the arm can fail. Affected-reference note for the implementation phase: pagingsweepcheck owns the honoring/not-ignored contract — its arm (L) already lists --for (the file page, 2026-09-12 L-W) and pins the BARE --for root as quintet-free, which the budgeted continuation preserves (quintet only on PAGED budgeted answers). If the feature updates --help's HONORED-by paragraph for budget-mode paging, pagingsweepcheck's arm (L) universe check must be reconciled in the same commit. * test(paging PoC): arms C/D/E/F + explore-parity — the complete gate-first phase, red vs the pre-change binary Completes the gate-first phase for the budgeted-bundle continuation (upstream issue #294, corrected scope). Every arm is RED on the pre-feature binary with a green mutation control, and every pre-existing arm in its host gate stays green on the same run. Home corrections vs the v2 spec, per §6.4 (the gate that owns the subject takes the arm): - ARM C (within-page rule-5 disclosure) folded into truncvocabcheck's arm (H) — that gate owns the budgeted-section vocabulary (§B8.3). v2's handoffcheck home was a mis-assignment (it gates --handoff, a different frozen spec). - ARM F (cliff invariance across pages, CONTINGENT on the #294 ruling — it encodes the proposed compute-once answer and is rewritten if maintainers pick per-page re-cliff) folded into arm (H), which already parses the paged docs; adaptivecutshapecheck keeps gating the pure adaptiveCut shape. - ARM D -> budgetpolicycheck: per-page est_tokens<=budget; a past-the-end page is shown="0" capped="1" has_more="0" (pageview.h past-the-end contract). - ARM E -> packtaskcheck: continuation pages carry their OWN tier marker (the packtask.h:1690 two-dialects-one-count lesson); offset=0 byte-identical to the un-paged pack-task. - (6d) explore-parity -> mcpverbscheck: explore refuses limit today (unknown field) while for serves the bounded widening page; the arm records the refusal and demands the same mcpverbs.h:327-333 argument path. Also noted for the implementation phase: pack-task's --offset=0 is accepted-and-ignored today (byte-identity floor passes trivially); the nonzero offset refusal is where the continuation must land. Environment: xmllint installed (libxml2-utils) so packtaskcheck runs its full F5 arms locally. * feat(paging): the --for budgeted-bundle candidate page (issue #294) — renderer + guard carve, arms (G)/(D) green, (H) green for --for The first implementation slice of the paging PoC (upstream issue #294, corrected scope). --for under --token-budget with an explicit --limit/--offset now serves a resumable page over the SAME ranked candidate set the bundle cuts: - window [offset, offset+limit) of the positive-score candidates, (score desc, id asc) — the same total order packSignatures selects with (radixSortByScoreDescId); - packed by the SAME sigs emitter (redaction, notes, doc excerpting, H1 ladder all reused — a window, not a new surface); - the pageview quintet spliced onto the opening (the rootOpenStr.insert idiom; the open tag is byte-pinned by forbudgetmonotoncheck, which is why the splice happens after render, never inside serialize.h); - tier="head"/"below-cliff" from forCut.cliffRank — computed ONCE, the same statistic --adaptive and the confidence disclosure read (arm F's invariance holds by construction); - est_tokens MEASURED from the page's own final bytes, two-pass to close the digit-count feedback loop. The UN-PAGED bundle is untouched and byte-identical (forbudgetmonotoncheck PASS on this change): --offset=0 ALONE stays the un-paged bundle — its resume point is its own , so an agent resumes with --offset=. The file page keeps every windowless-budget call exactly as forwidencheck pins it. cli.h: the kTokenBudgetGuard refusal now exempts --for/--pack-task beside an explicit window (the pages-AND-shapes-by-budget class --pr-context held alone), and the guard sentence says so. Red→green evidence, and what the process caught: - arms (G) seam: 5 real rows, page[0:3]+page[3:6]==page[0:6], no overlap — ALL PASS (the arm's first cut matched /, found 0 rows, and produced a VACUOUS green; fixed with the real tag + an anti-vacuous row guard) - arm (D): est_tokens<=budget per page; past-the-end is shown="0" capped="1" has_more="0" — ALL PASS - arm (H): the --for captures green (quintet + offset=0-alone byte-identity floor); pack_budget_page stays RED by design — the --pack-task slice is next - smoke-caught bugs: escapeXml scratch-buffer reuse mangled route= (per-attr buffers); the shown= digit parse was off by one ("shown=" is 7 chars); fixed char[160]/char[96] buffers truncated has_more=/root= mid-attribute on longer tasks (std::string assembly now — caller-controlled task length has no sane fixed size) - determinism: the page is byte-identical across runs (diff -q) Still red, by design, awaiting their slices: --pack-task (arm E + (H)'s pack_budget_page) and MCP explore limit parity (mcpverbscheck 6d). * feat(paging): the --pack-task candidate page + MCP explore limit parity (issue #294) — all six arms green Completes the paging PoC implementation. Three slices: 1. SHARED RENDERER: the --for candidate page (commit 0fb9397c0) is refactored into emitForCandidatePage( ing, lensRank, forCut, task, routeNote, rootArg, redactPtr, tokenBudget, pageLimit, pageOffset ) — one implementation, two callers (runForLens, runPackTask), so the two bundle verbs cannot serve a different page (the file's own one-implementation rule). 2. --pack-task: budget + explicit window pages its ranked candidates (same lensRank, lr.routeNote, one adaptiveCut computed ONCE — the same call --for's disclosure reads). --partition + window refuse together (a partitioned bundle has N+1 slices, no single candidate order). cli.h: --pack-task joins honorsPaging ONLY under budget+window — the bare bundle still refuses the pair. 3. MCP explore: limit/offset parity with the for twin — the same mcpPageArgs bounded pair, the same file-grain widening page (computeForFilePage/ renderForFilePageXml on the same lr), one kMcpVerbFields row driving the inputSchema, the unknown-field refusal and mcpw3fixcheck together. partition + limit/offset refuse together, mirroring the CLI. GATE-FIRST EVIDENCE (all arms were red vs the pre-change binary, commit 75b8c70a6): - truncvocabcheck (H): ALL PASS — quintet, within-page rule-5, cliff invariance, offset=0-alone byte-identity floor, mutation control - listingpagingcheck (G): ALL PASS — real seam, 5 rows, no gap/overlap - budgetpolicycheck (D + A): ALL PASS — est_tokens<=budget per page; past-the-end is shown=0 capped=1 has_more=0; the guard sentence still parses - packtaskcheck (E + F1..F5): ALL PASS — tier marker on continuation pages, offset=0 byte-identical - mcpverbscheck (6b/6c/6d + all): ALL PASS — explore now serves the quintet page - mcpw3fixcheck: ALL PASS — declared fields match the live inputSchemas - forwidencheck: ALL PASS — arm (6) REWRITTEN in the same commit as the behavior change: --token-budget beside a window no longer refuses (the combination now has a defined meaning, the candidate page); the arm now pins the STRONGER contract (budget+window answers -rooted with the quintet, so the file page stays budgetless by construction, never by refusal). This is a deliberate contract change, not weakened coverage. - shapingflagcheck, forbudgetmonotoncheck: ALL PASS — the guard buckets and the byte-pinned un-paged sigs ladder untouched. Determinism: --for, --pack-task and explore pages are byte-identical across runs (diff -q). Well-formedness: xmllint --noout clean on all page shapes. * fix(paging): reword the escape-buffer comment — prose drifted the fixedbufsweep mentions pin CI caught what the local sweep could not see: fixedbufsweep arm (S6) pins the count of lines mentioning snprintf/formatTo in src/ — comments included — and this branch's comment naming formatTo in prose drifted it 333→334 on every platform and shard. The gate's own instruction names the rule: do not bump the number without re-reading the sites; the honest fix is prose that does not drift a pinned enumeration — the comment now says 'one emit call's argument list'. * fix(rebase): reconstruct verbs_for.h from pristine upstream + the four paging transforms The rebase onto the discarded main (c001c477c) hit a structural conflict: upstream's new renderForHdrRowsXml landed at the exact anchor our refactor inserted the shared emitForCandidatePage inline, and the hunk-resolution glue nested our function inside upstream's mid-body cut (three function-definition errors + parser cascades). Reconstruction, deterministic: upstream's pristine verbs_for.h + four transforms applied programmatically — (1) the budget-aware file-page dispatch condition, (2) the shared inline after renderForHdrRowsXml's true close, (3) the runForLens call site after deriveForConfidence (which upstream evolved to three parameters — kept), (4) the runPackTask page branch before the partition anchor — plus the 29b29808d comment reword (a prose 'formatTo' had drifted fixedbufsweep's pinned mentions enumeration 333→334, the root cause of all 19 PR-3 CI failures). Verified on the reconstructed tree: 10/10 gates ALL PASS (fixedbufsweep, truncvocabcheck, listingpagingcheck, budgetpolicycheck, packtaskcheck, mcpverbscheck, mcpw3fixcheck, forwidencheck, shapingflagcheck, forbudgetmonotoncheck) — the byte-pinned un-paged sigs ladder included. * test(modifierguardcheck): budget+window (--for --limit --token-budget) answers with the candidate page — arm rewritten with the behavior change CI caught the sweep-miss: modifierguardcheck's guard-table row pinned the OLD kTokenBudgetGuard refusal for --token-budget beside --for --limit. The carve removed that refusal (the combination now has a defined meaning — the budgeted-bundle candidate page), so the row's $NOROOT trick (the old guard fired BEFORE root resolution) died on the bad root with usage output — the 'wrong message' it reported. The replacement pins the STRONGER contract on a REAL corpus: exit 0, a root carrying the pageview quintet — the same seam forwidencheck arm (6) holds from its side. Sweep-miss correction: the guard-surface gates beyond the original ten are now swept locally and green — modifierguardcheck, pagingsweepcheck, argvdiffcheck, tokenbudgetcheck, fordisclosurecheck, helpbudgetcheck (16 gates total ALL PASS on this head). * fix(paging): sink-form DISCLOSE for the candidate page's degrade paths — the page document is the sink selfcheckcheck arm (R) caught what my 16-gate sweep could not count: two sink-less DISCLOSE( msg ) sites in emitForCandidatePage raised the pinned enumeration 49 -> 51. The contract (Diagnostics.h §4b): the sink is the object whose field the emitter reads — so the page IS the sink. ForPageRender models DisclosureSink (two scoped-enum reasons: ChargeStreamRefused / ChargeStreamTorn) and the emitter READS it: on a degraded render, the page ships with the full quintet, zero rows, and reason= naming why the render was lost — the release reader is told IN the answer, never by a stderr-only note that ships nothing. The reword discipline repeated on me: this fix's own comment named the counted emitter-API tokens in prose, drifting fixedbufsweep's pin AGAIN (344 -> 345 on this base) — reworded to name no counted token. Lesson recorded in the comment itself: prose that NAMES a counted token IS a counted line. Verified: selfcheckcheck PASS (pin 49 restored), fixedbufsweep PASS (344 held), truncvocabcheck PASS (the page contract arms), and the happy-path smoke unchanged. * fix(mcpmanifest): move the tools/list ceiling for explore's declared limit/offset — 46,600 -> 46,700, measured 46,677 B mcpmanifestcheck arms (1)/(1b): explore's limit/offset schema properties (+184 B, 46,493 -> 46,677) crossed the 46,600 B per-session ceiling and the CHANGELOG's pinned [Unreleased] manifest-growth claim. Rule 5's sanctioned case, applied exactly as the gate's own RE-ANCHORED precedents prescribe: a ceiling moves UP only for a DECLARED argument the contract obliges to carry a description, in the commit that lands it, with its bytes attributed in the gate header — never for prose. The attribution entry names the ask (issue #294 parity, PR #3), the two properties, the measured bytes, and the 23 B headroom. CHANGELOG's claim re-derived to the live measurement (46,677 B) so arm (1b) asserts the pair stays matched. Verified: mcpmanifestcheck ALL PASS (ceiling + claim arms). --------- Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com> --- CHANGELOG.md | 15 ++- src/cli.h | 16 ++- src/mcp.h | 18 ++- src/mcprefusal.h | 5 +- src/mcpverbs.h | 16 ++- src/verbs_for.h | 224 ++++++++++++++++++++++++++++++++++++- test/budgetpolicycheck.sh | 56 ++++++++++ test/forwidencheck.sh | 13 ++- test/listingpagingcheck.sh | 71 ++++++++++++ test/mcpverbscheck.sh | 42 +++++++ test/modifierguardcheck.sh | 14 ++- test/packtaskcheck.sh | 56 ++++++++++ test/truncvocabcheck.sh | 110 ++++++++++++++++++ 13 files changed, 641 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 833ea7c58..20e9b50ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2446,13 +2446,16 @@ Both serve the CLI's own renderer rather than a second implementation of it: Both declare `legend`, so they take their posture per request like the rest of the family — absent means compact, `legend:"full"` restores the prose legend byte-for-byte, and the rows do not move between the two. -The `tools/list` manifest grows 43,500 → 46,493 B (method: `test/mcpmanifestcheck.sh`'s own formula — the +The `tools/list` manifest grows 43,500 → 46,677 B (method: `test/mcpmanifestcheck.sh`'s own formula — the compact JSON length of the served `tools` array — over one `tools/list` response on this repository's binary -before and after; its 46,600 B ceiling is unmoved, with 107 B of headroom — the 46,371 B this entry first -recorded was superseded, same round, by a `rank_by` description fix (`test/mcpmanifestcheck.sh`'s own -RE-ANCHORED/TRAIN 10 FIX ROUND comment history), and this prose copy went uncorrected for five days before -being re-derived by hand; `test/mcpmanifestcheck.sh` arm `(1b)` now asserts this figure against a live -`tools/list` measurement so that rot is a gate failure, not a future rediscovery). +before and after; the ceiling moves 46,600 → 46,700 B for the one sanctioned case that moves it: explore's +`limit`/`offset` join `for`'s as DECLARED arguments (upstream issue #294's parity ask, PR #3) — two schema +properties, +184 B over the 46,493 B the previous round recorded, with 23 B of headroom — the 46,371 B this +entry first recorded was superseded, same round, by a `rank_by` description fix +(`test/mcpmanifestcheck.sh`'s own RE-ANCHORED/TRAIN 10 FIX ROUND comment history), and this prose copy went +uncorrected for five days before being re-derived by hand; `test/mcpmanifestcheck.sh` arm `(1b)` now +asserts this figure against a live `tools/list` measurement so that rot is a gate failure, not a future +rediscovery). ### Fixed — `rank_by` over MCP no longer answers a different ranking than `--rank-by` on a tree with uncommitted changes diff --git a/src/cli.h b/src/cli.h index 373fd9ed9..5a5d5a035 100644 --- a/src/cli.h +++ b/src/cli.h @@ -3735,7 +3735,11 @@ inline bool honorsPaging( const Config& c ) noexcept // FILE-GRAIN widening page. Membership is conditional on purpose: the bare --for bundle keeps honoring // --token-budget/--max-tokens/--format=candidates --top-k, which validateShapingFlagsHonored refuses on // every paging member — and refuses beside the page too, where no byte ceiling exists to shape against. - || ( !c.forTask.empty() && ( c.pageLimit > 0 || c.pageOffset > 0 ) ); + || ( !c.forTask.empty() && ( c.pageLimit > 0 || c.pageOffset > 0 ) ) + // PAGING-POC (issue #294): --pack-task joins ONLY under a budgeted window — its candidate page + // mirrors --for's (the shared emitForCandidatePage). The bare --pack-task bundle keeps refusing + // the pair (it has no window to serve), and a windowless budget run is untouched. + || ( c.packTaskFlag && c.tokenBudget != 0 && ( c.pageLimit > 0 || c.pageOffset > 0 ) ); } // --limit/--offset on a verb that windows NOTHING. Same accept-then-silently-ignore class as every guard in @@ -3911,7 +3915,8 @@ inline constexpr PagingFamilyFlagGuard kMaxTokensGuard inline constexpr PagingFamilyFlagGuard kTokenBudgetGuard { "--token-budget is honored by the default map (the CI gate), --for, --pack-task, --recall, " - "--handoff, --from-trace, --run-trace and --pr-context — none of them, --pr-context aside (it pages AND shapes by budget), " + "--handoff, --from-trace, --run-trace and --pr-context — none of them, --pr-context and --for/--pack-task beside an explicit " + "--limit/--offset window aside (those page AND shape by budget: the budgeted-bundle candidate page, issue #294), " "in the --limit/--offset-honoring set (", ")", "no byte budget to gate", @@ -3944,7 +3949,12 @@ inline void validateShapingFlagsHonored( Config& c ) noexcept { refusePagingFamilyFlag( c, kMaxTokensGuard ); } - if( c.tokenBudget != 0 && !c.prContext ) + // PAGING-POC (issue #294): --for/--pack-task under an EXPLICIT --limit/--offset window page their + // budgeted bundle (the candidate-offset continuation) — they page AND shape by budget, the same + // class --pr-context holds alone today. The refusal skips that combination only; every other + // budgeted verb, and the bare bundles, refuse exactly as before. + const bool budgetWindowPages = ( !c.forTask.empty() || c.packTaskFlag ) && ( c.pageLimit > 0 || c.pageOffset > 0 ); + if( c.tokenBudget != 0 && !c.prContext && !budgetWindowPages ) { refusePagingFamilyFlag( c, kTokenBudgetGuard ); } diff --git a/src/mcp.h b/src/mcp.h index aa31220ee..b09d8464f 100644 --- a/src/mcp.h +++ b/src/mcp.h @@ -2540,6 +2540,20 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo // `pack_task` is the dispatch-only alias (same branch, not separately advertised in tools/list above). else if( ( name == "explore" || name == "pack_task" ) && !path.empty() && !task.empty() ) { + // PAGING-POC (issue #294, ask (b)): explore's limit/offset — the same bounded pair the + // `for` twin takes (mcpPageArgs → pageWindow), refused-together with partition: a + // partitioned bundle has N+1 slices and no single page to walk. + const McpPageParse explorePage = mcpPageArgs( args ); + if( !explorePage.refusal.empty() ) + { + resp = errResultMsg( -32602, explorePage.refusal ); + } + else if( ( explorePage.page.limit > 0 || explorePage.page.offset > 0 ) && partitionCount > 0 ) + { + resp = errResult( -32602, "partition and limit/offset are mutually exclusive — a partitioned bundle has N+1 slices and no single page to walk" ); + } + else + { // W3FIX H4: `partition` used to be a bare `std::uint32_t( partitionArg )` cast — N3's // radius bug, one field over — so partition:4294967299 ran a 3-way fan-out and // partition:2^32 served a single bundle, both silently. The band is a DECLARED domain now @@ -2550,7 +2564,9 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo static_assert( kMcpRecallTopKMax == 1000, "the top_k refusal names the band 1..1000 in mcprefusal.h's kMcpValueFields and in the " "tools/list memory_recall stanza — move all three together" ); - resp = textResult( packTaskText( path, task, budgetTokens, redactPtr, partitionCount, noRoute ) ); + resp = textResult( packTaskText( path, task, budgetTokens, redactPtr, partitionCount, noRoute, + explorePage.page.limit, explorePage.page.offset ) ); + } } // L4: `from_trace` — maps a pasted stack-trace/sanitizer/compiler-error TEXT onto indexed symbols // (fromTraceBundleText, tracelocus.h) — the SAME assembler --from-trace's CLI path calls. diff --git a/src/mcprefusal.h b/src/mcprefusal.h index f69fe9fcf..429881c51 100644 --- a/src/mcprefusal.h +++ b/src/mcprefusal.h @@ -1057,7 +1057,10 @@ inline constexpr McpVerbFields kMcpVerbFields[] = { { "affected", "path paths files legend" }, { "path_between", "path paths from to legend" }, { "connect", "path paths symbols radius legend" }, - { "explore", "path paths task budget_tokens partition legend no_route" }, + // PAGING-POC (issue #294, ask (b)): limit/offset are DECLARED because explore now HONORS them — the + // same file-grain widening page the `for` twin serves (one inputSchema row drives this table, the + // tools/list stanza and mcpw3fixcheck's enumeration together). + { "explore", "path paths task budget_tokens partition legend no_route limit offset" }, { "from_trace", "path paths trace budget_tokens legend" }, // 2026-09-10: limit/offset are DECLARED here because the verb now HONORS them (mcpPageArgs -> the // unflagged-row window in editcheck.h), the same rule the `impact`/`uses` rows above state. They diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 7cc680277..206d47c9c 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -4132,7 +4132,8 @@ inline std::pair qualityBaselineJson( const std::strin // value outside 2..16, which is silently clamped OFF rather than erroring an otherwise valid explore call) // ⇒ the plain single-bundle form, byte-identical to before. inline std::string packTaskText( const std::string& root, const std::string& task, std::size_t budgetTokens, - RedactCounts* redact = nullptr, std::uint32_t partitionCount = 0, bool noRoute = false ) + RedactCounts* redact = nullptr, std::uint32_t partitionCount = 0, bool noRoute = false, + int pageLimit = 0, int pageOffset = 0 ) { const McpIndex& ix = getIndex( root ); const IngestResult& ing = ix.ing; @@ -4161,6 +4162,19 @@ inline std::string packTaskText( const std::string& root, const std::string& tas // §L10b + verify-wave2 F6: same trim as the other route= construction sites — neither bracket. lr.routeNote = routeNoteOf( rc, shape, noRoute ); // row 6: the route CODE, ONE producer (filter.h) + // PAGING-POC (issue #294, ask (b)): explore's limit/offset parity with `for` — the FILE-GRAIN WIDENING + // PAGE the for twin serves (computeForFilePage/renderForFilePageXml, forpage.h) on THIS lr, so the two + // dialects cannot serve a different page (the same one-implementation rule the for twin's L-W block + // states). Its own root; nothing below runs. + if( pageLimit > 0 || pageOffset > 0 ) + { + const std::string_view mcpRootArg = ing.realPaths.empty() ? std::string_view( root ) : std::string_view(); + const ForFilePage filePage = computeForFilePage( ing, lr.rank, lr.evidence ); + const std::string pageRootOpen = ctxRootOpen( task, lr.routeNote, mcpRootArg ); + return renderForFilePageXml( ing, filePage, ForPageRenderParts{ task, pageRootOpen, forCoveragePct( lr.evidence, topLensId( lr.rank ) ), + pageLimit, pageOffset, mcpRootArg, /*compactLegend=*/false } ); + } + // input blow-up guard disclosure (lexical.h kMaxUniqueQueryTerms/dedupeQueryTerms) — same channel/ // attribute names as the other two --for/--pack-task surfaces. { diff --git a/src/verbs_for.h b/src/verbs_for.h index dfe6a6967..dc4b23094 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2214,6 +2214,201 @@ inline std::string renderForHdrRowsXml( const rw::IngestResult& ing, const std:: return x; } +// ── THE BUDGETED-BUNDLE CANDIDATE PAGE (paging PoC, upstream issue #294, corrected scope) ─────────── +// Shared by runForLens (--for) and runPackTask (--pack-task): the caller has ALREADY decided this run is +// a budgeted page (--token-budget != 0 AND an explicit --limit/--offset window) and passes its own +// lensRank, cliff statistic (adaptiveCut, computed ONCE by the caller — the same statistic --adaptive and +// the confidence disclosure read), task, route note, root argument and redaction seam. The page serves a +// window of the ranked candidates through the SAME sigs emitter, splicing the pageview quintet onto the +// opening after render (the open tag is byte-pinned by forbudgetmonotoncheck — serialize.h never +// gains a paging attribute). The UN-PAGED bundle the callers serve otherwise is untouched and +// byte-identical: its resume point is its own . +inline int emitForCandidatePage( const rw::IngestResult& ing, const std::vector& lensRank, const rw::AdaptiveCut& forCut, + std::string_view task, std::string_view routeNote, std::string_view rootArg, + rw::RedactCounts* redactPtr, std::size_t tokenBudget, int pageLimit, int pageOffset ) +{ + using namespace rw; // the file's idiom: function-scoped (verbs_for.h:88 and five more) + // the ranked candidate order: ALL ids sorted (score desc, id asc) — the same total order + // packSignatures selects with — truncated to the positive-scored candidates (the set the + // bundle's cut was against; total= counts it on EVERY page, so a re-decided denominator is + // impossible by construction). + std::vector candOrder; + { + const std::size_t S = ing.symbols.size(); + candOrder.resize( S ); + for( NodeId i = 0; i < S; ++i ) { candOrder[ i ] = i; } + rw::sortutil::radixSortByScoreDescId( candOrder, lensRank ); + candOrder.resize( std::min( forCut.positiveHits, S ) ); + } + const std::size_t candidateTotal = candOrder.size(); + const PageWindow win = pageWindow( candidateTotal, pageLimit, pageOffset ); + + // window the rank copy: candidates inside [win.begin, win.end) keep their score, everything + // else sorts below them (-1), so packSignatures' top-N selection serves exactly the window in + // rank order — no new selection logic, and the H1 ladder still trims within the page. + std::vector pageRank( ing.symbols.size(), -1.0f ); + for( std::size_t i = win.begin; i < win.end; ++i ) { pageRank[ candOrder[ i ] ] = lensRank[ candOrder[ i ] ]; } + + std::string sigsDoc; + const std::size_t sigsBytesBudget = tokenBudget > 0 + ? ( std::size_t( double( tokenBudget ) * kMinBytesPerToken ) > 600 + ? std::size_t( double( tokenBudget ) * kMinBytesPerToken ) - 600 : 0 ) + : 0; + // SINK FORM (Diagnostics.h §4b — the contract selfcheckcheck (R) pins): the page document is the + // sink. The degrade field is what the emitter reads and surfaces as reason= on the page itself, + // so a degraded render tells the release reader IN THE ANSWER — never silently-empty stdout, + // never a sink-less stderr-only note (selfcheckcheck: the pin goes DOWN, never up). + struct ForPageRender + { + enum class DisclosureWhy : std::uint8_t { ChargeStreamRefused, ChargeStreamTorn }; + DisclosureWhy why = DisclosureWhy::ChargeStreamRefused; + void disclose( DisclosureWhy w ) noexcept { why = w; } + } forPageRender; + bool rendered = false; + { + // inline capture (preRender the lambda at 2478 is declared below this block): the same + // MemoryStream + charge-stream + DISCLOSE-on-failure discipline, kept local so the page + // does not depend on declaration order. + const std::optional redactBefore = redactPtr != nullptr ? std::optional( *redactPtr ) : std::nullopt; + const auto restoreRedact = [ & ]() noexcept { if( redactBefore ) { *redactPtr = *redactBefore; } }; + rw::MemoryStream stream; + std::FILE* const buffer = rw::openChargeStream( stream ); + if( buffer == nullptr ) + { + DISCLOSE( forPageRender, ForPageRender::DisclosureWhy::ChargeStreamRefused ); + restoreRedact(); + } + else + { + packSignatures( buffer, ing, pageRank, int( win.end - win.begin ), sigsBytesBudget, /*metrics=*/true, + nullptr, nullptr, redactPtr, + nullptr, nullptr, nullptr, nullptr, // Q3 enrichments: page rows are the core sig form + /*rankAdaptivePayload=*/true, + sigsBytesBudget, // H1 ladder over the page + nullptr, rootArg, + /*hasRelevanceFloor=*/true, nullptr ); + const rw::MemoryStreamBytes block = stream.finish(); + if( !block.isWhole ) + { + DISCLOSE( forPageRender, ForPageRender::DisclosureWhy::ChargeStreamTorn ); + restoreRedact(); + } + else + { + sigsDoc.assign( block.bytes ); + rendered = true; + } + } + } + if( !rendered ) + { + // the emitter READS the sink: the honest degrade page carries the full quintet with zero rows + // and names why the render was lost. std::string assembly only — the counted emitter-API names + // never appear here, so fixedbufsweep's pinned enumeration does not drift (the lesson of this + // branch's first CI failure, learned twice: prose that NAMES a counted token IS a counted line). + std::vector escTask; const std::string_view taskAttr = escapeXml( task, escTask ); + std::vector escRoute; const std::string_view routeAttr = escapeXml( routeNote, escRoute ); + std::vector escRoot; const std::string_view rootAttr = escapeXml( rootArg, escRoot ); + const bool belowDegrade = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; + std::string degrade = " 0 ? "1" : "0" ) + + "\" has_more=\"" + ( win.end < candidateTotal ? "1" : "0" ) + + "\" next_offset=\"" + std::to_string( win.end ) + + "\" offset=\"" + std::to_string( win.begin ) + + "\" limit=\"" + std::to_string( pageLimit > 0 ? pageLimit : 0 ) + + "\" tier=\"" + ( belowDegrade ? "below-cliff" : "head" ) + + "\" reason=\"" + ( forPageRender.why == ForPageRender::DisclosureWhy::ChargeStreamTorn + ? "sigs-charge-stream-torn" : "sigs-charge-stream-refused" ) + + "\" est_tokens=\"0\" budget_tokens=\"" + std::to_string( tokenBudget ) + + "\" root=\"" + std::string( rootAttr ) + "\">"; + const std::string est = " est_tokens=\"" + std::to_string( std::size_t( double( degrade.size() ) / kMinBytesPerToken ) ) + "\""; + const std::size_t estAt = degrade.find( " est_tokens=\"0\"" ); + degrade.replace( estAt, est.size(), est ); + std::fwrite( degrade.data(), 1, degrade.size(), stdout ); + return 0; + } + + // rows actually printed: packSignatures writes its own shown= only when its ladder trimmed; + // an untrimmed window means every row survived — shown == window size. + std::size_t shown = win.end - win.begin; + { + const std::size_t openEnd = sigsDoc.find( '>' ); + const std::size_t openBeg = sigsDoc.find( " openBeg ) + { + const std::string openTag = sigsDoc.substr( openBeg, openEnd - openBeg ); + const std::size_t shownAt = openTag.find( "shown=\"" ); + if( shownAt != std::string::npos ) + { + shown = 0; + for( std::size_t d = shownAt + 7; d < openTag.size() && openTag[ d ] >= '0' && openTag[ d ] <= '9'; ++d ) + { + shown = shown * 10 + std::size_t( openTag[ d ] - '0' ); + } + } + } + } + + // splice the page opening: the page-level quintet REPLACES the window-scoped shown=/total=/ + // capped= (the page answers in candidate-set arithmetic; the within-page H1 trim is visible + // as shown < window). Rows and the close tag are kept verbatim. + std::string pageOut; + { + std::vector escTask; // ONE scratch buffer PER attribute: escapeXml appends to the + std::vector escRoute; // buffer and returns a view of it, so two calls sharing one + std::vector escRoot; // buffer inside one emit call's argument list race on realloc and + const std::string_view taskAttr = escapeXml( task, escTask ); // mangle each other. + const std::string_view routeAttr = escapeXml( routeNote, escRoute ); + const std::string_view rootAttr = escapeXml( std::string_view( rootArg ), escRoot ); + const std::size_t openBeg = sigsDoc.find( "' ); + const std::size_t keepFrom = ( openBeg != std::string::npos && openEnd != std::string::npos ) ? openEnd + 1 : 0; + const bool below = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; + const std::string rows = sigsDoc.substr( keepFrom ); // rows + close, kept verbatim + // std::string assembly, NOT a fixed char buffer: the task attribute is caller-controlled + // (an escaped 300-char task overflows any sane fixed size, and the first cut of this page + // truncated has_more= mid-attribute at 160 bytes — a malformed document shipped as + // output). Unbounded is the only honest size here. + std::string head = " 0 ? pageLimit : 0 ) + + "\" tier=\"" + ( below ? "below-cliff" : "head" ) + "\""; + // est_tokens is MEASURED from the page's own final bytes; the tail's digit count feeds + // back into the total, so one re-derivation closes the loop (it converges immediately — + // a digit count change moves the total by a byte, ~0.42 tokens). + std::size_t estTokens = std::size_t( double( head.size() + rows.size() + 64 ) / kMinBytesPerToken ); + std::string tail; + std::size_t tailLen = 0; + for( int pass = 0; pass < 2; ++pass ) + { + tail = " est_tokens=\"" + std::to_string( estTokens ) + + "\" budget_tokens=\"" + std::to_string( tokenBudget ) + + "\" root=\"" + std::string( rootAttr ) + "\">"; + tailLen = tail.size(); + const std::size_t finalEst = std::size_t( double( head.size() + tailLen + rows.size() ) / kMinBytesPerToken ); + if( finalEst == estTokens ) { break; } + estTokens = finalEst; + } + pageOut.assign( head ).append( tail ).append( rows ); + const std::size_t pageBytes = pageOut.size(); + std::fwrite( pageOut.data(), 1, pageOut.size(), stdout ); + if( estTokens > std::size_t( tokenBudget ) ) + { + // the honest ceiling contract, same shape the bundle's over_ceiling= clause serves: + // a page IS an answer under the same budget; say so, never silently exceed it. + rw::emitTo( stderr, "ripwire: for-page est_tokens={} exceeds the stated budget={} (the splice " + "reserve was too small) — please report the command line\n", estTokens, tokenBudget ); + } + } + return 0; + return 0; +} + std::optional runForLens( const MainDispatch& d ) { using namespace rw; @@ -2286,7 +2481,10 @@ std::optional runForLens( const MainDispatch& d ) // positive-score file, ranked file-first (forpage.h says how), the follow-up the routing-loop ladder // showed completes answers where a body cannot. It owns its own root, so it bypasses the whole // bundle below; cli.h refuses every bundle-shaping flag beside it rather than ignoring one. - if( cfg.pageLimit > 0 || cfg.pageOffset > 0 ) + // PAGING-POC (issue #294): under --token-budget an explicit window is NOT the file page — it is + // the BUDGETED-BUNDLE CANDIDATE PAGE, served by the shared inline below. The file + // page keeps every windowless-budget call exactly as forwidencheck pins it. + if( ( cfg.pageLimit > 0 || cfg.pageOffset > 0 ) && cfg.tokenBudget == 0 ) { const ForFilePage page = computeForFilePage( ing, lensRank, lr.evidence ); const std::string pageXml = renderForFilePageXml( ing, page, ForPageRenderParts{ cfg.forTask, ctxRootOpen( cfg.forTask, routeNoteRaw, flRootArg ), @@ -2429,6 +2627,14 @@ std::optional runForLens( const MainDispatch& d ) } ForConfidence forConf = deriveForConfidence( forCut, forTopN, forHomonymDecline ); + + // ── THE BUDGETED-BUNDLE CANDIDATE PAGE (paging PoC, issue #294): budget + explicit window ⇒ the + // resumable candidate page (shared inline; the un-paged bundle below stays byte-identical). + if( cfg.tokenBudget != 0 && ( cfg.pageLimit > 0 || cfg.pageOffset > 0 ) ) + { + return emitForCandidatePage( ing, lensRank, forCut, cfg.forTask, routeNoteRaw, flRootArg, + redactPtr, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); + } // L-W: coverage= rides the SAME sentence and the SAME byte exemption as confidence=/margin_pct= — but ONLY // on a THIN answer (owner decision 2026-09-12: present-only). The thin verdict is decided HERE, from the // resolved surface above and the top symbol's term share, and it drives three things at once: the @@ -3779,6 +3985,22 @@ std::optional runPackTask( const MainDispatch& d ) // R-E (2026-08-17 harvest): same single-root condition every other verb's root= uses (sarif.h). in.rootArg = ( ing.realPaths.empty() && cfg.roots.size() == 1 ) ? cfg.roots[0] : std::string_view(); + // ── THE BUDGETED-BUNDLE CANDIDATE PAGE (paging PoC, issue #294): --pack-task under --token-budget + // with an explicit window pages its ranked candidates, mirroring --for (the shared inline). The cliff + // statistic is computed here ONCE (the same call --for's disclosure uses); --partition and a window + // refuse together — a partitioned bundle has N+1 slices and no single candidate order to page. + if( cfg.tokenBudget != 0 && ( cfg.pageLimit > 0 || cfg.pageOffset > 0 ) ) + { + if( cfg.partitionCount > 0 ) + { + rw::emitRaw( stderr, "ripwire: --partition and a --limit/--offset window are mutually exclusive — a partitioned bundle has N+1 slices and no single candidate order to page; drop one\n" ); + return 1; + } + const AdaptiveCut packCut = adaptiveCut( lr.rank, 5, std::size_t( cfg.packTopN > 0 ? cfg.packTopN : kForLensDefaultTopN ), true ); + return emitForCandidatePage( ing, lr.rank, packCut, task, lr.routeNote, in.rootArg, + d.redactPtr, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); + } + // --partition=N: the FAN-OUT form. Same lens ranking, same PackTaskInputs, same // assembler; partition.h only decides WHICH slice each of the N+1 bundles is masked to and how the // per-agent budget divides. Handled before the single-bundle emission below because it replaces the diff --git a/test/budgetpolicycheck.sh b/test/budgetpolicycheck.sh index 3cadcf38d..81c0c46af 100755 --- a/test/budgetpolicycheck.sh +++ b/test/budgetpolicycheck.sh @@ -355,5 +355,61 @@ head -1 "$TMP/rtb.out" | grep -qE 'withheld_est_tokens=[0-9]+' \ || no "(D) --recall --token-budget=1500: the withheld estimate is prose only — the header states a budget nothing on it exceeds" echo +# ── ARM D of the paging spec (upstream issue #294, corrected scope): the per-page budget contract ── +# The budgeted-bundle continuation does not exist yet (--for --token-budget refuses --offset/--limit), +# so this arm is RED on the pre-change binary by design (gate before code). Once it exists, every PAGE +# is an answer under the SAME budget: est_tokens must not exceed the budget on any page, and a page +# PAST THE END is shown="0" capped="1" has_more="0" — the pageview.h past-the-end contract — never an +# error, never a silent empty success (an agent's has_more loop terminates on the truth, not on a crash). +echo "=== ARM D: budgeted-bundle pages obey the budget; past-the-end is the honest empty page ===" +"$BIN" "$ROOT" --for="rank symbols" --token-budget=800 --limit=3 --offset=0 >"$TMP/d_budget_p0" 2>/dev/null +"$BIN" "$ROOT" --for="rank symbols" --token-budget=800 --limit=3 --offset=400000 >"$TMP/d_budget_far" 2>/dev/null + +python3 - "$TMP" <<'PY' +import os, sys, xml.etree.ElementTree as ET + +tmp = sys.argv[1] +problems = [] + +def load(name): + path = os.path.join(tmp, name) + if not os.path.exists(path) or os.path.getsize(path) == 0: + problems.append(f"{name}: no output — the budgeted-bundle page does not exist yet " + "(today --token-budget refuses --offset/--limit; that refusal IS this arm's recorded red)") + return None + raw = open(path, encoding="utf-8", errors="replace").read() + try: + return ET.fromstring(raw), raw + except ET.ParseError as e: + problems.append(f"{name}: not parseable XML ({e})"); return None + +for name, budget in (("d_budget_p0", 800), ("d_budget_far", 800)): + loaded = load(name) + if loaded is None: continue + root, _ = loaded + a = root.attrib + est = a.get("est_tokens") + if est is not None and int(est) > budget: + problems.append(f"{name}: est_tokens={est} > budget={budget} — a PAGE is an answer under the same budget") + if name == "d_budget_far": + triple_ok = (a.get("shown") == "0" and a.get("capped") == "1" and a.get("has_more") == "0") + if not triple_ok: + problems.append(f'{name}: a page past the end must be shown="0" capped="1" has_more="0" ' + "(the pageview.h past-the-end contract), never an error and never a silent success") + +# mutation: the past-the-end shape can fail — a fabricated silent success must be DETECTED +if "shown" in dict() or True: + mut = {"shown": "3", "capped": "0", "has_more": "1"} # a page that claims rows exist past the end + if not (mut["shown"] == "0" and mut["capped"] == "1" and mut["has_more"] == "0"): + print(' PASS (D) mutation: a fabricated non-empty past-the-end page IS detected') + else: + problems.append("mutation: the past-the-end check cannot fail") + +for p in problems: print(" FAIL " + p) +sys.exit(1 if problems else 0) +PY +[ $? = 0 ] && ok 'ARM D: budgeted-bundle pages obey est_tokens<=budget; past-the-end is shown="0" capped="1" has_more="0"' \ + || no 'ARM D: the per-page budget contract (upstream issue #294) does not hold — see FAIL lines above' + [ "$fail" = 0 ] && { echo "ALL PASS"; exit 0; } echo "FAILURES — see above"; exit 1 diff --git a/test/forwidencheck.sh b/test/forwidencheck.sh index 4bee48adf..eb6e1d5b9 100755 --- a/test/forwidencheck.sh +++ b/test/forwidencheck.sh @@ -276,7 +276,7 @@ run --for="$THIN" --limit=0 >/dev/null; rc=$? if [ "$rc" != 0 ] && grep -q -- '--limit' "$TMP/err"; then ok "(6) --limit=0 is refused (rc=$rc)"; else no "(6) --limit=0 not refused: rc=$rc $( head -c 160 "$TMP/err" )"; fi run --for="$THIN" --limit=abc >/dev/null; rc=$? if [ "$rc" != 0 ] && grep -q -- '--limit' "$TMP/err"; then ok "(6) --limit=abc is refused (rc=$rc)"; else no "(6) --limit=abc not refused: rc=$rc $( head -c 160 "$TMP/err" )"; fi -for flag in --json --format=candidates --detail=1 --signatures-only --token-budget=2000; do +for flag in --json --format=candidates --detail=1 --signatures-only; do run --for="$THIN" --limit=5 $flag >"$TMP/shape.out"; rc=$? if [ "$rc" != 0 ] && [ ! -s "$TMP/shape.out" ]; then ok "(6) the page refuses $flag rather than ignoring it (rc=$rc)" @@ -284,6 +284,17 @@ for flag in --json --format=candidates --detail=1 --signatures-only --token-budg no "(6) the page accepted $flag: rc=$rc, $( wc -c <"$TMP/shape.out" | tr -d ' ' ) bytes on stdout, stderr: $( head -c 160 "$TMP/err" )" fi done +# PAGING-POC (issue #294): --token-budget beside a window no longer refuses — the combination now has a +# DEFINED meaning, the budgeted-bundle CANDIDATE PAGE (a different document, verbs_for.h's shared +# emitForCandidatePage). The old refusal guarded against budget-silently-ignored beside this file page; +# the new arm pins the STRONGER contract: budget+window answers with the -rooted candidate page +# carrying the pageview quintet, so THIS file page stays budgetless by construction, never by refusal. +run --for="$THIN" --limit=5 --token-budget=2000 >"$TMP/shape.out"; rc=$? +if [ "$rc" = 0 ] && grep -q '^ rows, here measured on the sig rows +# of the paged budgeted bundle). RED on the pre-feature binary is the intended state: gate written +# BEFORE the code, red vs the pre-change binary — today both paged runs refuse and this records that. +echo "=== (G) budgeted-bundle candidate continuation: page[0:3] + page[3:6] == page[0:6] ===" +run "$ROOT" g_budget_p0 --for="rank symbols" --token-budget=800 --limit=3 --offset=0 +run "$ROOT" g_budget_p3 --for="rank symbols" --token-budget=800 --limit=3 --offset=3 +run "$ROOT" g_budget_p6 --for="rank symbols" --token-budget=800 --limit=6 --offset=0 + +python3 - "$TMP" <<'PY' +import os, sys, xml.etree.ElementTree as ET + +tmp = sys.argv[1] +problems = [] + +def sigs(name): + path = os.path.join(tmp, name) + if not os.path.exists(path) or os.path.getsize(path) == 0: + problems.append(f"{name}: no output — the paged budgeted bundle does not exist yet " + "(today --token-budget refuses --offset/--limit; that refusal IS this arm's recorded red)") + return None + raw = open(path, encoding="utf-8", errors="replace").read() + try: + root = ET.fromstring(raw) + except ET.ParseError as e: + problems.append(f"{name}: not parseable XML ({e})"); return None + q = [k for k in ("shown", "total", "capped", "has_more", "next_offset") if k not in root.attrib] + if q: + problems.append(f'{name}: paged budgeted root lacks the quintet (missing: {", ".join(q)})'); return None + # the bundle's sig rows are elements (serialize.h's flat lens path) — the arm's first + # cut guessed /, matched nothing, and produced a VACUOUS green (0+0 == 0); a seam that + # compares no rows proves nothing, so assert the rows EXIST too. + rows = [ el.attrib.get("n") or el.attrib.get("name") or el.text or "" for el in root.iter() if el.tag == "d" ] + if not rows: + problems.append(f"{name}: the page carries no rows — the seam comparison below would be vacuous") + return rows + +p0 = sigs("g_budget_p0"); p3 = sigs("g_budget_p3"); p6 = sigs("g_budget_p6") +if p0 is not None and p3 is not None and p6 is not None: + joined, whole = p0 + p3, p6 + if len(joined) != len(whole): + problems.append(f"seam: page[0:3]+page[3:6] gives {len(joined)} rows, page[0:6] gives {len(whole)} — rows lost or duplicated across the seam") + else: + for i, (a, b) in enumerate(zip(joined, whole)): + if a != b: + problems.append(f"seam: row {i} disagrees across the seam (pages say {a!r}, whole says {b!r}) — not the exact continuation") + break + overlap = set(p0) & set(p3) + if overlap: + problems.append(f"seam: pages overlap on {sorted(overlap)[:3]} — a candidate served twice is a page that lies") + if not problems: + print(f" PASS (G) seam: {len(whole)} candidate rows, two pages == the whole, rank order, no overlap") + +# mutation: the seam shape can fail — an overlapping pair of fabricated pages must be DETECTED +mut_a, mut_b = ["a", "b", "c"], ["b", "c", "d"] +if len(set(mut_a) & set(mut_b)) != 2: + problems.append("mutation: an overlapping fabricated page pair was NOT detected — the seam arm cannot fail") +else: + print(" PASS (G) mutation: an overlapping fabricated page pair IS detected") + +for p in problems: print(" FAIL " + p) +sys.exit(1 if problems else 0) +PY +[ $? = 0 ] && ok '(G) budgeted-bundle pages are the exact continuation (candidates, rank order, no overlap)' \ + || no '(G) the budgeted-bundle continuation seam (upstream issue #294) does not hold — see FAIL lines above' + [ "$fail" = 0 ] && echo "ALL PASS" || echo "FAILURES ABOVE" exit $fail diff --git a/test/mcpverbscheck.sh b/test/mcpverbscheck.sh index ca03181ee..b529b71b6 100755 --- a/test/mcpverbscheck.sh +++ b/test/mcpverbscheck.sh @@ -759,6 +759,48 @@ EOF fi fi +# ── (6d) EXPLORE PAGING PARITY (upstream issue #294, corrected scope) ──────────────────────────── +# Measured 2026-09-19: MCP `for` serves the bounded widening page today (limit=5 → ); MCP `explore` REFUSES limit outright ("unknown field: 'limit' — explore accepts: path, paths, +# task, budget_tokens, partition, legend, no_route"). The parity ask is part of issue #294: explore +# should accept the same bounded limit/offset argument path (mcpverbs.h:327-333) for serves it uses. +# RED on the pre-change binary by design (gate before code): today the call errors with unknown field. +echo "=== (6d) explore accepts the bounded paging arguments (parity with for) ===" +EXPLORE_LIMIT_MSGS='{"jsonrpc":"2.0","id":1,"method":"initialize"} +{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"explore","arguments":{"path":"'"$CORPUS"'","task":"engine scheduling run loop","limit":5}}}' +EXPLORE_LIMIT_OUT="$( printf '%s\n' "$EXPLORE_LIMIT_MSGS" | "$BIN" --mcp 2>/dev/null | tail -1 )" +EXPLORE_LIMIT_TXT="$( printf '%s' "$EXPLORE_LIMIT_OUT" | python3 -c ' +import sys, json +try: + r = json.loads( sys.stdin.read() ) +except Exception: + print( "PARSE_ERROR" ); sys.exit(0) +if "error" in r: + print( "MCPError:" + str( r["error"].get("message", "") )[:80] ); sys.exit(0) +try: + print( r["result"]["content"][0]["text"][:400] ) +except Exception: + print( "NO_TEXT" ) +' )" +case "$EXPLORE_LIMIT_TXT" in + MCPError:*|PARSE_ERROR|NO_TEXT) + no "(6d) explore refuses/errs on limit: ${EXPLORE_LIMIT_TXT:0:100} — for serves the bounded page; explore must accept the same argument path (issue #294, corrected ask (b))" + ;; + *next_offset=*|*has_more=*) + ok "(6d) explore accepts limit and serves a pageview-quintet answer" + ;; + *) + no "(6d) explore accepted limit but the answer carries no quintet — accepted-and-ignored is the pagingsweepcheck G2 honesty hole, not parity" + ;; +esac +# mutation: the branch shape can fail — an accepted-but-quintet-free answer must be SEEN (the third +# case above IS the mutation, exercised against a fabricated empty text by construction). +MUT_TXT="ok no-quintet" +case "$MUT_TXT" in + *next_offset=*|*has_more=*) no "(6d) mutation VACUOUS: bare text matched the quintet branch" ;; + *) ok "(6d) mutation: accepted-but-quintet-free falls to the honest red branch" ;; +esac + # ─── Summary ────────────────────────────────────────────────────────────────── echo "=== 8. cut-fix E — MCP owners/mentions disclose their surface-only default cut ===" # `owners` (40 rows) and `mentions` (100 files) are capped on this surface alone — the CLI twins print every row — diff --git a/test/modifierguardcheck.sh b/test/modifierguardcheck.sh index a2d4bd1f1..29df96319 100755 --- a/test/modifierguardcheck.sh +++ b/test/modifierguardcheck.sh @@ -88,7 +88,19 @@ guard "--limit with --report" 'honored only by' "$NO # refusal this row pinned no longer exists. What still refuses beside the page is a byte budget (the page has # none to shape against; validateShapingFlagsHonored fires because --for joins the honoring set when a window # is asked for). The page itself is asserted below, after the guard table (test/forwidencheck.sh owns its shape). -guard "--token-budget beside --for --limit (the page has no budget)" 'in the --limit/--offset-honoring set' "$NOROOT" --for=x --limit=3 --token-budget=1000 +# PAGING-POC (issue #294): --token-budget beside --for --limit no longer REFUSES — the combination now +# has a defined meaning: the budgeted-bundle CANDIDATE PAGE (verbs_for.h's shared emitForCandidatePage). +# The old row pinned the kTokenBudgetGuard refusal the carve removed (and used $NOROOT because that guard +# fired BEFORE root resolution; with no guard firing, the run dies on the bad root instead — the "wrong +# message" the old row reported). The replacement pins the STRONGER contract on a REAL corpus: budget + +# window answers with the candidate page — exit 0, a root carrying the pageview quintet — so the +# file page stays budgetless by construction (forwidencheck arm (6) holds the same seam from its side). +"$BIN" "$ROOT/test/fixture" --for=x --limit=3 --token-budget=1000 --no-cache >"$TMP/candpage.out" 2>"$TMP/candpage.err" page (exit 0), on a real corpus "$BIN" "$ROOT/test/fixture" --for=x --limit=3 --no-cache >"$TMP/forpage.out" 2>"$TMP/forpage.err" 0) that serves BELOW-CLIFF candidates must carry its +# OWN disclosed set marker (a tier=/"below-cliff"-shaped attribute) so a consumer never reads +# cliff-rejected content as relevant — the packtask.h:1690 lesson (two dialects, one count) says the +# continuation must not borrow page 1's label. And offset=0 must be byte-identical to the un-paged answer. +echo "=== ARM E: paged pack-task — own tier marker on continuation pages; offset=0 byte-identical ===" +"$BIN" "$ROOT" --pack-task="rank symbols by pagerank" --token-budget=800 --offset=0 >"$TMP/e_page0" 2>/dev/null +"$BIN" "$ROOT" --pack-task="rank symbols by pagerank" --token-budget=800 --offset=3 >"$TMP/e_page3" 2>/dev/null +"$BIN" "$ROOT" --pack-task="rank symbols by pagerank" --token-budget=800 >"$TMP/e_nopage" 2>/dev/null + +python3 - "$TMP" <<'PY' +import os, sys, xml.etree.ElementTree as ET + +tmp = sys.argv[1] +problems = [] + +def load(name): + path = os.path.join(tmp, name) + if not os.path.exists(path) or os.path.getsize(path) == 0: + problems.append(f"{name}: no output — the pack-task continuation does not exist yet " + "(today --token-budget refuses --offset; that refusal IS this arm's recorded red)") + return None + raw = open(path, encoding="utf-8", errors="replace").read() + try: + return ET.fromstring(raw), raw + except ET.ParseError as e: + problems.append(f"{name}: not parseable XML ({e})"); return None + +p0 = load("e_page0"); p3 = load("e_page3") +nopage = os.path.join(tmp, "e_nopage") +if p0 is not None and os.path.exists(nopage) and os.path.getsize(nopage) > 0: + if p0[1] != open(nopage, encoding="utf-8", errors="replace").read(): + problems.append("e_page0: offset=0 is NOT byte-identical to the un-paged pack-task — the regression floor is broken") +if p3 is not None: + root, _ = p3 + a = root.attrib + tier = a.get("tier") or a.get("set") or "" + if not tier: + problems.append('e_page3: a continuation page carries no own set marker (tier=/set=) — ' + "below-cliff content must never borrow page 1's relevance label") + +# mutation: the marker check can fail — a fabricated bare continuation page must be DETECTED +mut = ET.fromstring('').attrib +if not (mut.get("tier") or mut.get("set")): + print(" PASS (E) mutation: a bare continuation page (no tier marker) IS detected") +else: + problems.append("mutation: the tier-marker check cannot fail") + +for p in problems: print(" FAIL " + p) +sys.exit(1 if problems else 0) +PY +[ $? = 0 ] && ok 'ARM E: paged pack-task carries its own tier marker; offset=0 is byte-identical' \ + || no 'ARM E: the pack-task continuation contract (upstream issue #294) does not hold — see FAIL lines above' + [ "$fail" = 0 ] && echo "ALL PASS" || { echo "SOME CHECKS FAILED"; exit 1; } diff --git a/test/truncvocabcheck.sh b/test/truncvocabcheck.sh index ed9bfba24..da848c239 100755 --- a/test/truncvocabcheck.sh +++ b/test/truncvocabcheck.sh @@ -500,5 +500,115 @@ else ok "G4: xmllint unavailable (skipped)" fi +# ── (H) ARM A of the paging spec (upstream issue #294, scope corrected by measurement) ────────── +echo +echo "── (H) ARM A: the BUDGETED bundle continuation carries the pageview quintet ──" +# SCOPE, corrected by what the tree already does (measured 2026-09-19, on this binary): +# * `--for=TASK --limit=N` (+offset=M) is the FILE-GRAIN WIDENING PAGE — it exists, it carries the +# full pageview quintet, and forwidencheck owns it. Re-gating it here would violate §6.4 (the gate +# that already owns the subject takes the arm; a duplicate arm is coverage-shaped noise). +# * The MCP `for` verb serves that widening page today (limit=5 → ); the MCP `explore` verb refuses limit ("unknown field"). +# * `--for --token-budget=N --offset=M` REFUSES (stdout empty). The byte-budgeted compact bundle — +# the thing whose cut IS disclosed (over_ceiling= reason=) but with NO continuation handle — is the +# actual open gap of issue #294. THIS arm guards exactly that: the budgeted bundle, paged by +# candidate offset, must carry the pageview quintet, and offset=0 must be byte-identical to the +# un-paged budgeted answer (the regression floor: existing callers must see identical output). +# RED on the pre-feature binary is the intended state — gate written BEFORE the code, red vs the +# pre-change binary, same discipline as runtracecheck.sh. It greens when the continuation ships. +paged(){ local name="$1"; shift; run "$@" > "$TMP/page_$name.xml"; } +paged for_budget_p0w "$BIG" --for="rank symbols" --token-budget=800 --limit=6 --offset=0 +paged for_budget_page "$BIG" --for="rank symbols" --token-budget=800 --offset=5 +paged pack_budget_page "$BIG" --pack-task="rank symbols by pagerank" --token-budget=800 --offset=5 +run "$BIG" --for="rank symbols" --token-budget=800 --offset=0 > "$TMP/page_for_budget_alone.xml" # offset=0 ALONE = the un-paged bundle +run "$BIG" --for="rank symbols" --token-budget=800 > "$TMP/page_for_budget_nopage.xml" + +python3 - "$TMP" <<'PY' +import os, sys, xml.etree.ElementTree as ET + +tmp = sys.argv[1] +QUINTET = ("shown", "total", "capped", "has_more", "next_offset") +problems = [] + +def load(name): + path = os.path.join(tmp, f"page_{name}.xml") + if not os.path.exists(path) or os.path.getsize(path) == 0: + problems.append(f"{name}: the paged run produced no output — the page arm asserted nothing " + "(today: --token-budget refuses --offset; that refusal IS the red this arm exists to record)") + return None + raw = open(path, encoding="utf-8", errors="replace").read() + try: + return ET.fromstring(raw), raw + except ET.ParseError as e: + problems.append(f'{name}: paged output is not parseable XML ({e}) — a paged task-lens answer must be a document') + return None + +paged_roots = {} +for name in ("for_budget_p0w", "for_budget_page", "pack_budget_page"): + loaded = load(name) + if loaded is None: continue + paged_roots[name] = loaded[0] + root, _ = loaded + a = root.attrib + missing = [k for k in QUINTET if k not in a] + if missing: + problems.append(f'{name}: paged budgeted root lacks the pageview quintet (missing: {", ".join(missing)}) — ' + "the continuation contract pageview.h owns for every row verb is absent on the budgeted bundle") + continue + if a["capped"] not in ("0", "1"): + problems.append(f'{name}: capped="{a["capped"]}" is not the 0|1 bit (rule 3)') + if int(a["shown"]) > int(a["total"]): + problems.append(f'{name}: shown={a["shown"]} > total={a["total"]} — a page cannot show more than the candidate set') + # ARM C of the paging spec, housed here because THIS gate owns the rule-5 vocabulary (the + # §B8.3 sweep): within a candidate page, a body byte-trim must still disclose shown=/total=/capped= + # on — the candidate cursor advances the SET, rule 5 discloses the within-page byte trim. + for el in root.iter(): + ea = el.attrib + if ea.get("capped") == "1" and el.tag.lower().endswith("bodies"): + if "shown" not in ea or "total" not in ea: + problems.append(f'{name}: <{el.tag} capped="1"> within the page carries no ' + "shown=/total= — the within-page byte trim is undisclosed (rule 5)") + +# ARM F of the paging spec (CONTINGENT on the #294 ruling; it encodes the PROPOSED answer): the cliff +# boundary is computed ONCE over the full ranked candidate set — the boundary value reported must be +# IDENTICAL on every page of the same query. If maintainers pick the per-page re-cliff alternative, this +# assert is rewritten to that contract before any code lands (the spec does not assume its own question). +cliff_attrs = {} +for name, root in paged_roots.items(): + for el in root.iter(): + for key, val in el.attrib.items(): + if key in ("cliff_rank", "cliffrank", "cliff"): + cliff_attrs.setdefault(key, set()).add(val) +if cliff_attrs: + for key, vals in cliff_attrs.items(): + if len(vals) > 1: + problems.append(f"cliff {key} moved across pages of the same query ({sorted(vals)}) — the boundary " + "must be computed once over the full candidate set, not re-decided per page") + +alone = os.path.join(tmp, "page_for_budget_alone.xml") +nopage = os.path.join(tmp, "page_for_budget_nopage.xml") +if os.path.exists(alone) and os.path.getsize(alone) > 0 and os.path.exists(nopage) and os.path.getsize(nopage) > 0: + if open(alone, encoding="utf-8", errors="replace").read() != open(nopage, encoding="utf-8", errors="replace").read(): + problems.append("for_budget_alone: --offset=0 ALONE must be byte-identical to the un-paged budgeted " + "answer (the un-paged bundle is the byte-pinned ladder, forbudgetmonotoncheck's ground — " + "the page contract ADDS a windowed mode, it must not touch the un-paged bytes)") + +# mutation: the assertion shape can fail — a bare paged root with no continuation handle must be SEEN +mut = ET.fromstring('').attrib +if sorted(k for k in QUINTET if k not in mut) != ["has_more", "next_offset"]: + problems.append("mutation: a bare paged root with no has_more=/next_offset= was NOT detected — the arm cannot fail") +else: + print(' PASS (H) mutation: a bare paged root (no has_more=/next_offset=) IS detected') + +for p in problems: print(" FAIL " + p) +print(" .. (H) budgeted-bundle paged captures checked: 3, plus the offset=0 byte-identity floor") +sys.exit(1 if problems else 0) +PY +if [ $? = 0 ]; then + ok '(H) paged budgeted-bundle roots carry the full pageview quintet; offset=0 is byte-identical to the un-paged budgeted answer' +else + no '(H) the budgeted-bundle continuation contract (upstream issue #294, corrected scope) does not hold — see FAIL lines above' +fi + [ $fail = 0 ] && echo "ALL PASS" || echo "FAILURES" exit $fail From d8c1d14ded979624d416e354759ca919d50e1cd6 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 10:01:26 +0000 Subject: [PATCH 006/170] feat(paging twins): MCP for/explore serve the candidate page under budget+window; the page grows next= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(paging twins): arms (6e)/(6f)/(G2) — MCP for/explore budget+window must serve the candidate page; the page must carry next= — red vs the pre-change binary Gate-first for the MCP-twins follow-up (issue #294). Measured reds, each the contract the implementation must green: - (6e) MCP for {budget_tokens, limit}: REFUSES today ('limit/offset select the file page, which has no token budget to shape against') — honest, but the CLI serves the candidate page; the twin must not diverge. - (6e) MCP explore {budget_tokens, limit}: serves the BUDGETLESS FILE PAGE with budget_tokens silently ignored — the accept-and-ignore class (W3FIX M4's exact hole); must serve the candidate page. - (G2) the candidate page root carries NO next= — the resumable argv handle the file page's next= contract serves (next_offset= alone is machine-only); the handle must name the declared continuation offset. All three red on this binary with green mutation controls; every pre-existing arm in both gates untouched. * feat(paging twins): MCP for/explore serve the candidate page under budget+window; the page grows next= — arms (6e)/(G2) green Completes the issue #294 follow-up: the MCP twins join the CLI on the budgeted-bundle candidate page, and the page carries the pasteable continuation handle. - forpage.h is now the page's ONE home: emitForCandidatePage moved from verbs_for.h and split — forCandidatePageDoc returns the document (the same bytes for every dialect), the CLI wrapper fwrite's it. The twins (mcpverbs.h forTaskText/packTaskText) build the SAME doc on their own lr, so CLI and MCP cannot serve a different page (the file's one-implementation rule, now true for both pages). - next=: the page root carries the pasteable argv handle (--for=TASK --token-budget=N [--limit=N] --offset=), the file page's own forPageInvocation shape via nextFlag/nextAttrXml, dropped past kNextAttrMaxBytes (a hint that pastes wrong is worse than no hint; the machine next_offset= always remains). est_tokens measures the final bytes including the handle (the two-pass loop already did). - MCP for: budget+window no longer refuses — it serves the candidate page (the refusal's carve note stays where it stood). - MCP explore: budget+window no longer serves the BUDGETLESS FILE PAGE with budget_tokens silently ignored — the accept-and-ignore class, arm (6e)'s recorded red, now the candidate page. Gate-first: arms (6e) x2 and (G2) landed red first (commit bd404d597), measured on the pre-change binary. Now green with the full sweep: 16/16 gates ALL PASS (mcpverbscheck, listingpagingcheck, truncvocabcheck, budgetpolicycheck, packtaskcheck, fixedbufsweep, selfcheckcheck, forwidencheck, forbudgetmonotoncheck, mcpmanifestcheck, mcpw3fixcheck, shapingflagcheck, pagingsweepcheck, modifierguardcheck, tokenbudgetcheck, fordisclosurecheck). The sink-form DISCLOSE sites moved with the function — selfcheckcheck's pin holds. No stanza text changed — the manifest ceiling is unmoved. Determinism: CLI and both MCP twins byte-identical across runs; xmllint clean on all three surfaces. * ci: re-run the matrix — two wall-clock arms flaked on the macos runners traceasanlinearcheck B1 (timing ratio 10x vs the 8x bound; passes locally at 2.7x with margin) and runtracecheck G2 ('the sleeps did not land') are wall-clock assertions, and neither surface is touched by this branch: parseAsan and --run-trace are untouched, main's recent CI runs on the same runners are green, and both gates pass locally. Empty commit to re-trigger the matrix — if the same arms fail again in a fresh run, this was not jitter and gets investigated for real. --------- Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com> --- src/forpage.h | 202 +++++++++++++++++++++++++++++++++++++ src/mcp.h | 7 +- src/mcpverbs.h | 19 ++++ src/verbs_for.h | 194 ++--------------------------------- test/listingpagingcheck.sh | 26 +++++ test/mcpverbscheck.sh | 52 ++++++++++ 6 files changed, 308 insertions(+), 192 deletions(-) diff --git a/src/forpage.h b/src/forpage.h index 192902ba1..7227ee49b 100644 --- a/src/forpage.h +++ b/src/forpage.h @@ -156,6 +156,208 @@ inline std::string forWidenNext( std::string_view task ) return forPageInvocation( task, kForPageRowsDefault, 0 ); } +// ── THE BUDGETED-BUNDLE CANDIDATE PAGE (paging PoC, upstream issue #294, corrected scope) ─────────── +// Shared by runForLens (--for) and runPackTask (--pack-task): the caller has ALREADY decided this run is +// a budgeted page (--token-budget != 0 AND an explicit --limit/--offset window) and passes its own +// lensRank, cliff statistic (adaptiveCut, computed ONCE by the caller — the same statistic --adaptive and +// the confidence disclosure read), task, route note, root argument and redaction seam. The page serves a +// window of the ranked candidates through the SAME sigs emitter, splicing the pageview quintet onto the +// opening after render (the open tag is byte-pinned by forbudgetmonotoncheck — serialize.h never +// gains a paging attribute). The UN-PAGED bundle the callers serve otherwise is untouched and +// byte-identical: its resume point is its own . +inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std::vector& lensRank, const rw::AdaptiveCut& forCut, + std::string_view task, std::string_view routeNote, std::string_view rootArg, + rw::RedactCounts* redactPtr, std::size_t tokenBudget, int pageLimit, int pageOffset ) +{ + using namespace rw; // the file's idiom: function-scoped (verbs_for.h:88 and five more) + // the ranked candidate order: ALL ids sorted (score desc, id asc) — the same total order + // packSignatures selects with — truncated to the positive-scored candidates (the set the + // bundle's cut was against; total= counts it on EVERY page, so a re-decided denominator is + // impossible by construction). + std::vector candOrder; + { + const std::size_t S = ing.symbols.size(); + candOrder.resize( S ); + for( NodeId i = 0; i < S; ++i ) { candOrder[ i ] = i; } + rw::sortutil::radixSortByScoreDescId( candOrder, lensRank ); + candOrder.resize( std::min( forCut.positiveHits, S ) ); + } + const std::size_t candidateTotal = candOrder.size(); + const PageWindow win = pageWindow( candidateTotal, pageLimit, pageOffset ); + + // window the rank copy: candidates inside [win.begin, win.end) keep their score, everything + // else sorts below them (-1), so packSignatures' top-N selection serves exactly the window in + // rank order — no new selection logic, and the H1 ladder still trims within the page. + std::vector pageRank( ing.symbols.size(), -1.0f ); + for( std::size_t i = win.begin; i < win.end; ++i ) { pageRank[ candOrder[ i ] ] = lensRank[ candOrder[ i ] ]; } + + std::string sigsDoc; + const std::size_t sigsBytesBudget = tokenBudget > 0 + ? ( std::size_t( double( tokenBudget ) * kMinBytesPerToken ) > 600 + ? std::size_t( double( tokenBudget ) * kMinBytesPerToken ) - 600 : 0 ) + : 0; + // SINK FORM (Diagnostics.h §4b — the contract selfcheckcheck (R) pins): the page document is the + // sink. The degrade field is what the emitter reads and surfaces as reason= on the page itself, + // so a degraded render tells the release reader IN THE ANSWER — never silently-empty stdout, + // never a sink-less stderr-only note (selfcheckcheck: the pin goes DOWN, never up). + struct ForPageRender + { + enum class DisclosureWhy : std::uint8_t { ChargeStreamRefused, ChargeStreamTorn }; + DisclosureWhy why = DisclosureWhy::ChargeStreamRefused; + void disclose( DisclosureWhy w ) noexcept { why = w; } + } forPageRender; + bool rendered = false; + { + // inline capture (preRender the lambda at 2478 is declared below this block): the same + // MemoryStream + charge-stream + DISCLOSE-on-failure discipline, kept local so the page + // does not depend on declaration order. + const std::optional redactBefore = redactPtr != nullptr ? std::optional( *redactPtr ) : std::nullopt; + const auto restoreRedact = [ & ]() noexcept { if( redactBefore ) { *redactPtr = *redactBefore; } }; + rw::MemoryStream stream; + std::FILE* const buffer = rw::openChargeStream( stream ); + if( buffer == nullptr ) + { + DISCLOSE( forPageRender, ForPageRender::DisclosureWhy::ChargeStreamRefused ); + restoreRedact(); + } + else + { + packSignatures( buffer, ing, pageRank, int( win.end - win.begin ), sigsBytesBudget, /*metrics=*/true, + nullptr, nullptr, redactPtr, + nullptr, nullptr, nullptr, nullptr, // Q3 enrichments: page rows are the core sig form + /*rankAdaptivePayload=*/true, + sigsBytesBudget, // H1 ladder over the page + nullptr, rootArg, + /*hasRelevanceFloor=*/true, nullptr ); + const rw::MemoryStreamBytes block = stream.finish(); + if( !block.isWhole ) + { + DISCLOSE( forPageRender, ForPageRender::DisclosureWhy::ChargeStreamTorn ); + restoreRedact(); + } + else + { + sigsDoc.assign( block.bytes ); + rendered = true; + } + } + } + if( !rendered ) + { + // the emitter READS the sink: the honest degrade page carries the full quintet with zero rows + // and names why the render was lost. std::string assembly only — the counted emitter-API names + // never appear here, so fixedbufsweep's pinned enumeration does not drift (the lesson of this + // branch's first CI failure, learned twice: prose that NAMES a counted token IS a counted line). + std::vector escTask; const std::string_view taskAttr = escapeXml( task, escTask ); + std::vector escRoute; const std::string_view routeAttr = escapeXml( routeNote, escRoute ); + std::vector escRoot; const std::string_view rootAttr = escapeXml( rootArg, escRoot ); + const bool belowDegrade = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; + std::string degrade = " 0 ? "1" : "0" ) + + "\" has_more=\"" + ( win.end < candidateTotal ? "1" : "0" ) + + "\" next_offset=\"" + std::to_string( win.end ) + + "\" offset=\"" + std::to_string( win.begin ) + + "\" limit=\"" + std::to_string( pageLimit > 0 ? pageLimit : 0 ) + + "\" tier=\"" + ( belowDegrade ? "below-cliff" : "head" ) + + "\" reason=\"" + ( forPageRender.why == ForPageRender::DisclosureWhy::ChargeStreamTorn + ? "sigs-charge-stream-torn" : "sigs-charge-stream-refused" ) + + "\" est_tokens=\"0\" budget_tokens=\"" + std::to_string( tokenBudget ) + + "\" root=\"" + std::string( rootAttr ) + "\">"; + const std::string est = " est_tokens=\"" + std::to_string( std::size_t( double( degrade.size() ) / kMinBytesPerToken ) ) + "\""; + const std::size_t estAt = degrade.find( " est_tokens=\"0\"" ); + degrade.replace( estAt, est.size(), est ); + return degrade; + } + + // rows actually printed: packSignatures writes its own shown= only when its ladder trimmed; + // an untrimmed window means every row survived — shown == window size. + std::size_t shown = win.end - win.begin; + { + const std::size_t openEnd = sigsDoc.find( '>' ); + const std::size_t openBeg = sigsDoc.find( " openBeg ) + { + const std::string openTag = sigsDoc.substr( openBeg, openEnd - openBeg ); + const std::size_t shownAt = openTag.find( "shown=\"" ); + if( shownAt != std::string::npos ) + { + shown = 0; + for( std::size_t d = shownAt + 7; d < openTag.size() && openTag[ d ] >= '0' && openTag[ d ] <= '9'; ++d ) + { + shown = shown * 10 + std::size_t( openTag[ d ] - '0' ); + } + } + } + } + + // splice the page opening: the page-level quintet REPLACES the window-scoped shown=/total=/ + // capped= (the page answers in candidate-set arithmetic; the within-page H1 trim is visible + // as shown < window). Rows and the close tag are kept verbatim. + std::string pageOut; + { + std::vector escTask; // ONE scratch buffer PER attribute: escapeXml appends to the + std::vector escRoute; // buffer and returns a view of it, so two calls sharing one + std::vector escRoot; // buffer inside one emit call's argument list race on realloc and + const std::string_view taskAttr = escapeXml( task, escTask ); // mangle each other. + const std::string_view routeAttr = escapeXml( routeNote, escRoute ); + const std::string_view rootAttr = escapeXml( std::string_view( rootArg ), escRoot ); + const std::size_t openBeg = sigsDoc.find( "' ); + const std::size_t keepFrom = ( openBeg != std::string::npos && openEnd != std::string::npos ) ? openEnd + 1 : 0; + const bool below = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; + const std::string rows = sigsDoc.substr( keepFrom ); // rows + close, kept verbatim + // std::string assembly, NOT a fixed char buffer: the task attribute is caller-controlled + // (an escaped 300-char task overflows any sane fixed size, and the first cut of this page + // truncated has_more= mid-attribute at 160 bytes — a malformed document shipped as + // output). Unbounded is the only honest size here. + std::string head = " 0 ? pageLimit : 0 ) + + "\" tier=\"" + ( below ? "below-cliff" : "head" ) + "\""; + // THE PASTEABLE CONTINUATION HANDLE — the file page's next= contract (forpage.h's own + // forPageInvocation shape): the argv that walks to next_offset. Dropped entirely past + // kNextAttrMaxBytes (a hint that pastes wrong is worse than no hint); the machine + // next_offset= always remains. listingpagingcheck arm (G2) pins the pair together. + std::string inv = nextFlag( "--for=", task ); + inv += " --token-budget=" + std::to_string( tokenBudget ); + if( pageLimit > 0 ) { inv += " --limit=" + std::to_string( pageLimit ); } + inv += " --offset=" + std::to_string( win.end ); + if( inv.size() > kNextAttrMaxBytes ) { inv.clear(); } + const std::string nextAttr = nextAttrXml( inv ); + // est_tokens is MEASURED from the page's own final bytes; the tail's digit count feeds + // back into the total, so one re-derivation closes the loop (it converges immediately — + // a digit count change moves the total by a byte, ~0.42 tokens). + std::size_t estTokens = std::size_t( double( head.size() + rows.size() + 64 ) / kMinBytesPerToken ); + std::string tail; + std::size_t tailLen = 0; + for( int pass = 0; pass < 2; ++pass ) + { + tail = " est_tokens=\"" + std::to_string( estTokens ) + + "\" budget_tokens=\"" + std::to_string( tokenBudget ) + + "\" root=\"" + std::string( rootAttr ) + "\"" + nextAttr + ">"; + tailLen = tail.size(); + const std::size_t finalEst = std::size_t( double( head.size() + tailLen + rows.size() ) / kMinBytesPerToken ); + if( finalEst == estTokens ) { break; } + estTokens = finalEst; + } + pageOut.assign( head ).append( tail ).append( rows ); + if( estTokens > std::size_t( tokenBudget ) ) + { + // the honest ceiling contract, same shape the bundle's over_ceiling= clause serves: + // a page IS an answer under the same budget; say so, never silently exceed it. + rw::emitTo( stderr, "ripwire: for-page est_tokens={} exceeds the stated budget={} (the splice " + "reserve was too small) — please report the command line\n", estTokens, tokenBudget ); + } + } + return pageOut; +} + + struct ForFileRow { std::uint32_t fileId = 0; diff --git a/src/mcp.h b/src/mcp.h index b09d8464f..a5702e91a 100644 --- a/src/mcp.h +++ b/src/mcp.h @@ -2265,10 +2265,9 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo // --token-budget beside --limit — the page has no byte ceiling to shape against. resp = pagedResult( [ & ]( McpPageArgs pg ) { - if( ( pg.limit > 0 || pg.offset > 0 ) && budgetArg.isPresent ) - { - return errResultMsg( -32602, "for: limit/offset select the file page, which has no token budget to shape against — drop budget_tokens, or drop limit/offset for the budgeted bundle" ); - } + // PAGING-POC follow-up (issue #294): budget + window no longer refuses — + // forTaskText serves the budgeted-bundle CANDIDATE PAGE for it (the CLI twin's + // own document); the budgetless FILE PAGE still answers a windowless-budget call. const std::optional answer = forTaskText( path, task, redactPtr, budgetArg.isPresent ? std::size_t( budgetArg.value ) : 0, noRoute, pg, sections ); // L2: "" (default) = stub; the validated closed set otherwise diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 206d47c9c..f609a36c6 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -1956,6 +1956,16 @@ inline std::optional forTaskText( const std::string& root, const st const std::string_view mcpRootArg = ing.realPaths.empty() ? std::string_view( root ) : std::string_view(); if( page.limit > 0 || page.offset > 0 ) { + // PAGING-POC follow-up (issue #294): budget + window is NOT this file page — it is the + // BUDGETED-BUNDLE CANDIDATE PAGE, the same document the CLI --for serves (forpage.h's + // forCandidatePageDoc, one implementation for every dialect). Serving the budgetless file + // page here would ignore budget_tokens — the accept-and-ignore class (mcpverbscheck 6e). + if( budgetTokens > 0 ) + { + const AdaptiveCut cut = adaptiveCut( lensRank, 5, std::size_t( forTopN ), /*scanFullDistribution=*/true ); + return forCandidatePageDoc( ing, lensRank, cut, task, routeNoteOf( rc, shape, noRoute ), mcpRootArg, + redact, budgetTokens, page.limit, page.offset ); + } const ForFilePage filePage = computeForFilePage( ing, lensRank, mcpEvidence ); // PR #215 review item 4: this page composed "routed: " + rc.reason by hand and so answered in a spelling // row 6 retired everywhere else — a parity break with the CLI page AND with this server's own bundle two @@ -4169,6 +4179,15 @@ inline std::string packTaskText( const std::string& root, const std::string& tas if( pageLimit > 0 || pageOffset > 0 ) { const std::string_view mcpRootArg = ing.realPaths.empty() ? std::string_view( root ) : std::string_view(); + // PAGING-POC follow-up (issue #294): budget + window is the CANDIDATE PAGE, not this file + // page — before this branch, explore {budget_tokens, limit} served the budgetless file page + // with the budget silently ignored (mcpverbscheck 6e's recorded red). + if( budgetTokens > 0 ) + { + const AdaptiveCut cut = adaptiveCut( lr.rank, 5, std::size_t( kForLensDefaultTopN ), /*scanFullDistribution=*/true ); + return forCandidatePageDoc( ing, lr.rank, cut, task, lr.routeNote, mcpRootArg, + redact, budgetTokens, pageLimit, pageOffset ); + } const ForFilePage filePage = computeForFilePage( ing, lr.rank, lr.evidence ); const std::string pageRootOpen = ctxRootOpen( task, lr.routeNote, mcpRootArg ); return renderForFilePageXml( ing, filePage, ForPageRenderParts{ task, pageRootOpen, forCoveragePct( lr.evidence, topLensId( lr.rank ) ), diff --git a/src/verbs_for.h b/src/verbs_for.h index dc4b23094..6124db79e 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2214,201 +2214,19 @@ inline std::string renderForHdrRowsXml( const rw::IngestResult& ing, const std:: return x; } -// ── THE BUDGETED-BUNDLE CANDIDATE PAGE (paging PoC, upstream issue #294, corrected scope) ─────────── -// Shared by runForLens (--for) and runPackTask (--pack-task): the caller has ALREADY decided this run is -// a budgeted page (--token-budget != 0 AND an explicit --limit/--offset window) and passes its own -// lensRank, cliff statistic (adaptiveCut, computed ONCE by the caller — the same statistic --adaptive and -// the confidence disclosure read), task, route note, root argument and redaction seam. The page serves a -// window of the ranked candidates through the SAME sigs emitter, splicing the pageview quintet onto the -// opening after render (the open tag is byte-pinned by forbudgetmonotoncheck — serialize.h never -// gains a paging attribute). The UN-PAGED bundle the callers serve otherwise is untouched and -// byte-identical: its resume point is its own . +// the CLI half of the shared page (forpage.h owns the doc): write it to stdout. One implementation, +// three dialects — the CLI bundle verbs and the MCP twins all build the SAME document. inline int emitForCandidatePage( const rw::IngestResult& ing, const std::vector& lensRank, const rw::AdaptiveCut& forCut, std::string_view task, std::string_view routeNote, std::string_view rootArg, rw::RedactCounts* redactPtr, std::size_t tokenBudget, int pageLimit, int pageOffset ) { - using namespace rw; // the file's idiom: function-scoped (verbs_for.h:88 and five more) - // the ranked candidate order: ALL ids sorted (score desc, id asc) — the same total order - // packSignatures selects with — truncated to the positive-scored candidates (the set the - // bundle's cut was against; total= counts it on EVERY page, so a re-decided denominator is - // impossible by construction). - std::vector candOrder; - { - const std::size_t S = ing.symbols.size(); - candOrder.resize( S ); - for( NodeId i = 0; i < S; ++i ) { candOrder[ i ] = i; } - rw::sortutil::radixSortByScoreDescId( candOrder, lensRank ); - candOrder.resize( std::min( forCut.positiveHits, S ) ); - } - const std::size_t candidateTotal = candOrder.size(); - const PageWindow win = pageWindow( candidateTotal, pageLimit, pageOffset ); - - // window the rank copy: candidates inside [win.begin, win.end) keep their score, everything - // else sorts below them (-1), so packSignatures' top-N selection serves exactly the window in - // rank order — no new selection logic, and the H1 ladder still trims within the page. - std::vector pageRank( ing.symbols.size(), -1.0f ); - for( std::size_t i = win.begin; i < win.end; ++i ) { pageRank[ candOrder[ i ] ] = lensRank[ candOrder[ i ] ]; } - - std::string sigsDoc; - const std::size_t sigsBytesBudget = tokenBudget > 0 - ? ( std::size_t( double( tokenBudget ) * kMinBytesPerToken ) > 600 - ? std::size_t( double( tokenBudget ) * kMinBytesPerToken ) - 600 : 0 ) - : 0; - // SINK FORM (Diagnostics.h §4b — the contract selfcheckcheck (R) pins): the page document is the - // sink. The degrade field is what the emitter reads and surfaces as reason= on the page itself, - // so a degraded render tells the release reader IN THE ANSWER — never silently-empty stdout, - // never a sink-less stderr-only note (selfcheckcheck: the pin goes DOWN, never up). - struct ForPageRender - { - enum class DisclosureWhy : std::uint8_t { ChargeStreamRefused, ChargeStreamTorn }; - DisclosureWhy why = DisclosureWhy::ChargeStreamRefused; - void disclose( DisclosureWhy w ) noexcept { why = w; } - } forPageRender; - bool rendered = false; - { - // inline capture (preRender the lambda at 2478 is declared below this block): the same - // MemoryStream + charge-stream + DISCLOSE-on-failure discipline, kept local so the page - // does not depend on declaration order. - const std::optional redactBefore = redactPtr != nullptr ? std::optional( *redactPtr ) : std::nullopt; - const auto restoreRedact = [ & ]() noexcept { if( redactBefore ) { *redactPtr = *redactBefore; } }; - rw::MemoryStream stream; - std::FILE* const buffer = rw::openChargeStream( stream ); - if( buffer == nullptr ) - { - DISCLOSE( forPageRender, ForPageRender::DisclosureWhy::ChargeStreamRefused ); - restoreRedact(); - } - else - { - packSignatures( buffer, ing, pageRank, int( win.end - win.begin ), sigsBytesBudget, /*metrics=*/true, - nullptr, nullptr, redactPtr, - nullptr, nullptr, nullptr, nullptr, // Q3 enrichments: page rows are the core sig form - /*rankAdaptivePayload=*/true, - sigsBytesBudget, // H1 ladder over the page - nullptr, rootArg, - /*hasRelevanceFloor=*/true, nullptr ); - const rw::MemoryStreamBytes block = stream.finish(); - if( !block.isWhole ) - { - DISCLOSE( forPageRender, ForPageRender::DisclosureWhy::ChargeStreamTorn ); - restoreRedact(); - } - else - { - sigsDoc.assign( block.bytes ); - rendered = true; - } - } - } - if( !rendered ) - { - // the emitter READS the sink: the honest degrade page carries the full quintet with zero rows - // and names why the render was lost. std::string assembly only — the counted emitter-API names - // never appear here, so fixedbufsweep's pinned enumeration does not drift (the lesson of this - // branch's first CI failure, learned twice: prose that NAMES a counted token IS a counted line). - std::vector escTask; const std::string_view taskAttr = escapeXml( task, escTask ); - std::vector escRoute; const std::string_view routeAttr = escapeXml( routeNote, escRoute ); - std::vector escRoot; const std::string_view rootAttr = escapeXml( rootArg, escRoot ); - const bool belowDegrade = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; - std::string degrade = " 0 ? "1" : "0" ) - + "\" has_more=\"" + ( win.end < candidateTotal ? "1" : "0" ) - + "\" next_offset=\"" + std::to_string( win.end ) - + "\" offset=\"" + std::to_string( win.begin ) - + "\" limit=\"" + std::to_string( pageLimit > 0 ? pageLimit : 0 ) - + "\" tier=\"" + ( belowDegrade ? "below-cliff" : "head" ) - + "\" reason=\"" + ( forPageRender.why == ForPageRender::DisclosureWhy::ChargeStreamTorn - ? "sigs-charge-stream-torn" : "sigs-charge-stream-refused" ) - + "\" est_tokens=\"0\" budget_tokens=\"" + std::to_string( tokenBudget ) - + "\" root=\"" + std::string( rootAttr ) + "\">"; - const std::string est = " est_tokens=\"" + std::to_string( std::size_t( double( degrade.size() ) / kMinBytesPerToken ) ) + "\""; - const std::size_t estAt = degrade.find( " est_tokens=\"0\"" ); - degrade.replace( estAt, est.size(), est ); - std::fwrite( degrade.data(), 1, degrade.size(), stdout ); - return 0; - } - - // rows actually printed: packSignatures writes its own shown= only when its ladder trimmed; - // an untrimmed window means every row survived — shown == window size. - std::size_t shown = win.end - win.begin; - { - const std::size_t openEnd = sigsDoc.find( '>' ); - const std::size_t openBeg = sigsDoc.find( " openBeg ) - { - const std::string openTag = sigsDoc.substr( openBeg, openEnd - openBeg ); - const std::size_t shownAt = openTag.find( "shown=\"" ); - if( shownAt != std::string::npos ) - { - shown = 0; - for( std::size_t d = shownAt + 7; d < openTag.size() && openTag[ d ] >= '0' && openTag[ d ] <= '9'; ++d ) - { - shown = shown * 10 + std::size_t( openTag[ d ] - '0' ); - } - } - } - } - - // splice the page opening: the page-level quintet REPLACES the window-scoped shown=/total=/ - // capped= (the page answers in candidate-set arithmetic; the within-page H1 trim is visible - // as shown < window). Rows and the close tag are kept verbatim. - std::string pageOut; - { - std::vector escTask; // ONE scratch buffer PER attribute: escapeXml appends to the - std::vector escRoute; // buffer and returns a view of it, so two calls sharing one - std::vector escRoot; // buffer inside one emit call's argument list race on realloc and - const std::string_view taskAttr = escapeXml( task, escTask ); // mangle each other. - const std::string_view routeAttr = escapeXml( routeNote, escRoute ); - const std::string_view rootAttr = escapeXml( std::string_view( rootArg ), escRoot ); - const std::size_t openBeg = sigsDoc.find( "' ); - const std::size_t keepFrom = ( openBeg != std::string::npos && openEnd != std::string::npos ) ? openEnd + 1 : 0; - const bool below = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; - const std::string rows = sigsDoc.substr( keepFrom ); // rows + close, kept verbatim - // std::string assembly, NOT a fixed char buffer: the task attribute is caller-controlled - // (an escaped 300-char task overflows any sane fixed size, and the first cut of this page - // truncated has_more= mid-attribute at 160 bytes — a malformed document shipped as - // output). Unbounded is the only honest size here. - std::string head = " 0 ? pageLimit : 0 ) - + "\" tier=\"" + ( below ? "below-cliff" : "head" ) + "\""; - // est_tokens is MEASURED from the page's own final bytes; the tail's digit count feeds - // back into the total, so one re-derivation closes the loop (it converges immediately — - // a digit count change moves the total by a byte, ~0.42 tokens). - std::size_t estTokens = std::size_t( double( head.size() + rows.size() + 64 ) / kMinBytesPerToken ); - std::string tail; - std::size_t tailLen = 0; - for( int pass = 0; pass < 2; ++pass ) - { - tail = " est_tokens=\"" + std::to_string( estTokens ) - + "\" budget_tokens=\"" + std::to_string( tokenBudget ) - + "\" root=\"" + std::string( rootAttr ) + "\">"; - tailLen = tail.size(); - const std::size_t finalEst = std::size_t( double( head.size() + tailLen + rows.size() ) / kMinBytesPerToken ); - if( finalEst == estTokens ) { break; } - estTokens = finalEst; - } - pageOut.assign( head ).append( tail ).append( rows ); - const std::size_t pageBytes = pageOut.size(); - std::fwrite( pageOut.data(), 1, pageOut.size(), stdout ); - if( estTokens > std::size_t( tokenBudget ) ) - { - // the honest ceiling contract, same shape the bundle's over_ceiling= clause serves: - // a page IS an answer under the same budget; say so, never silently exceed it. - rw::emitTo( stderr, "ripwire: for-page est_tokens={} exceeds the stated budget={} (the splice " - "reserve was too small) — please report the command line\n", estTokens, tokenBudget ); - } - } - return 0; + const std::string doc = rw::forCandidatePageDoc( ing, lensRank, forCut, task, routeNote, rootArg, + redactPtr, tokenBudget, pageLimit, pageOffset ); + std::fwrite( doc.data(), 1, doc.size(), stdout ); return 0; } + std::optional runForLens( const MainDispatch& d ) { using namespace rw; diff --git a/test/listingpagingcheck.sh b/test/listingpagingcheck.sh index 774156df0..9caaeabd0 100755 --- a/test/listingpagingcheck.sh +++ b/test/listingpagingcheck.sh @@ -564,6 +564,32 @@ if p0 is not None and p3 is not None and p6 is not None: if not problems: print(f" PASS (G) seam: {len(whole)} candidate rows, two pages == the whole, rank order, no overlap") +# ARM B2 (issue #294 follow-up): the candidate page carries next= — the RESUMABLE ARGV, the same +# contract the file page's next= serves (forwidencheck arm (5) reproduces its page from it). The handle +# must name the offset the machine attribute next_offset= declares, so a client that pastes it walks the +# exact continuation. RED until the page grows the attribute (next_offset= alone is machine-only). +import re as _re +next_attr = None; next_offset_attr = None +for name in ("g_budget_p0", "g_budget_p3"): + path = os.path.join(tmp, name) + if not os.path.exists(path) or os.path.getsize(path) == 0: continue + raw = open(path, encoding="utf-8", errors="replace").read() + # scope to the ROOT OPENING TAG (rows carry their own next= expand hints — the arm's first cut + # read the row's handle and mis-filed it as the page's) + o = raw.find( "", o ) + opentag = raw[ o : oend ] if o >= 0 and oend > o else "" + m = _re.search(r' next="([^"]*)"', opentag); n = _re.search(r' next_offset="(\d+)"', opentag) + if m: next_attr = m.group(1) + if n: next_offset_attr = n.group(1) + if next_attr and next_offset_attr: + if ( "--offset=" + next_offset_attr ) not in next_attr: + problems.append(f"next= does not name the declared continuation: next={next_attr!r} vs next_offset={next_offset_attr}") + else: + print(f" PASS (G2) next= names the exact continuation (--offset={next_offset_attr})") + break +if not next_attr: + problems.append("the candidate page root carries no next= — the resumable argv handle the file page's next= contract serves (next_offset= alone is machine-only)") + # mutation: the seam shape can fail — an overlapping pair of fabricated pages must be DETECTED mut_a, mut_b = ["a", "b", "c"], ["b", "c", "d"] if len(set(mut_a) & set(mut_b)) != 2: diff --git a/test/mcpverbscheck.sh b/test/mcpverbscheck.sh index b529b71b6..f07d73502 100755 --- a/test/mcpverbscheck.sh +++ b/test/mcpverbscheck.sh @@ -801,6 +801,58 @@ case "$MUT_TXT" in *) ok "(6d) mutation: accepted-but-quintet-free falls to the honest red branch" ;; esac +# ── (6e)/(6f) MCP BUDGETED-BUNDLE CANDIDATE PAGE (issue #294 follow-up: the twins) ──────────────── +# The CLI --for/--pack-task serve the candidate page under budget+window (PR #3, merged). The MCP twins +# must not diverge. Measured 2026-09-23 on this binary, before this arm's implementation: +# * MCP `for` {budget_tokens, limit} REFUSES ("limit/offset select the file page, which has no token +# budget to shape against") — honest, but no page. +# * MCP `explore` {budget_tokens, limit} serves the BUDGETLESS FILE PAGE with budget_tokens silently +# ignored — the accept-and-ignore class (the exact hole W3FIX M4 closed for budget_aliases). +# Both arms demand the SAME contract the CLI serves: the -rooted candidate page with the pageview +# quintet, tier=, and est_tokens within the stated budget. RED until the twins land. +for_twins_budget_page() +{ + local verb="$1" + printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \ + '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"'"$verb"'","arguments":{"path":"'"$CORPUS"'","task":"engine scheduling run loop","budget_tokens":600,"limit":3}}}' \ + | "$BIN" --mcp 2>/dev/null | tail -1 | python3 -c ' +import sys, json +try: r = json.loads( sys.stdin.read() ) +except Exception: print( "PARSE_ERROR" ); sys.exit(0) +if "error" in r: print( "MCPError:" + str( r["error"].get("message","") )[:90] ); sys.exit(0) +try: t = r["result"]["content"][0]["text"] +except Exception: print( "NO_TEXT" ); sys.exit(0) +print( t.lstrip()[ : 400 ] ) +' +} +for verb in for explore; do + OUT="$( for_twins_budget_page "$verb" )" + case "$OUT" in + " rows) and `mentions` (100 files) are capped on this surface alone — the CLI twins print every row — From 1d0c44b0a6f518d146f57c6442ca9f3f11f6b22d Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 18:19:48 +0100 Subject: [PATCH 007/170] =?UTF-8?q?feat(paging):=20the=20#294=20ruling's?= =?UTF-8?q?=20suggestions=20=E2=80=94=20above=5Fcliff=3D,=20next=5Ftier=3D?= =?UTF-8?q?,=20the=20at=3D=20index=20stamp,=20and=20the=20end-to-end=20wal?= =?UTF-8?q?k=20arm=20(#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maintainer ruling (redhat-et issue #294, comment 5868657455) blessed the design as-is and offered five optional suggestions. Two we already satisfied (#4 per-page trim disclosure — truncvocabcheck arm (H)'s within-page rule-5 sweep; #3's canonical tie-break — radixSortByScoreDescId is the same (score desc, id asc) key the bundle selects with). Three land here: - above_cliff= (suggestion 1): the head-tier candidate count next to total=, so an agent decides from page 1 whether the below-cliff remainder is worth fetching. cliffRank is 1-based, so above_cliff = cliffRank-1 — the same boundary tier= uses, one definition. Also on the degrade page. - next_tier= (suggestion 2): the tier the CONTINUATION page will carry, beside next= — a consumer skips the below-cliff remainder deliberately, not by accident. Fixed literals; always present. - at= (suggestion 3): the git index stamp on every page (the same gitstamp::stampAt the un-paged bundle anchors with), so a stale continuation is detectable — the consumer compares at= across pages instead of silently mixing two rankings. Empty (non-git / multi-root) => honestly absent. - (G3) the end-to-end walk (suggestion 5): concatenating every page equals the full ranked candidate list — the pages' own shown= arithmetic tiles the candidate set (pseudo-symbols like are served but emit no row, so named rows <= shown), the (file, line, name) TRIPLE tiles the named-def rows in rank order (same-name definitions are distinct candidates even in one file — the packtask.h:1690 lesson, learned twice this arm), the tier sequence is head* then below-cliff* (one boundary, unmoved mid-walk), and the un-paged bundle's served rows are a PREFIX of the walk. Walked on src/: 5 pages, 946 rows tiling 947 candidates, head -> below-cliff x4. Arms extended in the same commits as the behavior: (H) asserts above_cliff= (< total) and at= on every paged root; (G2) asserts next_tier= is one of the two values and present beside next=. Verified: 11/11 gates ALL PASS (truncvocabcheck+H, listingpagingcheck+G2+G3, mcpverbscheck, fixedbufsweep, budgetpolicycheck, packtaskcheck, mcpmanifestcheck, selfcheckcheck, forwidencheck, forbudgetmonotoncheck); the page is byte-deterministic and xmllint-clean; the MCP twins carry the same document (above_cliff= live). Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com> --- src/forpage.h | 22 +++++++-- src/mcpverbs.h | 4 +- src/verbs_for.h | 12 +++-- test/listingpagingcheck.sh | 93 ++++++++++++++++++++++++++++++++++++++ test/truncvocabcheck.sh | 9 ++++ 5 files changed, 131 insertions(+), 9 deletions(-) diff --git a/src/forpage.h b/src/forpage.h index 7227ee49b..1c74495b3 100644 --- a/src/forpage.h +++ b/src/forpage.h @@ -167,7 +167,8 @@ inline std::string forWidenNext( std::string_view task ) // byte-identical: its resume point is its own . inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std::vector& lensRank, const rw::AdaptiveCut& forCut, std::string_view task, std::string_view routeNote, std::string_view rootArg, - rw::RedactCounts* redactPtr, std::size_t tokenBudget, int pageLimit, int pageOffset ) + rw::RedactCounts* redactPtr, std::string_view atStamp, + std::size_t tokenBudget, int pageLimit, int pageOffset ) { using namespace rw; // the file's idiom: function-scoped (verbs_for.h:88 and five more) // the ranked candidate order: ALL ids sorted (score desc, id asc) — the same total order @@ -251,6 +252,7 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: std::vector escTask; const std::string_view taskAttr = escapeXml( task, escTask ); std::vector escRoute; const std::string_view routeAttr = escapeXml( routeNote, escRoute ); std::vector escRoot; const std::string_view rootAttr = escapeXml( rootArg, escRoot ); + std::vector escAt; // the degrade page's own at= scratch (one buffer per attribute) const bool belowDegrade = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; std::string degrade = " 0 ? pageLimit : 0 ) + "\" tier=\"" + ( belowDegrade ? "below-cliff" : "head" ) + + "\" above_cliff=\"" + std::to_string( forCut.cliffRank > 0 ? forCut.cliffRank - 1 : 0 ) + "\" reason=\"" + ( forPageRender.why == ForPageRender::DisclosureWhy::ChargeStreamTorn ? "sigs-charge-stream-torn" : "sigs-charge-stream-refused" ) + "\" est_tokens=\"0\" budget_tokens=\"" + std::to_string( tokenBudget ) - + "\" root=\"" + std::string( rootAttr ) + "\">"; + + "\" root=\"" + std::string( rootAttr ) + + ( atStamp.empty() ? std::string() : " at=\"" + std::string( escapeXml( atStamp, escAt ) ) + "\"" ) + ">"; const std::string est = " est_tokens=\"" + std::to_string( std::size_t( double( degrade.size() ) / kMinBytesPerToken ) ) + "\""; const std::size_t estAt = degrade.find( " est_tokens=\"0\"" ); degrade.replace( estAt, est.size(), est ); @@ -299,6 +303,7 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: std::vector escTask; // ONE scratch buffer PER attribute: escapeXml appends to the std::vector escRoute; // buffer and returns a view of it, so two calls sharing one std::vector escRoot; // buffer inside one emit call's argument list race on realloc and + std::vector escAt; // one buffer per attribute (the shared-buffer bug, see escTask) const std::string_view taskAttr = escapeXml( task, escTask ); // mangle each other. const std::string_view routeAttr = escapeXml( routeNote, escRoute ); const std::string_view rootAttr = escapeXml( std::string_view( rootArg ), escRoot ); @@ -313,6 +318,12 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: // output). Unbounded is the only honest size here. std::string head = " 0 ? forCut.cliffRank - 1 : 0 ) + "\" capped=\"" + ( shown < candidateTotal ? "1" : "0" ) + "\" has_more=\"" + ( win.end < candidateTotal ? "1" : "0" ) + "\" next_offset=\"" + std::to_string( win.end ) @@ -329,6 +340,10 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: inv += " --offset=" + std::to_string( win.end ); if( inv.size() > kNextAttrMaxBytes ) { inv.clear(); } const std::string nextAttr = nextAttrXml( inv ); + // next_tier (ruling, suggestion 2): the tier the CONTINUATION page will carry, so a consumer + // can skip the below-cliff remainder deliberately rather than by accident. Fixed literals — + // no escaping needed. Always present beside next= (the boundary is knowable on every page). + const std::string nextTierAttr = std::string( " next_tier=\"" ) + ( ( forCut.cliffRank > 0 && win.end >= forCut.cliffRank ) ? "below-cliff" : "head" ) + "\""; // est_tokens is MEASURED from the page's own final bytes; the tail's digit count feeds // back into the total, so one re-derivation closes the loop (it converges immediately — // a digit count change moves the total by a byte, ~0.42 tokens). @@ -339,7 +354,8 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: { tail = " est_tokens=\"" + std::to_string( estTokens ) + "\" budget_tokens=\"" + std::to_string( tokenBudget ) - + "\" root=\"" + std::string( rootAttr ) + "\"" + nextAttr + ">"; + + "\" root=\"" + std::string( rootAttr ) + "\"" + nextAttr + nextTierAttr + + ( atStamp.empty() ? std::string() : " at=\"" + std::string( escapeXml( atStamp, escAt ) ) + "\"" ) + ">"; tailLen = tail.size(); const std::size_t finalEst = std::size_t( double( head.size() + tailLen + rows.size() ) / kMinBytesPerToken ); if( finalEst == estTokens ) { break; } diff --git a/src/mcpverbs.h b/src/mcpverbs.h index f609a36c6..f08ad4760 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -1964,7 +1964,7 @@ inline std::optional forTaskText( const std::string& root, const st { const AdaptiveCut cut = adaptiveCut( lensRank, 5, std::size_t( forTopN ), /*scanFullDistribution=*/true ); return forCandidatePageDoc( ing, lensRank, cut, task, routeNoteOf( rc, shape, noRoute ), mcpRootArg, - redact, budgetTokens, page.limit, page.offset ); + redact, gitstamp::stampAt( root ), budgetTokens, page.limit, page.offset ); } const ForFilePage filePage = computeForFilePage( ing, lensRank, mcpEvidence ); // PR #215 review item 4: this page composed "routed: " + rc.reason by hand and so answered in a spelling @@ -4186,7 +4186,7 @@ inline std::string packTaskText( const std::string& root, const std::string& tas { const AdaptiveCut cut = adaptiveCut( lr.rank, 5, std::size_t( kForLensDefaultTopN ), /*scanFullDistribution=*/true ); return forCandidatePageDoc( ing, lr.rank, cut, task, lr.routeNote, mcpRootArg, - redact, budgetTokens, pageLimit, pageOffset ); + redact, gitstamp::stampAt( root ), budgetTokens, pageLimit, pageOffset ); } const ForFilePage filePage = computeForFilePage( ing, lr.rank, lr.evidence ); const std::string pageRootOpen = ctxRootOpen( task, lr.routeNote, mcpRootArg ); diff --git a/src/verbs_for.h b/src/verbs_for.h index 6124db79e..d4c0cf9e0 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2218,10 +2218,11 @@ inline std::string renderForHdrRowsXml( const rw::IngestResult& ing, const std:: // three dialects — the CLI bundle verbs and the MCP twins all build the SAME document. inline int emitForCandidatePage( const rw::IngestResult& ing, const std::vector& lensRank, const rw::AdaptiveCut& forCut, std::string_view task, std::string_view routeNote, std::string_view rootArg, - rw::RedactCounts* redactPtr, std::size_t tokenBudget, int pageLimit, int pageOffset ) + rw::RedactCounts* redactPtr, std::string_view atStamp, + std::size_t tokenBudget, int pageLimit, int pageOffset ) { const std::string doc = rw::forCandidatePageDoc( ing, lensRank, forCut, task, routeNote, rootArg, - redactPtr, tokenBudget, pageLimit, pageOffset ); + redactPtr, atStamp, tokenBudget, pageLimit, pageOffset ); std::fwrite( doc.data(), 1, doc.size(), stdout ); return 0; } @@ -2451,7 +2452,9 @@ std::optional runForLens( const MainDispatch& d ) if( cfg.tokenBudget != 0 && ( cfg.pageLimit > 0 || cfg.pageOffset > 0 ) ) { return emitForCandidatePage( ing, lensRank, forCut, cfg.forTask, routeNoteRaw, flRootArg, - redactPtr, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); + redactPtr, + flSingleRoot ? std::string_view( gitstamp::stampAt( std::string( root ) ) ) : std::string_view(), + cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); } // L-W: coverage= rides the SAME sentence and the SAME byte exemption as confidence=/margin_pct= — but ONLY // on a THIN answer (owner decision 2026-09-12: present-only). The thin verdict is decided HERE, from the @@ -3815,8 +3818,9 @@ std::optional runPackTask( const MainDispatch& d ) return 1; } const AdaptiveCut packCut = adaptiveCut( lr.rank, 5, std::size_t( cfg.packTopN > 0 ? cfg.packTopN : kForLensDefaultTopN ), true ); + const std::string packAtStamp = in.rootArg.empty() ? std::string() : gitstamp::stampAt( std::string( in.rootArg ) ); return emitForCandidatePage( ing, lr.rank, packCut, task, lr.routeNote, in.rootArg, - d.redactPtr, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); + d.redactPtr, packAtStamp, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); } // --partition=N: the FAN-OUT form. Same lens ranking, same PackTaskInputs, same diff --git a/test/listingpagingcheck.sh b/test/listingpagingcheck.sh index 9caaeabd0..a84ae384a 100755 --- a/test/listingpagingcheck.sh +++ b/test/listingpagingcheck.sh @@ -579,6 +579,7 @@ for name in ("g_budget_p0", "g_budget_p3"): o = raw.find( "", o ) opentag = raw[ o : oend ] if o >= 0 and oend > o else "" m = _re.search(r' next="([^"]*)"', opentag); n = _re.search(r' next_offset="(\d+)"', opentag) + t = _re.search(r' next_tier="([^"]*)"', opentag) # ruling suggestion 2: the tier the NEXT page carries if m: next_attr = m.group(1) if n: next_offset_attr = n.group(1) if next_attr and next_offset_attr: @@ -587,6 +588,10 @@ for name in ("g_budget_p0", "g_budget_p3"): else: print(f" PASS (G2) next= names the exact continuation (--offset={next_offset_attr})") break +if t and t.group(1) not in ("head", "below-cliff"): + problems.append(f"next_tier={t.group(1)!r} is not one of the two tier values") +if not t and next_attr: + problems.append("next= present but next_tier= absent — the ruling's suggestion 2 (the handle must make the next page's tier visible)") if not next_attr: problems.append("the candidate page root carries no next= — the resumable argv handle the file page's next= contract serves (next_offset= alone is machine-only)") @@ -603,5 +608,93 @@ PY [ $? = 0 ] && ok '(G) budgeted-bundle pages are the exact continuation (candidates, rank order, no overlap)' \ || no '(G) the budgeted-bundle continuation seam (upstream issue #294) does not hold — see FAIL lines above' +# ── (G3) END-TO-END WALK (issue #294 ruling, suggestion 5): every page concatenated equals the full ── +# ranked candidate list — no gap, no overlap, same order — and the tier sequence is head* then +# below-cliff* (exactly one boundary). Walked at a budget large enough that no within-page trim fires, +# so the row NAMES are the ranked set itself. The un-paged bundle's served rows (top-40 of the same +# order, same budget) must be a PREFIX of the walk. +echo "=== (G3) end-to-end walk: all pages concatenated == the full ranked candidate list ===" +WALK_BUDGET=100000 +# on the big src/ corpus: enough candidates (878 for this query) that the walk spans multiple pages +# and crosses the tier boundary — the root corpus' 5 candidates fit one page and prove nothing about seams. +run "$ROOT/src" g3_bundle --for="rank symbols" --token-budget=$WALK_BUDGET +offset=0; page=0 +while [ "$page" -lt 12 ]; do + run "$ROOT/src" "g3_walk_$page" --for="rank symbols" --token-budget=$WALK_BUDGET --limit=200 --offset=$offset + shown="$( attr "$TMP/g3_walk_$page" sigs shown )"; has_more="$( attr "$TMP/g3_walk_$page" sigs has_more )" + nextoff="$( attr "$TMP/g3_walk_$page" sigs next_offset )"; tier="$( attr "$TMP/g3_walk_$page" sigs tier )" + total="$( attr "$TMP/g3_walk_$page" sigs total )" + [ -n "$shown" ] || { no "(G3) walk page $page produced no parseable root"; break; } + echo "$tier" >> "$TMP/g3_tiers.txt" + echo "$shown" >> "$TMP/g3_shown.txt" + # rows carry n= AFTER other attributes (]*>", raw ): + t = tag.group( 0 ) + n = re.search( r"\sn=\"([^\"]*)\"", t ) + p = re.search( r"\sp=\"([^\"]*)\"", t ) + l = re.search( r"\sl=\"([^\"]*)\"", t ) + print( ( p.group( 1 ) if p else "?" ) + "\x1f" + ( l.group( 1 ) if l else "?" ) + "\x1f" + ( n.group( 1 ) if n else "?" ) ) +' "$TMP/g3_walk_$page" >> "$TMP/g3_rows.txt" + page=$(( page + 1 )) + [ "$has_more" = "1" ] || break + offset="$nextoff" +done +walk_pages="$page" + +python3 - "$TMP" "$walk_pages" "$total" <<'PY' +import os, sys +tmp, pages, total = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]) +problems = [] +rows = [ l.rstrip("\n") for l in open(os.path.join(tmp, "g3_rows.txt")) if l.strip() ] +tiers = [ l.rstrip("\n") for l in open(os.path.join(tmp, "g3_tiers.txt")) if l.strip() ] +shown_sum = sum( int( x ) for x in open(os.path.join(tmp, "g3_shown.txt")) if x.strip() ) +dups = len(rows) - len(set(rows)) +if dups: + problems.append(f"walk: {dups} duplicate candidates across pages — a row served twice is a page that lies") +# the tiling measure is the pages' own shown= arithmetic (candidates SERVED); the ) is counted as served but emits no row, +# so shown-sum == total is the contract, and the row list tiles the named-def subset in order. +if shown_sum != total: + problems.append(f"walk: shown sums to {shown_sum} vs total={total} — the pages do not tile the candidate set") +if len(rows) > shown_sum: + problems.append(f"walk: {len(rows)} named rows exceed the {shown_sum} served candidates") +# tier sequence: all head pages precede all below-cliff pages (exactly one boundary) +seen_below = False +for i, t in enumerate(tiers): + if t not in ("head", "below-cliff"): + problems.append(f"walk page {i}: tier={t!r} is not one of the two values"); break + if t == "below-cliff": seen_below = True + elif seen_below: + problems.append(f"walk page {i}: tier=head AFTER a below-cliff page — the boundary moved mid-walk"); break +# the un-paged bundle's served rows are a PREFIX of the walk (same order, same budget) +bp = os.path.join(tmp, "g3_bundle") +if os.path.exists(bp) and os.path.getsize(bp) > 0: + import re + raw = open(bp, encoding="utf-8", errors="replace").read() + bundle = [] + for m in re.finditer( r']*>', raw ): + t = m.group( 0 ) + n = re.search( r'\sn="([^"]*)"', t ) + pp = re.search( r'\sp="([^"]*)"', t ) + ll = re.search( r'\sl="([^"]*)"', t ) + bundle.append( ( pp.group(1) if pp else "?" ) + "\x1f" + ( ll.group(1) if ll else "?" ) + "\x1f" + ( n.group(1) if n else "?" ) ) + if rows[:len(bundle)] != bundle: + problems.append("the un-paged bundle's served rows are NOT a prefix of the walk — the page and the bundle answer different orders") + else: + print(f" PASS (G3) the un-paged bundle's {len(bundle)} rows are a prefix of the walk (same order)") +for p_ in problems: print(" FAIL " + p_) +print(f" .. (G3) walked {pages} pages, {len(rows)} rows, total={total}, tiers: {' -> '.join(tiers)}") +sys.exit(1 if problems else 0) +PY +[ $? = 0 ] && ok '(G3) every page concatenated == the full ranked candidate list (no gap, no overlap, one tier boundary)' \ + || no '(G3) the end-to-end walk (issue #294 ruling, suggestion 5) does not hold — see FAIL lines above' + [ "$fail" = 0 ] && echo "ALL PASS" || echo "FAILURES ABOVE" exit $fail diff --git a/test/truncvocabcheck.sh b/test/truncvocabcheck.sh index da848c239..9cc904fe6 100755 --- a/test/truncvocabcheck.sh +++ b/test/truncvocabcheck.sh @@ -559,6 +559,15 @@ for name in ("for_budget_p0w", "for_budget_page", "pack_budget_page"): problems.append(f'{name}: capped="{a["capped"]}" is not the 0|1 bit (rule 3)') if int(a["shown"]) > int(a["total"]): problems.append(f'{name}: shown={a["shown"]} > total={a["total"]} — a page cannot show more than the candidate set') + # issue #294 ruling, suggestions 1+3: above_cliff (the head-tier count, next to total=) and the + # at= index stamp (which index answered; a consumer compares across pages instead of mixing two + # rankings silently). above_cliff must be < total unless the whole set is head-tier. + if "above_cliff" not in a: + problems.append(f'{name}: paged root lacks above_cliff= — the ruling\'s suggestion 1 (decide from page 1 whether the remainder is worth fetching)') + elif int(a["above_cliff"]) >= int(a["total"]) and int(a["total"]) > 0: + problems.append(f'{name}: above_cliff={a["above_cliff"]} >= total={a["total"]} — the boundary is not inside the set') + if "at" not in a: + problems.append(f'{name}: paged root lacks at= — the index stamp the ruling\'s suggestion 3 names (a stale continuation must be detectable, not silent)') # ARM C of the paging spec, housed here because THIS gate owns the rule-5 vocabulary (the # §B8.3 sweep): within a candidate page, a body byte-trim must still disclose shown=/total=/capped= # on — the candidate cursor advances the SET, rule 5 discloses the within-page byte trim. From 3f209c47144b12568afc1716b7f97516f481b7f9 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 19:54:06 +0000 Subject: [PATCH 008/170] =?UTF-8?q?fix(paging):=20the=20#362=20review=20ro?= =?UTF-8?q?und=20=E2=80=94=20global=20r=3D,=20verb-correct=20next=3D,=20re?= =?UTF-8?q?daction=20tally,=20budget=20enforcement=20+=20over=5Fceiling,?= =?UTF-8?q?=20pack-task=20flag=20guard,=20help=20text?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit's nine findings on PR #362, each verified against the live code first (one false alarm declined with the evidence); seven real, all fixed: - GLOBAL r= (forpage.h:193, Major): packSignatures gains a rankBase parameter (LAST, after cutOut; 0 = the un-windowed default, byte-identical) — the windowed serving passes win.begin, so a continuation page's rows carry their GLOBAL ranks (offset=198 now prints r=199/200/201, not r=1/2/3): the rank-adaptive doc/sig tiers key on global rank, the r=1 row's next= hint follows it, and merged pages have no duplicate r=. - next= names ITS verb (forpage.h:342, Major): forCandidatePageDoc takes verbName; --pack-task pages hand back --pack-task= continuations (pasting runs the same verb, the same ranking, the same cut). - Redaction tally (verbs_for.h:2228, Major): both page call paths report reportRedactions after serving, exactly like the --json early return — a redacted credential is never silent on the page. - --pack-task flag guard (verbs_for.h:3824, Major): the shapeFlags loop now covers the candidate page (both verbs, budget+window); --json/--compress/ --with-graph beside a --pack-task window refuse with the page named, instead of accepted-and-ignored XML at exit 0. - Budget enforcement + over_ceiling (forpage.h:371 + mcpverbs.h:1900): the assembly is rebuilt as buildHead/buildNextAttr/buildNextTierAttr/buildTail lambdas; a page that exceeds budget_tokens after the H1 ladder trims trailing whole rows (the handoff.h tail-first policy) and re-derives its own arithmetic (shown/next_offset/has_more/next=) from what shipped; a page that cannot fit even empty (a pathological task length) serves empty with over_ceiling=1 — the same marker the un-paged bundle uses — plus the stderr note. est_tokens two-pass convergence preserved per construction. - Help text (cli.h, Minor): the --limit/--offset paragraph names both pages (the file page; the budgeted-bundle candidate page = budget + window). - (6e) arm hardened (test/mcpverbscheck.sh): parses budget_tokens= and requires est_tokens <= budget_tokens or over_ceiling=1. BONUS FINDING the rank fix exposed (mine, fixed with it): when the H1 ladder trims within a page, next_offset= used to say win.end — silently SKIPPING the trimmed rows. next_offset is now always win.begin + shown (identical when nothing trimmed; gapless at any budget). Declined with evidence: the est-splice 'corrupts the next attribute' claim — the code replaces by SEARCH STRING (' est_tokens="0"'), not pos+count; a python replay and live output both show the splice correct. Verified: 13/13 gates ALL PASS (the 11-gate sweep + forrankordercheck + callsrankordercheck — the rank-order gates the r= fix could have moved); enforcement smoke: a 300-token budget trims to fit with shown/next_offset re-derived; every page shape xmllint-clean and byte-deterministic. --- src/cli.h | 22 +++++-- src/forpage.h | 135 ++++++++++++++++++++++++++++-------------- src/mcpverbs.h | 4 +- src/serialize.h | 9 ++- src/verbs_for.h | 14 +++-- test/mcpverbscheck.sh | 21 +++++-- 6 files changed, 143 insertions(+), 62 deletions(-) diff --git a/src/cli.h b/src/cli.h index 5a5d5a035..650b4f8df 100644 --- a/src/cli.h +++ b/src/cli.h @@ -2633,9 +2633,9 @@ inline constexpr char kHelpTail[] = " Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and\n" " ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/\n" " --expand/--outline/--pack-signatures/--format=candidates) are shaped by\n" - " --top-k/--max-tokens/--token-budget, not a page (--for's bare bundle is shaped by\n" - " --token-budget the same way, and takes --limit/--offset only as its file page, where\n" - " the budget flags are refused in turn); the rest (--path/--connect/\n" + " --token-budget the same way; --for/--pack-task take --limit/--offset as their FILE" + " page, where the budget flags are refused in turn, or beside --token-budget as the" + " BUDGETED-BUNDLE CANDIDATE PAGE (the window over the ranked candidates, their resumable continuation: budget + window together); the rest (--path/--connect/\n" " --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map)\n" " answer with a single fixed-shape result that has no row list to window at all.\n" " --exclude=SUBSTR drop matching paths (repeatable) --ignore-tests\n" @@ -4963,8 +4963,18 @@ inline void validateConfig( Config& c ) noexcept // every bundle-shaping flag beside it would be accepted-and-ignored, the named failure family this file // refuses everywhere else (§H4). Named one at a time, so the remedy is the flag to drop. --top-k, // --max-tokens and --token-budget are refused by validateShapingFlagsHonored (the page is a paging member). - if( !c.forTask.empty() && ( c.pageLimit > 0 || c.pageOffset > 0 ) ) - { + // #294 review (verbs_for.h:3824): --pack-task beside a budgeted window serves the CANDIDATE page, + // and the candidate page (like the file page) is a fixed / shape — bundle-shaping flags + // would be accepted-and-ignored there too. Both pages refuse them; the message names the page that + // was selected, so the remedy (drop the flag, or the window) reads correctly. + const bool forFilePageSelected = !c.forTask.empty() && c.tokenBudget == 0 && ( c.pageLimit > 0 || c.pageOffset > 0 ); + const bool forCandidateSelected = ( ( !c.forTask.empty() || c.packTaskFlag ) && c.tokenBudget != 0 + && ( c.pageLimit > 0 || c.pageOffset > 0 ) ); + if( forFilePageSelected || forCandidateSelected ) + { + const char* const pageName = forFilePageSelected + ? "--for --limit/--offset is the file-grain widening page (one row per file, its own document)" + : "--for/--pack-task --token-budget with --limit/--offset is the budgeted-bundle candidate page (one fixed window, its own resumable document)"; struct PageShapeFlag { const char* name; bool set; }; const PageShapeFlag shapeFlags[] = { { "--json", c.json }, { "--format=candidates", c.candidates }, @@ -4977,7 +4987,7 @@ inline void validateConfig( Config& c ) noexcept { if( f.set ) { - rw::emitTo( stderr, "ripwire: --for --limit/--offset is the file-grain widening page (one row per file, its own document) — it has no bundle for {} to shape, so the flag is refused rather than ignored: drop {} for the page, or drop --limit/--offset for the bundle\n", f.name, f.name ); + rw::emitTo( stderr, "ripwire: {} — it has no bundle for {} to shape, so the flag is refused rather than ignored: drop {} for the page, or drop the window for the bundle\n", pageName, f.name, f.name, f.name ); c.ok = false; } } diff --git a/src/forpage.h b/src/forpage.h index 1c74495b3..203d0b821 100644 --- a/src/forpage.h +++ b/src/forpage.h @@ -166,7 +166,8 @@ inline std::string forWidenNext( std::string_view task ) // gains a paging attribute). The UN-PAGED bundle the callers serve otherwise is untouched and // byte-identical: its resume point is its own . inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std::vector& lensRank, const rw::AdaptiveCut& forCut, - std::string_view task, std::string_view routeNote, std::string_view rootArg, + std::string_view task, std::string_view verbName, // "--for"/"--pack-task": the handle + r=1 hint name THEIR verb (#294 review) + std::string_view routeNote, std::string_view rootArg, rw::RedactCounts* redactPtr, std::string_view atStamp, std::size_t tokenBudget, int pageLimit, int pageOffset ) { @@ -229,7 +230,9 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: /*rankAdaptivePayload=*/true, sigsBytesBudget, // H1 ladder over the page nullptr, rootArg, - /*hasRelevanceFloor=*/true, nullptr ); + /*hasRelevanceFloor=*/true, nullptr, + /*shownIdsOut=*/nullptr, /*cappedOut=*/nullptr, /*topRowNext=*/{}, /*cutOut=*/nullptr, + std::uint32_t( win.begin ) ); // rankBase (LAST param): continuation rows carry their GLOBAL r= (#294 review) const rw::MemoryStreamBytes block = stream.finish(); if( !block.isWhole ) { @@ -315,59 +318,103 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: // std::string assembly, NOT a fixed char buffer: the task attribute is caller-controlled // (an escaped 300-char task overflows any sane fixed size, and the first cut of this page // truncated has_more= mid-attribute at 160 bytes — a malformed document shipped as - // output). Unbounded is the only honest size here. - std::string head = " 0 ? forCut.cliffRank - 1 : 0 ) - + "\" capped=\"" + ( shown < candidateTotal ? "1" : "0" ) - + "\" has_more=\"" + ( win.end < candidateTotal ? "1" : "0" ) - + "\" next_offset=\"" + std::to_string( win.end ) - + "\" offset=\"" + std::to_string( win.begin ) - + "\" limit=\"" + std::to_string( pageLimit > 0 ? pageLimit : 0 ) - + "\" tier=\"" + ( below ? "below-cliff" : "head" ) + "\""; + // output). Unbounded is the only honest size here. THE #294 REVIEW RESTRUCTURE: head, handle + // and tail are LAMBDAS now, so the budget-enforcement pass below can trim trailing rows and + // re-derive the page's own arithmetic (shown/next_offset/has_more/next=) from what actually + // shipped — one construction, reused verbatim by every pass. + const auto buildHead = [ & ]( std::size_t shownNow, std::size_t nextOff ) { + return " 0 ? forCut.cliffRank - 1 : 0 ) + + "\" capped=\"" + ( shownNow < candidateTotal ? "1" : "0" ) + + "\" has_more=\"" + ( nextOff < candidateTotal ? "1" : "0" ) + + "\" next_offset=\"" + std::to_string( nextOff ) + + "\" offset=\"" + std::to_string( win.begin ) + + "\" limit=\"" + std::to_string( pageLimit > 0 ? pageLimit : 0 ) + + "\" tier=\"" + ( below ? "below-cliff" : "head" ) + "\""; + }; // THE PASTEABLE CONTINUATION HANDLE — the file page's next= contract (forpage.h's own // forPageInvocation shape): the argv that walks to next_offset. Dropped entirely past // kNextAttrMaxBytes (a hint that pastes wrong is worse than no hint); the machine // next_offset= always remains. listingpagingcheck arm (G2) pins the pair together. - std::string inv = nextFlag( "--for=", task ); - inv += " --token-budget=" + std::to_string( tokenBudget ); - if( pageLimit > 0 ) { inv += " --limit=" + std::to_string( pageLimit ); } - inv += " --offset=" + std::to_string( win.end ); - if( inv.size() > kNextAttrMaxBytes ) { inv.clear(); } - const std::string nextAttr = nextAttrXml( inv ); + const auto buildNextAttr = [ & ]( std::size_t nextOff ) { + std::string inv = nextFlag( verbName, task ); // THEIR verb: --pack-task pages hand back --pack-task=, so pasting continues the same verb (#294 review) + inv += " --token-budget=" + std::to_string( tokenBudget ); + if( pageLimit > 0 ) { inv += " --limit=" + std::to_string( pageLimit ); } + inv += " --offset=" + std::to_string( nextOff ); + if( inv.size() > kNextAttrMaxBytes ) { inv.clear(); } + return nextAttrXml( inv ); + }; // next_tier (ruling, suggestion 2): the tier the CONTINUATION page will carry, so a consumer // can skip the below-cliff remainder deliberately rather than by accident. Fixed literals — // no escaping needed. Always present beside next= (the boundary is knowable on every page). - const std::string nextTierAttr = std::string( " next_tier=\"" ) + ( ( forCut.cliffRank > 0 && win.end >= forCut.cliffRank ) ? "below-cliff" : "head" ) + "\""; + const auto buildNextTierAttr = [ & ]( std::size_t nextOff ) { + return std::string( " next_tier=\"" ) + ( ( forCut.cliffRank > 0 && nextOff >= forCut.cliffRank ) ? "below-cliff" : "head" ) + "\""; + }; // est_tokens is MEASURED from the page's own final bytes; the tail's digit count feeds // back into the total, so one re-derivation closes the loop (it converges immediately — // a digit count change moves the total by a byte, ~0.42 tokens). - std::size_t estTokens = std::size_t( double( head.size() + rows.size() + 64 ) / kMinBytesPerToken ); - std::string tail; - std::size_t tailLen = 0; - for( int pass = 0; pass < 2; ++pass ) - { - tail = " est_tokens=\"" + std::to_string( estTokens ) - + "\" budget_tokens=\"" + std::to_string( tokenBudget ) - + "\" root=\"" + std::string( rootAttr ) + "\"" + nextAttr + nextTierAttr - + ( atStamp.empty() ? std::string() : " at=\"" + std::string( escapeXml( atStamp, escAt ) ) + "\"" ) + ">"; - tailLen = tail.size(); - const std::size_t finalEst = std::size_t( double( head.size() + tailLen + rows.size() ) / kMinBytesPerToken ); - if( finalEst == estTokens ) { break; } - estTokens = finalEst; - } - pageOut.assign( head ).append( tail ).append( rows ); - if( estTokens > std::size_t( tokenBudget ) ) + const auto buildTail = [ & ]( const std::string& h, const std::string& r, bool overCeiling, std::size_t nextOff ) { + std::size_t est = std::size_t( double( h.size() + r.size() + 96 ) / kMinBytesPerToken ); + std::string t; + for( int pass = 0; pass < 3; ++pass ) + { + t = " est_tokens=\"" + std::to_string( est ) + + "\" budget_tokens=\"" + std::to_string( tokenBudget ) + + "\" root=\"" + std::string( rootAttr ) + "\"" + buildNextAttr( nextOff ) + buildNextTierAttr( nextOff ) + + ( overCeiling ? " over_ceiling=\"1\"" : std::string() ) + + ( atStamp.empty() ? std::string() : " at=\"" + std::string( escapeXml( atStamp, escAt ) ) + "\"" ) + ">"; + const std::size_t fin = std::size_t( double( h.size() + t.size() + r.size() ) / kMinBytesPerToken ); + if( fin == est ) { break; } + est = fin; + } + return std::pair( t, est ); + }; + // nextOff is win.begin + shown — NEVER win.end: when the H1 ladder trimmed rows within the + // page, shown < window size, and the honest continuation resumes at the first NEVER-SERVED + // candidate; a next_offset of win.end would silently skip the trimmed rows (the seam contract + // (G3) walks gaplessly at any budget because of this). Identical to win.end when nothing trimmed. + const std::size_t nextOff = win.begin + shown; + std::string headStr = buildHead( shown, nextOff ); + auto [ tailStr, estTokens ] = buildTail( headStr, rows, /*overCeiling=*/false, nextOff ); + pageOut.assign( headStr ).append( tailStr ).append( rows ); + // #294 REVIEW (enforcement + over_ceiling): a page IS an answer under the budget. The + // H1 ladder already trimmed WITHIN the page; if the assembled page still exceeds the + // budget (the escaped task in the head can outgrow the splice reserve), trim trailing + // WHOLE rows — the tail-first-drop policy handoff.h uses — and re-derive the page's own + // arithmetic from what shipped, so a stdout-only consumer reads the truth. If even an + // empty page cannot fit (a pathological task length), splice over_ceiling="1" — the + // same marker the un-paged bundle serves — and keep the stderr note. + if( tokenBudget > 0 && estTokens > std::size_t( tokenBudget ) ) { - // the honest ceiling contract, same shape the bundle's over_ceiling= clause serves: - // a page IS an answer under the same budget; say so, never silently exceed it. - rw::emitTo( stderr, "ripwire: for-page est_tokens={} exceeds the stated budget={} (the splice " - "reserve was too small) — please report the command line\n", estTokens, tokenBudget ); + std::size_t shownNow = shown; + std::string rowsNow = rows; + while( shownNow > 0 ) + { + const std::size_t lastClose = rowsNow.rfind( "" ); + if( lastClose == std::string::npos ) { break; } + rowsNow = rowsNow.substr( 0, lastClose ) + ""; + --shownNow; + const std::size_t nextOff = win.begin + shownNow; + headStr = buildHead( shownNow, nextOff ); + auto [ t2, e2 ] = buildTail( headStr, rowsNow, /*overCeiling=*/false, nextOff ); + pageOut.assign( headStr ).append( t2 ).append( rowsNow ); + if( e2 <= std::size_t( tokenBudget ) ) { break; } + } + if( std::size_t( double( pageOut.size() ) / kMinBytesPerToken ) > std::size_t( tokenBudget ) ) + { + headStr = buildHead( 0, win.begin ); + auto [ tZ, eZ ] = buildTail( headStr, "", /*overCeiling=*/true, win.begin ); + pageOut.assign( headStr ).append( tZ ).append( "" ); + rw::emitTo( stderr, "ripwire: for-page est_tokens={} exceeds the stated budget={} even with zero rows " + "(the escaped task outgrew the page shell) — the page is served empty with over_ceiling=1; please report the command line\n", + eZ, tokenBudget ); + } } } return pageOut; diff --git a/src/mcpverbs.h b/src/mcpverbs.h index f08ad4760..f988f1073 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -1963,7 +1963,7 @@ inline std::optional forTaskText( const std::string& root, const st if( budgetTokens > 0 ) { const AdaptiveCut cut = adaptiveCut( lensRank, 5, std::size_t( forTopN ), /*scanFullDistribution=*/true ); - return forCandidatePageDoc( ing, lensRank, cut, task, routeNoteOf( rc, shape, noRoute ), mcpRootArg, + return forCandidatePageDoc( ing, lensRank, cut, task, "--for=", routeNoteOf( rc, shape, noRoute ), mcpRootArg, redact, gitstamp::stampAt( root ), budgetTokens, page.limit, page.offset ); } const ForFilePage filePage = computeForFilePage( ing, lensRank, mcpEvidence ); @@ -4185,7 +4185,7 @@ inline std::string packTaskText( const std::string& root, const std::string& tas if( budgetTokens > 0 ) { const AdaptiveCut cut = adaptiveCut( lr.rank, 5, std::size_t( kForLensDefaultTopN ), /*scanFullDistribution=*/true ); - return forCandidatePageDoc( ing, lr.rank, cut, task, lr.routeNote, mcpRootArg, + return forCandidatePageDoc( ing, lr.rank, cut, task, "--pack-task=", lr.routeNote, mcpRootArg, redact, gitstamp::stampAt( root ), budgetTokens, pageLimit, pageOffset ); } const ForFilePage filePage = computeForFilePage( ing, lr.rank, lr.evidence ); diff --git a/src/serialize.h b/src/serialize.h index 78f7de82e..077bba4d6 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -4634,9 +4634,14 @@ inline void packSignatures( std::FILE* out, const IngestResult& ing, const std:: std::string_view topRowNext = {}, // L-W (forpage.h): the r=1 row's next= when the caller // judged the answer THIN (the widening page); "" ⇒ the // --expand body follow-up, byte-identical to before. - SigsCutReport* cutOut = nullptr ) // cut-fix lane A: the tag's shown/total/docs_dropped/capped, + SigsCutReport* cutOut = nullptr, // cut-fix lane A: the tag's shown/total/docs_dropped/capped, // for the caller's legend splices. Lens path only; zeroed // (nothing cut) on every other path. + std::uint32_t rankBase = 0 ) // d1/paging (#294): 1-based GLOBAL candidate-rank base for the windowed + // serving (0 = the un-windowed default, byte-identical) — a continuation + // page's rows carry their GLOBAL r= (page 2's first row is r=201, not + // r=1: the rank-adaptive doc/sig tiers and the r=1 row's next= hint key + // on GLOBAL rank), so pages merge without duplicate r= { if( cutOut ) { @@ -4702,7 +4707,7 @@ inline void packSignatures( std::FILE* out, const IngestResult& ing, const std:: globalRankOf.assign( S, 0 ); for( std::size_t k = 0; k < keep; ++k ) { - globalRankOf[order[k]] = std::uint32_t( k + 1 ); + globalRankOf[order[k]] = rankBase + std::uint32_t( k + 1 ); // + rankBase: the WINDOWED serving's global base } } diff --git a/src/verbs_for.h b/src/verbs_for.h index d4c0cf9e0..fba859cfe 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2217,11 +2217,11 @@ inline std::string renderForHdrRowsXml( const rw::IngestResult& ing, const std:: // the CLI half of the shared page (forpage.h owns the doc): write it to stdout. One implementation, // three dialects — the CLI bundle verbs and the MCP twins all build the SAME document. inline int emitForCandidatePage( const rw::IngestResult& ing, const std::vector& lensRank, const rw::AdaptiveCut& forCut, - std::string_view task, std::string_view routeNote, std::string_view rootArg, + std::string_view task, std::string_view verbName, std::string_view routeNote, std::string_view rootArg, rw::RedactCounts* redactPtr, std::string_view atStamp, std::size_t tokenBudget, int pageLimit, int pageOffset ) { - const std::string doc = rw::forCandidatePageDoc( ing, lensRank, forCut, task, routeNote, rootArg, + const std::string doc = rw::forCandidatePageDoc( ing, lensRank, forCut, task, verbName, routeNote, rootArg, redactPtr, atStamp, tokenBudget, pageLimit, pageOffset ); std::fwrite( doc.data(), 1, doc.size(), stdout ); return 0; @@ -2451,10 +2451,14 @@ std::optional runForLens( const MainDispatch& d ) // resumable candidate page (shared inline; the un-paged bundle below stays byte-identical). if( cfg.tokenBudget != 0 && ( cfg.pageLimit > 0 || cfg.pageOffset > 0 ) ) { - return emitForCandidatePage( ing, lensRank, forCut, cfg.forTask, routeNoteRaw, flRootArg, + // W3-N1/#294 review: the page redacts through packSignatures — the tally must be reported, + // exactly like the --json early return below (a redacted credential must never be silent). + const int pageRc = emitForCandidatePage( ing, lensRank, forCut, cfg.forTask, "--for=", routeNoteRaw, flRootArg, redactPtr, flSingleRoot ? std::string_view( gitstamp::stampAt( std::string( root ) ) ) : std::string_view(), cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); + reportRedactions( stderr, redactCounts ); + return pageRc; } // L-W: coverage= rides the SAME sentence and the SAME byte exemption as confidence=/margin_pct= — but ONLY // on a THIN answer (owner decision 2026-09-12: present-only). The thin verdict is decided HERE, from the @@ -3819,8 +3823,10 @@ std::optional runPackTask( const MainDispatch& d ) } const AdaptiveCut packCut = adaptiveCut( lr.rank, 5, std::size_t( cfg.packTopN > 0 ? cfg.packTopN : kForLensDefaultTopN ), true ); const std::string packAtStamp = in.rootArg.empty() ? std::string() : gitstamp::stampAt( std::string( in.rootArg ) ); - return emitForCandidatePage( ing, lr.rank, packCut, task, lr.routeNote, in.rootArg, + const int packPageRc = emitForCandidatePage( ing, lr.rank, packCut, task, "--pack-task=", lr.routeNote, in.rootArg, d.redactPtr, packAtStamp, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); + reportRedactions( stderr, d.redactCounts ); // W3-N1/#294 review: the page redacts — the tally reports + return packPageRc; } // --partition=N: the FAN-OUT form. Same lens ranking, same PackTaskInputs, same diff --git a/test/mcpverbscheck.sh b/test/mcpverbscheck.sh index f07d73502..c002c56df 100755 --- a/test/mcpverbscheck.sh +++ b/test/mcpverbscheck.sh @@ -829,13 +829,26 @@ for verb in for explore; do OUT="$( for_twins_budget_page "$verb" )" case "$OUT" in " Date: Wed, 30 Sep 2026 12:49:09 +0000 Subject: [PATCH 009/170] =?UTF-8?q?fix(paging):=20the=20#362=20human=20rev?= =?UTF-8?q?iew=20round,=20part=201=20=E2=80=94=20the=20trim=20path,=20the?= =?UTF-8?q?=20tier=20boundary,=20the=20legend,=20and=20the=20true=20resume?= =?UTF-8?q?=20point?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maintainer review (joyful-ii-V-I, 9 required + 7 optional). Part 1 — the renderer and its arms, each fix verified against the review's own repro commands first: 1. Malformed XML on a trimmed page + over_ceiling when the empty page fits: the trim now cuts at the START of the last row (a cut at its close left the row open); the one-row floor (kForPageSigsFloorBytes, named) keeps the top row whenever the window is non-empty, so a budget that cannot fit a row serves one row with over_ceiling=1 (repro budget 320: shown=1 est=966 over_ceiling=1; budget 300: shown=1 next=1 — forward-moving). 2. The duplicate seam: the resume point is now the last PRINTED row's candidate index + 1 (packSignatures' shownIdsOut; a pseudo-symbol uses a candidate slot without printing a row, so a served-count arithmetic lands one short and re-serves) — plus the pushShownSigId local-rank fix in serialize.h (under rankBase the global rank indexed order past the window into the masked tail: next_offset=2^32). (G4) the trimming walk (the review's own ask): 12 pages at budget 600, zero duplicates. 3. next= always moves forward; dropped with next_tier= when has_more=0 (a last page handing back a next= that returns the same empty page is a loop with no exit). 4. above_cliff= cliffRank (the head count itself — 'a cut at rank i keeps ranks [1,i]', lexical.h), and SINGLE-TIER windows: a head window ends AT the cliff, so tier= never labels below-cliff rows as head. 5. The legend: the page carries its own full legend comment (G4-safe, dashless verbs), and the compact dialect runs through the CENTRAL table (compactlegend.h: the candidate-page spec row + six vocabulary readings + the pack-task root-attr adjust, the expand/whole-file precedent; the CLI rule keyed on above_cliff above pack-task's; nativeCompactLegendVerb no longer skips the page; MCP for mapped) — so --legend=full|compact works through the same machinery as every other answer, and schema= rides the root (verified: schema=ripwire.candidate-page/v1, est repriced by the machinery). ref rides the twins' existing textResult path (legendRefPosture) — inherited, not re-implemented. 6. (partial) MCP explore's page branch now sits AFTER every ranking boost (the early position returned before applyDocMentionBoost and ranked a different candidate set). KNOWN RESIDUAL, flagged for the maintainers: the CLI pack-task page and the MCP explore page still disagree on the candidate denominator (907 vs 1161 on the same task) — the un-paged answers' kept heads MATCH (6=6), so this is a pre-existing divergence between computeLensRanking and packTaskText's inline pipeline that the page's positiveHits denominator exposes; an upstream ranking-parity question beyond the page, not introduced by it. 8. (partial) ForCandidatePageReq — the request struct (12 params -> 4); rw::emitRaw in the wrapper; kForPageSpliceReserve named; the degrade page built by the same lambdas so it cannot drift. Arms (each fixed WITH the behavior it pins, and hardened by the same review): (6e) parses the FULL text (over_ceiling sits past byte 400); (G) seam at a no-trim budget on src/ (the legend rides the budget, and the single-tier clamp means the repo-root corpus clamps every head window to the 1-row cliff — a correct page this identity cannot test); (G3) tiling under pseudo-symbols (shown-sum <= total; rows-extracted == shown-counted); (G4) new — the trimming walk; all extractions strip comments first (the central vocabulary DEFINES in prose — 12 false rows per walk). Verified: listingpagingcheck ALL PASS (G/G2/G3/G4), mcpverbscheck ALL PASS (6b/6c/6d/6e), truncvocabcheck, fixedbufsweep, selfcheckcheck PASS. The review's own repros re-run green (R1a well-formed, R1b one row + marker, R2 forward seam, R3a one-row floor, R3b no next= at the end, R4a above_cliff correct, R4b single-tier). Part 2 (next commit): the help lines (missing \n, the pack-task file-page wording, the signatures-only note) + COMMANDS.md regen + help_all re-pin; the runPackTaskPage helper (--quality-delta); the CHANGELOG ([0.6.2] restored, the [Unreleased] entry); the residual parity diagnosis. --- src/compactlegend.h | 21 ++ src/forpage.h | 380 ++++++++++++++++++------------------- src/main.cpp | 10 +- src/mcpverbs.h | 50 ++--- src/serialize.h | 6 +- src/verbs_for.h | 25 ++- test/listingpagingcheck.sh | 63 +++++- test/mcpverbscheck.sh | 2 +- 8 files changed, 319 insertions(+), 238 deletions(-) diff --git a/src/compactlegend.h b/src/compactlegend.h index 4daa36d97..edc79f7a6 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -83,6 +83,11 @@ inline constexpr CompactLegendSpec kCompactLegendSpecs[] = // 1-hop neighbours in either direction, rel= which one, of_top= there the bodies that qualified (bodiesTotal), shared= how many // of them a row neighbours (emitted above 1); run= rides a row only when a runner is derivable (testmap.h runHint). The // lens facts and route= ride only some answers and are present-only terms below. compactlegendcheck (D36). + // ── the budgeted-bundle candidate page (root ; issue #294 / PR #362) — hinted by the window + // flag pair that rode along, and reached from the pack-task hint too (the root-attr adjust below): + // one spec for both verbs and both dialects, because the page is ONE document shape. + { "sigs", "candidate-page", + "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" }, { "ctx", "pack-task", "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 (of_top= bodies; shared= bodies reached, absent at 1) > notes > (run= when derivable)" }, { "ctx", "from-trace", "trace frames mapped to indexed symbols, innermost first; the innermost in-corpus body included" }, { "ctx", "exemplar", "the best-in-class instance of kind= for the task, chosen by role: , to imitate" }, @@ -1179,6 +1184,15 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "budget_per_agent_tokens", "budget_per_agent_tokens=: one agent's budget, core_budget_tokens= plus partition_budget_tokens=", false, "ctx-partitions", MapHeaderRead::No, {}, "pack-task" }, // also defines core_budget_tokens= partition_budget_tokens= { "total_bytes", "total_bytes=N: bytes of all bundles together", false, "ctx-partitions", MapHeaderRead::No, {}, "pack-task" }, { "overlap_mean", "overlap_mean=/overlap_max=: pairwise Jaccard over the ids partitions name, before trimming", false, "ctx-partitions", MapHeaderRead::No, {}, "pack-task" }, // also defines overlap_max= + // the budgeted-bundle candidate page's own terms (#294/PR #362, root , key candidate-page): + // the paging quintet and limit=/offset= are universal (kCompactPagingAttrs above); these six are + // the page's own vocabulary, one reading each, the L4 one-attribute-one-reading law. + { "above_cliff", "above_cliff=N: the head-tier candidate count (the cliff rank — a cut at rank i keeps ranks [1,i]); a continuation with offset= >= N serves below-cliff rows", false, "sigs", MapHeaderRead::No, {}, "candidate-page" }, + { "tier", "tier=head|below-cliff: this page's tier; the window never crosses the cliff, so one tier per page", false, "sigs", MapHeaderRead::No, {}, "candidate-page" }, + { "next_tier", "next_tier=: the tier the continuation page carries (beside next=, which is dropped when has_more=0)", false, "sigs", MapHeaderRead::No, {}, "candidate-page" }, + { "r", "r=N: the row's GLOBAL candidate rank across pages (continuation rows keep theirs; pages merge without duplicate r)", false, "sigs", MapHeaderRead::No, {}, "candidate-page" }, + { "at", "at=: the git index this page answered from; a continuation whose at= differs was served by a different index", false, "sigs", MapHeaderRead::No, {}, "candidate-page" }, + { "reason", "reason=: the render degraded and the sigs block was lost (named), not hidden", false, "sigs", MapHeaderRead::No, {}, "candidate-page" }, { "shared_symbols", "shared_symbols=/union_symbols=: ids two or more partitions name / ids any partition names", false, "ctx-partitions", MapHeaderRead::No, {}, "pack-task" }, // also defines union_symbols= { "core_overlap", "core_overlap=: share of the core surface a partition reaches anyway", false, "ctx-partitions", MapHeaderRead::No, {}, "pack-task" }, // from-trace: src/tracelocus.h (renderTraceBlock, buildTraceHeader via ctxRootOpen, the budget_tokens attr) @@ -2070,6 +2084,13 @@ inline CompactOutcome applyCompactDialectOnce( std::string& doc, std::string_vie { effectiveHint = "expand-file"; } + // the candidate page (#294/PR #362): explore/pack_task answer it under the pack-task hint, but the + // page is its OWN document shape (root , the above_cliff mark) with its own spec row — the + // same root-attribute adjustment shape as the expand/whole-file row above. + if( hint == "pack-task" && rootOpen.find( " above_cliff=\"" ) != std::string_view::npos ) + { + effectiveHint = "candidate-page"; + } const CompactLegendSpec* spec = findCompactSpec( root.tag, effectiveHint ); if( spec == nullptr ) { return root.hasSchema ? CompactOutcome::AlreadyCompact : CompactOutcome::UnknownRoot; } const bool rootCarriesRoute = rootOpen.find( " route=\"" ) != std::string_view::npos; diff --git a/src/forpage.h b/src/forpage.h index 203d0b821..fad681318 100644 --- a/src/forpage.h +++ b/src/forpage.h @@ -156,36 +156,63 @@ inline std::string forWidenNext( std::string_view task ) return forPageInvocation( task, kForPageRowsDefault, 0 ); } -// ── THE BUDGETED-BUNDLE CANDIDATE PAGE (paging PoC, upstream issue #294, corrected scope) ─────────── -// Shared by runForLens (--for) and runPackTask (--pack-task): the caller has ALREADY decided this run is -// a budgeted page (--token-budget != 0 AND an explicit --limit/--offset window) and passes its own -// lensRank, cliff statistic (adaptiveCut, computed ONCE by the caller — the same statistic --adaptive and -// the confidence disclosure read), task, route note, root argument and redaction seam. The page serves a -// window of the ranked candidates through the SAME sigs emitter, splicing the pageview quintet onto the -// opening after render (the open tag is byte-pinned by forbudgetmonotoncheck — serialize.h never -// gains a paging attribute). The UN-PAGED bundle the callers serve otherwise is untouched and -// byte-identical: its resume point is its own . -inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std::vector& lensRank, const rw::AdaptiveCut& forCut, - std::string_view task, std::string_view verbName, // "--for"/"--pack-task": the handle + r=1 hint name THEIR verb (#294 review) - std::string_view routeNote, std::string_view rootArg, - rw::RedactCounts* redactPtr, std::string_view atStamp, - std::size_t tokenBudget, int pageLimit, int pageOffset ) +// ── THE BUDGETED-BUNDLE CANDIDATE PAGE (issue #294; the ruling + the #362 review round) ─────────── +// What the caller passes, in one struct (the #362 review's request-struct ask: forCandidatePageDoc's +// parameter list was 12 deep; this is also the runPackTask quality gate's remedy — one struct, not +// eight loose scalars threaded through two callers). +struct ForCandidatePageReq { - using namespace rw; // the file's idiom: function-scoped (verbs_for.h:88 and five more) + std::string_view task; + std::string_view verbName; // "--for=" / "--pack-task=": next= names THEIR verb (#294 review) + std::string_view routeNote; + std::string_view rootArg; + rw::RedactCounts* redactPtr = nullptr; + std::string_view atStamp; // gitstamp::stampAt: the index this page answered from + std::size_t tokenBudget = 0; + int pageLimit = 0; + int pageOffset = 0; +}; + +inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std::vector& lensRank, + const rw::AdaptiveCut& forCut, const ForCandidatePageReq& p ) +{ + using namespace rw; // the file's idiom: function-scoped + const std::string_view task = p.task; + const std::string_view verbName = p.verbName; + const std::string_view routeNote = p.routeNote; + const std::string_view rootArg = p.rootArg; + rw::RedactCounts* const redactPtr = p.redactPtr; + const std::string_view atStamp = p.atStamp; + const std::size_t tokenBudget = p.tokenBudget; + const int pageLimit = p.pageLimit; + const int pageOffset = p.pageOffset; + // the ranked candidate order: ALL ids sorted (score desc, id asc) — the same total order // packSignatures selects with — truncated to the positive-scored candidates (the set the // bundle's cut was against; total= counts it on EVERY page, so a re-decided denominator is - // impossible by construction). - std::vector candOrder; + // impossible by construction). candIdxOf maps an id back to its candidate index: the TRUE + // resume point comes from the last PRINTED row's index + 1 (#362 review, item 2), not from + // a served-count arithmetic that pseudo-symbols (a candidate slot with no printed row) skew. + std::vector candOrder; + std::vector candIdxOf( ing.symbols.size(), kNoNode ); { const std::size_t S = ing.symbols.size(); candOrder.resize( S ); for( NodeId i = 0; i < S; ++i ) { candOrder[ i ] = i; } rw::sortutil::radixSortByScoreDescId( candOrder, lensRank ); candOrder.resize( std::min( forCut.positiveHits, S ) ); + for( std::size_t k = 0; k < candOrder.size(); ++k ) { candIdxOf[ candOrder[ k ] ] = std::uint32_t( k ); } } const std::size_t candidateTotal = candOrder.size(); - const PageWindow win = pageWindow( candidateTotal, pageLimit, pageOffset ); + PageWindow win = pageWindow( candidateTotal, pageLimit, pageOffset ); + // SINGLE-TIER WINDOWS (#362 review, item 4): a head window never crosses the cliff — it ends + // AT the cliff, so every page is one tier and tier= never labels below-cliff rows as head. + // (cliffRank is the head count itself: "a cut at rank i keeps ranks [1,i]" — lexical.h.) + const bool below = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; + if( forCut.cliffRank > 0 && win.begin < forCut.cliffRank && win.end > forCut.cliffRank ) + { + win.end = forCut.cliffRank; + } // window the rank copy: candidates inside [win.begin, win.end) keep their score, everything // else sorts below them (-1), so packSignatures' top-N selection serves exactly the window in @@ -193,11 +220,19 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: std::vector pageRank( ing.symbols.size(), -1.0f ); for( std::size_t i = win.begin; i < win.end; ++i ) { pageRank[ candOrder[ i ] ] = lensRank[ candOrder[ i ] ]; } - std::string sigsDoc; + // the sigs block's byte budget: the page's shell (head+tail+legend) is RESERVED ahead of it + // (kForPageSpliceReserve, #362 review's named-constant ask), and the block never gets less + // than kForPageSigsFloorBytes — the ONE-ROW floor (#362 review, item 3): a budget that cannot + // fit a row serves the top row with over_ceiling="1" rather than an empty page whose next= + // points back at itself (a stuck loop). + constexpr std::size_t kForPageSpliceReserve = 600; + constexpr std::size_t kForPageSigsFloorBytes = 512; const std::size_t sigsBytesBudget = tokenBudget > 0 - ? ( std::size_t( double( tokenBudget ) * kMinBytesPerToken ) > 600 - ? std::size_t( double( tokenBudget ) * kMinBytesPerToken ) - 600 : 0 ) - : 0; + ? std::max( std::size_t( double( tokenBudget ) * kMinBytesPerToken ) > kForPageSpliceReserve + ? std::size_t( double( tokenBudget ) * kMinBytesPerToken ) - kForPageSpliceReserve : 0, + kForPageSigsFloorBytes ) + : kForPageSigsFloorBytes; + // SINK FORM (Diagnostics.h §4b — the contract selfcheckcheck (R) pins): the page document is the // sink. The degrade field is what the emitter reads and surfaces as reason= on the page itself, // so a degraded render tells the release reader IN THE ANSWER — never silently-empty stdout, @@ -208,11 +243,13 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: DisclosureWhy why = DisclosureWhy::ChargeStreamRefused; void disclose( DisclosureWhy w ) noexcept { why = w; } } forPageRender; - bool rendered = false; + + // the printed rows: packSignatures reports the ids it EMITTED (shownIdsOut) — the honest shown + // (rows printed) and the honest resume (the last printed row's candidate index + 1). + std::vector shownIds; + std::string sigsDoc; + bool rendered = false; { - // inline capture (preRender the lambda at 2478 is declared below this block): the same - // MemoryStream + charge-stream + DISCLOSE-on-failure discipline, kept local so the page - // does not depend on declaration order. const std::optional redactBefore = redactPtr != nullptr ? std::optional( *redactPtr ) : std::nullopt; const auto restoreRedact = [ & ]() noexcept { if( redactBefore ) { *redactPtr = *redactBefore; } }; rw::MemoryStream stream; @@ -231,8 +268,8 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: sigsBytesBudget, // H1 ladder over the page nullptr, rootArg, /*hasRelevanceFloor=*/true, nullptr, - /*shownIdsOut=*/nullptr, /*cappedOut=*/nullptr, /*topRowNext=*/{}, /*cutOut=*/nullptr, - std::uint32_t( win.begin ) ); // rankBase (LAST param): continuation rows carry their GLOBAL r= (#294 review) + /*shownIdsOut=*/&shownIds, /*cappedOut=*/nullptr, /*topRowNext=*/{}, /*cutOut=*/nullptr, + std::uint32_t( win.begin ) ); // rankBase: continuation rows carry their GLOBAL r= (#294 review) const rw::MemoryStreamBytes block = stream.finish(); if( !block.isWhole ) { @@ -246,178 +283,141 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: } } } + + // ── the assembly: one set of lambdas, reused by the degrade page AND the trim loop AND the ── + // ── final assembly, so the three cannot drift (#362 review's reuse ask) ─────────────────── + // one scratch buffer PER attribute: the escape helper appends to the buffer and returns a view + // of it, so two calls sharing one buffer inside one argument list race on realloc and mangle + // each other (the bug this page's first CI round caught on route=). + std::vector escTask; const std::string_view taskAttr = escapeXml( task, escTask ); + std::vector escRoute; const std::string_view routeAttr = escapeXml( routeNote, escRoute ); + std::vector escRoot; const std::string_view rootAttr = escapeXml( rootArg, escRoot ); + std::vector escAt; // the at= scratch (one buffer per attribute) + const auto buildHead = [ & ]( std::size_t shownNow, std::size_t nextOff, std::string_view extra ) { + return " 0 ? pageLimit : 0 ) + + "\" tier=\"" + ( below ? "below-cliff" : "head" ) + "\"" + + std::string( extra ); + }; + // THE PASTEABLE CONTINUATION HANDLE — the file page's next= contract: the argv that walks to + // next_offset. Dropped entirely past kNextAttrMaxBytes (a hint that pastes wrong is worse than + // no hint) and dropped when has_more="0" (#362 review, item 3: a last page handing back a next + // that returns an empty page carrying the same next= is a loop with no exit). + const auto buildNextAttr = [ & ]( std::size_t nextOff ) { + if( nextOff >= candidateTotal ) { return std::string(); } + std::string inv = nextFlag( verbName, task ); // THEIR verb: pasting continues the same verb (#294 review) + inv += " --token-budget=" + std::to_string( tokenBudget ); + if( pageLimit > 0 ) { inv += " --limit=" + std::to_string( pageLimit ); } + inv += " --offset=" + std::to_string( nextOff ); + if( inv.size() > kNextAttrMaxBytes ) { return std::string(); } + return nextAttrXml( inv ); + }; + // next_tier (the ruling's suggestion 2): the tier the CONTINUATION page will carry — only + // beside a next= (nothing further means no tier to advertise). + const auto buildNextTierAttr = [ & ]( std::size_t nextOff ) { + if( nextOff >= candidateTotal ) { return std::string(); } + return std::string( " next_tier=\"" ) + ( ( forCut.cliffRank > 0 && nextOff >= forCut.cliffRank ) ? "below-cliff" : "head" ) + "\""; + }; + // est_tokens is MEASURED from the page's own final bytes (legend included); the tail's digit + // count feeds back into the total, so one re-derivation closes the loop (it converges immediately). + const auto buildTail = [ & ]( const std::string& h, std::string_view legend, std::string_view r, + bool overCeiling, std::size_t nextOff ) { + std::size_t est = std::size_t( double( h.size() + legend.size() + r.size() + 96 ) / kMinBytesPerToken ); + std::string t; + for( int pass = 0; pass < 3; ++pass ) + { + t = " est_tokens=\"" + std::to_string( est ) + + "\" budget_tokens=\"" + std::to_string( tokenBudget ) + + "\" root=\"" + std::string( rootAttr ) + "\"" + buildNextAttr( nextOff ) + buildNextTierAttr( nextOff ) + + ( overCeiling ? " over_ceiling=\"1\"" : std::string() ) + + ( atStamp.empty() ? std::string() : " at=\"" + std::string( escapeXml( atStamp, escAt ) ) + "\"" ) + ">"; + const std::size_t fin = std::size_t( double( h.size() + t.size() + legend.size() + r.size() ) / kMinBytesPerToken ); + if( fin == est ) { break; } + est = fin; + } + return std::pair( t, est ); + }; + // THE LEGEND — the page's own vocabulary, defined the way every other answer defines its own: + // a leading comment after the root tag, full dialect emitted here, compact dialect rewritten by + // the central table (compactlegend.h: applyCompactDialect with the "for"/"pack-task" hint), so + // --legend=full|compact works through the SAME machinery on both dialects (#362 review, item 5). + // the legend text: NO double-hyphen anywhere (G4 — it rides inside an XML comment), so the + // verbs are named dashless, the file page's own convention ("the two flags are named without + // their dashes"). + const std::string dashlessVerb = verbName == "--pack-task=" ? std::string( "pack-task" ) + : std::string( "for" ); + const std::string legend = + ""; + + // the honest degrade page: the emitter READS the sink — the full quintet with zero rows and + // reason= naming what was lost, built by the SAME lambdas so it cannot drift (#362 review's ask). if( !rendered ) { - // the emitter READS the sink: the honest degrade page carries the full quintet with zero rows - // and names why the render was lost. std::string assembly only — the counted emitter-API names - // never appear here, so fixedbufsweep's pinned enumeration does not drift (the lesson of this - // branch's first CI failure, learned twice: prose that NAMES a counted token IS a counted line). - std::vector escTask; const std::string_view taskAttr = escapeXml( task, escTask ); - std::vector escRoute; const std::string_view routeAttr = escapeXml( routeNote, escRoute ); - std::vector escRoot; const std::string_view rootAttr = escapeXml( rootArg, escRoot ); - std::vector escAt; // the degrade page's own at= scratch (one buffer per attribute) - const bool belowDegrade = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; - std::string degrade = " 0 ? "1" : "0" ) - + "\" has_more=\"" + ( win.end < candidateTotal ? "1" : "0" ) - + "\" next_offset=\"" + std::to_string( win.end ) - + "\" offset=\"" + std::to_string( win.begin ) - + "\" limit=\"" + std::to_string( pageLimit > 0 ? pageLimit : 0 ) - + "\" tier=\"" + ( belowDegrade ? "below-cliff" : "head" ) - + "\" above_cliff=\"" + std::to_string( forCut.cliffRank > 0 ? forCut.cliffRank - 1 : 0 ) - + "\" reason=\"" + ( forPageRender.why == ForPageRender::DisclosureWhy::ChargeStreamTorn - ? "sigs-charge-stream-torn" : "sigs-charge-stream-refused" ) - + "\" est_tokens=\"0\" budget_tokens=\"" + std::to_string( tokenBudget ) - + "\" root=\"" + std::string( rootAttr ) - + ( atStamp.empty() ? std::string() : " at=\"" + std::string( escapeXml( atStamp, escAt ) ) + "\"" ) + ">"; - const std::string est = " est_tokens=\"" + std::to_string( std::size_t( double( degrade.size() ) / kMinBytesPerToken ) ) + "\""; - const std::size_t estAt = degrade.find( " est_tokens=\"0\"" ); - degrade.replace( estAt, est.size(), est ); - return degrade; + const std::string reason = " reason=\"" + std::string( forPageRender.why == ForPageRender::DisclosureWhy::ChargeStreamTorn + ? "sigs-charge-stream-torn" : "sigs-charge-stream-refused" ) + "\""; + const std::string h = buildHead( 0, win.end, reason ); + const auto [ t, est ] = buildTail( h, legend, "", /*overCeiling=*/false, win.end ); + return h + t + legend + ""; } + // rows: packSignatures' block minus its own open tag (kept verbatim with its close). + const std::size_t openBeg = sigsDoc.find( "' ); + const std::size_t keepFrom = ( openBeg != std::string::npos && openEnd != std::string::npos ) ? openEnd + 1 : 0; + std::string rowsNow = sigsDoc.substr( keepFrom ); - // rows actually printed: packSignatures writes its own shown= only when its ladder trimmed; - // an untrimmed window means every row survived — shown == window size. - std::size_t shown = win.end - win.begin; + // THE TRUE RESUME (#362 review, item 2): the last PRINTED row's candidate index + 1 — NOT + // win.begin + a served count: a pseudo-symbol (a candidate slot packSignatures serves without + // printing a row) skews the arithmetic one short, and the next page re-serves the last row + // (the duplicate seam). shownIds is packSignatures' own emitted-ids report, in rank order. + std::size_t kept = shownIds.size(); + std::size_t nextNow = ( kept > 0 ) ? std::size_t( candIdxOf[ shownIds.back() ] ) + 1 : win.end; + // the forward guarantee (#362 review, item 3): next_offset ALWAYS moves past this page's + // window start when anything was printed — a next= that points back at the page it came + // from is a loop with no exit. + if( kept > 0 && nextNow <= win.begin ) { nextNow = win.begin + 1; } + + // THE BUDGET TRIM (#362 review, item 1): the page IS an answer under the budget. The H1 + // ladder already trimmed within the block; if the assembled page still exceeds, drop + // trailing WHOLE rows — cutting at the START of the last row (a cut at its close leaves + // the row open and the document malformed) — never below ONE row while the window is + // non-empty (item 3: a budget that cannot fit a row serves the top row with over_ceiling="1" + // rather than an empty page whose next= points back at itself), and re-derive the page's own + // arithmetic (shown/next_offset/has_more/next=) from what shipped. + std::string h = buildHead( kept, nextNow, {} ); + auto [ t, est ] = buildTail( h, legend, rowsNow, /*overCeiling=*/false, nextNow ); + if( tokenBudget > 0 && est > tokenBudget ) { - const std::size_t openEnd = sigsDoc.find( '>' ); - const std::size_t openBeg = sigsDoc.find( " openBeg ) + const std::size_t rowFloor = ( win.end > win.begin ) ? std::size_t( 1 ) : std::size_t( 0 ); + while( kept > rowFloor && est > tokenBudget ) { - const std::string openTag = sigsDoc.substr( openBeg, openEnd - openBeg ); - const std::size_t shownAt = openTag.find( "shown=\"" ); - if( shownAt != std::string::npos ) - { - shown = 0; - for( std::size_t d = shownAt + 7; d < openTag.size() && openTag[ d ] >= '0' && openTag[ d ] <= '9'; ++d ) - { - shown = shown * 10 + std::size_t( openTag[ d ] - '0' ); - } - } + const std::size_t lastClose = rowsNow.rfind( "" ); + const std::size_t rowStart = rowsNow.rfind( ""; + --kept; + nextNow = ( kept > 0 ) ? std::size_t( candIdxOf[ shownIds[ kept - 1 ] ] ) + 1 : win.end; + h = buildHead( kept, nextNow, {} ); + std::tie( t, est ) = buildTail( h, legend, rowsNow, /*overCeiling=*/false, nextNow ); + if( est <= tokenBudget ) { break; } } - } - - // splice the page opening: the page-level quintet REPLACES the window-scoped shown=/total=/ - // capped= (the page answers in candidate-set arithmetic; the within-page H1 trim is visible - // as shown < window). Rows and the close tag are kept verbatim. - std::string pageOut; - { - std::vector escTask; // ONE scratch buffer PER attribute: escapeXml appends to the - std::vector escRoute; // buffer and returns a view of it, so two calls sharing one - std::vector escRoot; // buffer inside one emit call's argument list race on realloc and - std::vector escAt; // one buffer per attribute (the shared-buffer bug, see escTask) - const std::string_view taskAttr = escapeXml( task, escTask ); // mangle each other. - const std::string_view routeAttr = escapeXml( routeNote, escRoute ); - const std::string_view rootAttr = escapeXml( std::string_view( rootArg ), escRoot ); - const std::size_t openBeg = sigsDoc.find( "' ); - const std::size_t keepFrom = ( openBeg != std::string::npos && openEnd != std::string::npos ) ? openEnd + 1 : 0; - const bool below = forCut.cliffRank > 0 && win.begin >= forCut.cliffRank; - const std::string rows = sigsDoc.substr( keepFrom ); // rows + close, kept verbatim - // std::string assembly, NOT a fixed char buffer: the task attribute is caller-controlled - // (an escaped 300-char task overflows any sane fixed size, and the first cut of this page - // truncated has_more= mid-attribute at 160 bytes — a malformed document shipped as - // output). Unbounded is the only honest size here. THE #294 REVIEW RESTRUCTURE: head, handle - // and tail are LAMBDAS now, so the budget-enforcement pass below can trim trailing rows and - // re-derive the page's own arithmetic (shown/next_offset/has_more/next=) from what actually - // shipped — one construction, reused verbatim by every pass. - const auto buildHead = [ & ]( std::size_t shownNow, std::size_t nextOff ) { - return " 0 ? forCut.cliffRank - 1 : 0 ) - + "\" capped=\"" + ( shownNow < candidateTotal ? "1" : "0" ) - + "\" has_more=\"" + ( nextOff < candidateTotal ? "1" : "0" ) - + "\" next_offset=\"" + std::to_string( nextOff ) - + "\" offset=\"" + std::to_string( win.begin ) - + "\" limit=\"" + std::to_string( pageLimit > 0 ? pageLimit : 0 ) - + "\" tier=\"" + ( below ? "below-cliff" : "head" ) + "\""; - }; - // THE PASTEABLE CONTINUATION HANDLE — the file page's next= contract (forpage.h's own - // forPageInvocation shape): the argv that walks to next_offset. Dropped entirely past - // kNextAttrMaxBytes (a hint that pastes wrong is worse than no hint); the machine - // next_offset= always remains. listingpagingcheck arm (G2) pins the pair together. - const auto buildNextAttr = [ & ]( std::size_t nextOff ) { - std::string inv = nextFlag( verbName, task ); // THEIR verb: --pack-task pages hand back --pack-task=, so pasting continues the same verb (#294 review) - inv += " --token-budget=" + std::to_string( tokenBudget ); - if( pageLimit > 0 ) { inv += " --limit=" + std::to_string( pageLimit ); } - inv += " --offset=" + std::to_string( nextOff ); - if( inv.size() > kNextAttrMaxBytes ) { inv.clear(); } - return nextAttrXml( inv ); - }; - // next_tier (ruling, suggestion 2): the tier the CONTINUATION page will carry, so a consumer - // can skip the below-cliff remainder deliberately rather than by accident. Fixed literals — - // no escaping needed. Always present beside next= (the boundary is knowable on every page). - const auto buildNextTierAttr = [ & ]( std::size_t nextOff ) { - return std::string( " next_tier=\"" ) + ( ( forCut.cliffRank > 0 && nextOff >= forCut.cliffRank ) ? "below-cliff" : "head" ) + "\""; - }; - // est_tokens is MEASURED from the page's own final bytes; the tail's digit count feeds - // back into the total, so one re-derivation closes the loop (it converges immediately — - // a digit count change moves the total by a byte, ~0.42 tokens). - const auto buildTail = [ & ]( const std::string& h, const std::string& r, bool overCeiling, std::size_t nextOff ) { - std::size_t est = std::size_t( double( h.size() + r.size() + 96 ) / kMinBytesPerToken ); - std::string t; - for( int pass = 0; pass < 3; ++pass ) - { - t = " est_tokens=\"" + std::to_string( est ) - + "\" budget_tokens=\"" + std::to_string( tokenBudget ) - + "\" root=\"" + std::string( rootAttr ) + "\"" + buildNextAttr( nextOff ) + buildNextTierAttr( nextOff ) - + ( overCeiling ? " over_ceiling=\"1\"" : std::string() ) - + ( atStamp.empty() ? std::string() : " at=\"" + std::string( escapeXml( atStamp, escAt ) ) + "\"" ) + ">"; - const std::size_t fin = std::size_t( double( h.size() + t.size() + r.size() ) / kMinBytesPerToken ); - if( fin == est ) { break; } - est = fin; - } - return std::pair( t, est ); - }; - // nextOff is win.begin + shown — NEVER win.end: when the H1 ladder trimmed rows within the - // page, shown < window size, and the honest continuation resumes at the first NEVER-SERVED - // candidate; a next_offset of win.end would silently skip the trimmed rows (the seam contract - // (G3) walks gaplessly at any budget because of this). Identical to win.end when nothing trimmed. - const std::size_t nextOff = win.begin + shown; - std::string headStr = buildHead( shown, nextOff ); - auto [ tailStr, estTokens ] = buildTail( headStr, rows, /*overCeiling=*/false, nextOff ); - pageOut.assign( headStr ).append( tailStr ).append( rows ); - // #294 REVIEW (enforcement + over_ceiling): a page IS an answer under the budget. The - // H1 ladder already trimmed WITHIN the page; if the assembled page still exceeds the - // budget (the escaped task in the head can outgrow the splice reserve), trim trailing - // WHOLE rows — the tail-first-drop policy handoff.h uses — and re-derive the page's own - // arithmetic from what shipped, so a stdout-only consumer reads the truth. If even an - // empty page cannot fit (a pathological task length), splice over_ceiling="1" — the - // same marker the un-paged bundle serves — and keep the stderr note. - if( tokenBudget > 0 && estTokens > std::size_t( tokenBudget ) ) + if( est > tokenBudget ) { - std::size_t shownNow = shown; - std::string rowsNow = rows; - while( shownNow > 0 ) - { - const std::size_t lastClose = rowsNow.rfind( "" ); - if( lastClose == std::string::npos ) { break; } - rowsNow = rowsNow.substr( 0, lastClose ) + ""; - --shownNow; - const std::size_t nextOff = win.begin + shownNow; - headStr = buildHead( shownNow, nextOff ); - auto [ t2, e2 ] = buildTail( headStr, rowsNow, /*overCeiling=*/false, nextOff ); - pageOut.assign( headStr ).append( t2 ).append( rowsNow ); - if( e2 <= std::size_t( tokenBudget ) ) { break; } - } - if( std::size_t( double( pageOut.size() ) / kMinBytesPerToken ) > std::size_t( tokenBudget ) ) - { - headStr = buildHead( 0, win.begin ); - auto [ tZ, eZ ] = buildTail( headStr, "", /*overCeiling=*/true, win.begin ); - pageOut.assign( headStr ).append( tZ ).append( "" ); - rw::emitTo( stderr, "ripwire: for-page est_tokens={} exceeds the stated budget={} even with zero rows " - "(the escaped task outgrew the page shell) — the page is served empty with over_ceiling=1; please report the command line\n", - eZ, tokenBudget ); - } + std::tie( t, est ) = buildTail( h, legend, rowsNow, /*overCeiling=*/true, nextNow ); + rw::emitTo( stderr, "ripwire: for-page est_tokens={} exceeds the stated budget={} — the window cannot fit; " + "the top row is served with over_ceiling=1\n", est, tokenBudget ); } } - return pageOut; + return h + t + legend + rowsNow; } diff --git a/src/main.cpp b/src/main.cpp index 003a9b14b..e1f112166 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3745,6 +3745,10 @@ static constexpr CompactKeyRule kBundleKeyRules[] = { "skipped", []( const rw::Config& c ) noexcept { return c.skippedList; }, CompactKeyMark::FirstChild, "skipped" }, { "notes", []( const rw::Config& c ) noexcept { return c.notesList; }, CompactKeyMark::FirstChild, "notes" }, { "lego", []( const rw::Config& c ) noexcept { return !c.legoType.empty(); }, CompactKeyMark::FirstChild, "lego" }, + // the candidate page's rule sits ABOVE pack-task's: both ask on the same flags, and the mark + // (above_cliff, the page root's own attribute) is what tells the page from the bundle + // the pack-task row below must keep serving. + { "candidate-page", []( const rw::Config& c ) noexcept { return ( !c.forTask.empty() || c.packTaskFlag ) && c.tokenBudget != 0 && ( c.pageLimit > 0 || c.pageOffset > 0 ); }, CompactKeyMark::RootAttr, "above_cliff" }, { "pack-task", []( const rw::Config& c ) noexcept { return c.packTaskFlag || !c.packTask.empty(); }, CompactKeyMark::RootAttr, "budget_tokens" }, { "from-trace", []( const rw::Config& c ) noexcept { return !c.fromTrace.empty() || !c.runTrace.empty(); }, CompactKeyMark::RootAttr, "task" }, { "expand", []( const rw::Config& c ) noexcept { return !c.expand.empty(); }, CompactKeyMark::None, {} }, @@ -3823,7 +3827,11 @@ static bool nativeCompactLegendVerb( const rw::Config& c ) noexcept // L1 fix round (rv-r1-L1 LOW-1): --batch outranks --for in dispatch, so `--for=X --batch=F` answers the batch envelope — // an answer this layer shapes. Only a run --for actually answers is skipped. const bool batchAnswers = !c.batchFile.empty(); // the batch envelope answers, and this layer shapes it - return !batchAnswers && !c.forTask.empty(); + // the budgeted-bundle CANDIDATE PAGE (#294/PR #362) is NOT the --for bundle this skip protects: + // its legend is pure prose (no data comment), so the layer's rewrite is safe — and required, or + // --legend=compact is accepted and ignored on the page (the #362 review's item 5). + const bool forCandidatePage = !c.forTask.empty() && c.tokenBudget != 0 && ( c.pageLimit > 0 || c.pageOffset > 0 ); + return !batchAnswers && !c.forTask.empty() && !forCandidatePage; } // L1 fix round (rv-r1-L1 LOW-3): a DEFAULTED posture captured every run through a tmpfile, a 1.28 MB `--lint --sarif` diff --git a/src/mcpverbs.h b/src/mcpverbs.h index f988f1073..d50372cde 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -1963,8 +1963,8 @@ inline std::optional forTaskText( const std::string& root, const st if( budgetTokens > 0 ) { const AdaptiveCut cut = adaptiveCut( lensRank, 5, std::size_t( forTopN ), /*scanFullDistribution=*/true ); - return forCandidatePageDoc( ing, lensRank, cut, task, "--for=", routeNoteOf( rc, shape, noRoute ), mcpRootArg, - redact, gitstamp::stampAt( root ), budgetTokens, page.limit, page.offset ); + return forCandidatePageDoc( ing, lensRank, cut, ForCandidatePageReq{ task, "--for=", routeNoteOf( rc, shape, noRoute ), mcpRootArg, + redact, gitstamp::stampAt( root ), budgetTokens, page.limit, page.offset } ); } const ForFilePage filePage = computeForFilePage( ing, lensRank, mcpEvidence ); // PR #215 review item 4: this page composed "routed: " + rc.reason by hand and so answered in a spelling @@ -4172,28 +4172,6 @@ inline std::string packTaskText( const std::string& root, const std::string& tas // §L10b + verify-wave2 F6: same trim as the other route= construction sites — neither bracket. lr.routeNote = routeNoteOf( rc, shape, noRoute ); // row 6: the route CODE, ONE producer (filter.h) - // PAGING-POC (issue #294, ask (b)): explore's limit/offset parity with `for` — the FILE-GRAIN WIDENING - // PAGE the for twin serves (computeForFilePage/renderForFilePageXml, forpage.h) on THIS lr, so the two - // dialects cannot serve a different page (the same one-implementation rule the for twin's L-W block - // states). Its own root; nothing below runs. - if( pageLimit > 0 || pageOffset > 0 ) - { - const std::string_view mcpRootArg = ing.realPaths.empty() ? std::string_view( root ) : std::string_view(); - // PAGING-POC follow-up (issue #294): budget + window is the CANDIDATE PAGE, not this file - // page — before this branch, explore {budget_tokens, limit} served the budgetless file page - // with the budget silently ignored (mcpverbscheck 6e's recorded red). - if( budgetTokens > 0 ) - { - const AdaptiveCut cut = adaptiveCut( lr.rank, 5, std::size_t( kForLensDefaultTopN ), /*scanFullDistribution=*/true ); - return forCandidatePageDoc( ing, lr.rank, cut, task, "--pack-task=", lr.routeNote, mcpRootArg, - redact, gitstamp::stampAt( root ), budgetTokens, pageLimit, pageOffset ); - } - const ForFilePage filePage = computeForFilePage( ing, lr.rank, lr.evidence ); - const std::string pageRootOpen = ctxRootOpen( task, lr.routeNote, mcpRootArg ); - return renderForFilePageXml( ing, filePage, ForPageRenderParts{ task, pageRootOpen, forCoveragePct( lr.evidence, topLensId( lr.rank ) ), - pageLimit, pageOffset, mcpRootArg, /*compactLegend=*/false } ); - } - // input blow-up guard disclosure (lexical.h kMaxUniqueQueryTerms/dedupeQueryTerms) — same channel/ // attribute names as the other two --for/--pack-task surfaces. { @@ -4260,6 +4238,27 @@ inline std::string packTaskText( const std::string& root, const std::string& tas const notes::NoteIndex noteIndex = notes::loadNoteIndex( root ); const notes::NoteIndex* const notesPtr = noteIndex.empty() ? nullptr : ¬eIndex; + // PAGING-POC (#294 + the #362 review round, item 6): the page branch sits AFTER every ranking + // boost (the early position returned before applyDocMentionBoost above, and the page ranked a + // different candidate set than the un-paged answer this same function serves — total= 1155 vs + // 901 on the same task; the parity arm in test/mcpverbscheck.sh (6e) pins the two together). + // budget + window is the CANDIDATE PAGE; a window with no budget is the FILE-GRAIN WIDENING + // PAGE (computeForFilePage/renderForFilePageXml, forpage.h) — the for twin's own page, byte-identical. + if( pageLimit > 0 || pageOffset > 0 ) + { + const std::string_view mcpRootArg = ing.realPaths.empty() ? std::string_view( root ) : std::string_view(); + if( budgetTokens > 0 ) + { + const AdaptiveCut cut = adaptiveCut( lr.rank, 5, std::size_t( kForLensDefaultTopN ), /*scanFullDistribution=*/true ); + return forCandidatePageDoc( ing, lr.rank, cut, ForCandidatePageReq{ task, "--pack-task=", lr.routeNote, mcpRootArg, + redact, gitstamp::stampAt( root ), budgetTokens, pageLimit, pageOffset } ); + } + const ForFilePage filePage = computeForFilePage( ing, lr.rank, lr.evidence ); + const std::string pageRootOpen = ctxRootOpen( task, lr.routeNote, mcpRootArg ); + return renderForFilePageXml( ing, filePage, ForPageRenderParts{ task, pageRootOpen, forCoveragePct( lr.evidence, topLensId( lr.rank ) ), + pageLimit, pageOffset, mcpRootArg, /*compactLegend=*/false } ); + } + PackTaskInputs in; in.budgetTokens = budgetTokens; in.fanIn = &fanIn; @@ -5682,7 +5681,8 @@ inline bool mcpVerbDeclaresLegend( std::string_view verb ) noexcept inline std::string_view mcpCompactLegendHint( std::string_view verb ) noexcept { if( verb == "analyze" || verb == "rank_by" ) { return "map"; } // lane/t10-mcp-coverage: rank_by's XML root is the SAME shape analyze serves - if( verb == "explore" || verb == "pack_task" ) { return "pack-task"; } + if( verb == "explore" || verb == "pack_task" ) { return "pack-task"; } // their CANDIDATE PAGE (root ) adjusts to candidate-page inside the layer + if( verb == "for" ) { return "candidate-page"; } // the for twin's candidate page (root ); the un-paged answer has no spec row here and passes through, as it did under the empty hint if( verb == "from_trace" ) { return "from-trace"; } if( verb == "lego" ) { return "lego"; } if( verb == "exemplar" ) { return "exemplar"; } diff --git a/src/serialize.h b/src/serialize.h index 077bba4d6..5b4102622 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -5001,7 +5001,11 @@ inline void packSignatures( std::FILE* out, const IngestResult& ing, const std:: { continue; } - pushShownSigId( shownIdsOut, order, e.globalRank ); // lane 2 + // lane 2 — the LOCAL rank under rankBase: globalRank carries the WINDOWED serving's + // global offset (#294 review), and order[] holds the WINDOW's rows in its head, so the + // id mapping must subtract rankBase or it reads past the window into the masked tail + // (the #362 review round's next_offset=2^32 seam). + pushShownSigId( shownIdsOut, order, std::uint32_t( e.globalRank - rankBase ) ); w.write( e.head.c_str() ); if( !e.doc.empty() ) { w.write( "" ); w.write( escapeXml( e.doc, esc ) ); w.write( "" ); } w.write( escapeXml( e.sig, esc ) ); diff --git a/src/verbs_for.h b/src/verbs_for.h index fba859cfe..bdc9ecc48 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2217,13 +2217,13 @@ inline std::string renderForHdrRowsXml( const rw::IngestResult& ing, const std:: // the CLI half of the shared page (forpage.h owns the doc): write it to stdout. One implementation, // three dialects — the CLI bundle verbs and the MCP twins all build the SAME document. inline int emitForCandidatePage( const rw::IngestResult& ing, const std::vector& lensRank, const rw::AdaptiveCut& forCut, - std::string_view task, std::string_view verbName, std::string_view routeNote, std::string_view rootArg, - rw::RedactCounts* redactPtr, std::string_view atStamp, - std::size_t tokenBudget, int pageLimit, int pageOffset ) + const rw::ForCandidatePageReq& req ) { - const std::string doc = rw::forCandidatePageDoc( ing, lensRank, forCut, task, verbName, routeNote, rootArg, - redactPtr, atStamp, tokenBudget, pageLimit, pageOffset ); - std::fwrite( doc.data(), 1, doc.size(), stdout ); + // the CLI half of the shared page (forpage.h owns the doc): write it to stdout. One + // implementation, three dialects — the CLI bundle verbs and the MCP twins all build the SAME + // document. rw::emitRaw (#362 review's ask): the repo's own stdout writer, not the raw stream call. + const std::string doc = rw::forCandidatePageDoc( ing, lensRank, forCut, req ); + rw::emitRaw( stdout, doc.c_str() ); return 0; } @@ -2453,11 +2453,10 @@ std::optional runForLens( const MainDispatch& d ) { // W3-N1/#294 review: the page redacts through packSignatures — the tally must be reported, // exactly like the --json early return below (a redacted credential must never be silent). - const int pageRc = emitForCandidatePage( ing, lensRank, forCut, cfg.forTask, "--for=", routeNoteRaw, flRootArg, - redactPtr, + const int pageRc = emitForCandidatePage( ing, lensRank, forCut, rw::ForCandidatePageReq{ cfg.forTask, "--for=", routeNoteRaw, flRootArg, redactPtr, flSingleRoot ? std::string_view( gitstamp::stampAt( std::string( root ) ) ) : std::string_view(), - cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); - reportRedactions( stderr, redactCounts ); + cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset } ); + reportRedactions( stderr, redactCounts ); // W3-N1: the page redacts — the tally reports return pageRc; } // L-W: coverage= rides the SAME sentence and the SAME byte exemption as confidence=/margin_pct= — but ONLY @@ -3823,9 +3822,9 @@ std::optional runPackTask( const MainDispatch& d ) } const AdaptiveCut packCut = adaptiveCut( lr.rank, 5, std::size_t( cfg.packTopN > 0 ? cfg.packTopN : kForLensDefaultTopN ), true ); const std::string packAtStamp = in.rootArg.empty() ? std::string() : gitstamp::stampAt( std::string( in.rootArg ) ); - const int packPageRc = emitForCandidatePage( ing, lr.rank, packCut, task, "--pack-task=", lr.routeNote, in.rootArg, - d.redactPtr, packAtStamp, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset ); - reportRedactions( stderr, d.redactCounts ); // W3-N1/#294 review: the page redacts — the tally reports + const int packPageRc = emitForCandidatePage( ing, lr.rank, packCut, rw::ForCandidatePageReq{ task, "--pack-task=", lr.routeNote, in.rootArg, + d.redactPtr, packAtStamp, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset } ); + reportRedactions( stderr, d.redactCounts ); // W3-N1: the page redacts — the tally reports return packPageRc; } diff --git a/test/listingpagingcheck.sh b/test/listingpagingcheck.sh index a84ae384a..99f623563 100755 --- a/test/listingpagingcheck.sh +++ b/test/listingpagingcheck.sh @@ -516,9 +516,14 @@ PY # of the paged budgeted bundle). RED on the pre-feature binary is the intended state: gate written # BEFORE the code, red vs the pre-change binary — today both paged runs refuse and this records that. echo "=== (G) budgeted-bundle candidate continuation: page[0:3] + page[3:6] == page[0:6] ===" -run "$ROOT" g_budget_p0 --for="rank symbols" --token-budget=800 --limit=3 --offset=0 -run "$ROOT" g_budget_p3 --for="rank symbols" --token-budget=800 --limit=3 --offset=3 -run "$ROOT" g_budget_p6 --for="rank symbols" --token-budget=800 --limit=6 --offset=0 +# at a NO-TRIM budget: the page's own legend rides the budget, so a small budget trims within every +# page and the pure-window identity (page[0:3]+page[3:6] == page[0:6]) cannot hold — the identity is +# the WINDOW's; the trim path is the (G4) trimming walk below (the #362 review's ask). +# on src/ (cliff 40 > 6: the single-tier clamp never fires; the repo ROOT clamps every head +# window to the 1-row cliff — a correct page, but not one this pure-window identity can test) +run "$ROOT/src" g_budget_p0 --for="rank symbols" --token-budget=100000 --limit=3 --offset=0 +run "$ROOT/src" g_budget_p3 --for="rank symbols" --token-budget=100000 --limit=3 --offset=3 +run "$ROOT/src" g_budget_p6 --for="rank symbols" --token-budget=100000 --limit=6 --offset=0 python3 - "$TMP" <<'PY' import os, sys, xml.etree.ElementTree as ET @@ -631,6 +636,7 @@ while [ "$page" -lt 12 ]; do python3 -c ' import re, sys raw = open( sys.argv[1], encoding="utf-8", errors="replace" ).read() +raw = re.sub( r"", "", raw, flags=re.S ) # the legend DEFINES in prose: never count prose as rows # candidate identity is the (file, line, name) TRIPLE — same-name definitions are distinct # candidates even in ONE file (DYNMAP_DEFINE_RANK_32 sits at two lines of dynamic_map.hpp), # so name or (file, name) alone false-flags them as seam overlap. n=/p=/l= may sit in any @@ -661,10 +667,13 @@ if dups: # the tiling measure is the pages' own shown= arithmetic (candidates SERVED); the ) is counted as served but emits no row, # so shown-sum == total is the contract, and the row list tiles the named-def subset in order. -if shown_sum != total: - problems.append(f"walk: shown sums to {shown_sum} vs total={total} — the pages do not tile the candidate set") -if len(rows) > shown_sum: - problems.append(f"walk: {len(rows)} named rows exceed the {shown_sum} served candidates") +# the tile under pseudo-symbols: a candidate slot can be SERVED without printing a row (), +# so shown-sum <= total with the difference consumed by pseudo-slots; the coherence is rows-extracted +# == shown-counted plus no duplicates plus the prefix below. +if shown_sum > total: + problems.append(f"walk: shown sums to {shown_sum} vs total={total} — the pages over-serve the candidate set") +if len(rows) != shown_sum: + problems.append(f"walk: {len(rows)} rows extracted vs shown summing {shown_sum} — the count and the rows disagree") # tier sequence: all head pages precede all below-cliff pages (exactly one boundary) seen_below = False for i, t in enumerate(tiers): @@ -678,6 +687,7 @@ bp = os.path.join(tmp, "g3_bundle") if os.path.exists(bp) and os.path.getsize(bp) > 0: import re raw = open(bp, encoding="utf-8", errors="replace").read() + raw = re.sub( r'', '', raw, flags=re.S ) # the legend's prose is not a row bundle = [] for m in re.finditer( r']*>', raw ): t = m.group( 0 ) @@ -696,5 +706,44 @@ PY [ $? = 0 ] && ok '(G3) every page concatenated == the full ranked candidate list (no gap, no overlap, one tier boundary)' \ || no '(G3) the end-to-end walk (issue #294 ruling, suggestion 5) does not hold — see FAIL lines above' +# ── (G4) THE TRIMMING WALK (#362 review, item 2): the same walk at a budget that TRIMS within ── +# every page, asserting NO DUPLICATE candidate across the seam. The resume point is the last +# PRINTED row's candidate index + 1 (a pseudo-symbol consumes a slot without printing a row), so +# a served-count arithmetic lands one short and the next page re-serves a row — the walk catches it. +echo "=== (G4) trimming walk: a budget that trims, no duplicate candidate across the seam ===" +: > "$TMP/g4_rows.txt" +offset=0; page=0 +while [ "$page" -lt 12 ]; do + run "$ROOT/src" "g4_walk_$page" --for="rank symbols" --token-budget=600 --limit=40 --offset=$offset + has_more="$( attr "$TMP/g4_walk_$page" sigs has_more )" + nextoff="$( attr "$TMP/g4_walk_$page" sigs next_offset )" + [ -n "$nextoff" ] || { no "(G4) walk page $page produced no parseable root"; break; } + python3 -c ' +import re, sys +raw = open( sys.argv[1], encoding="utf-8", errors="replace" ).read() +raw = re.sub( r"", "", raw, flags=re.S ) # the legend DEFINES in prose: never count prose as rows +for tag in re.finditer( r"]*>", raw ): + t = tag.group( 0 ) + n = re.search( r"\sn=\"([^\"]*)\"", t ) + p = re.search( r"\sp=\"([^\"]*)\"", t ) + l = re.search( r"\sl=\"([^\"]*)\"", t ) + print( ( p.group( 1 ) if p else "?" ) + "\x1f" + ( l.group( 1 ) if l else "?" ) + "\x1f" + ( n.group( 1 ) if n else "?" ) ) +' "$TMP/g4_walk_$page" >> "$TMP/g4_rows.txt" + page=$(( page + 1 )) + [ "$has_more" = "1" ] || break + offset="$nextoff" +done +TRIM_DUPS=$( python3 - "$TMP/g4_rows.txt" < Date: Wed, 30 Sep 2026 13:37:13 +0000 Subject: [PATCH 010/170] =?UTF-8?q?fix(paging):=20the=20#362=20human=20rev?= =?UTF-8?q?iew=20round,=20part=202=20=E2=80=94=20help=20lines,=20COMMANDS?= =?UTF-8?q?=20regen,=20the=20released-changelog=20restore,=20and=20the=20[?= =?UTF-8?q?Unreleased]=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Help (item 7): the three added lines end with their \n again (one ran 336 columns); the wording corrected — --for takes --limit/--offset as its FILE page (bare --pack-task --limit REFUSES: it has no page without a budget), and --for/--pack-task beside --token-budget take the window as the CANDIDATE page, signatures only (bodies come via next= or --expand). - docs/COMMANDS.md regenerated (179 flags, 163 samples); help_all re-pinned with the both-emitters proof — the movement is identical on the std::print build and the std::format+fputs fallback build (printffmtparitycheck 42/42 ALL PASS on both). - CHANGELOG (item 9): the released [0.6.2] entry restored verbatim (the ceiling edit had landed in it); the [Unreleased] entry added for the candidate page — the full surface, the explore budget_tokens fix, the manifest figures (46,493 -> 46,677 B; the ceiling 46,600 -> 46,700 B for the one sanctioned case), referring to issue #294 / PR #362. - cli.h (optional): the emitTo call that passed 4 arguments for 3 placeholders — the 4th is gone. KNOWN RED, disclosed: showcasecapturecheck (I) — the capture's --legend-dict figure is stale (the live dictionary gained this round's 7 entries: 730 -> 737); the gate demands a regeneration and forbids hand-editing, but test/showcase_capture.py REFUSES to write — its --since=HEAD~N promise check for the --in=src case fails for every N in [3,14] ('the scoped page itself (past the display cut: the global block is too wide)'), a refusal that reproduces independent of this round's changes and looks like an interaction with the #350 train's recent-scope display behavior; it needs its own investigation before the capture can be regenerated. Next round. --- CHANGELOG.md | 35 ++++++++++++++++++++++++++--------- docs/COMMANDS.md | 4 ++-- src/cli.h | 11 +++++++---- test/printf_parity.manifest | 14 +++++++------- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20e9b50ef..ab89e01d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ found" while `Class::method` resolved. When no other spelling matches, a selecto usual (`defs=N`, or `--edit-check`'s refusal). A selector that resolved before resolves identically. `--whereis` is a lexical scan of every ref and is unchanged. Gate: `test/selectorscopecheck.sh` arms (i)-(m). +<<<<<<< HEAD ### Added — a memory guard on every root: zero-config, silent on normal runs, a disclosed partial answer past its line (#350, layer 3) ripwire measured none of its own memory, so a large tree (#350: a non-git home directory, 67 GB) could grow it until @@ -1090,6 +1091,25 @@ five times, round-robin across the sizes, and compares medians. The thresholds a stall (one 640 KB run delayed 0.6 s) fails the old arm and passes the new one. A deliberately quadratic per-word rescan in `scanAsanWordBoundaries` still fails B1, B2 and B3 (medians 1033 / 14094 ms / timeout). +### Added — the budgeted-bundle candidate page: `--for`/`--pack-task` under a budget take a resumable `--limit`/`--offset` window; MCP `explore` gains `limit`/`offset` (issue #294, PR #362) + +`--for=TASK --token-budget=N` with an explicit `--limit`/`--offset` now answers with the **candidate +page**: one `` window over the same ranked candidate set the bundle cuts — the pageview quintet +(`shown=/total=/capped=/has_more=/next_offset=`) on the root, `above_cliff=` the head-tier count, +`tier=` one tier per page (a head window ends at the cliff), `r=` each row's GLOBAL candidate rank, +`at=` the git index answered from, `next=` the pasteable continuation argv (dropped when +`has_more="0"`), `over_ceiling="1"` when the budget cannot fit the window (fewer rows served, never +silently more bytes), and the page's own legend in both dialects (`--legend=full|compact`, the central +compact table). `--pack-task` under a budget takes the same window; a window with no budget stays the +`--for` file page; `--partition` and a window refuse together. The un-paged bundles are byte-identical +(`forbudgetmonotoncheck`), and `--offset=0` alone remains the un-paged answer — its resume point is +its own ``. + +MCP `explore` declares `limit`/`offset` (the same bounded pair `for` takes) and answers the identical +document; previously it silently ignored `budget_tokens` beside a window and served the budgetless +file page. The `tools/list` manifest grows 46,493 → 46,677 B, and the ceiling moves 46,600 → 46,700 B +— the one sanctioned case that moves it: `explore`'s two declared schema properties +(`test/mcpmanifestcheck.sh` arm `(1b)` asserts the figure against a live measurement). ### Fixed — the Linux G1 sanitizer ritual completes: five string_view comparator lambdas stop wrapping, and the GCC ASan path builds (#342) `LSAN_OPTIONS=… ./asan/ripwire .` — the sanitizer ritual AGENTS.md requires before a PR — aborted on any @@ -2446,16 +2466,13 @@ Both serve the CLI's own renderer rather than a second implementation of it: Both declare `legend`, so they take their posture per request like the rest of the family — absent means compact, `legend:"full"` restores the prose legend byte-for-byte, and the rows do not move between the two. -The `tools/list` manifest grows 43,500 → 46,677 B (method: `test/mcpmanifestcheck.sh`'s own formula — the +The `tools/list` manifest grows 43,500 → 46,493 B (method: `test/mcpmanifestcheck.sh`'s own formula — the compact JSON length of the served `tools` array — over one `tools/list` response on this repository's binary -before and after; the ceiling moves 46,600 → 46,700 B for the one sanctioned case that moves it: explore's -`limit`/`offset` join `for`'s as DECLARED arguments (upstream issue #294's parity ask, PR #3) — two schema -properties, +184 B over the 46,493 B the previous round recorded, with 23 B of headroom — the 46,371 B this -entry first recorded was superseded, same round, by a `rank_by` description fix -(`test/mcpmanifestcheck.sh`'s own RE-ANCHORED/TRAIN 10 FIX ROUND comment history), and this prose copy went -uncorrected for five days before being re-derived by hand; `test/mcpmanifestcheck.sh` arm `(1b)` now -asserts this figure against a live `tools/list` measurement so that rot is a gate failure, not a future -rediscovery). +before and after; its 46,600 B ceiling is unmoved, with 107 B of headroom — the 46,371 B this entry first +recorded was superseded, same round, by a `rank_by` description fix (`test/mcpmanifestcheck.sh`'s own +RE-ANCHORED/TRAIN 10 FIX ROUND comment history), and this prose copy went uncorrected for five days before +being re-derived by hand; `test/mcpmanifestcheck.sh` arm `(1b)` now asserts this figure against a live +`tools/list` measurement so that rot is a gate failure, not a future rediscovery). ### Fixed — `rank_by` over MCP no longer answers a different ranking than `--rank-by` on a tree with uncommitted changes diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index e2b0a2fe2..eb15861e2 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -121,7 +121,7 @@ $ ./build/ripwire . --max-tokens=1500 ... [1 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace`, `--limit` +**Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace` **Caveats (stated by the binary):** @@ -4441,7 +4441,7 @@ $ ./build/ripwire . --hotspots --json **Answers:** paginate a high-cardinality verb paginate a high-cardinality verb. -HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's rows; its rows repeat on every page, complete). --edit-check is the same shape for a different reason: its rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --top-k/--max-tokens/--token-budget, not a page (--for's bare bundle is shaped by --token-budget the same way, and takes --limit/--offset only as its file page, where the budget flags are refused in turn); the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all. +HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's rows; its rows repeat on every page, complete). --edit-check is the same shape for a different reason: its rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --token-budget the same way; --for takes --limit/--offset as its FILE page, where the budget flags are refused in turn (bare --pack-task --limit refuses: it has no page without a budget), and --for/--pack-task beside --token-budget take the window as the BUDGETED-BUNDLE CANDIDATE PAGE — one window over the ranked candidates, signatures only (bodies come via next= or --expand), their resumable continuation; the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all. **Try it** diff --git a/src/cli.h b/src/cli.h index 650b4f8df..44bc5be6f 100644 --- a/src/cli.h +++ b/src/cli.h @@ -2633,9 +2633,12 @@ inline constexpr char kHelpTail[] = " Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and\n" " ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/\n" " --expand/--outline/--pack-signatures/--format=candidates) are shaped by\n" - " --token-budget the same way; --for/--pack-task take --limit/--offset as their FILE" - " page, where the budget flags are refused in turn, or beside --token-budget as the" - " BUDGETED-BUNDLE CANDIDATE PAGE (the window over the ranked candidates, their resumable continuation: budget + window together); the rest (--path/--connect/\n" + " --token-budget the same way; --for takes --limit/--offset as its FILE page, where\n" + " the budget flags are refused in turn (bare --pack-task --limit refuses: it has no\n" + " page without a budget), and --for/--pack-task beside --token-budget take the window\n" + " as the BUDGETED-BUNDLE CANDIDATE PAGE — one window over the ranked candidates,\n" + " signatures only (bodies come via next= or --expand), their resumable continuation;\n" + " the rest (--path/--connect/\n" " --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map)\n" " answer with a single fixed-shape result that has no row list to window at all.\n" " --exclude=SUBSTR drop matching paths (repeatable) --ignore-tests\n" @@ -4987,7 +4990,7 @@ inline void validateConfig( Config& c ) noexcept { if( f.set ) { - rw::emitTo( stderr, "ripwire: {} — it has no bundle for {} to shape, so the flag is refused rather than ignored: drop {} for the page, or drop the window for the bundle\n", pageName, f.name, f.name, f.name ); + rw::emitTo( stderr, "ripwire: {} — it has no bundle for {} to shape, so the flag is refused rather than ignored: drop {} for the page, or drop the window for the bundle\n", pageName, f.name, f.name ); // 3 placeholders, 3 args (the #362 review caught a 4th) c.ok = false; } } diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index 41c8cfe8b..64f1fa34d 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -1,4 +1,4 @@ -flagless 0 22b42aea387b3ba54ac0921ce1caed39aea23a4947579eec2276b72ad77330e6 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +flagless 0 19a4b1530abe359fb650db91a12393aae9b8269d1508fcb3bd399976eb4bb59a e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 lint 0 a3d29cee7631f2904dfeaa947f091d3e73181d325e6ddedd87a68580d5717ff5 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 lint_sarif 0 805e375b06b7755bcc6423607378e557ee82fef787cf09c34ba5c066639f4d7b e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 lint_select 0 740876ae2df8dbdced07c406c223f545e92a719d8bc3dee1cac56a85207f16c2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 @@ -9,15 +9,15 @@ pattern 0 b7a2467faab2b69e4fe8e3d8204f158329f6b349d644e5681af860ce6f8ba6ca e3b0c callers 0 31971e419a6df4d59c4464fcdd1afa56c6125e4fc76d096f0203c80cedd9a4a8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 impact 0 c48ba09589d13cad6011bd6fadbb2898d68e2f5bd97a10bf44324901cfa08d49 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 clones 0 a082f92ce84cd952ced8341e3faff33a23685729bfa4ab6f0eda0f7867e41d7c e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help 0 c74a677dde09eb76a71bfd0b9e2c75d074c47ecfac838654f84df80af6725b02 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -expand 0 f3e60d37ab964c4772d794326420441e0b027d1970ea5fb0c60efe6c89f808b8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help 0 83552cac9a8da4096cd63443205b290463a41c19b733e90e6287b251b3e9870a e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +expand 0 4ac45b31a8ed0fe3c2520e9e24aaa4dc8e73e0479ee326499f4b760bc4ab10cc e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 callees 0 cbd133fa53917bad05926d46615d4d8b4457ba9285513dc4b193fa57a1ef8b85 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -around 0 ff0b962658da396e7bf3360584de21ab2f0e8bebcd8c0e4e997a77994b004a9f e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +around 0 77cd3047066cce2bc45aeea739008ebd5a0045aa64cd9fed59ed4a277c62ad88 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 uses 0 5442767b638c8cac9eb0327cbfd5fde73f92fde5b85e2a995670a3982d903711 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 path 0 fe032ed241174b3892bd9369cbc3d3b904d9e6f681cc307c27c476b6a28ea59e e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 connect 0 6ceb99d6c5ecb9f57a1b7426da2ed2118705cb7cc6b3d0aa4456b84c0ff715f4 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 grep 0 ea5c66f3062d3ba805f271d18b146ee411f3b132f75d0c77062ecf2eda13f36e e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -pack_signatures 0 91446d0fc143c8d08605da1a3e7430834de105b15f0d2658f675c6bc3bf641a8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +pack_signatures 0 ca92eb613ac5d24a45eaacab5cc418afc45f0a8d8741e193067db2e509ca3d88 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 pack_task 0 f3de4fc3a28342cf515fedb8a02de0fea7f149ce8e948d39a7037c955a9eafd1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 arch 2 45b12ab02e3e9f2e32bf92fe0e1cef7b489e70711386e0749d50281743aeeec5 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 seams 0 069a723391d0a5687bdbe23d250a5dc901791fbcc676e5ac7435e9017cc30478 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 @@ -33,10 +33,10 @@ impact_json 0 c9cf3824750b16b493eb177644306987b7420db4ae37f07be18e9fa088574b8b e uses_json 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 96ca20ae3803368f04315f1f2f0062893e7827cee740fb44e04278ff5f32e791 grep_json 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 278827e2a7740aeeefa653b87ec47f74c98ec97a1758ce2e5426855a845a80c4 expand_missing 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 10f2dda880ec34c8d5a3f5bc50bd1bf50d0abe20c308de11ca8a16714f87b496 -callers_missing 1 39a4cf4b2702863152edcc6c270255bac015a17d25e834dbdff181d809181326 0770e05552f4baf1c25d213e35c5f273c6f341e3c518e8233a83ff849abcd1c9 +callers_missing 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0770e05552f4baf1c25d213e35c5f273c6f341e3c518e8233a83ff849abcd1c9 safe_delete 0 8b99d83566b89332ac4b2967abbb39284db775bbed601628b97b8c7909f04ac2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 verify_layer 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5288c345d7d6e335f88b9c1daa8935db22e1dcf89c0c8bc1f6140d4cb5af0b48 graph_query 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 bfa4319feb9dee09cfbd3991cf6fbd752297e99d14de9e75768820d2a9c8832f callers_limit 0 e40ee1ddba0fbf548c58c98b91fe052af8c34f8913edb3edd85245b12a6967b9 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help_all 0 5e82bb6e129da4086c1e64f5939be191964e5db6e7c49154edfee9d1cfdc44e6 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help_all 0 9a893430cba00628d857502b428cc11e3251ba82da028f2fe8a1e99d4286c9ef e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help_one 0 938ff4f8802a8f5d9cb10f9a1f3804ac755be44c8ab00e24e45bfe8255224a21 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 From ead2c7f28dc399b69a6055080d69645b893357aa Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 17:30:35 +0000 Subject: [PATCH 011/170] =?UTF-8?q?fix(paging):=20the=20#362=20review=20ro?= =?UTF-8?q?und=203=20=E2=80=94=20ranking=3D,=20dialect-true=20handles,=20t?= =?UTF-8?q?he=20in-doc=20compact=20dialect=20(ref=20posture=20+=20honest?= =?UTF-8?q?=20measure)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maintainer's round-2 re-review verified items 1-8 of the first list fixed by running them; four remained. Three land here: Item 1 (the parity ask, the honest minimum the maintainer named): the page now SAYS which ranking it paged — ranking="for-lens" (verbs_for.h computeLensRanking, the CLI --for/--pack-task pipeline) or ranking="mcp-lens" (the twins' shared inline pipeline, mcpverbs.h). Root cause, measured: the un-paged heads agree but the page denominators differ (CLI --pack-task 907 vs the twins 1161 on 'how does pagerank converge') — computeLensRanking and the twins' inline pipeline rank slightly different candidate sets. And the MCP pages no longer hand back a pasteable CLI next= (a --pack-task= argv would walk a different list): pasteHandle=false on the twins — the machine attributes (next_offset=/limit=/next_tier=) carry the programmatic continuation through the same verb; the CLI dialects keep the handle (same pipeline, pastes true). CHANGELOG's 'identical document' sentence corrected accordingly (the same page SHAPE; ranking= names the list). Item 3 (legend under ref on MCP): the page is compacted IN-DOC (compactLegend=true from every dialect that defaults compact) — the central layer (applyCompactDialect, candidate-page) writes schema= and reprices est_tokens=/settles over_ceiling= on the bytes that actually ship, so the outer layers see AlreadyCompact and the ref posture reduces the page like any other spec-keyed answer. VERIFIED LIVE: in a session that read ripwire://legend-dict, the for page takes the ref posture — the legend comment reduced, legend="ref" dict= dictv= in the trailer, every page attribute intact; the second identical call shrinks further (the session's served entries). The un-paged for answer is untouched. Item 4 (the stderr/page contradiction): the trim loop's stderr notice is GONE; the one notice left reads est_tokens off the FINAL emitted root and fires only when the final page carries over_ceiling="1" — the line can never contradict the document. The maintainer's own repro (src --for=rank symbols --token-budget=750 --limit=40 --offset=100) now: est_tokens=714, no over_ceiling, no stderr line. The in-doc compaction also fixes the second half (a compact page holding fewer rows than its budget allows): the trim loop measures the full dialect, the machinery reprices the compact dialect it emits. Verified: the full decisive sweep ALL PASS (truncvocabcheck, listingpaging [G/G2/G3/G4], mcpverbscheck [6b/6c/6d/6e], fixedbufsweep, budgetpolicycheck, selfcheckcheck, forbudgetmonotoncheck, mcpmanifestcheck). The one-row floor re-verified on the MCP twin (budget 600: shown=1 over_ceiling=1 — the honest no-fit answer, arm (6e) accepts it). REMAINING, disclosed: item 2 — the runPackTaskPage helper (--quality-delta still exits 2: runPackTask complexity 19->28, packTaskText params 6->8) is designed (ForCandidatePageReq landed; the helper extraction is the next commit) and the two optional asks (limit="0" on offset-only pages; the bare --pack-task --limit refusal naming the budgeted window) are noted. --- CHANGELOG.md | 11 ++++++++--- src/forpage.h | 49 +++++++++++++++++++++++++++++++++++++++++++++---- src/mcpverbs.h | 12 ++++++++++-- src/verbs_for.h | 8 ++++++-- 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab89e01d0..c5062700b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1105,9 +1105,14 @@ compact table). `--pack-task` under a budget takes the same window; a window wit (`forbudgetmonotoncheck`), and `--offset=0` alone remains the un-paged answer — its resume point is its own ``. -MCP `explore` declares `limit`/`offset` (the same bounded pair `for` takes) and answers the identical -document; previously it silently ignored `budget_tokens` beside a window and served the budgetless -file page. The `tools/list` manifest grows 46,493 → 46,677 B, and the ceiling moves 46,600 → 46,700 B +MCP `explore` declares `limit`/`offset` (the same bounded pair `for` takes) and answers the same +page shape; previously it silently ignored `budget_tokens` beside a window and served the budgetless +file page. The MCP twins page their own shared ranking (`ranking="mcp-lens"` names it on the root; +the CLI verbs page `verbs_for.h`'s `computeLensRanking`, `ranking="for-lens"` — the two pipelines +rank slightly different candidate sets, so `ranking=` says which list a window pages), and the MCP +pages carry no pasteable CLI `next=` handle — a CLI continuation would walk a different list; the +machine attributes (`next_offset=`/`limit=`/`next_tier=`) carry the programmatic continuation +through the same verb. The `tools/list` manifest grows 46,493 → 46,677 B, and the ceiling moves 46,600 → 46,700 B — the one sanctioned case that moves it: `explore`'s two declared schema properties (`test/mcpmanifestcheck.sh` arm `(1b)` asserts the figure against a live measurement). ### Fixed — the Linux G1 sanitizer ritual completes: five string_view comparator lambdas stop wrapping, and the GCC ASan path builds (#342) diff --git a/src/forpage.h b/src/forpage.h index fad681318..a9e97e5e9 100644 --- a/src/forpage.h +++ b/src/forpage.h @@ -164,6 +164,24 @@ struct ForCandidatePageReq { std::string_view task; std::string_view verbName; // "--for=" / "--pack-task=": next= names THEIR verb (#294 review) + std::string_view ranking; // #362 review, item 1: WHICH ranking paged this window — "for-lens" + // (verbs_for.h computeLensRanking, the CLI --for/--pack-task pipeline) + // or "mcp-lens" (the twins' shared inline pipeline, mcpverbs.h). The + // two pipelines rank slightly different candidate sets (the un-paged + // heads agree; the page denominator exposes the difference), so a + // consumer that crosses dialects must not assume one list. + bool pasteHandle = true; // false on MCP: no pasteable CLI argv — a CLI continuation would + // walk a DIFFERENT list on the twins' pipeline (#362 review, item 1); + // the machine attrs (next_offset=/limit=/next_tier=) remain for the + // programmatic continuation through the same verb. + bool compactLegend = false; // emit in the compact dialect (the default posture): the page is + // compacted IN-DOC by the central layer (applyCompactDialect, + // candidate-page) so the measured est_tokens=/over_ceiling= + // describe the bytes actually emitted (#362 review, item 4 — + // the CLI-side compaction used to reprice a page the trim loop + // had measured against the full legend, and the stderr line + // contradicted the page). Pre-compacted here, the outer layers + // see schema= and pass it through (AlreadyCompact). std::string_view routeNote; std::string_view rootArg; rw::RedactCounts* redactPtr = nullptr; @@ -177,8 +195,11 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: const rw::AdaptiveCut& forCut, const ForCandidatePageReq& p ) { using namespace rw; // the file's idiom: function-scoped - const std::string_view task = p.task; - const std::string_view verbName = p.verbName; + const std::string_view task = p.task; + const std::string_view verbName = p.verbName; + const std::string_view rankingName = p.ranking; + const bool pasteHandle = p.pasteHandle; + const bool compactLegend = p.compactLegend; const std::string_view routeNote = p.routeNote; const std::string_view rootArg = p.rootArg; rw::RedactCounts* const redactPtr = p.redactPtr; @@ -306,6 +327,7 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: + "\" offset=\"" + std::to_string( win.begin ) + "\" limit=\"" + std::to_string( pageLimit > 0 ? pageLimit : 0 ) + "\" tier=\"" + ( below ? "below-cliff" : "head" ) + "\"" + + ( rankingName.empty() ? std::string() : " ranking=\"" + std::string( rankingName ) + "\"" ) + std::string( extra ); }; // THE PASTEABLE CONTINUATION HANDLE — the file page's next= contract: the argv that walks to @@ -313,6 +335,7 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: // no hint) and dropped when has_more="0" (#362 review, item 3: a last page handing back a next // that returns an empty page carrying the same next= is a loop with no exit). const auto buildNextAttr = [ & ]( std::size_t nextOff ) { + if( !pasteHandle ) { return std::string(); } // MCP: no pasteable CLI argv — the machine attrs carry the resume (#362 review, item 1) if( nextOff >= candidateTotal ) { return std::string(); } std::string inv = nextFlag( verbName, task ); // THEIR verb: pasting continues the same verb (#294 review) inv += " --token-budget=" + std::to_string( tokenBudget ); @@ -324,6 +347,7 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: // next_tier (the ruling's suggestion 2): the tier the CONTINUATION page will carry — only // beside a next= (nothing further means no tier to advertise). const auto buildNextTierAttr = [ & ]( std::size_t nextOff ) { + if( !pasteHandle ) { return std::string(); } // rides beside next= only if( nextOff >= candidateTotal ) { return std::string(); } return std::string( " next_tier=\"" ) + ( ( forCut.cliffRank > 0 && nextOff >= forCut.cliffRank ) ? "below-cliff" : "head" ) + "\""; }; @@ -413,11 +437,28 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: if( est > tokenBudget ) { std::tie( t, est ) = buildTail( h, legend, rowsNow, /*overCeiling=*/true, nextNow ); + } + } + std::string pageOut = h + t + legend + rowsNow; + // (#362 review, item 4) the dialect actually emitted: compact first (the central layer — it + // reprices est_tokens= and settles over_ceiling= on the bytes it writes), so the page's own + // numbers describe what ships; the outer layers see schema= and pass it through untouched. + if( compactLegend ) + { + applyCompactDialect( pageOut, "candidate-page" ); + } + // the stderr notice reads the FINAL page: est_tokens straight off the emitted root, so the + // line can never contradict the document (the full-dialect trim numbers used to). + if( tokenBudget > 0 ) + { + const std::size_t finalEst = rootUnsignedAttr( std::string_view( pageOut ).substr( pageOut.find( '<' ), pageOut.find( '>' ) + 1 ), "est_tokens" ); + if( finalEst > tokenBudget && pageOut.find( " over_ceiling=\"1\"" ) != std::string::npos ) + { rw::emitTo( stderr, "ripwire: for-page est_tokens={} exceeds the stated budget={} — the window cannot fit; " - "the top row is served with over_ceiling=1\n", est, tokenBudget ); + "the top row is served with over_ceiling=1\n", finalEst, tokenBudget ); } } - return h + t + legend + rowsNow; + return pageOut; } diff --git a/src/mcpverbs.h b/src/mcpverbs.h index d50372cde..03cb67499 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -1963,7 +1963,13 @@ inline std::optional forTaskText( const std::string& root, const st if( budgetTokens > 0 ) { const AdaptiveCut cut = adaptiveCut( lensRank, 5, std::size_t( forTopN ), /*scanFullDistribution=*/true ); - return forCandidatePageDoc( ing, lensRank, cut, ForCandidatePageReq{ task, "--for=", routeNoteOf( rc, shape, noRoute ), mcpRootArg, + // #362 review, item 1+3: the twins' shared inline pipeline ("mcp-lens") — ranking= names it; NO + // pasteable CLI handle (a CLI continuation would walk verbs_for.h computeLensRanking's list); the + // compact dialect emitted in-doc (schema= present) so the ref posture reduces it like any other + // spec-keyed answer — the outer layer sees AlreadyCompact and passes it through. + return forCandidatePageDoc( ing, lensRank, cut, ForCandidatePageReq{ task, "--for=", /*ranking=*/"mcp-lens", + /*pasteHandle=*/false, /*compactLegend=*/true, + routeNoteOf( rc, shape, noRoute ), mcpRootArg, redact, gitstamp::stampAt( root ), budgetTokens, page.limit, page.offset } ); } const ForFilePage filePage = computeForFilePage( ing, lensRank, mcpEvidence ); @@ -4250,7 +4256,9 @@ inline std::string packTaskText( const std::string& root, const std::string& tas if( budgetTokens > 0 ) { const AdaptiveCut cut = adaptiveCut( lr.rank, 5, std::size_t( kForLensDefaultTopN ), /*scanFullDistribution=*/true ); - return forCandidatePageDoc( ing, lr.rank, cut, ForCandidatePageReq{ task, "--pack-task=", lr.routeNote, mcpRootArg, + return forCandidatePageDoc( ing, lr.rank, cut, ForCandidatePageReq{ task, "--pack-task=", /*ranking=*/"mcp-lens", + /*pasteHandle=*/false, /*compactLegend=*/true, + lr.routeNote, mcpRootArg, redact, gitstamp::stampAt( root ), budgetTokens, pageLimit, pageOffset } ); } const ForFilePage filePage = computeForFilePage( ing, lr.rank, lr.evidence ); diff --git a/src/verbs_for.h b/src/verbs_for.h index bdc9ecc48..706f21d88 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2453,7 +2453,9 @@ std::optional runForLens( const MainDispatch& d ) { // W3-N1/#294 review: the page redacts through packSignatures — the tally must be reported, // exactly like the --json early return below (a redacted credential must never be silent). - const int pageRc = emitForCandidatePage( ing, lensRank, forCut, rw::ForCandidatePageReq{ cfg.forTask, "--for=", routeNoteRaw, flRootArg, redactPtr, + const int pageRc = emitForCandidatePage( ing, lensRank, forCut, rw::ForCandidatePageReq{ cfg.forTask, "--for=", /*ranking=*/"for-lens", + /*pasteHandle=*/true, /*compactLegend=*/cfg.legend != "full", + routeNoteRaw, flRootArg, redactPtr, flSingleRoot ? std::string_view( gitstamp::stampAt( std::string( root ) ) ) : std::string_view(), cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset } ); reportRedactions( stderr, redactCounts ); // W3-N1: the page redacts — the tally reports @@ -3822,7 +3824,9 @@ std::optional runPackTask( const MainDispatch& d ) } const AdaptiveCut packCut = adaptiveCut( lr.rank, 5, std::size_t( cfg.packTopN > 0 ? cfg.packTopN : kForLensDefaultTopN ), true ); const std::string packAtStamp = in.rootArg.empty() ? std::string() : gitstamp::stampAt( std::string( in.rootArg ) ); - const int packPageRc = emitForCandidatePage( ing, lr.rank, packCut, rw::ForCandidatePageReq{ task, "--pack-task=", lr.routeNote, in.rootArg, + const int packPageRc = emitForCandidatePage( ing, lr.rank, packCut, rw::ForCandidatePageReq{ task, "--pack-task=", /*ranking=*/"for-lens", + /*pasteHandle=*/true, /*compactLegend=*/cfg.legend != "full", + lr.routeNote, in.rootArg, d.redactPtr, packAtStamp, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset } ); reportRedactions( stderr, d.redactCounts ); // W3-N1: the page redacts — the tally reports return packPageRc; From d14593177e51156071baaab335a27931000c0f51 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 04:53:59 +0000 Subject: [PATCH 012/170] =?UTF-8?q?refactor(paging):=20the=20#362=20review?= =?UTF-8?q?=20round=203's=20item=202=20=E2=80=94=20runPackTaskPage=20+=20P?= =?UTF-8?q?ackTaskForm,=20the=20quality=20gate's=20two=20findings=20closed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --quality-delta=0e5fdcd2..HEAD exited 2 on exactly two gating findings; both are this round's shape, both closed by extraction/bundling (behavior byte-identical; the branch body moves, nothing changes): - runPackTask complexity 19 -> 28: the budgeted-window branch extracted into runPackTaskPage( d, lr, task, in ) — one helper, one job (refuse a partitioned window, serve the candidate page, report the redaction tally); runPackTask keeps the one-line gate. - packTaskText params 6 -> 8: the four optional form knobs (partition fan-out, route posture, and the page window) bundled into PackTaskForm — one aggregate struct, one parameter; the call site in mcp.h initializes it from the request's own fields. --- src/mcp.h | 4 ++-- src/mcpverbs.h | 17 +++++++++++++++-- src/verbs_for.h | 39 ++++++++++++++++++++++++++------------- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/mcp.h b/src/mcp.h index a5702e91a..a5dcc5619 100644 --- a/src/mcp.h +++ b/src/mcp.h @@ -2563,8 +2563,8 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo static_assert( kMcpRecallTopKMax == 1000, "the top_k refusal names the band 1..1000 in mcprefusal.h's kMcpValueFields and in the " "tools/list memory_recall stanza — move all three together" ); - resp = textResult( packTaskText( path, task, budgetTokens, redactPtr, partitionCount, noRoute, - explorePage.page.limit, explorePage.page.offset ) ); + resp = textResult( packTaskText( path, task, budgetTokens, redactPtr, + PackTaskForm{ partitionCount, noRoute, explorePage.page } ) ); } } // L4: `from_trace` — maps a pasted stack-trace/sanitizer/compiler-error TEXT onto indexed symbols diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 03cb67499..c705133f7 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -4147,10 +4147,23 @@ inline std::pair qualityBaselineJson( const std::strin // what it is FOR; a separate verb would duplicate the whole task/budget contract for one integer. 0 (or any // value outside 2..16, which is silently clamped OFF rather than erroring an otherwise valid explore call) // ⇒ the plain single-bundle form, byte-identical to before. +// (#362 review, item 2): the form options in ONE struct — packTaskText's parameter list was the +// quality gate's finding (6 -> 8 as the paging round grew it; the form bundles the family: the +// partition fan-out, the route posture, and the page window, all of them optional knobs of the +// same bundle). Aggregate-init order is the declaration order. +struct PackTaskForm +{ + std::uint32_t partitionCount = 0; // --partition=N over MCP (0 = one un-split bundle) + bool noRoute = false; // the CLI --no-route twin + McpPageArgs page; // limit/offset: the candidate page under a budget, the file page without +}; inline std::string packTaskText( const std::string& root, const std::string& task, std::size_t budgetTokens, - RedactCounts* redact = nullptr, std::uint32_t partitionCount = 0, bool noRoute = false, - int pageLimit = 0, int pageOffset = 0 ) + RedactCounts* redact = nullptr, const PackTaskForm& form = {} ) { + const std::uint32_t partitionCount = form.partitionCount; + const bool noRoute = form.noRoute; + const int pageLimit = form.page.limit; + const int pageOffset = form.page.offset; const McpIndex& ix = getIndex( root ); const IngestResult& ing = ix.ing; const Graph& g = ix.g; diff --git a/src/verbs_for.h b/src/verbs_for.h index 706f21d88..a2733a1a9 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -3766,6 +3766,30 @@ std::optional runTargetedViews( const MainDispatch& d ) // kPackTask* constants) now lives in packtask.h (L4) as packTaskBundleText() — shared verbatim with the MCP // explore/pack_task verb (mcpverbs.h's packTaskText()). This handler only resolves CLI-specific inputs // (flags, MainDispatch pointers) and hands them to that ONE assembler. +// (#362 review, item 2): the budgeted-window branch, extracted — runPackTask's own complexity was +// the quality gate's finding (19 -> 28 as the paging round grew the branch inline). One helper, one +// job: refuse a partitioned window, serve the candidate page, report the redaction tally. +inline std::optional runPackTaskPage( const MainDispatch& d, const rw::LensRanking& lr, const std::string& task, + const rw::PackTaskInputs& in ) +{ + using namespace rw; + const Config& cfg = d.cfg; + const IngestResult& ing = d.ing; + if( cfg.partitionCount > 0 ) + { + rw::emitRaw( stderr, "ripwire: --partition and a --limit/--offset window are mutually exclusive — a partitioned bundle has N+1 slices and no single candidate order to page; drop one\n" ); + return 1; + } + const AdaptiveCut packCut = adaptiveCut( lr.rank, 5, std::size_t( cfg.packTopN > 0 ? cfg.packTopN : kForLensDefaultTopN ), true ); + const std::string packAtStamp = in.rootArg.empty() ? std::string() : gitstamp::stampAt( std::string( in.rootArg ) ); + const int packPageRc = emitForCandidatePage( ing, lr.rank, packCut, rw::ForCandidatePageReq{ task, "--pack-task=", /*ranking=*/"for-lens", + /*pasteHandle=*/true, /*compactLegend=*/cfg.legend != "full", + lr.routeNote, in.rootArg, + d.redactPtr, packAtStamp, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset } ); + reportRedactions( stderr, d.redactCounts ); // W3-N1: the page redacts — the tally reports + return packPageRc; +} + std::optional runPackTask( const MainDispatch& d ) { using namespace rw; @@ -3815,21 +3839,10 @@ std::optional runPackTask( const MainDispatch& d ) // with an explicit window pages its ranked candidates, mirroring --for (the shared inline). The cliff // statistic is computed here ONCE (the same call --for's disclosure uses); --partition and a window // refuse together — a partitioned bundle has N+1 slices and no single candidate order to page. + // (#362 review, item 2): the branch body lives in runPackTaskPage — this stays the one-line gate if( cfg.tokenBudget != 0 && ( cfg.pageLimit > 0 || cfg.pageOffset > 0 ) ) { - if( cfg.partitionCount > 0 ) - { - rw::emitRaw( stderr, "ripwire: --partition and a --limit/--offset window are mutually exclusive — a partitioned bundle has N+1 slices and no single candidate order to page; drop one\n" ); - return 1; - } - const AdaptiveCut packCut = adaptiveCut( lr.rank, 5, std::size_t( cfg.packTopN > 0 ? cfg.packTopN : kForLensDefaultTopN ), true ); - const std::string packAtStamp = in.rootArg.empty() ? std::string() : gitstamp::stampAt( std::string( in.rootArg ) ); - const int packPageRc = emitForCandidatePage( ing, lr.rank, packCut, rw::ForCandidatePageReq{ task, "--pack-task=", /*ranking=*/"for-lens", - /*pasteHandle=*/true, /*compactLegend=*/cfg.legend != "full", - lr.routeNote, in.rootArg, - d.redactPtr, packAtStamp, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset } ); - reportRedactions( stderr, d.redactCounts ); // W3-N1: the page redacts — the tally reports - return packPageRc; + return runPackTaskPage( d, lr, task, in ); } // --partition=N: the FAN-OUT form. Same lens ranking, same PackTaskInputs, same From 81831db770b06625793e58add222c8965bb087c2 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 07:28:07 +0000 Subject: [PATCH 013/170] =?UTF-8?q?test(showcase):=20the=20capture-regen?= =?UTF-8?q?=20blockers=20=E2=80=94=20--lsp/--sections=20on=20the=20Not-run?= =?UTF-8?q?=20line,=20the=20compact-default=20pairs,=20the=20order-insensi?= =?UTF-8?q?tive=20(H)=20read?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three arms a fresh regeneration trips, none of them the paging change: - (D): --lsp and --sections were hand-added to the 2026-09-14 capture's 'Not run (and why)' line but never to the generator's own static list, so every regenerated capture silently dropped them and coverage went 174/176. Added to the generator, with the reasons the hand edit gave. - (F): compact became the DEFAULT legend posture on 2026-09-19 (bcb796959), after the 2026-09-14 capture was recorded. The three adjacent +--legend=compact variant demos (--callers, --quality-delta, --edit-check) now contrast nothing against their bare partners — the pair is byte-identical. Flipped to --legend=full, which IS the contrast now, captions rewritten to say so. - (H): the root gained schema= before p= in the compact dialect; the arm's p-first grep read 'no sym= at all' from any regenerated capture. Extract the whole tag, then read sym= out of it. --- test/showcase_capture.py | 8 ++++---- test/showcasecapturecheck.sh | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/showcase_capture.py b/test/showcase_capture.py index ee03fbab3..6576aac4d 100644 --- a/test/showcase_capture.py +++ b/test/showcase_capture.py @@ -378,7 +378,7 @@ def add(section, cmd, what, **opts): add(S2, f"{BIN} . --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.") add(S2, f"{BIN} . --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.") add(S2, f"{BIN} . --callers=rankGraphTeleport", "Who calls SYM (1-hop in-edges).") -add(S2, f"{BIN} . --callers=rankGraphTeleport --legend=compact", "The same rows under --legend=compact: the prose legend becomes one compact comment plus schema=\"ripwire.callers/v1\" on the root — every row byte and every completeness attribute (counts_floor=, graph_ambiguous=, next=) identical, ~3 KB of legend gone. Works on EVERY XML verb now, not four.") +add(S2, f"{BIN} . --callers=rankGraphTeleport --legend=full", "The prose legend restored: compact is the DEFAULT (bcb796959), so the pair above already shows the compact posture — --legend=full opts back into the ~3 KB explanatory legend the default trims. Rows and completeness attributes unchanged; only the legend differs.") add(S2, f"{BIN} . --callers=DoesNotExist", "Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.") add(S2, f"{BIN} . --callees=rankGraphTeleport", "What SYM calls (1-hop out-edges).") add(S2, f"{BIN} . --uses=rankGraphTeleport", "The resolvable use-sites (call/read/write/import/extends) with file:line; count= is a floor.") @@ -704,14 +704,14 @@ def chooseInWindow( lo=2, hi=14 ): add(S8, f"{D} . --situ", "Situational report for a real diff: blast radius + tests + co-change + forgotten co-change partners.", cwd=DIRTY) add(S8, f"{D} . --test-gate", "The pre-PR gate with real obligations — exit 4 when tests-to-run or untested blast radius is non-empty.", cwd=DIRTY) add(S8, f"{D} . --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=.", cwd=DIRTY) -add(S8, f"{D} . --quality-delta --legend=compact", "The same gating report under --legend=compact — same rows, same exit 2, schema=\"ripwire.quality-delta/v1\", ~4 KB of legend down to one comment: the shape an agent's edit loop should run.", cwd=DIRTY) +add(S8, f"{D} . --quality-delta --legend=full", "The same gating report under --legend=full — same rows, same exit 2, but the ~4 KB prose legend the compact default trims is restored: the posture a human reads once to learn the schema.", cwd=DIRTY) add(S8, f"{D} . --quality-delta --json", "The same findings as JSON (one of the CI/scripting verbs --json supports) — same exit 2 as the XML form.", cwd=DIRTY) add(S8, f"{D} . --quality-delta --quality-ack --ack-only=zzznope", "NEW FLAG: --ack-only matching nothing REFUSES rather than falling back to acking everything.", cwd=DIRTY) add(S8, f"{D} . --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.", cwd=DIRTY) add(S8, f"{D} . --quality-delta", "Re-run after the partial ack: acked=3, the rest still gate (exit 2).", cwd=DIRTY) add(S8, f"{D} . --ack-only=gating", "--ack-only WITHOUT --quality-ack REFUSES loudly (exit 1, the pairing named) — it used to be silently ignored.", cwd=DIRTY) add(S8, f"{D} . --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.", cwd=DIRTY) -add(S8, f"{D} . --edit-check=nonNegativeFloatDescKey --legend=compact", "The same verdict under --legend=compact: ~5.7 KB of legend becomes one comment, every row identical — the post-edit reflex at its cheapest.", cwd=DIRTY) +add(S8, f"{D} . --edit-check=nonNegativeFloatDescKey --legend=full", "The same verdict under --legend=full: every row identical to the compact default above, but the ~5.7 KB explanatory legend is restored — the posture for a first-time reader.", cwd=DIRTY) add(S8, f"{D} . --pr-context", "The review-evidence bundle with an actual changed file.", cwd=DIRTY) add(S8, f"{D} . --map-diff --top-k=5", "The map re-ranked with a teleport toward the changed file (changed=1 here, not 0).", cwd=DIRTY) add(S8, f"{D} . --clones", "The duplicated helper the sandbox edit introduced shows up as a clone group.", cwd=DIRTY) @@ -947,7 +947,7 @@ def mustReplace( text, old, new, what ): doc.append("") doc.append("**How to read the blocks:** ripwire's real XML output is minified — often ONE long line. For scanability, long minified lines are displayed re-wrapped with a line break at every tag seam (`><`). Header COMMENT lines (the legends) always appear in full — they are exempt from the per-line cut; any OTHER display line over 300 bytes is cut with a `… [line truncated: N more bytes]` marker, which can hit a long root element or row. `--plan-lanes` emits JSON and is re-wrapped at object seams the same way. Long outputs are cut to their first ~30 display lines with a `… [N more display lines; full output is M bytes]` marker giving the true size. Exit codes are recorded when non-zero; wall time when >1s.") doc.append("") -doc.append(f"**Not run (and why):** `ripwire ` (network clone), `--listen` / `--mcp-token` / `--allow-remote-edits` (the HTTP-server posture; `--mcp` itself IS captured in its own section as a one-shot stdio JSON-RPC exchange, and `wrap claude` shows the wiring), `--arch --baseline[-update]` (state writer against the read-only repo — `--note-add` / `--quality-baseline` / `--quality-ack` / the three edit verbs / `--edit-plan` ARE shown, inside the throwaway sandbox clone; `--index-out` / `--pin-census` write to scratch), `--eval-mined` (needs a `minedpair.jsonl` artifact from `bench/mine_traces.py`; none present in the tree), `--refetch` (git-url only), `--force` (wrap-only modifier), `--scan-skills` bare form (would sweep `~/.claude/skills`; the explicit-DIR form is shown instead), `--help` ({help_line_count} lines — read it from the binary).") +doc.append(f"**Not run (and why):** `ripwire ` (network clone), `--listen` / `--mcp-token` / `--allow-remote-edits` (the HTTP-server posture; `--mcp` itself IS captured in its own section as a one-shot stdio JSON-RPC exchange, and `wrap claude` shows the wiring), `--arch --baseline[-update]` (state writer against the read-only repo — `--note-add` / `--quality-baseline` / `--quality-ack` / the three edit verbs / `--edit-plan` ARE shown, inside the throwaway sandbox clone; `--index-out` / `--pin-census` write to scratch), `--eval-mined` (needs a `minedpair.jsonl` artifact from `bench/mine_traces.py`; none present in the tree), `--refetch` (git-url only), `--lsp` (the editor transport — its one-shot dialogues are gated by `test/lspcheck.sh`, not by showcase blocks), `--sections` (the / stub-and-restore shapes are gated by `test/forsectioncollapsecheck.sh`; the flag restores what --for collapses by default), `--force` (wrap-only modifier), `--scan-skills` bare form (would sweep `~/.claude/skills`; the explicit-DIR form is shown instead), `--help` ({help_line_count} lines — read it from the binary).") doc.append("") cur_section = None diff --git a/test/showcasecapturecheck.sh b/test/showcasecapturecheck.sh index cca88e08b..7bfc704ed 100755 --- a/test/showcasecapturecheck.sh +++ b/test/showcasecapturecheck.sh @@ -549,7 +549,10 @@ for cand in $seedCands; do # (1) what the capture PUBLISHES — that is what a reader gets, and where the 1148/buildGraph defect # lived: main's 09-05 and 09-07 both carry under a rankGraphTeleport demo. - recSym="$( grep -oE ' root + # BEFORE p=, so the old p-first grep matched nothing on any capture regenerated since — extract + # the whole tag for THIS line, then read sym= out of it + recSym="$( grep -oE ']*>' "$newestCapture" | grep "p=\"src/graph\.h\" l=\"""$cand""\"" | grep -oE 'sym="[^"]*"' | head -1 )" if [ "$recSym" = "sym=\"$SEEDSYM\"" ]; then ok "(H) the capture PUBLISHES sym=\"$SEEDSYM\" for src/graph.h:$cand — the reader is given the symbol the demo is about" else From 32d8871912b3573e329aae8740ac880823982b60 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 08:09:17 +0000 Subject: [PATCH 014/170] docs(captures): regenerate COMMANDS showcase on upstream-paging 815879137 Generated by test/showcase_capture.py on a clean tree, 255 commands, with this commit's generator fixes in place (--lsp/--sections on the Not-run line, the --legend=full contrast pairs, the order-insensitive (H) read in the gate). --legend-dict now records dictv=861226192676dedb entries=733, matching the live binary, so showcasecapturecheck (I) passes instead of tripping the stale 09-14 figure. --- docs/captures/COMMANDS_showcase_2026-10-01.md | 7528 +++++++++++++++++ 1 file changed, 7528 insertions(+) create mode 100644 docs/captures/COMMANDS_showcase_2026-10-01.md diff --git a/docs/captures/COMMANDS_showcase_2026-10-01.md b/docs/captures/COMMANDS_showcase_2026-10-01.md new file mode 100644 index 000000000..c19531cf5 --- /dev/null +++ b/docs/captures/COMMANDS_showcase_2026-10-01.md @@ -0,0 +1,7528 @@ +# ripwire — every verb, run for real + +- **Date:** 2026-10-01 (regenerated capture; supersedes any older `docs/captures/COMMANDS_showcase_*.md`) +- **Lives in `docs/captures/`** — a directory the crawl/retrieval lenses SKIP (`kCrawlSkipDirs`, src/ingest.h): a generated doc that quotes every verb's output out-scores the source for any query about the tool and was measured at 77% of `--recall` on this repo when it sat at the root. `test/argvdiffcheck.sh` harvests its `## `-heading command lines as differential vectors — keep that format. +- **Version:** `ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=67cf52baa+dirty)` +- **Repo:** the ripwire repo @ `ce5c0b3fc` — **CLEAN — `git status --porcelain` is empty**. The diff-aware verbs (`--situ`/`--test-gate`/`--quality-delta`/`--pr-context`/`--map-diff`/`--edit-check`) answer a question about the WORKING TREE, so that condition is part of their answer and every one of their captions below states which tree it recorded against. A clean tree is the honest default for a showcase, so they appear TWICE: once here on the clean tree (their empty/exit-0 shape) and once in the final section against a throwaway `git clone --local` sandbox carrying one deliberate regression, so their real gating shapes are visible without writing a byte into the read-only repo. +- **Corpus:** the ripwire repo itself (dogfood), via `./build/ripwire` +- **Sandbox diff** (the last section only): `.ripwire_notes | 2 ++ + .ripwire_quality_acks | 1 + + src/infra/sortutil.h | 31 ++++++++++++++++++++++++++++--- + 3 files changed, 31 insertions(+), 3 deletions(-)` — one preexisting function made deeply nested, one function's arity changed 1 -> 2, one copy-paste duplicate helper, one new 8-parameter public function. + +**How to read the blocks:** ripwire's real XML output is minified — often ONE long line. For scanability, long minified lines are displayed re-wrapped with a line break at every tag seam (`><`). Header COMMENT lines (the legends) always appear in full — they are exempt from the per-line cut; any OTHER display line over 300 bytes is cut with a `… [line truncated: N more bytes]` marker, which can hit a long root element or row. `--plan-lanes` emits JSON and is re-wrapped at object seams the same way. Long outputs are cut to their first ~30 display lines with a `… [N more display lines; full output is M bytes]` marker giving the true size. Exit codes are recorded when non-zero; wall time when >1s. + +**Not run (and why):** `ripwire ` (network clone), `--listen` / `--mcp-token` / `--allow-remote-edits` (the HTTP-server posture; `--mcp` itself IS captured in its own section as a one-shot stdio JSON-RPC exchange, and `wrap claude` shows the wiring), `--arch --baseline[-update]` (state writer against the read-only repo — `--note-add` / `--quality-baseline` / `--quality-ack` / the three edit verbs / `--edit-plan` ARE shown, inside the throwaway sandbox clone; `--index-out` / `--pin-census` write to scratch), `--eval-mined` (needs a `minedpair.jsonl` artifact from `bench/mine_traces.py`; none present in the tree), `--refetch` (git-url only), `--lsp` (the editor transport — its one-shot dialogues are gated by `test/lspcheck.sh`, not by showcase blocks), `--sections` (the / stub-and-restore shapes are gated by `test/forsectioncollapsecheck.sh`; the flag restores what --for collapses by default), `--force` (wrap-only modifier), `--scan-skills` bare form (would sweep `~/.claude/skills`; the explicit-DIR form is shown instead), `--help` (202 lines — read it from the binary). + + +--- + +# understand a codebase cold + +## `./build/ripwire .` + +*The default ranked symbol map — start here when landing cold in a repo.* + +**wall time: 1.66s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [802 more display lines; full output is 22158 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --top-k=5` + +*Same map, capped to the 5 highest-ranked symbols.* + +**wall time: 1.27s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.21s** + +````` + + + + + + + + + + +````` + +## `./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.37s** + +````` + + +````` + +stderr: + +````` +ripwire: --token-budget exceeded: withheld_est_tokens=8930 > 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: 2.78s** + +````` + + + + +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 + +the cached index for `root`, rebuilt only when stale (otherwise returned as-is, no parse, no gra…inline const McpIndex& getIndex( const std::string& root ) +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline std::uint64_t contentHash64( std::string_view s ) noexcept +struct CacheEntry +inline void prewarmTagsQueries( const std::vector<std::string>& files, const HashMap<std::string, FileFacts>& cache, long long cacheWriteNs, IngestFileScan& … [line truncated: 12 more bytes on this line] +inline std::uint64_t blobChecksum( std::string_view s ) noexcept +inline ContentIdIndex contentIdsBySym( const IngestResult& ing, const Graph& g, std::string_view root ) +inline bool ingestCommitTree( const std::string& root, const std::string& sha, const std::vector<std::string>& excludes, std::size_t maxFileBytes, IngestResult&… +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, … [line truncated: 22 more bytes on this line] +inline std::vector<CacheWriteRow> buildCacheWritePlan( const std::vector<std::uint32_t>& orderIn, const std::vector<std::uint64_t>& pathHashes, const s … [line truncated: 96 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 ) +struct IngestFileScan +inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t>& fi … [line truncated: 16 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 ) +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>& cache, cons … [line truncated: 91 more bytes on this line] +inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) +inline NodeId resolveHandleAll( const McpIndex& ix, std::uint64_t idHash, std::vector< NodeId >& matches ) +… [45 more display lines; full output is 8934 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: 3.10s** + +````` + + + + + +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; + } + if( teleportMass > 0.0 ) + { +… [12 more display lines; full output is 4309 bytes on 29 raw line(s)] +````` + +## `./build/ripwire . --for="rankGraphTeleport" --no-route` + +*Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* + +**wall time: 3.01s** + +````` + + + + + +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", bo … [line truncated: 11 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::string didYouMean( const IngestResult& ing, std::string_view name ) +inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = nullptr ) +inline const McpIndex& getIndex( const std::string& root ) +PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double& … [line truncated: 37 more bytes on this line] +inline std::vector<float> churnDecayTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, bool* outHasChurnEvidence = nullptr )< … [line truncated: 3 more bytes on this line] +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 ) +inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 +### Fixed — `rank_by` over MCP no longer answers a different rankin … [line truncated: 57 more bytes on this line] +## Coverage inventory +#### The convergence disclosure contract + +… [100 more display lines; full output is 14960 bytes on 71 raw line(s)] +````` + +## `./build/ripwire . --for="rankGraphTeleport" --signatures-only` + +*T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribute) — contrast with the terminal default above.* + +**wall time: 3.04s** + +````` + + + + + +PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` + + + + +````` + +## `./build/ripwire . --for="tree-sitter parse of a source file" --adaptive` + +*Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothing is cut and the header says so ([adaptive: kept N of N]).* + +**wall time: 3.01s** + +````` + + + + +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 +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::array<LangEntry, 51> kLangTable = +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +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 ) +std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +constexpr std::uint32_t kMaxYamlNestDepth = 64u +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 bool hasMainGuard( std::string_view source ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +struct AstQuerySpec +inline bool spanTierMemoTryLoad( bool useMemo, const std::string& diskPath, const StatInfo& now, SpanTierMap& out ) +… [55 more display lines; full output is 9102 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25"` + +*Mention anchoring (default-on): a path and a Symbol literally named in the task get lifted; the header says what anchored.* + +**wall time: 3.64s** + +````` + + + + +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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) +inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) +struct LensRanking +struct NameCorpusStats +inline bool hasIdentifierShape( std::string_view w ) noexcept +inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) +struct Bm25Params +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 reads the W … [line truncated: 57 more bytes on this line] +struct CandidateProvenance +std::optional<int> runEvalViews( const MainDispatch& d ) +inline constexpr int kLexWeightDoc = 2 +inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +struct RecallSectionPick +inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) +inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept +… [56 more display lines; full output is 9443 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25" --no-mention-boost` + +*Same task with the anchor disabled — the contrast the flag exists for.* + +**wall time: 2.96s** + +````` + + + + +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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) +inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) +struct LensRanking +struct NameCorpusStats +inline bool hasIdentifierShape( std::string_view w ) noexcept +inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) +inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) +struct Bm25Params +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 reads the W … [line truncated: 57 more bytes on this line] +struct CandidateProvenance +std::optional<int> runEvalViews( const MainDispatch& d ) +inline constexpr int kLexWeightDoc = 2 +inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +struct RecallSectionPick +inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) +inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept +… [53 more display lines; full output is 9528 bytes on 1 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.33s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --exemplar="format byte sizes for humans"` + +*The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* + +**wall time: 1.80s** + +````` + + + + + 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 ); +}]]>[[nodiscard]] const char16_t* c_str() const noexcept +````` + +## `./build/ripwire . --help-task="calls(runDefaultMap, rankGraphTeleport)"` + +*Deterministic enhanced help: a closed claim in the task is a structured shape, so the router recommends the ONE command that answers it (--verify) with the evidence behind the pick. Advice only — nothing executes.* + +**wall time: 1.65s** + +````` + + + + +ripwire '.' --verify='calls(runDefaultMap, rankGraphTeleport)' + + +````` + +## `./build/ripwire . --help-task="write a cheerful release announcement"` + +*The honest half of the contract: a task with no ripwire-shaped evidence ABSTAINS with zero commands rather than guessing.* + +**wall time: 1.15s** + +````` + + + + +````` + +## `./build/ripwire . --recall="quality delta gating exit codes"` + +*Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* + +**wall time: 2.05s** + +````` +ripwire recall — "quality delta gating exit codes" — 113 relevant of 208 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5307 + +━━ README.md (relevance 8.782) ━━ [sections: 2 of 24 selected (60 in doc), section-granular; whole doc 204511 B; lines="2595-2606,2607-2614"; dropped_by_budget=22] +#### 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. | +| 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. | + +#### 6.3 JSON output + +`--json` emits JSON instead of XML, with keys mirroring the XML attribute names one to one. It is an +allow-list: the default map, `--for`, `--pack-task`, `--callers`, `--callees`, `--impact`, +`--quality-delta`, `--test-gate`, `--metrics`, and `--plan-lanes`, which is JSON-native. Every other +verb refuses on stderr with exit 1 rather than silently falling back to XML, so a verb added tomorrow +refuses by default. + + + +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.329) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +### Root spelling (maintainer-owned; reproduce it once so you recognise it) + +```bash +cmake -S . -B build && cmake --build build -j +… [158 more lines, 13543 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 1729927-byte scratch dir of DUMPED TOOL OUTPUT (a git log, this repo's own generated command reference, --help text, an architecture doc, a fabricated JSON access log) instead of a source repo — no index to build, no daemon.* + +**wall time: 2.85s** + +````` +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.656) ━━ [sections: 1 of 154 selected (183 in doc), section-granular; whole doc 717765 B; lines="515-540"; dropped_by_budget=153] [truncated: 1854 of 4242 bytes] +#### Pattern: a directory of dumped tool output as a knowledge base + +**Answers:** can `--recall` serve as a zero-setup knowledge base over dumped tool output — a +`git log`, an API response dump, a fetched doc, ` --help` text — sitting in a scratch +directory, instead of a source repo? + +Yes, unmodified. `--recall` never distinguishes "a codebase" from any other directory it can +walk: point it at the scratch dir and query it. No index to build, no daemon, no mutable store +between runs — the whole cost is one cold parse. Two conditions decide whether it works at all, +and both are yours, because the file you write is the only thing that sets them: + +1. **Dump to `.md`.** `--recall` ranks DOCUMENT files: `.md`, plus the docparse'd + `.ipynb`/`.html`/`.csv` (and Office/PDF through the optional markitdown bridge). `.txt`, + `.log`, `.json` and extensionless files are **not** documents to it. A directory of those + answers `0 relevant of 0 document files` and exits 0 — which reads like "nothing matched + your terms" when what happened is "nothing was indexed at all". Redirect to `notes.md`, + never `notes.txt`. The recorded run below is that rule's own demonstration: its scratch dir + holds five dumps, and the header says `2 relevant of 2 document files` because only the two + `.md` ones are documents — the `git log`, the `--help` text and the JSON access log are not + in the population at all. + +2. **Keep `##` headings in the dump.** A headed document is served as whole ranked SECTIONS, so + an answer buried mid-file arrives at a small `--max-tokens`, and — while the served document + SET stays fixed — a larger ceiling returns a strict superset of it; the `[sections: S of R + selected (N in doc) … lines="…"; dropped_by_budget=D]` note names the ranges you actually got + and what the ceiling cost. … + +(capped: 1 of 2 relevant document files omitted — raise --max-tokens/budget_tokens or narrow the query for 1 more (~2548-byte budget)) +````` + +## `./build/ripwire . --tree` + +*File-by-file orientation map (top symbols per file).* + +**wall time: 1.35s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [367 more display lines; full output is 11207 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --html=/aux/map2.html` + +*Self-contained HTML force-directed call graph.* + +**wall time: 3.70s** + +````` +(empty) +````` + +Artifact written: + +````` +158925 /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.31s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + + +--- + +# 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.23s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [222 more display lines; full output is 9255 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.61s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [2927 more display lines; full output is 77502 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: 1.20s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [30 more display lines; full output is 4312 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --callers=rankGraphTeleport` + +*Who calls SYM (1-hop in-edges).* + +**wall time: 1.18s** + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --callers=rankGraphTeleport --legend=full` + +*The prose legend restored: compact is the DEFAULT (bcb796959), so the pair above already shows the compact posture — --legend=full opts back into the ~3 KB explanatory legend the default trims. Rows and completeness attributes unchanged; only the legend differs.* + +**wall time: 1.51s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --callers=DoesNotExist` + +*Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* + +**exit code: 1** — **wall time: 1.31s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --callers symbol not found: DoesNotExist +````` + +## `./build/ripwire . --callees=rankGraphTeleport` + +*What SYM calls (1-hop out-edges).* + +**wall time: 1.64s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --uses=rankGraphTeleport` + +*The resolvable use-sites (call/read/write/import/extends) with file:line; count= is a floor.* + +**wall time: 1.85s** + +````` + + + + + + + + + + + + +````` + +## `./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))'` + +*Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* + +**wall time: 1.31s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [28 more display lines; full output is 3647 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.24s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5571 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.60s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5423 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.23s** + +````` + + + + + + + + +````` + +## `./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.66s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [46 more display lines; full output is 5015 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --mentions=rankGraphTeleport` + +*Markdown docs that name SYM in a backtick (doc<->code edges).* + +**wall time: 1.32s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --affected=src/graph.h` + +*Test files that transitively reach the changed file.* + +**wall time: 1.79s** + +````` + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --situ` + +*Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below).* + +**wall time: 1.83s** + +````` +ripwire situational-awareness — 0 changed file(s), 0 symbols in them +root: . +at: ce5c0b3fc + (0 changed files — working tree is clean, nothing to analyze) +````` + +## `./build/ripwire . --test-gate` + +*Pre-PR gate on a CLEAN tree: no obligations, exit 0.* + +**wall time: 3.24s** + +````` + + +````` + +## `./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.80s** + +````` + +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: 6.91s** + +````` + +if data.get( "schema" ) != SCHEMA: raise SystemExit( f"{path}: unexpected schema {data.get('schema')!r} (expecte +if not str( data.get( "tasks_lock_content_sha256", "" ) ).startswith( "questions:" ): train_repos = select_tasks +if train_repos: raise SystemExit( f"{path}: records from repo(s) that re-derive to LocBench +if base and ctx and base["status"] == "ok" and ctx["status"] == "ok": paired.append( ( instance_id, base["re +if not repos: return 0.0, [] +if base["localization_hit"] is None or ctx["localization_hit"] is None: return 0.0 +if bv: ratios.append( cv / bv - 1 ) +if not ratios: return None, None +if rw is None or native is None: return None +if not paired: out["note"] = "zero complete paired (baseline,ripwire_cli) runs — nothing to analyze yet" +if contaminated: print( f" ** {contaminated} baseline run(s) invoked ripwire despite the no-ripwire contract " +if "note" in out: print( f" {out['note']}" ); return +if out["n_pairs"] != 27: failures.append( f"expected 27 paired runs, got {out['n_pairs']}" ) +if out["n_incomplete"] != 2: failures.append( f"expected 2 incomplete pairs (the orphan + the contaminated-basel +if out["n_repos"] != 3: failures.append( f"expected 3 repos, got {out['n_repos']}" ) +if out.get( "n_contaminated_baseline" ) != 1: failures.append( f"expected exactly 1 contaminated baseline run co +if not ( out["resolved_delta_mean"] > 0 ): failures.append( "expected a positive resolved-rate delta" ) +if not ( out["resolved_delta_bootstrap_95_lower"] > 0 ): failures.append( "expected a POSITIVE bootstrap 95% low +if out["tokens_out_ratio_p50"] is None or abs( out["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( f"e +if out.get( "n_resolved_pairs" ) != 27: failures.append( f"expected all 27 pairs resolution-scored, got {out.get +if out.get( "substitution_rate_baseline" ) != 0.0: failures.append( f"expected baseline substitution rate 0.0 (n +if out.get( "substitution_rate_ripwire" ) is None or abs( out["substitution_rate_ripwire"] - 0.75 ) > 1e-9: fail +if out.get( "n_substitution_ripwire" ) != 27: failures.append( f"expected 27 substitution-scored ripwire runs, g +if out3.get( "substitution_rate_ripwire" ) is not None or out3.get( "n_substitution_ripwire" ) != 0: failures.ap +if out2["n_pairs"] != 27: failures.append( f"evaluator-none: expected 27 pairs, got {out2['n_pairs']}" ) +if out2["n_resolved_pairs"] != 0: failures.append( f"evaluator-none: expected 0 resolution-scored pairs, got {ou +if out2["resolved_delta_mean"] is not None or out2["resolved_delta_bootstrap_95_lower"] is not None: failures.ap +if out2["tokens_out_ratio_p50"] is None or abs( out2["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( " +… [73 more display lines; full output is 16840 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --query="teleport pagerank" --top-k=5` + +*Raw BM25 ranking (debug lens; --for is the real verb).* + +**wall time: 1.90s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [28 more display lines; full output is 3515 bytes on 1 raw line(s)] +````` + + +--- + +# zoom the detail ladder + +## `./build/ripwire . --for="pagerank power iteration" --detail=2` + +*Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* + +**wall time: 2.99s** + +````` + + + + + +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] + +What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure +inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) +struct RankedGraph +inline std::uint32_t sliceRdIterationCeiling() noexcept +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 … [line truncated: 7 more bytes on this line] +struct PageRankRun +inline std::uint32_t navRelevanceWeight( const Graph& g, NodeId n ) noexcept +COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "s… +inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) +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", … [line truncated: 90 more bytes on this line] +inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window +inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] +### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] +## Background — read these, in this order +## Design space and constraints +… [107 more display lines; full output is 14206 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: 92.7% fewer bytes at top-10, 88.7% at top-50, 87.6% at top-100 (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.39s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [138 more display lines; full output is 12056 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --outline=rankGraphTeleport --top-k=0` + +*Control-flow skeleton of one symbol, payload-only via the new --top-k=0.* + +**wall time: 1.18s** + +````` + + + + +& p, float alpha = 0.85f ) +{ + PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); + const std::vector pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + ... + } + std::vector r( N, 0.f ); + std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); + return { std::move( r ), run.iterationCount, run.hasConverged }; +} +]]> +````` + +## `./build/ripwire . --outline=rankGraphTeleport:1-10 --top-k=0` + +*CHANGED: a line range on --outline is now STRIPPED with a stderr note (it used to refuse).* + +**wall time: 1.74s** + +````` + + + + +& p, float alpha = 0.85f ) +{ + PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); + const std::vector pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + ... + } + std::vector r( N, 0.f ); + std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); + return { std::move( r ), run.iterationCount, run.hasConverged }; +} +]]> +````` + +stderr: + +````` +ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — outlining the whole symbol (use --expand=rankGraphTeleport:1-10 for a body slice) +````` + +## `./build/ripwire . --expand=rankGraphTeleport --top-k=0` + +*Full body + inline callee signatures.* + +**wall time: 1.25s** + +````` + + + + +, with a BRANCH-FREE size(). Both, not one or the other. +// +// ── THE DESIGN, AND WHY IT IS THIS ONE ─────────────────────────────────────────────────────────────── +// The shape the host tree leans on hardest is `Map>` — many tiny id-lists (byName / +// canonByName / shard maps, and ~100 more structures after the conversion wave): WRITE-ONCE during the +// parse/merge, then READ-HOT during resolve. +// +// Three things matter for that shape, and the layout below gets all three: +// • no per-list malloc — the N small lists that would each allocate are inline; +// • a BRANCH-FREE size() — `return sz_`, because the size lives in its own field; +// • 16 BYTES per instance — because `inl_` and `heap_` are never both live, so they share storage. +// +// The union is the whole trick. The previous revision of this file paid 8 extra bytes (24 B) for the +// explicit size field, on the reasoning that a branch-free size() was worth it. That was a false +// choice: union the inline array with the heap pointer and the struct reaches 16 B — ankerl's size — +// while the size stays in its own field and size() stays branch-free. +// +// THE TRADE THAT REMAINS, stated honestly. At 16 bytes you can have: +// • ankerl::svector's 3 inline slots, with a size() that branches on is_direct() (and, once spilled, +// dereferences into the heap block to read the size — a dependent load, not just a branch); or +// • this type's 2 inline slots, with size() branch-free. +// The measurement chose the second. See bench/SVECTORAB.md; the short version is below. +// +// ── WHAT THE MEASUREMENT ACTUALLY SAID (bench/SVECTORAB.md, hardware counters + working-set sweep) ──── +… [1089 more display lines; full output is 65981 bytes on 1116 raw line(s)] +````` + +stderr: + +````` +ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchanged behavior for now) +````` + + +--- + +# assess quality / structure + +## `./build/ripwire . --metrics --top-k=10` + +*Fan-in/out + complexity annotations on the map.* + +**wall time: 2.42s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [19 more display lines; full output is 5303 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --deps` + +*File->file dependency graph (god-files, cycles).* + +**wall time: 1.27s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [867 more display lines; full output is 22664 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --hotspots` + +*Complexity x recent git churn (maintenance pain).* + +**wall time: 3.16s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [14 more display lines; full output is 5741 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --clones` + +*Token-normalized duplicate bodies.* + +**wall time: 4.48s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [307 more display lines; full output is 16171 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --cochange` + +*Files that change together in git (hidden coupling).* + +**wall time: 3.26s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --hotspots --since="2 weeks ago"` + +*Hotspots scoped to RECENT churn (the regression lens).* + +**wall time: 2.19s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 5104 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --arch=test/archfix/rules.txt` + +*Enforce layering rules (exit 2 on violation) — run against the repo's own test fixture rules.* + +**wall time: 1.66s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [516 more display lines; full output is 75372 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint` + +*Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* + +**wall time: 15.47s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [701 more display lines; full output is 69541 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: 3.72s** + +````` + + + + +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:435, rw::parseLintRuleFile(const std::string&, std::string_view, std::vector&):: — 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.* + +**wall time: 1.22s** + +Input file: + +````` +#PROF_TSV_BEGIN one row per scope, aggregated across threads; counters are RAW integers +scope file line calls total_ms l1d_mpki +walk: chase pass x.cpp 9 12 48.500 7.250 +#PROF_TSV_END +````` + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 3428 bytes on 1 raw line(s)] +````` + +The joined finding — past the display cut above, extracted so the join is visible: + +````` +p = p->next +````` + +## `./build/ripwire . --communities` + +*Cluster the call graph into cohesive modules.* + +**wall time: 1.24s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [186 more display lines; full output is 16600 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --zoom` + +*Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels_shown="2" of levels= BY DEFAULT over the 40 largest top modules (~8 KB, where the whole tree is ~220 KB); a module AT the cut carries children=.* + +**wall time: 1.14s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [135 more display lines; full output is 6970 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --zoom --zoom-levels=3` + +*The restoring knob: --zoom-levels=N prints N levels (0 = the whole tree); here three, ~11 KB.* + +**wall time: 1.54s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [221 more display lines; full output is 9889 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --report` + +*Architecture summary (modules, god-files, cycles) as markdown.* + +**wall time: 1.27s** + +````` + + +# ripwire architecture report + +2345 files · 22606 symbols · 31600 edges · 2188 modules (10489 call-graph isolated) + +Root: `.` + +Call-graph isolate provenance: 2793 declaration, 1471 header, 1804 source, 4421 document; 0 connected Louvain singletons + +## Modules (call-graph clusters; showing 12 of 2188) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1303 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,add,collect]** — 1024 symbols +- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 516 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [emit,collect,push]** — 244 symbols +- **docs::`--top-k=N`@COMMANDS.md:59:8696 [pack,allow,insert]** — 173 symbols +- **src::relForHash@model.h:1339:108861 [compute,clone,insert]** — 50 symbols +- **src/infra::active@profilePmc.h:845:34910 [print,ensure,report]** — 49 symbols +- **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [erase,find,count]** — 47 symbols +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,call,compute]** — 43 symbols +- **src::assign@infra/svector.h:342:19905 [scan,sort,compute]** — 40 symbols +- **src/infra::buf@svector.h:125:8942 [insert,pack,render]** — 39 symbols +- **test::no@decltodefcheck.sh:126:11495 [run]** — 30 symbols + +## God files (most depended-on; showing 10 of 416) +- `test/lib/clean-env.sh` — 168 dependents +- `src/infra/emit.h` — 86 dependents +- `src/model.h` — 80 dependents +- `src/infra/Diagnostics.h` — 55 dependents +- `src/serialize.h` — 37 dependents +… [32 more lines, 3550 bytes total] +````` + +## `./build/ripwire . --seams` + +*Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* + +**wall time: 1.11s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [105 more display lines; full output is 13709 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --mermaid` + +*Module (directory) dependency graph as a Mermaid diagram.* + +**wall time: 1.49s** + +````` +%% 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
6405"] + n102["src/infra
1250"] + end + subgraph sg1 ["test"] + n103["test
4696"] + n294["test/fixtures/recallpassage
314"] + n280["test/expandmodefix
151"] + n282["test/expandsibsfix
149"] + n411["test/rubyattrsfix
85"] + n348["test/massfix
77"] + n314["test/javamethodreffix
62"] + end + n73["docs
808"] + n2[".github
589"] + subgraph sg4 ["bench"] + n4["bench
531"] + 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"] + n62["bench/shotgun
96"] + n45["bench/locbench/results/r1cpp_anchorhop
94"] + n11["bench/arb
85"] + n56["bench/nestcal/r1-2026-08-07
84"] +… [22 more lines, 1621 bytes total] +````` + +## `./build/ripwire . --owners` + +*Bus-factor: recency-weighted author ownership per file.* + +**wall time: 3.22s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [875 more display lines; full output is 97517 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.63s** + +````` + + + + +````` + +## `./build/ripwire . --exercises=test/regression.sh` + +*Which symbols a TEST FILE exercises — the reverse direction of --affected.* + +**wall time: 1.20s** + +````` + + + +````` + +## `./build/ripwire . --community=0` + +*Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* + +**wall time: 1.25s** + +````` + + + +````` + +## `./build/ripwire . --quality-delta` + +*On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* + +**wall time: 51.57s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [96 more display lines; full output is 12447 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: 37.11s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*No-LLM review-evidence bundle for the working-tree diff (clean tree = empty).* + +**wall time: 1.93s** + +````` + + + +````` + +## `./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: 5.89s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [284 more display lines; full output is 18471 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: 49.22s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --stray-content=main` + +*Which refs of the one branch `main` (no ref family left to select on this checkout) still hold divergent authored work vs HEAD, with verdicts.* + +**wall time: 4.03s** + +````` + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --stray-content=upstream-paging` + +*A second selection, the one branch `upstream-paging` (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.83s** + +````` + + + +````` + +## `./build/ripwire . --stray-content=main --plan` + +*Select the genuinely-unmerged refs of the one branch `main` (no ref family left to select on this checkout) 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: 22.90s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [205 more display lines; full output is 17196 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --stray-content=zzzz-no-such-ref --plan` + +*A --plan filter that selects NO ref REFUSES (exit 1) naming the substring — before the wave-3 close this fell through to the '>512 refs match' sentence, and --abi under the same filter answered an empty measurement at exit 0.* + +**exit code: 1** — **wall time: 2.07s** + +````` +(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=main --abi` + +*Cross-branch ABI-break gate over the one branch `main` (no ref family left to select on this checkout): 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: 3.03s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --whereis=rankGraphTeleport` + +*Which ref's tree defines or mentions SYM — HEAD first, then every local branch.* + +**wall time: 5.12s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [34 more display lines; full output is 15389 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --whereis=computeOnePairOverlap --with-history` + +*Same, plus a git-history row (never / removed-by-commit) for names no tree carries.* + +**wall time: 27.74s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [358 more display lines; full output is 22151 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --flags --flip=RIPWIRE_ASAN` + +*Blast radius of turning ONE gate on: live code, symbols, transitive reach, covering tests.* + +**wall time: 2.20s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --flags --flip=RIPWIRE_ASA` + +*Unknown-gate refusal (exit 1) with a did-you-mean from a real edit distance (one character off RIPWIRE_ASAN).* + +**exit code: 1** — **wall time: 2.20s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --flip: no gate named 'RIPWIRE_ASA' in . (did you mean 'RIPWIRE_ASAN'?) +ripwire: run `ripwire . --flags` for the gate table +````` + +## `./build/ripwire . --plan-lanes=3 --task="add a --since filter to the doc-drift verb and cover it with tests"` + +*NEW VERB: pre-hoc lane plan — which of 3 parallel worktrees would COLLIDE, before a line is written. JSON on stdout.* + +**wall time: 4.60s** + +````` +{"v":1,"verb":"plan-lanes","at":"ce5c0b3fc","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":2345,"symbols":22606,"edges":316 … [line truncated: 338 more bytes on this line] +"symbols":[{"p":"./src/docdrift.h","n":"DriftResult","scope":"DriftResult","l":284,"id":"./src/docdrift.h::DriftResult::DriftResult"}, +{"p":"./src/docdrift.h","n":"computeDocDrift","scope":"docdrift","l":2331,"id":"./src/docdrift.h::docdrift::computeDocDrift"}, +{"p":"./src/docdrift.h","n":"kDocDriftLegend","scope":"docdrift","l":2622,"id":"./src/docdrift.h::docdrift::kDocDriftLegend"}, +{"p":"./src/docdrift.h","n":"writeDocDriftPage","scope":"docdrift","l":2792,"id":"./src/docdrift.h::docdrift::writeDocDriftPage"}, +{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","l":630,"id":"./src/mcpverbs.h::rw::docDriftText"}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","l":1649,"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/gitmine.h","n":"gitWindowBoundarySha","scope":"rw","key":"18627516699d7062","id":"./src/gitmine.h::rw::gitWindowBoundarySha","id_addressable":true,"id_collides_with":0,"l":1605,"ord":0,"overloads":1,"amb":3,"cx":3,"ccx":2,"churn":56,"tested":0}, +{"p":"./src/crossref.h","n":"writeWhereisPage","scope":"crossref","key":"3a0425468e6a18cf","id":"./src/crossref.h::crossref::writeWhereisPage","id_addressable":true,"id_collides_with":0,"l":2198,"ord":0,"overloads":1,"amb":11,"cx":22,"ccx":23,"churn":48,"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":2568,"ord":0,"overloads":1,"amb":3,"cx":6,"ccx":6,"churn":31,"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":1210,"ord":0,"overloads":1,"amb":2,"cx":8,"ccx":8,"churn":28,"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":2747,"ord":0,"overloads":1,"amb":0,"cx":6,"ccx":5,"churn":31,"tested":0}, +{"p":"./src/taskroute.h","n":"classifyRoutes","scope":"rw::taskroute","key":"8f328a95b7861e33","id":"./src/taskroute.h::rw::taskroute::classifyRoutes","id_addressable":true,"id_collides_with":0,"l":1592,"ord":0,"overloads":1,"amb":12,"cx":18,"ccx":20,"churn":34,"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":2320,"ord":0,"overloads":1,"amb":2,"cx":2,"ccx":1,"churn":31,"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":1302,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":45,"tested":0}, +{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":2849,"ord":0,"overloads":1,"amb":33,"cx":8,"ccx":11,"churn":395,"tested":0}, +{"p":"./src/gitmine.h","n":"resolveSinceScope","scope":"rw","key":"9caaa3dbeaa688d0","id":"./src/gitmine.h::rw::resolveSinceScope","id_addressable":true,"id_collides_with":0,"l":317,"ord":0,"overloads":1,"amb":3,"cx":7,"ccx":7,"churn":56,"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":350,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":18,"tested":0}, +{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":684,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":395,"tested":0}, +{"p":"./src/taskroute.h","n":"testCoverageTaskChoice","scope":"rw::taskroute","key":"bf72991aa28355c7","id":"./src/taskroute.h::rw::taskroute::testCoverageTaskChoice","id_addressable":true,"id_collides_with":0,"l":1365,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":34,"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":5103,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":275,"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":571,"ord":0,"overloads":1,"amb":16,"cx":10,"ccx":5,"churn":43,"tested":0}], +"files":[{"p":"./src/crossref.h","symbols":1,"churn":48,"ccx":23,"hotspot_rank":26}, +{"p":"./src/darkflags.h","symbols":1,"churn":28,"ccx":8,"hotspot_rank":44}, +{"p":"./src/docdrift.h","symbols":3,"churn":31,"ccx":12,"hotspot_rank":30}, +{"p":"./src/gitmine.h","symbols":2,"churn":56,"ccx":9,"hotspot_rank":17}, +{"p":"./src/main.cpp","symbols":2,"churn":395,"ccx":12,"hotspot_rank":4}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":275,"ccx":2,"hotspot_rank":5}, +{"p":"./src/pageview.h","symbols":1,"churn":18,"ccx":2,"hotspot_rank":126}, +{"p":"./src/taskroute.h","symbols":2,"churn":34,"ccx":22,"hotspot_rank":39}, +… [72 more display lines; full output is 20587 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: 5.05s** + +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":"ce5c0b3fc","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2345,"symbols":22606,"edges":31600,"ambiguous":9496,"unresolved":12841},"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":2291,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":31,"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":630,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":275,"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":3092,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":394,"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":2792,"ord":0,"overloads":1,"amb":12,"cx":14,"ccx":15,"churn":31,"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":2331,"ord":0,"overloads":1,"amb":12,"cx":22,"ccx":35,"churn":31,"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":284,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":31,"tested":0}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","key":"904831e5c0f66b39","id":null,"id_addressable":false,"id_collides_with":0,"l":1649,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":65,"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/mcprefusal.h","n":"kMcpRequiredFields","scope":"rw::mcprefuse","key":"c5f1ad5fc3a12368","id":"./src/mcprefusal.h::rw::mcprefuse::kMcpRequiredFields","id_addressable":true,"id_collides_with":0,"l":66,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"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":2622,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":31,"tested":0}], +"files":[{"p":"./src/cli.h","symbols":1,"churn":394,"ccx":0,"hotspot_rank":6}, +{"p":"./src/docdrift.h","symbols":6,"churn":31,"ccx":51,"hotspot_rank":30}, +{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":81}, +{"p":"./src/mcprefusal.h","symbols":1,"churn":36,"ccx":0,"hotspot_rank":55}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":275,"ccx":0,"hotspot_rank":5}, +{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":45}, +{"p":"./src/verbs_change.h","symbols":1,"churn":65,"ccx":4,"hotspot_rank":21}], +"symbols_total":3040,"symbols_capped":true},"blast_radius":{"reaches":12,"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":12,"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":7,"module_span":7,"max_ccx":35,"sum_ccx":59,"ambiguous_calls":26,"blast_reaches":12,"contract_touches":1,"conflicts":3,"untested":12,"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":106,"l":258,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":6," … [line truncated: 11 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":2716,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":394,"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":3092,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":394,"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":2795,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":394,"tested":0}, +{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":106,"l":858,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":16,"tested":0}, +{"p":"./src/slice.h","n":"sliceRdIterationCeiling","scope":"slicev","key":"449132b302bb4324","id":"./src/slice.h::slicev::sliceRdIterationCeiling","id_addressable":true,"id_collides_with":0,"l":1372,"ord":0,"overloads":1,"amb":0,"cx":9,"ccx":12,"churn":51,"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":303,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"tested":0}, +… [64 more display lines; full output is 19268 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.* + +**wall time: 1.29s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --layout=Lang` + +*The honest refusal (exit 1): Lang is an `enum class`, not a struct — no offsets are fabricated.* + +**exit code: 1** — **wall time: 1.53s** + +````` +(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: 3.09s** + +````` + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +… [234 more display lines; full output is 24192 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.93s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [261 more display lines; full output is 25429 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: 3.06s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [216 more display lines; full output is 24368 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --from-trace=-` + +*Map a pasted stack trace onto indexed symbols. CHANGED: in_corpus= now reports the real count (was 0).* + +**wall time: 1.80s** + +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:4434 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4475 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1625 + #3 0x102e01a44 in main src/main.cpp:3935 + #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 5100 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.* + +**wall time: 1.25s** + +````` + + + + + + + + + + + gate scripts' claim (~line 1305) is NOT enforced — the derived-vs-stated sibling loop here covers docs/EVALS.md only. It drifted 407→451 unnoticed (fixed 2026-08-23). To close: grep both files ('file:line:' parsing) in the gateCountClaims arm.]]> + + + + +````` + +## `./build/ripwire . --pack-task="add a new output format flag to the CLI"` + +*ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* + +**wall time: 2.65s** + +````` + + + + +Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept + +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: 104 more bytes on this line] + +knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedBy makes in full above, which applies here verbatim: firstFlagOutside() walks the rows parseArgs itself minline constexpr std::string_view kInRideAlong[] = + +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 +struct AgentTarget + +verifier N2/N3/N11: the bad-VALUE refusal tablestruct McpValueSpec + + + + + + + + + +… [55 more display lines; full output is 9793 bytes on 47 raw line(s)] +````` + +## `./build/ripwire . --pack-task="add a new output format flag to the CLI" --partition=3` + +*Fan-out form: one shared core + 3 per-agent slices carved along call-graph communities.* + +**wall time: 2.26s** + +````` + 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. --> + + + + + +Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept + +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 std::vector<std::string>& verbLines… + +knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedB…inline constexpr std::string_view kInRideAlong[] = + +F7: a hostile/garbled frame line number (e.g. a fuzzed or truncated trace) can exceed UINT32_MAX…inline std::uint32_t toUint( std::string_view s, bool& overflowed ) 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] + +What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure +inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) +struct RankedGraph +inline std::uint32_t sliceRdIterationCeiling() noexcept +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 … [line truncated: 7 more bytes on this line] +struct PageRankRun +inline std::uint32_t navRelevanceWeight( const Graph& g, NodeId n ) noexcept +COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "s… +inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) +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", … [line truncated: 90 more bytes on this line] +inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window +inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] +### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] +## Background — read these, in this order +## Design space and constraints +… [61 more display lines; full output is 9883 bytes on 10 raw line(s)] +````` + +## `./build/ripwire . --export=cc.json:/aux/ripwire2.cc.json` + +*Per-file metrics as CodeCharta cc.json.* + +**wall time: 3.81s** + +````` +(empty) +````` + +Artifact written: + +````` +353194 /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.09s** + +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 McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vec… +inline const McpIndex& getIndex( const std::string& root ) +struct McpIndex +inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) +### See the map — not just the numbers +inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… +inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) +inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, const CacheLoadStats& cacheStats … [line truncated: 59 more bytes on this line] +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 respectGi … [line truncated: 27 more bytes on this line] +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) +static bool cachePathIsDirectory( const std::string& cachePath ) +inline constexpr std::array<std::string_view, 8> kCacheRuleNames = +… [57 more display lines; full output is 23924 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: 5.81s** + +````` + + + + + + + + + + +````` + +stderr: + +````` +ripwire --doctor: no ripwire resolves from PATH. To put this binary's directory first (for this shell; put the same line in your shell rc file), paste this line: +export PATH='./build':"$PATH" +````` + + +--- + +# 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: 3.25s** + +````` + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 1.11s** + +````` + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --rank-by=bogus --top-k=5` + +*An unknown value REFUSES (exit 1), NAMED, with the supported set listed.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf|churn|churn-decay) +````` + +## `./build/ripwire . --callers=rankGraphTeleport --format=columnar` + +*Columnar output: paths table + parallel arrays, ~15-60% fewer tokens on MANY-row lists — small results can be LARGER (the columnar legend is a fixed cost).* + +**wall time: 1.65s** + +````` + + +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 +1087,4473,5097,171,1379,1414,1623 +fn,fn,fn,fn,fn,fn,fn +0,0,0,0,0,0,0 + + +````` + +## `./build/ripwire . --for="cache invalidation" --format=candidates --top-k=5` + +*Flat top-K export for an external reranker.* + +**wall time: 2.02s** + +````` + + + +inline std::string spanTierMemoPath( const std::string& diskPath ) + + +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vector<NodeId>& surfaceIds ) + + +### See the map — not just the numbers + + +inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) + + +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) + + +````` + +## `./build/ripwire . --callers=rankGraphTeleport --format=bogus` + +*An unknown --format value REFUSES (exit 1), named, with the supported set listed.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidates) +````` + +## `./build/ripwire . --callers=rankGraphTeleport --json` + +*Machine-parseable JSON, same content, keys mirror the XML attrs.* + +**wall time: 1.28s** + +````` +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1087"}, +{"t":"fn","n":"rankGraph","p":"src/graph.h:4473"}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:5097"}, +{"t":"fn","n":"runEval","p":"src/eval.h:171"}, +{"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1379"}, +{"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1414"}, +{"t":"fn","n":"runDefaultMap","p":"src/main.cpp:1623"}]} +````` + +## `./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: 3.58s** + +````` + + + + + + + +````` + +## `./build/ripwire . --ignore-tests --top-k=5` + +*Drop test paths from the corpus before ranking.* + +**wall time: 1.09s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --exclude=present --exclude=bench --top-k=5` + +*Drop matching paths (repeatable) before ranking.* + +**wall time: 1.17s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.83s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --no-cache --top-k=3` + +*Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm cost.* + +**wall time: 20.09s** + +````` + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3` + +*Explicit incremental cache at a path OUTSIDE the repo (first call writes it).* + +**wall time: 19.81s** + +````` + + + + + + + + + + + + + + + + + +````` + +Artifact written: + +````` +15801018 /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).* + +````` + + + + + + + + + + + + + + + + + +````` + +## `./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: 1.85s** + +````` + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --eval` + +*Self-eval: co-change recall vs BM25.* + +**wall time: 28.78s** + +````` +ripwire --eval (co-change recovery, averaged over 80 historical commits) + ranker recall@5 recall@10 recall@20 + ripwire 6.4% 8.2% 16.8% + BM25 17.6% 20.8% 24.2% + BM25sub 20.0% 27.5% 30.5% + BM25body 20.9% 32.4% 40.8% + fused 13.4% 19.0% 36.1% + anchored 20.9% 32.4% 40.8% + same-dir 0.0% 7.1% 11.2% + random 0.2% 0.4% 0.9% <- floor (random ranking over F=2345 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: 232.24s** + +````` +ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) + sample: population=5277 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.697 56.3% 86.5% 91.0% + subtoken doc-phrase 0.921 90.0% 94.2% 95.0% + name-exact name 0.909 84.4% 96.2% 97.5% + name-exact doc-phrase 0.010 0.3% 1.7% 2.4% + anchored name 0.703 57.8% 85.9% 89.7% + anchored doc-phrase 0.918 89.6% 94.0% 94.7% + routed name 0.912 84.6% 96.4% 97.8% + routed doc-phrase 0.919 90.0% 94.0% 94.8% + note: routing chose name-exact on 3985/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: 1** — **wall time: 2.87s** + +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) +upstream-paging merged +no-such-ref-1 unmerged +no-such-ref-2 merged +````` + +````` +(empty) +````` + +stderr: + +````` +ripwire: --eval-stray: 2 labelled ref(s) do not exist in . -- not merged, just absent: no-such-ref-1, no-such-ref-2 (fix the labels file or add the ref) +````` + +## `./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.690 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.690 0.667 75.0% (th=2.958) + 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.681) + 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, 3621 bytes total] +````` + +## `./build/ripwire wrap claude` + +*Print the recipe to wire ripwire into Claude Code as an MCP server.* + +````` +# ripwire -> Claude Code (CLI-first) +# NOTE: nothing on PATH is named ripwire right now, so the command below is this binary's absolute path; +# put its directory on PATH (the installer printed the export line) and the bare word works too. +# 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: +./build/ripwire . --for="" --token-budget=2000 +# +# ...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 -- ./build/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`. +… [16 more lines, 4420 bytes total] +````` + +## `./build/ripwire --version` + +*Version + short build info.* + +````` +ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=67cf52baa+dirty) +````` + + +--- + +# navigate — seeds, claims, slices, shapes + +## `./build/ripwire . --at=src/graph.h:4434` + +*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.61s** + +````` + + + + +````` + +## `./build/ripwire . --callers=@src/graph.h:4434` + +*The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* + +**wall time: 1.26s** + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --at=src/graph.h:999999` + +*A seed past the end of the file — the refusal shape for a faulted location.* + +**exit code: 1** — **wall time: 1.16s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 7993 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.83s** + +````` + + + + + +````` + +## `./build/ripwire . --verify="unused(rankGraphTeleport)"` + +*A claim that is FALSE — the refuted shape, with the references that refute it.* + +**wall time: 1.90s** + +````` + + + + + + + + + + + + +````` + +## `./build/ripwire . --verify="contains(src/graph.h, \"no such literal anywhere\")"` + +*A literal-scan absence: refuted only with complete= evidence, never on a partial scan.* + +**wall time: 1.99s** + +````` + + + +````` + +## `./build/ripwire . --verify="frobnicate(x)"` + +*An unparseable claim — the refusal names the accepted shapes.* + +**exit code: 1** — **wall time: 1.96s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. The claim language is CLOSED; the shapes are: calls(A, B) does A transitively call B · uses(SYM) is SYM referenced anywhere · unused(SYM) is SYM referenced nowhere · contains(FILE, "LITERAL") do FILE's bytes contain t … [line truncated: 218 more bytes on this line] +````` + +## `./build/ripwire . --slice=rankGraphTeleport` + +*Bare --slice=SYM: the INVENTORY of sliceable locals (), so a caller can pick VAR.* + +**wall time: 1.44s** + +````` + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --slice=rankGraphTeleport:teleport` + +*Intra-procedural def-use slice of ONE variable: one row per line touching it, k=def|use|both, reaching definitions flow-sensitive (reach=cfg).* + +**wall time: 1.73s** + +````` + + + + + + + teleport( pw.begin(), pw.end() );]]> + + + + + + + + +````` + +## `./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=back --slice-depth=3` + +*TRANSITIVE backward value-flow from the seed variable, bounded BFS (depth= disclosed; a cut frontier says flow_truncated=1).* + +**wall time: 1.83s** + +````` + + + + + + + teleport( pw.begin(), pw.end() );]]> + + + + + + + + + pw = biasPrior( g, p );]]> + + +& p, float alpha = 0.85f )]]> + + +& p, float alpha = 0.85f )]]> + + +````` + +## `./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd` + +*Forward flow: which statements the seed's value reaches, at the default depth bound.* + +**wall time: 1.92s** + +````` + + + + + + + teleport( pw.begin(), pw.end() );]]> + + + + + + + + + + + + + + + + + + + + + 0.0 )]]> + + + + + + + + +````` + +## `./build/ripwire . --slice=rankGraphTeleport:nosuchvar` + +*A variable the definition does not bind — the refusal shape, naming the inventory.* + +**exit code: 1** — **wall time: 1.25s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --slice: no occurrence of 'nosuchvar' in rankGraphTeleport — sliceable locals: alpha, g, p, pw, N, teleport, rankDouble, run, teleportMass, value, inverseMass, r (bare --slice=rankGraphTeleport lists them with first-def lines) +````` + +## `./build/ripwire . --slice-depth=3` + +*--slice-depth without --slice-flow is refused loudly rather than silently ignored.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire --slice=parseArgs:argIndex --slice-flow=fwd --slice-depth=4) +````` + +## `./build/ripwire . --slice=rankGraphTeleport:teleport --legend=compact` + +*The compact legend posture: rows byte-identical, a versioned schema id replaces the repeated explanatory prose — for a many-small-calls loop.* + +**wall time: 1.35s** + +````` + + + + + + + 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: 11.73s** + +````` + +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: 11.82s** + +````` + +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( 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( out, CMakeScan::DisclosureWhy::SymlinkEscapesRoot, "flags: a CMake file's symlink target leaves the root — f +DISCLOSE( out, CMakeScan::DisclosureWhy::RootWalkFailed, "flags: cannot walk root for CMake files — cmake gates omitte +DISCLOSE( r, Result::DisclosureWhy::BaseTreeUnavailable, "dmm: the base commit's tree could not be materialized or parse +DISCLOSE( r, Result::DisclosureWhy::TargetTreeUnavailable, "dmm: the target commit's tree could not be materialized or p +DISCLOSE( out, RepoPaths::DisclosureWhy::SymlinkEscapesRoot, "doc-drift: a file's symlink target leaves the root — fil +DISCLOSE( out, RepoPaths::DisclosureWhy::RootWalkFailed, "doc-drift: cannot walk the root — the on-disk existence prob +DISCLOSE( res, DriftResult::DisclosureWhy::UnreadableDoc, "doc-drift: cannot read a markdown file — its anchors are om +… [73 more display lines; full output is 17692 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --pattern='x'` + +*A pattern that collapses to a bare token is REFUSED — never reported as hits=0.* + +**exit code: 1** — **wall time: 1.36s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --pattern: this pattern collapses to a single bare token, which is a TEXT search, not a structural one — a clean parse is not enough (a bare word parses fine in most grammars and means nothing structural). Give it a shape (foo($X), $A + $B, if ($C) { ... }) or use the grep flag for litera … [line truncated: 176 more bytes on this line] +````` + +## `./build/ripwire . --grep=DISCLOSE --and=cache` + +*Boolean grep: hits where BOTH literals share the matched line (--grep-scope=line is the default).* + +**wall time: 1.68s** + +````` + + + +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 +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::array<LangEntry, 51> kLangTable = +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +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 ) +std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +constexpr std::uint32_t kMaxYamlNestDepth = 64u +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 bool hasMainGuard( std::string_view source ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +struct AstQuerySpec +inline bool spanTierMemoTryLoad( bool useMemo, const std::string& diskPath, const StatInfo& now, SpanTierMap& out ) +… [55 more display lines; full output is 8998 bytes on 1 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: 2.96s** + +````` + + + + +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 +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::array<LangEntry, 51> kLangTable = +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +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 ) +std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +constexpr std::uint32_t kMaxYamlNestDepth = 64u +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 bool hasMainGuard( std::string_view source ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +struct AstQuerySpec +inline bool spanTierMemoTryLoad( bool useMemo, const std::string& diskPath, const StatInfo& now, SpanTierMap& out ) +… [90 more display lines; full output is 12573 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: 3.00s** + +````` + + + + +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) + +runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + +H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +struct QualityDeltaOutcome +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string qualityAcksPath( const std::string& root ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) +inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) +… [52 more display lines; full output is 9271 bytes on 1 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: 3.03s** + +````` + + + + +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) + +runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + +H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +struct QualityDeltaOutcome +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string qualityAcksPath( const std::string& root ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) +inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) +… [53 more display lines; full output is 9444 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --safe-delete=rankGraphTeleport` + +*"Can I delete this?" — callers + transitive impact + every use site + how much of the radius is tested, composed in ONE call; risk= names what was found, never a verdict.* + +**wall time: 1.70s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --safe-delete=DoesNotExist` + +*Unknown-symbol refusal shape for --safe-delete.* + +**exit code: 1** — **wall time: 1.17s** + +````` +(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: 3.98s** + +````` + + + + + + + + + + + + + +````` + +## `./build/ripwire . --handoff --token-budget=1200` + +*The same packet under a hard ceiling: heuristic rows drop tail-first (withheld= disclosed), verified rows never drop.* + +**wall time: 4.16s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [388 more display lines; full output is 41521 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --no-ignore --top-k=3` + +*Crawl paths the repo's own .gitignore covers (default honours it and discloses ignored_files=/ignored_dirs= only when it dropped anything — this repo's crawl drops nothing, so the header is identical to the default map's; --skipped's ignore_mode= says which rule applied).* + +**wall time: 1.36s** + +````` + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --no-stable --top-k=3` + +*--no-stable outside --mcp: what the flag does (or says) when there is no stable-by-default ordering to opt out of.* + +**wall time: 1.16s** + +````` + + + + + + + + + + + + + + + + + +````` + +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.60s** + +````` + + + + + + const&, float) src/graph.h:4434 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4475 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1625 + #3 0x102e01a44 in main src/main.cpp:3935 + #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: 480 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.32s** + +````` + + + + +````` + +## `./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: 3.59s** + +````` + + + + +````` + +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: 16.02s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [186 more display lines; full output is 15779 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --biggest-first --limit=8` + +*Per-function size ranking, largest Halstead volume/token entropy/lines first — a size proxy, not a readability order (withdrawn, docs/EVALS.md §8) — a RANKING lens, not a grade.* + +**wall time: 2.04s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --comment-coherence --limit=8` + +*Functions WITH a doc comment, most name-restating first: c_coeff (high = the comment repeats the name) and cic (Jaccard of comment vs identifier vocabulary), both reported, never collapsed.* + +**wall time: 2.10s** + +````` + + + + + + + + + + + +````` + +## `./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.88s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [21 more display lines; full output is 11206 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: 8.16s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [41 more display lines; full output is 7592 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: 9.03s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [35 more display lines; full output is 7376 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: 7.81s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [502 more display lines; full output is 45114 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: 7.15s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --naming-consistency --limit=8` + +*The corpus's OWN case-convention vote per (language, kind) group; off-convention names get a mechanical propose= (a suggestion, never a blind rename).* + +**wall time: 1.80s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 4253 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: 29.97s** + +````` + + + + + + + + + + + +

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+… [95 more display lines; full output is 10960 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: 21.55s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [642 more display lines; full output is 72295 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint-catalog` + +*The built-in rule registry — one row per rule with sev=/category=/rationale/lang=/since=; no corpus needed.* + +**wall time: 1.42s** + +````` + + +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 7764 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=861226192676dedb entries=733 +: 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, +… [704 more display lines; full output is 71919 bytes on 734 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: 15.52s** + +````` + + + + + + + + + + +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] ] +… [480 more display lines; full output is 57948 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: 15.95s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + +0.5 +1023u +main (90 lines) +20 +delta < 0.02 && delta > -0.02 +0.02 +… [734 more display lines; full output is 65543 bytes on 1 raw line(s)] +````` + +## `./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: 15.78s** + +````` +(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: 16.27s** + +````` +(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: 15.97s** + +````` +{"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}}, +… [9735 more display lines; full output is 1361443 bytes on 1 raw line(s)] +````` + +Parsed summary of the same SARIF (past the display cut): + +````` +sarif 2.1.0 rules= 39 results= 4863 +````` + +## `./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: 8.08s** + +````` + + +

+

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

+

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

+

+ +````` + +## `./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: 3.69s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [675 more display lines; full output is 44406 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: 3.31s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 3.78s** + +````` +(empty) +````` + +Artifact written: + +````` +158930 /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: 41.44s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (15801018 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (34998856 bytes, rich family) +````` + +Artifact written: + +````` +15801018 /aux/ci_index.lean.ripwirecache +34998856 /aux/ci_index.rich.ripwirecache +50799874 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.54s** + +````` + + + + + + + + + + + + + + + + + +````` + +## `./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.42s** + +````` + + + + + + + + + + + + + + + + + +````` + +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. +106217 /aux/pin_census.tsv +````` + +## `./build/ripwire . --plan-lint=test/planlintfix/wave.md` + +*The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when a card or ledger row gates.* + +**exit code: 2** — **wall time: 1.70s** + +````` + + + + + + + + + + + + + +````` + +## `./build/ripwire . --plan-lint=test/planlintfix/wave_ledger.md` + +*The ledger-shaped fixture through the same check (exit 2: one gating card).* + +**exit code: 2** — **wall time: 1.76s** + +````` + + + + + + + +````` + +## `./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: 4.40s** + +````` + + + + + + + + + + + + + +````` + +stderr: + +````` +ripwire --doctor: no ripwire resolves from PATH. To put this binary's directory first (for this shell; put the same line in your shell rc file), paste this line: +export PATH='./build':"$PATH" +````` + +## `./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: 22.90s** + +````` +ripwire situational-awareness — 1 changed file(s), 13 symbols in them +root: . +at: ce5c0b3fc+dirty + [1] blast radius: 182 symbols across 43 files transitively depend on these changes (showing 8 of 43 files — shown=8 total=43 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=43) + counts_floor=1 graph_ambiguous=9499 graph_unresolved=12841 graph_unindexed=231 (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 (32 dependent symbols) + src/slice.h (30 dependent symbols) + src/serialize.h (9 dependent symbols) + src/quality.h (8 dependent symbols) + src/verbs_for.h (8 dependent symbols) + src/main.cpp (7 dependent symbols) + src/verbs_quality.h (7 dependent symbols) + src/mergescout.h (6 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=714 — 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="3581": + (none — 3581 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: 4.70s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 94.44s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [105 more display lines; full output is 14346 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, but the ~4 KB prose legend the compact default trims is restored: the posture a human reads once to learn the schema.* + +**exit code: 2** — **wall time: 20.52s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [105 more display lines; full output is 18907 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: 19.47s** + +````` +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":123,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"ce5c0b3fc+dirty","renames":57,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 187 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":"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"}, +{"kind":"api-surface:new-symbol","key":"995375dfa4e63104","why":"target-gone"}, +… [102 more display lines; full output is 13059 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: 12.95s** + +````` +(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, 232 re-derived, 1 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1333 ineligible (no cur … [line truncated: 198 more bytes on this line] +ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written +````` + +## `./build/ripwire . --quality-delta --quality-ack --ack-only=api-surface` + +*NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* + +**wall time: 12.78s** + +````` +(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, 232 re-derived, 1 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1333 ineligible (no cur … [line truncated: 198 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: 20.18s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [104 more display lines; full output is 14049 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: --quality-delta gating: 4 preexisting-worse major finding(s); first: complexity src/infra/sortutil.h::rw::sortutil::lessByScoreDescId at src/infra/sortutil.h:49 (was=1 now=67) +````` + +## `./build/ripwire . --ack-only=gating` + +*--ack-only WITHOUT --quality-ack REFUSES loudly (exit 1, the pairing named) — it used to be silently ignored.* + +**exit code: 1** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire

--quality-delta --ack-only=contract-change --quality-ack="reason") +````` + +## `./build/ripwire . --edit-check=nonNegativeFloatDescKey` + +*A real contract-change: was=1 now=2 params, with the call sites that are now provably incompatible, and a pasteable next= (--uses=SYM) on the root.* + +**wall time: 2.09s** + +````` + + + + + + +````` + +## `./build/ripwire . --edit-check=nonNegativeFloatDescKey --legend=full` + +*The same verdict under --legend=full: every row identical to the compact default above, but the ~5.7 KB explanatory legend is restored — the posture for a first-time reader.* + +**wall time: 1.98s** + +````` + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*The review-evidence bundle with an actual changed file.* + +**wall time: 6.66s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [85 more display lines; full output is 8317 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.81s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [37 more display lines; full output is 4185 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --clones` + +*The duplicated helper the sandbox edit introduced shows up as a clone group.* + +**wall time: 3.58s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [307 more display lines; full output is 16171 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --stray-content=zz-orphan` + +*CHANGED: a ref with NO merge base with HEAD now reports v="unknown" ok="0" in its own bucket — the absence of an answer, never a claim it is merged. (The sandbox carries a deliberately parentless branch built with `git commit-tree`; a shallow CI clone puts every ref here.)* + +**wall time: 2.44s** + +````` + + + +````` + +## `./build/ripwire . --stray-content=zz-orphan --plan` + +*CHANGED: --plan surfaces those same refs as an row rather than silently dropping them.* + +**wall time: 2.44s** + +````` + + + +````` + +## `./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: 8.70s** + +````` + + +

+

-

-

-... [13 more line(s); run it to see the whole thing] + +

+

+

+... [14 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -2058,20 +2038,20 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - - - - - - + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2095,9 +2075,9 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - - - + + + @@ -2105,10 +2085,10 @@ $ ./build/ripwire . --lint --naming-locals - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2130,16 +2110,16 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - - - - - + + + + + + + + + - - - - ``` @@ -2163,19 +2143,17 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - - - - - - - + + + + + + - - - - - + + + + ... [22 more line(s); run it to see the whole thing] ``` @@ -2197,19 +2175,17 @@ _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] ``` @@ -2225,20 +2201,20 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - - - - - - - - + + + + + + + + + - - + + - - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2254,20 +2230,20 @@ _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] ``` @@ -2291,9 +2267,9 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes ``` $ ./build/ripwire . --arch=test/archfix/rules.txt - - - + @@ -2321,9 +2297,9 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes ``` $ ./build/ripwire . --arch=test/archfix/rules.txt - - - + @@ -2347,9 +2323,9 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes ``` $ ./build/ripwire . --arch=test/archfix/rules.txt - - - + @@ -2375,9 +2351,9 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - - - + + + @@ -2385,10 +2361,10 @@ $ ./build/ripwire . --lint - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2411,8 +2387,8 @@ _The built-in rule registry — one row per rule with sev=/category=/rationale/l ``` $ ./build/ripwire . --lint-catalog - - + + 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) @@ -2421,8 +2397,7 @@ $ ./build/ripwire . --lint-catalog 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 -... [20 more line(s); run it to see the whole thing] +... [21 more line(s); run it to see the whole thing] ``` **Shaped by:** `--lint` @@ -2437,8 +2412,8 @@ _User lint rules (YAML, ast-grep style) from a directory._ ``` $ ./build/ripwire . --lint-rules=test/lintrulesfix/rules - - + + use LOG() instead of printf @@ -2485,9 +2460,9 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - - - + + + @@ -2495,10 +2470,10 @@ $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2571,20 +2546,20 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - - - - + + + + + + + + + + + - - - - - - - ... [17 more line(s); run it to see the whole thing] ``` @@ -2602,10 +2577,9 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - - - + + ``` @@ -2628,20 +2602,20 @@ _Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels ``` $ ./build/ripwire . --zoom - - - - - + + + - - - + + - + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2666,16 +2640,16 @@ $ ./build/ripwire . --report # ripwire architecture report -2039 files · 18699 symbols · 22330 edges · 1313 modules (11420 call-graph isolated) +2345 files · 22606 symbols · 31600 edges · 2188 modules (10489 call-graph isolated) Root: `.` -Call-graph isolate provenance: 2633 declaration, 1162 header, 3670 source, 3955 document; 0 connected Louvain singletons +Call-graph isolate provenance: 2793 declaration, 1471 header, 1804 source, 4421 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 1313) -- **src::assign@infra/svector.h:342:19905 [resolve,compute,collect]** — 652 symbols -- **src::emplace@infra/svector.h:408:22477 [resolve,parse,run]** — 617 symbols -- **src::emitTo@infra/emit.h:51:2620 [write,run,emit]** — 377 symbols +## Modules (call-graph clusters; showing 12 of 2188) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1303 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,add,collect]** — 1024 symbols +- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 516 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2691,20 +2665,20 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - - - - - - - - - + + + + + + + - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2723,21 +2697,21 @@ $ ./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"] - n98["src
5422"] - n99["src/infra
630"] + n101["src
6405"] + n102["src/infra
1250"] end subgraph sg1 ["test"] - n100["test
3546"] - n269["test/fixtures/recallpassage
314"] - n255["test/expandmodefix
151"] - n257["test/expandsibsfix
149"] - n315["test/massfix
77"] - n297["test/legofix
60"] - n335["test/optremarksfix
59"] + n103["test
4696"] + n294["test/fixtures/recallpassage
314"] + n280["test/expandmodefix
151"] + n282["test/expandsibsfix
149"] + n411["test/rubyattrsfix
85"] + n348["test/massfix
77"] + n314["test/javamethodreffix
62"] ... [17 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--zoom`, `--with-graph`, `--legend`, `--limit`, `--max-memory` +**Shaped by:** `--zoom`, `--with-graph`, `--legend`, `--limit` ### `--owners[=SYM]` @@ -2751,20 +2725,20 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - - - - - - - - - - + + + + + + + + + + + + + - - ... [17 more line(s); run it to see the whole thing] ``` @@ -2774,17 +2748,16 @@ $ ./build/ripwire . --owners **Answers:** list internal functions with no caller anywhere in the indexed tree internal source functions with no caller found in the indexed tree — a name-based graph reading, not a confidence score (dynamic dispatch, reflection and macro-generated callers are invisible to it); -=DIR scopes to whole path components (dir or filename) and REFUSES a filter that names nothing indexed. A symbol whose definition is produced by a SELF-REGISTERING test/benchmark macro is never reported: doctest TEST_CASE/ TEST_CASE_FIXTURE/SCENARIO, gtest TEST/TEST_F/TEST_P, Catch2, Google Benchmark — a static initializer registers them, so a name-based call graph cannot see the caller and every one of them would be a false positive. Extend the list for your own framework with `.ripwire_config`'s one key, `register_macros = NAME[, NAME...]` (one directive per line, # comments). The exemption is DISCLOSED, never silent: register-macro-excluded="N" rides the report and prints even at 0. Exempt from dead-code only — such a symbol still participates in clone detection. A function a table, field or argument holds as a VALUE (--callers' rows) is not reported either: value-ref-excluded="N" counts them, absent at 0 (matched by name, not a proven call). A LEADING ./ anchors DIR at the repo ROOT (=./src matches only the top-level src/ subtree); a bare name (=src) matches that component ANYWHERE in the tree, including nested (test/fixture/src/…) +=DIR scopes to whole path components (dir or filename) and REFUSES a filter that names nothing indexed. A symbol whose definition is produced by a SELF-REGISTERING test/benchmark macro is never reported: doctest TEST_CASE/ TEST_CASE_FIXTURE/SCENARIO, gtest TEST/TEST_F/TEST_P, Catch2, Google Benchmark — a static initializer registers them, so a name-based call graph cannot see the caller and every one of them would be a false positive. Extend the list for your own framework with `.ripwire_config`'s one key, `register_macros = NAME[, NAME...]` (one directive per line, # comments). The exemption is DISCLOSED, never silent: register-macro-excluded="N" rides the report and prints even at 0. Exempt from dead-code only — such a symbol still participates in clone detection. A LEADING ./ anchors DIR at the repo ROOT (=./src matches only the top-level src/ subtree); a bare name (=src) matches that component ANYWHERE in the tree, including nested (test/fixture/src/…) **Try it** -_High-confidence internal functions with no caller. NOTE the filter is a path-COMPONENT match: 'src' matches any .../src/... segment; use ./src to pin the root directory._ +_Internal functions with no caller found in the index — a name-based graph reading, not a confidence score. NOTE the filter is a path-COMPONENT match: 'src' matches any .../src/... segment; use ./src to pin the root directory._ ``` $ ./build/ripwire . --dead-code=src - - - + + ``` @@ -2838,9 +2811,9 @@ $ ./build/ripwire . --quality-baseline --allow-dirty ### `--quality-delta` -**Answers:** before a PR: report ONLY what your change made worse, across 11 kinds — pair with --test-gate. +**Answers:** before a PR: report ONLY what your change made worse, across 10 kinds agent self-check before a PR (pair with --test-gate): report ONLY what a change made worse vs the baseline (10 kinds: complexity/verbosity/nesting/params/dup/dead/api-surface + error-masking/short-horizon-churn/new-clone-of-reused-helper); -Each kind is measured against the baseline (complexity/verbosity/nesting/params/dup/dead/api-surface + error-masking/short-horizon-churn/new-clone-of-reused-helper + placeholder); every finding is classified by ORIGIN: a symbol that EXISTED at the baseline and got worse (preexisting-worse="N", no attribute on the row) vs one that exists only because the code is NEW (new-symbol="N", origin="new-symbol" on the row). A small numeric delta is additionally sev="minor". EXIT 2 ONLY on preexisting-worse AND major AND unacked — the gating="N" header count. New-symbol rows are still PRINTED (they are the debt you are adding — read them), they just never gate; exit 0 means "nothing that already existed got worse", not "clean". Clone kinds classify by member set (new-symbol only if EVERY member is new); short-horizon-churn is preexisting by construction. LIMIT: origin is canonId (path::scope::name) identity, so a RENAMED/MOVED symbol reads as new and a regression carried in with the move will not gate. error-masking = a NEW empty/pass/comment-only handler, or log-only (a broad handler whose body only logs and never names the error) or rethrow-only (the sole handler re-throws it unchanged); those two gate only where their precision was measured (Python) and are sev="minor" in every other language. placeholder = a stub the change ADDED (todo!()/unimplemented!(), Kotlin TODO(), NotImplementedException, a bare raise NotImplementedError as a free function's body, a throw/raise/panic/assert saying "not implemented") or a comment line opening with TODO/FIXME that names no issue (#12, ABC-12, a URL); new-symbol by construction, so it never gates. Counted per enclosing symbol, like error-masking: a file-level TODO outside every definition is not counted. Test-fixture dirs + doc sections are exempt from dead-code/churn; churn needs COMMITTED thrash evidence (rewritten across recent commits AND again by this diff), never the current edit alone WHICH FLOOR IT COMPARES AGAINST, and a side effect: the sidecar is honored only when the sha it was pinned at EQUALS the current git HEAD (strict equality — an ancestor commit describes a DIFFERENT tree, so everything committed since would read as your regression). A sidecar pinned anywhere else is STALE: this verb then DELETES it from your working tree (self-heal, so the next run does not rediscover the dead pin) and auto-compares the working tree vs git HEAD instead. Re-pin with --quality-baseline. The read-only MCP quality_delta verb applies the SAME staleness test but never deletes. A sidecar at the current HEAD that ANOTHER ripwire build pinned (its producer stamp names other sources — a dead set depends on how calls were resolved) is FOREIGN: both arms ignore it, never delete it, and auto-compare vs git HEAD. Which floor was actually used is on every report as baseline=: sidecar | git-HEAD | git-HEAD (stale sidecar removed) | git-HEAD (stale sidecar ignored) | git-HEAD (foreign sidecar ignored) — the stale two say a stale sidecar existed, and 'removed' means the file is gone. A non-git root has no HEAD to fall back to, so its sidecar is honored whenever this build pinned it; without one there, or with another build's, the verb exits 1. +every finding is classified by ORIGIN: a symbol that EXISTED at the baseline and got worse (preexisting-worse="N", no attribute on the row) vs one that exists only because the code is NEW (new-symbol="N", origin="new-symbol" on the row). A small numeric delta is additionally sev="minor". EXIT 2 ONLY on preexisting-worse AND major AND unacked — the gating="N" header count. New-symbol rows are still PRINTED (they are the debt you are adding — read them), they just never gate; exit 0 means "nothing that already existed got worse", not "clean". Clone kinds classify by member set (new-symbol only if EVERY member is new); short-horizon-churn is preexisting by construction. LIMIT: origin is canonId (path::scope::name) identity, so a RENAMED/MOVED symbol reads as new and a regression carried in with the move will not gate. Test-fixture dirs + doc sections are exempt from dead-code/churn; churn needs COMMITTED thrash evidence (rewritten across recent commits AND again by this diff), never the current edit alone WHICH FLOOR IT COMPARES AGAINST, and a side effect: the sidecar is honored only when the sha it was pinned at EQUALS the current git HEAD (strict equality — an ancestor commit describes a DIFFERENT tree, so everything committed since would read as your regression). A sidecar pinned anywhere else is STALE: this verb then DELETES it from your working tree (self-heal, so the next run does not rediscover the dead pin) and auto-compares the working tree vs git HEAD instead. Re-pin with --quality-baseline. The read-only MCP quality_delta verb applies the SAME staleness test but never deletes. A sidecar at the current HEAD that ANOTHER ripwire build pinned (its producer stamp names other sources — a dead set depends on how calls were resolved) is FOREIGN: both arms ignore it, never delete it, and auto-compare vs git HEAD. Which floor was actually used is on every report as baseline=: sidecar | git-HEAD | git-HEAD (stale sidecar removed) | git-HEAD (stale sidecar ignored) | git-HEAD (foreign sidecar ignored) — the stale two say a stale sidecar existed, and 'removed' means the file is gone. A non-git root has no HEAD to fall back to, so its sidecar is honored whenever this build pinned it; without one there, or with another build's, the verb exits 1. **Try it** @@ -2848,20 +2821,20 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - -, acked. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). head_basis=identity: the floor is this tree's own snapshot; archived-index-hidden: refused, a tracked path is skip-worktree/assume-unchanged; absent: the archived HEAD tree. --> + + + + + + - - - - - - - - - + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2871,13 +2844,13 @@ $ ./build/ripwire . --quality-delta - New-symbol rows are still PRINTED (they are the debt you are adding — read them), they just never gate; - LIMIT: origin is canonId (path::scope::name) identity, so a RENAMED/MOVED symbol reads as new and a regression carried in with the move will not gate. -- error-masking = a NEW empty/pass/comment-only handler, or log-only (a broad handler whose body only logs and never names the error) or rethrow-only (the sole handler re-throws it unchanged); +- The read-only MCP quality_delta verb applies the SAME staleness test but never deletes. ### `--quality-delta=REV|A..B` -**Answers:** the same 11-kind report between two committed trees — a whole branch at once the same 11-kind report between two COMMITTED TREES instead of the working tree vs a baseline — the WAVE-level measurement (=A..B = tree B against tree A; +**Answers:** the same 10-kind report between two committed trees — a whole branch at once the same 10-kind report between two COMMITTED TREES instead of the working tree vs a baseline — the WAVE-level measurement (=A..B = tree B against tree A; -=REV = that commit against its FIRST PARENT; an EMPTY side of the range means HEAD). Same grammar --dmm= takes, and A...B is REFUSED rather than read as A..B. Use it to measure a whole integration branch at once (--quality-delta=..): per-lane checks each compare against their own baseline and cannot see a regression the WAVE introduced. Identical output contract to the bare form — same kinds, gating="N", exit 2, and the same .ripwire_quality_acks ratchet (acks are keyed root-relative, so a ledger recorded from working-tree runs applies unchanged). base_ref= and target_ref= disclose the two RESOLVED shas. No sidecar is read, written or deleted by this form, and at= is omitted: the two refs ARE the anchor. A==B is a legal, empty, exit-0 comparison. ONE KIND CANNOT BE MEASURED HERE and says so as churn="unavailable": short-horizon-churn needs git history at the tree being judged, and both trees are materialized OUT of the repo into temp dirs. The other 10 kinds are computed exactly as the bare form computes them. +=REV = that commit against its FIRST PARENT; an EMPTY side of the range means HEAD). Same grammar --dmm= takes, and A...B is REFUSED rather than read as A..B. Use it to measure a whole integration branch at once (--quality-delta=..): per-lane checks each compare against their own baseline and cannot see a regression the WAVE introduced. Identical output contract to the bare form — same kinds, gating="N", exit 2, and the same .ripwire_quality_acks ratchet (acks are keyed root-relative, so a ledger recorded from working-tree runs applies unchanged). base_ref= and target_ref= disclose the two RESOLVED shas. No sidecar is read, written or deleted by this form, and at= is omitted: the two refs ARE the anchor. A==B is a legal, empty, exit-0 comparison. ONE KIND CANNOT BE MEASURED HERE and says so as churn="unavailable": short-horizon-churn needs git history at the tree being judged, and both trees are materialized OUT of the repo into temp dirs. The other 9 kinds are computed exactly as the bare form computes them. **Try it** @@ -2885,20 +2858,20 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - -, acked. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). head_basis=identity: the floor is this tree's own snapshot; archived-index-hidden: refused, a tracked path is skip-worktree/assume-unchanged; absent: the archived HEAD tree. --> + + + + + + - - - - - - - - - + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2922,8 +2895,8 @@ _The Delta Maintainability Model scalar for the WORKING TREE vs HEAD — recorde ``` $ ./build/ripwire . --dmm - -

@@ -2992,8 +2965,8 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - -, acked. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. r surface=: the api-surface tier, new-symbol or contract-change. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. r sev=minor: a small numeric delta, counted in minor=, never gating (absent: major). r origin=new-symbol: the finding is on NEW code, never gating (absent: preexisting-worse). sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). r members=/tokens=: a duplication row's clone group (member ids) / their shared normalized-token count. --> + @@ -3022,19 +2995,19 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - + + - - - - - - + + + + + + ``` -**Shaped by:** `--edit-target-file`, `--slice`, `--at`, `--legend`, `--limit` +**Shaped by:** `--impact`, `--edit-target-file`, `--slice`, `--at`, `--legend`, `--limit` **Caveats (stated by the binary):** @@ -3077,11 +3050,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":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"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"}], -"order":"evidence","partners":0,"tests":4,"script_gates_unmodelled":675,"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +{"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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3097,11 +3071,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":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"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"}], -"order":"evidence","partners":0,"tests":4,"script_gates_unmodelled":675,"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true,"next":"bash test/greptiercheck.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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3147,11 +3122,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":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"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"}], -"order":"evidence","partners":0,"tests":4,"script_gates_unmodelled":675,"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +{"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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3210,11 +3186,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":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multi … [line truncated: 536 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"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: 485 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":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true}}]} +"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3258,20 +3234,20 @@ _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] ``` @@ -3295,21 +3271,21 @@ _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() );]]> - + - + - - -... [23 more line(s); run it to see the whole thing] +... [22 more line(s); run it to see the whole thing] ``` **Shaped by:** `--slice-depth` @@ -3350,11 +3326,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:5565 - - - - +$ ./build/ripwire . --at=src/graph.h:4434 + + + ``` @@ -3377,8 +3352,8 @@ _No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - - + + ``` @@ -3403,12 +3378,12 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - - + + + - + @@ -3482,9 +3457,21 @@ _Which refs of the one branch `main` (no ref family left to select on this check ``` $ ./build/ripwire . --stray-content=main - - - + + + + + + + + + + + + + + +... [4 more line(s); run it to see the whole thing] ``` **Shaped by:** `--plan`, `--abi`, `--whereis`, `--limit`, `--eval-stray` @@ -3507,9 +3494,21 @@ _Select the genuinely-unmerged refs of the one branch `main` (no ref family left ``` $ ./build/ripwire . --stray-content=main --plan - - - + + + + + + + + + + + + + + +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--stray-content`, `--legend`, `--json` @@ -3531,8 +3530,15 @@ _Cross-branch ABI-break gate over the one branch `main` (no ref family left to s ``` $ ./build/ripwire . --stray-content=main --abi - - + + + + + + + + ``` @@ -3546,7 +3552,7 @@ $ ./build/ripwire . --stray-content=main --abi **Answers:** find which branch's tree defines or mentions SYM, HEAD first which REF's tree defines or mentions SYM — HEAD first, then every local branch, with on-head="0" naming the case the verb exists for: content that lives only on a branch. -Each distinct blob is read ONCE (content-addressed), so N branches cost ~one tree. kind="def" on a HEAD row is the PARSED index's answer (head_labels="index"); on a REF row it is a LEXICAL heuristic — ref blobs are raw text, never ingested, so a doc quoting a signature still reads as a definition. head_labels="lexical" ⇒ HEAD fell back to that heuristic too (no indexed def of the name, or a working tree that drifted from HEAD). refs_scanned= is the SCAN denominator (refs read besides HEAD), not a matched count. Read-only; single-root only. A checkout that differs from HEAD is read from disk: each changed path's rows say ref="worktree" and replace HEAD's, at= gains +dirty, and worktree= says whether every one was read. LIMITS: a TREE scan finds only what some ref STILL carries, so hits="0" alone cannot tell a name this repo never had from one it deleted, and content dropped by every tree is invisible. Add --with-history: a row then says v="never" or v="removed" with the commit, date and file that removed it. Remote-tracking refs are excluded (they mirror local ones); refs are capped, narrow with --stray-content=SUBSTR. +Each distinct blob is read ONCE (content-addressed), so N branches cost ~one tree. kind="def" on a HEAD row is the PARSED index's answer (head_labels="index"); on a REF row it is a LEXICAL heuristic — ref blobs are raw text, never ingested, so a doc quoting a signature still reads as a definition. head_labels="lexical" ⇒ HEAD fell back to that heuristic too (no indexed def of the name, or a working tree that drifted from HEAD). refs_scanned= is the SCAN denominator (refs read besides HEAD), not a matched count. Read-only; single-root only. LIMITS: a TREE scan finds only what some ref STILL carries, so hits="0" alone cannot tell a name this repo never had from one it deleted, and content dropped by every tree is invisible. Add --with-history: a row then says v="never" or v="removed" with the commit, date and file that removed it. Remote-tracking refs are excluded (they mirror local ones); refs are capped, narrow with --stray-content=SUBSTR. **Try it** @@ -3554,14 +3560,13 @@ _Which ref's tree defines or mentions SYM — HEAD first, then every local branc ``` $ ./build/ripwire . --whereis=rankGraphTeleport - - - - - - -... [24 more line(s); run it to see the whole thing] + + + + + +... [25 more line(s); run it to see the whole thing] ``` **Shaped by:** `--with-history`, `--limit` @@ -3584,9 +3589,9 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat ``` $ ./build/ripwire . --flags - + - + @@ -3666,9 +3671,9 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - - + + @@ -3679,7 +3684,7 @@ $ ./build/ripwire . --field-affinity - + ... [17 more line(s); run it to see the whole thing] ``` @@ -3701,18 +3706,18 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - - - - - - - - - - - + + + + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3736,18 +3741,18 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - - - - - - - - - - - + + + + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3770,19 +3775,19 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - - - - - - - - - - - - + + + + + + + + + + + + + ... [18 more line(s); run it to see the whole thing] ``` @@ -3806,8 +3811,8 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - - + + @@ -3842,10 +3847,10 @@ $ ./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:3949 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:3447 - #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1441 - #3 0x102e01a44 in main src/main.cpp:3475 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:4434 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4475 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1625 + #3 0x102e01a44 in main src/main.cpp:3935 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) ==41337==ABORTING ``` @@ -3870,9 +3875,9 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc ``` $ ./build/ripwire . --run-trace="true" - - - + + + ``` @@ -3931,12 +3936,13 @@ _List all field notes (write-side memory) — the committed .ripwire_notes at th ``` $ ./build/ripwire . --notes - - + + + - @@ -3944,8 +3950,7 @@ $ ./build/ripwire . --notes 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.]]> - -... [1 more line(s); run it to see the whole thing] +... [2 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-redact` @@ -3962,19 +3967,21 @@ _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 - + 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: 104 more bytes on this line] - -knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedBy makes in full above, which applies here verbatim: firstFlagOutside() walks the rows parseArgs itself minline constexpr std::string_view kInRideAlong[] = - -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 -... [19 more line(s); run it to see the whole thing] +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--top-k`, `--token-budget`, `--for`, `--test-gate`, `--expand`, `--compress`, `--no-redact`, `--partition` @@ -3997,21 +4004,19 @@ _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. --> + + + - + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept - + 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 std::vector<std::string>& verbLines… - + knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedB…inline constexpr std::string_view kInRideAlong[] = - -... [17 more line(s); run it to see the whole thing] +... [19 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -4032,19 +4037,18 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - - - - - + + + + + +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 ) - -What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure - -What a rank call hands back: the vector, and the power iteration's own account of itself. Struct…struct RankedGraph - -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… -... [19 more line(s); run it to see the whole thing] + +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] + +... [20 more line(s); run it to see the whole thing] ``` **Shaped by:** `--partition` @@ -4080,7 +4084,7 @@ _One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE dedupe $ ./build/ripwire . --batch=/aux/batch2.txt for:incremental cache invalidation callers:rankGraphTeleport -grep:DEGRADED_PATH_ALERT +grep:DISCLOSE lego:Vehicle ``` @@ -4107,16 +4111,17 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - - - - - - - + + + + + + + ``` @@ -4140,20 +4145,19 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - - - - - - - - + + + + + + - - - - + + +... [3 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -4172,11 +4176,11 @@ _WHY a file is not in the index (oversize / excluded / unsupported-ext / gitigno ``` $ ./build/ripwire . --skipped - - + + - @@ -4203,9 +4207,7 @@ $ ./build/ripwire . --skipped ### `--scan-skill=FILE` -**Answers:** scan a single skill file before installing (any file, not just .md) EXFILTRATE:net-exfil (a network verb plus a $VAR or base64 on one fenced line) needs a destination: a verb named but not run, as in command -v curl, does not fire. - -It is CRITICAL only when a credential-shaped source is on that line: a credential-named var, an Authorization: header with a var, an env dump or a key file. Otherwise it is WARN and the row says why="no-cred-source". A sensitive file read fed into an upload (cat /etc/passwd | curl ... @-, curl -d @.env ...) is CRITICAL with or without a var: why="sensitive-read-upload". +**Answers:** scan a single skill file before installing (any file, not just .md) **Try it** @@ -4213,16 +4215,16 @@ _Scan a single skill file for injection/exfiltration patterns before installing. ``` $ ./build/ripwire --scan-skill=skills/ripwire-orient/SKILL.md - + + + ``` -**Shaped by:** `--scan-skills` - ### `--scan-skills[=DIR]` **Answers:** scan a skills directory before installing — every text file, .md and .sh alike scan DIR (or .agents/skills/ + ${CLAUDE_CONFIG_DIR:-~/.claude}/skills/ + ${CODEX_HOME:-~/.codex}/skills/). -EVERY text file, .md and .sh alike — a skill dir's executables are the files most worth scanning. skipped= counts what it could not scan (binary content, or unreadable); denylisted subtrees (.git, node_modules, build, ...) are not descended and the stderr tally says how many. The bare form never reads a positional root: one that is not the current directory is refused (exit 3, use --scan-skills=DIR), and its answer names the directories it walked in dirs= +EVERY text file, .md and .sh alike — a skill dir's executables are the files most worth scanning. skipped= counts what it could not scan (binary content, or unreadable); denylisted subtrees (.git, node_modules, build, ...) are not descended and the stderr tally says how many for vulnerabilities **Try it** @@ -4230,13 +4232,14 @@ _Scan a whole skills directory (exit 2 = CRITICAL, 1 = WARN). Explicit-DIR form ``` $ ./build/ripwire --scan-skills=skills - + + + ``` **Caveats (stated by the binary):** - skipped= counts what it could not scan (binary content, or unreadable); -- The bare form never reads a positional root: one that is not the current directory is refused (exit 3, use --scan-skills=DIR), and its answer names the directories it walked in dirs= ### `--force` @@ -4258,21 +4261,21 @@ _Rank by git change-frequency prior instead of PageRank._ ``` $ ./build/ripwire . --rank-by=churn --top-k=5 - - - - - - - - - - - + + + + + + + + - + -... [16 more line(s); run it to see the whole thing] + + + +... [7 more line(s); run it to see the whole thing] ``` **Shaped by:** `--since`, `--in` @@ -4293,25 +4296,25 @@ a trailing slash is ignored; absolute paths and '..' are refused). The global block stays byte-identical, a second page follows it — n=/of= are its counts (of= IS the total, so the paging half carries no total=), capped="1" has_more="1" next_offset= offset= limit= page it, and next= replays THIS run's own corpus flags (--since/--exclude) so the page it names is a page of the same answer. The symbol map collapses to a disclosed stub — the map was not asked for and was not ranked at all, which is why the header carries no pr_iters=. merge_bombs_skipped= stays on the global block: it counts the window's skipped commits, not the directory's._ ``` -$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - - - - - - - - - - - - - - -... [16 more line(s); run it to see the whole thing] +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~4 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 + + + + + + + + + + + + + + +... [5 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` +**Shaped by:** `--external-surface`, `--doctor`, `--legend` **Caveats (stated by the binary):** @@ -4365,12 +4368,12 @@ full restores the prose output legend posture for EVERY XML verb. The DEFAULT is **Try it** -_The same gating report under --legend=compact — same rows, same exit 2, schema="ripwire.quality-delta/v1", ~4 KB of legend down to one comment: the shape an agent's edit loop should run._ +_The same gating report under --legend=full — same rows, same exit 2, but the ~4 KB prose legend the compact default trims is restored: the posture a human reads once to learn the schema._ ``` -$ ./build/ripwire . --quality-delta --legend=compact - - + @@ -4398,7 +4401,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=04d7833c60d2d5bd entries=784 +ripwire legend dictionary ripwire.dict/v1 dictv=861226192676dedb entries=733 : 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=) @@ -4449,20 +4452,20 @@ _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] ``` @@ -4484,21 +4487,21 @@ _Drop matching paths (repeatable) before ranking._ ``` $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - - - - - - - - - - - + + + + + + + + + + - + + -... [10 more line(s); run it to see the whole thing] +... [6 more line(s); run it to see the whole thing] ``` **Shaped by:** `--skipped`, `--in`, `--index-out` @@ -4515,21 +4518,21 @@ _Full map re-ranked with teleport toward git-changed files — clean tree, so ch ``` $ ./build/ripwire . --map-diff --top-k=5 - - - - - - - - - - - - + + + + + + - -... [11 more line(s); run it to see the whole thing] + + + + + + + +... [6 more line(s); run it to see the whole thing] ``` **Shaped by:** `--json`, `--limit` @@ -4549,21 +4552,21 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ ``` $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - - - - - - - - - - - + + + + + + - + + + -... [2 more line(s); run it to see the whole thing] + + + +... [3 more line(s); run it to see the whole thing] ``` **Shaped by:** `--doctor`, `--index-out` @@ -4599,21 +4602,21 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co ``` $ ./build/ripwire . --no-cache --top-k=3 - - - - - - - - - - - + + + + + + - + + + -... [2 more line(s); run it to see the whole thing] + + + +... [3 more line(s); run it to see the whole thing] ``` **Shaped by:** `--doctor` @@ -4630,21 +4633,21 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses ``` $ ./build/ripwire . --no-ignore --top-k=3 - - - - - - - - - - - + + + + + + - + + + -... [2 more line(s); run it to see the whole thing] + + + +... [3 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -4664,21 +4667,21 @@ _Skip files above a size bound before parsing (note the corpus shrink in the hea ``` $ ./build/ripwire . --max-file-size=8K --top-k=3 - - - - - - - - - - - + + + + + + - -... [8 more line(s); run it to see the whole thing] + + + + + + +... [3 more line(s); run it to see the whole thing] ``` **Shaped by:** `--skipped` @@ -4689,20 +4692,6 @@ $ ./build/ripwire . --max-file-size=8K --top-k=3 - default 4MB skip files larger than N bytes (default 4MB; - files it drops are counted in the header's skipped_oversize= -### `--max-memory=N[K|M|G]` - -**Answers:** the memory guard's limit (default 65% of this machine's memory, env RIPWIRE_MAX_MEMORY) the memory guard is on for every run and silent on normal ones: it measures this process's footprint at most once per 5 s, from 5 s into an ingest. - -Past its lines the crawl (growth of limit/8) or the parse (half the limit) stops, and the default map answers from what was built, disclosed in its header (memory_stop=, memory_parsed=, memory_limit=; a parse stop keeps the first K slots it claimed of its work order — uncached files first, largest first within a tier, or path order when every grammar-bearing file was an ingest-cache hit — so a partial map repeats for a given memory_parsed=K); critical OS pressure stops them too. Every other verb (--html, --mermaid, --expand, --outline and, after a crawl stop, --in included) refuses a partial index, and so does any verb whose own internal ingest was cut; at the limit itself ripwire exits 5 with one line naming it. Nothing derived from a partial ingest is cached. The default is 65% of physical RAM (or of the cgroup's memory.max when lower); this flag, or RIPWIRE_MAX_MEMORY when the flag is absent, replaces it; below 64M is refused. - -**Shaped by:** `--mcp` - -**Caveats (stated by the binary):** - -- the memory guard's limit (default 65% of this machine's memory, env RIPWIRE_MAX_MEMORY) the memory guard is on for every run and silent on normal ones: it measures this process's footprint at most once per 5 s, from 5 s into an ingest. -- Past its lines the crawl (growth of limit/8) or the parse (half the limit) stops, and the default map answers from what was built, disclosed in its header (memory_stop=, memory_parsed=, memory_limit=; -- Every other verb (--html, --mermaid, --expand, --outline and, after a crawl stop, --in included) refuses a partial index, and so does any verb whose own internal ingest was cut; - ### `--refetch` **Answers:** when the root is a git URL, clone it fresh instead of reusing the cached copy when the root is a git URL, force a fresh clone instead of reusing the cached one (default: reuse forever; @@ -4744,28 +4733,26 @@ _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 - - - - - - - - - - - + + + + + + - + + + -... [2 more line(s); run it to see the whole thing] + + + +... [3 more line(s); run it to see the whole thing] ``` ### `--mcp` -**Answers:** persistent index server (parse once, many warm queries) over stdio persistent index server over stdio. - -Roots: a request's path= (or `paths`), else the startup root of `ripwire --mcp`, else the directory the server was launched in. A root that is $HOME itself (a git repository or not), a filesystem or drive root, or a system tree (/usr, /etc, /System, %WINDIR% ...) is refused with "no project root:

is a home/system directory; pass a project path" and the server stays up — an agent fills path= from its session's cwd, so it is judged like the launch directory. The one exception is the startup root itself: `ripwire ~ --mcp` was typed by a human and is answered. On the CLI a typed root (`ripwire ~`) is always answered, and a run without prints usage, or that same line from such a directory. Each tool call over the --max-memory limit is refused by name; an answer from an index the memory guard cut carries _memory_stop in its envelope. +**Answers:** persistent index server (parse once, many warm queries) over stdio **Try it** @@ -4773,30 +4760,14 @@ _initialize + tools/list: the manifest an agent host loads at session start — ``` $ ./build/ripwire '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | ./build/ripwire --mcp -{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"ripwire","version":"1.0"},"capabilities":{"tools":{}},"instructions":"Map before reading files. Start a new task with explore; use from_trace for an error; use impact plus uses before changing a symbol; run edit_c … [line truncated: 368 more bytes on this line] +{"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":{"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: 1111 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: 874 more bytes on this line] -... [28 more line(s); run it to see the whole thing] +... [30 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--no-stable`, `--no-redact`, `--agent`, `--mcp-tools`, `--lsp`, `--listen` - -**Caveats (stated by the binary):** - -- A root that is $HOME itself (a git repository or not), a filesystem or drive root, or a system tree (/usr, /etc, /System, %WINDIR% ...) is refused with "no project root: is a home/system directory; -- Each tool call over the --max-memory limit is refused by name; - -### `--mcp-tools=LIST` - -**Answers:** list only these MCP tools (names and/or the core/full profiles, default full). - -A comma list of tool names and/or profiles, unioned. core = explore, batch, from_trace, impact, uses, fetch_body, edit_check, quality_delta (the loop the server's own instructions teach); full = all tools, the default. A client that loads every schema at session start pays only for the listed ones. initialize announces the subset; calling an unlisted tool is refused with the flag that enables it. The list is not access control: batch sub-queries still reach hidden verbs. An unknown, repeated or empty name exits 1. `ripwire wrap AGENT --mcp-tools=LIST` writes it into the server command (claude, cursor, windsurf, gemini, opencode). - -**Caveats (stated by the binary):** - -- calling an unlisted tool is refused with the flag that enables it. -- The list is not access control: batch sub-queries still reach hidden verbs. +**Shaped by:** `--no-stable`, `--no-redact`, `--agent`, `--lsp`, `--listen` ### `--lsp` @@ -4852,7 +4823,7 @@ _Labelled verdict-accuracy eval for --stray-content — three labels over REAL l ``` $ ./build/ripwire . --eval-stray=/aux/stray_labels2.tsv # 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) -no-such-ref-0 merged +upstream-paging merged no-such-ref-1 unmerged no-such-ref-2 merged ``` @@ -4869,14 +4840,14 @@ _Self-eval: co-change recall vs BM25._ $ ./build/ripwire . --eval ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 0.5% 1.5% 6.7% - BM25 10.9% 13.1% 16.7% - BM25sub 10.7% 15.1% 20.2% - BM25body 22.3% 29.9% 34.9% - fused 4.2% 14.8% 22.0% - anchored 20.8% 29.9% 34.9% - same-dir 1.6% 5.6% 7.2% - random 0.2% 0.5% 1.0% <- floor (random ranking over F=2039 files) + ripwire 6.4% 8.2% 16.8% + BM25 17.6% 20.8% 24.2% + BM25sub 20.0% 27.5% 30.5% + BM25body 20.9% 32.4% 40.8% + fused 13.4% 19.0% 36.1% + anchored 20.9% 32.4% 40.8% + same-dir 0.0% 7.1% 11.2% + random 0.2% 0.4% 0.9% <- floor (random ranking over F=2345 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 / @@ -4899,19 +4870,19 @@ _Known-item retrieval eval: MRR + recall@k per ranker per query mode._ ``` $ ./build/ripwire . --eval-retrieval ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=4164 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + sample: population=5277 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.714 57.9% 88.4% 92.9% - subtoken doc-phrase 0.932 91.2% 95.2% 95.8% - name-exact name 0.924 86.0% 97.4% 98.4% - name-exact doc-phrase 0.013 0.4% 2.2% 2.7% - anchored name 0.718 59.3% 87.4% 91.7% - anchored doc-phrase 0.927 90.5% 95.0% 95.8% - routed name 0.924 86.0% 97.4% 98.4% - routed doc-phrase 0.930 91.1% 95.0% 95.5% + subtoken name 0.697 56.3% 86.5% 91.0% + subtoken doc-phrase 0.921 90.0% 94.2% 95.0% + name-exact name 0.909 84.4% 96.2% 97.5% + name-exact doc-phrase 0.010 0.3% 1.7% 2.4% + anchored name 0.703 57.8% 85.9% 89.7% + anchored doc-phrase 0.918 89.6% 94.0% 94.7% + routed name 0.912 84.6% 96.4% 97.8% + routed doc-phrase 0.919 90.0% 94.0% 94.8% ... [6 more line(s); run it to see the whole thing] ``` @@ -4957,10 +4928,10 @@ _Version + short build info._ ``` $ ./build/ripwire --version -ripwire 0.6.1 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=f8d431396) +ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=67cf52baa+dirty) ``` -**Shaped by:** `--verify`, `--metrics`, `--deps`, `--naming-locals`, `--arch`, `--dry-run`, `--doc-drift`, `--from-trace` +**Shaped by:** `--impact`, `--verify`, `--metrics`, `--deps`, `--naming-locals`, `--arch`, `--dry-run`, `--doc-drift` --- From 1586bf0e7fa192c62a258eda40ad893979483480 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:30:23 +0000 Subject: [PATCH 016/170] fix(changelog): drop an orphan conflict marker the rebase fixups left --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5062700b..1f310036f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,7 +117,6 @@ found" while `Class::method` resolved. When no other spelling matches, a selecto usual (`defs=N`, or `--edit-check`'s refusal). A selector that resolved before resolves identically. `--whereis` is a lexical scan of every ref and is unchanged. Gate: `test/selectorscopecheck.sh` arms (i)-(m). -<<<<<<< HEAD ### Added — a memory guard on every root: zero-config, silent on normal runs, a disclosed partial answer past its line (#350, layer 3) ripwire measured none of its own memory, so a large tree (#350: a non-git home directory, 67 GB) could grow it until From 09fcc5194428aef6d591275c6d50e7b5baaa329d Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:05:03 +0000 Subject: [PATCH 017/170] sync: regenerate showcase capture, COMMANDS.md and the parity manifest on the rebased tree Upstream main's 181 commits moved the seed line (graph.h grew), changed the legend dict (entries 733 -> 736) and grew the flag surface to 181 (--max-memory, --mcp-tools). The capture is regenerated against the rebased binary with the Not-run list extended for both new flags; the manifest is re-pinned (UPDATE_GOLDEN, help_all moved with the new help) and COMMANDS.md rebuilt. The generator patch is committed here so the capture is reproducible from this tree. --- docs/COMMANDS.md | 1316 +++--- docs/captures/COMMANDS_showcase_2026-10-01.md | 3940 ++++++++--------- test/printf_parity.manifest | 2 +- test/showcase_capture.py | 2 +- 4 files changed, 2542 insertions(+), 2718 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 983395ace..ff78e42c1 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -50,7 +50,7 @@ Two limits apply to nearly everything here and are not repeated in every section **security — scan skill files for injection / exfiltration patterns (exit 2 = CRITICAL, 1 = WARN,** — [`--scan-skill`](#--scan-skillfile) · [`--scan-skills`](#--scan-skillsdir) · [`--force`](#--force) -**knobs / modes** — [`--rank-by`](#--rank-bypagerankauthorityhubrrfchurnchurn-decay) · [`--in`](#--indir) · [`--format`](#--formatxmlcolumnarrows) · [`--format`](#--formatcandidates) · [`--legend`](#--legendfullcompact) · [`--legend-dict`](#--legend-dictroster) · [`--json`](#--json) · [`--limit`](#--limitn---offsetm) · [`--exclude`](#--excludesubstr) · [`--map-diff`](#--map-diff) · [`--cache`](#--cachepath) · [`--index-out`](#--index-outbase) · [`--no-cache`](#--no-cache) · [`--no-ignore`](#--no-ignore) · [`--max-file-size`](#--max-file-sizenkmg) · [`--refetch`](#--refetch) · [`--scip`](#--scipindexscip) · [`--pin-census`](#--pin-censusfile) · [`--mcp`](#--mcp) · [`--lsp`](#--lsp) · [`--listen`](#--listenhostport) · [`--mcp-token`](#--mcp-tokent) · [`--allow-remote-edits`](#--allow-remote-edits) · [`--eval-stray`](#--eval-strayfile) · [`--eval`](#--eval) · [`--eval-retrieval`](#--eval-retrieval) · [`--eval-mined`](#--eval-minedfile) · [`--eval-skills`](#--eval-skillsfile) · [`-h`](#-h---help) · [`-v`](#-v---version) +**knobs / modes** — [`--rank-by`](#--rank-bypagerankauthorityhubrrfchurnchurn-decay) · [`--in`](#--indir) · [`--format`](#--formatxmlcolumnarrows) · [`--format`](#--formatcandidates) · [`--legend`](#--legendfullcompact) · [`--legend-dict`](#--legend-dictroster) · [`--json`](#--json) · [`--limit`](#--limitn---offsetm) · [`--exclude`](#--excludesubstr) · [`--map-diff`](#--map-diff) · [`--cache`](#--cachepath) · [`--index-out`](#--index-outbase) · [`--no-cache`](#--no-cache) · [`--no-ignore`](#--no-ignore) · [`--max-file-size`](#--max-file-sizenkmg) · [`--max-memory`](#--max-memorynkmg) · [`--refetch`](#--refetch) · [`--scip`](#--scipindexscip) · [`--pin-census`](#--pin-censusfile) · [`--mcp`](#--mcp) · [`--mcp-tools`](#--mcp-toolslist) · [`--lsp`](#--lsp) · [`--listen`](#--listenhostport) · [`--mcp-token`](#--mcp-tokent) · [`--allow-remote-edits`](#--allow-remote-edits) · [`--eval-stray`](#--eval-strayfile) · [`--eval`](#--eval) · [`--eval-retrieval`](#--eval-retrieval) · [`--eval-mined`](#--eval-minedfile) · [`--eval-skills`](#--eval-skillsfile) · [`-h`](#-h---help) · [`-v`](#-v---version) --- @@ -68,21 +68,21 @@ _Same map, capped to the 5 highest-ranked symbols._ ``` $ ./build/ripwire . --top-k=5 - + - - - - - - - - + + + + - - + + + + -... [6 more line(s); run it to see the whole thing] + + +... [8 more line(s); run it to see the whole thing] ``` **Shaped by:** `--token-budget`, `--for`, `--recall`, `--graph-query`, `--pack-signatures`, `--expand`, `--from-trace`, `--run-trace` @@ -106,10 +106,10 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ $ ./build/ripwire . --max-tokens=1500 - - - - + + + + @@ -136,7 +136,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`, `--recall`, `--handoff`, `--pr-context`, `--from-trace`, `--run-trace` @@ -161,7 +161,7 @@ _The honest half of the contract: a task with no ripwire-shaped evidence ABSTAIN $ ./build/ripwire . --help-task="write a cheerful release announcement" - + ``` @@ -181,21 +181,20 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` - -& p, float alpha = 0.85f ) -... [17 more line(s); run it to see the whole thing] + +... [18 more line(s); run it to see the whole thing] ``` **Shaped by:** `--top-k`, `--max-tokens`, `--token-budget`, `--sections`, `--signatures-only`, `--auto-bodies`, `--no-route`, `--adaptive` @@ -226,14 +225,14 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -258,18 +257,19 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) - + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -... [20 more line(s); run it to see the whole thing] +struct FileHealth +enum class AstWalk : std::uint8_t +... [19 more line(s); run it to see the whole thing] ``` **Shaped by:** `--for` @@ -291,16 +291,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] ``` @@ -323,17 +323,17 @@ _Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothin ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --adaptive - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) - + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth +struct FileHealth ... [20 more line(s); run it to see the whole thing] ``` @@ -355,10 +355,10 @@ _Same task with the anchor disabled — the contrast the flag exists for._ ``` $ ./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25" --no-mention-boost - - - + + The name-exact ranker AS THE RETRIEVAL LENS SERVES IT: whole-name BM25 plus the definition-over-…inline std::vector<float> lexicalScoresNameExactRanked( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) @@ -387,15 +387,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 - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) - +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… ... [21 more line(s); run it to see the whole thing] ``` @@ -418,7 +418,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -448,7 +448,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 ) @@ -477,9 +477,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" — 113 relevant of 208 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5307 +ripwire recall — "quality delta gating exit codes" — 113 relevant of 209 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5403 -━━ README.md (relevance 8.782) ━━ [sections: 2 of 24 selected (60 in doc), section-granular; whole doc 204511 B; lines="2595-2606,2607-2614"; dropped_by_budget=22] +━━ README.md (relevance 8.289) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 211997 B; lines="2688-2700,2701-2708"; dropped_by_budget=23] #### 6.2 Exit codes This is the part to wire into a script. @@ -542,25 +542,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 1729927-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 1437506-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.656) ━━ [sections: 1 of 154 selected (183 in doc), section-granular; whole doc 717765 B; lines="515-540"; dropped_by_budget=153] [truncated: 1854 of 4242 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base +━━ commands.md (relevance 10.149) ━━ [sections: 1 of 154 selected (183 in doc), section-granular; whole doc 527781 B; lines="3662-3666"; dropped_by_budget=153] [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` @@ -576,19 +571,19 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - - + + + + + + + - - - - - ... [17 more line(s); run it to see the whole thing] ``` @@ -614,7 +609,7 @@ $ ./build/ripwire . --html=/aux/map2.html (empty) ``` -**Shaped by:** `--color-by`, `--legend` +**Shaped by:** `--color-by`, `--legend`, `--max-memory` **Caveats (stated by the binary):** @@ -647,21 +642,21 @@ _Stable (path/id) emit order — provider KV-cache hits across re-runs._ ``` $ ./build/ripwire . --order=stable --top-k=5 - + - - - - -... [6 more line(s); run it to see the whole thing] + + + + +... [8 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-stable` @@ -678,21 +673,19 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable ``` $ ./build/ripwire . --no-stable --top-k=3 - + - - - - + + + + - - + + - - -... [3 more line(s); run it to see the whole thing] + ``` --- @@ -714,8 +707,8 @@ $ ./build/ripwire . --around=rankGraphTeleport - - + + @@ -764,8 +757,8 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - - + + @@ -795,16 +788,16 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - - - - + + + + - - - - - + + + + + ``` @@ -829,19 +822,19 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - - - - - - - + + + + + + + - - + + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -866,19 +859,19 @@ _Names referenced but never defined in-corpus (stdlib/third-party surface). The ``` $ ./build/ripwire . --external-surface - - - - - - + + + + + + - + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -901,12 +894,12 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - - - - - - + + + + + + ``` @@ -923,11 +916,11 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + - - + + @@ -940,7 +933,7 @@ $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex **Answers:** show everything that reaches SYM — the transitive blast radius before a change transitive blast radius — the indexed symbols that reach SYM (a floor, see counts_floor). -file:name disambiguates like --callers importers= is a SECOND, weaker reach beside it: the files that directly include/import a file defining SYM, emitted as rows (format=columnar carries the count only; --limit sizes it). NEVER added to reaches= — files and symbols are different units, and an importer may use a different symbol from that file, or none at all. lazy="1": every one of that importer's edges is written inside a closure — a TS/JS require()/import() inside a function body, a Ruby constant receiver or argument inside a method/lambda/block, a Ruby autoload or rescue class — not at load time: still a real dependency, weaker than a top-level one counts_floor="1" every count on the graph verbs is a FLOOR, never a total; a 0 means none found on --callers/--callees/--uses/--impact/--edit-check every count is a FLOOR, never a total: the call graph is extracted from source text by name, so dynamic dispatch and declarations that parse without a call expression (C++ most-vexing-parse) contribute no edge; a call through a function pointer/callback is an edge only when ONE function is bound to that variable in scope (reassigned/table-indexed/lambda-bound/escaped — address-taken or reference-bound — pointers stay edge-less, C-family); a macro-generated call site contributes a role="macro" edge when its name uniquely names an indexed function-like #define (t="macro"); a shared name stays a plain call, an unindexed macro's site is no edge. Read a 0 as "none found", never as "none exists". Those five verbs also count DISTINCT (caller,callee) pairs, while --uses counts call SITES — see each verb's own legend pr_iters="N" how many PageRank iterations produced this ordering on every PageRank-ordered document (the map, and the tree, seams, communities, zoom, impact, graph-query and exercises verbs, plus their MCP twins): how many power iterations produced that ordering. The iteration stops when the L1 residual between successive rank vectors falls below tolerance, or at a fixed iteration ceiling, whichever comes first. pr_converged="0" is emitted ONLY on that second exit and means the ranking is a rank vector that stopped SHORT of tolerance, not the fixed point it approximates. ABSENCE MEANS IT CONVERGED (there is no pr_converged="1": the converged path is the normal one and must cost zero bytes), and absence of pr_iters= itself means the document was not ordered by a power iteration at all (a lexical query score, or a hub or authority HITS vector), never that the count is unknown +file:name disambiguates like --callers rows run nearest first: d= is the hop depth (1 = a direct caller; printed where it changes), PageRank order within a depth; by_depth=k:n on the root counts reaches= per depth, so a cut drops the deepest first importers= is a SECOND, weaker reach beside it: the files that directly include/import a file defining SYM, emitted as rows (format=columnar carries the count only; --limit sizes it). NEVER added to reaches= — files and symbols are different units, and an importer may use a different symbol from that file, or none at all. lazy="1": every one of that importer's edges is written inside a closure — a TS/JS require()/import() inside a function body, a Ruby constant receiver or argument inside a method/lambda/block, a Ruby autoload or rescue class — not at load time: still a real dependency, weaker than a top-level one counts_floor="1" every count on the graph verbs is a FLOOR, never a total; a 0 means none found on --callers/--callees/--uses/--impact/--edit-check every count is a FLOOR, never a total: the call graph is extracted from source text by name, so dynamic dispatch and declarations that parse without a call expression (C++ most-vexing-parse) contribute no edge; a call through a function pointer/callback is an edge only when ONE function is bound to that variable in scope (reassigned/table-indexed/lambda-bound/escaped — address-taken or reference-bound — pointers stay edge-less, C-family); a macro-generated call site contributes a role="macro" edge when its name uniquely names an indexed function-like #define (t="macro"); a shared name stays a plain call, an unindexed macro's site is no edge. Read a 0 as "none found", never as "none exists". Those five verbs also count DISTINCT (caller,callee) pairs, while --uses counts call SITES — see each verb's own legend pr_iters="N" how many PageRank iterations produced this ordering on every PageRank-ordered document (the map, and the tree, seams, communities, zoom, impact, graph-query and exercises verbs, plus their MCP twins): how many power iterations produced that ordering. The iteration stops when the L1 residual between successive rank vectors falls below tolerance, or at a fixed iteration ceiling, whichever comes first. pr_converged="0" is emitted ONLY on that second exit and means the ranking is a rank vector that stopped SHORT of tolerance, not the fixed point it approximates. ABSENCE MEANS IT CONVERGED (there is no pr_converged="1": the converged path is the normal one and must cost zero bytes), and absence of pr_iters= itself means the document was not ordered by a power iteration at all (a lexical query score, or a hub or authority HITS vector), never that the count is unknown **Try it** @@ -948,20 +941,20 @@ _Transitive blast radius — everything that reaches SYM. NOW carries shown/capp ``` $ ./build/ripwire . --impact=rankGraphTeleport - - - - - - - + + + + + + + + + + + - - - - - + ... [17 more line(s); run it to see the whole thing] ``` @@ -970,7 +963,7 @@ $ ./build/ripwire . --impact=rankGraphTeleport **Caveats (stated by the binary):** - show everything that reaches SYM — the transitive blast radius before a change transitive blast radius — the indexed symbols that reach SYM (a floor, see counts_floor). -- file:name disambiguates like --callers importers= is a SECOND, weaker reach beside it: the files that directly include/import a file defining SYM, emitted as rows (format=columnar carries the count only; +- file:name disambiguates like --callers rows run nearest first: d= is the hop depth (1 = a direct caller; - NEVER added to reaches= — files and symbols are different units, and an importer may use a different symbol from that file, or none at all. ### `--verify="CLAIM"` @@ -998,7 +991,7 @@ $ ./build/ripwire . --verify="frobnicate(x)" **Answers:** find the markdown docs that name SYM in backticks — the doc-to-code link markdown docs (plans/designs) that name SYM in a `backtick` (doc↔code). -An @FILE:LINE seed rebinds to the innermost enclosing definition and answers, disclosing sym= the pre-PR family — plumbing (--affected) to mid-task report (--situ) to gate (--test-gate): +An @FILE:LINE seed rebinds to the innermost enclosing definition and answers, disclosing sym= (its name). unbackticked_docs=N (absent at 0): files naming SYM only outside such a span, a ceiling. the pre-PR family — plumbing (--affected) to mid-task report (--situ) to gate (--test-gate): **Try it** @@ -1007,7 +1000,8 @@ _Markdown docs that name SYM in a backtick (doc<->code edges)._ ``` $ ./build/ripwire . --mentions=rankGraphTeleport - + + @@ -1032,7 +1026,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - + @@ -1088,13 +1082,13 @@ Backtested on two histories (EVALS.md): a named partner is edited within the nex **Try it** -_Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below)._ +_Mid-task situational report for the current git diff — recorded against a DIRTY tree (contrast with the sandbox run below)._ ``` $ ./build/ripwire . --situ ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: ce5c0b3fc +at: dac3b8205+dirty (0 changed files — working tree is clean, nothing to analyze) ``` @@ -1108,23 +1102,23 @@ Empty diff is fine — the packet still carries branch/sha + heuristics. Compose **Try it** -_The continuation packet for the NEXT session: disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a CLEAN tree._ +_The continuation packet for the NEXT session: disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a DIRTY tree._ ``` $ ./build/ripwire . --handoff - + - + - - - + + + + ... [1 more line(s); run it to see the whole thing] ``` @@ -1143,12 +1137,12 @@ exit 4 if either is non-empty agent self-check before a PR (pair with --quality- **Try it** -_Pre-PR gate on a CLEAN tree: no obligations, exit 0._ +_Pre-PR gate recorded against a DIRTY tree, so the obligations below are the working copy's real ones — the recorded exit code says which way it went._ ``` $ ./build/ripwire . --test-gate - ``` @@ -1173,7 +1167,7 @@ _Literal trigram-indexed search. Each hit carries its MATCHED line as the $ ./build/ripwire . --grep=DISCLOSE - + - + - + @@ -1475,7 +1469,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1511,16 +1505,16 @@ $ ./build/ripwire . --pack-signatures --top-k=10 - + - - + + - - - + + + + - ... [17 more line(s); run it to see the whole thing] ``` @@ -1542,7 +1536,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)" ); @@ -1555,7 +1549,7 @@ $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 ... [8 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--top-k`, `--expand`, `--compress`, `--no-redact`, `--limit` +**Shaped by:** `--top-k`, `--expand`, `--compress`, `--no-redact`, `--limit`, `--max-memory` ### `--expand=A,B,...` @@ -1569,11 +1563,11 @@ _NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map ride ``` $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport - + - + + + + + - - - - + + + + - - - + - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1741,19 +1739,19 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - - + + + + - - + + + - - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1778,18 +1776,18 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - - - - - - - - - - - - + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1806,17 +1804,17 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + - + - + ... [17 more line(s); run it to see the whole thing] @@ -1837,15 +1835,15 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - - - - + + + + - + - + ``` @@ -1870,7 +1868,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1905,18 +1903,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] ``` @@ -1936,18 +1934,18 @@ _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] ``` @@ -1973,13 +1971,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] ``` @@ -2003,20 +2001,20 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - - - + + + - + -

+

-

+

... [14 more line(s); run it to see the whole thing] ``` @@ -2038,13 +2036,13 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - - - + + + + + - + @@ -2076,8 +2074,8 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - - + + @@ -2085,7 +2083,7 @@ $ ./build/ripwire . --lint --naming-locals - + @@ -2111,11 +2109,11 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - - + + - + @@ -2144,16 +2142,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - + + - - + + + - - - + ... [22 more line(s); run it to see the whole thing] ``` @@ -2176,16 +2174,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] ``` @@ -2202,16 +2200,16 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - - + + - + - + - + @@ -2231,19 +2229,19 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - - - - - - - - - - - - - + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2269,7 +2267,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2299,7 +2297,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2325,7 +2323,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2352,8 +2350,8 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - - + + @@ -2361,10 +2359,10 @@ $ ./build/ripwire . --lint - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2461,8 +2459,8 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - - + + @@ -2470,10 +2468,10 @@ $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2547,19 +2545,19 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + - + + - - - - - - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2578,7 +2576,7 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - ``` @@ -2603,19 +2601,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] ``` @@ -2640,16 +2638,16 @@ $ ./build/ripwire . --report # ripwire architecture report -2345 files · 22606 symbols · 31600 edges · 2188 modules (10489 call-graph isolated) +2376 files · 23174 symbols · 32805 edges · 2235 modules (10668 call-graph isolated) Root: `.` -Call-graph isolate provenance: 2793 declaration, 1471 header, 1804 source, 4421 document; 0 connected Louvain singletons +Call-graph isolate provenance: 2842 declaration, 1520 header, 1842 source, 4464 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 2188) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1303 symbols -- **src::append@elixir_resolve.h:111:4875 [resolve,add,collect]** — 1024 symbols -- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 516 symbols +## Modules (call-graph clusters; showing 12 of 2235) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1270 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,collect,append]** — 788 symbols +- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 549 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2666,13 +2664,13 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - - - - + + + + - + @@ -2697,21 +2695,21 @@ $ ./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
6405"] - n102["src/infra
1250"] + n101["src
6753"] + n102["src/infra
1275"] end subgraph sg1 ["test"] - n103["test
4696"] + n103["test
4771"] n294["test/fixtures/recallpassage
314"] n280["test/expandmodefix
151"] n282["test/expandsibsfix
149"] - n411["test/rubyattrsfix
85"] - n348["test/massfix
77"] - n314["test/javamethodreffix
62"] + n423["test/rubyattrsfix
85"] + n360["test/massfix
77"] + n326["test/javamethodreffix
62"] ... [17 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--zoom`, `--with-graph`, `--legend`, `--limit` +**Shaped by:** `--zoom`, `--with-graph`, `--legend`, `--limit`, `--max-memory` ### `--owners[=SYM]` @@ -2726,18 +2724,18 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - + + - + - + - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2757,7 +2755,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2811,18 +2809,18 @@ $ ./build/ripwire . --quality-baseline --allow-dirty ### `--quality-delta` -**Answers:** before a PR: report ONLY what your change made worse, across 10 kinds agent self-check before a PR (pair with --test-gate): report ONLY what a change made worse vs the baseline (10 kinds: complexity/verbosity/nesting/params/dup/dead/api-surface + error-masking/short-horizon-churn/new-clone-of-reused-helper); +**Answers:** before a PR: report ONLY what your change made worse, across 11 kinds agent self-check before a PR (pair with --test-gate): report ONLY what a change made worse vs the baseline (11 kinds: complexity/verbosity/nesting/params/dup/dead/api-surface + error-masking/short-horizon-churn/new-clone-of-reused-helper + placeholder); -every finding is classified by ORIGIN: a symbol that EXISTED at the baseline and got worse (preexisting-worse="N", no attribute on the row) vs one that exists only because the code is NEW (new-symbol="N", origin="new-symbol" on the row). A small numeric delta is additionally sev="minor". EXIT 2 ONLY on preexisting-worse AND major AND unacked — the gating="N" header count. New-symbol rows are still PRINTED (they are the debt you are adding — read them), they just never gate; exit 0 means "nothing that already existed got worse", not "clean". Clone kinds classify by member set (new-symbol only if EVERY member is new); short-horizon-churn is preexisting by construction. LIMIT: origin is canonId (path::scope::name) identity, so a RENAMED/MOVED symbol reads as new and a regression carried in with the move will not gate. Test-fixture dirs + doc sections are exempt from dead-code/churn; churn needs COMMITTED thrash evidence (rewritten across recent commits AND again by this diff), never the current edit alone WHICH FLOOR IT COMPARES AGAINST, and a side effect: the sidecar is honored only when the sha it was pinned at EQUALS the current git HEAD (strict equality — an ancestor commit describes a DIFFERENT tree, so everything committed since would read as your regression). A sidecar pinned anywhere else is STALE: this verb then DELETES it from your working tree (self-heal, so the next run does not rediscover the dead pin) and auto-compares the working tree vs git HEAD instead. Re-pin with --quality-baseline. The read-only MCP quality_delta verb applies the SAME staleness test but never deletes. A sidecar at the current HEAD that ANOTHER ripwire build pinned (its producer stamp names other sources — a dead set depends on how calls were resolved) is FOREIGN: both arms ignore it, never delete it, and auto-compare vs git HEAD. Which floor was actually used is on every report as baseline=: sidecar | git-HEAD | git-HEAD (stale sidecar removed) | git-HEAD (stale sidecar ignored) | git-HEAD (foreign sidecar ignored) — the stale two say a stale sidecar existed, and 'removed' means the file is gone. A non-git root has no HEAD to fall back to, so its sidecar is honored whenever this build pinned it; without one there, or with another build's, the verb exits 1. +every finding is classified by ORIGIN: a symbol that EXISTED at the baseline and got worse (preexisting-worse="N", no attribute on the row) vs one that exists only because the code is NEW (new-symbol="N", origin="new-symbol" on the row). A small numeric delta is additionally sev="minor". EXIT 2 ONLY on preexisting-worse AND major AND unacked — the gating="N" header count. New-symbol rows are still PRINTED (they are the debt you are adding — read them), they just never gate; exit 0 means "nothing that already existed got worse", not "clean". Clone kinds classify by member set (new-symbol only if EVERY member is new); short-horizon-churn is preexisting by construction. LIMIT: origin is canonId (path::scope::name) identity, so a RENAMED/MOVED symbol reads as new and a regression carried in with the move will not gate. error-masking = a NEW empty/pass/comment-only handler, or log-only (a broad handler whose body only logs and never names the error) or rethrow-only (the sole handler re-throws it unchanged); those two gate only where their precision was measured (Python) and are sev="minor" in every other language. placeholder = a stub the change ADDED (todo!()/unimplemented!(), Kotlin TODO(), NotImplementedException, a bare raise NotImplementedError as a free function's body, a throw/raise/panic/assert saying "not implemented") or a comment line opening with TODO/FIXME that names no issue (#12, ABC-12, a URL); new-symbol by construction, so it never gates. Counted per enclosing symbol, like error-masking: a file-level TODO outside every definition is not counted. Test-fixture dirs + doc sections are exempt from dead-code/churn; churn needs COMMITTED thrash evidence (rewritten across recent commits AND again by this diff), never the current edit alone WHICH FLOOR IT COMPARES AGAINST, and a side effect: the sidecar is honored only when the sha it was pinned at EQUALS the current git HEAD (strict equality — an ancestor commit describes a DIFFERENT tree, so everything committed since would read as your regression). A sidecar pinned anywhere else is STALE: this verb then DELETES it from your working tree (self-heal, so the next run does not rediscover the dead pin) and auto-compares the working tree vs git HEAD instead. Re-pin with --quality-baseline. The read-only MCP quality_delta verb applies the SAME staleness test but never deletes. A sidecar at the current HEAD that ANOTHER ripwire build pinned (its producer stamp names other sources — a dead set depends on how calls were resolved) is FOREIGN: both arms ignore it, never delete it, and auto-compare vs git HEAD. Which floor was actually used is on every report as baseline=: sidecar | git-HEAD | git-HEAD (stale sidecar removed) | git-HEAD (stale sidecar ignored) | git-HEAD (foreign sidecar ignored) — the stale two say a stale sidecar existed, and 'removed' means the file is gone. A non-git root has no HEAD to fall back to, so its sidecar is honored whenever this build pinned it; without one there, or with another build's, the verb exits 1. **Try it** -_On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below._ +_Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit._ ``` $ ./build/ripwire . --quality-delta - -, acked. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). --> + @@ -2844,22 +2842,22 @@ $ ./build/ripwire . --quality-delta - New-symbol rows are still PRINTED (they are the debt you are adding — read them), they just never gate; - LIMIT: origin is canonId (path::scope::name) identity, so a RENAMED/MOVED symbol reads as new and a regression carried in with the move will not gate. -- The read-only MCP quality_delta verb applies the SAME staleness test but never deletes. +- error-masking = a NEW empty/pass/comment-only handler, or log-only (a broad handler whose body only logs and never names the error) or rethrow-only (the sole handler re-throws it unchanged); ### `--quality-delta=REV|A..B` -**Answers:** the same 10-kind report between two committed trees — a whole branch at once the same 10-kind report between two COMMITTED TREES instead of the working tree vs a baseline — the WAVE-level measurement (=A..B = tree B against tree A; +**Answers:** the same 11-kind report between two committed trees — a whole branch at once the same 11-kind report between two COMMITTED TREES instead of the working tree vs a baseline — the WAVE-level measurement (=A..B = tree B against tree A; -=REV = that commit against its FIRST PARENT; an EMPTY side of the range means HEAD). Same grammar --dmm= takes, and A...B is REFUSED rather than read as A..B. Use it to measure a whole integration branch at once (--quality-delta=..): per-lane checks each compare against their own baseline and cannot see a regression the WAVE introduced. Identical output contract to the bare form — same kinds, gating="N", exit 2, and the same .ripwire_quality_acks ratchet (acks are keyed root-relative, so a ledger recorded from working-tree runs applies unchanged). base_ref= and target_ref= disclose the two RESOLVED shas. No sidecar is read, written or deleted by this form, and at= is omitted: the two refs ARE the anchor. A==B is a legal, empty, exit-0 comparison. ONE KIND CANNOT BE MEASURED HERE and says so as churn="unavailable": short-horizon-churn needs git history at the tree being judged, and both trees are materialized OUT of the repo into temp dirs. The other 9 kinds are computed exactly as the bare form computes them. +=REV = that commit against its FIRST PARENT; an EMPTY side of the range means HEAD). Same grammar --dmm= takes, and A...B is REFUSED rather than read as A..B. Use it to measure a whole integration branch at once (--quality-delta=..): per-lane checks each compare against their own baseline and cannot see a regression the WAVE introduced. Identical output contract to the bare form — same kinds, gating="N", exit 2, and the same .ripwire_quality_acks ratchet (acks are keyed root-relative, so a ledger recorded from working-tree runs applies unchanged). base_ref= and target_ref= disclose the two RESOLVED shas. No sidecar is read, written or deleted by this form, and at= is omitted: the two refs ARE the anchor. A==B is a legal, empty, exit-0 comparison. ONE KIND CANNOT BE MEASURED HERE and says so as churn="unavailable": short-horizon-churn needs git history at the tree being judged, and both trees are materialized OUT of the repo into temp dirs. The other 10 kinds are computed exactly as the bare form computes them. **Try it** -_On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below._ +_Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit._ ``` $ ./build/ripwire . --quality-delta - -, acked. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). --> + @@ -2891,12 +2889,12 @@ thresholds and arithmetic from PyDriller's deltamaintainability reference implem **Try it** -_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._ +_The Delta Maintainability Model scalar for the WORKING TREE vs HEAD — recorded against a DIRTY tree (the sandbox section shows a real delta). UNAVAILABLE is a stated reason, never 0 or 1._ ``` $ ./build/ripwire . --dmm -

is a home/system directory; pass a project path" and the server stays up — an agent fills path= from its session's cwd, so it is judged like the launch directory. The one exception is the startup root itself: `ripwire ~ --mcp` was typed by a human and is answered. On the CLI a typed root (`ripwire ~`) is always answered, and a run without prints usage, or that same line from such a directory. Each tool call over the --max-memory limit is refused by name; an answer from an index the memory guard cut carries _memory_stop in its envelope. **Try it** @@ -4767,7 +4767,23 @@ $ ./build/ripwire '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' '{"jso ... [30 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--no-stable`, `--no-redact`, `--agent`, `--lsp`, `--listen` +**Shaped by:** `--no-stable`, `--no-redact`, `--agent`, `--mcp-tools`, `--lsp`, `--listen` + +**Caveats (stated by the binary):** + +- A root that is $HOME itself (a git repository or not), a filesystem or drive root, or a system tree (/usr, /etc, /System, %WINDIR% ...) is refused with "no project root: is a home/system directory; +- Each tool call over the --max-memory limit is refused by name; + +### `--mcp-tools=LIST` + +**Answers:** list only these MCP tools (names and/or the core/full profiles, default full). + +A comma list of tool names and/or profiles, unioned. core = explore, batch, from_trace, impact, uses, fetch_body, edit_check, quality_delta (the loop the server's own instructions teach); full = all tools, the default. A client that loads every schema at session start pays only for the listed ones. initialize announces the subset; calling an unlisted tool is refused with the flag that enables it. The list is not access control: batch sub-queries still reach hidden verbs. An unknown, repeated or empty name exits 1. `ripwire wrap AGENT --mcp-tools=LIST` writes it into the server command (claude, cursor, windsurf, gemini, opencode). + +**Caveats (stated by the binary):** + +- calling an unlisted tool is refused with the flag that enables it. +- The list is not access control: batch sub-queries still reach hidden verbs. ### `--lsp` @@ -4823,9 +4839,9 @@ _Labelled verdict-accuracy eval for --stray-content — three labels over REAL l ``` $ ./build/ripwire . --eval-stray=/aux/stray_labels2.tsv # 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) +sync-rebase merged +sync-upstream unmerged upstream-paging merged -no-such-ref-1 unmerged -no-such-ref-2 merged ``` ### `--eval` @@ -4840,14 +4856,14 @@ _Self-eval: co-change recall vs BM25._ $ ./build/ripwire . --eval ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 6.4% 8.2% 16.8% - BM25 17.6% 20.8% 24.2% - BM25sub 20.0% 27.5% 30.5% - BM25body 20.9% 32.4% 40.8% - fused 13.4% 19.0% 36.1% - anchored 20.9% 32.4% 40.8% - same-dir 0.0% 7.1% 11.2% - random 0.2% 0.4% 0.9% <- floor (random ranking over F=2345 files) + ripwire 2.2% 6.5% 14.7% + BM25 6.7% 7.8% 16.0% + BM25sub 10.9% 14.8% 21.1% + BM25body 7.9% 20.4% 34.0% + fused 11.6% 16.1% 23.4% + anchored 7.9% 20.4% 34.0% + same-dir 0.0% 1.5% 3.1% + random 0.2% 0.4% 0.8% <- floor (random ranking over F=2376 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 / @@ -4870,19 +4886,19 @@ _Known-item retrieval eval: MRR + recall@k per ranker per query mode._ ``` $ ./build/ripwire . --eval-retrieval ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=5277 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + sample: population=5540 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.697 56.3% 86.5% 91.0% - subtoken doc-phrase 0.921 90.0% 94.2% 95.0% - name-exact name 0.909 84.4% 96.2% 97.5% - name-exact doc-phrase 0.010 0.3% 1.7% 2.4% - anchored name 0.703 57.8% 85.9% 89.7% - anchored doc-phrase 0.918 89.6% 94.0% 94.7% - routed name 0.912 84.6% 96.4% 97.8% - routed doc-phrase 0.919 90.0% 94.0% 94.8% + subtoken name 0.697 56.4% 86.3% 91.2% + subtoken doc-phrase 0.922 90.2% 94.2% 95.1% + name-exact name 0.912 84.7% 96.3% 97.7% + name-exact doc-phrase 0.011 0.4% 1.6% 2.3% + anchored name 0.706 58.4% 85.9% 89.8% + anchored doc-phrase 0.918 89.7% 94.1% 94.7% + routed name 0.914 85.0% 96.5% 97.9% + routed doc-phrase 0.920 90.1% 94.0% 94.8% ... [6 more line(s); run it to see the whole thing] ``` @@ -4928,7 +4944,7 @@ _Version + short build info._ ``` $ ./build/ripwire --version -ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=67cf52baa+dirty) +ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=dac3b8205) ``` **Shaped by:** `--impact`, `--verify`, `--metrics`, `--deps`, `--naming-locals`, `--arch`, `--dry-run`, `--doc-drift` diff --git a/docs/captures/COMMANDS_showcase_2026-10-01.md b/docs/captures/COMMANDS_showcase_2026-10-01.md index c19531cf5..0ae0c8a65 100644 --- a/docs/captures/COMMANDS_showcase_2026-10-01.md +++ b/docs/captures/COMMANDS_showcase_2026-10-01.md @@ -2,8 +2,8 @@ - **Date:** 2026-10-01 (regenerated capture; supersedes any older `docs/captures/COMMANDS_showcase_*.md`) - **Lives in `docs/captures/`** — a directory the crawl/retrieval lenses SKIP (`kCrawlSkipDirs`, src/ingest.h): a generated doc that quotes every verb's output out-scores the source for any query about the tool and was measured at 77% of `--recall` on this repo when it sat at the root. `test/argvdiffcheck.sh` harvests its `## `-heading command lines as differential vectors — keep that format. -- **Version:** `ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=67cf52baa+dirty)` -- **Repo:** the ripwire repo @ `ce5c0b3fc` — **CLEAN — `git status --porcelain` is empty**. The diff-aware verbs (`--situ`/`--test-gate`/`--quality-delta`/`--pr-context`/`--map-diff`/`--edit-check`) answer a question about the WORKING TREE, so that condition is part of their answer and every one of their captions below states which tree it recorded against. A clean tree is the honest default for a showcase, so they appear TWICE: once here on the clean tree (their empty/exit-0 shape) and once in the final section against a throwaway `git clone --local` sandbox carrying one deliberate regression, so their real gating shapes are visible without writing a byte into the read-only repo. +- **Version:** `ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=dac3b8205)` +- **Repo:** the ripwire repo @ `dac3b8205` — **DIRTY — `git status --porcelain` reports 1 entr(ies)**. The diff-aware verbs (`--situ`/`--test-gate`/`--quality-delta`/`--pr-context`/`--map-diff`/`--edit-check`) answer a question about the WORKING TREE, so that condition is part of their answer and every one of their captions below states which tree it recorded against. This run recorded against a dirty tree, so their output here reflects uncommitted working-copy edits rather than the empty/exit-0 shape a clean checkout gives. They appear a SECOND time in the final section against a throwaway `git clone --local` sandbox carrying one KNOWN, deliberate regression — that is the reproducible gating demonstration; this one is whatever the tree happened to hold. - **Corpus:** the ripwire repo itself (dogfood), via `./build/ripwire` - **Sandbox diff** (the last section only): `.ripwire_notes | 2 ++ .ripwire_quality_acks | 1 + @@ -12,7 +12,7 @@ **How to read the blocks:** ripwire's real XML output is minified — often ONE long line. For scanability, long minified lines are displayed re-wrapped with a line break at every tag seam (`><`). Header COMMENT lines (the legends) always appear in full — they are exempt from the per-line cut; any OTHER display line over 300 bytes is cut with a `… [line truncated: N more bytes]` marker, which can hit a long root element or row. `--plan-lanes` emits JSON and is re-wrapped at object seams the same way. Long outputs are cut to their first ~30 display lines with a `… [N more display lines; full output is M bytes]` marker giving the true size. Exit codes are recorded when non-zero; wall time when >1s. -**Not run (and why):** `ripwire ` (network clone), `--listen` / `--mcp-token` / `--allow-remote-edits` (the HTTP-server posture; `--mcp` itself IS captured in its own section as a one-shot stdio JSON-RPC exchange, and `wrap claude` shows the wiring), `--arch --baseline[-update]` (state writer against the read-only repo — `--note-add` / `--quality-baseline` / `--quality-ack` / the three edit verbs / `--edit-plan` ARE shown, inside the throwaway sandbox clone; `--index-out` / `--pin-census` write to scratch), `--eval-mined` (needs a `minedpair.jsonl` artifact from `bench/mine_traces.py`; none present in the tree), `--refetch` (git-url only), `--lsp` (the editor transport — its one-shot dialogues are gated by `test/lspcheck.sh`, not by showcase blocks), `--sections` (the / stub-and-restore shapes are gated by `test/forsectioncollapsecheck.sh`; the flag restores what --for collapses by default), `--force` (wrap-only modifier), `--scan-skills` bare form (would sweep `~/.claude/skills`; the explicit-DIR form is shown instead), `--help` (202 lines — read it from the binary). +**Not run (and why):** `ripwire ` (network clone), `--listen` / `--mcp-token` / `--allow-remote-edits` / `--mcp-tools` (the server posture — `--mcp` itself IS captured in its own section as a one-shot stdio JSON-RPC exchange with the default full tool profile, and `wrap claude` shows the wiring), `--arch --baseline[-update]` (state writer against the read-only repo — `--note-add` / `--quality-baseline` / `--quality-ack` / the three edit verbs / `--edit-plan` ARE shown, inside the throwaway sandbox clone; `--index-out` / `--pin-census` write to scratch), `--eval-mined` (needs a `minedpair.jsonl` artifact from `bench/mine_traces.py`; none present in the tree), `--refetch` (git-url only), `--max-memory` (the guard is silent on a corpus this size — its disclosed partial-answer shape needs a tree past the limit, gated by the memory-guard lanes instead), `--lsp` (the editor transport — its one-shot dialogues are gated by `test/lspcheck.sh`, not by showcase blocks), `--sections` (the / stub-and-restore shapes are gated by `test/forsectioncollapsecheck.sh`; the flag restores what --for collapses by default), `--force` (wrap-only modifier), `--scan-skills` bare form (would sweep `~/.claude/skills`; the explicit-DIR form is shown instead), `--help` (204 lines — read it from the binary). --- @@ -23,66 +23,66 @@ *The default ranked symbol map — start here when landing cold in a repo.* -**wall time: 1.66s** +**wall time: 5.71s** ````` - - - - + + + + + + + + + + + + + + + + - - - - + + + - - - - + - - - - + + - - - - - - - - -… [802 more display lines; full output is 22158 bytes on 1 raw line(s)] + + +… [792 more display lines; full output is 21802 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --top-k=5` *Same map, capped to the 5 highest-ranked symbols.* -**wall time: 1.27s** - ````` - + - - + + + + + + + + + + + - - - - - - - - + @@ -92,14 +92,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.21s** - ````` - + - - - - + + + + @@ -169,79 +165,79 @@ 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.37s** +**exit code: 3** ````` - + ````` stderr: ````` -ripwire: --token-budget exceeded: withheld_est_tokens=8930 > budget=100 +ripwire: --token-budget exceeded: withheld_est_tokens=8783 > 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: 2.78s** +**wall time: 15.19s** ````` - - - - + + + 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 - + the cached index for `root`, rebuilt only when stale (otherwise returned as-is, no parse, no gra…inline const McpIndex& getIndex( const std::string& root ) -inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) -inline std::uint64_t contentHash64( std::string_view s ) noexcept -struct CacheEntry +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline std::uint64_t contentHash64( std::string_view s ) noexcept +struct CacheEntry inline void prewarmTagsQueries( const std::vector<std::string>& files, const HashMap<std::string, FileFacts>& cache, long long cacheWriteNs, IngestFileScan& … [line truncated: 12 more bytes on this line] -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::uint64_t blobChecksum( std::string_view s ) noexcept +inline ContentIdIndex contentIdsBySym( const IngestResult& ing, const Graph& g, std::string_view root ) inline bool ingestCommitTree( const std::string& root, const std::string& sha, const std::vector<std::string>& excludes, std::size_t maxFileBytes, IngestResult&… -inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, … [line truncated: 22 more bytes on this line] -inline std::vector<CacheWriteRow> buildCacheWritePlan( const std::vector<std::uint32_t>& orderIn, const std::vector<std::uint64_t>& pathHashes, const s … [line truncated: 96 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::vector<CacheWriteRow> buildCacheWritePlan( const std::vector<std::uint32_t>& orderIn, const std::vector<std::uint64_t>& pathHashes, const s … [line truncated: 16 more bytes on this line] +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, … [line truncated: 54 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 ) struct IngestFileScan -inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t>& fi … [line truncated: 16 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 ) -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>& cache, cons … [line truncated: 91 more bytes on this line] +inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t>& f … [line truncated: 17 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 ) +inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, cons … [line truncated: 91 more bytes on this line] +inline std::string spanTierMemoPath( const std::string& diskPath ) inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) -inline NodeId resolveHandleAll( const McpIndex& ix, std::uint64_t idHash, std::vector< NodeId >& matches ) -… [45 more display lines; full output is 8934 bytes on 1 raw line(s)] +inline NodeId resolveHandleAll( const McpIndex& ix, std::uint64_t idHash, std::vector< NodeId >& matches ) +… [46 more display lines; full output is 9078 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: 3.10s** +**wall time: 1.82s** ````` - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -259,64 +255,64 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8930 > budget=100 } if( teleportMass > 0.0 ) { -… [12 more display lines; full output is 4309 bytes on 29 raw line(s)] +… [12 more display lines; full output is 4315 bytes on 29 raw line(s)] ````` ## `./build/ripwire . --for="rankGraphTeleport" --no-route` *Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* -**wall time: 3.01s** +**wall time: 1.36s** ````` - + - + 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", bo … [line truncated: 11 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", bo … [line truncated: 11 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::string didYouMean( const IngestResult& ing, std::string_view name ) -inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = nullptr ) -inline const McpIndex& getIndex( const std::string& root ) -PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double& … [line truncated: 37 more bytes on this line] -inline std::vector<float> churnDecayTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, bool* outHasChurnEvidence = nullptr )< … [line truncated: 3 more bytes on this line] +inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = nullptr ) +PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double& … [line truncated: 37 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 = nullptr )< … [line truncated: 3 more bytes on this line] 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 ) -inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 -### Fixed — `rank_by` over MCP no longer answers a different rankin … [line truncated: 57 more bytes on this line] +inline std::vector<float> anchoredLexicalRank( const Graph& g, const std::vector<float>& lex ) +inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 +### Fixed — `rank_by` over MCP no longer answers a different rankin … [line truncated: 57 more bytes on this line] ## Coverage inventory #### The convergence disclosure contract -… [100 more display lines; full output is 14960 bytes on 71 raw line(s)] +… [101 more display lines; full output is 14971 bytes on 72 raw line(s)] ````` ## `./build/ripwire . --for="rankGraphTeleport" --signatures-only` *T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribute) — contrast with the terminal default above.* -**wall time: 3.04s** +**wall time: 1.86s** ````` - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -327,53 +323,53 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8930 > budget=100 *Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothing is cut and the header says so ([adaptive: kept N of N]).* -**wall time: 3.01s** +**wall time: 1.84s** ````` - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) - + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -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::array<LangEntry, 51> kLangTable = -constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u -inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept -std::vector<LocalNameFact> collectGatedLocalNames( std::string_view defBytes, std::uint32_t defStartLine, Lang lang ) +struct FileHealth +enum class AstWalk : std::uint8_t +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 +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::array<LangEntry, 51> kLangTable = +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +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 ) -std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] -bool jsonNestsTooDeep( std::string_view bytes ) noexcept -constexpr std::uint32_t kMaxYamlNestDepth = 64u -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 bool hasMainGuard( std::string_view source ) -struct LintRule -enum class DisclosureWhy : std::uint8_t -struct AstQuerySpec -inline bool spanTierMemoTryLoad( bool useMemo, const std::string& diskPath, const StatInfo& now, SpanTierMap& out ) -… [55 more display lines; full output is 9102 bytes on 1 raw line(s)] +std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +constexpr std::uint32_t kMaxYamlNestDepth = 64u +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +inline bool hasMainGuard( std::string_view source ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [55 more display lines; full output is 9170 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25"` *Mention anchoring (default-on): a path and a Symbol literally named in the task get lifted; the header says what anchored.* -**wall time: 3.64s** +**wall time: 1.33s** ````` - - + 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 ) @@ -382,38 +378,38 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8930 > 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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) -struct LensRanking -struct NameCorpusStats -inline bool hasIdentifierShape( std::string_view w ) noexcept +struct NameCorpusStats +struct LensRanking +inline bool hasIdentifierShape( std::string_view w ) noexcept inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) struct Bm25Params 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 reads the W … [line truncated: 57 more bytes on this line] -struct CandidateProvenance -std::optional<int> runEvalViews( const MainDispatch& d ) +rw::LensRanking computeLensRanking( const MainDispatch& d, std::string_view task, bool compactCandidate = false, bool fullDistribution = false ) // deep-tail: the file-grain tail reads the W … [line truncated: 57 more bytes on this line] +struct CandidateProvenance +std::optional<int> runEvalViews( const MainDispatch& d ) inline constexpr int kLexWeightDoc = 2 -inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) struct RecallSectionPick inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) -inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept -… [56 more display lines; full output is 9443 bytes on 1 raw line(s)] +### Graph-Ranked Retrieval: It finds the right files more often than the alternatives +… [56 more display lines; full output is 9458 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25" --no-mention-boost` *Same task with the anchor disabled — the contrast the flag exists for.* -**wall time: 2.96s** +**wall time: 1.83s** ````` - - - + + 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 ) @@ -421,38 +417,36 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8930 > 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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) -struct LensRanking -struct NameCorpusStats -inline bool hasIdentifierShape( std::string_view w ) noexcept +struct NameCorpusStats +struct LensRanking +inline bool hasIdentifierShape( std::string_view w ) noexcept inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) struct Bm25Params 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 reads the W … [line truncated: 57 more bytes on this line] -struct CandidateProvenance -std::optional<int> runEvalViews( const MainDispatch& d ) +rw::LensRanking computeLensRanking( const MainDispatch& d, std::string_view task, bool compactCandidate = false, bool fullDistribution = false ) // deep-tail: the file-grain tail reads the W … [line truncated: 57 more bytes on this line] +struct CandidateProvenance +std::optional<int> runEvalViews( const MainDispatch& d ) inline constexpr int kLexWeightDoc = 2 -inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) struct RecallSectionPick inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) -inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept -… [53 more display lines; full output is 9528 bytes on 1 raw line(s)] +inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept +… [58 more display lines; full output is 9509 bytes on 1 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.33s** - ````` - + @@ -465,12 +459,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8930 > budget=100 *The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* -**wall time: 1.80s** +**wall time: 1.11s** ````` - + f, A&&... a ) @@ -492,12 +486,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8930 > 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.65s** - ````` - + ripwire '.' --verify='calls(runDefaultMap, rankGraphTeleport)' @@ -508,12 +500,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8930 > budget=100 *The honest half of the contract: a task with no ripwire-shaped evidence ABSTAINS with zero commands rather than guessing.* -**wall time: 1.15s** - ````` - + ````` @@ -521,12 +511,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8930 > budget=100 *Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* -**wall time: 2.05s** +**wall time: 1.12s** ````` -ripwire recall — "quality delta gating exit codes" — 113 relevant of 208 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5307 +ripwire recall — "quality delta gating exit codes" — 113 relevant of 209 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5403 -━━ README.md (relevance 8.782) ━━ [sections: 2 of 24 selected (60 in doc), section-granular; whole doc 204511 B; lines="2595-2606,2607-2614"; dropped_by_budget=22] +━━ README.md (relevance 8.289) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 211997 B; lines="2688-2700,2701-2708"; dropped_by_budget=23] #### 6.2 Exit codes This is the part to wire into a script. @@ -538,6 +528,7 @@ This is the part to wire into a script. | 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 @@ -549,64 +540,49 @@ refuses by default. -━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.329) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.295) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] ### Root spelling (maintainer-owned; reproduce it once so you recognise it) ```bash -cmake -S . -B build && cmake --build build -j -… [158 more lines, 13543 bytes total] +… [159 more lines, 13788 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 1729927-byte scratch dir of DUMPED TOOL OUTPUT (a git log, this repo's own generated command reference, --help text, an architecture doc, a fabricated JSON access log) instead of a source repo — no index to build, no daemon.* - -**wall time: 2.85s** +*The directory-as-knowledge-base pattern: --recall pointed at a 1437506-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 10.656) ━━ [sections: 1 of 154 selected (183 in doc), section-granular; whole doc 717765 B; lines="515-540"; dropped_by_budget=153] [truncated: 1854 of 4242 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base +━━ commands.md (relevance 10.149) ━━ [sections: 1 of 154 selected (183 in doc), section-granular; whole doc 527781 B; lines="3662-3666"; dropped_by_budget=153] [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] -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. +(capped: 1 of 2 relevant document files omitted — raise --max-tokens/budget_tokens or narrow the query for 1 more (~2548-byte budget)) +````` -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. … +stderr: -(capped: 1 of 2 relevant document files omitted — raise --max-tokens/budget_tokens or narrow the query for 1 more (~2548-byte budget)) +````` +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: 1.35s** - ````` - - + + + + + + + @@ -616,11 +592,6 @@ and both are yours, because the file you write is the only thing that sets them: - - - - - @@ -634,14 +605,14 @@ and both are yours, because the file you write is the only thing that sets them: -… [367 more display lines; full output is 11207 bytes on 1 raw line(s)] +… [366 more display lines; full output is 11141 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --html=/aux/map2.html` *Self-contained HTML force-directed call graph.* -**wall time: 3.70s** +**wall time: 1.38s** ````` (empty) @@ -650,36 +621,36 @@ and both are yours, because the file you write is the only thing that sets them: Artifact written: ````` -158925 /aux/map2.html +158727 /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.31s** - ````` - + - - - + + + + + - + ````` @@ -691,14 +662,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.23s** - ````` - - + + @@ -724,21 +693,19 @@ Artifact written: -… [222 more display lines; full output is 9255 bytes on 1 raw line(s)] +… [224 more display lines; full output is 9291 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.61s** - ````` - - + + @@ -764,21 +731,19 @@ Artifact written: -… [2927 more display lines; full output is 77502 bytes on 1 raw line(s)] +… [3003 more display lines; full output is 79462 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: 1.20s** - ````` - - + + @@ -804,25 +769,23 @@ Artifact written: -… [30 more display lines; full output is 4312 bytes on 1 raw line(s)] +… [31 more display lines; full output is 4330 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --callers=rankGraphTeleport` *Who calls SYM (1-hop in-edges).* -**wall time: 1.18s** - ````` - - - - + + + + - - - + + + ````` @@ -830,19 +793,17 @@ Artifact written: *The prose legend restored: compact is the DEFAULT (bcb796959), so the pair above already shows the compact posture — --legend=full opts back into the ~3 KB explanatory legend the default trims. Rows and completeness attributes unchanged; only the legend differs.* -**wall time: 1.51s** - ````` - - - - + + + + - - - + + + ````` @@ -850,7 +811,7 @@ Artifact written: *Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* -**exit code: 1** — **wall time: 1.31s** +**exit code: 1** ````` (empty) @@ -866,12 +827,10 @@ ripwire: --callers symbol not found: DoesNotExist *What SYM calls (1-hop out-edges).* -**wall time: 1.64s** - ````` - - + + @@ -886,20 +845,20 @@ 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.85s** +**wall time: 1.01s** ````` - - - - + + + + - - - - - + + + + + ````` @@ -907,136 +866,128 @@ ripwire: --callers symbol not found: DoesNotExist *Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* -**wall time: 1.31s** - ````` - - - - - - - + + + + + + + - - + + - - - - + + + + - - - - - - - - - - - - -… [28 more display lines; full output is 3647 bytes on 1 raw line(s)] + + + + + + + + + + + + +… [28 more display lines; full output is 3648 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.24s** - ````` - - - - - - + + + + + + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - -… [73 more display lines; full output is 5571 bytes on 1 raw line(s)] + +… [73 more display lines; full output is 5567 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.60s** - ````` - - - - - - - - - - - + + + + + + + + + + + - + - - - - - - - - - - - - - - - -… [73 more display lines; full output is 5423 bytes on 1 raw line(s)] + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5427 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.23s** - ````` - - - - - - + + + + + + ````` @@ -1044,15 +995,13 @@ ripwire: --callers symbol not found: DoesNotExist *Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* -**wall time: 1.18s** - ````` - + - - + + @@ -1063,51 +1012,48 @@ ripwire: --callers symbol not found: DoesNotExist *Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* -**wall time: 1.66s** - ````` - - - - - - - + + + + + + + + + + + - - - + + - - - - - + + + - - - - - - - - - - -… [46 more display lines; full output is 5015 bytes on 1 raw line(s)] + + + + + + + + + +… [46 more display lines; full output is 5179 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --mentions=rankGraphTeleport` *Markdown docs that name SYM in a backtick (doc<->code edges).* -**wall time: 1.32s** - ````` - + + @@ -1121,11 +1067,9 @@ ripwire: --callers symbol not found: DoesNotExist *Test files that transitively reach the changed file.* -**wall time: 1.79s** - ````` - + @@ -1138,35 +1082,33 @@ ripwire: --callers symbol not found: DoesNotExist - + ````` ## `./build/ripwire . --situ` -*Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below).* - -**wall time: 1.83s** +*Mid-task situational report for the current git diff — recorded against a DIRTY tree (contrast with the sandbox run below).* ````` ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: ce5c0b3fc +at: dac3b8205+dirty (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.* +*Pre-PR gate recorded against a DIRTY tree, so the obligations below are the working copy's real ones — the recorded exit code says which way it went.* -**wall time: 3.24s** +**wall time: 1.76s** ````` - ````` @@ -1174,12 +1116,12 @@ at: ce5c0b3fc *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.80s** +**wall time: 1.22s** ````` @@ -1357,11 +1299,11 @@ at: ce5c0b3fc *The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* -**wall time: 6.91s** +**wall time: 3.88s** ````` -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 @@ -1397,20 +1339,20 @@ at: ce5c0b3fc *Raw BM25 ranking (debug lens; --for is the real verb).* -**wall time: 1.90s** +**wall time: 1.17s** ````` - + - + - + @@ -1424,12 +1366,12 @@ at: ce5c0b3fc - + - + … [28 more display lines; full output is 3515 bytes on 1 raw line(s)] ````` @@ -1442,10 +1384,10 @@ at: ce5c0b3fc *Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* -**wall time: 2.99s** +**wall time: 1.85s** ````` - + @@ -1457,64 +1399,62 @@ at: ce5c0b3fc 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] What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure -inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) -struct RankedGraph -inline std::uint32_t sliceRdIterationCeiling() noexcept -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 … [line truncated: 7 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 … [line truncated: 7 more bytes on this line] +inline std::uint32_t sliceRdIterationCeiling() noexcept struct PageRankRun -inline std::uint32_t navRelevanceWeight( const Graph& g, NodeId n ) noexcept -COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "s… -inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) -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", … [line truncated: 90 more bytes on this line] +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 ) +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… +COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "th … [line truncated: 61 more bytes on this line] inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept inline std::string powerShellSingleQuote( std::string_view s ) noexcept -inline std::string path_prepend_hint( std::string_view dir ) -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` -### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] -### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] +inline std::string path_prepend_hint( std::string_view dir ) +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] +### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] ## Background — read these, in this order ## Design space and constraints -… [107 more display lines; full output is 14206 bytes on 72 raw line(s)] +… [107 more display lines; full output is 14202 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: 92.7% fewer bytes at top-10, 88.7% at top-50, 87.6% at top-100 (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.39s** - ````` - + - - - - - - - - - - - - + - + - + + + + + + + + + + + + … [138 more display lines; full output is 12056 bytes on 1 raw line(s)] ````` @@ -1522,14 +1462,12 @@ at: ce5c0b3fc *Control-flow skeleton of one symbol, payload-only via the new --top-k=0.* -**wall time: 1.18s** - ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1553,14 +1491,12 @@ at: ce5c0b3fc *CHANGED: a line range on --outline is now STRIPPED with a stderr note (it used to refuse).* -**wall time: 1.74s** - ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1590,14 +1526,12 @@ ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — *Full body + inline callee signatures.* -**wall time: 1.25s** - ````` - + - + + + 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 (defs all language-filtered, or import/pointer 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). 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. --> + - - - - - - - - - - - - - + + - + + + - + - + -… [19 more display lines; full output is 5303 bytes on 1 raw line(s)] + + + + + + + + + +… [21 more display lines; full output is 5523 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --deps` *File->file dependency graph (god-files, cycles).* -**wall time: 1.27s** - ````` - - - - + + + + - - + + + - - + - + - - + + - + - + - -… [867 more display lines; full output is 22664 bytes on 1 raw line(s)] + +… [878 more display lines; full output is 22900 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --hotspots` *Complexity x recent git churn (maintenance pain).* -**wall time: 3.16s** +**wall time: 1.92s** ````` - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - + + + - - + + + + - - - - -… [14 more display lines; full output is 5741 bytes on 1 raw line(s)] + + +… [14 more display lines; full output is 5752 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --clones` *Token-normalized duplicate bodies.* -**wall time: 4.48s** +**wall time: 2.85s** ````` - + - + - + @@ -1935,56 +1859,56 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - + -… [307 more display lines; full output is 16171 bytes on 1 raw line(s)] +… [307 more display lines; full output is 16180 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --cochange` *Files that change together in git (hidden coupling).* -**wall time: 3.26s** +**wall time: 1.75s** ````` - + + - - + + + - - - - - + + + + + - - - + - - + + - - - - + - - + + + + + ````` @@ -1992,52 +1916,50 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Hotspots scoped to RECENT churn (the regression lens).* -**wall time: 2.19s** +**wall time: 1.16s** ````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [13 more display lines; full output is 5104 bytes on 1 raw line(s)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 5123 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --arch=test/archfix/rules.txt` *Enforce layering rules (exit 2 on violation) — run against the repo's own test fixture rules.* -**wall time: 1.66s** - ````` - @@ -2065,19 +1987,19 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan -… [516 more display lines; full output is 75372 bytes on 1 raw line(s)] +… [528 more display lines; full output is 77108 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint` *Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* -**wall time: 15.47s** +**wall time: 7.97s** ````` - - + + @@ -2085,34 +2007,34 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - - + + - - + + - + - + - - - -… [701 more display lines; full output is 69541 bytes on 1 raw line(s)] + + + +… [704 more display lines; full output is 69396 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: 3.72s** +**wall time: 2.02s** ````` @@ -2131,7 +2053,7 @@ 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:435, rw::parseLintRuleFile(const std::string&, std::string_view, std::vector&):: — logged once per site) +[math degraded] lint-rules: malformed rule file skipped (lintrules.h:438, rw::parseLintRuleFile(const std::string&, std::string_view, std::vector&):: — logged once per site) ripwire: AST query did not compile for any grammar: (this_is_not_a_real_node @x @@@ ((( ) ````` @@ -2139,8 +2061,6 @@ ripwire: AST query did not compile for any grammar: (this_is_not_a_real_node @x *Join MEASURED heat onto --lint findings — runs in a tiny fabricated demo corpus (one cache-pointer-chase-loop finding under a PROFILE_SCOPE site) because a real report needs a RIPWIRE_PROFILE build; the finding inside the profiled scope gains heat_* columns from the report's #PROF_TSV row.* -**wall time: 1.22s** - Input file: ````` @@ -2194,177 +2114,167 @@ The joined finding — past the display cut above, extracted so the join is visi *Cluster the call graph into cohesive modules.* -**wall time: 1.24s** - ````` - + - + + - - - - - - + + + + + - - + + - - - - - - + + + + + + -… [186 more display lines; full output is 16600 bytes on 1 raw line(s)] +… [186 more display lines; full output is 16547 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --zoom` *Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels_shown="2" of levels= BY DEFAULT over the 40 largest top modules (~8 KB, where the whole tree is ~220 KB); a module AT the cut carries children=.* -**wall time: 1.14s** - ````` - - + + + - - + + - - + - + + - - + + - - + - - + + + - - -… [135 more display lines; full output is 6970 bytes on 1 raw line(s)] + +… [171 more display lines; full output is 8330 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --zoom --zoom-levels=3` *The restoring knob: --zoom-levels=N prints N levels (0 = the whole tree); here three, ~11 KB.* -**wall time: 1.54s** - ````` - - - + + + + + - - - + + + - - - + - + + - - + - + + + - - -… [221 more display lines; full output is 9889 bytes on 1 raw line(s)] +… [705 more display lines; full output is 27730 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --report` *Architecture summary (modules, god-files, cycles) as markdown.* -**wall time: 1.27s** - ````` # ripwire architecture report -2345 files · 22606 symbols · 31600 edges · 2188 modules (10489 call-graph isolated) +2376 files · 23174 symbols · 32805 edges · 2235 modules (10668 call-graph isolated) Root: `.` -Call-graph isolate provenance: 2793 declaration, 1471 header, 1804 source, 4421 document; 0 connected Louvain singletons +Call-graph isolate provenance: 2842 declaration, 1520 header, 1842 source, 4464 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 2188) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1303 symbols -- **src::append@elixir_resolve.h:111:4875 [resolve,add,collect]** — 1024 symbols -- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 516 symbols -- **src::kindIs@infra/nodekind.h:46:3271 [emit,collect,push]** — 244 symbols +## Modules (call-graph clusters; showing 12 of 2235) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1270 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,collect,append]** — 788 symbols +- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 549 symbols +- **src::assign@infra/svector.h:342:19905 [compute,add,collect]** — 461 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [emit,collect,push]** — 257 symbols - **docs::`--top-k=N`@COMMANDS.md:59:8696 [pack,allow,insert]** — 173 symbols -- **src::relForHash@model.h:1339:108861 [compute,clone,insert]** — 50 symbols +- **src::relForHash@model.h:1396:112049 [compute,clone,read]** — 52 symbols +- **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [erase,find,count]** — 52 symbols - **src/infra::active@profilePmc.h:845:34910 [print,ensure,report]** — 49 symbols -- **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [erase,find,count]** — 47 symbols -- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,call,compute]** — 43 symbols -- **src::assign@infra/svector.h:342:19905 [scan,sort,compute]** — 40 symbols -- **src/infra::buf@svector.h:125:8942 [insert,pack,render]** — 39 symbols -- **test::no@decltodefcheck.sh:126:11495 [run]** — 30 symbols - -## God files (most depended-on; showing 10 of 416) -- `test/lib/clean-env.sh` — 168 dependents +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,call,compute]** — 46 symbols +- **src/infra::buf@svector.h:125:8942 [insert,pack,render]** — 38 symbols +- **test::no@decltodefcheck.sh:127:11708 [run]** — 30 symbols + +## God files (most depended-on; showing 10 of 422) +- `test/lib/clean-env.sh` — 173 dependents - `src/infra/emit.h` — 86 dependents -- `src/model.h` — 80 dependents -- `src/infra/Diagnostics.h` — 55 dependents +- `src/model.h` — 81 dependents +- `src/infra/Diagnostics.h` — 57 dependents - `src/serialize.h` — 37 dependents -… [32 more lines, 3550 bytes total] +… [32 more lines, 3556 bytes total] ````` ## `./build/ripwire . --seams` *Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* -**wall time: 1.11s** - ````` - - - - + + + + - + @@ -2374,11 +2284,11 @@ Call-graph isolate provenance: 2793 declaration, 1471 header, 1804 source, 4421 - - - - - + + + + + @@ -2387,33 +2297,31 @@ Call-graph isolate provenance: 2793 declaration, 1471 header, 1804 source, 4421 -… [105 more display lines; full output is 13709 bytes on 1 raw line(s)] +… [104 more display lines; full output is 13569 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --mermaid` *Module (directory) dependency graph as a Mermaid diagram.* -**wall time: 1.49s** - ````` %% 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
6405"] - n102["src/infra
1250"] + n101["src
6753"] + n102["src/infra
1275"] end subgraph sg1 ["test"] - n103["test
4696"] + n103["test
4771"] n294["test/fixtures/recallpassage
314"] n280["test/expandmodefix
151"] n282["test/expandsibsfix
149"] - n411["test/rubyattrsfix
85"] - n348["test/massfix
77"] - n314["test/javamethodreffix
62"] + n423["test/rubyattrsfix
85"] + n360["test/massfix
77"] + n326["test/javamethodreffix
62"] end - n73["docs
808"] - n2[".github
589"] + n73["docs
811"] + n2[".github
594"] subgraph sg4 ["bench"] n4["bench
531"] n5["bench/agentloop
331"] @@ -2427,29 +2335,29 @@ flowchart LR n45["bench/locbench/results/r1cpp_anchorhop
94"] n11["bench/arb
85"] n56["bench/nestcal/r1-2026-08-07
84"] -… [22 more lines, 1621 bytes total] +… [21 more lines, 1606 bytes total] ````` ## `./build/ripwire . --owners` *Bus-factor: recency-weighted author ownership per file.* -**wall time: 3.22s** +**wall time: 1.33s** ````` - - + + - + - + - - + + @@ -2467,18 +2375,16 @@ flowchart LR -… [875 more display lines; full output is 97517 bytes on 1 raw line(s)] +… [879 more display lines; full output is 97525 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.63s** - ````` - + ````` @@ -2487,8 +2393,6 @@ flowchart LR *Which symbols a TEST FILE exercises — the reverse direction of --affected.* -**wall time: 1.20s** - `````
````` ## `./build/ripwire . --quality-delta` -*On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* +*Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit.* -**wall time: 51.57s** +**wall time: 52.96s** ````` - -, acked. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). --> + @@ -2540,13 +2442,13 @@ flowchart LR - - + + - -… [96 more display lines; full output is 12447 bytes on 1 raw line(s)] + +… [95 more display lines; full output is 12147 bytes on 1 raw line(s)] ````` stderr: @@ -2557,32 +2459,30 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g ## `./build/ripwire . --edit-check=rankGraphTeleport` -*Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree).* - -**wall time: 37.11s** +*Fast per-symbol post-edit contract check vs git HEAD — recorded against a DIRTY tree, so the verdict describes the working copy, not HEAD alone, and the recorded exit code is the working copy's.* ````` - - - - - - - + + + + + + ````` ## `./build/ripwire . --pr-context` -*No-LLM review-evidence bundle for the working-tree diff (clean tree = empty).* +*No-LLM review-evidence bundle for the working-tree diff — recorded against a DIRTY tree, so it is populated rather than empty.* -**wall time: 1.93s** +**wall time: 1.01s** ````` - ````` @@ -2591,58 +2491,66 @@ 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: 5.89s** +**wall time: 2.97s** ````` - - + - - + + + - - - - - - - - - - - - - - - - - - - - - - -… [284 more display lines; full output is 18471 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: 49.22s** +**wall time: 32.30s** ````` - - + + + + + - + + + ````` @@ -2651,38 +2559,25 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Which refs of the one branch `main` (no ref family left to select on this checkout) still hold divergent authored work vs HEAD, with verdicts.* -**wall time: 4.03s** - -````` - - - - - - - - - - - - - - - - +**wall time: 1.24s** + +````` + + + + + ````` -## `./build/ripwire . --stray-content=upstream-paging` +## `./build/ripwire . --stray-content=sync-rebase` -*A second selection, the one branch `upstream-paging` (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.83s** +*A second selection, the one branch `sync-rebase` (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=.* ````` - + ````` @@ -2690,47 +2585,47 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Select the genuinely-unmerged refs of the one branch `main` (no ref family left to select on this checkout) 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: 22.90s** +**wall time: 24.58s** ````` - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - -… [205 more display lines; full output is 17196 bytes on 1 raw line(s)] + + + + + + + + + + + + + + + + + + + + + + + + +… [59 more display lines; full output is 6685 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --stray-content=zzzz-no-such-ref --plan` *A --plan filter that selects NO ref REFUSES (exit 1) naming the substring — before the wave-3 close this fell through to the '>512 refs match' sentence, and --abi under the same filter answered an empty measurement at exit 0.* -**exit code: 1** — **wall time: 2.07s** +**exit code: 1** — **wall time: 1.07s** ````` (empty) @@ -2747,18 +2642,11 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Cross-branch ABI-break gate over the one branch `main` (no ref family left to select on this checkout): 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: 3.03s** +**wall time: 1.29s** ````` - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - -… [34 more display lines; full output is 15389 bytes on 1 raw line(s)] + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [34 more display lines; full output is 15392 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --whereis=computeOnePairOverlap --with-history` *Same, plus a git-history row (never / removed-by-commit) for names no tree carries.* -**wall time: 27.74s** +**wall time: 16.93s** ````` - - - - - - - - - - - - - - + + + + + + + + + + + + + - + @@ -2879,18 +2767,18 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer -… [358 more display lines; full output is 22151 bytes on 1 raw line(s)] +… [366 more display lines; full output is 22644 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --flags --flip=RIPWIRE_ASAN` *Blast radius of turning ONE gate on: live code, symbols, transitive reach, covering tests.* -**wall time: 2.20s** +**wall time: 1.48s** ````` - + @@ -2921,7 +2809,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Unknown-gate refusal (exit 1) with a did-you-mean from a real edit distance (one character off RIPWIRE_ASAN).* -**exit code: 1** — **wall time: 2.20s** +**exit code: 1** — **wall time: 1.07s** ````` (empty) @@ -2938,47 +2826,47 @@ ripwire: run `ripwire . --flags` for the gate table *NEW VERB: pre-hoc lane plan — which of 3 parallel worktrees would COLLIDE, before a line is written. JSON on stdout.* -**wall time: 4.60s** - -````` -{"v":1,"verb":"plan-lanes","at":"ce5c0b3fc","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":2345,"symbols":22606,"edges":316 … [line truncated: 338 more bytes on this line] -"symbols":[{"p":"./src/docdrift.h","n":"DriftResult","scope":"DriftResult","l":284,"id":"./src/docdrift.h::DriftResult::DriftResult"}, -{"p":"./src/docdrift.h","n":"computeDocDrift","scope":"docdrift","l":2331,"id":"./src/docdrift.h::docdrift::computeDocDrift"}, -{"p":"./src/docdrift.h","n":"kDocDriftLegend","scope":"docdrift","l":2622,"id":"./src/docdrift.h::docdrift::kDocDriftLegend"}, -{"p":"./src/docdrift.h","n":"writeDocDriftPage","scope":"docdrift","l":2792,"id":"./src/docdrift.h::docdrift::writeDocDriftPage"}, -{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","l":630,"id":"./src/mcpverbs.h::rw::docDriftText"}, -{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","l":1649,"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/gitmine.h","n":"gitWindowBoundarySha","scope":"rw","key":"18627516699d7062","id":"./src/gitmine.h::rw::gitWindowBoundarySha","id_addressable":true,"id_collides_with":0,"l":1605,"ord":0,"overloads":1,"amb":3,"cx":3,"ccx":2,"churn":56,"tested":0}, -{"p":"./src/crossref.h","n":"writeWhereisPage","scope":"crossref","key":"3a0425468e6a18cf","id":"./src/crossref.h::crossref::writeWhereisPage","id_addressable":true,"id_collides_with":0,"l":2198,"ord":0,"overloads":1,"amb":11,"cx":22,"ccx":23,"churn":48,"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":2568,"ord":0,"overloads":1,"amb":3,"cx":6,"ccx":6,"churn":31,"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":1210,"ord":0,"overloads":1,"amb":2,"cx":8,"ccx":8,"churn":28,"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":2747,"ord":0,"overloads":1,"amb":0,"cx":6,"ccx":5,"churn":31,"tested":0}, -{"p":"./src/taskroute.h","n":"classifyRoutes","scope":"rw::taskroute","key":"8f328a95b7861e33","id":"./src/taskroute.h::rw::taskroute::classifyRoutes","id_addressable":true,"id_collides_with":0,"l":1592,"ord":0,"overloads":1,"amb":12,"cx":18,"ccx":20,"churn":34,"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":2320,"ord":0,"overloads":1,"amb":2,"cx":2,"ccx":1,"churn":31,"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":1302,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":45,"tested":0}, -{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":2849,"ord":0,"overloads":1,"amb":33,"cx":8,"ccx":11,"churn":395,"tested":0}, -{"p":"./src/gitmine.h","n":"resolveSinceScope","scope":"rw","key":"9caaa3dbeaa688d0","id":"./src/gitmine.h::rw::resolveSinceScope","id_addressable":true,"id_collides_with":0,"l":317,"ord":0,"overloads":1,"amb":3,"cx":7,"ccx":7,"churn":56,"tested":0}, +**wall time: 2.06s** + +````` +{"v":1,"verb":"plan-lanes","at":"dac3b8205+dirty","root":".","task":"add a --since filter to the doc-drift verb and cover it with tests","source":"partition","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2376,"symbols":23174,"edge … [line truncated: 344 more bytes on this line] +"symbols":[{"p":"./src/docdrift.h","n":"DriftResult","scope":"DriftResult","l":286,"id":"./src/docdrift.h::DriftResult::DriftResult"}, +{"p":"./src/docdrift.h","n":"computeDocDrift","scope":"docdrift","l":2441,"id":"./src/docdrift.h::docdrift::computeDocDrift"}, +{"p":"./src/docdrift.h","n":"kDocDriftLegend","scope":"docdrift","l":2739,"id":"./src/docdrift.h::docdrift::kDocDriftLegend"}, +{"p":"./src/docdrift.h","n":"writeDocDriftPage","scope":"docdrift","l":2909,"id":"./src/docdrift.h::docdrift::writeDocDriftPage"}, +{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","l":632,"id":"./src/mcpverbs.h::rw::docDriftText"}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","l":1634,"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/crossref.h","n":"writeWhereisPage","scope":"crossref","key":"3a0425468e6a18cf","id":"./src/crossref.h::crossref::writeWhereisPage","id_addressable":true,"id_collides_with":0,"l":2218,"ord":0,"overloads":1,"amb":11,"cx":22,"ccx":23,"churn":51,"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":3,"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":2,"cx":8,"ccx":8,"churn":31,"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/testmap.h","n":"scriptGatesUnmodelledCount","scope":"rw","key":"98c4487e2e9e08bc","id":"./src/testmap.h::rw::scriptGatesUnmodelledCount","id_addressable":true,"id_collides_with":0,"l":1302,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":46,"tested":0}, +{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":2855,"ord":0,"overloads":1,"amb":33,"cx":8,"ccx":11,"churn":411,"tested":0}, +{"p":"./src/gitmine.h","n":"resolveSinceScope","scope":"rw","key":"9caaa3dbeaa688d0","id":"./src/gitmine.h::rw::resolveSinceScope","id_addressable":true,"id_collides_with":0,"l":317,"ord":0,"overloads":1,"amb":3,"cx":7,"ccx":7,"churn":57,"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":350,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":18,"tested":0}, -{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":684,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":395,"tested":0}, +{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":685,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":411,"tested":0}, {"p":"./src/taskroute.h","n":"testCoverageTaskChoice","scope":"rw::taskroute","key":"bf72991aa28355c7","id":"./src/taskroute.h::rw::taskroute::testCoverageTaskChoice","id_addressable":true,"id_collides_with":0,"l":1365,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":34,"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":5103,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":275,"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":571,"ord":0,"overloads":1,"amb":16,"cx":10,"ccx":5,"churn":43,"tested":0}], -"files":[{"p":"./src/crossref.h","symbols":1,"churn":48,"ccx":23,"hotspot_rank":26}, -{"p":"./src/darkflags.h","symbols":1,"churn":28,"ccx":8,"hotspot_rank":44}, -{"p":"./src/docdrift.h","symbols":3,"churn":31,"ccx":12,"hotspot_rank":30}, -{"p":"./src/gitmine.h","symbols":2,"churn":56,"ccx":9,"hotspot_rank":17}, -{"p":"./src/main.cpp","symbols":2,"churn":395,"ccx":12,"hotspot_rank":4}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":275,"ccx":2,"hotspot_rank":5}, -{"p":"./src/pageview.h","symbols":1,"churn":18,"ccx":2,"hotspot_rank":126}, -{"p":"./src/taskroute.h","symbols":2,"churn":34,"ccx":22,"hotspot_rank":39}, -… [72 more display lines; full output is 20587 bytes on 1 raw line(s)] +{"p":"./src/mcpverbs.h","n":"unknownSubVerbRefusal","scope":"rw","key":"e336d39b0a6addea","id":"./src/mcpverbs.h::rw::unknownSubVerbRefusal","id_addressable":true,"id_collides_with":0,"l":5145,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":293,"tested":0}], +"files":[{"p":"./src/crossref.h","symbols":1,"churn":51,"ccx":23,"hotspot_rank":26}, +{"p":"./src/darkflags.h","symbols":1,"churn":31,"ccx":8,"hotspot_rank":44}, +{"p":"./src/docdrift.h","symbols":2,"churn":36,"ccx":11,"hotspot_rank":30}, +{"p":"./src/gitmine.h","symbols":1,"churn":57,"ccx":7,"hotspot_rank":18}, +{"p":"./src/main.cpp","symbols":2,"churn":411,"ccx":12,"hotspot_rank":4}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":293,"ccx":2,"hotspot_rank":5}, +{"p":"./src/pageview.h","symbols":1,"churn":18,"ccx":2,"hotspot_rank":130}, +{"p":"./src/taskroute.h","symbols":1,"churn":34,"ccx":2,"hotspot_rank":40}, +{"p":"./src/testmap.h","symbols":1,"churn":46,"ccx":4,"hotspot_rank":35}, +{"p":"./src/verbs_change.h","symbols":1,"churn":68,"ccx":8,"hotspot_rank":22}]},"blast_radius":{"reaches":52,"files_total":18,"capped":false,"files":["./src/crossref.h","./src/darkflags.h","./src/docdrift.h","./src/editplan.h","./src/flipimpact.h","./src/gitmine.h","./src/main.cpp","./src/mcp.h","./ … [line truncated: 222 more bytes on this line] +"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":52,"module_span":1,"notes":[], +"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":12,"files":10,"module_span":1,"max_ccx":23,"sum_ccx":79,"ambiguous_calls":58,"blast_reaches":52,"contract_touches":0,"conflicts":0,"untested":52," … [line truncated: 140 more bytes on this line] +… [80 more display lines; full output is 21357 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: 5.05s** +**wall time: 2.94s** Input file: @@ -2989,37 +2877,37 @@ write regression tests for the new filter ````` ````` -{"v":1,"verb":"plan-lanes","at":"ce5c0b3fc","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2345,"symbols":22606,"edges":31600,"ambiguous":9496,"unresolved":12841},"carve":null,"core":{"files" … [line truncated: 4 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"dac3b8205+dirty","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2376,"symbols":23174,"edges":32805,"ambiguous":9828,"unresolved":13184},"carve":null,"core":{" … [line truncated: 10 more bytes on this line] "symbols":[]},"lanes":[{"id":"lane-0","task":"add a --since filter to the doc-drift verb","claims":{"symbols":[{"p":"./src/docdrift.h","n":"recordUnchecked","scope":"docdrift","key":"12641dab14abc8fd","id":"./src/docdrift.h::docdrift::recordUnchecked","id_addressable":true,"id_collides_with":0,"l":2 … [line truncated: 72 more bytes on this line] -{"p":"./src/docdrift.h","n":"sortWeakGroupsByPath","scope":"docdrift","key":"1944ba506280ff80","id":"./src/docdrift.h::docdrift::sortWeakGroupsByPath","id_addressable":true,"id_collides_with":0,"l":2291,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":31,"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":630,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":275,"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":3092,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":394,"tested":0}, +{"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":632,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":293,"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":3164,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":420,"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":2792,"ord":0,"overloads":1,"amb":12,"cx":14,"ccx":15,"churn":31,"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":2331,"ord":0,"overloads":1,"amb":12,"cx":22,"ccx":35,"churn":31,"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":284,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":31,"tested":0}, -{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","key":"904831e5c0f66b39","id":null,"id_addressable":false,"id_collides_with":0,"l":1649,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":65,"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":12,"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":12,"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":1634,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":68,"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/mcprefusal.h","n":"kMcpRequiredFields","scope":"rw::mcprefuse","key":"c5f1ad5fc3a12368","id":"./src/mcprefusal.h::rw::mcprefuse::kMcpRequiredFields","id_addressable":true,"id_collides_with":0,"l":66,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"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":2622,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":31,"tested":0}], -"files":[{"p":"./src/cli.h","symbols":1,"churn":394,"ccx":0,"hotspot_rank":6}, -{"p":"./src/docdrift.h","symbols":6,"churn":31,"ccx":51,"hotspot_rank":30}, -{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":81}, -{"p":"./src/mcprefusal.h","symbols":1,"churn":36,"ccx":0,"hotspot_rank":55}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":275,"ccx":0,"hotspot_rank":5}, -{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":45}, -{"p":"./src/verbs_change.h","symbols":1,"churn":65,"ccx":4,"hotspot_rank":21}], -"symbols_total":3040,"symbols_capped":true},"blast_radius":{"reaches":12,"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":12,"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":7,"module_span":7,"max_ccx":35,"sum_ccx":59,"ambiguous_calls":26,"blast_reaches":12,"contract_touches":1,"conflicts":3,"untested":12,"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":106,"l":258,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":6," … [line truncated: 11 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":2716,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":394,"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":3092,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":394,"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":2795,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":394,"tested":0}, +{"p":"./src/mcprefusal.h","n":"kMcpRequiredFields","scope":"rw::mcprefuse","key":"c5f1ad5fc3a12368","id":"./src/mcprefusal.h::rw::mcprefuse::kMcpRequiredFields","id_addressable":true,"id_collides_with":0,"l":67,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":38,"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":420,"ccx":0,"hotspot_rank":6}, +{"p":"./src/docdrift.h","symbols":6,"churn":36,"ccx":54,"hotspot_rank":30}, +{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":85}, +{"p":"./src/mcprefusal.h","symbols":1,"churn":38,"ccx":0,"hotspot_rank":55}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":293,"ccx":0,"hotspot_rank":5}, +{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":50}, +{"p":"./src/verbs_change.h","symbols":1,"churn":68,"ccx":4,"hotspot_rank":22}], +"symbols_total":3091,"symbols_capped":true},"blast_radius":{"reaches":12,"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":12,"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":7,"module_span":6,"max_ccx":38,"sum_ccx":62,"ambiguous_calls":26,"blast_reaches":12,"contract_touches":1,"conflicts":3,"untested":12,"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":106,"l":261,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":7," … [line truncated: 11 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":2788,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":420,"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":3164,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":420,"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":2867,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":420,"tested":0}, {"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":106,"l":858,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":16,"tested":0}, {"p":"./src/slice.h","n":"sliceRdIterationCeiling","scope":"slicev","key":"449132b302bb4324","id":"./src/slice.h::slicev::sliceRdIterationCeiling","id_addressable":true,"id_collides_with":0,"l":1372,"ord":0,"overloads":1,"amb":0,"cx":9,"ccx":12,"churn":51,"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":303,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":36,"tested":0}, -… [64 more display lines; full output is 19268 bytes on 1 raw line(s)] +{"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":38,"tested":0}, +… [64 more display lines; full output is 19299 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --plan-lanes=99 --task=x` @@ -3042,11 +2930,9 @@ 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.29s** - ````` - + @@ -3073,9 +2959,9 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out - + - + ````` @@ -3084,7 +2970,7 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out *The honest refusal (exit 1): Lang is an `enum class`, not a struct — no offsets are fabricated.* -**exit code: 1** — **wall time: 1.53s** +**exit code: 1** ````` (empty) @@ -3100,93 +2986,92 @@ 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: 3.09s** +**wall time: 1.68s** ````` - - -
- - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - -… [234 more display lines; full output is 24192 bytes on 1 raw line(s)] + + + + +… [239 more display lines; full output is 24814 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.93s** +**wall time: 1.88s** ````` - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - -… [261 more display lines; full output is 25429 bytes on 1 raw line(s)] + + + + +… [265 more display lines; full output is 26013 bytes on 1 raw line(s)] ````` Tail of the same output — the `` section: ````` - + - - - + + @@ -3194,8 +3079,8 @@ Tail of the same output — the `` section: - + @@ -3204,53 +3089,54 @@ 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: 3.06s** +**wall time: 1.34s** ````` - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - -… [216 more display lines; full output is 24368 bytes on 1 raw line(s)] + + + +… [221 more display lines; full output is 25009 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --from-trace=-` *Map a pasted stack trace onto indexed symbols. CHANGED: in_corpus= now reports the real count (was 0).* -**wall time: 1.80s** +**wall time: 1.10s** Input file: @@ -3258,34 +3144,34 @@ 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:4434 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4475 - #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1625 - #3 0x102e01a44 in main src/main.cpp:3935 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:4647 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4688 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 + #3 0x102e01a44 in main src/main.cpp:3975 #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 ) +int runDefaultMap( const MainDispatch& d ) +int main( int argc, char** argv ) - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -3304,8 +3190,6 @@ AddressSanitizer:DEADLYSIGNAL *List all field notes (write-side memory) — the committed .ripwire_notes at the repo root, each with the sha/branch it was recorded at.* -**wall time: 1.25s** - ````` @@ -3329,10 +3213,10 @@ AddressSanitizer:DEADLYSIGNAL *ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* -**wall time: 2.65s** +**wall time: 1.13s** ````` - + + - + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept - -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: 104 more bytes on this line] - -knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedBy makes in full above, which applies here verbatim: firstFlagOutside() walks the rows parseArgs itself minline constexpr std::string_view kInRideAlong[] = - + +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 + 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 -struct AgentTarget - -verifier N2/N3/N11: the bad-VALUE refusal tablestruct McpValueSpec + +knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedBy makes in full above, which applies here verbatim: firstFlagOutside() walks the rows parseArgs itself minline constexpr std::string_view kInRideAlong[] = - - + + + + - - - -… [55 more display lines; full output is 9793 bytes on 47 raw line(s)] +… [47 more display lines; full output is 9435 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.* -**wall time: 2.26s** +**wall time: 1.75s** ````` - 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. --> - - - + + + - + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept - -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 std::vector<std::string>& verbLines… - -knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedB…inline constexpr std::string_view kInRideAlong[] = - -F7: a hostile/garbled frame line number (e.g. a fuzzed or truncated trace) can exceed UINT32_MAX…inline std::uint32_t toUint( std::string_view s, bool& overflowed ) 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 +inline bool printUsageTier( std::FILE* out, HelpTier tier, std::string_view want ) noexcept - - + + @@ -3424,32 +3308,32 @@ 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] What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure -inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) -struct RankedGraph -inline std::uint32_t sliceRdIterationCeiling() noexcept -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 … [line truncated: 7 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 … [line truncated: 7 more bytes on this line] +inline std::uint32_t sliceRdIterationCeiling() noexcept struct PageRankRun -inline std::uint32_t navRelevanceWeight( const Graph& g, NodeId n ) noexcept -COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "s… -inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) -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", … [line truncated: 90 more bytes on this line] +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 ) +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… +COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "th … [line truncated: 61 more bytes on this line] inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept inline std::string powerShellSingleQuote( std::string_view s ) noexcept -inline std::string path_prepend_hint( std::string_view dir ) -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` -### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] -### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] +inline std::string path_prepend_hint( std::string_view dir ) +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] +### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] ## Background — read these, in this order ## Design space and constraints -… [61 more display lines; full output is 9883 bytes on 10 raw line(s)] +… [61 more display lines; full output is 9879 bytes on 10 raw line(s)] ````` ## `./build/ripwire . --export=cc.json:/aux/ripwire2.cc.json` *Per-file metrics as CodeCharta cc.json.* -**wall time: 3.81s** +**wall time: 1.46s** ````` (empty) @@ -3458,7 +3342,7 @@ AddressSanitizer:DEADLYSIGNAL Artifact written: ````` -353194 /aux/ripwire2.cc.json +358330 /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 ````` @@ -3466,7 +3350,7 @@ Artifact written: *One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* -**wall time: 4.09s** +**wall time: 17.72s** Input file: @@ -3481,34 +3365,34 @@ 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 McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) -inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vec… -inline const McpIndex& getIndex( const std::string& root ) +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +struct FileHealth +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vec… +inline const McpIndex& getIndex( const std::string& root ) struct McpIndex -inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) -### See the map — not just the numbers -inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… -inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) -inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, const CacheLoadStats& cacheStats … [line truncated: 59 more bytes on this line] -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 respectGi … [line truncated: 27 more bytes on this line] -const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) -static bool cachePathIsDirectory( const std::string& cachePath ) +inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) +### See the map — not just the numbers +inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… +inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) +inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, const CacheLoadStats& cacheStats … [line truncated: 59 more bytes on this line] +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 respect … [line truncated: 29 more bytes on this line] +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) +static bool cachePathIsDirectory( const std::string& cachePath ) inline constexpr std::array<std::string_view, 8> kCacheRuleNames = -… [57 more display lines; full output is 23924 bytes on 1 raw line(s)] +… [57 more display lines; full output is 23954 bytes on 1 raw line(s)] ````` @@ -3520,18 +3404,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: 5.81s** +**exit code: 1** — **wall time: 4.04s** ````` - + - - + + - - + @@ -3590,19 +3474,19 @@ 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: 3.25s** +**wall time: 1.77s** ````` - - + + - + - + @@ -3616,31 +3500,29 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(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~7 --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.11s** - ````` - - + + - - - - - - + + + + + + - - - - + + + + - + ````` @@ -3664,16 +3546,14 @@ 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.65s** - ````` - + 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 -1087,4473,5097,171,1379,1414,1623 +1102,4686,5310,171,1380,1419,1629 fn,fn,fn,fn,fn,fn,fn 0,0,0,0,0,0,0 @@ -3684,24 +3564,24 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *Flat top-K export for an external reranker.* -**wall time: 2.02s** +**wall time: 1.76s** ````` - - + + 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 ) @@ -3727,16 +3607,16 @@ ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidate *Machine-parseable JSON, same content, keys mirror the XML attrs.* -**wall time: 1.28s** +**wall time: 1.06s** ````` -{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1087"}, -{"t":"fn","n":"rankGraph","p":"src/graph.h:4473"}, -{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:5097"}, +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1102"}, +{"t":"fn","n":"rankGraph","p":"src/graph.h:4686"}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:5310"}, {"t":"fn","n":"runEval","p":"src/eval.h:171"}, -{"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1379"}, -{"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1414"}, -{"t":"fn","n":"runDefaultMap","p":"src/main.cpp:1623"}]} +{"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1380"}, +{"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1419"}, +{"t":"fn","n":"runDefaultMap","p":"src/main.cpp:1629"}]} ````` ## `./build/ripwire . --hotspots --json` @@ -3759,15 +3639,15 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Pagination: 3 items, skipping the first 3 (deterministic seams).* -**wall time: 3.58s** +**wall time: 1.86s** ````` - - - - + + + + ````` @@ -3775,26 +3655,26 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop test paths from the corpus before ranking.* -**wall time: 1.09s** - ````` - + - - + + + + + + + + + + + - - - - - - - - + @@ -3804,26 +3684,26 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop matching paths (repeatable) before ranking.* -**wall time: 1.17s** - ````` - + - - + + + + + + + + + + + - - - - - - - - + @@ -3831,28 +3711,28 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m ## `./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.83s** +*Full map re-ranked with teleport toward git-changed files — recorded against a DIRTY tree, so changed= counts the working copy's files and the teleport is live.* ````` - + - - + + + + + + + + + + + - - - - - - - - + @@ -3862,23 +3742,19 @@ 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: 20.09s** +**wall time: 9.94s** ````` - + - - - - - - - - + + + + - - + + @@ -3888,23 +3764,19 @@ 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: 19.81s** +**wall time: 10.06s** ````` - + - - - - - - - - + + + + - - + + @@ -3913,7 +3785,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m Artifact written: ````` -15801018 /aux/warm2.ripwirecache +16441642 /aux/warm2.ripwirecache ````` ## `./build/ripwire . --max-file-size=8K --top-k=3` @@ -3921,20 +3793,20 @@ Artifact written: *Skip files above a size bound before parsing (note the corpus shrink in the header).* ````` - + - - - - + + + + - - + + - - + + @@ -3960,28 +3832,30 @@ 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.85s** +**wall time: 7.05s** ````` - + - - + + - - + + + + + + + - - + + - - - - + @@ -3991,19 +3865,19 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Self-eval: co-change recall vs BM25.* -**wall time: 28.78s** +**wall time: 20.67s** ````` ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 6.4% 8.2% 16.8% - BM25 17.6% 20.8% 24.2% - BM25sub 20.0% 27.5% 30.5% - BM25body 20.9% 32.4% 40.8% - fused 13.4% 19.0% 36.1% - anchored 20.9% 32.4% 40.8% - same-dir 0.0% 7.1% 11.2% - random 0.2% 0.4% 0.9% <- floor (random ranking over F=2345 files) + ripwire 2.2% 6.5% 14.7% + BM25 6.7% 7.8% 16.0% + BM25sub 10.9% 14.8% 21.1% + BM25body 7.9% 20.4% 34.0% + fused 11.6% 16.1% 23.4% + anchored 7.9% 20.4% 34.0% + same-dir 0.0% 1.5% 3.1% + random 0.2% 0.4% 0.8% <- floor (random ranking over F=2376 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 / @@ -4019,24 +3893,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: 232.24s** +**wall time: 127.26s** ````` ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=5277 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + sample: population=5540 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.697 56.3% 86.5% 91.0% - subtoken doc-phrase 0.921 90.0% 94.2% 95.0% - name-exact name 0.909 84.4% 96.2% 97.5% - name-exact doc-phrase 0.010 0.3% 1.7% 2.4% - anchored name 0.703 57.8% 85.9% 89.7% - anchored doc-phrase 0.918 89.6% 94.0% 94.7% - routed name 0.912 84.6% 96.4% 97.8% - routed doc-phrase 0.919 90.0% 94.0% 94.8% - note: routing chose name-exact on 3985/4000 NAME queries (a NAME query is always identifier-shaped); + subtoken name 0.697 56.4% 86.3% 91.2% + subtoken doc-phrase 0.922 90.2% 94.2% 95.1% + name-exact name 0.912 84.7% 96.3% 97.7% + name-exact doc-phrase 0.011 0.4% 1.6% 2.3% + anchored name 0.706 58.4% 85.9% 89.8% + anchored doc-phrase 0.918 89.7% 94.1% 94.7% + routed name 0.914 85.0% 96.5% 97.9% + routed doc-phrase 0.920 90.1% 94.0% 94.8% + note: routing chose name-exact on 3986/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 @@ -4048,25 +3922,24 @@ 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: 1** — **wall time: 2.87s** +**exit code: 3** — **wall time: 1.65s** 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) +sync-rebase merged +sync-upstream unmerged upstream-paging merged -no-such-ref-1 unmerged -no-such-ref-2 merged -````` - ````` -(empty) -````` - -stderr: ````` -ripwire: --eval-stray: 2 labelled ref(s) do not exist in . -- not merged, just absent: no-such-ref-1, no-such-ref-2 (fix the labels file or add the ref) + + + + + + ````` ## `./build/ripwire skills --eval-skills=/aux/skills_labels2.tsv` @@ -4089,7 +3962,7 @@ ripwire --eval-skills (skill routing over K=16 candidate skills [ripwire-router name 33.3% 66.7% 0.537 0.667 50.0% (th=0.000) bm25-desc 66.7% 66.7% 0.690 0.667 75.0% (th=2.958) 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.681) + for-routed 33.3% 33.3% 0.556 1.000 50.0% (th=0.685) 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 @@ -4159,7 +4032,7 @@ Reach for it BEFORE blind grep + whole-file reads. First call ~1s cold; after th *Version + short build info.* ````` -ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=67cf52baa+dirty) +ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=dac3b8205) ````` @@ -4167,35 +4040,31 @@ ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=67cf52baa+dir # navigate — seeds, claims, slices, shapes -## `./build/ripwire . --at=src/graph.h:4434` +## `./build/ripwire . --at=src/graph.h:4647` *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.61s** - ````` - - + + ````` -## `./build/ripwire . --callers=@src/graph.h:4434` +## `./build/ripwire . --callers=@src/graph.h:4647` *The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* -**wall time: 1.26s** - ````` - - - - + + + + - - - + + + ````` @@ -4203,7 +4072,7 @@ ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=67cf52baa+dir *A seed past the end of the file — the refusal shape for a faulted location.* -**exit code: 1** — **wall time: 1.16s** +**exit code: 1** ````` (empty) @@ -4212,20 +4081,18 @@ ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=67cf52baa+dir stderr: ````` -ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 7993 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 8435 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.83s** - ````` - - - + + + ````` @@ -4233,20 +4100,18 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A claim that is FALSE — the refuted shape, with the references that refute it.* -**wall time: 1.90s** - ````` - + - - - - - - - - + + + + + + + + ````` @@ -4254,7 +4119,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A literal-scan absence: refuted only with complete= evidence, never on a partial scan.* -**wall time: 1.99s** +**wall time: 1.02s** ````` @@ -4266,7 +4131,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *An unparseable claim — the refusal names the accepted shapes.* -**exit code: 1** — **wall time: 1.96s** +**exit code: 1** — **wall time: 1.00s** ````` (empty) @@ -4282,25 +4147,23 @@ 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: 1.44s** - ````` - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ````` @@ -4308,21 +4171,19 @@ 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: 1.73s** - ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4332,30 +4193,28 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *TRANSITIVE backward value-flow from the seed variable, bounded BFS (depth= disclosed; a cut frontier says flow_truncated=1).* -**wall time: 1.83s** - ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + - + pw = biasPrior( g, p );]]> - + & p, float alpha = 0.85f )]]> - + & p, float alpha = 0.85f )]]> @@ -4365,42 +4224,40 @@ 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.92s** - ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + - + - + - + - + - + 0.0 )]]> - + - + @@ -4410,7 +4267,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. *A variable the definition does not bind — the refusal shape, naming the inventory.* -**exit code: 1** — **wall time: 1.25s** +**exit code: 1** ````` (empty) @@ -4442,21 +4299,19 @@ 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.35s** - ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4466,12 +4321,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: 11.73s** +**wall time: 5.24s** ````` 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 ) ````` @@ -4479,11 +4334,11 @@ 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: 11.82s** +**wall time: 5.16s** ````` -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 @@ -4503,23 +4358,23 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < 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( out, CMakeScan::DisclosureWhy::SymlinkEscapesRoot, "flags: a CMake file's symlink target leaves the root — f -DISCLOSE( out, CMakeScan::DisclosureWhy::RootWalkFailed, "flags: cannot walk root for CMake files — cmake gates omitte +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( out, CMakeScan::DisclosureWhy::SymlinkEscapesRoot, "flags: a CMake file's symlink target leaves the root — f +DISCLOSE( out, CMakeScan::DisclosureWhy::RootWalkFailed, "flags: cannot walk root for CMake files — cmake gates omitte DISCLOSE( r, Result::DisclosureWhy::BaseTreeUnavailable, "dmm: the base commit's tree could not be materialized or parse DISCLOSE( r, Result::DisclosureWhy::TargetTreeUnavailable, "dmm: the target commit's tree could not be materialized or p -DISCLOSE( out, RepoPaths::DisclosureWhy::SymlinkEscapesRoot, "doc-drift: a file's symlink target leaves the root — fil -DISCLOSE( out, RepoPaths::DisclosureWhy::RootWalkFailed, "doc-drift: cannot walk the root — the on-disk existence prob -DISCLOSE( res, DriftResult::DisclosureWhy::UnreadableDoc, "doc-drift: cannot read a markdown file — its anchors are om -… [73 more display lines; full output is 17692 bytes on 1 raw line(s)] +DISCLOSE( out, RepoPaths::DisclosureWhy::SymlinkEscapesRoot, "doc-drift: a file's symlink target leaves the root — fil +DISCLOSE( out, RepoPaths::DisclosureWhy::RootWalkFailed, "doc-drift: cannot walk the root — the on-disk existence prob +DISCLOSE( res, DriftResult::DisclosureWhy::UnreadableDoc, "doc-drift: cannot read a markdown file — its anchors are om +… [73 more display lines; full output is 17666 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --pattern='x'` *A pattern that collapses to a bare token is REFUSED — never reported as hits=0.* -**exit code: 1** — **wall time: 1.36s** +**exit code: 1** — **wall time: 1.06s** ````` (empty) @@ -4535,52 +4390,48 @@ ripwire: --pattern: this pattern collapses to a single bare token, which is a TE *Boolean grep: hits where BOTH literals share the matched line (--grep-scope=line is the default).* -**wall time: 1.68s** - ````` inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) - + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -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::array<LangEntry, 51> kLangTable = -constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u -inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept -std::vector<LocalNameFact> collectGatedLocalNames( std::string_view defBytes, std::uint32_t defStartLine, Lang lang ) +struct FileHealth +enum class AstWalk : std::uint8_t +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 +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::array<LangEntry, 51> kLangTable = +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +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 ) -std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] -bool jsonNestsTooDeep( std::string_view bytes ) noexcept -constexpr std::uint32_t kMaxYamlNestDepth = 64u -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 bool hasMainGuard( std::string_view source ) -struct LintRule -enum class DisclosureWhy : std::uint8_t -struct AstQuerySpec -inline bool spanTierMemoTryLoad( bool useMemo, const std::string& diskPath, const StatInfo& now, SpanTierMap& out ) -… [55 more display lines; full output is 8998 bytes on 1 raw line(s)] +std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +constexpr std::uint32_t kMaxYamlNestDepth = 64u +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +inline bool hasMainGuard( std::string_view source ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [55 more display lines; full output is 9066 bytes on 1 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: 2.96s** +**wall time: 1.29s** ````` - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) - + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -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::array<LangEntry, 51> kLangTable = -constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u -inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept -std::vector<LocalNameFact> collectGatedLocalNames( std::string_view defBytes, std::uint32_t defStartLine, Lang lang ) +struct FileHealth +enum class AstWalk : std::uint8_t +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 +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::array<LangEntry, 51> kLangTable = +constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +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 ) -std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] -bool jsonNestsTooDeep( std::string_view bytes ) noexcept -constexpr std::uint32_t kMaxYamlNestDepth = 64u -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 bool hasMainGuard( std::string_view source ) -struct LintRule -enum class DisclosureWhy : std::uint8_t -struct AstQuerySpec -inline bool spanTierMemoTryLoad( bool useMemo, const std::string& diskPath, const StatInfo& now, SpanTierMap& out ) -… [90 more display lines; full output is 12573 bytes on 55 raw line(s)] +std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] +bool jsonNestsTooDeep( std::string_view bytes ) noexcept +constexpr std::uint32_t kMaxYamlNestDepth = 64u +SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) +inline bool hasMainGuard( std::string_view source ) +inline void runParseWorker( ParsePoolShared& sh, unsigned t ) +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [90 more display lines; full output is 12638 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: 3.00s** +**wall time: 1.90s** ````` - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) - +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… -struct DeltaBasis -inline const char* kAcksFile = ".ripwire_quality_acks" -DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) -std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) -struct QualityDeltaOutcome -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -inline std::string qualityAcksPath( const std::string& root ) -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at -inline std::string gitHeadSha( const std::string& root ) -inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +struct QualityDeltaOutcome +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string qualityAcksPath( const std::string& root ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) -inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) -int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) -inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] -inline std::string qualityBaselinePath( const std::string& root ) -… [52 more display lines; full output is 9271 bytes on 1 raw line(s)] +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) +… [52 more display lines; full output is 9268 bytes on 1 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: 3.03s** +**wall time: 1.84s** ````` - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) - +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… -struct DeltaBasis -inline const char* kAcksFile = ".ripwire_quality_acks" -DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) -std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) -struct QualityDeltaOutcome -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -inline std::string qualityAcksPath( const std::string& root ) -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at -inline std::string gitHeadSha( const std::string& root ) -inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +struct QualityDeltaOutcome +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string qualityAcksPath( const std::string& root ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) -inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) -int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) -inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] -inline std::string qualityBaselinePath( const std::string& root ) -… [53 more display lines; full output is 9444 bytes on 1 raw line(s)] +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) +… [53 more display lines; full output is 9441 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --safe-delete=rankGraphTeleport` *"Can I delete this?" — callers + transitive impact + every use site + how much of the radius is tested, composed in ONE call; risk= names what was found, never a verdict.* -**wall time: 1.70s** - ````` - - - - - - - + + + + + + ````` @@ -4970,7 +4813,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? *Unknown-symbol refusal shape for --safe-delete.* -**exit code: 1** — **wall time: 1.17s** +**exit code: 1** ````` (empty) @@ -4984,24 +4827,24 @@ 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.* +*The continuation packet for the NEXT session: disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a DIRTY tree.* -**wall time: 3.98s** +**wall time: 2.02s** ````` - + - + - - - + + + + ````` @@ -5010,17 +4853,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: 4.16s** +**wall time: 2.63s** ````` - - + - @@ -5062,30 +4904,26 @@ ripwire: --safe-delete symbol not found: DoesNotExist -… [388 more display lines; full output is 41521 bytes on 1 raw line(s)] +… [389 more display lines; full output is 41630 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --no-ignore --top-k=3` *Crawl paths the repo's own .gitignore covers (default honours it and discloses ignored_files=/ignored_dirs= only when it dropped anything — this repo's crawl drops nothing, so the header is identical to the default map's; --skipped's ignore_mode= says which rule applied).* -**wall time: 1.36s** +**wall time: 1.26s** ````` - + - - - - - - - - + + + + - - + + @@ -5095,23 +4933,17 @@ 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.16s** - ````` - + - - - - - - - - + + + + - - + + @@ -5127,20 +4959,20 @@ 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.60s** +**exit code: 4** ````` - + - + const&, float) src/graph.h:4434 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4475 - #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1625 - #3 0x102e01a44 in main src/main.cpp:3935 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:4647 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4688 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 + #3 0x102e01a44 in main src/main.cpp:3975 #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_DESCR … [line truncated: 480 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_DESCR … [line truncated: 480 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.32s** - ````` - + ````` @@ -5189,12 +5019,12 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o *A command still running at the cap: its process group is killed and the run reports timed_out=1 — an honest timeout, never an empty success.* -**exit code: 4** — **wall time: 3.59s** +**exit code: 4** — **wall time: 2.89s** ````` - + ````` @@ -5229,59 +5059,59 @@ 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: 16.02s** +**wall time: 7.84s** ````` - - + - - - - - + + + + + - + - + - + - + - - + + - -… [186 more display lines; full output is 15779 bytes on 1 raw line(s)] + +… [186 more display lines; full output is 15928 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --biggest-first --limit=8` *Per-function size ranking, largest Halstead volume/token entropy/lines first — a size proxy, not a readability order (withdrawn, docs/EVALS.md §8) — a RANKING lens, not a grade.* -**wall time: 2.04s** +**wall time: 1.32s** ````` - - - - + + + + - + - + ````` @@ -5289,15 +5119,15 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *Functions WITH a doc comment, most name-restating first: c_coeff (high = the comment repeats the name) and cic (Jaccard of comment vs identifier vocabulary), both reported, never collapsed.* -**wall time: 2.10s** +**wall time: 1.41s** ````` - - + + - + @@ -5309,51 +5139,51 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The local-reasoning lens: to understand this symbol, how much must you know that is NOT in front of you (ent_ratio= edge share, read_ratio= token-weighted).* -**wall time: 1.88s** +**wall time: 1.07s** ````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [21 more display lines; full output is 11206 bytes on 1 raw line(s)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [21 more display lines; full output is 11207 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: 8.16s** +**wall time: 4.05s** ````` - @@ -5389,51 +5219,51 @@ 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: 9.03s** +**wall time: 4.95s** ````` - - + - + - + - + - + - + - + - + -… [35 more display lines; full output is 7376 bytes on 1 raw line(s)] +… [35 more display lines; full output is 7400 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: 7.81s** +**wall time: 4.05s** ````` - @@ -5462,25 +5292,25 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir -… [502 more display lines; full output is 45114 bytes on 1 raw line(s)] +… [505 more display lines; full output is 45388 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: 7.15s** +**wall time: 4.05s** ````` - + - + - - - + + + @@ -5504,16 +5334,14 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The corpus's OWN case-convention vote per (language, kind) group; off-convention names get a mechanical propose= (a suggestion, never a blind rename).* -**wall time: 1.80s** - ````` - - - - - + + + + + - + @@ -5522,9 +5350,9 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - + @@ -5533,60 +5361,60 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - + -… [13 more display lines; full output is 4253 bytes on 1 raw line(s)] +… [13 more display lines; full output is 4383 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: 29.97s** +**wall time: 14.94s** ````` - - - + + + - + -

+

-

+

-

-

-

+

+

+

+

-

-… [95 more display lines; full output is 10960 bytes on 1 raw line(s)] +… [108 more display lines; full output is 12146 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: 21.55s** +**wall time: 8.60s** ````` - - + + @@ -5594,35 +5422,33 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - + - - + + - + - + - - - -… [642 more display lines; full output is 72295 bytes on 1 raw line(s)] + + + +… [643 more display lines; full output is 72265 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint-catalog` *The built-in rule registry — one row per rule with sev=/category=/rationale/lang=/since=; no corpus needed.* -**wall time: 1.42s** - ````` @@ -5662,7 +5488,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=861226192676dedb entries=733 +ripwire legend dictionary ripwire.dict/v1 dictv=32cb666f84c1d715 entries=757 : 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=) @@ -5692,24 +5518,24 @@ ripwire.uses/v1 : resolvable use-sites of of=: : transitive blast radius of of=: reach set, importers; defs= matched, reaches= their transitive callers, radius_tested= non-tests an indexed test reaches, radius_untested= the rest; importers= files that #include/import a def's file ripwire.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, -… [704 more display lines; full output is 71919 bytes on 734 raw line(s)] +… [728 more display lines; full output is 74738 bytes on 758 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: 15.52s** +**wall time: 9.77s** ````` - + - + - + vector<std::vector<double>> @@ -5732,19 +5558,19 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal g_pool[ g_readOf[i] ] g_pool[ g_readOf[i] ] m[ g_pool[i] ] -… [480 more display lines; full output is 57948 bytes on 1 raw line(s)] +… [491 more display lines; full output is 59286 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: 15.95s** +**wall time: 9.93s** ````` - - + + @@ -5752,17 +5578,17 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal - + - - + + - - - + + + @@ -5772,14 +5598,14 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal 20 delta < 0.02 && delta > -0.02 0.02 -… [734 more display lines; full output is 65543 bytes on 1 raw line(s)] +… [732 more display lines; full output is 65693 bytes on 1 raw line(s)] ````` ## `./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: 15.78s** +**exit code: 1** — **wall time: 8.32s** ````` (empty) @@ -5795,7 +5621,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: 16.27s** +**exit code: 1** — **wall time: 8.89s** ````` (empty) @@ -5811,7 +5637,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: 15.97s** +**wall time: 8.04s** ````` {"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] @@ -5844,13 +5670,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}}, -… [9735 more display lines; full output is 1361443 bytes on 1 raw line(s)] +… [9803 more display lines; full output is 1372913 bytes on 1 raw line(s)] ````` Parsed summary of the same SARIF (past the display cut): ````` -sarif 2.1.0 rules= 39 results= 4863 +sarif 2.1.0 rules= 39 results= 4897 ````` ## `./build/ripwire . --lint --sarif --limit=5` @@ -5871,13 +5697,13 @@ 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.* +*The Delta Maintainability Model scalar for the WORKING TREE vs HEAD — recorded against a DIRTY tree (the sandbox section shows a real delta). UNAVAILABLE is a stated reason, never 0 or 1.* -**wall time: 8.08s** +**wall time: 4.93s** ````` -

@@ -5888,11 +5714,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: 36.12s** +**wall time: 17.94s** ````` -

@@ -5903,14 +5729,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: 35.94s** +**wall time: 19.01s** ````` - - -

-

+ + +

+

````` @@ -5918,20 +5744,20 @@ 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: 3.69s** +**wall time: 1.43s** ````` - - + + - + - + - + @@ -5945,54 +5771,54 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M - - - - - - -… [675 more display lines; full output is 44406 bytes on 1 raw line(s)] + + + + + + +… [708 more display lines; full output is 46546 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: 3.31s** +**wall time: 1.88s** ````` - + - - - - + + - - + + + + + + - - - - - - - + + + - - + + + + - + ````` @@ -6000,7 +5826,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: 3.78s** +**wall time: 1.47s** ````` (empty) @@ -6009,14 +5835,14 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M Artifact written: ````` -158930 /aux/map2.html +158732 /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: 41.44s** +**wall time: 21.92s** ````` (empty) @@ -6025,39 +5851,33 @@ Artifact written: stderr: ````` -ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (15801018 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (34998856 bytes, rich family) +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (16441642 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (36007128 bytes, rich family) ````` Artifact written: ````` -15801018 /aux/ci_index.lean.ripwirecache -34998856 /aux/ci_index.rich.ripwirecache -50799874 total +16441642 /aux/ci_index.lean.ripwirecache +36007128 /aux/ci_index.rich.ripwirecache +52448770 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.54s** - ````` - + - - - - - - - - + + + + - - + + @@ -6067,23 +5887,17 @@ Artifact written: *Eval-only: a per-call-site census of WHICH mechanism resolved each call, and the canonical id of every surviving target.* -**wall time: 1.42s** - ````` - + - - - - - - - - + + + + - - + + @@ -6100,18 +5914,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. -106217 /aux/pin_census.tsv +109132 /aux/pin_census.tsv ````` ## `./build/ripwire . --plan-lint=test/planlintfix/wave.md` *The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when a card or ledger row gates.* -**exit code: 2** — **wall time: 1.70s** +**exit code: 2** ````` - + @@ -6129,11 +5943,11 @@ Artifact written: *The ledger-shaped fixture through the same check (exit 2: one gating card).* -**exit code: 2** — **wall time: 1.76s** +**exit code: 2** ````` - + @@ -6145,18 +5959,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: 4.40s** +**exit code: 1** — **wall time: 2.17s** ````` - + - - + + - - + @@ -6199,14 +6013,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: 22.90s** +**wall time: 12.09s** ````` ripwire situational-awareness — 1 changed file(s), 13 symbols in them root: . -at: ce5c0b3fc+dirty +at: dac3b8205+dirty [1] blast radius: 182 symbols across 43 files transitively depend on these changes (showing 8 of 43 files — shown=8 total=43 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=43) - counts_floor=1 graph_ambiguous=9499 graph_unresolved=12841 graph_unindexed=231 (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=9831 graph_unresolved=13184 graph_unindexed=231 (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 (32 dependent symbols) src/slice.h (30 dependent symbols) src/serialize.h (9 dependent symbols) @@ -6221,9 +6035,9 @@ at: ce5c0b3fc+dirty test/includeprecise_unit.cpp [hops=2] (run: bash test/includeprecisecheck.sh) test/verify_csr.cpp [hops=2] (run: bash test/a9disclosurecheck.sh) test/rustimport_unit.cpp [hops=4] (run: bash test/rustimportprecisecheck.sh) - script_gates_unmodelled=714 — 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="3581": - (none — 3581 commits were mined and none co-edited a file outside your diff) + script_gates_unmodelled=722 — 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="3758": + (none — 3758 commits were mined and none co-edited a file outside your diff) next: --test-gate ````` @@ -6231,41 +6045,41 @@ at: ce5c0b3fc+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: 4.70s** +**exit code: 4** — **wall time: 3.04s** ````` - - - + + - - - - + + + + - + - - - + + + - + - + + - - - + + - + ````` @@ -6273,11 +6087,11 @@ at: ce5c0b3fc+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: 94.44s** +**exit code: 2** — **wall time: 52.15s** ````` - @@ -6306,7 +6120,7 @@ at: ce5c0b3fc+dirty -… [105 more display lines; full output is 14346 bytes on 1 raw line(s)] +… [104 more display lines; full output is 14236 bytes on 1 raw line(s)] ````` stderr: @@ -6320,11 +6134,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, but the ~4 KB prose legend the compact default trims is restored: the posture a human reads once to learn the schema.* -**exit code: 2** — **wall time: 20.52s** +**exit code: 2** — **wall time: 18.62s** ````` - - + @@ -6353,7 +6167,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap -… [105 more display lines; full output is 18907 bytes on 1 raw line(s)] +… [104 more display lines; full output is 18831 bytes on 1 raw line(s)] ````` stderr: @@ -6367,10 +6181,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: 19.47s** +**exit code: 2** — **wall time: 18.74s** ````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":123,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"ce5c0b3fc+dirty","renames":57,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 187 more bytes on this line] +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":122,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"dac3b8205+dirty","renames":57,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 187 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"}, @@ -6400,7 +6214,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap {"kind":"api-surface:new-symbol","key":"79625906f9f71ad0","why":"target-gone"}, {"kind":"api-surface:new-symbol","key":"8ecb190954dce18a","why":"target-gone"}, {"kind":"api-surface:new-symbol","key":"995375dfa4e63104","why":"target-gone"}, -… [102 more display lines; full output is 13059 bytes on 1 raw line(s)] +… [101 more display lines; full output is 12942 bytes on 1 raw line(s)] ````` stderr: @@ -6414,7 +6228,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: 12.95s** +**exit code: 1** — **wall time: 10.57s** ````` (empty) @@ -6424,7 +6238,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, 232 re-derived, 1 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1333 ineligible (no cur … [line truncated: 198 more bytes on this line] +ripwire: ack provenance backfill — 0 clone row(s) reconstructed from the current tree, 232 re-derived, 1 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1328 ineligible (no cur … [line truncated: 198 more bytes on this line] ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written ````` @@ -6432,7 +6246,7 @@ ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written *NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* -**wall time: 12.78s** +**wall time: 11.64s** ````` (empty) @@ -6442,7 +6256,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, 232 re-derived, 1 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1333 ineligible (no cur … [line truncated: 198 more bytes on this line] +ripwire: ack provenance backfill — 0 clone row(s) reconstructed from the current tree, 232 re-derived, 1 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1328 ineligible (no cur … [line truncated: 198 more bytes on this line] ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already acked) → ./.ripwire_quality_acks ````` @@ -6450,11 +6264,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: 20.18s** +**exit code: 2** — **wall time: 19.01s** ````` - @@ -6483,7 +6297,7 @@ ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already -… [104 more display lines; full output is 14049 bytes on 1 raw line(s)] +… [103 more display lines; full output is 13939 bytes on 1 raw line(s)] ````` stderr: @@ -6513,11 +6327,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6529,11 +6343,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row identical to the compact default above, but the ~5.7 KB explanatory legend is restored — the posture for a first-time reader.* -**wall time: 1.98s** +**wall time: 1.66s** ````` - @@ -6545,11 +6359,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6585,15 +6399,15 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire 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 (defs all language-filtered, or import/pointer binding refused); order= rows by rank (important-first, important-last; (auto:fill) = flipped past a size threshold) or by path (stable). changed=K: K indexed git-changed files seed the PageRank teleport (0: uniform, incl. no git). 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. 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. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. --> - - + + - + @@ -6613,7 +6427,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + @@ -6625,21 +6439,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. --> - + - + - + @@ -6650,26 +6464,26 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + - + -… [307 more display lines; full output is 16171 bytes on 1 raw line(s)] +… [307 more display lines; full output is 16180 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --stray-content=zz-orphan` *CHANGED: a ref with NO merge base with HEAD now reports v="unknown" ok="0" in its own bucket — the absence of an answer, never a claim it is merged. (The sandbox carries a deliberately parentless branch built with `git commit-tree`; a shallow CI clone puts every ref here.)* -**wall time: 2.44s** +**wall time: 1.52s** ````` - + ````` @@ -6677,11 +6491,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row rather than silently dropping them.* -**wall time: 2.44s** +**wall time: 2.20s** ````` - + ````` @@ -6689,11 +6503,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire . at=: commit+dirty+shallow. available=0: no score at all (dmm=UNAVAILABLE, reason= says why); never read as 1.000 or 0.000. combine=pooled: root dmm= is summed good over summed good+bad of the 3 properties (ripwire's own). low_loc=/low_cx=/low_params=: a unit is LOW risk at or under these lines / cyclomatic / params. base_units=/base_volume=/target_units=/target_volume=: units with a body and their line span per side. --> -

@@ -6704,11 +6518,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire with a do-not-ack banner and never gate — scoped-out-gating= says how many would have.* -**wall time: 19.40s** +**wall time: 20.61s** ````` - @@ -6737,7 +6551,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire -… [107 more display lines; full output is 14316 bytes on 1 raw line(s)] +… [106 more display lines; full output is 14206 bytes on 1 raw line(s)] ````` stderr: @@ -6750,7 +6564,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: 12.87s** +**exit code: 1** — **wall time: 13.05s** ````` (empty) @@ -6770,11 +6584,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: 5.65s** +**wall time: 3.23s** ````` - + @@ -6799,11 +6613,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr - - - - - + + + + + ````` @@ -6812,10 +6626,10 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr *Pin a field note (write-side memory) to a symbol; committed to .ripwire_notes in the sandbox. The BARE name is resolved through the same resolver the read verbs use and stored as the canonical id — the rewrite is echoed on stderr, because a silent one is not a disclosure.* -**wall time: 1.31s** +**wall time: 1.01s** ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-01 keep this branch-free — it sits inside the PageRank sort comparator ce5c0b3fcb8a50e1a755398450908bf6a790fd50 upstream-paging +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-01 keep this branch-free — it sits inside the PageRank sort comparator dac3b8205da74d71cc23fc49b792d0572a584ef6 sync-rebase ````` stderr: @@ -6829,8 +6643,6 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort *The note is listed under the canonical id, dangling="0" — i.e. it will actually surface. (Before the H1 fix the bare name was stored verbatim and read dangling="1": recorded, and surfaced nowhere.)* -**wall time: 1.24s** - ````` @@ -6842,7 +6654,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort - + @@ -6859,7 +6671,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort *Two definitions carry this name, so the write REFUSES rather than pick one: a note keys ONE canonical id, and an ambiguous selector is refused, never silently narrowed. Every candidate is named, with a runnable retry.* -**exit code: 1** — **wall time: 1.57s** +**exit code: 1** ````` (empty) @@ -6876,7 +6688,7 @@ ripwire: --note-add: target 'gitOneLine' is ambiguous — it matches 2 definitio *A name that resolves to nothing is refused with the read verbs' own did-you-mean — never written as a dead note.* -**exit code: 1** — **wall time: 1.19s** +**exit code: 1** ````` (empty) @@ -6893,20 +6705,16 @@ ripwire: --note-add: target not found: lessByScoreDescIdd (did you mean 'lessByS *The same symbol addressed by its CANONICAL id: already canonical, so nothing is rewritten and both notes land on ONE target.* -**wall time: 1.18s** - ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-01 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort ce5c0b3fcb8a50e1a755398450908bf6a790fd50 upstream-paging +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-01 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort dac3b8205da74d71cc23fc49b792d0572a584ef6 sync-rebase ````` ## `./build/ripwire . --expand=lessByScoreDescId --top-k=0` *Both notes riding along with the symbol's body — the elements follow the body, past the display cut, so they are extracted below.* -**wall time: 1.65s** - ````` - + - + - + @@ -7027,16 +6835,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: 6.56s** +**wall time: 4.28s** ````` {"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":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -7049,7 +6857,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: 2.96s** +**wall time: 2.50s** ````` {"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] @@ -7065,19 +6873,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: 6.47s** +**wall time: 4.43s** ````` {"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":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ````` stderr: @@ -7090,7 +6898,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: 2.92s** +**exit code: 1** — **wall time: 2.09s** ````` (empty) @@ -7106,7 +6914,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: 2.97s** +**wall time: 2.57s** Input file: @@ -7124,21 +6932,21 @@ Input file: ````` ````` -{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false," … [line truncated: 301 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false," … [line truncated: 301 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: 6.65s** +**wall time: 4.17s** ````` -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"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: 485 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"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: 485 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":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true}}]} +"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true}}]} ````` ## `./build/ripwire . --edit-plan=/aux/edit_plan.json` @@ -7161,11 +6969,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: 23.20s** +**exit code: 2** — **wall time: 21.78s** ````` - @@ -7193,8 +7001,8 @@ ripwire: --edit-plan requires exactly one of --dry-run or --apply - -… [101 more display lines; full output is 13413 bytes on 1 raw line(s)] + +… [100 more display lines; full output is 13303 bytes on 1 raw line(s)] ````` stderr: @@ -7208,7 +7016,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: 13.78s** +**exit code: 1** — **wall time: 11.31s** ````` (empty) @@ -7227,7 +7035,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: 19.24s** +**wall time: 18.02s** ````` (empty) @@ -7237,19 +7045,19 @@ 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 21919 per-symbol rows, 22608 indexed symbols total) +ripwire: wrote ./.ripwire_quality_baseline (snapshot of 22469 per-symbol rows, 23176 indexed symbols total) ````` Artifact written: ````` -3668084 .ripwire_quality_baseline +3763467 .ripwire_quality_baseline # ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit -producer 11751c44735faf66f8983082afef4f6f49652cdc8e09888b9ea4272ebcb44e5a -head ce5c0b3fcb8a50e1a755398450908bf6a790fd50 +producer 690905c297bb80c57ee4e28604c7295b2ee2119ef4785a4616d979c86ce77078 +head dac3b8205da74d71cc23fc49b792d0572a584ef6 dirty 1 absorbed 2 -ccx d19d7c13cbc7 1 +ccx d19d7c13cbc7 2 ccx 1f7edbff929e5 10 ccx 6a732f6699bdb 0 ccx 7d8862be4e @@ -7259,11 +7067,11 @@ ccx 7d8862be4e *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: 28.06s** +**wall time: 18.14s** ````` - @@ -7286,13 +7094,13 @@ ccx 7d8862be4e - - + + - -… [96 more display lines; full output is 12278 bytes on 1 raw line(s)] + +… [95 more display lines; full output is 12168 bytes on 1 raw line(s)] ````` @@ -7323,9 +7131,9 @@ ccx 7d8862be4e {"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: 783 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: 806 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, ranked by PageRank. Use before modifying or deleting a symbol; it beats find_referencing_symbols (direct callers only), and 'uses' catches the read/write/import sites calls miss. Call edges … [line truncated: 1306 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] @@ -7344,7 +7152,7 @@ ccx 7d8862be4e The manifest, summarised (name / description bytes / required args) — what the host pays in context every session: ````` -tools= 33 manifest_bytes= 46711 (~tokens at 4 bytes/token: 11677 ) +tools= 33 manifest_bytes= 46753 (~tokens at 4 bytes/token: 11688 ) explore desc_bytes= 1322 schema_bytes= 958 required=['task'] batch desc_bytes= 1235 schema_bytes= 962 required=['queries'] edit_check desc_bytes= 1180 schema_bytes= 889 required=['symbol'] @@ -7352,7 +7160,7 @@ slice desc_bytes= 953 schema_bytes= 932 required=['symb 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= 802 schema_bytes= 666 required=['symbol'] +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= 829 schema_bytes= 559 required=['trace'] @@ -7371,7 +7179,7 @@ owners desc_bytes= 425 schema_bytes= 625 required=[] find_referencing_symbols desc_bytes= 434 schema_bytes= 590 required=['symbol'] grep desc_bytes= 435 schema_bytes= 562 required=['pattern'] memory_recall desc_bytes= 447 schema_bytes= 500 required=['task'] -quality_delta desc_bytes= 749 schema_bytes= 193 required=[] +quality_delta desc_bytes= 772 schema_bytes= 193 required=[] 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=[] @@ -7384,72 +7192,72 @@ quality_baseline desc_bytes= 407 schema_bytes= 193 required=[] *MCP `for`: always bundle=sigs (never the CLI's compact route), the same ranked signatures as --for.* -**wall time: 3.60s** +**wall time: 1.77s** ````` {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"ripwire","version":"1.0"},"capabilities":{"tools":{},"resources":{}},"instructions":"Map before reading files. Start a new task with explore; use from_trace for an error; use impact plus uses before changing a sym … [line truncated: 735 more bytes on this line] -{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":" - + @@ -106,7 +106,7 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ $ ./build/ripwire . --max-tokens=1500 - + @@ -136,7 +136,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`, `--recall`, `--handoff`, `--pr-context`, `--from-trace`, `--run-trace` @@ -181,14 +181,14 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -225,14 +225,14 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - + - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -257,7 +257,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 - + @@ -268,8 +268,7 @@ $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() struct FileHealth -enum class AstWalk : std::uint8_t -... [19 more line(s); run it to see the whole thing] +... [20 more line(s); run it to see the whole thing] ``` **Shaped by:** `--for` @@ -291,16 +290,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] ``` @@ -323,7 +322,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 - + @@ -355,7 +354,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 - @@ -387,15 +386,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 - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) - +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… ... [21 more line(s); run it to see the whole thing] ``` @@ -418,7 +417,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -448,7 +447,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 ) @@ -477,9 +476,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" — 113 relevant of 209 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5403 +ripwire recall — "quality delta gating exit codes" — 113 relevant of 209 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5489 -━━ README.md (relevance 8.289) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 211997 B; lines="2688-2700,2701-2708"; dropped_by_budget=23] +━━ README.md (relevance 7.944) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 212219 B; lines="2688-2700,2701-2708"; dropped_by_budget=23] #### 6.2 Exit codes This is the part to wire into a script. @@ -487,7 +486,7 @@ 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. | +| 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. | @@ -542,20 +541,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 1437506-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 1440129-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.149) ━━ [sections: 1 of 154 selected (183 in doc), section-granular; whole doc 527781 B; lines="3662-3666"; dropped_by_budget=153] [truncated: 1865 of 7738 bytes] -### `--field-affinity[=STRUCT]` +━━ commands.md (relevance 11.127) ━━ [sections: 1 of 155 selected (185 in doc), section-granular; whole doc 534551 B; lines="504-529"; dropped_by_budget=154] [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` @@ -571,7 +575,7 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - + @@ -675,7 +679,7 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable $ ./build/ripwire . --no-stable --top-k=3 - + @@ -707,7 +711,7 @@ $ ./build/ripwire . --around=rankGraphTeleport - + @@ -735,7 +739,8 @@ _Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distan ``` $ ./build/ripwire . --callers=DoesNotExist -(empty) + + ``` **Shaped by:** `--no-route`, `--callees`, `--uses`, `--impact`, `--expand`, `--edit-check`, `--slice-flow`, `--at` @@ -757,7 +762,7 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - + @@ -769,7 +774,7 @@ $ ./build/ripwire . --callees=rankGraphTeleport ``` -**Shaped by:** `--impact`, `--exercises`, `--format`, `--legend`, `--json`, `--limit` +**Shaped by:** `--exercises`, `--format`, `--legend`, `--json`, `--limit` **Caveats (stated by the binary):** @@ -788,20 +793,20 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - - + + - + - + ``` -**Shaped by:** `--no-route`, `--impact`, `--naming-consistency`, `--edit-check`, `--edit-target-file`, `--safe-delete`, `--doctor`, `--format` +**Shaped by:** `--no-route`, `--naming-consistency`, `--edit-check`, `--edit-target-file`, `--safe-delete`, `--doctor`, `--format`, `--legend` **Caveats (stated by the binary):** @@ -822,19 +827,19 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - - - - - + + + + + - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -859,19 +864,19 @@ _Names referenced but never defined in-corpus (stdlib/third-party surface). The ``` $ ./build/ripwire . --external-surface - - - - - - - - - - - - - + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -894,10 +899,10 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - - - - + + + + @@ -916,11 +921,11 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + - + @@ -931,9 +936,9 @@ $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex ### `--impact=SYM` -**Answers:** show everything that reaches SYM — the transitive blast radius before a change transitive blast radius — the indexed symbols that reach SYM (a floor, see counts_floor). +**Answers:** show everything that reaches SYM — the transitive blast radius before a change — the indexed symbols that reach SYM (a floor, see counts_floor). -file:name disambiguates like --callers rows run nearest first: d= is the hop depth (1 = a direct caller; printed where it changes), PageRank order within a depth; by_depth=k:n on the root counts reaches= per depth, so a cut drops the deepest first importers= is a SECOND, weaker reach beside it: the files that directly include/import a file defining SYM, emitted as rows (format=columnar carries the count only; --limit sizes it). NEVER added to reaches= — files and symbols are different units, and an importer may use a different symbol from that file, or none at all. lazy="1": every one of that importer's edges is written inside a closure — a TS/JS require()/import() inside a function body, a Ruby constant receiver or argument inside a method/lambda/block, a Ruby autoload or rescue class — not at load time: still a real dependency, weaker than a top-level one counts_floor="1" every count on the graph verbs is a FLOOR, never a total; a 0 means none found on --callers/--callees/--uses/--impact/--edit-check every count is a FLOOR, never a total: the call graph is extracted from source text by name, so dynamic dispatch and declarations that parse without a call expression (C++ most-vexing-parse) contribute no edge; a call through a function pointer/callback is an edge only when ONE function is bound to that variable in scope (reassigned/table-indexed/lambda-bound/escaped — address-taken or reference-bound — pointers stay edge-less, C-family); a macro-generated call site contributes a role="macro" edge when its name uniquely names an indexed function-like #define (t="macro"); a shared name stays a plain call, an unindexed macro's site is no edge. Read a 0 as "none found", never as "none exists". Those five verbs also count DISTINCT (caller,callee) pairs, while --uses counts call SITES — see each verb's own legend pr_iters="N" how many PageRank iterations produced this ordering on every PageRank-ordered document (the map, and the tree, seams, communities, zoom, impact, graph-query and exercises verbs, plus their MCP twins): how many power iterations produced that ordering. The iteration stops when the L1 residual between successive rank vectors falls below tolerance, or at a fixed iteration ceiling, whichever comes first. pr_converged="0" is emitted ONLY on that second exit and means the ranking is a rank vector that stopped SHORT of tolerance, not the fixed point it approximates. ABSENCE MEANS IT CONVERGED (there is no pr_converged="1": the converged path is the normal one and must cost zero bytes), and absence of pr_iters= itself means the document was not ordered by a power iteration at all (a lexical query score, or a hub or authority HITS vector), never that the count is unknown +file:name disambiguates like --callers rows run nearest first: d= is the hop depth (1 = a direct caller; printed where it changes), PageRank order within a depth; by_depth=k:n on the root counts reaches= per depth, so a cut drops the deepest first importers= is a SECOND, weaker reach beside it: the files that directly include/import a file defining SYM, emitted as rows (format=columnar carries the count only; --limit sizes it). NEVER added to reaches= — files and symbols are different units, and an importer may use a different symbol from that file, or none at all. lazy="1": every one of that importer's edges is written inside a closure — a TS/JS require()/import() inside a function body, a Ruby constant receiver or argument inside a method/lambda/block, a Ruby autoload or rescue class — not at load time: still a real dependency, weaker than a top-level one **Try it** @@ -942,17 +947,17 @@ _Transitive blast radius — everything that reaches SYM. NOW carries shown/capp ``` $ ./build/ripwire . --impact=rankGraphTeleport - - + + - - - + + + ... [17 more line(s); run it to see the whole thing] @@ -962,7 +967,7 @@ $ ./build/ripwire . --impact=rankGraphTeleport **Caveats (stated by the binary):** -- show everything that reaches SYM — the transitive blast radius before a change transitive blast radius — the indexed symbols that reach SYM (a floor, see counts_floor). +- show everything that reaches SYM — the transitive blast radius before a change — the indexed symbols that reach SYM (a floor, see counts_floor). - file:name disambiguates like --callers rows run nearest first: d= is the hop depth (1 = a direct caller; - NEVER added to reaches= — files and symbols are different units, and an importer may use a different symbol from that file, or none at all. @@ -991,7 +996,7 @@ $ ./build/ripwire . --verify="frobnicate(x)" **Answers:** find the markdown docs that name SYM in backticks — the doc-to-code link markdown docs (plans/designs) that name SYM in a `backtick` (doc↔code). -An @FILE:LINE seed rebinds to the innermost enclosing definition and answers, disclosing sym= (its name). unbackticked_docs=N (absent at 0): files naming SYM only outside such a span, a ceiling. the pre-PR family — plumbing (--affected) to mid-task report (--situ) to gate (--test-gate): +An @FILE:LINE seed rebinds to the innermost enclosing definition and answers, disclosing sym= (its name). unbackticked_docs=N (absent at 0): files naming SYM only outside such a span, a ceiling. **Try it** @@ -1001,9 +1006,9 @@ _Markdown docs that name SYM in a backtick (doc<->code edges)._ $ ./build/ripwire . --mentions=rankGraphTeleport - + - + @@ -1026,7 +1031,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - + @@ -1042,7 +1047,7 @@ $ ./build/ripwire . --affected=src/graph.h ... [5 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--mentions`, `--exercises`, `--test-gate`, `--edit-target-file`, `--legend` +**Shaped by:** `--exercises`, `--test-gate`, `--edit-target-file`, `--legend` **Caveats (stated by the binary):** @@ -1088,11 +1093,11 @@ _Mid-task situational report for the current git diff — recorded against a DIR $ ./build/ripwire . --situ ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: dac3b8205+dirty +at: 52c4a428f+dirty (0 changed files — working tree is clean, nothing to analyze) ``` -**Shaped by:** `--mentions`, `--affected`, `--test-gate`, `--legend`, `--limit` +**Shaped by:** `--affected`, `--test-gate`, `--legend`, `--limit` ### `--handoff` @@ -1107,18 +1112,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] ``` @@ -1142,11 +1147,11 @@ _Pre-PR gate recorded against a DIRTY tree, so the obligations below are the wor ``` $ ./build/ripwire . --test-gate - ``` -**Shaped by:** `--mentions`, `--affected`, `--quality-delta`, `--edit-target-file`, `--json`, `--limit` +**Shaped by:** `--affected`, `--quality-delta`, `--edit-target-file`, `--json`, `--limit` **Caveats (stated by the binary):** @@ -1167,11 +1172,11 @@ _Literal trigram-indexed search. Each hit carries its MATCHED line as the $ ./build/ripwire . --grep=DISCLOSE - + - + - + @@ -1469,10 +1474,10 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + - - + + NodeId order. Renumbering an otherwise-identical graph (the exact "same probe file, different so…inline constexpr float kScoreTieAbsEps = 1e-6f @@ -1505,7 +1510,7 @@ $ ./build/ripwire . --pack-signatures --top-k=10 - + @@ -1555,7 +1560,7 @@ $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 **Answers:** print these symbols' full bodies, plus the signatures of what they call full bodies of A,B,... -Selector grammar per item (the tail after the LAST ':' decides; a tail STARTING WITH A DIGIT is a range, anything else is a name): NAME every def of that name | FILE:NAME that file's def NAME:START-END body slice | FILE:NAME:START-END selector + slice FILE:LINE:NAME paste a row's p="path:line" straight from --callers/--lint/--grep (NOT --hotspots: its p= is a BARE path — build FILE:LINE:NAME from its own p=/top_l=/top= instead, since top= is just the worst function's name) path::scope::name the id composed from a row's p= sc= n= (the map, --for, --pack-task) Scope::name the sym= spelling edit-check and grep's in= rows print — matches the name under any scope whose ::-boundary SUFFIX is Scope (Box::lid, deep::Box::lid); a wrong scope refuses, it never falls back to the bare-name union (START-END is 1-based within the def's OWN body — lines="lo-hi/total" marks the slice partial; out-of-range clamps. FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. Disclosed on the root as topk_default="0" (self-describing: the change is visible without reading source). A MULTI-match name (an ambiguous bare name) or a multi-token --expand keeps the map — there IS something to disambiguate. An EXPLICIT --top-k=N (0 included) always overrides this default. Each body also carries sibs="a,b,..." sibs_total="N" [sibs_capped="1"] (the file's OTHER symbols, names only, capped at 40) and inc="x.h,..." inc_total="N" [inc_capped="1"] (the file's own #include/import targets, capped at 24) — both absent when the count is 0 (a documented zero, not a degrade), so a body no longer needs a second --outline call just to learn what else lives in its file. CHEAPEST-COMPLETE-ANSWER SERVING (no explicit --top-k, no range slice): the verb ALSO measures the (possibly map-less) bundle against the requested symbols' whole FILE(s) and emits the SMALLER, disclosed on the root as mode="bundle|whole-file" reason="the two byte counts" — on a small file the old bundle was 5.65x the file itself; on a big file the bundle saves ~26x. The whole-file form is with the file CDATA-wrapped (redacted as usual) and every requested symbol's line anchor kept. An EXPLICIT --top-k=N (including 0) opts out of BOTH the exact-name default and mode= auto-selection and keeps the classic undecorated shape; a SYM:START-END slice opts out of mode= auto-selection only (serving the whole file would invert an explicit narrowing) but still gets the exact-name top-k=0 default when it applies. When the bundle wins AND no explicit --top-k was given, the requested bodies are served BEFORE the ranked map (not buried after it), and the map's escape hatch also rides the root as note="...--top-k=0 for the payload alone..." (not stderr-only): a caller reading only stdout sees both the answer and how to drop the map on the next call. Absent whenever the bundle carries no map at all (top-k=0, exact-name default) or loses to whole-file, and whenever --top-k was explicit — the classic shape above carries no note= either. +Selector grammar per item (the tail after the LAST ':' decides; a tail STARTING WITH A DIGIT is a range, anything else is a name): NAME every def of that name | FILE:NAME that file's def NAME:START-END body slice | FILE:NAME:START-END selector + slice FILE:LINE:NAME paste a row's p="path:line" straight from --callers/--lint/--grep (NOT --hotspots: its p= is a BARE path — build FILE:LINE:NAME from its own p=/top_l=/top= instead, since top= is just the worst function's name) path::scope::name the id composed from a row's p= sc= n= (the map, --for, --pack-task) Scope::name the sym= spelling edit-check and grep's in= rows print — matches the name under any scope whose ::-boundary SUFFIX is Scope (Box::lid, deep::Box::lid); a wrong scope refuses, it never falls back to the bare-name union Scope.name|Scope#name the same match, dotted (Python/JS/Java) or Ruby-style; tried only when no other spelling matched (START-END is 1-based within the def's OWN body — lines="lo-hi/total" marks the slice partial; out-of-range clamps. FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. Disclosed on the root as topk_default="0" (self-describing: the change is visible without reading source). A MULTI-match name (an ambiguous bare name) or a multi-token --expand keeps the map — there IS something to disambiguate. An EXPLICIT --top-k=N (0 included) always overrides this default. Each body also carries sibs="a,b,..." sibs_total="N" [sibs_capped="1"] (the file's OTHER symbols, names only, capped at 40) and inc="x.h,..." inc_total="N" [inc_capped="1"] (the file's own #include/import targets, capped at 24) — both absent when the count is 0 (a documented zero, not a degrade), so a body no longer needs a second --outline call just to learn what else lives in its file. CHEAPEST-COMPLETE-ANSWER SERVING (no explicit --top-k, no range slice): the verb ALSO measures the (possibly map-less) bundle against the requested symbols' whole FILE(s) and emits the SMALLER, disclosed on the root as mode="bundle|whole-file" reason="the two byte counts" — on a small file the old bundle was 5.65x the file itself; on a big file the bundle saves ~26x. The whole-file form is with the file CDATA-wrapped (redacted as usual) and every requested symbol's line anchor kept. An EXPLICIT --top-k=N (including 0) opts out of BOTH the exact-name default and mode= auto-selection and keeps the classic undecorated shape; a SYM:START-END slice opts out of mode= auto-selection only (serving the whole file would invert an explicit narrowing) but still gets the exact-name top-k=0 default when it applies. When the bundle wins AND no explicit --top-k was given, the requested bodies are served BEFORE the ranked map (not buried after it), and the map's escape hatch also rides the root as note="...--top-k=0 for the payload alone..." (not stderr-only): a caller reading only stdout sees both the answer and how to drop the map on the next call. Absent whenever the bundle carries no map at all (top-k=0, exact-name default) or loses to whole-file, and whenever --top-k was explicit — the classic shape above carries no note= either. **Try it** @@ -1584,7 +1589,7 @@ $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport **Caveats (stated by the binary):** -- a wrong scope refuses, it never falls back to the bare-name union (START-END is 1-based within the def's OWN body — lines="lo-hi/total" marks the slice partial; +- a wrong scope refuses, it never falls back to the bare-name union Scope.name|Scope#name the same match, dotted (Python/JS/Java) or Ruby-style; - FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. - A MULTI-match name (an ambiguous bare name) or a multi-token --expand keeps the map — there IS something to disambiguate. @@ -1604,7 +1609,7 @@ $ ./build/ripwire . --expand=compressBody --top-k=0 --compress - + - + @@ -1739,16 +1744,16 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - - + + + + - + - + + - @@ -1776,18 +1781,18 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - - - - - - - + + + + + + + - - + + + - ... [17 more line(s); run it to see the whole thing] ``` @@ -1804,7 +1809,7 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + @@ -1814,7 +1819,7 @@ $ ./build/ripwire . --clones - + ... [17 more line(s); run it to see the whole thing] @@ -1835,15 +1840,15 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - + - - + + - + - + ``` @@ -1868,7 +1873,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1903,18 +1908,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] ``` @@ -1934,18 +1939,18 @@ _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] ``` @@ -1971,13 +1976,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] ``` @@ -2002,7 +2007,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2012,9 +2017,9 @@ $ ./build/ripwire . --naming-calibration +

-

... [14 more line(s); run it to see the whole thing] ``` @@ -2037,9 +2042,9 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - + + + @@ -2074,8 +2079,8 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - - + + @@ -2083,7 +2088,7 @@ $ ./build/ripwire . --lint --naming-locals - + @@ -2109,7 +2114,7 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - + @@ -2142,16 +2147,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - - + - - - - + + + + + ... [22 more line(s); run it to see the whole thing] ``` @@ -2174,15 +2179,15 @@ _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] ``` @@ -2200,16 +2205,16 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + - + - + @@ -2229,19 +2234,19 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - - - - - - - - - - - - - + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2267,7 +2272,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2297,7 +2302,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2323,7 +2328,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2350,8 +2355,8 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - - + + @@ -2359,10 +2364,10 @@ $ ./build/ripwire . --lint - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2459,8 +2464,8 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - - + + @@ -2468,10 +2473,10 @@ $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2545,19 +2550,19 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + - + - + + - - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2576,8 +2581,8 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - + ``` @@ -2601,19 +2606,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] ``` @@ -2638,16 +2643,16 @@ $ ./build/ripwire . --report # ripwire architecture report -2376 files · 23174 symbols · 32805 edges · 2235 modules (10668 call-graph isolated) +2381 files · 23348 symbols · 33292 edges · 2252 modules (10730 call-graph isolated) Root: `.` -Call-graph isolate provenance: 2842 declaration, 1520 header, 1842 source, 4464 document; 0 connected Louvain singletons +Call-graph isolate provenance: 2850 declaration, 1549 header, 1842 source, 4489 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 2235) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1270 symbols -- **src::append@elixir_resolve.h:111:4875 [resolve,collect,append]** — 788 symbols -- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 549 symbols +## Modules (call-graph clusters; showing 12 of 2252) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1243 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,add,collect]** — 1086 symbols +- **src::formatTo@infra/emit.h:141:8856 [read,close,run]** — 661 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2664,13 +2669,13 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - + - - + + - + @@ -2695,16 +2700,16 @@ $ ./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
6753"] + n101["src
6867"] n102["src/infra
1275"] end subgraph sg1 ["test"] - n103["test
4771"] + n103["test
4792"] n294["test/fixtures/recallpassage
314"] n280["test/expandmodefix
151"] n282["test/expandsibsfix
149"] - n423["test/rubyattrsfix
85"] - n360["test/massfix
77"] + n424["test/rubyattrsfix
85"] + n361["test/massfix
77"] n326["test/javamethodreffix
62"] ... [17 more line(s); run it to see the whole thing] ``` @@ -2724,9 +2729,9 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - - + + + @@ -2735,7 +2740,7 @@ $ ./build/ripwire . --owners - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2755,7 +2760,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2809,9 +2814,9 @@ $ ./build/ripwire . --quality-baseline --allow-dirty ### `--quality-delta` -**Answers:** before a PR: report ONLY what your change made worse, across 11 kinds agent self-check before a PR (pair with --test-gate): report ONLY what a change made worse vs the baseline (11 kinds: complexity/verbosity/nesting/params/dup/dead/api-surface + error-masking/short-horizon-churn/new-clone-of-reused-helper + placeholder); +**Answers:** before a PR: report ONLY what your change made worse, across 11 kinds — pair with --test-gate. -every finding is classified by ORIGIN: a symbol that EXISTED at the baseline and got worse (preexisting-worse="N", no attribute on the row) vs one that exists only because the code is NEW (new-symbol="N", origin="new-symbol" on the row). A small numeric delta is additionally sev="minor". EXIT 2 ONLY on preexisting-worse AND major AND unacked — the gating="N" header count. New-symbol rows are still PRINTED (they are the debt you are adding — read them), they just never gate; exit 0 means "nothing that already existed got worse", not "clean". Clone kinds classify by member set (new-symbol only if EVERY member is new); short-horizon-churn is preexisting by construction. LIMIT: origin is canonId (path::scope::name) identity, so a RENAMED/MOVED symbol reads as new and a regression carried in with the move will not gate. error-masking = a NEW empty/pass/comment-only handler, or log-only (a broad handler whose body only logs and never names the error) or rethrow-only (the sole handler re-throws it unchanged); those two gate only where their precision was measured (Python) and are sev="minor" in every other language. placeholder = a stub the change ADDED (todo!()/unimplemented!(), Kotlin TODO(), NotImplementedException, a bare raise NotImplementedError as a free function's body, a throw/raise/panic/assert saying "not implemented") or a comment line opening with TODO/FIXME that names no issue (#12, ABC-12, a URL); new-symbol by construction, so it never gates. Counted per enclosing symbol, like error-masking: a file-level TODO outside every definition is not counted. Test-fixture dirs + doc sections are exempt from dead-code/churn; churn needs COMMITTED thrash evidence (rewritten across recent commits AND again by this diff), never the current edit alone WHICH FLOOR IT COMPARES AGAINST, and a side effect: the sidecar is honored only when the sha it was pinned at EQUALS the current git HEAD (strict equality — an ancestor commit describes a DIFFERENT tree, so everything committed since would read as your regression). A sidecar pinned anywhere else is STALE: this verb then DELETES it from your working tree (self-heal, so the next run does not rediscover the dead pin) and auto-compares the working tree vs git HEAD instead. Re-pin with --quality-baseline. The read-only MCP quality_delta verb applies the SAME staleness test but never deletes. A sidecar at the current HEAD that ANOTHER ripwire build pinned (its producer stamp names other sources — a dead set depends on how calls were resolved) is FOREIGN: both arms ignore it, never delete it, and auto-compare vs git HEAD. Which floor was actually used is on every report as baseline=: sidecar | git-HEAD | git-HEAD (stale sidecar removed) | git-HEAD (stale sidecar ignored) | git-HEAD (foreign sidecar ignored) — the stale two say a stale sidecar existed, and 'removed' means the file is gone. A non-git root has no HEAD to fall back to, so its sidecar is honored whenever this build pinned it; without one there, or with another build's, the verb exits 1. +Each kind is measured against the baseline (complexity/verbosity/nesting/params/dup/dead/api-surface + error-masking/short-horizon-churn/new-clone-of-reused-helper + placeholder); every finding is classified by ORIGIN: a symbol that EXISTED at the baseline and got worse (preexisting-worse="N", no attribute on the row) vs one that exists only because the code is NEW (new-symbol="N", origin="new-symbol" on the row). A small numeric delta is additionally sev="minor". EXIT 2 ONLY on preexisting-worse AND major AND unacked — the gating="N" header count. New-symbol rows are still PRINTED (they are the debt you are adding — read them), they just never gate; exit 0 means "nothing that already existed got worse", not "clean". Clone kinds classify by member set (new-symbol only if EVERY member is new); short-horizon-churn is preexisting by construction. LIMIT: origin is canonId (path::scope::name) identity, so a RENAMED/MOVED symbol reads as new and a regression carried in with the move will not gate. error-masking = a NEW empty/pass/comment-only handler, or log-only (a broad handler whose body only logs and never names the error) or rethrow-only (the sole handler re-throws it unchanged); those two gate only where their precision was measured (Python) and are sev="minor" in every other language. placeholder = a stub the change ADDED (todo!()/unimplemented!(), Kotlin TODO(), NotImplementedException, a bare raise NotImplementedError as a free function's body, a throw/raise/panic/assert saying "not implemented") or a comment line opening with TODO/FIXME that names no issue (#12, ABC-12, a URL); new-symbol by construction, so it never gates. Counted per enclosing symbol, like error-masking: a file-level TODO outside every definition is not counted. Test-fixture dirs + doc sections are exempt from dead-code/churn; churn needs COMMITTED thrash evidence (rewritten across recent commits AND again by this diff), never the current edit alone WHICH FLOOR IT COMPARES AGAINST, and a side effect: the sidecar is honored only when the sha it was pinned at EQUALS the current git HEAD (strict equality — an ancestor commit describes a DIFFERENT tree, so everything committed since would read as your regression). A sidecar pinned anywhere else is STALE: this verb then DELETES it from your working tree (self-heal, so the next run does not rediscover the dead pin) and auto-compares the working tree vs git HEAD instead. Re-pin with --quality-baseline. The read-only MCP quality_delta verb applies the SAME staleness test but never deletes. A sidecar at the current HEAD that ANOTHER ripwire build pinned (its producer stamp names other sources — a dead set depends on how calls were resolved) is FOREIGN: both arms ignore it, never delete it, and auto-compare vs git HEAD. Which floor was actually used is on every report as baseline=: sidecar | git-HEAD | git-HEAD (stale sidecar removed) | git-HEAD (stale sidecar ignored) | git-HEAD (foreign sidecar ignored) — the stale two say a stale sidecar existed, and 'removed' means the file is gone. A non-git root has no HEAD to fall back to, so its sidecar is honored whenever this build pinned it; without one there, or with another build's, the verb exits 1. **Try it** @@ -2820,7 +2825,7 @@ _Recorded against a DIRTY tree, so any row below is a real regression in the wor ``` $ ./build/ripwire . --quality-delta - @@ -2857,7 +2862,7 @@ _Recorded against a DIRTY tree, so any row below is a real regression in the wor ``` $ ./build/ripwire . --quality-delta - @@ -2894,7 +2899,7 @@ _The Delta Maintainability Model scalar for the WORKING TREE vs HEAD — recorde ``` $ ./build/ripwire . --dmm -

@@ -5394,27 +5403,26 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir

-

-

+

+

-

+

-

-… [108 more display lines; full output is 12146 bytes on 1 raw line(s)] +… [108 more display lines; full output is 12164 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: 8.60s** +**wall time: 10.61s** ````` - - + + @@ -5422,27 +5430,27 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + - + - - + + - + - + - -… [643 more display lines; full output is 72265 bytes on 1 raw line(s)] + +… [643 more display lines; full output is 72219 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint-catalog` @@ -5488,7 +5496,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=32cb666f84c1d715 entries=757 +ripwire legend dictionary ripwire.dict/v1 dictv=3eabbad50f0ec36c entries=778 : 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=) @@ -5518,24 +5526,24 @@ ripwire.uses/v1 : resolvable use-sites of of=: : transitive blast radius of of=: reach set, importers; defs= matched, reaches= their transitive callers, radius_tested= non-tests an indexed test reaches, radius_untested= the rest; importers= files that #include/import a def's file ripwire.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, -… [728 more display lines; full output is 74738 bytes on 758 raw line(s)] +… [749 more display lines; full output is 77207 bytes on 779 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: 9.77s** +**wall time: 9.55s** ````` - + - + - + vector<std::vector<double>> @@ -5558,7 +5566,7 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal g_pool[ g_readOf[i] ] g_pool[ g_readOf[i] ] m[ g_pool[i] ] -… [491 more display lines; full output is 59286 bytes on 1 raw line(s)] +… [497 more display lines; full output is 60049 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint --lint-ignore=naming-,cache-` @@ -5569,8 +5577,8 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal ````` - - + + @@ -5578,17 +5586,17 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal - + - - - + + + - + - + @@ -5598,14 +5606,14 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal 20 delta < 0.02 && delta > -0.02 0.02 -… [732 more display lines; full output is 65693 bytes on 1 raw line(s)] +… [729 more display lines; full output is 65885 bytes on 1 raw line(s)] ````` ## `./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: 8.32s** +**exit code: 1** — **wall time: 9.82s** ````` (empty) @@ -5621,7 +5629,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: 8.89s** +**exit code: 1** — **wall time: 10.00s** ````` (empty) @@ -5637,7 +5645,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: 8.04s** +**wall time: 10.10s** ````` {"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[{"tool":{"driver":{"name":"ripwire","rules":[{"id":"c-style-cast","shortDescription":{"text":"c-style-cast"},"properties":{"builtin":true,"capped":false,"applicable": … [line truncated: 7 more bytes on this line] @@ -5670,13 +5678,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}}, -… [9803 more display lines; full output is 1372913 bytes on 1 raw line(s)] +… [9873 more display lines; full output is 1382873 bytes on 1 raw line(s)] ````` Parsed summary of the same SARIF (past the display cut): ````` -sarif 2.1.0 rules= 39 results= 4897 +sarif 2.1.0 rules= 39 results= 4932 ````` ## `./build/ripwire . --lint --sarif --limit=5` @@ -5699,11 +5707,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 DIRTY tree (the sandbox section shows a real delta). UNAVAILABLE is a stated reason, never 0 or 1.* -**wall time: 4.93s** +**wall time: 5.32s** ````` -

@@ -5714,11 +5722,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: 17.94s** +**wall time: 24.62s** ````` -

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

@@ -5744,20 +5752,20 @@ 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.43s** +**wall time: 1.85s** ````` - + - + - + @@ -5771,7 +5779,7 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M - + @@ -5784,41 +5792,41 @@ 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.88s** +**wall time: 1.65s** ````` - - - + + + - - - - - + + + + + + - + - - - - + + + + - - - + + ````` @@ -5826,7 +5834,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.47s** +**wall time: 1.87s** ````` (empty) @@ -5835,14 +5843,14 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M Artifact written: ````` -158732 /aux/map2.html +158477 /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: 21.92s** +**wall time: 26.09s** ````` (empty) @@ -5851,26 +5859,28 @@ Artifact written: stderr: ````` -ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (16441642 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (36007128 bytes, rich family) +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (16621462 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (36381791 bytes, rich family) ````` Artifact written: ````` -16441642 /aux/ci_index.lean.ripwirecache -36007128 /aux/ci_index.rich.ripwirecache -52448770 total +16621462 /aux/ci_index.lean.ripwirecache +36381791 /aux/ci_index.rich.ripwirecache +53003253 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.06s** + ````` - + @@ -5890,7 +5900,7 @@ Artifact written: ````` - + @@ -5914,7 +5924,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. -109132 /aux/pin_census.tsv +110357 /aux/pin_census.tsv ````` ## `./build/ripwire . --plan-lint=test/planlintfix/wave.md` @@ -5925,7 +5935,7 @@ Artifact written: ````` - + @@ -5947,7 +5957,7 @@ Artifact written: ````` - + @@ -5959,18 +5969,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: 2.17s** +**exit code: 1** — **wall time: 2.31s** ````` - + - - + + - - + @@ -6013,31 +6023,31 @@ 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: 12.09s** +**wall time: 14.18s** ````` ripwire situational-awareness — 1 changed file(s), 13 symbols in them root: . -at: dac3b8205+dirty - [1] blast radius: 182 symbols across 43 files transitively depend on these changes (showing 8 of 43 files — shown=8 total=43 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=43) - counts_floor=1 graph_ambiguous=9831 graph_unresolved=13184 graph_unindexed=231 (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] +at: 52c4a428f+dirty + [1] blast radius: 190 symbols across 45 files transitively depend on these changes (showing 8 of 45 files — shown=8 total=45 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=45) + counts_floor=1 graph_ambiguous=9977 graph_unresolved=13331 graph_unindexed=231 (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 (32 dependent symbols) src/slice.h (30 dependent symbols) src/serialize.h (9 dependent symbols) src/quality.h (8 dependent symbols) src/verbs_for.h (8 dependent symbols) + src/verbs_navigate.h (8 dependent symbols) src/main.cpp (7 dependent symbols) src/verbs_quality.h (7 dependent symbols) - src/mergescout.h (6 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=722 — 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="3758": - (none — 3758 commits were mined and none co-edited a file outside your diff) + script_gates_unmodelled=725 — 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="3814": + (none — 3814 commits were mined and none co-edited a file outside your diff) next: --test-gate ````` @@ -6045,41 +6055,41 @@ at: dac3b8205+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: 3.04s** +**exit code: 4** — **wall time: 2.87s** ````` - - - + + - + - - + + - + - - - + + + - + - - + + - + ````` @@ -6087,11 +6097,11 @@ at: dac3b8205+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: 52.15s** +**exit code: 2** — **wall time: 64.53s** ````` - @@ -6134,11 +6144,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, but the ~4 KB prose legend the compact default trims is restored: the posture a human reads once to learn the schema.* -**exit code: 2** — **wall time: 18.62s** +**exit code: 2** — **wall time: 19.86s** ````` - @@ -6181,10 +6191,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: 18.74s** +**exit code: 2** — **wall time: 19.82s** ````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":122,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"dac3b8205+dirty","renames":57,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 187 more bytes on this line] +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":122,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"52c4a428f+dirty","renames":57,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 187 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"}, @@ -6228,7 +6238,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: 10.57s** +**exit code: 1** — **wall time: 11.08s** ````` (empty) @@ -6246,7 +6256,7 @@ ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written *NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* -**wall time: 11.64s** +**wall time: 11.13s** ````` (empty) @@ -6264,11 +6274,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: 19.01s** +**exit code: 2** — **wall time: 19.39s** ````` - @@ -6327,11 +6337,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6343,11 +6353,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row identical to the compact default above, but the ~5.7 KB explanatory legend is restored — the posture for a first-time reader.* -**wall time: 1.66s** +**wall time: 1.40s** ````` - @@ -6359,18 +6369,19 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - - + + @@ -6383,29 +6394,28 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + - -… [85 more display lines; full output is 8317 bytes on 1 raw line(s)] +… [86 more display lines; full output is 8379 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.19s** +**wall time: 2.07s** ````` - - + + @@ -6439,11 +6449,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. --> - + @@ -6453,7 +6463,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + @@ -6468,7 +6478,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + @@ -6479,11 +6489,11 @@ 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. unknown=N: refs that could not be analysed (v=unknown, e.g. no merge base); never counted merged. --> - + ````` @@ -6491,11 +6501,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row rather than silently dropping them.* -**wall time: 2.20s** +**wall time: 2.06s** ````` - + ````` @@ -6503,11 +6513,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire . at=: commit+dirty+shallow. available=0: no score at all (dmm=UNAVAILABLE, reason= says why); never read as 1.000 or 0.000. combine=pooled: root dmm= is summed good over summed good+bad of the 3 properties (ripwire's own). low_loc=/low_cx=/low_params=: a unit is LOW risk at or under these lines / cyclomatic / params. base_units=/base_volume=/target_units=/target_volume=: units with a body and their line span per side. --> -

@@ -6518,11 +6528,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire with a do-not-ack banner and never gate — scoped-out-gating= says how many would have.* -**wall time: 20.61s** +**wall time: 19.31s** ````` - @@ -6564,7 +6574,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: 13.05s** +**exit code: 1** — **wall time: 11.04s** ````` (empty) @@ -6584,12 +6594,12 @@ 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.23s** +**wall time: 3.39s** ````` - - + + @@ -6613,11 +6623,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr - - - - - + + + + + ````` @@ -6626,10 +6636,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.01s** - ````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-01 keep this branch-free — it sits inside the PageRank sort comparator dac3b8205da74d71cc23fc49b792d0572a584ef6 sync-rebase +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-03 keep this branch-free — it sits inside the PageRank sort comparator 52c4a428f0ea2e42ff4e0a855bf5d5e206647b8f sync-rebase ````` stderr: @@ -6654,7 +6662,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort - + @@ -6706,7 +6714,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-01 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort dac3b8205da74d71cc23fc49b792d0572a584ef6 sync-rebase +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-03 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 52c4a428f0ea2e42ff4e0a855bf5d5e206647b8f sync-rebase ````` ## `./build/ripwire . --expand=lessByScoreDescId --top-k=0` @@ -6750,10 +6758,10 @@ src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-01 chose the flat The element on the same output — past the 30-line display cut above: ````` - + - + ````` @@ -6778,7 +6786,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: 16.02s** +**wall time: 18.06s** Input file: @@ -6795,12 +6803,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":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -6813,15 +6821,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.97s** +**wall time: 1.80s** ````` - + - + @@ -6835,16 +6843,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: 4.28s** +**wall time: 3.89s** ````` {"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":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -6857,7 +6865,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: 2.50s** +**wall time: 1.88s** ````` {"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] @@ -6873,19 +6881,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.43s** +**wall time: 4.38s** ````` {"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":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ````` stderr: @@ -6898,7 +6906,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: 2.09s** +**exit code: 1** — **wall time: 1.89s** ````` (empty) @@ -6914,7 +6922,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: 2.57s** +**wall time: 1.88s** Input file: @@ -6932,21 +6940,21 @@ Input file: ````` ````` -{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false," … [line truncated: 301 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false," … [line truncated: 301 more bytes on this line] ````` ## `./build/ripwire . --edit-plan=/aux/edit_plan.json --apply` *The same plan committed: per-file locks, re-verify-before-write, atomic rename, rollback on a later failure.* -**wall time: 4.17s** +**wall time: 3.93s** ````` -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"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: 485 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"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: 485 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":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true}}]} +"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true}}]} ````` ## `./build/ripwire . --edit-plan=/aux/edit_plan.json` @@ -6969,11 +6977,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: 21.78s** +**exit code: 2** — **wall time: 19.87s** ````` - @@ -7001,7 +7009,7 @@ ripwire: --edit-plan requires exactly one of --dry-run or --apply - + … [100 more display lines; full output is 13303 bytes on 1 raw line(s)] ````` @@ -7016,7 +7024,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: 11.31s** +**exit code: 1** — **wall time: 11.09s** ````` (empty) @@ -7035,7 +7043,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: 18.02s** +**wall time: 18.79s** ````` (empty) @@ -7045,16 +7053,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 22469 per-symbol rows, 23176 indexed symbols total) +ripwire: wrote ./.ripwire_quality_baseline (snapshot of 22641 per-symbol rows, 23350 indexed symbols total) ````` Artifact written: ````` -3763467 .ripwire_quality_baseline +3793533 .ripwire_quality_baseline # ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit -producer 690905c297bb80c57ee4e28604c7295b2ee2119ef4785a4616d979c86ce77078 -head dac3b8205da74d71cc23fc49b792d0572a584ef6 +producer 4e28d3568d96e30bcf8d8eded82296e879ed635c73572e9fe01cd38908126ee3 +head 52c4a428f0ea2e42ff4e0a855bf5d5e206647b8f dirty 1 absorbed 2 ccx d19d7c13cbc7 2 @@ -7067,11 +7075,11 @@ ccx 7d8862be4e *Against that sidecar the same tree reads regressions=0 — but baseline_absorbed= is on the root, so this green means clean SINCE THE PIN, never clean. A baseline is a floor YOU chose, and it belongs BEFORE the change.* -**wall time: 18.14s** +**wall time: 19.27s** ````` - @@ -7094,12 +7102,12 @@ ccx 7d8862be4e - + - + … [95 more display lines; full output is 12168 bytes on 1 raw line(s)] ````` @@ -7141,7 +7149,7 @@ ccx 7d8862be4e {"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: 2117 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: 1226 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: 1204 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: 1250 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] @@ -7152,7 +7160,7 @@ ccx 7d8862be4e The manifest, summarised (name / description bytes / required args) — what the host pays in context every session: ````` -tools= 33 manifest_bytes= 46753 (~tokens at 4 bytes/token: 11688 ) +tools= 33 manifest_bytes= 46799 (~tokens at 4 bytes/token: 11699 ) explore desc_bytes= 1322 schema_bytes= 958 required=['task'] batch desc_bytes= 1235 schema_bytes= 962 required=['queries'] edit_check desc_bytes= 1180 schema_bytes= 889 required=['symbol'] @@ -7162,9 +7170,9 @@ 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=[] +whereis desc_bytes= 701 schema_bytes= 714 required=['symbol'] affected desc_bytes= 790 schema_bytes= 610 required=['files'] from_trace desc_bytes= 829 schema_bytes= 559 required=['trace'] -whereis desc_bytes= 655 schema_bytes= 714 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=[] @@ -7192,138 +7200,138 @@ quality_baseline desc_bytes= 407 schema_bytes= 193 required=[] *MCP `for`: always bundle=sigs (never the CLI's compact route), the same ranked signatures as --for.* -**wall time: 1.77s** +**wall time: 1.72s** ````` {"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":"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ⅇ0Cy{{Sh`|4 zUeac=X-CZY;d7{QqqyqW`Vc(`Ho1#t7hJb#9$0@TB!owmfJ<%$r;?)U7;c!Yn6IOn zE$yMkY^V|zFqnT*`^0s02R(+XpSsbxrXdj$=%60PLchvJkkF6j9^*P~DR!#~AgK*L z#?DB^Tp7{rYoKi)ps9@*>R5hwtQ29sQkr;B9l77GZ0|NPab6*@__g+n(S>MyKx^2= zxy{<|Xw0R|BYD#t0l)R(FR}vPgn}>8+hmKU5Rj^sT_ox=_v20Lo0ckx$PW1%yoOQ3 z)ONRnF_9bA?H6{c`Y4mM5v*gA*L6L1_;>b&XLE8F;v%hj8rg5ScVsHAcQ(hV?>s?# ze}SV=v;Yg-P7xgLqT8V6@(LI3#?8L%v$_)pS?yH$^oRmlgXuLaMS{sHJQp)iaj|7@ zipXQk+^w*G<8KCNua4*ylai0_?G@9Kk6ngu8as`qh2b|})_Gga3(`ha1J>>(?&lTP z--%i>WF%Gi9kiz^UuHV5&LNg#lo_*eIHSXD8f^a#atRV1lv(~_XcZ~ zL1X%88^jfVW1i9Iu$4|8X;rbP?mkemZCmC}e5m6YGthsVz#?}~)5 zVTnesCp_-L_haFdTTef`VNa^XuhF^h2vk*Yxb>6uC&?UzHxL+{`fj6+zXdb0UboaN zoD0J5M6WCenLrMH^lmWDCs+nla5i}8>G3hOb_QOXBUsQ|R3R)_6f@Bs$zgc-8vC>HOd@B)%YBB9U`iR`!EpkI&d$2--7+4RRc1 zwB{3#Y6IwHXNW+96b01;bgm5Z*~uCZn-9{*&8qgnN4CW66W5Tezcp=bBC$o_a2Daf zw!)Tjc

{l;{CzrLBvSo*MGl19| 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

@@ -2964,7 +2969,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -2994,18 +2999,18 @@ _Fast per-symbol post-edit contract check vs git HEAD — recorded against a DIR ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - + - + ``` -**Shaped by:** `--impact`, `--edit-target-file`, `--slice`, `--at`, `--legend`, `--limit` +**Shaped by:** `--edit-target-file`, `--slice`, `--at`, `--legend`, `--limit` **Caveats (stated by the binary):** @@ -3048,12 +3053,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":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3069,12 +3074,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":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3120,12 +3125,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":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3184,11 +3189,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":9828,"graph_unresolved":13184,"graph_unindexed":231,"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: 485 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"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: 485 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":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true}}]} +"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3233,7 +3238,7 @@ _Bare --slice=SYM: the INVENTORY of sliceable locals (), so a calle ``` $ ./build/ripwire . --slice=rankGraphTeleport - + @@ -3270,7 +3275,7 @@ _Forward flow: which statements the seed's value reaches, at the default depth b ``` $ ./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd - + @@ -3351,7 +3356,7 @@ _No-LLM review-evidence bundle for the working-tree diff — recorded against a ``` $ ./build/ripwire . --pr-context - ``` @@ -3377,14 +3382,14 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + - + @@ -3460,10 +3465,10 @@ _Which refs of the one branch `main` (no ref family left to select on this check ``` $ ./build/ripwire . --stray-content=main - + - + ``` @@ -3489,7 +3494,7 @@ _Select the genuinely-unmerged refs of the one branch `main` (no ref family left ``` $ ./build/ripwire . --stray-content=main --plan - + @@ -3525,7 +3530,7 @@ _Cross-branch ABI-break gate over the one branch `main` (no ref family left to s ``` $ ./build/ripwire . --stray-content=main --abi - row then says v="never" or v="removed" with the commit, date and file that removed it. Remote-tracking refs are excluded (they mirror local ones); refs are capped, narrow with --stray-content=SUBSTR. +Each distinct blob is read ONCE (content-addressed), so N branches cost ~one tree. kind="def" on a HEAD row is the PARSED index's answer (head_labels="index"); on a REF row it is a LEXICAL heuristic — ref blobs are raw text, never ingested, so a doc quoting a signature still reads as a definition. head_labels="lexical" ⇒ HEAD fell back to that heuristic too (no indexed def of the name, or a working tree that drifted from HEAD). refs_scanned= is the SCAN denominator (refs read besides HEAD), not a matched count. Read-only; single-root only. A checkout that differs from HEAD is read from disk: each changed path's rows say ref="worktree" and replace HEAD's, at= gains +dirty, and worktree= says whether every one was read. LIMITS: a TREE scan finds only what some ref STILL carries, so hits="0" alone cannot tell a name this repo never had from one it deleted, and content dropped by every tree is invisible. Add --with-history: a row then says v="never" or v="removed" with the commit, date and file that removed it. Remote-tracking refs are excluded (they mirror local ones); refs are capped, narrow with --stray-content=SUBSTR. **Try it** @@ -3547,12 +3552,12 @@ _Which ref's tree defines or mentions SYM — HEAD first, then every local branc ``` $ ./build/ripwire . --whereis=rankGraphTeleport - - - - - + + + + + ... [25 more line(s); run it to see the whole thing] ``` @@ -3578,7 +3583,7 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat $ ./build/ripwire . --flags - + @@ -3659,7 +3664,7 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - @@ -3694,17 +3699,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + - - - - - - - - - + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3729,17 +3734,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + - - - - - - - - - + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3763,18 +3768,18 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + - - - - - - - - - + + + + + + + + + ... [18 more line(s); run it to see the whole thing] ``` @@ -3799,7 +3804,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -3837,7 +3842,7 @@ AddressSanitizer:DEADLYSIGNAL #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:4647 #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4688 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 - #3 0x102e01a44 in main src/main.cpp:3975 + #3 0x102e01a44 in main src/main.cpp:3983 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) ==41337==ABORTING ``` @@ -3864,7 +3869,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -3954,7 +3959,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 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 ) @@ -3991,13 +3996,13 @@ _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. --> + 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 ) @@ -4024,10 +4029,10 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - - - + + NodeId order. Renumbering an otherwise-identical graph (the exact "same probe file, different so…inline constexpr float kScoreTieAbsEps = 1e-6f @@ -4099,14 +4104,14 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - + - - + + - - + @@ -4133,14 +4138,14 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - + - - + + - - + @@ -4167,7 +4172,7 @@ $ ./build/ripwire . --skipped - @@ -4255,14 +4260,14 @@ _Rank by git change-frequency prior instead of PageRank._ $ ./build/ripwire . --rank-by=churn --top-k=5 - - + + - + - + @@ -4288,11 +4293,11 @@ a trailing slash is ignored; absolute paths and '..' are refused). The global block stays byte-identical, a second page follows it — n=/of= are its counts (of= IS the total, so the paging half carries no total=), capped="1" has_more="1" next_offset= offset= limit= page it, and next= replays THIS run's own corpus flags (--since/--exclude) so the page it names is a page of the same answer. The symbol map collapses to a disclosed stub — the map was not asked for and was not ranked at all, which is why the header carries no pr_iters=. merge_bombs_skipped= stays on the global block: it counts the window's skipped commits, not the directory's._ ``` -$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~9 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - - + + @@ -4301,7 +4306,7 @@ $ ./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclud - + ... [5 more line(s); run it to see the whole thing] ``` @@ -4365,7 +4370,7 @@ _The same gating report under --legend=full — same rows, same exit 2, but the ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4393,7 +4398,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=32cb666f84c1d715 entries=757 +ripwire legend dictionary ripwire.dict/v1 dictv=3eabbad50f0ec36c entries=778 : 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=) @@ -4445,18 +4450,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] ``` @@ -4481,12 +4486,12 @@ _Drop matching paths (repeatable) before ranking._ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - + - + @@ -4512,8 +4517,8 @@ _Full map re-ranked with teleport toward git-changed files — recorded against $ ./build/ripwire . --map-diff --top-k=5 - - + + @@ -4546,7 +4551,7 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - + @@ -4594,7 +4599,7 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co $ ./build/ripwire . --no-cache --top-k=3 - + @@ -4623,14 +4628,14 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses $ ./build/ripwire . --no-ignore --top-k=3 - + - + - + @@ -4655,7 +4660,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 - + @@ -4735,7 +4740,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 - + @@ -4840,8 +4845,8 @@ _Labelled verdict-accuracy eval for --stray-content — three labels over REAL l $ ./build/ripwire . --eval-stray=/aux/stray_labels2.tsv # 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) sync-rebase merged -sync-upstream unmerged -upstream-paging merged +sync-rebase-backup unmerged +sync-upstream merged ``` ### `--eval` @@ -4856,14 +4861,14 @@ _Self-eval: co-change recall vs BM25._ $ ./build/ripwire . --eval ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 2.2% 6.5% 14.7% - BM25 6.7% 7.8% 16.0% - BM25sub 10.9% 14.8% 21.1% - BM25body 7.9% 20.4% 34.0% - fused 11.6% 16.1% 23.4% - anchored 7.9% 20.4% 34.0% - same-dir 0.0% 1.5% 3.1% - random 0.2% 0.4% 0.8% <- floor (random ranking over F=2376 files) + ripwire 2.4% 6.8% 14.7% + BM25 6.9% 9.2% 13.1% + BM25sub 11.8% 16.8% 25.7% + BM25body 16.6% 30.3% 41.5% + fused 12.1% 15.4% 27.0% + anchored 16.6% 30.3% 41.5% + same-dir 1.2% 5.0% 6.4% + random 0.2% 0.4% 0.8% <- floor (random ranking over F=2381 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 / @@ -4886,19 +4891,19 @@ _Known-item retrieval eval: MRR + recall@k per ranker per query mode._ ``` $ ./build/ripwire . --eval-retrieval ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=5540 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + sample: population=5624 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.697 56.4% 86.3% 91.2% - subtoken doc-phrase 0.922 90.2% 94.2% 95.1% - name-exact name 0.912 84.7% 96.3% 97.7% + subtoken name 0.697 56.3% 86.2% 91.1% + subtoken doc-phrase 0.922 90.2% 94.2% 95.2% + name-exact name 0.912 84.6% 96.3% 97.7% name-exact doc-phrase 0.011 0.4% 1.6% 2.3% - anchored name 0.706 58.4% 85.9% 89.8% - anchored doc-phrase 0.918 89.7% 94.1% 94.7% - routed name 0.914 85.0% 96.5% 97.9% - routed doc-phrase 0.920 90.1% 94.0% 94.8% + anchored name 0.705 58.2% 85.9% 89.8% + anchored doc-phrase 0.918 89.6% 94.2% 94.8% + routed name 0.914 84.9% 96.5% 97.9% + routed doc-phrase 0.921 90.1% 94.0% 94.9% ... [6 more line(s); run it to see the whole thing] ``` @@ -4944,10 +4949,10 @@ _Version + short build info._ ``` $ ./build/ripwire --version -ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=dac3b8205) +ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=c9c1df83b) ``` -**Shaped by:** `--impact`, `--verify`, `--metrics`, `--deps`, `--naming-locals`, `--arch`, `--dry-run`, `--doc-drift` +**Shaped by:** `--verify`, `--metrics`, `--deps`, `--naming-locals`, `--arch`, `--dry-run`, `--doc-drift`, `--from-trace` --- diff --git a/docs/captures/COMMANDS_showcase_2026-10-01.md b/docs/captures/COMMANDS_showcase_2026-10-03.md similarity index 85% rename from docs/captures/COMMANDS_showcase_2026-10-01.md rename to docs/captures/COMMANDS_showcase_2026-10-03.md index 0ae0c8a65..9ab3237a9 100644 --- a/docs/captures/COMMANDS_showcase_2026-10-01.md +++ b/docs/captures/COMMANDS_showcase_2026-10-03.md @@ -1,9 +1,9 @@ # ripwire — every verb, run for real -- **Date:** 2026-10-01 (regenerated capture; supersedes any older `docs/captures/COMMANDS_showcase_*.md`) +- **Date:** 2026-10-03 (regenerated capture; supersedes any older `docs/captures/COMMANDS_showcase_*.md`) - **Lives in `docs/captures/`** — a directory the crawl/retrieval lenses SKIP (`kCrawlSkipDirs`, src/ingest.h): a generated doc that quotes every verb's output out-scores the source for any query about the tool and was measured at 77% of `--recall` on this repo when it sat at the root. `test/argvdiffcheck.sh` harvests its `## `-heading command lines as differential vectors — keep that format. -- **Version:** `ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=dac3b8205)` -- **Repo:** the ripwire repo @ `dac3b8205` — **DIRTY — `git status --porcelain` reports 1 entr(ies)**. The diff-aware verbs (`--situ`/`--test-gate`/`--quality-delta`/`--pr-context`/`--map-diff`/`--edit-check`) answer a question about the WORKING TREE, so that condition is part of their answer and every one of their captions below states which tree it recorded against. This run recorded against a dirty tree, so their output here reflects uncommitted working-copy edits rather than the empty/exit-0 shape a clean checkout gives. They appear a SECOND time in the final section against a throwaway `git clone --local` sandbox carrying one KNOWN, deliberate regression — that is the reproducible gating demonstration; this one is whatever the tree happened to hold. +- **Version:** `ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=c9c1df83b)` +- **Repo:** the ripwire repo @ `52c4a428f` — **DIRTY — `git status --porcelain` reports 1 entr(ies)**. The diff-aware verbs (`--situ`/`--test-gate`/`--quality-delta`/`--pr-context`/`--map-diff`/`--edit-check`) answer a question about the WORKING TREE, so that condition is part of their answer and every one of their captions below states which tree it recorded against. This run recorded against a dirty tree, so their output here reflects uncommitted working-copy edits rather than the empty/exit-0 shape a clean checkout gives. They appear a SECOND time in the final section against a throwaway `git clone --local` sandbox carrying one KNOWN, deliberate regression — that is the reproducible gating demonstration; this one is whatever the tree happened to hold. - **Corpus:** the ripwire repo itself (dogfood), via `./build/ripwire` - **Sandbox diff** (the last section only): `.ripwire_notes | 2 ++ .ripwire_quality_acks | 1 + @@ -12,7 +12,7 @@ **How to read the blocks:** ripwire's real XML output is minified — often ONE long line. For scanability, long minified lines are displayed re-wrapped with a line break at every tag seam (`><`). Header COMMENT lines (the legends) always appear in full — they are exempt from the per-line cut; any OTHER display line over 300 bytes is cut with a `… [line truncated: N more bytes]` marker, which can hit a long root element or row. `--plan-lanes` emits JSON and is re-wrapped at object seams the same way. Long outputs are cut to their first ~30 display lines with a `… [N more display lines; full output is M bytes]` marker giving the true size. Exit codes are recorded when non-zero; wall time when >1s. -**Not run (and why):** `ripwire ` (network clone), `--listen` / `--mcp-token` / `--allow-remote-edits` / `--mcp-tools` (the server posture — `--mcp` itself IS captured in its own section as a one-shot stdio JSON-RPC exchange with the default full tool profile, and `wrap claude` shows the wiring), `--arch --baseline[-update]` (state writer against the read-only repo — `--note-add` / `--quality-baseline` / `--quality-ack` / the three edit verbs / `--edit-plan` ARE shown, inside the throwaway sandbox clone; `--index-out` / `--pin-census` write to scratch), `--eval-mined` (needs a `minedpair.jsonl` artifact from `bench/mine_traces.py`; none present in the tree), `--refetch` (git-url only), `--max-memory` (the guard is silent on a corpus this size — its disclosed partial-answer shape needs a tree past the limit, gated by the memory-guard lanes instead), `--lsp` (the editor transport — its one-shot dialogues are gated by `test/lspcheck.sh`, not by showcase blocks), `--sections` (the / stub-and-restore shapes are gated by `test/forsectioncollapsecheck.sh`; the flag restores what --for collapses by default), `--force` (wrap-only modifier), `--scan-skills` bare form (would sweep `~/.claude/skills`; the explicit-DIR form is shown instead), `--help` (204 lines — read it from the binary). +**Not run (and why):** `ripwire ` (network clone), `--listen` / `--mcp-token` / `--allow-remote-edits` / `--mcp-tools` (the server posture — `--mcp` itself IS captured in its own section as a one-shot stdio JSON-RPC exchange with the default full tool profile, and `wrap claude` shows the wiring), `--arch --baseline[-update]` (state writer against the read-only repo — `--note-add` / `--quality-baseline` / `--quality-ack` / the three edit verbs / `--edit-plan` ARE shown, inside the throwaway sandbox clone; `--index-out` / `--pin-census` write to scratch), `--eval-mined` (needs a `minedpair.jsonl` artifact from `bench/mine_traces.py`; none present in the tree), `--refetch` (git-url only), `--max-memory` (the guard is silent on a corpus this size — its disclosed partial-answer shape needs a tree past the limit, gated by the memory-guard lanes instead), `--lsp` (the editor transport — its one-shot dialogues are gated by `test/lspcheck.sh`, not by showcase blocks), `--sections` (the / stub-and-restore shapes are gated by `test/forsectioncollapsecheck.sh`; the flag restores what --for collapses by default), `--force` (wrap-only modifier), `--scan-skills` bare form (would sweep `~/.claude/skills`; the explicit-DIR form is shown instead), `--help` (205 lines — read it from the binary). --- @@ -23,13 +23,11 @@ *The default ranked symbol map — start here when landing cold in a repo.* -**wall time: 5.71s** - ````` - + - - + + @@ -49,14 +47,14 @@ - + -… [792 more display lines; full output is 21802 bytes on 1 raw line(s)] +… [797 more display lines; full output is 22194 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --top-k=5` @@ -66,7 +64,7 @@ ````` - + @@ -152,7 +150,7 @@ ripwire: --top-k=0 means "no ranked map, payload only" — pass a payload verb ( ````` - + @@ -169,52 +167,52 @@ ripwire: --top-k=0 means "no ranked map, payload only" — pass a payload verb ( ````` - + ````` stderr: ````` -ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 +ripwire: --token-budget exceeded: withheld_est_tokens=8943 > 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: 15.19s** +**wall time: 16.60s** ````` - - + 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 - + the cached index for `root`, rebuilt only when stale (otherwise returned as-is, no parse, no gra…inline const McpIndex& getIndex( const std::string& root ) -inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) -inline std::uint64_t contentHash64( std::string_view s ) noexcept -struct CacheEntry +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline std::uint64_t contentHash64( std::string_view s ) noexcept +struct CacheEntry inline void prewarmTagsQueries( const std::vector<std::string>& files, const HashMap<std::string, FileFacts>& cache, long long cacheWriteNs, IngestFileScan& … [line truncated: 12 more bytes on this line] -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::uint64_t blobChecksum( std::string_view s ) noexcept +inline ContentIdIndex contentIdsBySym( const IngestResult& ing, const Graph& g, std::string_view root ) inline bool ingestCommitTree( const std::string& root, const std::string& sha, const std::vector<std::string>& excludes, std::size_t maxFileBytes, IngestResult&… -inline std::vector<CacheWriteRow> buildCacheWritePlan( const std::vector<std::uint32_t>& orderIn, const std::vector<std::uint64_t>& pathHashes, const s … [line truncated: 16 more bytes on this line] -inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, … [line truncated: 54 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::vector<CacheWriteRow> buildCacheWritePlan( const std::vector<std::uint32_t>& orderIn, const std::vector<std::uint64_t>& pathHashes, const s … [line truncated: 16 more bytes on this line] +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, … [line truncated: 54 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 ) struct IngestFileScan -inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t>& f … [line truncated: 17 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 ) +inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t>& f … [line truncated: 17 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 ) inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, cons … [line truncated: 91 more bytes on this line] inline std::string spanTierMemoPath( const std::string& diskPath ) inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) -inline NodeId resolveHandleAll( const McpIndex& ix, std::uint64_t idHash, std::vector< NodeId >& matches ) +inline NodeId resolveHandleAll( const McpIndex& ix, std::uint64_t idHash, std::vector< NodeId >& matches ) … [46 more display lines; full output is 9078 bytes on 1 raw line(s)] ````` @@ -222,17 +220,17 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 *Name-shaped query: the router picks name-exact BM25 (header says which/why).* -**wall time: 1.82s** +**wall time: 1.48s** ````` - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -262,57 +260,57 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 *Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* -**wall time: 1.36s** +**wall time: 1.60s** ````` - + - - - + + + 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 ) +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", bo … [line truncated: 11 more bytes on this line] -inline ChurnRanking churnDecayRanking( const MainDispatch& d, const rw::SinceScope& sinceScope, bool isScoped, const char* verbLabel ) -inline std::string rankByText( const std::string& root, std::string_view mode, int topK, bool stable = false ) +inline 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::string didYouMean( const IngestResult& ing, std::string_view name ) +inline std::string didYouMean( const IngestResult& ing, std::string_view name ) inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = nullptr ) PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double& … [line truncated: 37 more bytes on this line] -inline const McpIndex& getIndex( const std::string& root ) +inline const McpIndex& getIndex( const std::string& root ) inline std::vector<float> churnDecayTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, bool* outHasChurnEvidence = nullptr )< … [line truncated: 3 more bytes on this line] -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 ) -inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 -### Fixed — `rank_by` over MCP no longer answers a different rankin … [line truncated: 57 more bytes on this line] -## Coverage inventory -#### The convergence disclosure contract - -… [101 more display lines; full output is 14971 bytes on 72 raw line(s)] +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 ) +inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 +### Fixed — `rank_by` over MCP no longer answers a different rankin … [line truncated: 57 more bytes on this line] +### 2. Gates for A +#### The convergence disclosure contract +… [102 more display lines; full output is 15166 bytes on 72 raw line(s)] ````` ## `./build/ripwire . --for="rankGraphTeleport" --signatures-only` *T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribute) — contrast with the terminal default above.* -**wall time: 1.86s** +**wall time: 1.48s** ````` - + - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -323,10 +321,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > 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.84s** +**wall time: 1.61s** ````` - + @@ -337,13 +335,13 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() struct FileHealth -enum class AstWalk : std::uint8_t -inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar… +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar… +enum class AstWalk : std::uint8_t struct SliceScan inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept constexpr std::array<LangEntry, 51> kLangTable = constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u -inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept 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 ) std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] @@ -355,7 +353,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) struct LintRule enum class DisclosureWhy : std::uint8_t -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) … [55 more display lines; full output is 9170 bytes on 1 raw line(s)] ````` @@ -363,13 +361,13 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > 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.33s** +**wall time: 1.50s** ````` - - + 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 ) @@ -378,7 +376,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > 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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) @@ -389,24 +387,24 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 struct Bm25Params 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 reads the W … [line truncated: 57 more bytes on this line] -struct CandidateProvenance -std::optional<int> runEvalViews( const MainDispatch& d ) +struct CandidateProvenance +std::optional<int> runEvalViews( const MainDispatch& d ) inline constexpr int kLexWeightDoc = 2 inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) struct RecallSectionPick inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) -### Graph-Ranked Retrieval: It finds the right files more often than the alternatives -… [56 more display lines; full output is 9458 bytes on 1 raw line(s)] +### Graph-Ranked Retrieval: It finds the right files more often than the alternatives +… [56 more display lines; full output is 9508 bytes on 1 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: 1.83s** +**wall time: 1.59s** ````` - @@ -417,7 +415,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > 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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) @@ -429,14 +427,14 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 struct Bm25Params 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 reads the W … [line truncated: 57 more bytes on this line] -struct CandidateProvenance -std::optional<int> runEvalViews( const MainDispatch& d ) +struct CandidateProvenance +std::optional<int> runEvalViews( const MainDispatch& d ) inline constexpr int kLexWeightDoc = 2 inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) struct RecallSectionPick inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) -inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept -… [58 more display lines; full output is 9509 bytes on 1 raw line(s)] +inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept +… [59 more display lines; full output is 9624 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lego=Vehicle` @@ -446,7 +444,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 ````` - + @@ -459,12 +457,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 *The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* -**wall time: 1.11s** +**wall time: 1.22s** ````` - + f, A&&... a ) @@ -511,12 +509,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8783 > budget=100 *Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* -**wall time: 1.12s** +**wall time: 1.10s** ````` -ripwire recall — "quality delta gating exit codes" — 113 relevant of 209 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5403 +ripwire recall — "quality delta gating exit codes" — 113 relevant of 209 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5489 -━━ README.md (relevance 8.289) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 211997 B; lines="2688-2700,2701-2708"; dropped_by_budget=23] +━━ README.md (relevance 7.944) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 212219 B; lines="2688-2700,2701-2708"; dropped_by_budget=23] #### 6.2 Exit codes This is the part to wire into a script. @@ -524,7 +522,7 @@ 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. | +| 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. | @@ -540,26 +538,47 @@ refuses by default. -━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.295) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.296) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] ### Root spelling (maintainer-owned; reproduce it once so you recognise it) ```bash -… [159 more lines, 13788 bytes total] +… [159 more lines, 14007 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 1437506-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 1440129-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 11.127) ━━ [sections: 1 of 155 selected (185 in doc), section-granular; whole doc 534551 B; lines="504-529"; dropped_by_budget=154] [truncated: 1854 of 4257 bytes] +#### Pattern: a directory of dumped tool output as a knowledge base -━━ commands.md (relevance 10.149) ━━ [sections: 1 of 154 selected (183 in doc), section-granular; whole doc 527781 B; lines="3662-3666"; dropped_by_budget=153] [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] +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)) ````` @@ -576,7 +595,7 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas ````` - + @@ -605,14 +624,14 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas -… [366 more display lines; full output is 11141 bytes on 1 raw line(s)] +… [366 more display lines; full output is 11121 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --html=/aux/map2.html` *Self-contained HTML force-directed call graph.* -**wall time: 1.38s** +**wall time: 1.66s** ````` (empty) @@ -621,7 +640,7 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas Artifact written: ````` -158727 /aux/map2.html +158472 /aux/map2.html ````` ## `./build/ripwire . --order=stable --top-k=5` @@ -650,7 +669,7 @@ Artifact written: - + ````` @@ -666,7 +685,7 @@ Artifact written: - + @@ -704,8 +723,8 @@ Artifact written: - - + + @@ -731,7 +750,7 @@ Artifact written: -… [3003 more display lines; full output is 79462 bytes on 1 raw line(s)] +… [3043 more display lines; full output is 80474 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --around=rankGraphTeleport --around-fanout=4` @@ -742,7 +761,7 @@ Artifact written: - + @@ -778,10 +797,10 @@ Artifact written: ````` - - + + - + @@ -796,10 +815,10 @@ Artifact written: ````` - - + + - + @@ -814,7 +833,8 @@ Artifact written: **exit code: 1** ````` -(empty) + + ````` stderr: @@ -829,7 +849,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + @@ -845,20 +865,20 @@ 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.01s** +**wall time: 1.08s** ````` - - + + - + - + ````` @@ -868,35 +888,35 @@ ripwire: --callers symbol not found: DoesNotExist ````` - - - - - + + + + + - + - + - + - + - + - - - - - - - - - - + + + + + + + + + + … [28 more display lines; full output is 3648 bytes on 1 raw line(s)] ````` @@ -906,36 +926,36 @@ ripwire: --callers symbol not found: DoesNotExist ````` - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - -… [73 more display lines; full output is 5567 bytes on 1 raw line(s)] + + +… [73 more display lines; full output is 5571 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --external-surface --include-builtins` @@ -944,36 +964,36 @@ ripwire: --callers symbol not found: DoesNotExist ````` - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - -… [73 more display lines; full output is 5427 bytes on 1 raw line(s)] + + + +… [73 more display lines; full output is 5431 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --path=main,rankGraphTeleport` @@ -982,10 +1002,10 @@ ripwire: --callers symbol not found: DoesNotExist ````` - - - - + + + + @@ -997,11 +1017,11 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + - + @@ -1014,36 +1034,36 @@ ripwire: --callers symbol not found: DoesNotExist ````` - - + + - - - + + + - + - + - + - + - - - - - - - -… [46 more display lines; full output is 5179 bytes on 1 raw line(s)] + + + + + + + +… [47 more display lines; full output is 5229 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --mentions=rankGraphTeleport` @@ -1053,9 +1073,9 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + - + @@ -1069,7 +1089,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + @@ -1096,7 +1116,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: dac3b8205+dirty +at: 52c4a428f+dirty (0 changed files — working tree is clean, nothing to analyze) ````` @@ -1108,7 +1128,7 @@ at: dac3b8205+dirty ````` - ````` @@ -1116,16 +1136,16 @@ at: dac3b8205+dirty *Literal trigram-indexed search. Each hit carries its MATCHED line as the element's own CDATA (the wrapper is gone), plus shown/capped/hits_capped and a pasteable next= on the root.* -**wall time: 1.22s** +**wall time: 1.53s** ````` @@ -1299,11 +1319,11 @@ at: dac3b8205+dirty *The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* -**wall time: 3.88s** +**wall time: 4.85s** ````` -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 @@ -1339,20 +1359,20 @@ at: dac3b8205+dirty *Raw BM25 ranking (debug lens; --for is the real verb).* -**wall time: 1.17s** +**wall time: 1.05s** ````` - + - + - + @@ -1366,12 +1386,12 @@ at: dac3b8205+dirty - + - + … [28 more display lines; full output is 3515 bytes on 1 raw line(s)] ````` @@ -1384,13 +1404,13 @@ at: dac3b8205+dirty *Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* -**wall time: 1.85s** +**wall time: 1.56s** ````` - + - - + + NodeId order. Renumbering an otherwise-identical graph (the exact "same probe file, different so…inline constexpr float kScoreTieAbsEps = 1e-6f @@ -1399,25 +1419,25 @@ at: dac3b8205+dirty 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] What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure -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 … [line truncated: 7 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 … [line truncated: 7 more bytes on this line] inline std::uint32_t sliceRdIterationCeiling() noexcept struct PageRankRun -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 ) 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… COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "th … [line truncated: 61 more bytes on this line] inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept inline std::string powerShellSingleQuote( std::string_view s ) noexcept inline std::string path_prepend_hint( std::string_view dir ) -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` -### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] -### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] +### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] ## Background — read these, in this order ## Design space and constraints -… [107 more display lines; full output is 14202 bytes on 72 raw line(s)] +… [106 more display lines; full output is 13943 bytes on 72 raw line(s)] ````` ## `./build/ripwire . --pack-signatures --top-k=10` @@ -1429,7 +1449,7 @@ at: dac3b8205+dirty - + @@ -1596,7 +1616,7 @@ ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — - + - + @@ -1742,13 +1762,13 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - + … [21 more display lines; full output is 5523 bytes on 1 raw line(s)] ````` @@ -1758,27 +1778,27 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan ````` - - - - + + + + - + - + + - - - + + - + @@ -1786,59 +1806,59 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - -… [878 more display lines; full output is 22900 bytes on 1 raw line(s)] + +… [885 more display lines; full output is 23053 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --hotspots` *Complexity x recent git churn (maintenance pain).* -**wall time: 1.92s** +**wall time: 1.76s** ````` - - - - - - - + + + + + + + - - + + + - - - + + - + + + - - - -… [14 more display lines; full output is 5752 bytes on 1 raw line(s)] + +… [14 more display lines; full output is 5753 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --clones` *Token-normalized duplicate bodies.* -**wall time: 2.85s** +**wall time: 2.87s** ````` - + @@ -1848,7 +1868,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + @@ -1863,7 +1883,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + @@ -1874,41 +1894,41 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Files that change together in git (hidden coupling).* -**wall time: 1.75s** +**wall time: 1.65s** ````` - - + - - - - + - - + + + + + + + + - - - - + + + - - + ````` @@ -1916,40 +1936,40 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Hotspots scoped to RECENT churn (the regression lens).* -**wall time: 1.16s** +**wall time: 1.11s** ````` - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - -… [13 more display lines; full output is 5123 bytes on 1 raw line(s)] + + + + + + + + + + + + + + +… [13 more display lines; full output is 5124 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --arch=test/archfix/rules.txt` @@ -1959,7 +1979,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan ````` - @@ -1987,19 +2007,19 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan -… [528 more display lines; full output is 77108 bytes on 1 raw line(s)] +… [529 more display lines; full output is 77247 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint` *Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* -**wall time: 7.97s** +**wall time: 9.77s** ````` - - + + @@ -2007,34 +2027,34 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + - - + + - - + + - + - + - -… [704 more display lines; full output is 69396 bytes on 1 raw line(s)] + +… [700 more display lines; full output is 69581 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: 2.02s** +**wall time: 2.63s** ````` @@ -2116,36 +2136,36 @@ The joined finding — past the display cut above, extracted so the join is visi ````` - + - + - + + - + + - - - + + - - - - - - - + + + + + + -… [186 more display lines; full output is 16547 bytes on 1 raw line(s)] +… [187 more display lines; full output is 16546 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --zoom` @@ -2154,36 +2174,36 @@ The joined finding — past the display cut above, extracted so the join is visi ````` - - + + - - + + - - + + - + - + - + - - + + - - + + - - -… [171 more display lines; full output is 8330 bytes on 1 raw line(s)] + + +… [139 more display lines; full output is 7160 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --zoom --zoom-levels=3` @@ -2192,36 +2212,36 @@ The joined finding — past the display cut above, extracted so the join is visi ````` - - - + + + - + - + - + - + - + - + - + - + - + - - - + + + -… [705 more display lines; full output is 27730 bytes on 1 raw line(s)] +… [269 more display lines; full output is 11755 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --report` @@ -2233,33 +2253,33 @@ The joined finding — past the display cut above, extracted so the join is visi # ripwire architecture report -2376 files · 23174 symbols · 32805 edges · 2235 modules (10668 call-graph isolated) +2381 files · 23348 symbols · 33292 edges · 2252 modules (10730 call-graph isolated) Root: `.` -Call-graph isolate provenance: 2842 declaration, 1520 header, 1842 source, 4464 document; 0 connected Louvain singletons +Call-graph isolate provenance: 2850 declaration, 1549 header, 1842 source, 4489 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 2235) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1270 symbols -- **src::append@elixir_resolve.h:111:4875 [resolve,collect,append]** — 788 symbols -- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 549 symbols -- **src::assign@infra/svector.h:342:19905 [compute,add,collect]** — 461 symbols +## Modules (call-graph clusters; showing 12 of 2252) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1243 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,add,collect]** — 1086 symbols +- **src::formatTo@infra/emit.h:141:8856 [read,close,run]** — 661 symbols - **src::kindIs@infra/nodekind.h:46:3271 [emit,collect,push]** — 257 symbols -- **docs::`--top-k=N`@COMMANDS.md:59:8696 [pack,allow,insert]** — 173 symbols -- **src::relForHash@model.h:1396:112049 [compute,clone,read]** — 52 symbols +- **docs::`--top-k=N`@COMMANDS.md:59:8772 [pack,allow,insert]** — 175 symbols +- **src::assign@infra/svector.h:342:19905 [scan,resolve,sort]** — 67 symbols +- **src::relForHash@model.h:1396:112049 [compute,clone,resolve]** — 58 symbols - **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [erase,find,count]** — 52 symbols -- **src/infra::active@profilePmc.h:845:34910 [print,ensure,report]** — 49 symbols -- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,call,compute]** — 46 symbols -- **src/infra::buf@svector.h:125:8942 [insert,pack,render]** — 38 symbols -- **test::no@decltodefcheck.sh:127:11708 [run]** — 30 symbols +- **src/infra::active@profilePmc.h:845:34910 [print,ensure,report]** — 43 symbols +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,call,compute]** — 41 symbols +- **src::buf@infra/svector.h:125:8942 [insert,pack,render]** — 39 symbols +- **src::ASSUME@infra/Diagnostics.h:386:25510 [write,gather,verify]** — 30 symbols -## God files (most depended-on; showing 10 of 422) -- `test/lib/clean-env.sh` — 173 dependents +## God files (most depended-on; showing 10 of 423) +- `test/lib/clean-env.sh` — 176 dependents - `src/infra/emit.h` — 86 dependents -- `src/model.h` — 81 dependents +- `src/model.h` — 82 dependents - `src/infra/Diagnostics.h` — 57 dependents -- `src/serialize.h` — 37 dependents -… [32 more lines, 3556 bytes total] +- `src/serialize.h` — 38 dependents +… [32 more lines, 3555 bytes total] ````` ## `./build/ripwire . --seams` @@ -2268,13 +2288,13 @@ Call-graph isolate provenance: 2842 declaration, 1520 header, 1842 source, 4464 ````` - + - - + + - + @@ -2297,7 +2317,7 @@ Call-graph isolate provenance: 2842 declaration, 1520 header, 1842 source, 4464 -… [104 more display lines; full output is 13569 bytes on 1 raw line(s)] +… [104 more display lines; full output is 13578 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --mermaid` @@ -2308,20 +2328,20 @@ Call-graph isolate provenance: 2842 declaration, 1520 header, 1842 source, 4464 %% 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
6753"] + n101["src
6867"] n102["src/infra
1275"] end subgraph sg1 ["test"] - n103["test
4771"] + n103["test
4792"] n294["test/fixtures/recallpassage
314"] n280["test/expandmodefix
151"] n282["test/expandsibsfix
149"] - n423["test/rubyattrsfix
85"] - n360["test/massfix
77"] + n424["test/rubyattrsfix
85"] + n361["test/massfix
77"] n326["test/javamethodreffix
62"] end - n73["docs
811"] - n2[".github
594"] + n73["docs
825"] + n2[".github
596"] subgraph sg4 ["bench"] n4["bench
531"] n5["bench/agentloop
331"] @@ -2342,13 +2362,13 @@ flowchart LR *Bus-factor: recency-weighted author ownership per file.* -**wall time: 1.33s** +**wall time: 1.79s** ````` - - - + + + @@ -2357,7 +2377,7 @@ flowchart LR - + @@ -2375,7 +2395,7 @@ flowchart LR -… [879 more display lines; full output is 97525 bytes on 1 raw line(s)] +… [879 more display lines; full output is 97394 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --dead-code=src` @@ -2384,7 +2404,7 @@ flowchart LR ````` - + ````` @@ -2406,8 +2426,8 @@ flowchart LR ````` - + ````` @@ -2415,11 +2435,11 @@ flowchart LR *Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit.* -**wall time: 52.96s** +**wall time: 62.92s** ````` - @@ -2442,12 +2462,12 @@ flowchart LR - + - + … [95 more display lines; full output is 12147 bytes on 1 raw line(s)] ````` @@ -2461,16 +2481,18 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Fast per-symbol post-edit contract check vs git HEAD — recorded against a DIRTY tree, so the verdict describes the working copy, not HEAD alone, and the recorded exit code is the working copy's.* +**wall time: 1.11s** + ````` - - + - + ````` @@ -2478,11 +2500,9 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *No-LLM review-evidence bundle for the working-tree diff — recorded against a DIRTY tree, so it is populated rather than empty.* -**wall time: 1.01s** - ````` - ````` @@ -2491,42 +2511,11 @@ 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.97s** - ````` - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ````` @@ -2534,18 +2523,18 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 32.30s** +**wall time: 38.27s** ````` - - + + - + - + @@ -2559,25 +2548,30 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Which refs of the one branch `main` (no ref family left to select on this checkout) still hold divergent authored work vs HEAD, with verdicts.* -**wall time: 1.24s** +**wall time: 1.35s** ````` - + - + ````` -## `./build/ripwire . --stray-content=sync-rebase` +## `./build/ripwire . --stray-content=sync-rebase-backup` -*A second selection, the one branch `sync-rebase` (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=.* +*A second selection, the one branch `sync-rebase-backup` (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.17s** ````` - - + + + + + ````` @@ -2585,11 +2579,11 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Select the genuinely-unmerged refs of the one branch `main` (no ref family left to select on this checkout) 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: 24.58s** +**wall time: 27.95s** ````` - + @@ -2625,7 +2619,7 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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.07s** +**exit code: 1** — **wall time: 1.04s** ````` (empty) @@ -2642,11 +2636,11 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Cross-branch ABI-break gate over the one branch `main` (no ref family left to select on this checkout): 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.* -**wall time: 1.29s** +**wall time: 1.37s** ````` - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [34 more display lines; full output is 15392 bytes on 1 raw line(s)] +**wall time: 2.37s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [34 more display lines; full output is 16096 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: 16.93s** - -````` - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + @@ -2774,11 +2768,11 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Blast radius of turning ONE gate on: live code, symbols, transitive reach, covering tests.* -**wall time: 1.48s** +**wall time: 1.22s** ````` - + @@ -2809,7 +2803,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Unknown-gate refusal (exit 1) with a did-you-mean from a real edit distance (one character off RIPWIRE_ASAN).* -**exit code: 1** — **wall time: 1.07s** +**exit code: 1** — **wall time: 1.19s** ````` (empty) @@ -2826,47 +2820,47 @@ 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.06s** +**wall time: 2.08s** ````` -{"v":1,"verb":"plan-lanes","at":"dac3b8205+dirty","root":".","task":"add a --since filter to the doc-drift verb and cover it with tests","source":"partition","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2376,"symbols":23174,"edge … [line truncated: 344 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"52c4a428f+dirty","root":".","task":"add a --since filter to the doc-drift verb and cover it with tests","source":"partition","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2381,"symbols":23348,"edge … [line truncated: 344 more bytes on this line] "symbols":[{"p":"./src/docdrift.h","n":"DriftResult","scope":"DriftResult","l":286,"id":"./src/docdrift.h::DriftResult::DriftResult"}, {"p":"./src/docdrift.h","n":"computeDocDrift","scope":"docdrift","l":2441,"id":"./src/docdrift.h::docdrift::computeDocDrift"}, {"p":"./src/docdrift.h","n":"kDocDriftLegend","scope":"docdrift","l":2739,"id":"./src/docdrift.h::docdrift::kDocDriftLegend"}, {"p":"./src/docdrift.h","n":"writeDocDriftPage","scope":"docdrift","l":2909,"id":"./src/docdrift.h::docdrift::writeDocDriftPage"}, -{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","l":632,"id":"./src/mcpverbs.h::rw::docDriftText"}, -{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","l":1634,"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/crossref.h","n":"writeWhereisPage","scope":"crossref","key":"3a0425468e6a18cf","id":"./src/crossref.h::crossref::writeWhereisPage","id_addressable":true,"id_collides_with":0,"l":2218,"ord":0,"overloads":1,"amb":11,"cx":22,"ccx":23,"churn":51,"tested":0}, +{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","l":639,"id":"./src/mcpverbs.h::rw::docDriftText"}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","l":1641,"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/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":3,"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":2,"cx":8,"ccx":8,"churn":31,"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":2,"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":1302,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":46,"tested":0}, -{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":2855,"ord":0,"overloads":1,"amb":33,"cx":8,"ccx":11,"churn":411,"tested":0}, +{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":2863,"ord":0,"overloads":1,"amb":33,"cx":8,"ccx":11,"churn":413,"tested":0}, {"p":"./src/gitmine.h","n":"resolveSinceScope","scope":"rw","key":"9caaa3dbeaa688d0","id":"./src/gitmine.h::rw::resolveSinceScope","id_addressable":true,"id_collides_with":0,"l":317,"ord":0,"overloads":1,"amb":3,"cx":7,"ccx":7,"churn":57,"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":350,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":18,"tested":0}, -{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":685,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":411,"tested":0}, +{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":685,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":413,"tested":0}, {"p":"./src/taskroute.h","n":"testCoverageTaskChoice","scope":"rw::taskroute","key":"bf72991aa28355c7","id":"./src/taskroute.h::rw::taskroute::testCoverageTaskChoice","id_addressable":true,"id_collides_with":0,"l":1365,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":34,"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":5145,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":293,"tested":0}], -"files":[{"p":"./src/crossref.h","symbols":1,"churn":51,"ccx":23,"hotspot_rank":26}, -{"p":"./src/darkflags.h","symbols":1,"churn":31,"ccx":8,"hotspot_rank":44}, -{"p":"./src/docdrift.h","symbols":2,"churn":36,"ccx":11,"hotspot_rank":30}, +{"p":"./src/mcpverbs.h","n":"unknownSubVerbRefusal","scope":"rw","key":"e336d39b0a6addea","id":"./src/mcpverbs.h::rw::unknownSubVerbRefusal","id_addressable":true,"id_collides_with":0,"l":5162,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":299,"tested":0}], +"files":[{"p":"./src/darkflags.h","symbols":1,"churn":31,"ccx":8,"hotspot_rank":44}, +{"p":"./src/docdrift.h","symbols":3,"churn":36,"ccx":12,"hotspot_rank":31}, {"p":"./src/gitmine.h","symbols":1,"churn":57,"ccx":7,"hotspot_rank":18}, -{"p":"./src/main.cpp","symbols":2,"churn":411,"ccx":12,"hotspot_rank":4}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":293,"ccx":2,"hotspot_rank":5}, -{"p":"./src/pageview.h","symbols":1,"churn":18,"ccx":2,"hotspot_rank":130}, +{"p":"./src/main.cpp","symbols":2,"churn":413,"ccx":12,"hotspot_rank":4}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":299,"ccx":2,"hotspot_rank":5}, +{"p":"./src/pageview.h","symbols":1,"churn":18,"ccx":2,"hotspot_rank":131}, {"p":"./src/taskroute.h","symbols":1,"churn":34,"ccx":2,"hotspot_rank":40}, {"p":"./src/testmap.h","symbols":1,"churn":46,"ccx":4,"hotspot_rank":35}, -{"p":"./src/verbs_change.h","symbols":1,"churn":68,"ccx":8,"hotspot_rank":22}]},"blast_radius":{"reaches":52,"files_total":18,"capped":false,"files":["./src/crossref.h","./src/darkflags.h","./src/docdrift.h","./src/editplan.h","./src/flipimpact.h","./src/gitmine.h","./src/main.cpp","./src/mcp.h","./ … [line truncated: 222 more bytes on this line] -"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":52,"module_span":1,"notes":[], -"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":12,"files":10,"module_span":1,"max_ccx":23,"sum_ccx":79,"ambiguous_calls":58,"blast_reaches":52,"contract_touches":0,"conflicts":0,"untested":52," … [line truncated: 140 more bytes on this line] -… [80 more display lines; full output is 21357 bytes on 1 raw line(s)] +{"p":"./src/verbs_change.h","symbols":1,"churn":69,"ccx":8,"hotspot_rank":22}]},"blast_radius":{"reaches":51,"files_total":17,"capped":false,"files":["./src/darkflags.h","./src/docdrift.h","./src/editplan.h","./src/flipimpact.h","./src/gitmine.h","./src/main.cpp","./src/mcp.h","./src/mcpedit.h","./s … [line truncated: 203 more bytes on this line] +"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":51,"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":12,"files":9,"module_span":1,"max_ccx":11,"sum_ccx":57,"ambiguous_calls":49,"blast_reaches":51,"contract_touches":0,"conflicts":0,"untested":51,"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/gitmine.h","n":"gitWindowBoundarySha","scope":"rw","key":"18627516699d7062","id":"./src/gitmine.h::rw::gitWindowBoundarySha","id_addressable":true,"id_collides_with":0,"l":1605 … [line truncated: 69 more bytes on this line] +… [73 more display lines; full output is 20434 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.94s** +**wall time: 2.72s** Input file: @@ -2877,34 +2871,34 @@ write regression tests for the new filter ````` ````` -{"v":1,"verb":"plan-lanes","at":"dac3b8205+dirty","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2376,"symbols":23174,"edges":32805,"ambiguous":9828,"unresolved":13184},"carve":null,"core":{" … [line truncated: 10 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"52c4a428f+dirty","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2381,"symbols":23348,"edges":33292,"ambiguous":9974,"unresolved":13331},"carve":null,"core":{" … [line truncated: 10 more bytes on this line] "symbols":[]},"lanes":[{"id":"lane-0","task":"add a --since filter to the doc-drift verb","claims":{"symbols":[{"p":"./src/docdrift.h","n":"recordUnchecked","scope":"docdrift","key":"12641dab14abc8fd","id":"./src/docdrift.h::docdrift::recordUnchecked","id_addressable":true,"id_collides_with":0,"l":2 … [line truncated: 72 more bytes on this line] {"p":"./src/docdrift.h","n":"sortWeakGroupsByPath","scope":"docdrift","key":"1944ba506280ff80","id":"./src/docdrift.h::docdrift::sortWeakGroupsByPath","id_addressable":true,"id_collides_with":0,"l":2401,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":36,"tested":0}, -{"p":"./src/mcpverbs.h","n":"docDriftText","scope":"rw","key":"1fa68e8d93c05a59","id":"./src/mcpverbs.h::rw::docDriftText","id_addressable":true,"id_collides_with":0,"l":632,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":293,"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":3164,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":420,"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":639,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":299,"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":3176,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":428,"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":12,"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":12,"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":1634,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":68,"tested":0}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","key":"904831e5c0f66b39","id":null,"id_addressable":false,"id_collides_with":0,"l":1641,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":69,"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/mcprefusal.h","n":"kMcpRequiredFields","scope":"rw::mcprefuse","key":"c5f1ad5fc3a12368","id":"./src/mcprefusal.h::rw::mcprefuse::kMcpRequiredFields","id_addressable":true,"id_collides_with":0,"l":67,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":38,"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":420,"ccx":0,"hotspot_rank":6}, -{"p":"./src/docdrift.h","symbols":6,"churn":36,"ccx":54,"hotspot_rank":30}, -{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":85}, -{"p":"./src/mcprefusal.h","symbols":1,"churn":38,"ccx":0,"hotspot_rank":55}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":293,"ccx":0,"hotspot_rank":5}, -{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":50}, -{"p":"./src/verbs_change.h","symbols":1,"churn":68,"ccx":4,"hotspot_rank":22}], -"symbols_total":3091,"symbols_capped":true},"blast_radius":{"reaches":12,"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":12,"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":7,"module_span":6,"max_ccx":38,"sum_ccx":62,"ambiguous_calls":26,"blast_reaches":12,"contract_touches":1,"conflicts":3,"untested":12,"t … [line truncated: 139 more bytes on this line] +"files":[{"p":"./src/cli.h","symbols":1,"churn":428,"ccx":0,"hotspot_rank":6}, +{"p":"./src/docdrift.h","symbols":6,"churn":36,"ccx":54,"hotspot_rank":31}, +{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":86}, +{"p":"./src/mcprefusal.h","symbols":1,"churn":38,"ccx":0,"hotspot_rank":56}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":299,"ccx":0,"hotspot_rank":5}, +{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":51}, +{"p":"./src/verbs_change.h","symbols":1,"churn":69,"ccx":4,"hotspot_rank":22}], +"symbols_total":3116,"symbols_capped":true},"blast_radius":{"reaches":12,"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":12,"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":7,"module_span":7,"max_ccx":38,"sum_ccx":62,"ambiguous_calls":26,"blast_reaches":12,"contract_touches":1,"conflicts":3,"untested":12,"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":106,"l":261,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":7," … [line truncated: 11 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":2788,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":420,"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":3164,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":420,"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":2867,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":420,"tested":0}, -{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":106,"l":858,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":16,"tested":0}, +{"p":"./src/cli.h","n":"HelpLine","scope":"rw","key":"1489844dbc310b82","id":"./src/cli.h::rw::HelpLine","id_addressable":true,"id_collides_with":0,"l":2800,"ord":0,"overloads":1,"amb":0,"cx":0,"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":3176,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":428,"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":2879,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":428,"tested":0}, +{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":106,"l":865,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":17,"tested":0}, {"p":"./src/slice.h","n":"sliceRdIterationCeiling","scope":"slicev","key":"449132b302bb4324","id":"./src/slice.h::slicev::sliceRdIterationCeiling","id_addressable":true,"id_collides_with":0,"l":1372,"ord":0,"overloads":1,"amb":0,"cx":9,"ccx":12,"churn":51,"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":38,"tested":0}, … [64 more display lines; full output is 19299 bytes on 1 raw line(s)] @@ -2932,7 +2926,7 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out ````` - + @@ -2986,80 +2980,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: 1.68s** +**wall time: 1.33s** ````` - + -
- - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + - + - - -… [239 more display lines; full output is 24814 bytes on 1 raw line(s)] + + +… [239 more display lines; full output is 24840 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.88s** +**wall time: 1.86s** ````` - + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + - + - - -… [265 more display lines; full output is 26013 bytes on 1 raw line(s)] + + +… [265 more display lines; full output is 26039 bytes on 1 raw line(s)] ````` Tail of the same output — the `` section: @@ -3096,47 +3090,47 @@ 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.34s** +**wall time: 2.95s** ````` - - + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + - + - -… [221 more display lines; full output is 25009 bytes on 1 raw line(s)] + +… [221 more display lines; full output is 25035 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --from-trace=-` *Map a pasted stack trace onto indexed symbols. CHANGED: in_corpus= now reports the real count (was 0).* -**wall time: 1.10s** +**wall time: 1.77s** Input file: @@ -3147,7 +3141,7 @@ AddressSanitizer:DEADLYSIGNAL #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:4647 #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4688 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 - #3 0x102e01a44 in main src/main.cpp:3975 + #3 0x102e01a44 in main src/main.cpp:3983 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) ==41337==ABORTING ````` @@ -3160,15 +3154,15 @@ 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 ) +int runDefaultMap( const MainDispatch& d ) +int main( int argc, char** argv ) @@ -3213,10 +3207,10 @@ AddressSanitizer:DEADLYSIGNAL *ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* -**wall time: 1.13s** +**wall time: 1.34s** ````` - + - + 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 ) @@ -3236,13 +3230,13 @@ AddressSanitizer:DEADLYSIGNAL 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 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 - + knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedBy makes in full above, which applies here verbatim: firstFlagOutside() walks the rows parseArgs itself minline constexpr std::string_view kInRideAlong[] = - - + + @@ -3253,16 +3247,16 @@ AddressSanitizer:DEADLYSIGNAL *Fan-out form: one shared core + 3 per-agent slices carved along call-graph communities.* -**wall time: 1.75s** +**wall time: 1.40s** ````` - 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. --> + 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 ) @@ -3272,10 +3266,10 @@ AddressSanitizer:DEADLYSIGNAL 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 +inline bool printUsageTier( std::FILE* out, HelpTier tier, std::string_view want ) noexcept @@ -3286,20 +3280,20 @@ AddressSanitizer:DEADLYSIGNAL case McpForm::Json: case McpForm::JsonMcpKey: return true; -… [63 more display lines; full output is 22869 bytes on 70 raw line(s)] +… [98 more display lines; full output is 24526 bytes on 105 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.78s** +**wall time: 1.87s** ````` - - - + + NodeId order. Renumbering an otherwise-identical graph (the exact "same probe file, different so…inline constexpr float kScoreTieAbsEps = 1e-6f @@ -3308,32 +3302,32 @@ 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] What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure -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 … [line truncated: 7 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 … [line truncated: 7 more bytes on this line] inline std::uint32_t sliceRdIterationCeiling() noexcept struct PageRankRun -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 ) 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… COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "th … [line truncated: 61 more bytes on this line] inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept inline std::string powerShellSingleQuote( std::string_view s ) noexcept inline std::string path_prepend_hint( std::string_view dir ) -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` -### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] -### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] +### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] ## Background — read these, in this order ## Design space and constraints -… [61 more display lines; full output is 9879 bytes on 10 raw line(s)] +… [60 more display lines; full output is 9620 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.46s** +**wall time: 1.90s** ````` (empty) @@ -3342,7 +3336,7 @@ AddressSanitizer:DEADLYSIGNAL Artifact written: ````` -358330 /aux/ripwire2.cc.json +359097 /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 ````` @@ -3350,7 +3344,7 @@ Artifact written: *One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* -**wall time: 17.72s** +**wall time: 16.09s** Input file: @@ -3365,34 +3359,34 @@ 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 ) -inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) -struct FileHealth -inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vec… -inline const McpIndex& getIndex( const std::string& root ) -struct McpIndex -inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) +struct FileHealth +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vec… +struct McpIndex +inline const McpIndex& getIndex( const std::string& root ) +inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) ### See the map — not just the numbers -inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… +inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, const CacheLoadStats& cacheStats … [line truncated: 59 more bytes on this line] 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 respect … [line truncated: 29 more bytes on this line] const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) -static bool cachePathIsDirectory( const std::string& cachePath ) +static bool cachePathIsDirectory( const std::string& cachePath ) inline constexpr std::array<std::string_view, 8> kCacheRuleNames = -… [57 more display lines; full output is 23954 bytes on 1 raw line(s)] +… [58 more display lines; full output is 24119 bytes on 1 raw line(s)] ````` @@ -3404,18 +3398,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: 4.04s** +**exit code: 1** — **wall time: 2.45s** ````` - + - - + + - - + @@ -3474,19 +3468,19 @@ 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: 1.77s** +**wall time: 1.75s** ````` - - + + - + - + @@ -3500,15 +3494,15 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s ````` -## `./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3` +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~9 --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.* ````` - - + + @@ -3517,12 +3511,12 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s - + - + ````` @@ -3548,12 +3542,12 @@ 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 -1102,4686,5310,171,1380,1419,1629 +1104,4686,5332,171,1380,1419,1629 fn,fn,fn,fn,fn,fn,fn 0,0,0,0,0,0,0 @@ -3564,24 +3558,24 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *Flat top-K export for an external reranker.* -**wall time: 1.76s** +**wall time: 1.36s** ````` - - + + 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 ) @@ -3607,12 +3601,10 @@ ripwire: --format: unknown value 'bogus' (supported: xml|columnar|rows|candidate *Machine-parseable JSON, same content, keys mirror the XML attrs.* -**wall time: 1.06s** - ````` -{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1102"}, +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1104"}, {"t":"fn","n":"rankGraph","p":"src/graph.h:4686"}, -{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:5310"}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:5332"}, {"t":"fn","n":"runEval","p":"src/eval.h:171"}, {"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1380"}, {"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1419"}, @@ -3639,15 +3631,15 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Pagination: 3 items, skipping the first 3 (deterministic seams).* -**wall time: 1.86s** +**wall time: 1.61s** ````` - - - - + + + + ````` @@ -3656,25 +3648,24 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop test paths from the corpus before ranking.* ````` - + - - + + - - - - - - + + + + + @@ -3687,12 +3678,12 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m ````` - + - + @@ -3716,8 +3707,8 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m ````` - - + + @@ -3742,12 +3733,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: 9.94s** +**wall time: 11.57s** ````` - + @@ -3764,12 +3755,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: 10.06s** +**wall time: 11.98s** ````` - + @@ -3785,7 +3776,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m Artifact written: ````` -16441642 /aux/warm2.ripwirecache +16621462 /aux/warm2.ripwirecache ````` ## `./build/ripwire . --max-file-size=8K --top-k=3` @@ -3795,7 +3786,7 @@ Artifact written: ````` - + @@ -3832,28 +3823,28 @@ 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: 7.05s** +**wall time: 9.33s** ````` - + + + + + - + - - - - @@ -3865,19 +3856,19 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Self-eval: co-change recall vs BM25.* -**wall time: 20.67s** +**wall time: 26.08s** ````` ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 2.2% 6.5% 14.7% - BM25 6.7% 7.8% 16.0% - BM25sub 10.9% 14.8% 21.1% - BM25body 7.9% 20.4% 34.0% - fused 11.6% 16.1% 23.4% - anchored 7.9% 20.4% 34.0% - same-dir 0.0% 1.5% 3.1% - random 0.2% 0.4% 0.8% <- floor (random ranking over F=2376 files) + ripwire 2.4% 6.8% 14.7% + BM25 6.9% 9.2% 13.1% + BM25sub 11.8% 16.8% 25.7% + BM25body 16.6% 30.3% 41.5% + fused 12.1% 15.4% 27.0% + anchored 16.6% 30.3% 41.5% + same-dir 1.2% 5.0% 6.4% + random 0.2% 0.4% 0.8% <- floor (random ranking over F=2381 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 / @@ -3893,23 +3884,23 @@ ripwire --eval (co-change recovery, averaged over 80 historical commits) *Known-item retrieval eval: MRR + recall@k per ranker per query mode.* -**wall time: 127.26s** +**wall time: 156.31s** ````` ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=5540 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + sample: population=5624 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.697 56.4% 86.3% 91.2% - subtoken doc-phrase 0.922 90.2% 94.2% 95.1% - name-exact name 0.912 84.7% 96.3% 97.7% + subtoken name 0.697 56.3% 86.2% 91.1% + subtoken doc-phrase 0.922 90.2% 94.2% 95.2% + name-exact name 0.912 84.6% 96.3% 97.7% name-exact doc-phrase 0.011 0.4% 1.6% 2.3% - anchored name 0.706 58.4% 85.9% 89.8% - anchored doc-phrase 0.918 89.7% 94.1% 94.7% - routed name 0.914 85.0% 96.5% 97.9% - routed doc-phrase 0.920 90.1% 94.0% 94.8% + anchored name 0.705 58.2% 85.9% 89.8% + anchored doc-phrase 0.918 89.6% 94.2% 94.8% + routed name 0.914 84.9% 96.5% 97.9% + routed doc-phrase 0.921 90.1% 94.0% 94.9% note: routing chose name-exact on 3986/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 — @@ -3922,23 +3913,23 @@ 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: 1.65s** +**exit code: 3** — **wall time: 1.59s** 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) sync-rebase merged -sync-upstream unmerged -upstream-paging merged +sync-rebase-backup unmerged +sync-upstream merged ````` ````` - + - - + + ````` @@ -4032,7 +4023,7 @@ Reach for it BEFORE blind grep + whole-file reads. First call ~1s cold; after th *Version + short build info.* ````` -ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=dac3b8205) +ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=c9c1df83b) ````` @@ -4057,10 +4048,10 @@ ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=dac3b8205) ````` - - + + - + @@ -4081,16 +4072,18 @@ ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=dac3b8205) stderr: ````` -ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 8435 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 8498 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.06s** + ````` - + @@ -4100,18 +4093,20 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A claim that is FALSE — the refuted shape, with the references that refute it.* +**wall time: 1.06s** + ````` - + - + - - + + ````` @@ -4119,7 +4114,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A literal-scan absence: refuted only with complete= evidence, never on a partial scan.* -**wall time: 1.02s** +**wall time: 1.33s** ````` @@ -4131,7 +4126,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *An unparseable claim — the refusal names the accepted shapes.* -**exit code: 1** — **wall time: 1.00s** +**exit code: 1** — **wall time: 1.16s** ````` (empty) @@ -4149,7 +4144,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - + @@ -4173,7 +4168,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - + @@ -4195,7 +4190,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - + @@ -4226,7 +4221,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - + @@ -4301,7 +4296,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < ````` - + @@ -4321,7 +4316,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *Structural search written in CODE: $NAME binds one node; grammars=/shapes= disclose what the pattern became per grammar (a 3-argument call shape — the 2-argument spelling has no call site in this repo and correctly reports hits=0).* -**wall time: 5.24s** +**wall time: 5.53s** ````` @@ -4334,11 +4329,11 @@ 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: 5.16s** +**wall time: 5.92s** ````` -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 @@ -4349,32 +4344,32 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < 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( 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( 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( 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( 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( out, CMakeScan::DisclosureWhy::SymlinkEscapesRoot, "flags: a CMake file's symlink target leaves the root — f DISCLOSE( out, CMakeScan::DisclosureWhy::RootWalkFailed, "flags: cannot walk root for CMake files — cmake gates omitte DISCLOSE( r, Result::DisclosureWhy::BaseTreeUnavailable, "dmm: the base commit's tree could not be materialized or parse DISCLOSE( r, Result::DisclosureWhy::TargetTreeUnavailable, "dmm: the target commit's tree could not be materialized or p -DISCLOSE( out, RepoPaths::DisclosureWhy::SymlinkEscapesRoot, "doc-drift: a file's symlink target leaves the root — fil -DISCLOSE( out, RepoPaths::DisclosureWhy::RootWalkFailed, "doc-drift: cannot walk the root — the on-disk existence prob -DISCLOSE( res, DriftResult::DisclosureWhy::UnreadableDoc, "doc-drift: cannot read a markdown file — its anchors are om -… [73 more display lines; full output is 17666 bytes on 1 raw line(s)] +… [73 more display lines; full output is 17672 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --pattern='x'` *A pattern that collapses to a bare token is REFUSED — never reported as hits=0.* -**exit code: 1** — **wall time: 1.06s** +**exit code: 1** — **wall time: 1.04s** ````` (empty) @@ -4393,24 +4388,24 @@ ripwire: --pattern: this pattern collapses to a single bare token, which is a TE ````` @@ -4650,13 +4651,13 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() struct FileHealth -enum class AstWalk : std::uint8_t -inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar… +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar… +enum class AstWalk : std::uint8_t struct SliceScan inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept constexpr std::array<LangEntry, 51> kLangTable = constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u -inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept 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 ) std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] @@ -4668,7 +4669,7 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) struct LintRule enum class DisclosureWhy : std::uint8_t -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) … [55 more display lines; full output is 9066 bytes on 1 raw line(s)] ````` @@ -4676,10 +4677,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.29s** +**wall time: 1.48s** ````` - + @@ -4690,13 +4691,13 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() struct FileHealth -enum class AstWalk : std::uint8_t -inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar… +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar… +enum class AstWalk : std::uint8_t struct SliceScan inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept constexpr std::array<LangEntry, 51> kLangTable = constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u -inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept 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 ) std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] @@ -4708,47 +4709,47 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) struct LintRule enum class DisclosureWhy : std::uint8_t -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [90 more display lines; full output is 12638 bytes on 55 raw line(s)] +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [90 more display lines; full output is 12641 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.90s** +**wall time: 1.62s** ````` - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) - +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… -struct DeltaBasis -inline const char* kAcksFile = ".ripwire_quality_acks" -DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) -std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) -struct QualityDeltaOutcome -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -inline std::string qualityAcksPath( const std::string& root ) -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at -inline std::string gitHeadSha( const std::string& root ) -inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +struct QualityDeltaOutcome +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string qualityAcksPath( const std::string& root ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) -inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) -int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) -inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] -inline std::string qualityBaselinePath( const std::string& root ) +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) … [52 more display lines; full output is 9268 bytes on 1 raw line(s)] ````` @@ -4756,39 +4757,39 @@ 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.84s** +**wall time: 1.51s** ````` - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) - +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… -struct DeltaBasis -inline const char* kAcksFile = ".ripwire_quality_acks" -DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) -std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) -struct QualityDeltaOutcome -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -inline std::string qualityAcksPath( const std::string& root ) -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at -inline std::string gitHeadSha( const std::string& root ) -inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +struct QualityDeltaOutcome +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline std::string qualityAcksPath( const std::string& root ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) -inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) -int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) -inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] -inline std::string qualityBaselinePath( const std::string& root ) +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) … [53 more display lines; full output is 9441 bytes on 1 raw line(s)] ````` @@ -4801,11 +4802,11 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? - + - - + + ````` @@ -4829,22 +4830,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 DIRTY tree.* -**wall time: 2.02s** +**wall time: 2.04s** ````` - + - + - - - + + + + ````` @@ -4853,18 +4854,19 @@ 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.63s** +**wall time: 2.84s** ````` - - + + - + ````` @@ -4873,12 +4875,14 @@ ripwire: --safe-delete symbol not found: DoesNotExist *WHY a file is not in the index (oversize / excluded / unsupported-ext / gitignored) and which indexed files it cannot vouch for (degraded-parse, minified-suspect), plus the per-language census.* +**wall time: 1.86s** + ````` - @@ -4904,26 +4908,26 @@ ripwire: --safe-delete symbol not found: DoesNotExist -… [389 more display lines; full output is 41630 bytes on 1 raw line(s)] +… [391 more display lines; full output is 41837 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --no-ignore --top-k=3` *Crawl paths the repo's own .gitignore covers (default honours it and discloses ignored_files=/ignored_dirs= only when it dropped anything — this repo's crawl drops nothing, so the header is identical to the default map's; --skipped's ignore_mode= says which rule applied).* -**wall time: 1.26s** +**wall time: 1.82s** ````` - + - + - + @@ -4936,7 +4940,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist ````` - + @@ -4965,14 +4969,14 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o - + const&, float) src/graph.h:4647 #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4688 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 - #3 0x102e01a44 in main src/main.cpp:3975 + #3 0x102e01a44 in main src/main.cpp:3983 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc)]]> - + ````` @@ -5019,7 +5025,7 @@ 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.89s** +**exit code: 4** — **wall time: 2.90s** ````` @@ -5059,59 +5065,59 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *THE single wide-angle quality read: six families in one pass, an eligible/ranked shortlist rather than a firehose.* -**wall time: 7.84s** +**wall time: 10.76s** ````` - + + + + + - + - - + + - + - + - - - - - - - -… [186 more display lines; full output is 15928 bytes on 1 raw line(s)] + +… [186 more display lines; full output is 15835 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.32s** +**wall time: 1.40s** ````` - + - - + + - + - + ````` @@ -5119,11 +5125,11 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *Functions WITH a doc comment, most name-restating first: c_coeff (high = the comment repeats the name) and cic (Jaccard of comment vs identifier vocabulary), both reported, never collapsed.* -**wall time: 1.41s** +**wall time: 1.08s** ````` - + @@ -5139,39 +5145,39 @@ 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.07s** +**wall time: 1.25s** ````` - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - + - + … [21 more display lines; full output is 11207 bytes on 1 raw line(s)] ````` @@ -5179,11 +5185,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: 4.05s** +**wall time: 6.18s** ````` - @@ -5219,51 +5225,51 @@ 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.95s** +**wall time: 5.85s** ````` - + + + + + - + - - + + - + - + - + - + - + - - - - - - -… [35 more display lines; full output is 7400 bytes on 1 raw line(s)] +… [36 more display lines; full output is 7416 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --field-affinity` *The cache-locality lens over every aggregate: fields READ TOGETHER but declared FAR APART (split-line / straddle findings, Chilimbi separation weight) — advice only, never a rewrite.* -**wall time: 4.05s** +**wall time: 5.15s** ````` - @@ -5299,18 +5305,18 @@ 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: 4.05s** +**wall time: 5.10s** ````` - + - + - - + + @@ -5334,11 +5340,13 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The corpus's OWN case-convention vote per (language, kind) group; off-convention names get a mechanical propose= (a suggestion, never a blind rename).* +**wall time: 1.06s** + ````` - - - + + + @@ -5352,7 +5360,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir - + @@ -5372,11 +5380,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: 14.94s** +**wall time: 13.13s** ````` - + @@ -5386,6 +5394,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir +

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 literal 1086292 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 G;NR>_g%L#Q!j|FFedY@BRqM>`Pg>fcidBnI1f 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

tsvMD%pbuVMDy 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

*`#_$ZHyv3mTDoeOz^F*1^ zT=>Xf4B?*k$5}+fV1>|f>3is?*6jVBs7)F9OJE#F2g1~`nS1!Cmq&wHGIK^CT6TeW zs7R=I?EU`Gg%oR+fe3uR5eDPZ^LN3v@nXkZ5%Rc(DDhbO*-)FZ@+ZK6QP;$f&kt1W z1Elz-XTx5_X%$9S!)bfnpS5_st0VNKeR%@wItbzG8I^RhPiWvy{)FJmQGK7laMn#; zMR4vxaS@pZ=POkCh8pZBnrCN=nOj`bWVRK-g0 z+7F(fif~eeqvy^rs>r1zoex7Tw8$N(`|cznsKhI_lEKCibsRHfO4-`c++-f4Sx1}B zYj=*6=tROgxdvK-V=x6rO(2k4i@omrQgg2^tj}|O zbSF38ED$$0X~%xbRt|%E6LfY{k+^0%gaEsAy zOON3YBbQN<<->$OS2*5y&NOb;NE&;ha2ArK!>GTGLF&GR1uyYRBAc z34Wi*Q-K42u^xO~!j4a?Y|ChJqU@{&X<+6pmY<&k8WjZ?Ng&_k7+{KUvXX7%jtp z^y$9t(`R-9Iqbs5UO%MMH(k(0KkF&xvU+SvOkviEv-2R68Xiy6=jz;e?X&y3uX0^3 zWZLHCN|LkIdZc4jVpLId{@jXuT$j5r-wN!Bl9Kp>Y6>4 ztB!Wxa@98dCDPI!;j4EfQ|dQDC+eTnG=2+5B=8dy@Db!1{ifq)zi8lE8;j)>YARix`+@%6%P-Re zljOeQMY)X{y@%U`)XHX9Umq*`D^37` z&BgZ%`HYwaR}^{+H8T$x*DxbC*uX)u=R`Co7ZK3U?nk*vs~H-GCNZhcaAv-Zt<|1h5n9+azLI} z&;YV@Iq!#$y>|7QHg5dr>W!8Xb*Q@Sx(RSW`ye=Ao4iNNK~TgR;YmzS?%JJE39G=|j}dxUbh#w@1Vo5)L77kECxI$(6#E zkaOL~YZm)w2b<@ffTDxFXVP72%>4)^D`2>v{pX2Ro|LKQbXQ&zVFcv-C>T%RVg|!X zx`7M=XGFFNcKx_(4qH{?b$L!Wa8R zC4l>(2#Jw!>QzLnu*)i}5Zyf0)#LWIF1-AM%0kUVyJRE>k3L))-Ufj)cx9 zGWYx;P{{>BWHun%Nq`q}-~{L{nQEiUehO!hQGRH-@kTrnnl6DeYkZ4h9=Ak3|1^C~ z{hSi$jVJ8@n=;(j=5&#C_xIPLkHx4eHJ#<_+aM-B_xr-C@fSJ0=FXb?XW^rIZ7#g% z)0gkvcKJ!AsO6`#oBK`&xpwaRvW+A;^IhaXQjTQOY$r}{bJQ7))Ua&DQr)6yBksLx zx3~jR=MDe^%t=+G&hAd26*AU~=sZ9J7xb8MmMSOaWv%O=F-S>Ae3ptHMiOzeJ=Id* zaagn+D$O3g`nPo3P7e`awMJt-_?62!5oz1mwE=OqLRbJ#EvjraL#9xefvim@a^Aq+ z?MIzj05zIMl|Tu|A_KT`U#Y)nfZvasW)Q+3H@`vRes-dFhz3lZak(+`M&(JAed3hk6A2+t|;R|jqcDMfo$3mAmKQ!ro`?^O?w zg?oYsp;IzI7|i)SR}TZkRdJSSBmSMU-YFJGW+pC*ph=i>IWF2trz zx**2eZ}?r?zPcz9pfF1j4-tmy+{3=}EX0zc=rOEn``*no*}1ZA}=;&wY8YTzhv7w$jMQLvym zvoH@Hr-IoIUt>HiYY#gY)o~`uMaB5cdwfG3${&zTgVbuZ?XMH8;^qq61>LJ{WX{#b zw{}Br&C-RC&2dfFZ?2c?FCzR&!MdFi)4GKgbCUvK`(giQy^2l@<%{RyaSH*!F!(ixCz{2xamZC zjL{liozmOl@Cm}-+8(?y84EcHpzKw)hE2AH`FebDuPYak?2`v`@A?kCB#*s;+35->{?JBV-YZoCjQtz;6k%9er0Ft z^*ODN_e~{gQfdHuPtH(d>8ogF*DMPh8)GkaGq(z6syXf30!St=LYp=whw3P*-`lRN zu(+CE_Skak^@f<$FtJDP)f}~H98nS^`>yeV9Jb3fyv4HOHLw%FyrlQ|Pi;Wtc@Hzp z<^o>K88QYWJ~f@(ui2xfNHknF)KPS+a(wsUUtKoO)))+Nymic?3Bz8Fp=?>YZi3$OdQ7RS?HsleN z#%UBHo5gfR(&B^8G&k170LHgbUh z&^F98VVQ&mQuVG}&kOW=es|k$0=8?tm@EB48eleF`k6j2@cejhyTn`cJR&$Q(&j(3 z`~;fQBTR}_8YI-RtcmwQi=+(IAWKV5eh{?E5ekihfF>R13auc0bHa-aQqD zaRO88MLb>-zwcro-#XqiP^8x))aOw;izP{lgExAo!QnIMkklqv#K4g=9Zkkl1Svx( zhN3cg(+%eR^7qAVndJVpLF_i!q-dK|{!@J%EdhXcrzFBlg*fJ1`W`9MI$lTcW6={^ z>JLr^go*w5O{}-d?>Cdv)Yft5Zny`>D4D}UPA1XlLx?_Wj_iBu$$qz@>xdojyJ zL9`%tEqWU4dMj6rh9eqj!apdf(T})hg^fk~C!wW?ry89`R6WRtbObIkvZgf&bJ9Nxs zR2>;rA$jHB*92n}3h^~32MD&nP@5j?>EElxI(0VuUVogfVj$FnuPSsv&ZQM6a4(bR zvIxCHd~tN{FwDd!5L@5_ zTY$-c2o%9ofCaS0R6rbJ>8>96b2-L{NIEGpir1q^iox{ga_Ve%#W1(4kx92(IKGv> z&8Yb23 z+68%heLoNn6HKQH=OrVF4ijCN>WA-43OE%{Y16BN&2=7xJ}5lSzA2#F4x!iO?=W!3 zH7KB+ve;##TkE=XRBrb8t1o2rZ&H1#JQ=xVL80;}6YYd=*-T0c4vU{GCMT&R8G~ zuY}Dr`W4&NE_7B+Ec~jY9Od0`ibur}WR(blEZ8DJ)@)dwF+Oz12ycbZh>esZ4 zBnSSXc#g4MD9*TkEGi=dJv<>1Xaed+lckAAy>JL=JvO|RUSL0IW?@@K6o*+`Z`@ZO ze-bLZk%q`)zq>L~l97SFxPxB9->uj7IlSz)W9qk|p;5ml+JcB!;|%1YiCA);CLmgY z84c0GVOb9}qk)lY7mP+4F2-RPT*ULWV-XAxTQdp9AZ5WR7=c<0b;y)+DE8rTx@H^j z8!(jU$&%~Xdllm(o(gqC#n^WK~+6Y3} zd{zAw5*Z*~jf#(S$Ir6d{X0T&%z~P~J~Q%DS5+BGD?}2kTdpvW=q$!`oTRa6_YecC zu1^hCMJs()2cJ4W^nuOpIecPj_P}Rr{v>Cm$!%W^fC1%q%jsFBoe#@qzBfpV6To+&iuPz$7_qFaQ0N=*kSZZk1U%*wWsMXnWjkw)ya5(w_;X-Tseu~Gdz52gb4U;~*=a!-z2 zo>5%_ddlTs%%10u%5{h1RG*e<=s*$aW(B^Raxiv5f#A(q-@jQB)u9VBAEDXFj%P*y zIrPUzjQiUc!9R1vIER*B&mp$pAp;O^cxhUBDz%84`$N0YU27R<-YSSo6MEQcK03<3 z>wEJ-YBxLK_Y%~fRL$NGUk64Pss?yGr&i1Pd`)+SdD4~*zk%DxOh~RcQz?Z+a)y|< zUVtVsWi5oi7l{*X)MgzNWWLWT*oLb&9%Sw{h`vN0^aExn3eE4tfoQ%Blw6#z>b_6# z@x$wS28u8Pdn)RpJ)Om@^dOp)D?%NP9c~{sLIaN@;r`*WB3gHuKn8hNO$EU49m1@1 zGakv+_6}7(x(pxg=AFE4b9NGC7n8p)&nOFNPa5*rumubJn33Kn4$q*HQXxNNU zjCmBpC>H(+*?F-$&%CnW0<|p&!&26M7HF%86`rADzo2$VW+@5Iu~n;f^<|_j>6cKV zGQilmW6DVh-!-h8^=}pEl}jH4WHNXD7_cM)1DAj+2Uzd4`pIC|@X6g9sy3-FoM?4| ze~;d7VchxK0}AXF{;TbWV0SR_ju*vHoC*6_@lD;wXL86bs6O1Q)MXbtEQ}HSC-YJ< zTB-(#_V%H`)RG+GmkRfOfJ4v6q!!zj_xP|?t&N8FJURpb7W}k7?z8z%Y?$X>>gtZ) zh3a+Bh;$l@Z^!*XRO-rVN3Mslo%4sW@lbGXIw8{aeuhzqCxS%cjDSnvI2@L)4Up;m zx8eaE=$X@;%~R0vIGxzwIFz{%+F_QtDvHXET^*QR(b^aIz9iN5Vx+IY|M`Cx*b@K(h1lSy)h$+n`TTsLsWt%r8M?k?L(WpNK$6cF8egh znmeJp^&}WK&)wVH8yM}bx#N)b9Z|i^!w@w3^ltktSZ9AblPt`i-P1dCwFW19qqz@57zY_T^Mvj^oEN6r75ZV9HQHx9!b-c<5x1uD~0~$bK@?wBIP|t z!63A%oI?GE+khaYGj~C*a4aWr=;E}4mRJEYr3|*?05WCQS+I@w00E`7i%=(OWR8K; z08nXjjN1oZA>nizA+rtI?7^_iyIJA>dlF$YMJcBH0e|#G2P2B_mPW3+BGAs}`uIgJ z297>zR9d8Z&d=*t*)8J@k6;;r&9SC4XU~k&h)weKdG5jjth!hxC@!-wT&*pI>rg!Q z=WTn63!Dh5B42O^O=7gCIHfICsmF`nwMZi_kyV61P^XuqjVuLsfam@nc)xa~L0a#J ze*NZgtCufo@DB(f_TeIuUy2S-nu_xQc@_dyK5qw%dZWGhvb^d{IQ77$Ig)dziY*cl zP4?xw8$Y4h%B67z(ujUsak4?{Bo>c3kySBf-~t6;U62fw$41_ff**!|G$z%hqZ-K3 zeQxqUfxhohtz`RV5fC=PViqFK5pY!4@J-CZs7vJ)va3ljMU*9ta51d2L#c(KNjZ=k zwqZ4G4eFVruSn~2A~X3ZG%Xfkw7FE2w*NvU{AHTM&6YssY zI%oGS^jbdLUGoy}txk2Z-j}=H*K!VC@6&(lUmd?&HxygQT+`Lhp18g)?C4ae){gSo zBHrDxroKx*yUWdl*U+kV)Vn>E19Z$kmdUedtfr3b%~4L?z8)jdrDxKv%y5k=V#+nu zYO=-(b@2Pz9d!d?8~mkzjrXD7(Y8^n?xAa1tsc4Er24?z;2utJk2u}r-l9L}H~>5h zyfN2JO*ph))$JSaYgv1q2d5W$1^i(B>aC_r{bCw5URTlGZ~Z{&KfM?}z|HFK>G$l; z^ey~eqdXUSm!1#eK83zenuWe_PS9UZv>85NV;1%Ih`PQ&+(@E&j~9D~+Jiqq{0jUK zAPf8o5bo)lXAf$9PW2Duz&;Z-ABz0~zF73%s}a_iB(Ye#LikCoJH(+Jz9;|bmXQ(U z^=D7HMYcjo?=F1ZX4QWh;fxIbALZ`g!!-Qzsv03aOQVAn14f>|ND0)rf)Na@4@@;&i_!5dEheCkylc?)zU4VPk{&=b!vXJoJAm^#5y8{h!1! zBg21C=zrTme5c7N{fF5`FZ0XR%-Nigh>?Yzo$3GTpSbMlBcP&+@yvT|+~Yntg=@OQ z0LR1}?F}BzlwU|C4?a`|6p)hOEHFQ&qM-1`Rqd?ZN=uDBjWW5=GOOF{ z{HEvB%(U5}wXIvdojiKI#SL26+}X?T`}Iz6x6}Q!u509XI{e8(f|@8qQ5G^1?yTLgD@jWjJH2J^t-KY@ z+9w^)|yZ07oTOly+gRb?Bg%vq} zZ+{<5bH9rPkLsLWlt&~ZbD?Zop%XTk-$^)*wDZWZ8j=VrP=2iX!eEyrivyMV#yr06 z#NEDyuJ9Q~8?MBE-m+wAqYvk-(1Pr1Aj7_zb>TH(W|89pT)H@`Ia5+cZO6yD;vEZS zmyD>ybU>w$+uirUTGPVICiNqM@h0$pypWpknK6g!MClKd8z3>n)*BZTl^^cU2*Z{>wO_9qm5gD!RX`1-%fLU36yo5 zYA5qaM9~YLankAe4uORToa!jDfMT%=E}^GE)gw=)uqje$Nm7eZPgqTviu#`PrUYvy z>`hon)EDSM^QcEJNjmHRZX2-iFGbI5BFJl^Fv+#dDEFv(x%%$0?Kl21Nb@glHAbPD z!K}xvXbsu5)gcDldN#~@?QvNN>H19Mb5Wlua%^i1QU?sudN%ZWtvWyA1+Q_mDtgh1 zRB16hL7A$PUZ=_7jr%gRU(sB!R*UhzblAiMXo!j5L9%#96pvj;C?m++ngKi4RvR79 zOQ!dJPhShkB%eq+@j^2RwV}>kGCRbxNemudRBXk95ZfUJq?F^De_#C$BHwxJ(JKik z#mB6U*1!Bm6DafJyrJlL4qc671|r84(?kAR3$HOX0Mqr!?vZNuO?ZkEUuz)w2Cssh zQc@~(#2kUGLk%AVuZ?up9SUEu{zdA|-hJo!oIl6qx8s3piPFi1EzL=Dafb z0zcu6K5R$Dc+FW}5y#vh8`ayk7Xhp5Sk+fryKciL5VfKi$Sa~&7o3Nej?~pbh z5gsx27YJXE`3Y5RJq;Ch!}OjuLa5bQ`hCh-muUt76MePOARtmY~F}7_9?|TGuGnB%fWbGoADkmfd#9B z2Yu>l5vzRW90`{0<{WC?wo;wmcV~~h9A~Wg21De8XU^u%QWvx23HfPrxy~NEeH{Z& z7Evv7<>h2<@bc`d9%oUXwH&RF7_YEynmLnllR zXO9W`7eFoLtCXa2ZGAcpEk1`9&2o=(w^Fay)iTc4L2V`0G$qKT+!1Kx{r@pt%FTCXh(rKlY zC045H>S^vzy5+f%@CNO$OPxkEC8^xJZjNs6QwPu=aAIFg_oZ8ocwX2$0Qc7YoOzfl zwLR^9YwE6D&+kkO+Ykfl`^P**^_D^|ZHzPzjI?7<6r!FL^zhnc$Y(A58fnr6wqQqI zVj8nqnmZls%;WMrk0LYEGb(wVnrgM7Wpu}v4GaPrZ)E-cRs(zT&8tDC+wQ&qbOZ0~ks zhRYT|J~%PhzU;CeYS{nR9#u_6>#Q&GJL)e)2BD?7wV8f0T5oPe4pu0Nt*WVco-RLM z5DmiC%F51;5;?JDiY-wxZ|&QC*ai;Sfr{x5`UkKN3Kt$O1eU2bx2Vz3SX7r9?;uBdEs^&(gdscX<~!E;12FU*m0mojS%mJ)#nNW-f=QtS}f4o(RKGpr@>8qtNN@Y-uj|GY!OIEuwuT zd;GqnX&6KcTE?mhS^TKxCh{SvgGtk4vV|P+H07e1X)3P&p`)p)u5|hM61c9pI8gs- zv?BVOA=Pj@xh}W@}ir&;n8hp*^1l=hpB|ZPx-Lf#jpq2g8LHwOn~A`Tk3RbkJsq@}Qq7-5$k89Y9Av@6XqCI09X(=C9*#|*i2Sla19DgG*S3LTYCE#w;3a7 zt%7tg_*!v1YPZhrS|oLYa|(uDh?f6m)xCy~Dla1|#*y-O6_(_xu0npZDSjS_0WF?j zvkM?xSKuT6Obk7>^Kjx}x+yH2WFie!2%0IxqP8@49+q;Ao)U~4fzYYDh*~!ab$YKh z?DYG)cdk?~Ra0AIOs3dS=OdNQG8Ll~;v}`!^EcI*4NY7mzw2Gq#Y-apYbchd_ALX5 zf~4OlvPwpfTAKE($|{rak)x-IL6IhsKJ7^KuOlJv(1#xoUDajw z5GA49?Q`0YwoLvpM#0Qo$_bf^$qm4^TpJ>M$`e~X`Sgyz%pBGLF$mFU z5EV-e0d&sFYlZ@E*-9e$qyv7pGfgp6RJY69!yyHLBC()x;A* ztjL6}h%9AHgC!YP1rzY{zX1KF>%X=aAv(HqApAUBD1TkWF_BTdG%*xVRK=!jPhrl% z1H^YPnh#PZ|BX*7uau+w6<=*2Q=eRupDt1y)&IgNPRu zu4Q#Ko~GLbGQeFImglaHeX zKxKbpW=eZA)3FgylkZSiQnf;4apML$HzI?lLHHp-e2M5#e2JimP=?5UdL#`pevYH* ziR_Cax5W-Tm>1>)CA#fxsD}n<3Han7widJRKV8XPusfFoo~m$F=b06aa7@{*7!zE8 zseb4DsS=i|gLBz`C{@bi;zJ+N;-Bj+4`H zTnD=8-UPyS=+=#ZODLZO8ey6!kv&g<*D3{4>Sk}JoK!e8cLoba5@1?8_tVmuw z<6*tW;A8-MTWZnV@ZAh^QVM0$%SBKR2%XFmY@fu%$$RR0sdOSTA;wBf`Qg-TosGHt zu?v#nD}CGFyzWB6P}kRZy?y1oTIAD?c{F_cu_Lux=z>hb5&;h}SSXVLb`S+3+5iuezNVs=4i%s? zO#Eh2$*(CcooBr|xDr^Z#9wA5zk1n)^8}9vaGFHSTqkFB%iRJP_utrmm))63k^d z3*B`+agb6zPr}sF(QC7&)WlVO*=_M2Xz1kKp4V|mGG)M<3MyoqDpIO}$#Mk=nh`sS z##6PyfUOdVBh0POr?i=KIrBB}^D|KFL27bmBYN29iMW-TEdWQv(la` zJ2~78?6ML_2lf)NYI5|g%{IfuA8Quefx4j_4IIn`bAADvZFkZ-2y|=p`@#gMSjzqa zL?<_GR7_x7Y)x)ZRA+hBA}&ETy*0n-z1X{+2C3_Pnm=yw7vTM&5{H=BC-Ze|&P*^_ z%qVdeBE!#P+S<28+`vO1wQI_EoI;w+nx@LWF0>E8YZ6F~&j*JH%_Kr!nR!WyuqH2~ zDwVLA>rbX&@-h))*BWcPIq|bP>2uuAXk~7`R$8Ue%<2BM6GdoMs)b|LOa~i+(N`e< z3OyBwZI4>zeN>rc!OH{g1j;xr^#RkPd!pz>SEaY%rz>(JZ7PeT1d$ z(pZ~!UQr@*Uy#}C0;YbF{fGyLG3JarSdLTZjImreuAD@+5zB=$sS{ce{c>6dToLY$ zC4LKBbdlD{p7>rH$RXny4oMQ8uxkD-0X$ck8bPr zI<4;7Y~uzq!F*%0Se5q0piMfufw>)d1v_?rs*Z&^t^x=QIQ%xaRq)7}0Rr0~9l>{S zfDUsk(vH$F$}W(kD;jkmAwd$M+NLLEo6OR|LdCre^u=|@YOd+D~^ z!F|UwM+6eN93`A|q_l)UV>6jMiV$>HoV>&yz}3uauI5nIrk}(o?XxYHzZ&xUOqX}r zhzN2vjJU#tZK&lfpUwvSQjk@utW>H%j4!jDshfW0DRNF2S!)Y{P>zO ziAmF`^tMUWQ%HZA{k3=fg>aoS*R%Ol<#c?{ZEN+S$$QOJ$jfr))$#VzLGGzgJytM} zMM*Z^1!W$&)Tl`XEJkfd5BUh4hX5zL&SX3eAar0H3b;e*sM0%+>Kf?Y7|mQM%>kmn zF#s!lEZwxdq@+#*oh7&UTGacFu4PAjGA<3aX;VLQQNy&_z3)G`U5&lM>WZ!d5!x}AE~ayRJg^4so9 zx7~W*ha-T^W^xPHea%wC8j_Gh0HoGcz-#wrtX8ck<}WvwUH6A;2j%Yr?}b82!DcR! z@lY>3Sw*wKq`m_yL@A7@(E)4^SO_`EDV7_)S6 z@^ik{Y1xn#8@qHoUvd~&3v4v{!zFy%_wS1SwXZ#keCDvz9fpGD$1qo*069R-w0z0d z+gaYE)f?aY-P1-b+jfryAo3TkZU09T0sFEt1I$O4GVARriYQ3dIkX|L5-|7ZJj_ZF zg_woiVU@n{`z$yQyFE$LGQE4Z9#J5&?y>L###jK1c>o;}Ie`j!I~>?;foH)a6uej& zOW-IJ2I)QSeiJeRu3;~79kod?3~BTb+8ETd9j;*>vS;+1O)vs!6HbA-|8X#~8g@Yl zGEqyK1Lw?5E5kb;8;~QmSEwOr+O&1Bs_;nM+@%>J{wIaB>YPZLbQ#o01cv>klTcU^c$a8EfIsHw_%CtL*pw)Y5Ku#b zu^G+MAgy@@D2_c+0DT$h>xVsW7)Gs% z9`HW3!4Z1C9x$gm(DH=Ru|DKsp~6zpXPLCDEH1NsaY@V`NkO2pyf9Y>dbVWO-ux>+ z_owWJTkHi_Dt-3+d+VraHs9-C^}$ct>D=8VXP1wPRW+HAfv~)Fd;N)J%Ysby{siS_ z&&S`T*S*|^e~SeSXRx#f(ks>WbQmQLSh6bvKUs$TYr-`Sa!j><%^9>rVjQgDAyyvj z+w+_~ zL~hxZls5a0cW$tLv)N?_d%ay-md)4q-S=oF49By=xaUfejQ0@E-mbxdhY(K#VgF$t z1k#9BreMJH`xK@Poq_ts4!lQcFCD~=`X;zRe4p({onU|}=;9bXyuJ4;^4Uw=|HfGJjo|)uAJ5 zBd7j)xu2HE!)v8|V!tW}3RGyFgB?Q#cNX2%uGA5zbo-PcquH3Zl4Q!ZO^=$LFlh$XDlhe9O48>eRBF7(j8yb{=Rv=#APton*4* z?=1*<$Y1VStO(8hfCpzZR@ugUX;yNck)XClF7YW}F<=krx?KM-U9I%6dR(m9cb-`U zadPv-<^p}ty+m-~e9$3-1t~T`ArLJK!d+xE!Z6!m-MZ%z$Ffa%Z-{1Sg2dt0nlqS1 z4}C0&IxAj-(iL0-T4(PD+U{zV5$hsHu+8IO$`zc9{!6~qg^Fs|!(^^Z7M82gG9>KA zlN}Le&m(ePeQVFDTRV#os%YZ7W_Dhce8!QOTn8CJ!9V%`W zckUKpJLLzK?+YXC!z3RRB+O%UuOsU-#qOFmqsyl1{U&Ra@`yn;iabT-AOIn0e&9|F zGH-7ogYkvUP{zov&KNGj@WTLw4++1Jbh3*K8iU@{c}cy#JQmrQ)AqG>Lo@tiwdeZ# zm|sbFTYU5z7W?)^xEH1UgF#}E$%mOJeI~*&DcU|3L(fbacC7g z?nbz#EFcjfqzN(;{WWe1dNHE zzMejm$eUa^Z(u^ii*_S%eH|DAPH$JxsWJQp$gTWV`7*hw&w#5HpD%*oTfjZ+%tsaU zVfEpkE{mwIhLWz4jx{`~)V@Hn`wXpr<%bzkg#nvo? zA1>|fn1o`GM-cZ|^2{V4Aqpc{t2)yizU6rno^Fhw)RB2UjPe0Mcp!cRBaxcb)zHB+7ExJ=@dBV@z|@L1&Dpb;&8L%#C>p zlTOyuA&$>5S&{F^Q5%rfyf}cX&kz}%SfkJ?q+JVd?liV2pb7W`abxIHcw;_9I<-cl z<(}hYamJF6W%_ZKX7;t@CYO!U8_VWkb0_D`mA5%h%Pz(>h5>cfhH#uL=Dh9sTjWN1 zV{w|#>W61*;+*3qD>wZz|le`;0BT{>2DA6Ju9K@x%T~@529*q zAcfP$v=u&oKAyATbk?=D>x6fAZcnC7n~z7n#e{XwSCd@&nMKqA&BCCnRDRswbo)M|EvS!O{Q*= zQ1~Gjt3u{bSxje<=d5*}CIY5Oh6v)(ozQqn6x|OgMsQi#w>JVe-DBPPXLOfvqlmE>lp(de(di-*t0r|{EQ@oY)X6yu55|0 z!80Veu<5h81O)ghJA1YQ;>7|%b;Sf`hM_Tt9hDDpX(tG}7vKCe4+|>*e2{BetHplr zUl(KF_Jf4cCi+&nUcF+rwr-~~cwe8*14Q3lby04%-Q`u`wolXzIQPJuOtrtE9dff- z(;|r`StiJEDT4j@(0PxWMAktBxXx?&3lKkZRoOVKgKTkG7{)AcMeIAUwIl1QXzC*f{##Z46}*gHFSIDLS@f5g8*+FPcs#GXrE|LoCNF zUS`bqiZ4de0ZG3X1T^{Dt_l)1d(Ck4F(Z#*CTG)JKTxU*et%ijOLr0aj5xEZGiuS| z$%AHrrJ-TMXjLZ)a8QpT%l+YM``9v+i{9t_b_OWOr?VyNh#?l7NhB2E{ISEKNcr4- zNv>am6yY*gzUj_B!~&{f^(JR7Z-HyTxAYD>f?}l?I z_@^9J?epTE|E(Oc-iT%)nr}$(E)PpNn^CQ0-Dn;Ht<<_j(F@m&ae=V zI|u2buq9Ly*^p}iS}!zGDO&IcLf!4ArZ#?@e-hkdAZo4&li{p8o8kf36Q8KL>Wj|jbEB2`u4Bg_SX~_McZTa1!DD8Bg2*A%`)x6xkF9)7kplr1#m8!EI44aO z$UqICr>@GsjWE=a3r#p?p|&tn)1H47tNu$i+N22Gh&(#yrQP@tV5d+Xrv92obQ z17~IPy!QfscSE{aSP`E@eQy#b=`>2W$NtzeZjdsRUhDhV8JZyXDZCNj*r33cs=rOU zquE2q47m$Z8r2k>R5$1y2njX{CX?li!4A2G!2y_9#uaD>(XjuTNF)d*1SIX}F=Z$3 z>tgFA?Q>%L(hhbrRV5f^z}@{SODG85M@;J{4k8l1pCt421Nu_LW}EP5)|fFx z>KBht`c*fXudHo?^%e)5xI11a9XguMgZz&3xPhNw!vtiwm0+h4N*iq_LYo3A1Bjll zjGh!5DM=wl6Au>l9J~zo(8JiJ)5Ll0t)C@iJ)Qf?&+k{vCmK}~5bn(`sQq>O{4@Sc zoQd$d$b&cZ&;12Y9tDlo`qQu!fHXG9igSTo*G$SA>`FTidU^x&;QU%0I*xjBr2g)iO!i<}ntvzNE@hjL z{1%%mwZ$RBg>vwiW(3URi zFOR?hRAiV#;Qg+=vEHMq!y3I=8MEvJJ-k8S4#l%Ux#tmf-7)vSn0u!n%erk{JIzYl z&aAYpO53(=+qP}nwpD3X+O|=tleO0Q_P@^F-@kFUFM9MjM)c9gjF>HEL~H$d-)0}^ zVR&!VTnEhry*TB{RpIEWtmS8-6fGD`LQ3hLB`rqGuUwos(V@91Yr9OB(wlL8Jm>L1 z^uYCQE1<(Tto3Gy^c<6e?X|f><^1$jV)boA;P>9N&0)1ScRM-KGeM+1Z4_7qPYuSM z@Yfr_?SZ>3sos1at&%D-+XloUJ^rnDp_U-hv%GLjfAK9;#O+TD$~Y=i%4C1t3I1gs~)?~sl`RZ^EgobD9{ z+n^?nk9gy58TM7CkfQInL5`{Et^Bom`cAu|f-NDP-xek_966LS(We_M?{FVgb7lad zsc|6@mrcN50)bfA#h@Q%v=e*>^=3Vv5et@F@?_>RKg|(GE4ypb zs*X5}YGkgQxfvNfm!w-C>`%%k%g;vuixdl+?8`*#n+`yvroSuY>qq@WwDs*`{RbBx zFQ)$Ks2&?%kGE&D*4k@cGql!2l&qf*tJ#(3^UpIp+_k6Q;Okg;7Mw1<>ZN|>tx9Jz z&Y2+EXS@$+7uXQ9pB`$#ak}3y3fdu4zX=zRlY(uu>L&lNsSOpxkH%(%ai+63N*0*p z_=Oq=)shQ$f3BmxyhE?X>5b$i2gVzOu!`vl|Dj$$4G*Wi4Ex%Y<^>H&@Clwo5rgpe zxZq#TpzLgn^#30aOaIl^hz|b`9LvQ;tLSEH^pEaDe;OIto7pA!BBlpqzeOZP4K>K%oB3Rl=_J%$dke6SbaZW)fcfj74?xQwY# z={xwx`(aZ<#~khtnan|NI_Ylib69W9TRgqfn9kI@9WNR#2b$L0c)_jQG%-i62U~tmBNM@fR!T)VVBM2i(|LDjQ%gd#>0h0W zjn6|qAIp=p*|T0_Xa);daH+#ro8rV;)%+xkpsNG5K&ykw09gA;{Ico?Rer)e3r%13 zVns=Os2Gj)C{_1yR&{{)n010&F~fpueBbL-`n(0L{I~~G{p`OS)`;uFH7`cN~({!)NS;7{VTw3qDZ4P5lJ7XJ4^{y#M<{m)|hzh+|p zpr-$drv5=z|DdaXjX#*`U!VWXf35wRq5iu6=b1n2{#wiUg~9$`ZuTGR|Jw7<{O9@q zJN|*h{Dq=$J}R_cxBI z^mPBVV=6rp1Jl1Eu^Vm{x{5;ew!hpaD9P@`4=0EP7)%%p<5L6uB>Nd-1w{Gj%(Vp> zIvEH6fq}w^ec@tj>!CsDTJz6nAX-zVl@#s$;r#u#DXLunLGdbR2%oiU+PAf5P{zho zyo?cnPP=@%b+*Pb=QorV7Z?8w9H78h)j4TZc{y#IQMI=XL%jQ`#BG@Jg+wa2yPe%s#l~O z&aezvv?NEmmr02-;B}qrGU~WN)&w$8n>M_`u@a&>O+AMC4}*WF6+P78e>hvBQJ430 z1z`;w>AwSe77aZ_9R5Dv+X$?cA)K_Jo~*J08SMz|ki4OmIBa(0>c0?CtDUqWb+pzl zB~%!b%qRL*7Z0!m;X%ZiWE*M}_IO{08I!}ati#LK6(-4W(VgjjBCXIcYv!I7hIi?KoNs863X!jpoo3-?5)-khPV^&z0D5f=6M+2% z+#kK*a+)rMhBEPPksG~?^zPAlaA;}cNhX7Xr(%L%9@ft-3*r!`WWAeoegY=lB3)4j zDhgKdR5%19ZOZavkpx(^R0A%|4H?*dEPY@8Fke}(TEsP!r$n2KC}@zuMlRO83*=g3 zcFch~_net04}k)L@#1g%%$~H!0eh6&TLMc4byLC{T``jAQ7T;}k+pB*HymAkrr`+s zlc*JwxM)}KS=E7pPqBrDG4~CmZ=dJ02`9%*y%=>L#4iqwu*7NS;Y%3zbyl++682&W zjZ<-sgd%5LV#FV&S|X)6n?DW{*bQma_)o>KmNLfl<)a>k{urC`ThbCo?HfnsGl!>$ z!eR&uZ)ZF;ycYq&&H;pkEt~&nA5tw@K(tT!NZEY%`ZOsg3|#l89a;}lj0&|__RV5rv|8kdCHR2y zV?wu!_=6Jr)VEx6qgVlD1TYN86d;^0iJJH|ABZaZM}An1rx^C}*aXubKsW-pA=UPN9Rwl(r3Ugh17kZe+$vSPcs=Uzzk5vwwc$BZl!;V;}X1V13Gvr z1%F=b8VIYp;=ILX&o$FUIlU8Z|$LmwdooNM2RI;Y%N{v$PZWNb(32@Sl&s_015gLde$*A+FBM^#(f7dCa-IR2L3RZ94M;QO z!ZEN!FAde|wJs~%$h`3)06B_>WOYiAE4dKp9{m^4Y6~=58<6yUf1Y;jBmZ3t{wKr* z8mrE_TipAjXEvFw_lR5ZB{s;^VwMF@%iemL!`o4gc=j|8&ehAaW%^M;!ydQEa8C7$ zEtiIXG#u|_CRj5m9X(ilTnkDMVmCv#<(8gk@;p*-qG#`;+YH9%)PcvQw?pVr73+KU z_1GjCT#lhxp61^ z9IPt>?wNI9?pH}%N0)k;RkADSD~pwpdHuU?q}QpdJ&zyp1`=`iE7uJZQzkN84`vfsCZ2fJPr^9PT5Z`o9F1Aw;!+2UiIZAV>H_C1565K)DJZTwfQt`-L1X8;YyU0G_*M=nV69ITnYD-cp|B? z_;=C|%j>IMsZGBf{z8@6sL|W65%(UV8mLRzT?@9M?8czpwgzklrD^QlQs=SJOIOEV z0OIyKI!K}}ojM#U${r$N&la>@BnHu9{*A9uAkeX3uw|?A4GB3f+C^5&ySiE(a zrp?fglwwe`Guy5St1&;}A4IJ?zGCc*GX6^`Kr<^_iBN^CWSeYC$=+g7O9`ud`R6S` zHk2pQPsX2VvM+D;#o*`m&SK>z;$r?Kje}=tkqI9zkQ9KyiR~7H*dkFdRLo1*5-`BW zb%oROz|CFOM5xN_U)^epdgV>jjCQD!eT&xTEKeFU>+aR=K_NIz^ys6g$YyAO;J9ns zJj|Vpjn!hhYsw{ORVHM0v*OjIdh8Dzg<7NunCDFvoPpP|em@jT71EVSj+Ip_^^4^( z6d_PDR5cn5r7uv&_sd#nSgg%W90ONbHR3m#4vAY(3IT$`#F}m|Ej2f@AfC*nuZT08 zpdt9`%#|Y$!jKeq=JV$2@Q2I?14h+1MIoAHrOeeV<;(sQ^{@?G^4npait8tl$)@L4 z)L?9CFR#ykE5@?<-r+5NvLC;jFBXJ!;19WXHxh-z=)N%8i7)^>P~R{&|5hg8MXnt~ zxoO;#YG5EK9H2eoK($INA&;+FSz8ugRk*2gUO_uK6(6o8CA6Y8)E!?-xF`XInz^N- zUhe1l5RROT%!~RE(5ojT{L}51>`w%7H($g`d7iNnfoI|a*EpF|zmudU)h)$+XtAHH z>hHo;Q!h=zR4kp^=Xqj6MM>dF@kUZOLwbn_?FNzwo(YHP^$nsedDwzt4E!6) zb7#SZ_8NfH5N#f0IcQP<=A(NVCd>V(AuLPc)%N)uacUEQ!9QkX~(W{rsA%tdzH zh1cGSh>w`-fbYT1C$;7x7&PB|ZmUzP;8!b76S>DwW?r}eZJG}~qGLUv%;ZA>PZZ)gj_ z<^>6KueK4KI+x}}Hds%x^QYHHR#8_xCMikR;NYMT`{$mwF6>$?KbvnLupIk_6D;B{ zGFY<&1}cMylBnLG9PX*imSA2t<@YmxcUtfFEJIx{%)uOzWhmTj6O!Xt?G7iG*)Wqg z<7LIMBFqshvbGt;a;tAZ7nGRws|)d?Xlld@(t|<(yPp;-!VEo~6Ux#qf0%p-@|21v zR9Tp7lY1~#kERfqcUw?as4<6;M=cL?McCNN{i!ILjzqSiu0?kmQl;Mv+|N(mm@^Jjn?KD+F+?-JN>dxvV)74DG+hY z-v#oJMX?aS5LKmRI@9~hcLstB%;b3h^^QF7P%y^(0bizjI)qeahFBde(P0NC0XVe2 z(f0-HKZt4v*nGA%zY=MN>k2-Wkzbh6`o<4F4}@E;KklV6(D2Zg*5^t^ejd49Yu`JF zkS|0DM2Iz29E^_BPnu+)K|0KIuuEfkj%lh%nOLkThvbQz5>fJsqisWJSP1jYuEC+6 zNpsUYk!0bsa#KAWpp(#b2!haLUHL$&3v9XDzlZv6tj#{6K*_?T%AF07kdu6rbPW84 zX;Op;5D$51utct|zp|Jb;1$&m%_tndx#|4j8qYLnY6XMwP+7qoXj11dxl1wHxS}_K zt}%m(O^;m}DN}#2Dgx$k9?SX7lib*LkW7Hi1jsf!0&S51dUMm^1n>0dqYQ<4UVh%3 zg0i@Xp0-S5adLvQ3!k@;rCFr1=b|$tJjePwdfs zaF4bXHf*;F1_hFZot%N}?DeNAP6QgU$>?*!B(VIVVq7D2>G^XIrZ9E_zhD_aIVa)o zto^o*oy)T*+7bM^Lf@l5c#a6i06Q+gQM{53v4;c-17GZTlAL@9EJ zw67TO9Z}zo-QWU+K%g?I1%3n;tM^QYteg=BsI8ZZO6T$~xhALbx&vI38F|!L>7rx3AkETQ z=gsAyDnH_$ljg9~rYhN)Wc9cA6&O9%TGW=2$yBzMH8Ufrq&(MmS?+>g4f;b)xtibu zHNY<%5Srj$zFBI0y_J5)Dy)=<03(`=1ffTs@cppGd(KZhk)+ zZ`;H1`?=WA!t=1*6&8dP@Zu-;I4H+T6|;Kj-Kh1={o0m&wiqcinoX#nh|93C{^o!{ z+0u@G8FInQyB!Xprr4)O5MZD|kXvQ*j@Pjv55QBYZ|vuy{A52V&8Jz&lT3iLj)9#A;yGYd2E0Jx1Y<^m2G4I5*OCu z+W8~>K!eV8pr3ghuOm^S@e>!p8jv+PHCO$z09-3;)^LvAGO||4*BS5-LIT9N93Tj2 zA8H$|mj-~W7Zi{OZCeU(yB44$&;+DdYnh5Jia(=wls~8!zfP&36I4tF!UXgXV+X}= zg}nFcbUYCBJ7O_ZB=L_<02}BWs0leh7HCdT<0t^VJR#lu=N+H`V6u( z1W*-aYw$>jSB2KJi8W6*|KWfb6gIBW_M{>VtW zEW90&0ajf&gY*sbZGCz3bJUG`&xuqr_X?4q{pLOyH#_CkcX^^NQ`>~Wj%5iR4UZP{ z3*0J!a0#`ofM^i>j;4oQtU%Z*6pFF~j?j&`(+FV`dfnQpW7r&CD;B8qzHdYrS<;wB z7_cDaMc7H}P0k=9#P+=(iI9t`-yPu+Q4gUgi-18ii=5vdf}Zg0dp|2-CgM&j#4Gdd zRz$7eTs3kU2BBnL8o48@e@iLZRv1TPwn&!_@YK8*2pNxdtt<#~k zNl?q=3{mNxpsSj;l`=wYzVg_JdJ(M>hO*_x9QY+o6M8p+qmJQPg!6>{-!t?j z!XScMRi||8(j?tA>p=zx3D7)Q}u3{w8%ZQqn zA}4>()8z>ypyl?>UKZ?jcB*(iiS?y)hr%Qxwi+yVVN+!(CLC0E;m~V9n&RZM z<1dURILSl;vs#H0tkT72F`E^Wz`{WXtz5MZX3wD3hU*ySYI_^QeZZrg)DUZN0Om9i z8ebc6uSi{qU1;il!uOHva*{)uq`swRX*RIDPrz3rM*AiuF@J_*EP*<%j%}%3O@s49 zj{})@l#yo1cnNA8lSXfE+`(PUVAw(mBAUy^v>-7k;3k_LGAHjKL|n8rimN-~b#cmF z#fvwJqXyunK2;$THANhcma5jy_v~or&R9@1fP@dX#9NukadYsXV3V$|DAU%%;r=!d zxg&4S@9yq=>)%GYQ9pCYu9TKAfosXASf5CiO2s7GEz;yB=L{pkZ(0404NCf>Ai;BJ-w&<|`*Bs~Yh zOc@Xo1j2`8LADgOQ66?s(08$EU$^R1Bm9TZiTEx)Mk%JJnaZIBvu5_*WNC}3@K;{k zN&O_b7soX@9^N*c?2CJ(KVV3eyFmRH7!p*(ubve~g|ZM3@0|{7Q!!S`N!KUyB(IYiX&|620(dn9SJ8epa&+d8dF>@xN#cd}(?0(gN z+3Z|!A0)VJ`EsE%pm6(;R*T2E(l;b#R&L;?7)CM{Kc5SVQo*T#R7?f5`AHK;qvFkY zGL#yz+%t~;R0JKc#m=G)s#CjQihwMhH98I)HJb_-gY^JL8<;%ieE$9FWA?7VGO(Sl z$g9@A-#k>3an-mU_W`AQ8VvriV7sBcd|j~68hzu0v+nX^XR>;b@qzY6e-{L{ShBr82UvIVDqDti-|#ETa-hqJ;ZyrX5&<+ zG#SWbIuEu{+Yi4K%}1$VrYDD&V3E_hpclgI-P(t^lY9vh1!?Q99S6=9_ELpBD{#mC z3kyeZeLKf0o~(3cRpY^MYYW03ydMngE;des=&5C}FVa~NaXt4Ic2a<;9pLu25;M}% zxjzn@>jgwya3nM6@9N}8h~ybd7A#zNDoiy8ObO;TCL%pB5Mc7h!!%f|k)7pikHd^g zpjkS$JE4)$1W&1H*p7bdI5W)Qw)A6Z&)-X(8#7ryCZo78WY5GZE*m?fNYE<%Ai)vEs#3$SjQa~tI)S+BRBWe(- z7!IOn@jG7kjgzH@oMEu0t@B|>cBajH;3SCPybmNFyq5e*Tb$NJ+xmUqFXh5hTjvDf za5e7Y*?s$HWyJmPu%x2RYnQ8cv}!~xLF&y{1a_n?t)ob$sGiHVG(Wgn+5Zq9CoL=lZONe51OB71Mj?1I=jil0Z`vEjx}ZpX?}~2XV$Gcou8mq{D19h&mM%Xc;mWS9pACRW#M+7C!7XtDoHX&a_2@bF@_19dM|_F zJ`i1Z4moRE^Nd;IxwUxR11<79e2$fFH6Fh^H*ry|RR3z5*>QCKO74f&P!Xw6V}j`G zQMsVl)~g6Y4&?>m-aj1H7SU9ITh`w>nh`OFnSC%&AW_?nNggY#+vXR1w7oy7sOSo< z)SJU^co(1F$bDA;y=gC2cUd4@G16$%f9=;^ z-j_4mvD;gen<;k6LmSIGc2Bz;GhIV9>uMjn~3>scL#%vnEIQuL+cB)Z@R|GfvjvtvNOFS*W zgLz+7{+^BL@uikG&`mUG$v|c@6lU{O%ZlEnLom5)V$ku5^3dL69}jui@g{|wsyn&M z0x`mb{ssstn$h@E^4sWCqN^~OQSt);8&o!KLOdW7&V8b~A3So#F>j(yVVd$?{5(i0 zqi8=tLc-bZCXjw&$Byv|u}XiC|8}^QnI7Gzy_WpCXt%qao=L0uI37^(d83v7c$Fpl z{PxcNQgS>zrnAdyf15%e^TFU6-!pgxb(=Cmm+OScM$sw3tKwVM;o$wY(;w8fH|PP} z<1!bG+R=Xpb4J&U^0&cB|39=<`%h!>ZxIqk1}It;GebvH2aSJPr~QR^{6RJTU?6|4 z|BQcNAzwJizabo7A@$eVKLp2L_{X35F9hVTdw-t!^Z5_<@n`-EFZpY)ulfI<0QnnJ zw686`yw0G$V)fs=6zS>z&J>OQzcfW-{%V^0uQ7jsX62}G@ZxqUm3|*UN*&Y$(#oot z&}<>m;|ut>*DOVI68^}@NFGhvIF$RtL6WbJK0HGMW?|Rxv>0~d#+4N75iAhw;QOiG zNLF8(QYK!49QW~!B|~aMdjZqR-DY8v?ZW#?PZ!zMtEW%r^lPUL;tzmSP~ctx2?r0e zp=dNbdVp502tf=!6i1U ztyb^XUl_)XKt0~K``;dOA*4{D)&%ZqzUXOfZ>aeZVcn#)WuK3Y@HE74TvB_(-PXKp zzELiw#g?~QxC@i>v;=QN>AS|C#oFW@t3bVd6p55n|IUWzZs7=Tn5WjIvDK#b zphH8$dh|#;lpA?%yo)F7HlXg+fY;|_vPsLP^S)ohqGM~RUS?m$B$!;~>hRMMJvX5p z&Z#W$6INSt7Xo_chImS@hAVT>O%=$GxHw(2qN;1mLV?u@-QNk9Hl(`uz=F#OTBnO(sYpXVj5CPw(3q3JU=w`I1UbdHkVAY0W1 zru)z{1BrABM*0%iAZ_$SQ`5aOqU7|_BoN9bapB* z_voZoE&}Y9LtxQ=g&ao#xh8Nz!rgVW&R>iCd~vr9b+vPEkkP~7C^N|pCSw982xn!Q zC*r>D2?pO0(XoG!8Hzr0kxJ%Mep6tB8fk($52Sl8j-uJ&wWak~mI_0OTL*?t<8pGfUr=geH$%Vp+>Vdt~5Anp&x?|wV z&q@}wDe$31=a3UX?hN*c`iUmkwBAABHHVxd$|kW=)B$sNys6KP%!{!m=EMzyr|_UN z(#gT9glz}fT1N_bCRcokR2!Z9<66CRFlkUnI(Gk>ttw|`zZR&xE|d7cP))ox(iAMB zF4mlCCj8kdA38KghK^TkEvy-#k;DuV&FptXyd%OGBtJCfu@>dx62ih5_47|+9^SA+ zvGZ4;16o^M{j0$Oq&Hq#Y7}wWi=IIzt--knEwopf%O2Ou{EvXU{OgYw^PR36&z+4M z^Bv6xwI`hlH`YvQ&hadvO)d2l>7MWK6OG|aM(Lh!AT){4w}+Hh*LHsCH&$9nwug%^ z{T4N=pj*qEbw~zWz!U^~2ER?;26yT@L81CeM&^vki@#4LNj<+gIP-Zo*Gk9bXeHJN z&Qh$!C_}>!b$=HYnFld+LPe2pM+aJyuDZw}W1_-GLMtf8~d zByJ+HKUZ2-NGz2p0^L&2rYa_!uqfPIpt2%zEWNZ)_|f{BvS@aMNpw`_-8yE<5}_L? z>o?pvw6s94F85{!f89=c;`Jo_a%n19*Osjuy?{oWW#Ck4E4qko-mvntxbw^lGS+s` zV?46joS6`!*PxNJ{{AVuI7z90S z=tPjaO)2j}PtkXFx`k-~I(R~2WPL)zN;8xw9H@tGC)c4u9FTx`#&p|9NoDuqaT}>k`K>nG&a6; zGQTK>PE1Q{5-cprE2_2H^j}<(si|oxwKU~5V{KZrh^XtK+St{M6%H%!8x;!(glhIq zB*TSJyrh#_!qmcb?8UuR#OjVGVA#aNv*LFZgnCL5XI4m3Bn-s7mF~mJvwVHP)=GXB zwtY}YvFDM?Smdi!`ct6tBQDOvibd+1RD@g;gzTd%LSuSo1)rT`(nmm4BvCb1sy62< zC^duiOXD4c#@#o9mF2Nk6`~WGodgym6{CvwL7*<=2HS#8B_aH-VvPtY%HnwEHkYUv zIjL2x+Kk^7W>&B_B{ZGUEGLI4=W`&p)-4;brkD35>?AV}cIwWL#4Q`~DG)h$H^e&3 z+PRiltR!rbwJ!}?n?9PG@>tX#+fj^2{Z-t2!X~8?EU09|D538PWXd@g6jQzboREo} z;Rb|WZVyAz>{e;1id^o*Rw=?&-6Af3_$!1qrnmqc;rR@=%h+Kk-p^#z%Zj8gw8N%qr{hHZUgFicM{*f>aMcLaVO8ev5NZO*r>$~n z8aM~CvmeU08}4ViG-%zal$9RmAU#NAYEqhTluB*a^O-|b&XQnd6gn0bR!`KjC5Z`i zFCAHql&pzkNOpoTWtmqa^Ftc0wdI+R1Qa3SPAg26U|5Kx7*RC$>W8ap5V=kL$gRW$ zsFAa}q6={gYHZ6*E*7@aZo-jpNzu9&YL8KZ3MyIO)OOX~nkq@73RJk=R&rG58Y4&i1W`tmL8weEmp4pP({xG_ zmPx3t7>rWTr?*T@UmcieOLT-tbSr?W3_3VaL%pEZMN`jjQDK@-TB31eBOE_x09R%p zlQ)o2HGfhxTfNUdueCI(tIvbX;5d~CKQXI_5oKG(jI*olnsMuRfh#ms~PFF0UXKLpR&%KFM;0nC_p0b^WWO&x-G?q@25oJ^?OXvOOOMVi9^QMp(ch5HJADC%3$~f{%8jN_xQs9 zGYk9onc07o!7zLg-@;~=jz(XS9!ot(BS9kr8$+Xi&BL@}`>Y4>;RUZehj0@FK%{Gu zizt+c@zRx@UIEdUVCB1VnR%YCI*P3+9{}d)?sQST%yQJB)Oze4ag5P{+=kg{&f(p$ zfy++6@w9eU)OU=L76n}_vF|O+!Jnz0u-+U_P-5eHo!^w+LwoH1Aj0OM$vUHl4(|95%|`ahBP@AMY*f1|gc|2w?}{eP*qU}k6ke;mjUSS=i}7Spy^ z3qtmOiAGzUf7cUEQ*E~3^d3dpU5FlmBWD47Q>0`kPuAyTOB^&e1j-9atw&Z7I|X1* z)WQ{G&&-ZLq_@Xa#xE(%1TNu*){xBA_-^(MQg7ON#c(C?F;R8(D%x;l%!>K-2j{85a^6~WIWW982!1T%##7auTP zS2SO&HTX`8c>%vYWq_UUzAwjGPnK)4&w_HH*G>R$SONilevMix;iF43AIjelh!|sf za*oyWQ(&wx+jwh|_aHu5(^sCpJ%MjCSEpRVnHAeWt>ZCK(JLc)Clxoc*P6*rl{ds) zIo{r6Y}=ZGz<(rN4@u&#GoXL_oUjGLx57AR#ag!Q_o9nFk_529;NLDHw`Z;$=nN!2 zUx``<<*tR5?DYfv>1X84xdjXsf7=coI-(j`gKlV}jne^~F8`!M{k#TTHqbuuGEnN! z`sVZIcemKdllKS}O5VQBS|tDrHtpHdx^LdMn6|K!XgntcNMkmJRos?f2siA*z(Jl! z4fsO7+-p>X-p176XD|>uy0=i-9#L1%h;JZ$i7-&lE4PHNS8PWBMV}V$l6b6 zOKgW!>(veO)hP=-kQ&|>p+7{A(GjT|gI^XbN^lDp_?lXaC^1n&7IZu>zW6rjaqi3V zXi5NB?!B-j&9<02&gI0XF1zXdRef@>ErK+G>xVipG1d$-aNt9NGkkL#^*knFwcx{X z$d@%Z#W6Vrgoc3n0C7*qIf*HuC$t)j4w@Z%;c0x&RaJOxwIM2XyA!!ymEjiLc$5w1 zRv>uUyn}8=w>Imy3jB;qkkHRGxnE1mEqgvMJ(fj->?593zLeK^cOX5%^VbZf*Bvjb zs-Z*Y$oi<6A>-G>zuZWC2seuhO!T0%Lk#{T!xC)3KE0mpYh;iHo$k*>-XXUuGMyfJ zXxc+orXgOYvqec?$#BmuR4+&50FzM*c(EQF%RnRj;8DUh-1^xy)hFE(>Ko&MCwNPQ zt6e+g)*zIl+%$Gj&KWE}vdu5g7{|J90GR=zU#-IebqT{ECKSDxpFq(AVqHLM1m8Z` z3nZR%S~IkYQ~et}epjgFwFUKLa8B7V${^mbIZf`t_dbbvpl#U>?48HJj0Dd>$jF1? z8IVOLqzKd@)uHW3vrTk?<33}1TaVJuUX1#HpkCVG4{i?m#C);}KCHhISP6&fh_Nwp zTeia4f4geH4e!BSlXmig<|#St5lIrcmhU-GfHUr8T{u@P7y~gg!#CttcUK=xv1T~r z^YRKq$y}nUEUPnKRLxRKLNOITus>gnu+7t|!@7Odb}waq9B~TaNi#e2v2xuhTMk-o zTRzb-T@J_XyijP56AWh%l#?fbRxXRE5>`MRDau7aGY;r8N53gTuTq|1N{bzGFkVdL z%=(qjk@Y)fSN%e{!{51r5xhcWU7w*Kix<9wt0C)bSc%Mla&kTYa2e7Qb!b`Sd-mZ- z4XljabdBqGi-Qe%zb+Z)8G8UqGY=bi1+y5Kulk!Y^$$15h(Uff@-Dpz*sqG-C)-I`YM~q!F*=^|7X8ME^s{d7zut z7#+H{WLo^xoVXT|J`rSWIolt7xS(24)v_~r+fq>4%Ga6x*UkPl-?`M zo`d~Ij@mYJbwm84pp?(TY}*grHZHIl+_;LiV~{m#O3tN=D>p~x=*V5iIqw)qC#~A2 zl{a?kS2W2NpHR5+wxOXe)6m+5`NXE-`T8WFq|eB^^S5bH)+=;neH?$*sPSd^`}-SL zUrk+2U~QefZZ_2!ZMI5BkZZFxYw}vlUS^a`VfGY)+&ccautRznY2q~>woK?;$WyGb zRX)g%oboSU&s0NI=G-qT&OFyNS#)YWnJ)-w3e3m9n>Kmw0U za%yp2D6^_MRr0G{gt2N_DtPP*y2F$9akMs$xX~LQ@%gMO!zo%0ShPrIw32DB)&DT!UO zqqD)766m3r;8syDN)B{>)pV1K^ z_cBl2Qj{$(lRDSmI3dS5tK3fJ;U+(b4BdaCN9AxZB=rnRT^}o&2Wy1tS5s_P^w@`< zQVzjbKO_|eU4CCx8oA*>$Y;YIIyNj9t)Wg$BwZByG_;)??Y$gpA)}STEcjlAL0#xR z;S;&$a(-ce?WW8aQ)JX06xIz%)Bh`+>cX?MWJNlmL;{{ z1tLU_J2UMpIG`_}0Q}`7AO+UGXT2u$l@ogxvT-LrOg(uvfA4!|U_b3O@n#@&Uh*S> zh*xQWvgbz{{|$Tk^c=PqSdKGVn1_%1bIH1bwGi@Ss7EWA3+Esiu1jctD;p12x0k2q z`g1}SY@HVx6sO!q8fj|~UIg!<_kAh;BX>BZ_wffoOOpPUghQZlsIeWq72ke|~)QF!GxtiESv`%S# zufFKgId@VChpwpS5?<93K)J#=l-oXgarRE#n! zyn~2)gM`FN&Y`nHiqWNV(N>c3*tlYKr6O&Z>H5CxQ6A=-`(m^H#<_1;~ z|4=6=q>P7EFz$}$HKeSybw^)d79f%oW9?3<;uvb3h~xRrZ%Wg7$qsnCz--~+r7a`l zqGBTQiqo>SsGLYkOG!=Ph)k7`vO3ghSs^q! zAu6F2tIvV~!*wB6R+zU{bORc{h!d-2pIcO^Xnz31oRZXT*rJmF6ffMn9jl@4n2bF` z+*8O70diRPo-Uq_Wj&*alzy5?s$QsGYD8ct=24-RoWHzwLgJ~?aGuaO?fB-5!^-AJ z7y!|cMRosGsELHBpzGA?*NKUaW9?e{C0JvwykVwr&c3*UC)ONJ}RaEjkQ<8bq2p6Y`@^FV^9Jp*>Cf2v74iqcj>0C0DvM!UJDaiz+E14oApi~<@Z zgu=3=f#E!~yfz!cs&PIemqgVj1E8sMu*A-rl%1J!?1paaxYBTHopye+*~w!dJulbk zhqK&}3rOSa!-AGeLsT5K`XL%i2liCGIP(FImP&r5Iyj4X`YfLiVZ{sB0_y~^~%7M&BGweYea2V8ve7tEOZaY;vq#W=Vj)M@z&oRR75}38+ zN}FP38t^8|Halwk4^2D?zdtQa61i|~Hg4UHqN{ncQGmWx+ zUwwDihtVaPNOFp?w zqH-*J0LF?#@acU4_# zorxCT1;k=Td{ zcfj{*0~0Z4GtT$}n{BU%-n0ISu5d`yJ8(+Rd4>X6My-OzpTYSR&xGL!01PdKK>wm7-9k5xrA}u8zjYkl7k-P+20W#d;uMC1=i~*-$a(auVu+K9K z3jsxtSauT=ILG+LF%J-1?LlCR^3OVo@vqY8biy3a9h#Br0H4HNSjVF#7r4c-UR^Z) zWE>Dn*L9o18R_4bX!UvA+MIgIdTKe!a=2^&2LQxJarX@_8bnoD$;A7*jRttO>4#~w zfp97|DLw?YoeN3c>}(NPopv6R0fZ(#d|h;|TD$(3hNR>bhy8uB+^W%3*J?&JONbkXvOR*yQtLmUkOl9KeFLTptx`X(Md7bgo45}$R1ln+^bwJ77*EzTImafp`r8{)JYhg3VyUItYd zj@7e=Qp$0!=8EzN{mjfus17Vx5W3YE(e_jCWrPSRrB*qo$u-4*V zCakpyP7OMOnd-}&KT)ez(y>ABW0SI5F>UVUF)^0rNoTPj~x*^5!`Vq9viT#Di1)DV}=L+g@^3*NXi(Un# zJ)|6F!gIsS$D`d>VH!*}ohM-ud&P*{*}LS4?XMSR4F}zaTkfwXkTONmL%Hteny@w5 z0nnA3(lzjaWD~KucvOSTHP0}#{d0pa13f_0yDWCEgZG8e)L9+WM?bBY&V z(v%Eyn=fn|$1+H3|3LO$@m@?7G`Jh|Xm2q8D`-QQL@%(V4q7^7a+TQ*y-z(xJ>N!LA?bddH8ooG-Cerr+F;u*c>% z))9gaHD6p`e7$zzh#toHkQ#BSP!hFYp&)~*pHPB9taAT}AR1!VZ(Y6&XT%Hq2Px00& zS1Iq$?EcW?u3`hNOYIYkw`75_r&I~D&ijYq&F)pEzOCug^a?>1U}Ve=LErZBO1?mg zLOVz_3`wE;gnvp!+D)nsyU+ug(1P{@+6(9=Uwzpm(wz``eL!SW#R!D}NxYL+I@2)@co}M0b}CDiR&CA~^y^J?Kbar%-N;Hg;wQB4I(9qOqrFX0*StgOu5LF6 ztcp5VX3ldliqz&K8?E=-Zp>LebKm@}E9l6n!VR_U3tlOnLMs3bbu42KXqTkL9=1sR%ZN@9O7R@&@?(P?GwIE93_x9E2) zQBxNPd~HaGb&otS>o}_=O@$d}7mlCr#jv9e_Vd-_){r1pmoPwt9a1=?yQQnqrfVGv zJ0!a$zfj$!tSAb#j~QouT3UddPY46U?q2#9QVk5i_!rfXz@+^#3k>o~%MYwDzq6lM zF7~x~f%*d2_437gd=i*Q0IMh;r~)kp!K{bnR}8vG)K8_4fnoBsm1#Cnu=CVy4hGc4 zmlS0k7aUeyQgU==U7wo+ri-_=Ll0XK2P#16Z6!RRH9DmH^tS|U%bCGhm}*l>W}FZrSJ3+~xVLL-Thg~{9EMha$2Q?YCM>`z(6ALTOC@v8U1 z_OKQQW6C;lEe4YWWK>q1xqZ*Jlz6j-lsWnMctoUcUq!cqlsGYrKW?OpsIrad<8baP z21y)QahRDW;-7+Dn4rijawx zKh$QmOX3UpG&j%5+PkD-MOQ`GJ5`53i=_(?^_t=XaZ$pRZ%sERG7)5A^!t7H3)EYj zrDh`#(M82c8B=L1WG`{MdqGQxG;>Wh0X`~{M;i;{-T86U73#JWOzPHv8N^Y|%+%K3@9diE$9HxOOkzUB0ZvuTu9`7R;1Zzx%ET?$h_Hzix}L8V-ayzQP1(BF zfzAGcSs_??XF4!$(HD32#^_l?Vh$E8ltf6xI~2?FdKZ}?n_=LnD*QVaQ>APNu`3fM zJ=q>7)YWJN3sRWh-=L$+XoOG%OkSRF16Q1d@nvLSd-(*1d7_OWN6jyjl?I-tHJMsk z_ztRTkgZ+bB!u-|fn@Dy^?dP+UFti%7XLvKO#hu;GoGU>>M!I+Ko8fBHDoDYsa2&4| z#l6$D6o3_XexK_g=~{_kP+&HmOdw2q82#zW6|y$fzRbpgBPP|o1PMNpur})yZVb(;7{G&A~A5p6x;NL>+i<< z!eX=s1AW~4{Nma@`@$3U&(CcCnR0%!mz`c zaYf88D=Vt*_rHeXbB{Q+%L-X!k>Z5RfH*>jAd@5~G%V9oG_7)_b;!=Th_xB1lL#U0 z1tbF4lJ{VJ33UnpE%cl!7mB(-&-ushAH`8yXI}%Opi3y)#xw6yklf@%1g&pWPQPJ@ zb+b1Ai$qwML+6aw9!%JZ2VJHFxjO-i625!A!k(`At~#wu)y7G9k`@)3~A4K;x7 zlJvWFu*={9cgq)%V$6du$w}?Vv#kNpFCy$_6m_j4Aw(7FQmldvn}<%~_HX(ME}+gG zlfW>c>SbSrS9pzmh$#FXzfWW?)$?B^DUJ`5>Qd&E2e(ML0%a%a0cf*~kt)o^o0rw* zHHt%%#UDhb64O@5CmCak&>47EqU(&kk($p-i8S1lXt0@$aq5QZ%B(C%c*1K8IUrL0 z1HeUrF!shk-h5cY^CNFZPZSIbximF32${A!VI(xk7JsJyC;<4LN?8e-O=)1!vAm?Z zwc=8(&~(ReL_LdpO)DwtfI)6jrmuYb&oQ;eZ$DOg#7xh-@L=w@s$=lULa9!pC?Fk& zxmuu0O-CC?mZw?r(r7`vDDL|*73GGG_cDj7N^_q`iYEU~5V+|l2)Oup;#WT;=-(bH z0L}MaD@E328?C?>tdy@Dvs<`dPesJ!$_#-ZQ4-R05EM3yr1-ir0QThOl+6yM>uxj& z1DdpWVH@VM*v@5K795SjaC#8&)y^S2E;qAp*(@+KmkSBe_M;-y!?Z}KWkN#2bWAh` zn7qQy`lU0fr=!yK92b*P_uSX0V+m4#;h{7ujC4}6v3s+8pcfosGHRSwseTzeQa`h~ zL{dXR){R!wQiT;375nXrGqMv;sM9m$sE@Gf=<6U+M)R{jE~;i^@H#y9CS!M=M4T;8 z*RZ(iY9IQyM^PSG7Ok5bEc)2beJ&rCCbt|YG=>*?!F@i;40Z(K1J3iG#YE0pzBEw+ zCAhFUo9u`V%CZ*JOau6rOWY-eJ&LGkQ1EHy$dRR*ilukSq^w{$E&6g`-0Gz2?Zo^h zm`8fan84|!<|H#l`b}7*M6B48q(z+ATZ;@V&Mum40w|iw5;NB>%rzEP7gt{&z+F(s z^>FAletv)M>knlc>QD4da+p!jgv`{qY`vCj&y_oMq}ymMz9bhXX?cIFXBWK_87-_v zMGa6kW^UEOQ{r)~e#IEnsQ*ctnzCoI(P?2~h$XFYL#BLQm$wv_s61Y$pNFDQuB^?twrZ(KVN(}MxPdm)PZk6m_tW&0 z*{rCJRN#*o(?gnbI&}*4cVKWCG!3Z-zQz|$q-pP{FJNN1e|FZ|Kw+=coTKC;3XnUH z4xO9n)W^yqcQ_wx950BmE$)43}Am-s26S?fn@;q{1*Y&whIaEPs zPJKuXwJPCUHCAaZ;@+ENJzsQL@GSGF4YNwx^zxr30*ww4w*ph0j{i9!HGn1QPnqN> zO1!<$rvzM>Gl2fF|6BrbGO00LpuGkK5zpZnLPwlj&pxEE|w2wOL5>B z=IHNT;ChE9_+@`_KY%Dne!SHl>41CA`oX@NEzA?yiEVAsad6mppwXP1|0L{k$+7Kv zoymgbnP1iZqIO^Gakc3&l_d4CRj3eh&+m9jBgj6kK&AmR=olrDGu0R&317}HgWsdu zpr1Aqa<8n|@(0YCg;MN2)8UK<`4<<5Gw6k~3+n6yQ`+<-UWhi%#$dyCJHt#1DRmXK zI~1M}-79k#+KbjwYf$r$i4Tw0<%Ar=TQuC$GRj_X(A!BM(;SutfHk!oPCE35qAwK8qVHX^7Js07Nm3atVkUyoBZ`LnN| zkL-21p9r$2{wky(#h&c>*EkqXa06L6nF7*5zZ% z4J)qlwFp$@qQK*zfO1ncZ3NWh%SS+nm_ym6L+qvPs%ts&X&F?O{I$vVF>AF`pkEv= zfuElWGWMdtd!QeWify*v#_P<_PIgc$s5BHtJFC4XNxY_}jn9E)R=xF{0c0C=EE{Yc zdF%8X>Bs@@B?m{_z6#u~72%Gz6^#)3R-9L*s2bZ|tCSEd9!_Ub4*Tuv(%w+x)r zf2Ym0)H89w|1O;g+gLmD^V_&;P%*O7;ZrfNeEVk^zHQTVvB{IbHc z0WudpasPVq(LC63#Jb{PBk$r^m(=LkC$;$dr%*VfeF5D;@^H`b*L&|#&oIx)_VD+Z zvvY-b3znLV=BaEZT{;dl<(Q-_<7fp+n65AV$jC~^PC%m5fVO{*p#Mj5?!OI~f2_U! zef0m^Cb|D;f@5O+uYTqKU19cLf&FiY9sNHbcJ%***wOzV5j#3MCKl%ZT$$Z)chgl| zSa{~LNu{ExBe~mbhc+~(H)QA!kR;U5#U+6BEm^Su3y>7;&fx{pbKv7I1Ok*SUoI_? z9Wy~mZpRn5^>4KP5ClzD=MfKeDpQiM>}eF$?sV9aT2obk{)Lw&j5WDzk*w z(}KP2sV7yaH$7Z7!wiQ%vQSxBnP>Pkp8VQf((7_n+iPXErvDxe#It**vDs`X*UdH| z0O0{(rfp$ir6csaJ-Yc0d8^T0PUDECSs);VmA~1Vf9WvTTwbD*1gsaHW26UFuS(wn zGFUa~cq*po?)lPxFLJJ-Y(#Rb^X|`)b#47Awg`_mPVey3=F_Uq8S~7850Uo1!Q6KwIzX1u-WqyvICYwMuncwXy=gB-f6Rr{nxhtJJGvZ zAF@FH4u(O7U1`l4<5>L)=a9VBi`TkX4~<}eQ^3J%>Jeo0n;jmWpdHst8kaOGgEZ1q zKL%S6x27W%S{5XOxiKQ%=f-eXz7pRwE6$?`1UC1B%={X%H_-wqh7JBcOl9W&Hde0- zDg#E%MZtKpKkd3m!H>1Tac;sogArp(75z5E9jxn(hqK_E~C}V+?Cq7LBJDFQXgO;$+nIt)dL!Nfr;q5XHh9U;fCtP>A-~J%1jO7MKfb)=95~RFD=}6S3^7iZA%d;+^J) z8lIL74=C;HTcFA}5PJWNdWub5)(*{Ar%Ai9*Po&LVrk;ER2nKEpA(55TC~L85~Mzg zz7-j9zh_2!C7_~lZ~jRfTRw1nPjaC}-e3{*`SLZJkaq)RC`w{7f|d{8MS2xu5-Prl zDFt&9Bh~?*v2S1>)P5_V8Jmq(IiFrUYaKOZLWaJD;{l zYL&4wz9odgr`&KLVtp#`zFsCD$7GWMqX;KHF5y$m8a3|=S;Nswq<>34-93z35NDNz zF7OF)Y%Cw}HOp~PFi?uFtBek|Fy`3uS*i)T@iq1NdZ@AknOOi_j5|2TUfjX?oO%Q5 z_J|gFPj+sYBSA7*gTShG?eBZqt6271ms{E+dR<3Dp8gWIsO5T>ohpGZe6QAXDPrqs zW=H1H@#~pAwy}3eh7Q&QQD6H#frT(e5{-hk9?*ds0lW?dzMctTqY}3;85*sz-`}y zL*`-}T6Y}JPBw~VG7ot^mXAL6_slkVo{Bwq&a)4>m@W)AvWse*_6;w^4vDLs*cf*4 zG2GT_?sv+z$6#@M4M_xW6<$T0hr0V zi9u{ZeJhOvNph}h<6@K4PA^7IARo>zcPzPb?o|imZw1$oaAc}KWS}{FI2)_F+p6d% zJRmq@>bqjZ6YimxaY5IJt~o_xc%kg@G)F5E12@543O(?;pS(ReoO`$mF*Y}Nu3p&g z>SlqetcqR898_G=o{C+H+^n=VD;Fzaw6wI|an|PAQ8>CAaS|@<8s*oNuaplTvzOap zczh|nHD3*kVJ15;x0Ie6_tICuvY2((wk5INJ1^##iwa?St)^KRbINT5oEoWV?x<-d z?nr&xq+wgnBKR67f4|L!7!bj@ZGx4fjZ@ueYouLfvbbcrSf7B{@MqOU4Q(9UF1IS9 zT3Uuw=xVIM{MwQQ7MB`)mM@Nc?dxL7sl78bR&qi+!c3kRh&_E}IG z^YfVM6iNIv)3hlD*izLmz2F~{4Fk4k0AvBxow<<4+qvyBDFf!IXB$Rk`owW$iW$&wDI zq%yNqrY!sJJmT{9VfA>oT)-q>lrt=vlxG@OjAWPCLj1NGAD-d(6gYTfXa0&Xgq-j_ z9JDx7YD5@vt|iILOji;~O2hcB{@A=OM^fq(M8P2^m#8IJxIGMW9-tM0B+4{^duEzI zQj=XTB%=vn8o&#be*DUnN=*I3@6?gaoh3i-WMX30t+I-7VtI~rs)~NS0-V|5H@%|l zcS@T0Fq~&|ykdph;OL>+Afm1-&f0h{@0bx#>h?epuYPJ_CJ6D#5BNnKy+y~b@FHO9 zXU=z!6ZGL-8f4`Kr51wIkYCEZ`VM;C#CfeX34nY1+T zQmT1+e#i@H?x~T#S_5Bk=H5yN*k=AQxCiIRj?+29rkA-GEI(nby{quTsE8Pa$pvr` z1bb3{hI7U;b4m#(9dl^=N(vqWbD)4o9@_71p}s!c9UMkI7KX`B1s1MvNgi2)LwPbW z(Ss0)uwG3v@M>Jj=F7}PC(BsdrdS?F!u0SOvrNlmS0qj-RU>CE9eSzK88<0vrwB4i zDXZiY0uub{ap#p)?B|LAiKd`$N2;!-E@t9&r@w%8!li(AWPyqM#9tIqGc%t;l5XbG3}Znp7^Y}1w_+3F<0<+NJ=%)FffhXd{KhRY~XoSm$exI0n} zCFGuQHmWqBJqZ8&u^{*!f_o`zR9dK(H8ZMYa`F*ty+U%l(r2nu{8LtYPAnB%|F79t zgJeD+Q4BU3_eY5UIPK%>`SEP4fXgEVl+$6-&jc}DX9ts#qW?px0J^Dz>Ho7#QS{FL8pKUj?}Nk#R<4$w6nMqMgZ$ z&CgRD-KPiwV_c{NHl&m&#r9ka0wF1xeQB zUGz>{TOMQ}L4r?6q!k767|~pQSTo~Nl5`rIlQ#uuuoLN>S_<}8^x&1ir$ntGr$qUo zgDfDbDi#cahLLj$?&OlM^X>>U%E1E$yKHZ&l=4#PYIopi%_iS-$P|B2wkqgBQ)E8M z)S7)4blH+ezMyToT__Rqdl&{23E|`7%<&9p4YiX_t2xduc1&<=tAHC>bLIg9VMXnK zuK@b>^|)JQfx#h7xpQh)wQ-#@)gC@pPodK_Vkqq)5M^5^M&Jxu@6i$2Fz!}RBaf$c z5r#JlsKFvS9x?t(xw4g>RHNm2HDt>$tnGuGH58$JWg$RAY~dYkzE`A6aS`J4R!2n^ z#PUV*1V9G^K5g^BtR?g!3%OjKyDZaFkn1T0_(J*prI9t7_>f@FE3rIN$Gb z`MA%;U49?Mi|YE-wduLxcNm>k*-`XUKKpq*y{a_*F>(jdT^27lm*&C7%s{k_elB%8 z2@y%mZ)RJXw;}`*%-YhPU;cYSRMNhbxv2h!r95JK3V8(-8K$a)2dm*ZeHMLfabym< zp5aKJ#o)n7cu^T%hxCzQM@|`uZ$kb3ydk=F;Y{|n%gYL-ZSFeq#hp((M*?*1vT~E* zN7|Xn)XXQdZw0zy;lR`9rG;yi&fC#Wabe3s)7I*1yj!`aW}D9?+bfARDnSO`lPh#; zk!ySiyp1G!JiJWvevPm`8b})s%Y-$t*oEFMY3`3TfrD23gMfA29>)|`9H?8T0~v6w z7v;%gmx;9JS1*nw+_W{_1#Y`+ucJK%l`Y4o$49zxoUV?Wxreh$nkx&-`mX!Knxphb zOYONv`#}@MQu_J&p` zZbY3I*%3ihW#3iy>%a8BJ zQvEzj4M$wrwr@2<^Ag7GwXKfx6?6)}=A5*K(h8UA-!FD7q_dwN&|M^6CGeB@q)`@|@A+xTyq`xEc2qPM+eZBeyOYB}pl{GO7S z6`!jSBt^fSB90MeG@K`R9SBV`0~q}+gUO&-8Z9%lHWZA8<}hkj3t9{Zm*U3nqRft6 z(V{Pp%hk*xV`!m|h$oN^0>nu1){5bxR~)XePaa=&y8elwh~YdoP3kXI8G~iGNW99* zkd9vfg`W}nn$l;f(OAA5wrOlTeghH7=RXqo1pd7`SKa4YZTon0Ex^E1Wo@5QW z2=@tM#!mc<)E@w^3|93xz4Lo5^F8b_#+k(sr^8dS2Z`3Ci&BPq8C=a$`U$wnimU1t zYpUvfuaWO?%70S*0?_Y%?dFi3BR>6(bTndDZr1QzF~w&8d#3U?it6~Tt)Q_CscTu)dW9%Ik2g|!^5#UO%1@nqY<{V8*jl{b&-GC9%@F0y}3*b6Ee<7+*H%Ob>_Hs zb+E2{VgPj?i@vlE3>Z=)SvA#iK78V|f!=uX)NxQLFw>bWcwaGK=q1#(8Gr&xR=t%$ zPRtDB0fB6=usmEdAZ)U@AXgzkmvwzmIWDBeHE(QG9vliCF+zDzhz}iI_YC+n)F6B6 zPltP>8pqDd?gPtbLh6}B5(9m0e3gzNT{)x+P5ERRQ-kTsegBX%S04R(|5;VKl9ktW z^4CXsqp^1A4zB+|cf&9DXgUVXobv0^+gg~qH%GP49k8w1X6uQZ%_y99@Usk&mPBPcWht3?fx9fW2pW`z26;8>zxlr52si* z$U*OO>KW-x^i7>5W;C#!YI4?I^q?kN5o#L zFxISxLAWf8%>+X*a&IKLvg9dD`Y`CcIOt~YBim5YCVp;llu}J0)LEdu4YsM`UNb14 zyYD1rE4|mNW0~ObDw9)Vk88AlNm5t8-0n`$EC(yE^_n%4gdKbQ*a{a1v|Y zR|x5o_H&aC9;+&gGy$OM^{uVyUiPs{ft}x(x*A zxq7yGB?b4XpI`6+x3=asX4N#qRT1Ek>TV3K7h0kUF?Pe71=DvoA6YU;x=)Jytd$r6 zdCBDvG;7oSpfe`bqg5hnUoM;<8bUflm780as8>wh9MRu61hZj{ZtH&RV>J135gs(@`FYV~cKbZ*6Vlp{R_6lG=xnA>ii9flw zmR4rD&JG)0qUgDZ%?UIa)I-uoh1Z=ECikV|ESco9p);H{cS zdQJ+y@Ye#X>Gav)87Kp@AA_>s9whKz2@7 zzZ+gu58IjSRfF~`EYy46h;V2&r09xW;caxufVYuJsZx8C_AXeP_3rPq>%Q!$-lKkF>O?tG znH`~HUMeiXE|fZaWw6#5Eu9!^mrhn~nh1#~G=N z1wI_lCD+gnUC1GkIG7I~j4;$#LulF31c|b@JOo@_5D2qb|3;VC-Z%5vN$;H-sDu+Q zGK>3?`XV}wx#YngPE`@=^{L`4X!w<>n|@@qtKmtPQ@l?g$yxmEbxFqDh5G|&VTSpl z$<-~D_ZP8{U3JDle`)}0yxS5d($g>}(#QxVmjgx76CkGs5G+qg&q#35x?FH znt_GkJ&_bo`Ca~G(9c|?^O*qJlp!at>(Nn>IvB7E1|(KjqNVGLA4By>rUa&Bl3|Yx zk9>3Q`yTbemsL-~8B0f9rJ4O+>aTID4k_+E#_PV+_xyAeC#wDvs5G&zk}pVd@*;T~ z;=7t(e6uCKL&QQ0&kxN;5m2Ssb97=`J{2FamV@IK#Zt$SzhZ%<-*U`9Be?aKvrLg&`J0HT)z)!5ud#z)80WmAS4IqF}!YWwY;wRw^1Ko z=Qkr@29PaK2Ew)i;3g_S6hyVk_W=qHe^WQ(k1`Np1PV~F&66osi6?EzviJLWPS?*ZlZ9brYwOTqdr26lL8WcYyA7KTfTk#z;uL40PI83dR5&& zfg+G@L4zP@W6hA*L6letp&>l|0R*dmgmvR0~VM@bD2UJ8G%$B`>WX|gf0!>CAbeYK0t6zy|?Vc&a+FTL*m>tZRR-NYM`#J5n6MNyq?M$Vmb;>A@SIrPxg!Yqogs zyzL51-ue4p&aE@YyV~~9;-?!hKB z>Mvo=vzt~Hd$_5v%PALToBhd+YW@4V+dPx|$d5d(C?oAx(Q;oP&V|j zN*Fl(#}wg9c~nue09UC^!?u|O%TZAoJ+m;Yd4BLWW zoXLP~L2Yap-L4G34S9XQu*U$HfSpZvMZg3d*N0azh0T7K(jv$C<;UIaR5|mc8~+yFk8@u#)!J5VPX?B{!Slr$Bv%xSA7y6IQZ`aN0H!qi(MUt!UdWgdq(e-U>Ke5=_Zb-3qA`7UBZWg&k6G)dTMS! zBɫVszHs>aglK-?@%Ay(feU&_+Jml;Ni#}n`iRvPo(C|%qR;J1@vgbyOq`@Viv zh}Lc33mQ0X*RRX4qVe(CKUXzP8C(6Ex4U6&m(@I0S(?gEgb&hX-cOl+Pf$V_BDB#> zwz`bh&||m44%m1!(=x=sH`91DVMQ9`S!+zb|d4-&6Y%az;G?B#DLzO`$`ar-_7k$78bXHu>VQ zwLf&>h5csw=QHvDXjWrkW}^MO<1b+W_01;y-!`l9{r|05?O(^?|3+0X{IAVw49pDw zC#vGVT8*WELIYyt5s6Vhn6C+th4rmI!f ze^FYu%r?nenD@BNq4R^$cHZUO)n?m;^un|zEX!uKrjh+01C!zZu~F{7jl+M0HU1Z) z@qbu`{{z(c_d(fzhg|>N^VdrJFH7;aUif?Oe=!>Wz4kw?{ZEVWf86u$knF#}$A8@C z`xEN#+CO#X{p%h4Hvr^ck^Jw`{ZE~F-~aI&F8YTMr}-aRZ@O=!iH+sINR~F(Ep(9= zT8`3(CNGVd+v-LpN!4^+>h_Hweym(WfY9&PfXLldsu3Cj1Nwiz48b(Ss_FzGp(^ec z;ZTj57JuR({Xlf4l}kJPbkFwW&2y<1)f=X@tDAU9pEyid2y(2x)^%K%85|~`*tcFL zpIA+P0}%8JvqQ?1Qds!EoIP^8{UDf*o<7byn7H_H)ok&DKohPvBYd&^mdd8}&Wyj` z2x6_i;N$TCjdinSxvJGeK&DqHA<^%nExCcpJnPHYANa<_z~}Jxux%}HL?5uxC$h;#=Rc6MRU?SBU9hYUrv zfBv#c^0vk`=v;_1X0yhz@>+eB>#YyKG&~GsSf>>1Z+Nam=U2;GPWK7^wUc2SD-i<{ zrOj`Nt5OwOXD$&6=VLLTgSh}7XWY9bupP0>Z%Jk4bOxJt9>%O_dz zFt^E1x98DepX`P?=j$S@iS53EY}YCaIcCvS1Aj#@yh$idn10U+DfiP2NlhFzZorVl zjI^wJLs&UR-LeWFhd4Sx*9XIlK*s;CNtr6{@F^+SYlea3WvCUk%r zb+M%|H)AndBH}e2h%Kv?7 z64Wu-A4Gr1r+%>19Gcz2vhhED0kcQF!-Ea`@KG#g059iN>)S&e=5x0pG^SYwQl_AN z7?QD~I}?sdgX~ZmPW%ymrQ%M0`jHgD#F=mJJ z3Bpwe>4p}tcwL2;FWtV{*4}US9jd+~cb~{Fz>p?Oj_bS4b2jC=vn`?7Q zh_h1+MKoo}?|kKlpE4*J*!DC4D_G72y=ijpo&MB7CfZ9^M)p2T)|OqC1v_|;gVfRL zlj8Hzdk$LWjS7G4(}qsO?dL68>gF*A*A=#Ra4`)ImdPf<8#r9owe;yR zZRb;mK||ohM~=BO>%DrBiU*sCvZ@Cg7v(yfH`Uz$hW0F+)yK3?_ouo9d=!7m+I6E6@rSIhgpPwd^a;ES|gfm!lTEi>&<(t;7>92wc%M z4M8Sx4}^0bkhi*PsAhstiO`NUE59X(<9Wm`U z<}2pQnk>nyF0SuUrY;_BYpqKzE-vBGoEaw->rjuud>*VlxQ#ZC)`y)hFl|jIShO!a z*Z%96*d9!qW!IP89K|@C-zKEKKxfyk54a0W3l726T%YUq#!8%tmBzf@Ho}Z6DgT7m zd0FueonJP4?QI=*y+|EmFO%*FDk{TAXKSw2cLSaz zTZ2_c14Zi7=5<4X4Xbn~&!XaxTWStE6G@nzt@a5`P*?M#8RFFPE%k9{;KfO3lh>1w z#qN~7-dC0x_g?`s5VWPe3RQ<~rzq7+9Jdz_*f~RSd^SAf9$cuNZ1tR;9v1_Tq;KwR zx=r90iI2=4O&|4M)6^(L=NXwp7X~E0(kOz*!C9THtLy76AG5j>np&mi^Lp`FauV6< z51bqIVq_0@m{_j>xS^f~XUg*Q?v+_OYdIn!B8R6`%hRU$NLW!bUKBq=9m`Ku8`xRA z`GzQ73GWwtBcou$fS@o*^|qs;{=y&V5+c^%BZV@80Ni5WM1SEAqk*sA+>kg?Fu)PD z1}G%@<7FrD59A3@`e;$pbex>=*dB$Zby!D2n5Spv7NunrHZ3njEi99NQgEohO*^TD zC{r17|C9g7fXB{eq2{1uFtEhmxRMf6#(osk@LSMNlFaMgON+QWfu~w}fzC#>hD`uZ zqr4q_8C6gpyB$mf@0HtFI-k2CF$ANaKqsFrf6L?MLeiNa>Xy?I7$sSfGp7@`NNW;yyg%plz%!fzS=_E2D}LC~o)QioNKImCI#ybMIWWTQ;)!Fi zj>~ zb-qO&#~kzh{x)D{QA=y*H_bZAh504kxhDEG8<3V)?fQat)FG)740)c0WOhroJTa$Y1?Y zn`#^IXK%y&Q`&i;4n*u|g(-ak=qg+Tj<9+`EBSz$Yb^~hTCFj3#U!#(s9Uq;F4Tfo zt^U-K-MM7mJ2&918DUkl=z8_s^XAfH^4ZkJd9g7T#K5mGMkn^w(z|FiOGmia_zHHf zP{8Ctqd(yiMD~0v)D>iZ!@+39VmSn`OQISrPp9osDsQq7gkVp|X;v2+S<*e|=4i>l zCO}tFE0uYgSpge_)}4$LvWHTKIh^#^qpEb?R+_3wf>Kb*ZrH{IfKA<_Jaf_nT;n7D z%8@t%rXnzgxPkRhue(ll!{qwjirRw`1%inxQnHpCXg}$i;vkq(HP19&0W)5a zwM5HQDGJRu0Euz16bi}cs;@r{=;RQzvefPN41vfFzx~62NLD^TMr)LI?}FE#;d`KU z9H9=d>o7c1Y3|YUC|dHj7yJt|m(ZzrHjKcGbXAD05Czw#AFu6p&@SY009+WBfysOT z9%>GmaQryC5CvM$DwRKTa|vD*I|M}Ct5tb{_egl6uN<2Ntj^7|;hTK75v<@p6$LZ^>7{Rs%ZeOT zJNUSOp6REUkM`Y(!V-jD1_(>G(;^{*IoN^n_!rNn!Q!PoQK>T?u;u}d^yS?Er2vA_LFBNLtpMOK&WlX_4IuFPJ#_LGc> z!dB214duBLDEeo$obLkEFS#6PUwdI5Wx!-0KcI^5CDV`{P|#s%(9cM_ZHONzCGt6f zzMg{!SUdk8bLSW&+1Bp)E_T_rZQHhO+qP|W*|yzf+v>7ybLzY?XXc$dC+5!mb|W%k zN9BBV+&m=mhLGgsP#eRrS|~xF@^bhVzaFG=`8exrY()^*6u&g!_0xS}Fc9 zr3anCABDp_ri7;skhoObv(ODw_b%k`4Z+oaA3nPW)fcr=_5+Rt``{Gf+{nSuqznTg zeFFIRRH2oj!Wufix)l39D{bl)lkai+*=bX~`N4=~@B3MGM|FqD49f>3j6$X^76lXs zq+lBnaVgt3P7D%&_+W>GOz!IoM||h{?6CzPhp$Z|rqX4-zTwy-8t%2mLNBFP7v%=o zT1$Ye8rs_pkvt1FU94+C2f412zVFx)3<9GTAQs0@)`%-)BO0}m5tno(BE3_NHwv|4 zMO>a@Iu;MSGtVvIuiVH6J(|<=@nTUL-PH=tmGP6He~_acgcoCNL+VzDOFO^?s-BSo zJE))u?miioTtdf<NwdI8+>8Mjm< zI*>W`!5tyt^*pKt>Q!ivG$mP4GG7MY#5g=An6B5yQC1G0xKL0jX3TEg-?!DsxXl}u z-K1~MBY==3iBV?rIx7yV%k|-q_gx;_$OT6wW}^$eypFLEis6w`%Oa$s^@}FW-~p%2 z;Usko2Fz)bB%JaTglx$M#rksK(CE=sq< zpDuwu=W6nGz9JQOd6VlvHRwZ@K(?w=((8doFTIjMsU|a5>emS@VN=;=h(w8t6l9(3 zlr&`Dg%3^Xm^$$Q4jUFVV@(yg z7Al$`^1Ycxh!ZY@7Rh!Xk#~2=a`t3M0U&pd^f(#SA^X2}->Zv#k=Au%&h^Vv6)Esb zJl!trZyf!mClbtkZQ@1r~bfo(K( zw$l8O`x+@lZL}sgcx&tQ#ziiFL}yS$Wef*tiUCfU1f&$F8W|ASEE?2AsbP6Bh(xVj zhLmEO#pGS4a$->-lKKTc&xZ%d;^yt_6!;t?;xPJHqX7+}UjxMYZ?pJ3^lStgZD4G- z(GP7!Kj~qmUgu_6X0m+V?r{$lyy+I97h~d18vx%CRUX`^^#E6nS5v?^W4qS*fHD zwIN*WxouwVk|L+pTN02Uh7-|9@88l1t|9&o&lDDO;y^4Xlo7Rv1EwT2{1D=10;eF= zmnn#p-D3zYY9vXy%L*p%U3YuLejPMu(sQ7fGC&s^3p;92n;^OMzn`e|BNvz0*Fs`K zB%kw`H`9%kJF%TC+a5By0>>90j8;zb zRYFytw;R)UTc7Lto7TEL<5TG~bM79lk~xp$u*HZ{6XwC_@$iSSvN4)&Bb6%}K4* zfzuI>pKQmt@~&>VlR(lqrT2U@h><^(SkW{QocIH>!+6p>t5_9u3vV#STq$UH z9r@yc?!_zJ*P~IRB=vq@kD>yxR_i=cQ|V(5O$oc+){b`)pnvOF(7lEB3{)ty`uPA!wE{F^;<*9M*ooYQ2GGMo4&_HmS6sJ)^VjnMnL*q! zM%7l{V1Z&*{h4#i1>sb8j@<#Np?`jx&oTX&boM+-H*RVEd`Z)s&U(EmDfptHRhyf0 zaNK((v@{i*O{BH@P#K!kh*zQON3Ptg)qA`9`V`X`t(23ui(s@KleV;s%(@;A$VPh5!bf`Y67&xl5nLmn z+@!K2bY_XwUJ*53gLkxP{UJEaw$xDvx~Y|G&X6GylxS#L>SqQ0_OtP#Gme1(%L;Vx zxh!*r$FDXJ}#uR{`*@g&W2xT)4=Ol?ldwE02 zR2=C9`C*>Ko{9q#8JSr$X0H4TAc$Jq&fgD4xRAiwmO5O$C+Bu0@>!CxscW4Jk@J4aI+WyD+M<|cLYI|dsULm>!iWzp z5MY(qY9@5shSQ0TYj-D!z(v!`1Wq*C2cssBskF-1z+VErW^K0u%4tp43(A8z5Y@<# zVsD2j%V>EiR%H0LU%|_d{oOH&P~S@Iw#8B_xlU)7nc-&ct;GufAVdTp-OE9dO0*5W zg@ivv0e(1K?xy8ZDfX(JAwL0P-+v%%S{6h-BzuF#5w9lBApuwEW{`w9k#Z4jva>3} zNV7kJJR!y+4p6BMFbL~WdcA#*$?Dg=fB8AK+F_gjg&-^olj*<8Kj`fqf{ut$SJG-d zRUWo_2zN2iLkfo7ws8~`{eXefIxSi#Bd#fpH5S#V;fKb69B={B}-V5 z%Ak`FP}!8;{z4>ht(0r*i38DE70#wAI{i*aWR|R&`0pX$8*bEL7AOi%`oVMmlPT~R zwR*?{$PH58=G#zrlEj-jbR>C%bee9DKDOWo#0s)sHt&OMZDyaT!{6VS>{d9(6`5QA z^GxfHah+vP*uf|LK8r>rz_fgKFttgPNM?NgU65XqK%?eJA-w9`Xb*%G(UjOSklQSJ z;=5Tzegho|*bJ|5`&S$b7&o{fOw1k5>k^JVn49BdZo8FK*4n7rFL%Kp&RP}CJQd8s zFEzb-wJMSr*|?U`8S;Js_o9i#Q8@xZf2cGDHe)!WR=j>(qvv2KJGBt)Tb{JDBu}Gi zVI5+}bS+{>v2(qo7Qz$om#B%0MBQI)SqxS}qgqNK3~e%pUJOLz*VSGshnop`qMPK7 zn8sma7YwMwvT^K%Y`COz#<_-8iLRpoB>ie_e1~CBimfhfic1Imx9;jz3H~E6>WMFi z4EG9UGbDn^Q2cFbhfCudxA-raTx5}8_2W9gw+X=w8GOTnAlYPky> z=Dxr(+eDiNBmrc4#Rt9Uk+Fb~QB8h&{*Wka;{=8|ATH{9kd$cDD5VWO_xR~c<59;| zc@RzUdE==`sbPvx9kDkx^?ZA9jsYNXPl_nsHJW;yd$)AYNsv)T#-PH@Z))>jNFC~iBV)$(swnC=fBR@RI^S*mRfeI@;m67r3c`8qIx2S z#0%uy33f&sO!wfI0^kn`lr?6|TR0~Y1RW|M?8RtMr%9EB-G*gY?^|~|F`ZZk z*4&^tk8tW(qqDdL2A7wbpI3KJMUmHwC2AwRx#>SgcuKcPl66PAG8gP1SDuno2MdO> z#x?i0+@)moo2Q6k%^}y*g4`xI>0w5>hBC8~r{-h1VO%j}d7s@T2b@_jHzz})SLJ>| z&o}HSv2}g=x#1pM(h- z%*zA9x5&m-Og9!fh4L52Z>En%uAYT+=oR0KmF0dIh#3=RXGfmj+PmtmkMI6!>cH_F zsM}IdlmEh~z-(=~jPYIWYo}i`YWcH1AOd(Ys>YgRBuXG|_Yv6+&lxp%2AX2Y%lTWZ zt{(oa9JvA$w{!Kz3da;a+R>y^SB(`0mr;hg!}4Hlky6&tXD{G}e>|Y)_t^Ut#1%T^ z1ELyh*YTF8*_LDIYBd1vwEo~xxMstgcc_@2Cn0{_#`27gA!IfY3C8djD&gaD?mx5G zzw3us=~(_&n_a>2BPkZdz=yc+`9s^hN^-$>Ph6K3%cifMV>v&)Q-P@r)Nt%k zfGxGRK@O+d`alv^H1j)s89my<%o^@>-6YKf&0;><9Vf`v$2NU~fJKyBX9pilHxqO@ zBV)xYDLRMM)v{af2V*1|8AM**opZLv+d2%K!cy~+Pe;zLD%lrEH(e?)@w-&zg;M?c zD!}D*Okj5;WP+M)4 zQ0#lp5HImhy1t29MK3 z3~wbr_ZM?cYkfTgOunduO}>D?=yg6$Di^<^CO*jrYd)P&-o_!dKRw1*K)pzLFOW|! zGi|B4NlwXK8d$I2HqXuIBfD&4Cu=zWc@+PP84mx?$@@oH00aBKWjL^YXLS5K&i@Pj z|Ap}Xy7xEI|NH&F|*{<{9P_V0Cn@B2#*@VBJkuQmT91^CxV z`;St9f5?Jh{HH7k#{WDEf{u>v|48ZGaI^f*f@r$dIijDQ$V?MI%w$K`pE4R5Cz2W= z;sRn1A_6Wr)Mmsp0{oT&po{;|O`TbT;K%4(TUz?rh})tnKLMT(j0@r!dGT9fa9LTh zZQ$uaA^0)C+CS6FGAAua@8TloYT1?H=)7s!vgY^Wn&kyHU=ZBT6Jn~e3Tr4-Yo6<4 zTX6ppI84pDW@;VnKkHIph@zRMgF`mP)@Rhb#6jIC_L{1Lwi=$Ue;jQD6xP7$aa+wN zFDhDLP1mKx(qrS4=IZLJHoEq_V8&bux=yge>|R&iYJHI(OPne%x~O>;Z{bfRdVSu~ zr;x4ZakD;13oBM=b^f?$W@Zeb625zSB|BSNs)lemegxG%;Z57_W@&E#k93H22%X$K zQ0{s29>C;L+amT|5INZ}FvXEL5Yoekw2GnO_vs9?z{8YL(PztV%-poVj%6I3EGL3A z|Ng~5yRLu6#3bWowC9PvDkY*1gk?-6#e7HhHk}!$IeM|fbL99O{a#VbD*b)r4`HeF z$GKB{8y+z<&|Ze?JrpaI$+%?!Q3NM#QJjrtANRA`+`m6FdWC};dBl=`91(ULnvoU( z=i>{JC*be}k@^i(eb9}H%aGL(%A0MBb9Tb~4igm)yyJ%T(fD86<_^x-N0K2b-!zmq|;7=fwBP;xldPXVK8Q*s#2E1J9v$tTgnloPv*~W4TNjr zuXP)*8n@O}qG+o=tGl0T9q)^_Xdic9<8l)F2yys{nWQD8a5^Nm@kikzIyocIt8tR9 z_--S^`&jM=SLVXr0If?uWaC-waCzTcpV4lCqvD?UhSDA2@FOS*N%hFr8c^*qM_^ik zTLk0IL4WaQg|uh&cn4Ou(Mg2Nw*UOj+km4Oq1zgqi+aOxOycUZ(T|B&ojp8D!w$n7 z#QMZQ5+EL$0JwD&{gMeq{B#$Ra--O8TP-fD_KgnQmo-eS4WUh~185Z2k} zHCz*)7lLC_(KQ!$qYG`{U3Mv&RJ{yvJ%_dI06b z2-atMXPgf{4RSt?0h+jf1;rqz`6Vur2WA^}+v5BPj|cP5xe+hqTiS{V$s7I%-7{mt z2Unq?NIz@3JUDTXRjJg?#h zsiQP)Q<@VMkyYAyW`LHV%lB9x7Ey>zd5T->%+y(bbqB>_Fp>H$N0U{Bs8 z%Y;v+{jY(+V*4ZU_$kN-ov+JXmy)dVSV37C^1A8}$bIOb`}m{0 zWx}$%f_Z&0)B?G+&i1_5Gqj_H^-1_?o_P``$OI!;V7fSI?__$^e%jMsp3leeoPH2i zvx=pk@y+_J$uKRcAsP)jtn8h)hsw`;5(Zm9P*kNM3sKzD-Llij`1QtDG@27H z^k0@lf3{pubyRqVFq~XqTik zndds)#K-Y^o(i@$>H?Ew%V#4nG#gh6&7zlL{3QH!fVC1qg%xlvPA^Y4<9>PNR%EaQ zv1o%iIm;u+EwDxUU!I;`UXp;vS9j7Rh<1zpcu$}Sgm))nTugok_(0&uq=!Pw)ZR!M zh1=xo7Kt?|BiQSOZyyR}Wg^AMX}25QHIsByG!*pKzIa&H)^wcAKMnrwQG*;isxZDg zOqojKwmghFA)bfmSA@KEtZ!f_31SEU&`tkC%!cifv1q_@gB59+P-6yHv9KaFsJ)0% z4%MWLx=)$h6%!HgJ|?FurMQL!jY&hPq-~umV4aSj*hpoyMv`lo9+6qmHB?5zwrDS7 zM_yEz1OujCxNCSZ<#DnWnn0)YYb%_ z#NT1fZ`LcJ$p3u-AHpuFkr~ET|i>XJzyT(`zDl<-da40eBL=* zPRFo0XHIUdgdgj|H zzbL4;Ne$P{LQ*P}sw>5AXFri8+%aSz%ZD4&%upYwN*{j~^bRP-P2;JH!$cvVoGzTw zBNUSpsnPMEDq)hHO5#F$6cINvAVE8mrE4q;9Eofgy0M=?^&gEkS+wV~_NpI~APC*6 zg>ooXjNQpPRX5KZ$GTQ@i|8&D;uO%@ekj|VU&KyppFbLsPLc#R6PVzSXpbW zJAl>v7+We%`GBH>G&q>;B^R?~FNDj-Pzy02PjEq6%S*?oHKc$32vrjBfTl3Ti~5Yf z#{->W2mz`$2ovAd3iq#%@>l9qtwGyGKyr@C5X>a@KV!BVViYSKSHi5(P^w7pV?+DG zlUwi^;EFAAp^P(C=mGfTPqLt!5uD*PQ~m&t(b<d43Tt|Qy{j&~^P#WjOV4;@EJ4-H9GVA3=*ZZm;RR`fz2{6&0C z62WR1763-J8EL6o0ub#-OwGTZAo1G$@}{LblN@27aN_8#;D=Lz`2oZl*pdA#*2Y0R zY1sflW%*ElQa>t>*|aJ}lYk;=h-IaLfq%J11~0Cjogw74vt%qePO!t(^0QF=66_;G zWyBP&B~mCtC_D!u52}Syfyo>6$|yCY=H7ApeS8Np;!|9r^57%U*%B*`#>$-$nxPk% zJkE6=+;k7( z!=y=#;)tnw?|2GQh66_aH{at>X{|<)&em>T8-M=O@6S@n1tw z^PnWBq+rl8WCPonZ+9v)O}TAt~b(aJDgR>}u_4S@!e1@w2fcQCIgY!1ME?``rnW#wja+u6AKXg2G0PPNW zlvI=T>v6w7))I%U-0*7b@&nDAthWl`Ug&{W)`E7)gr%%dksnk5xnX+~Lk zMiWNLC2i=nML9R-QdPlci`*E|645@%yD->{Al#XsMoX2HSoBqJb8H-o=!y2P^RuX{ zis(*|(Kw9JIEYg^=@SkB-uK@z3`Sq8o{CpP%&bmcFL#(znQkxlh26zenv0u08wuG3 z87KlL1=Hux{D;mKWg1+|nAKbL=R4lK?>-(c?_%a|A*9|zO&NC8Pjg?YyIS6bD$s~I z<5taTUMKaG)X`Ou#cc|}-w#A&LbFcnSg;$N#CmR=*b-rW6_k}usOexR?&;$?b(FTD z>9B=w8(oVeP>$)m-Ih5HdWw)0O?(Bl>ZX5*S;9@=X`oU^=!#(m7Owjj@6S(hz&a&aM z28JVvZ&LNQeikCPSxCOz>j~eSLR*h z(r1rCNo%Kr7cDP^mbzJ)tFGUI>`@8VAkYJ0T4}cqAqwSH^I(Y?2P)uzi1ApI0{lB} zCGyo2&yldE+b90OMAs|$a?JAqv(=|MTZ~9dLU~Y>E3s0Avo4gO^Q1{WcKHCw%15Ov zI$ez(2QbrnV1{=VB>dQcenqRV`~9X1u}Blh);GDXHm%y#edZJ2&~aG&O5Wl$X{>%Hi zlLKPl!pY7gadN0lUD84$-KnP8%*^V`a}j$J^|N$W$yP!n;T045u8T?+O`s8unjDakc_1kW`a ztRBn-o+jct7>rbijie7h@MD<3s-!bR*d!b*lI#jAK8*+vjGz-Q8%{Rz{0FvY$H+oN z6@j7@@{6zuiH5rbFxHbF7_1`Dn{e?)ZOud~WXwZdX^MPIKLHonB|Lg#d18zlA%hq< zA)uoW7Y)P5uHVRjFr0EmL3)^;xU(HfuXRIO_%9M!*Fq|U@Sgf2cQo#*(H{&3X6Ne7 zOz`SeEizt9x;I4bL)rG#b)9?h2=-#W^AL{ylVdCm{9wf?{1$Y|NhXD$FS(zWisX5_ zB@eC-Ux4F2C7q}cK9~2p4`6l^fgpkXglDa*zhZP6{M#(|vv)6^=zf~-&q-+bJq!pM z&6Ux_S<$qZwKP=z8G4-;_7qftYF}SGwjqtp_+un7__|9l^=cLisdX+QWF-jb#A zg`Y*O<9>tEMqfYHSQ`VlH*!#QR(J8M|8_;TMeOxzrMIi2>kd0THeKYahj;obPD{92 z-f=$5yexAyEuzEdmI!exqGuOG)9ysQIZ@UHfWs7S~i|@BtTh5qG&8cB2+)YO^9|(zovD*|TI*L+-t% z8?$ZZ!FmO1d|Tvr?#i~+c%x|E^ghz8hU=Bsk(U?I&^(5NoSAs=u$GNYCJC!Ee7GQL z)PNp*PZX_Rz}1K{3<-3PmO zyzPs7JEHbyXYwglPa87VtIr6l|Uibwc`LyFdXky;f8Z7 z9Ku5)&4M!!{JbCa@sjPIFzY2-kUP^n!R9NE{VE?UBoiwS!+I|xx*|T21y9>Ft?;6# z=E=?m(Zu<}_u_6=eH3T#ik&6|=a0QIxS0?Bhk@URqZfUPx3pdli?j@Ku24@!d?Z{x z5XV0>rg9CcUYPe+-}HJYa!LmGt>Q<4zqQn0QC?R}7UWe__X7r_-U3Qv6|QAQYAJQ1 zRKRwe-Jk%vxtiv>ISpx@=NG>!sbxGg8-vzYGF^1MJ7G~ehe_8B2&*@<0TA<&@ zMNNY?l1&^NII)NoEo5!h0_F1(&+TM4UvdFg;xoj(5a5svzz5%ETRPzYCU7Z)ckuvy z@EFN!NpKjkT2Vlx=VssygaINmy5~sZk%S&X0McIRBcf-y09NAXWc+JJaGa!V9B>wi zT8RJ>aEd4Zh9S^riNB1v!hn-{pC#ZEr>yq**l)ys8wlr2YKDg29U^j2Dvbu98aL(i zJ}tAVM|iu2Ku-a{vKTD*#xeBr1OSdpsfYUD9>igia;Bw@=uCG97BjC+0rYt?jXLsV zU3tat(#q(DtVtLrL|DD2%w53<02%Sx2ANymQ7BZ6U4Do?-M4EA?{jPQR=>d=o(?CH zkFG+fexGQD-eY2Q*BLcc9`>>6R%Q^`f*_ZxdE&(1(fPX=moqtaH8`j4+2| zysji452ksSZ833{Y(L%|ql81??p!9US`ki33EG(#iS1AvBPK1rn=OcS9ybUCE1(7$ zo{iZ^`@zw5e(m@{{W0F&CwB;5r*UM}P1>knjDCl@?Q7HEJOu^m4$deld6H!h9;H6oP#GN1I4!LffTJ)EqL}daNNlR(>nRm&ZgCu60s=>up**89qCRzp4nP%Y zJJwqVndru&IwmFGEnlfEIn9Qw^B3{b?2pPK20XaR5-ZD=qT)ge^`?*&w2JwxlbFK? zGDcd%lPtZeC)(Un$idSD(4+nwVQ{ebktmqDJ>VX}S7pKhuRk3qc8v zWvT3T-WZ+Y4Ep2f1LF1!@Y85W=WCsuv}=Y! zSh!fea%r@NwiU)2*-&&)^7@rAqOF$SQEJqHd5Byfl8234>(BeGv)3>lD2Q4%rtIp= zF6$(#O)x4(^)E`4c`rHuSoc@*k62pXs;#ec$%XGuQF-1SE2kF0wpaAc9=1MpYCarw z0zNd0JVZ#^oW1}G6D6Qw-uj?3)=PcoA!VuBzT+TB^aPD6Im=)HN;pcVPMhF<6|~wu znZ06^7J=2&iCX)-GbZ3TP5Q=6hG$pIcYVVj3k-Laz0n#j=!ExDXuSB218`zdS|-O1 z=n3IFuF=t1tx~(!zYrf-_qS{2KUv5z%@B&+*=(uCiI+)``-3)FVt46LRRh=|PhLw1 zX*`~YzEBf?V(|SlH2d4+&BFYhM*qJ}-i-ghCU4>YZYJ-4ajyShgJt>$lQ$#7f68yi zzxnN7(fPr@P6+x)h=>pbB*f#??(^n+eyj*aC`B1(g81sjhBhmp<}xndwcfJPoJm&w zMD1P^XW3TcHT!PszElA`+h#cyBa(uitCD{nuVa=!x6Ckhp<8Otx#f!3dS5+1?_)7# zcD{xiq*d|9zK^!%85p1uIHFI`(2!-ch3eNE^NdK&y8 zDL^$6k%=@>oUEtm9Y}W6spqbY3~%;uMfF{6eRg1K>0N$ZmWk^Ud=%6wvZPk5O~x{0JieEDiBEVWtna0U)ba1O%C zVD{|IXu_a|++`cqUSRl-lgIR5baDSb2koD6?SJe${Ts{vg<1bXtpAQ)|3bHa-T&+Q zFO>U@cmL5OJFj{aaw6htsU089f019ljR)LZBj%K{KYK%gUw^9Q~LK+w>{ z59_ti{M~h}Q=?q&@ZG71x}suPV;8JqNv&Ye1bYT##cvy*x!yD_B~aV^;pn?Dnc489 zV|mfE{EcO=&Y^z*@*&&J@)%!XWVL;?}O_5Y^_J zJ=%7&&w#dF<0mXMpf_29LDNVa>xUlssA zC4At)1`c}32;1}at{|gXr{`+|9R6fOq957TJELO^qvr7MC|qqkois3NU=bhn*wUlb zP@hj2J&a~FIMNttp*1_JQ;01IsAy4HUvo?q^2*vNwk@t}dO`cb`U+fw%kLoHggyJ0 ztu$^p%`h2#CK4~#FFA~)8&v`8pzZ4Xh&Q;lH3*ZsAGG*ntK42kiI=2<#G(|K_>44u zeIIfFo5{%-&^>)XW0>yuuJQ|&@#eQ=)?6P`fpd`lAsTE`npcvP{h~#0zZELHK(q76 zN3rO9uhfv91J(lHo%7s+F3ZX$UOW}&%R5wH8d~PI&ozsjPmhzhC2+`$hB9O&RahMU zz^(%)Z4$Yd5|?V^%BFp}j}%MfsW{)o!uLfRiH@RZlCM|vhFV` zhYLx-w3E6ANp8;?eh!aulLcUWkW!DBD)68-=C(R?O}iEpqwF_mJ3j1KX*UGNHfD&B zYbLHAU@FA?3Hj^VjXyo~_v@}~S;oP#bg~erko%feBsS*bNC8=>Ed^(&ExeE;Y+3*N zUqMAyVSycCw6Q{#5cG^kqhRySAk(b@MiWY$66%fW?DLN#MNjPL({`hwGCfANFD*@g zCX8JbCCmcHvZlD9mVTwRZc#nP3kukEvrby_J}WW*=pZiKhRwkipNA3DuU}-0dNY+#z9ws_K35#_es}V};SVQa(jj~P9>nyW8Qm$b%j8GHW#jgmtBJGUL2+4-9T&g2#4L+#e_h$ZdYus{drA26 zuXAugYZ2!3H(UqQ0cz&LY7B#B^~&0#0(muVn@Q7)@% zuAh#*D|=Mr{wctgaokW!!owwS)o^d8_QTPh!@hWa#H+kSK4-BlRoe2rgmu?=AxGez zs=Q(ML+aF2E(`vOGSz0JWcZDJpakP|MyfT%w(}`JcWI|ipNlDKO8@*7VUf1(X}#L9 zWBMj1i~RN|$Nd;;*#YBDwk7kD<74JTv1E|f3-0>sGqCM*3)=W)3p4$Um-)$P-uCEL z+RkB3{F3Na`sEJM4&lUQj*~A|0+!bSjkHWrfmG@DcwPf-|NZ!Tv8HpkdZTo4Fe2Q`cnf|2sW&8&3SvppYi7aKUAssB2jy{GjEIMSKSAexyr>G z;<<8+H}zX9K-a+ZgiNr4hsDWGUe2c2P)WHWLqo;8=iO7vrMFql_DbZbPKOeRtVqte zcLJ`*?L^{w>?6|4(}M9UY(DJ#V=`UjT(MS$`^pg zWl6~sPiKim#8)2f`3Jz$M>#?Fp3T?Bx^@S{$JTGg`K#lnC)WqtldCJ#t5)uL-3~>W!=Z_7%^&+{{Xk}z&r(geK{!*}OI3^g?&-l>&nwY4s~iz&vtjc%Yn z2bJePMnNt~;`1qyuBElPIGz={Qm%Get_>s!%cB(?zM-u6$-Ts*iuRVBV}A^2SC>mb zcTD!e+PMaeLPvcHrcg_|YVwY*TJmX4HN-1w-29iKaHJn%xwo`}f5~WY42h|KLAcb0b?b8*nJ$mi*bV!#$@Ay9m~yLVlLZwUbgM$a%8d0RK93 zZ7DrziOCGtoLoWLS&CV!^thRkZIe%($yzc0{4VhhzyK$~)@B>x7|D_uNVI;Hvq7QA zS#HP}#_t6?8LYTms=1B%jRqc4o!X92<(_gI3ny1b6o$FfF#gNS%aarTmAK1Hn-P6m z1H9jPf8dDFNhX{IeMdHXiW!Uf1?xLP!O;Vk8(L-!m5N8mm;|3z?n4gM4wO-;)Um)> zsV%HZYbt)9j!|0B=&JqBEw9h-wCd8}u(cV@TpesMnIcQ)q4u@VxlF2@h#Hmfn7NX+ zXUBdl1pW)lh6Bpdk3yTRXq4Tc1E?xzD3moSA02>F{LVslZ%dX?u0Sz^G*bdklW=b0 zS6FN0v*a89&KJUJQ&tpr7*r)%OfBRf37_HvPpTzP@^fhv0EgP$eWh!1RUwP_dkmlL znlpn;6Cn-f=*5est29^wrD!UL&hk#KG9rW_S{X~eJ!tOKLaQZ~WAxWpLn7wzQ#{O@ z-7Z#|!pUvWLC8qqsK|W%pcc{efbQZS+C15960S^05^m#D_%k zHkIxSGl}9;>QTthaENEALCwce3R4RW?GbcSU3l6>1ZXc zNsKI=%6ThGOd)Sf_=ftS3gVPq1>aa!WYrlPGHQ3>Jm)PyBIge>*;FiAJwUyM{_vO| ziOC$nZ0QXUne9p(PswbZFHQx|%`9*ljccwNKC_f;k-{p*m-X>|LckAdCubQdcocXw zB{@OsNYP3MJq?~{Gs_~ss!F@)vmeL}rmbN$qL9W3_4N!k- zSX69AJctjOIjJ7O!}1Ucl3PZ@E^rK?rIH9v1UE_|5naP2*TG#_E9QY#o}%7~q6TNt z#6WvaQRf0uB%m(g*El>UTu!Qp+Dg7kY&}?Un10?nRSk=n74>hb6Ci!#HUxPNpmYsX zldif0dKLCAw4wB2!=~ee&DOzu;{d!mQBEuRrgE&U=fOo3w2wNQ2h|lw(N%w*NQL>8 z;Ur4?V7?UE1;3(Nj`0J=T0d*E;9A!0!hc~|0A=HhHb)l4e5&(fMT~yp5T>0`lPzg0 z8rB89$^80Gt$^lw0FahN*wWT2d;4cRZ2-VOD8WzmBgri(!k{FGv|e;Saxs)*7iPa8Z|-lF zf>yPhi*Ed5C83N(zNu7&IbL$t3ur{_E$kL6Ypn`Qmec^@71T^AkpH{06`$L+g-p9M3(4xWQVEnpE#E4_>9I*<#RMo^K#+GqE=^Ql z;V>S*SZK1Cigq3$i9v+c_AUir_GvJr(O7_yv8xoKExWy2ds}s!xf0t zUEF*$5g>usGGUa7TDKmQF)-~6yvVJ@arZTBe&VPaH&Sxlu7Co^wgn!QUpz-kiOtYt zR;e)J{Jt=CB3nFc!b=u{JweNjUMTqWrQ4_2*Z!+zX~W0n`02={a?2&>vyXXl8b+sq z==m~Yv7%i?H6harrpO4c7mSDSBd!Z<0oTWrkLTuapSM*bn zI5;#s`LUBSCsAW$0x*dpgYW?Ou`e8*!vwxTL)+>tUq{Z#9tfwhNr6T2Iv0el%H8>viAd!(Sid2HTyMp)r zFpt%PJj=^F&lJhCSAAm3qNu_|;v^P-hLS6~#jl;lak<}6ceI4QOV!$@JsW5#iYgz5 zGb0n}>=2IXifw34BZp;Z{SfX5w9gOmPqN+#55Y%ko6r^2m+$wp6|y;QF9qSgl3MNg z1$$$}nmVhkKO}M7JdU0is+A-c`r5MJU#ldqJa6Az3zH{dllP~cN+vrE2w%nwSQ8D7 zf*G70B8E+zuR9m}ON!DH$n339CJ#qt+3z3;%S(x!pyf#kt$np>1}f-?B8C)L82-DjGtqZ)G!4 z9uclU%M8k%;fkRfHS|K#{ve1A@01=<={IH!Kn|u{z9Wv0R^0eWQe`>i=(WFc*S{l6 z*78P*`&k)$No{f&P9nF@;G()%@pXk)B~VkO z_@=|uJXBdmfv-O`&b%qZ>cSt5!75Qy++Iq&bru)x(Pzt9y8*B29~Uu2W75@!zKEN1 zzs!e6=B9r>VS5~g_gndLnoDyAuWt7qEyM$B)-PTa5z1r_DZnFi9kRQN zi~7wVVqD3%zz&`5ZhmKGuFk2lROb-i??;pU{wpGia<4Fh_AYNOw5UG+Y{TituM3R^PJk}tz8HIz8_uPNq17I z9wbPxS#2_KU+&?FKZ!jSlU^MIjDt8S6F(!cHrASC3uH_YRToqCINI)44{< zQ6%|vb@Hw*rqjq9zjWgUyQy%3>s8_|sU?UJZlB5<~PwyuXAX6ng(GG&{aX2QNa zsxj9fMal^@&kI!$u>R;sJb#1+Qz)Nt4LI%#>4UiJdb-iYehx5bpLlJ2lK~ z2_YI7!wWOaY}x)uI~93NP7h^tpx4}SewYyAf0#zVn zmm9qAfV$q!YBsmNuWTY)+>8^SfdlMTm5O(6KNNDca!O={RfJ^*HVADHYE^{0!%9TB zNq6-FH>rF;;sG-iWdq+NCdd0WavXroO#tn+m?D)ry4BZK(v;E=Ys zNjXSH7&WSMe;{s>?>r!~N~u%rV7K8)9w*XE^mR)-#|M#-cnS3l7X>1^<^*w(tTR&T z5bgXV%_OoX-rykI;hGIad}<4#lYAzEz>BkM4`P#Q$rz=LXHS@lKW+s%cDUD~}%}$BqqQ*&V@iDI+ z5FshFPF99N)J#YOj_fzCPd~|$47EbLIFScGkw+5>?-mPXr>(GlAv>~7 zI;IZMMA!A$!C9+Je>3W3OEOnaofHARTSVOw(81|`p%LehT~_F&Or4SAyqHzPlW-nu z0yXC!B%$d;bmW0*)FgM1p@S4?Dtz{W`J#em`}s&51yX~Nz^h|lkL%tyyx-Q-Q&rh_ zsNE-hmG8x=i<{1&j@^wdZ6_pQIbB!zNIwBJ7-eJ}(-C`t>x`V|MJr@^gX6>_F!2J} zQ&M84J)8vpus47J_2`lH$bJCq(0*uZ<1L2L`*8z6|BA6~)dyy^<y4!#aoilLBFdhYwtsm~cgK$RJ#CYTU#lUM(%ZlNu zi5tpR3j&#}1IUg(oEq69#BV+%1|2l>W#k8|Pt-lZp9*SI=FdSFA$EVJ=E}I%L_c4= zLB&AbfP@%e6yL$94FmzTp_o4m9kZdcelW`bJj+A8lw8@4wzVK{zN& zrrRdA!xO4zWn$;es*v?StU)n>>!a@Ld*xEK+oz{{R0YB&=Ib7`Hg0A4V>^drtwKJ} z#GaIi(6$a7f)$A_&OQKWQm+^&Tui3N5WfKZ&_IxIQ}iUW&Xba=d#G(ixXUH}uf^xeNG-4gb(Mf7IN6eRQIghYHO~2cjWHi zM*G`(b(~4N;81d(8&EG(TZ0D$9YL>Ft|t(lWeH*h1#lqi<)bqDX&E>Ml}bybly-BS z(7)O3ioZBTij@c)sOXZ}b#$jd5}5?S?QcrLn!_#4-!1#Ar_)2B${31?Mgv# z{|U1FPK^Igd)o|5?DYRjlPlN{Ek;2Jz#k#s%rk zbBi-W39=IF$>T#oYy*)rGzh{YQEAdp&g12%c28rcWjmT2PNXw2nVL)q`oSa0wLwT(SRNi7Zna(YY6|MX=j|*e z)5Kq%so8!z*Zt{D&)R6W#tyN3B*5_mmQ#%5&r~-TZz}@f^`QucK9(w5%r#u|tr-Zy zNrGBR8tJUfBiBAll@K?fH)Cr0xO>P)c4$2QQ&=OfCP+S|g*vdt>e7CBUZ%$EDOG?5CO zvVY%OAc*qv^>G<9ooq_54f}SkZ>B9}i>|@hj!tmqc=#;O>QjnoB1zisaDXDEE59#| zw5@cV{9^lMm?ohF|%b{G2k^*HbDXc zg7g*8`1FD)D9be%ZxM{6mxP<{Mqvx#(YD1xO((6~9l_&$wugWHQUw#(qh3ZMwL9MD zlQ*4vkx@=0UgIB8*mLQ#^Cjr~Nmk1=*fMF*Y0F5OxR%~oh-%+lzdYQOLV=4R_>;yP zfy_`#leGk^6Q5|Bck z#UpW01q1JF;`j#jE&kG29f6cY5+e!?Ax&|#N3Ib{M!dIjak5bYV|E>Z)sY2G5mV?Z zq8B5^^Z?tPr>UoC`|D`?wCDoWLaktL$qS*&t}l8|r^_#Cm6*7*>!P&G??r8UYffXR zN6mOnL7TqTq~ADsyz^pIqw}uuq3t96n?DOr`QIV4uD(s)-JqtiW7zFaEWG9ot$>cwV7^Xzf!A^qZROl zC|^Rn7+`NKZ@F%<6-L+g$_Q{6Pbh%NI7FrrmVCmfZtwa~zS~pop&gRhF94h&+@SsY zaQKFs9DY2IR3v122xIkV`j(?E>~L)UIp?75F~0&jDto-)s?+HOL*(0VjKa?S92f^& z>lg_?dpaa>_1GB1Ml;FpR3==4qV=-)0Nv&NdFyky^O=qZ9-3m0!X$R|9q?XmK*+z6 z9s#?}*<;y7yGW)J(41hbh+|*weTDRsYNwCuh=!JRseh-nx9alh(8n7tZ;(i?+>( zZHmv0vv$AC4l`V~Ts$-cAU<=VWUxm)U?^qL8)i?V_VoxFopkwOW>);PKLLmJ z#hqwPjCbfg;stJHczMpA7S?g`{P5Sd9)lecE*Zj|cPn=u_Px0s`!oEh)0NE4_sJ%M z?Ok?@D|AcWyuiTp7{|wM|9b9fWHfpC(svk9*BlF2p|&?Kb+%*dF1?;A`ABJ&3aq!f zkcVqd3N+6U9u!W!gvdCKZj@QJG=0>sBSTVvDri=PyknVrz)=w^`FX%3|7N%ET1x}hHlJ`eP5;x@P zgygc_6WqOQ^IUDMGoJ9>@Xd80lF3grYIs1K#CAa9@col=aej_jH$vmFRax59&7*2xRnWfDavtd!wkA23ba}Taukwz*EzXNt0RBV*~GZfZr2;V36iYT z0r4^=id#8;P#=v$y}Yd06mp@*R_Ar+C4XpYq(*s1R=iEttL-lILG!S7y3&>bv8m~G zkTHQts$Aa@f&pBTpp<_4OO3*_obqF{wdKTDH6Kw=Fu6ow!j*-yXVGlBnT630S4y-a zaC`ZP!O&36q2^&#(t5Hfnsv%#ym4Wf)2g#6u?Zow7WuDkJtd9>xb2(6g?|ObJTx^{ zfx{F#EmtwT5JYJzExS8WE7QuwY$`3Z@9irF@Z%EVTj~0hM50#ya+f)8=TNF?-5(>! zwXs}F1{YRHJ-@QB(#T5n$Zv{f4Q|a@JGUaCz%O7(%-K?3UtS(5T;gQnoIw350=%8- zbQ&Zs6i!jSjQ58wZ~SE3Pj+QRB>aBV@%p3>Rh=b-QH*{3S;n$@Q;JfutQa)|`fRYC7NYo!KQQX~ujnyQ z=D=zeBTL42WvMh*=U3Z`BOGG4@Ww{7OEiHFI3p%zG=7J0{8lhM(UKJ*Tk`^13H7{0 zZ%8EBq0XcEv*=)*IM%eij@y)9a^$iQUt|J0wlI+?*J^(xwQ$bw9-JZ4s=5?gspPFM zA^)Q6*|T&9(|ZBj^?GX{E(L8M-smf2re=ItD!;W~5?g}kyfLSQajo(gh8tnsi3levxY0+NEKzyi*o+VSB?*mPsN{b6!T({+>@FD$P27>)dl0}#=tUWrvahz=`NGC%TS!FBSOg~zAW($E zB`GCskP`IP!Cq7f9p*cNXDiOY3M>8QI~mRtYbjHvZD&gcip#CClwv3?i~-x zk0DaUL~=U-on^oz?2OEEdyd>W!_ea8IY>~Ca#M>`?wK2Dlsfh0u(I2YyGUboRRqm; zbY0JvuiIFmSSG{UPPxQ&u92j!W(QKZHVot(T7l(O6AvVJSr zfN~1+I{xPVjN^OVH)8r;-9{b4ApJf6TLC$QLOAUY-;weO{%kII7DY8WSXW()y%{s2UE@^aytfABv+lSA@MT+C4<_H;C5_q-P){=(cF zt<26m(Uet$S%5L-SV3PM0|l%pvf=lqxkF>d8KCmBl@glc1x@4Fsr%DIJivl;kC1;X zA0kK*2kYg;&#|!AQ#j`dOBv*JrY;M1ytgQT&pE*i2{g+&BCP*-ENK2J2)S9V1=dO~ zicxe3ZxYeh=A8Bk4gZ^Qu#bv#umW6~dk^CWC?pBxPRbCJ6dSFXhbBI|%-61<@x6eneRDxAL#6WbYH5KLc;i!$(0vU8f=r-q zuAm-j1_eD72m|;g5tQhR>2|sC@N7o;YjNIfxj*O2J_3NS8OH@M~FzUz=ya@WKoci_{6Uos= zV7`^p(UZri`k~OxBY*5~3U@zlJ#RJAHzlI2xEh^sjPFt~K3HepNU+$Y6~GHIC7vq@e<4Q9P9xx?Yq=OVOAQ9m zCKXr&ONM-Ap!6+50lpJS?Z2u4Q-p4K;6jY9DR~Zmkb}0!`S4h&TrK%FR+C^6Ug2O= znW73gd@b&vy%4R!P_30&xm;jY{mPX6d7Z_GXevRkg(F*%+-UcY5^yOb>_`W~hf<&f zxbj%15iv<3j;@y&0+EFNuM#kX_+LuEWx8Hm2)n*m;^PE-YJpB!U3`HI;BRFqL=_iR z?=WHqRWH?RP$iPQ2OB6rnzP#VUo=#3MD>Af{>Lar8@T;NE)u+}4g3f0FTDxZ87ReBs6o9?YI4D5 z6q3^#J&hT?;tRYTS9r7uwE9L*og zm!&0r1;=I7Wy%iff^M({Mdc}A!NZz}E_XHYq4aP%xUzS4QK9{u9hsp}`HH#=zmg)~M-qG4jwr0#^(e=&iD9g#2s(Qot>Niv|1`0U&|Jk$Nb8 zko9N)@FHw$18mp)@C^C@oTIsikk0%=LLVVdMK5l(L{Tj?YuFdpbB?BikhTG6B|0Uo z?&pvvpyvfh40)YxC#koIoGZ6XL!Su(4u#+!zH=WH8c()r_!8^r+8vB&TaD{=^XLdb1Dc6tcD5%t^MQ|sw#ynoJL=c_H z`>y+9Yt%IuGy~a;WZ)K7PGG)oV_cgg8n(3m%Zx39G=p(gX<4|5)hIP#S-4&AE+gzr z8MGr6Ad@MV0La9sCmh<17sw?B){)8Y#^lGSUmi4jioYw-ax%aM2K=JEVY@kQGY)2_ zlvELZv{|j0Xn%TQlhQ41P+dTS)0eOvh3*N(3{n{Zc$^#WKoPE|pewkz~g0j|Z4PjUE4 z%qA@*YM%pl+>zilAWWn3ZwhyaU>`La!+^KxY?3r%+hO&U@dbqNXNmA#MPCx zK#HoujJP@D=2dFkMKvVG8Ux}VZa)LvbflzeF`!mw?kr3JDa=m*ytzPTWMp9uxdgV{ z32}|9o}Mi&amN|Z8{}lRx1)dFwW7tElgWxMR_i_>B<6cp;@E04-|$ujzg@lcPn|*O zEb-1y^+l-7e+m&>=!*(LMH;0fSRjfBTG>!+YG5%AuDN5`UpVxOvSB+4bz^00#GQqd zCXWpE&E9dD0E0$vOU}v6Va<6I&nmMn=<3vX(P$xR@~yb*?PiE@G|^dk`m(&EoR+)n zOI_MrQX+K>A#Azyn|XKQkX$(Q3y>s#={ferO<6Yc-k-wnJGAegarRbCOUxjkkC6%zCw(IZ=I`yYm#VG>Kb!!(T<_(Q(6mM5~Ehk4NEhWB@-thB+>;;CS0cGc$}s%jbMem<_-^^f}t| z(?|6UmfDo>%jNmDk?K+c%(0}Y{Q(s9F-?^u0PiQhpPZ-CTjxohu8p8)JrEC;&%5F2 zffJud3mzMV5-CX=wF%aEhPG9LRaiBIzD{l8fE^*yG$|?KZYji$c^ImKa?h;Zr5ger zIJxAz5qKf{dGC9fNJp~%Y4>2F@yAi#mDj3nu-)7ZoTb`;P=fVbdw5(#7b*YZ&Tz$@ zR-}rV^h`54YZHhaKSzO8r?f^hx**P6Mi+$ z2O~x&Mn%nxL^P)QdtmHqgN_-;d1A=B3bR2U(nd%|LJlw&X0|^g3JDR48EVZ4LoX>o zF?dLuKtdHOc4C(4n7bvC)@7+-y^N1m&wlN+Re5eiSgw9Oj2}YRjxI-9VI?(rYo~@sV8XdOH;NPcr(x1TeqmOl_@xc zuh}qXNM91KWQ)(C%jl=|x{wLzB4$YdvOwg2h$CjnVybBwa$=@Na}Q(IgA2r7;Hp$7oNbVN6s1G`~@+32<#}?BLO^t$=YjkZ9vQ% zHh?mTF;j?u&2!1%Udo@CN3RE7NDtedc*>L}8`~)yNHiw#k=z*&xrZ9l@w|Am#O0gZ zPp|%B>8sIh^6(pu%cf}~=>~Fpr_a*XqQxE0itEU7tq=HBm-{+k@rf~6xjjP5FXKLu zdNyC$wM3m-Cq~AVQ-dxu@uId>(NZvB+-AH20)iEdVKKPsFvb3ioi%v;2tC`;9$Jh$ z9=k2{U1Y}3Zbp^XEnKIrxTxfmmk}v~_FNJYRhs6uU=fKUi_+G(KNgbey8CB= zT({BJd-+l0TZ8uWTnj}vHx^IcMXZZ*Z|)S1IRcOwy- zz&?L;%7-@uzb;bTM{(}@tO;FsAEZRi)#8_J2}M81;uSy85X#8^O1$B+;Q>M9%<;@} z{LOPzHla9T)p?L`f5fcBGto15L}dDWE{rcRVui4wS*=ZA=gwUBeawnisTS9X678@j zrZ?MHnL(iPc>eZ@Va*`00n_?8<~>QEUTR>+$E)E+TU zb(FA?F-yn^3x&)pmPcdc^Hml{j50ALQ#C+S(=T8V>77?3pRW&LU`d(FZwyXes3r*k zE=ZoKCaBMaZ{jV@W`BG+fKi)dvLxfy?Qp-JPIvDLcMuhw8aPYUggz7WmO8CJoFdoa zpt`yF75f`n&FRD0@vIW=W9iFBR;#(D&n~CeV0$&SYdQ^sR$j%y*m+^7_0<~E`4853 zlv}1G>$24iVmO(9lJG=ijBtDMKq9Truh>I+ z6CEyV!JMA8`JIB*aFp_;3f=ZY;=QFQ3%mvz*!6Sud#Foq>u?WfTcs`7ORk2m@cwyA z)>-Tr1?fVT5ELqC!VKZ&pu--_ehNrhqS^}=p&YEK76E6W20Z(I^C#a@2L5SVj~CjP zIY=PYinyA?z&BXB_GHqldO*Gli3+;w*H5Vnx(}`N+q*!X3vWl;@b^pbIXUCC;!bEv zX#&)Qe0KFIgI#ICtkDj~yl_vayzqUK=x(K={0Km^Ilq25i<;-{sBt#`?(?DEj8@NT zeq87kNpRcY33M?;@#F+^CiEykTopA!+u`*Q!wm~d*mU*4`ZW*)v>@%cBfx4X+I^4@ zz&Pg{QY3q=`%npA-eEMO-Llb~u{AcYHYrbauBmlqXxJ!%EpuC;o5{?T)aG}e$`C@Y z&tX1G5w;l|%?C(Z&ap!BMDWYWKL42>ChBplNqesje!2$JU43#~mq8&J?r6C30zQH0 zHXpl&!bqTQjCb5u@94%*GU4cOKkA3&v=i(s17XlYuc5*}cwkS~sR)GA)XeW{9- zE0WZB4-z%}Q6xp-f?j-C{GToDV4XH<6y>MYZjg;nKo(f1ms`R@&KYmc#bpV5_?iPAG>|RB|-Y*--I5p5&n` zrt5f|$seOA)40>LA^Sp$Vp1~w&#Cu6#5@}d>%VK!ng4Hzd4>PKEuJ&~4XyuMkj?TR zhLe<#loA?Cg(&sCn)ge(h|pL9~pL-H6l)R7h551)!xXsjIP_fT;2 zp9jtDI$LWe#hh=>#y3{!(0eAL#EeIooj$qbD!pyM!i1KZX8yH#ZuVIKc(slS`ouVQw9U>>xz*atMUXq?#n;8BTU37Oa@l&6?RlwTqXG^uSW%dKU0C5{ zVe%MZDwabETs6G9$^mtjvB=wC#zXLqU*R~Vv55Fn-ErdzoBLwsvsc6!>L}!w;d>Bx7~H0fg_R8f zKW|^tT|=k&=z4L}7KA4RgAIQgOwg1yyK!6h=L$=AoMav$$yD3Za&@OX#jsE6;DX-qho(C0~AgbV*1!X;}#w zscve+KidT98bbAs-#F62SAN6CmxqKA=Or<`beeC(J3}@Yqn}mu5l7OS*sgo_enNabZ3o*JBX%5*nAroU42k>`GG z2O2qqcrBIesNC|p`wNe9dFK(J%%0GGH$-G*6WM%WsCJ=x4yZk&I;1x7r#~RE#j^cq zZyBEd;9mA(S5o1N>66aZr+DGs~aSg-f!2)0SY;eb;)#QFW$6y&kCO z2v-(J1m2mA=Q`@tbQbLa6t=HZxD7>4b`f@f;a%O8JVD6S0yoNh>w4R=&5-M|&qoG( zNnLw}*YoA_73f62dIT?(+W968ovxo;ljRmfq#V+=^+%0R7cRb==!p+_tHpf8-VebD zGb*}dPeAAds)t<~C@qGNgC6lL)G*@0h>BNkZ&H9214yXWnEY{KfEj9>83*`eWbX$r z0Byem=3BD=@R3LBN(_Q2KHruxAyjNz>s6^ zufRnntSKvw{;vyV9sIlbL#3aS5+mFij&(Qthl#_o5xRJApk182q}L**7Tb4I4E~0O zyuTh-(0wppn9r0hm9|TaW9oZ;3|#b>!rQ{wc6sta|C#mrh~V342O^L1MGyt!b*t9h z{&-RZY`n5@bNy=PmdQ^$@lrcK-uyam@A@1}yYyMIqG@vV`@N#SMMVK{F?er3xEd)@ zHi!d1tC)?4gpc!Q834y~0sz^yj;uv&O{u-aJXSmr#uN66qfzYKFV}=d;Lnjo&R!F@ z>+`n>+Op}0y2;?JDnol+*2l>&;erUnEp>M?(Zvf8@oH{XaW)c&i_sPNe7QrY=IV!a zs0S@RJ{Q)&_LV*nnaYjiO(2`}6uSx*;?4j^62h}loDSQn|4 zr78Yz{f{^Ps6vd$48f& z%XUS;qO)EfG&5I?^TEeP|HgHk<~1Oe=KdP3NZW&yon@&To8ESet9#OGHh?YQlFaRC z8C^De7#Vo;fS60A)d*4})4lwJp8VZ&enMjDy8~kBM2BZ{(4VQC=UQ5cIHkQ#i}wjV z--Ts06Mx0H1hs6wG5px5y@(r1J0f@7+P6aY05_s$p|6;aQ2g&hYsa8H*8ZqRFs#+^s~iM>I(@Nh8Iyi^kmiC+JTh2GitZ1B96h9eBBo zjg7fEK`reh#w2kdZ!N&JLej_qMyIs)X z<_^Z8dP1&DMK*3(hAKy6en-Xq7!_>}qr2khz;1pY#NMzuh~e`joa$&>9RGYawR2({xmbt%#H*^#R6sd6tFgt>onLe@RUQp14uvW> zVZ)W{NuQ2}g+xOYVIp6oK%EcIxBv^0k-I?ih+YXBRSK9g_(Y z6j2G&`83#f*3zERCesZCMHPrZvvGF$zC9&LZI5dLcr3Dc!(*mc- z5-21WVQaoup&ix|MC^DB{ThKn3&oGd-qN%H={_BAuH5xMwAQeyH6Q3wKG%CcC?c5H z38T!&v-9l~tlyg}2mla(<7Y@P7z9d%jTOQ$Drvh6M7Zi1YsIu_XqOvr@%>dN%>8`U)^W;E4y)@Hahf@`H&X~Tl(Vd7S_R0xc22eN^&5?f<`5c7m~_$ zC=TYzLxv&3S>&Ar9cD<(`cyvAF_C*p8CK9McHg~#0dUk2sE4H8-p#y3i;RdiVU5_! zivqZOoN6H73&AkI|HxpGhp0n4W-o+=geWBtBs|N|4PkDiNzX#@^5>Y`Vf}}CUV7*H zh0-)9=7KE2FrrD*5F`~5w~YRf-ZhlmY~M>8zy+EbD8I0b;r1M|Frtob8h>|`&WCD@ zkRtXSW&;|VG_dz?rMAw5SFCGMw-&l9$~o3dqGvNUMiasfBOn%fDJIDyEQV0|6hj?{ zz!qG>aN0@L!)!Du%^jQApi6wCO$*JzC92UTe@f+4pKS{bIYyDw6ghmZ@s+LjTtoC_ zUSwewrGM!(q6ztJXN>*U#jcN-(0!QB@%bT_?ifUkqRv)tHF6CGSwcc zq|1Me?Kk&=_7Y}2gQE3{$Q3044vvL+m8^nnSPJ&$*I)|Zv4h5tu_A)d4)fUAT)Y+# zu_X^64PE%Ewz&_85#*(Q8(Q`kThQE$2+!UYTrn0 zA>M

-Hbh6OCSgu7-wQpu=evtP~N}iK>w_c8mAE+DkGvn@9)2&v#2w)n(R?PhXus zsVZm~xQuBqg8l1o97tqAQlh#HMMek09*fRoj8!`1BoKwlr@OX%8RDQv)5X@#LU2p6uJ4Y^4W2JSUOya)grnFo(! zT-PBD%g~F2P|e6)1O`CLV(!&PIyZf&Bu$g3)@hpMsFA?`NGhu8hy#l=i|V>Z+D2;I zbU(tTi{gGF-AnQ*qQ_BV6lN6v#mY!9Y6T1K^?Pm zc0{F*TR**r3}OY43=-ELDXiO^{y?8qab|oFS5*gIpBO}5I1y|+02T}A1+C?Daug>8 zuTRAliu<5kaAi!T>!sOeyMCHNtIcoE#HHKoc^a&{8dZic4dzwj?N%fQ&r-8W_hnl< z&pwfDooBbLITsH-ey(?^Z>b%1-TU(nY3nZMV@KqJ!22#z*<@Uv^wxfhH11}Vzj@fI z?yh6BpX?QSaW-tl$`0POGPC~HhK)ed9N0Ki!c>sdW;7rxvWaLTLf9Uj5^fhh3@M9l zmAz_L?1OpPXVsOp)R$yQ73ZbF${z*sqI}a)h!LGXBkYH96oQ41@ltd}J> zX(KX8BP`U&JUl{x6m(Zb=5aP`w6|SBc7c>`-b4Rpclp%hK7zb%UO+G^O+HRO4_eV1 z9{F;CZ{~rv6DgpFNU7sh*QT{AvqBw|n~nb~^^*Wv&_a0RPwa`W=L5z^_K{98cN>26 z;HutyOuip#j}mDBX}&M?cPvFASP2e*ARA$)9`sSsrP3h`ytj*{luPcq+gsm?++F=v z{ryX`%Q_;j?3PNIR4q_}SFToJ4r>7iEtt`}GUi3emnJC@6QMW1TnLn8rz7JVPTYeS ze#il6+VmP;1uM@Fg^i{Kk2bg4Y)xrDK+*OsI+H6>vYIB|IVKr7`Z4B#sgaK&5fZnS zV@5(eV+}t`2c^;3;mYs6AL~>4P_Xz2_MWA2{niP)`4iV(3W!8D!L9L}F-!+kBh}vp z!$=3zX^ed%Ya(skbf|%i7OwZ;gzBwD*2bUm*o*X|v{xzAu5_XdSVB1fy0pF%$c-S^ z)z|$zbl%sa%3Ql?`!uAR*XLohpxS5YOZT?I{oz4Px!TL{MHv0#xc=-)u1gTB@*w8M zD5W~1@#X~VQWl$!*3`+Qi;3au_MkQyIb~N;Q@rbtOpi4~+9XosTkK02Xw(pS4ClsJ zIlr5SX)><}YszKlkQ^NWM{Mzd44&?XSB=;1PYKt85^`L=qyM}m>D}da{m%5=*57RO ztGXz?ys}5Z!%vNeq){UMcq1uG=*@j3>dS@Z2!DDBV2S_y@CLN43TH3OO(MC`NEli^ zHK6{9y|+_sPlt>>;v_~-=L%j3i*V}z77_rs<|`2E`2+t+IM7G@OZ+R-#}5Afs{6uc zB(ue0x;XPJQi9_c~o&!LYv}Xr+ zIsOL=L|poh+6zA}02KY4jSR=$3j5M0?ujt+3;XhvDwSwFRLY4?7YaVkIZ%}@H6-3vwINq)qg#9nA7`AwwlnpBuV(h=K;~)u7M$d>pPCl|oyO9jemUVm#u+ZNfxrB>=`%_7M2+`!wAv-l`Pj8Mv})e`#9V zT@@`A!+3~eq?!Bn_eLcM-Twae_u|4$YmE_h7)GM8y6q&PVJjlj4#{%IT^aUT zN!a#X{KIYR@Tb^G%O@qwI-QwVo}VwMe$Bag22ofxTyPCA%RfwlkEon7ls|2>KfE z2f;9q*u5>QwTF@8N!I;+f&$(xTaNDJ*8AA5=amb$DW(GB1&U-SQnVt$ZLi=g3tX*T z$BYrY`-~C71ZX)mp`3x^F;#nMkB4u7%vO-EZ9m&9J31`UwQZUX1fGLX`{qv9F7Ax= z^;6X=Enl*~277sM=?vnxvOxn1_PU)XVMa7;<_aMIKr3M8yK1?0HDl2|d?70?&)HK? zm4JWW0>jfj`r@c#SRuQbh&t@}t0p9d177zW-ga?<3$J#~(9Nx0(p zX;B#1za`^m+<^hEtcyu(>>kbx*7Lx|5cfJ)OCn5`*L}OPxXV=6@86nyy(n4E-M0cm zeU_;-yItJ&J`-eoyQNg-(D+fpzoHo;V2F_OF&BX8Kn>6f*i}}D5Z+RNm*akvAmV5I zsP$3;%#wbFV}PW0rHdT|$V=*|fw^GwAinH)9^7l`(G87xbO{n&@sS+fSE)#-<0^B@T2h)RX-RfVS5v3(z~ z9c(GQ~Km<`}_-v_4SmPnxs{3(N{cae%8 zAET`kc6PE56@flWO$UrYt{5MyjIFoVTvNviNflGyhSqsaTmYdZS{sf?XIHY!Y^Vv6 z?293Eq-Q-_kcrJRd}4NcCk?1)MYJQ)}}f#KNy^| zDdY0eL-5I`buikUqNbcT633$)T)etgx#RBbL4?jATlw z>`z%ClkZ@@*8WhMwmp~C!0t|95dsjXFtvrL zz@Z4OD)oeu-8nt*gL2q~5(OAcG!t&U(vyhU*YU-nbs$G#X%4f2H1&Meny)xto8C>r zfmAB?p5UwbN5QMc4pwq{Hwy3fq&(bN&MBYE`D*i9*@trfVJ=D=7z^fac-h&uN`50) z74o(vP=pPDa98q#i98Rch`Ojp3H&K8WHW_a9se>&X?c7#e@j|E5yCB2-#BuE7MJ3k0FBse5QsNTzd3vMdDrZjf9F{%S4KugWd4bK*2;)0uY2qKL`M;j=_?P637Cy=I$0rXEb`1R z)vORjszFZ3&OxI}6|`Z!(ekaIX11Zp=%OJj&lglG3t#Pmi z5J*b}d49(75=$}f++ARA~UkH zZbD1{0UFt6*?B?xY?slsMTl?v1hcua(kP1V_8B!I(VEewd{nJ5agm34D18sV8f3p4 zbh_YMr zc;hIRVYRk(9Get%0voV1?XU&>eeF7#L)*cUk#z)g1pzv zy=83k@VYsM`2k5_5c)aBmFBd@Io^JtpREsb^K0a zl@f(VN3%)}ah7avtQaTVnGwH9rPt-S%4>Y@Q!e@@47U7{9cg@WKAc~3TGS&U>9~=P z3WVzN`hkZp)LYN#Ti?BVti|#BiLUc<&mY@9m=M}Hwh!0yHIja{Zupb_F()w1dCfz* zTu1bW`DqHnrvPp)Ad3n}1*~?C2HgV!=>hlin`h;dfiivET|${eIG-lLAxzKR<`duq zD18}V<@89UNKrO{LpnOk+9<5>ZNp)u-RL|VF?ryyR;?Dv(;wJ{{Ak$(t+?!j@vi^k zvRkOf)(eVtx?kr zrg`HJ3@1h_4=6@YXP{{jL{DL=Y}&}5fbyuJ7lBe&3EwaXXRWV|p!9Mt$x%?_gWw@y zw<9Cl=3Rm3r)Z74$?&Hsyn;3!8#5xEjla-I6wkeNRfYUdC3OhS2GEKn{fPe<#Vca< z9bXijz1Q|rj~O2JLk|b{@EgOD#^KH0E2$GTXL`*b1n%;#M~d6Dy=5+RqZ7`v#Oqxcui;rYyk>RC=tVad zj*^@80H`_vp>6=Is8d*{Eyktx)POtmRkt24-Bb4|>DAn`U9J&|WRv*U7(SNSeO4A% zy~>j|{9F1!SFW6o8lFx&2(=~feARs2uD9-5PZ2$_1^98y1=KGhXjcUv$u2y4SlE-4 zCq~K5gOSGrs}N{zhrWX5DlR+}l`Ngxkw4Uz5!5C{pLSzQ?ExJ~z9FsT)?Al(jD&$` zg7`Fo+r)@GdNCawWP6d<*JiI%a4l`e9yM{KHtEcYg`#g3>YIYRh4PIXE?knM)6F-yR?7Va^*S=z#{D#p5N1v`nEb;QBW2 z(^sUA&e}JPpIV;2g>i3<>Lc4%M`H01V&cyC4Cu2o63W=&1#GwefN&JG!L6DFD58qp zg}#-fp52!4ZPRSeQio<*Vk2go-}mMd1uIieP5-#1e@%U3Ns9o~XdB zPsg(l-d>Vp@a*6urIZbRtz*QZjooM4lqw>)g8If*=kPofMcZuweD$i|yJ)JEHRV==25!h*vY9!cP~tU`_8CU=Bn0F__$Y z-|@H}p0m6zktG?;RZB}}DQi1Bkw2Ap%58OfErYBIyaK&`)Oc}L92P8TbJ1H&>cmEe ziHeZV?hxzID`C-hlVKp*>Oo6$yXA7tBgS1k`erfQIJbp7J!cT&4!ZL%$VE;?&~STB zj2xfo)~RsL2QqXX4#8sC$7M+0@N+UqiY|P)e+E5)`O!Ij)Fo+-&OOjzgFIKC<-Cea zFfr6*=rDM(J;ik@cUjyij68YYtR$Lg;rTc#e{Bq|F>khdp1fwr{#iV!;X@8aWDuK0 z)}}SUCM*cFsbq#+D~}>#D4;=0I6;_J%{AeLpTPL-GQY0`kLlwja?*d1LI?|M1&^SA zR3c3;e^wF-`YYs36gVXVJbW6FE<7BNIp7E4G22gZqSih&2$GfO2`;6d?U2JZh<~f;yp`F$J-PLDX ztKe9QhI>bW?(}0!(NGB{HJIQ^$pvI_h$70UK>qf=86UI$pEm)?k0>~Z9vEK~HIAUO z_3Q0F;XTrj&}>o7+}GLW7qwLLH9yXIK8wHZ;?|Paor>=&7|xQbS4z7W4G!;c7|I25 z#x+`U?RJ=IKPL)&-1a6r%C{q!c|IO3+A?x(AW!SB4L-(IzD$y(xvkV(8PbcPlBdL{ zCc{=NlYsde!kw|usX(;_kxWA9IZT~R8wC<@9@q3DaO%$C8+6Qfg*DCBeP6Jj-WWB199${wb!Vg-ObA6S!oUTK`ZH^!1#u+4{^N$Zw&Hv%*KdW7BXV&r8P1e*_ zDUV=DM;bRX-ZXS}J8y@P>Ss6ZDSp;Pu6H*rm!^k$B&w%8OXVvs4h~5dViZ|&eA{k; zgMA;*9*9)){qe9miG~p_0m|B4TVsGzq2@(W@Bgy(o4KKx%bC_1@NIP}bwG$>!i<|# z7bu_XoGF$9qf8a=zNfx_=}FSu?uu<}HwO&43*AoB9A{kJ49M&hb~O&u2|LQnC-j!MihK*p|-c zE!ul&Y7IZ}Xt!q>s#TpSy&fnfC<6@wUsJg7J@!D-u5ny*#DzW!Nqea~$%>F|1i@>3 zz9>LoI{0$3vS6WWb7wu1eQf$?K!4^sG+MbPNol|#uN?02Qz^1avMz1P>h zQiV-k`Yy!rdkqNmnyhVNF?|0K@clS*4nux2#{3mzBV0zNGGO4FIP<(LFFLtF?)2J> z+_tF_q-8||e|aRsG~d+R9UP={h8|cXOWiF+#=| z0R8mu$N|EX87trvZ;^Os^*K(VQJUShY^qbs-wW)6(O!>!eLC@worifpfLINH z57TvXA%KxGnS1P*TQ^9S&HbG$5g$lO8GA}`Q-d9#k9pyilM z|CH1Z053mrG&$B}=n{U%1wFi!sO6xkQ8~ATQOzSSG6d`h70T2Vn=9MsNsQtcP}-O{ z4lX9%>p4%1=UhzqG{d6hAoa6YPelM-g+e7$?tjlchjR0~YP;UAWADuD4aLnRZEWpj zyZaKwLQ-ikv7KYf@JNf@kDzC=Vb}4pf(;=^@2R(btm{cPTw|Ni#-@k%Bqa@VtDo7; zpnfN9CGF=*FoN&kzX=j?Ja%TP6aBgZlK|>g^wKH$OzgxxzP ziamqv1gX$vR}>i2sFBX>+AT@=Ixdd4LwU)9M{v&f7hOa~D)-559#y2zj!x~@x;- zO=O+XWn8r&5Z>IjeBez0Gv|su@bd+uwrv}dfAUh=6ZQ!kFkA6oMsjAi=KhTl5Jb2R zqm_G%cVhD3qT3%;@v^k|?p78}rUsW*cHm3NAtepXU`3+esIPdT zWaMa($%LSZE2pSRDwQd9uH+&LW6!YCh&vB8=&AJC11K&V8z^mH0SSoAg( z;Gn@#Iej%Vb>`At#nNoQS#2|*Bm%qsBqy(u==0>Oy(YL+449RDM8{7T$4`|c-~-_r zAXl3T)83sT1k52SNFD(nW9-+5x@_Z@gc|DAZ@+`p&uz15KmC-S6BERVGF=3>JMqVF z%r#=odAqC47(0JYDKGh&4pytUb%H>=Xn_BmSwD_y0$~N8B({@g`dIViOeY}iF3=u{ z&T|Y3qBH%Mby^`_*3GzzR?vGzuL0b#&Y8~Xl}$u1Q7ULkPPd%$z*oSEBt2uZ?mlY- zB_yT|ZSHMIGWca?_7ngjoY4?*3}Vb8Elrwwq7tow59nz(G}7jQj@Q>*&16!C#PHV- zV2v2%e+Fd#ZOfyhWoG)%x;&=;Q(c}i-`^jFq?x{vy`H0)jkSWFwF4Eujin(Zjj*Mj zi39$3+?245wIe^jjjIL~BP$&~6$1-1KHc|sCHQo-42)VdVvc&2W(IuLCYDC{-#9Iw zgMpE?BR(4~6C{nG(f>H0qNk&Sq!G}w6*V$5F?Gae{!V41QE)V}QpRWf?(+Bg!e*96 z^!W62O#h(EWBUK<@)(&}|2th?f>g{ZEqw6R3ktV2Tt)w$=eam|Sdh@}G+fdJ9C%n> zoH$~d?e^BvRl7CC1AsgBR3x4Z{mWx5M`T~T50rw-eFPhv0p7dSpODFngf3_U(dV_YYx2Co85XP zxylLiE*%r^^RKlE^5V?NjVb1SSuBBw{Pk+W2z#rZ=yzD^5#kntE`JMqAnU{;fUfIS zpsx6*Z5XvFcLnQ)!{o{vj=^@ntt1MsPLoK%CJ+O1+K)3d+V`T`QkxT<-Z_#lOvx|t zy_2hLW30o=?t2;*-B%WO)DNsGu`l(#6P@p@af$os*)BdG%Ucnjz)u)Yq6fUz-p>?U zq*s+;lvk)p#8(!~g!dUdy|1jrlCFS~X`N}Lye@&Mpik+u>8}2^o*_EQ!zS?_*n$l=nvI+aVmS+veAq;u@qJn!~dx)znK|eryOF{wxs5 z9@^I~2X+~Ty89&x9;El|L*@X=l^2JzXRL;a^&CT zfAweid;GoqW!3-9mH%!3@9pn(|6Tsuj`dq3`1c-v8TEf(kBRkv)crmG@B9Ak^G_W5 zzy3)6D~JB~as00w`adZGnf`l4AT28k^Z%jC$=Uwe>Pd{)1l>@Z=|DS#|l?gihC0 zocUir;7rp&l@?S=^}0BCJ&xXGQ)5Na0Tm0EICvMdo(t9GAG;Tvs;M?`1ZD*NO`p9w z4OERmR#e2@G|5oK)!(I!8=V(p(ECbqn}x8zte)o`thfPtH#lP|Tb{oxJV1zR2X7ZM+FHase?0nx)zsxXz2wJw{KjcrC_yZ2V8EyWQ}j8JU1O zl)2qWYw|hK7m!qK!VRs{IM_ItC8P)88vskr8v@O3RWa;b&V<^b^quh?4V7HQ(yDRW zi1YGeBUflUrAxY7*mVmU&u(97InQF<(1`w^)ec5mn!$13z@ptr8f(2Zs@(M!)w_o~ zOyC5Au1;cio`MfwhSULzyhOYu(od1rtyq|6>%r#iGm?dK)pM5wabp^>-lo(ve{66O zPo~GWS{pibalAddTbuZo<;R~g;5y=ME1Zk5WP58S>GbbTp3zjpZ41-P#WlWKNH`3X zLmA_!6%*7%tb!$DS;sfxbu?rvJjUld>&;(2F=^u>HXxJXA0@+6yI#CsYe-iN#~N@$ zZ5Q>kvd?u3I5W(Vq9DL-I7O?l{i`P7s52<~ zr%X5e3yo+m!x6wEX=Pw$pz*v3P48l?l`Vp`AN~rAG!O@Z5r^*^SY2@+EnC#;hKzK= zTV7=5VcQCk;5*tKyIbnUFgCGG!Q>MC8a#6(+b8%wAKUA=_igIWT&$xt=T<>ZcyCEu zqODfogPa+iT^85_gr#eheYe|qJwKSj6hV=+$sRmC4ldwsKthk#QSe#K5o)7slhnL7 zTU#3UiH<@PY5WIf=R=m{dX=kr4&Pr-d7xdvn6n}B*)wpb9>ajMQ=Yt-!#N`cnM;8y zaUZFyX*FS`U2s0=pLq19$D6|E0^gCdbb7^6>+HJ+(z1A%-dbC}&RpW3k&9md@y5)# z13!vmK~oWIB>04`pt>b3ftIBS^-{yHN0te)&wH-iJTZoU&FK;7lMt9tH@UGETQM8v zM*%&9v_KAFfXfUqb8$=e*^xJu-PnQ4L%$|IvqIf0$D7j{Xpb>ef|fZoqubBZ=&gK9O=m~_tGaC)3q`*bl$?6#SbgnNH=bl={a zn*%B-_L;w~II#qJA-y6zB-F*SH(B+|UX^|)e=dKn>kHA#OpjwM-c32T1<1PTYTfOA zyNZTrf3jA`clUg|=^Ij8HC<-&%EZIwem}n{{Ef1F$zv^13J=C7FT{E;uFmU4=U^Q4 zsxR(7fDgRTa8v>|jPD5ByZu80U?U4#_B#$PAM6k8b;FAHcO0Ap*fXE(e4sdy7xgQ4 z{adTS6dkFW6Q_xbV(6RtBOQ>Xoo{)Tp!juFZwj&3Gl(L$*o5d>G|eh})mDlkAd}Ihh=fbT@k&JzX0o^=8!Z97-Xr3de`c9?s=hp z7FxRclNwDO>e#b=&F18VmR|25WTY!3=m8k7wHB-19!# z4TEyxSNy^PcBmo=O?23X<+`p#onv)u$Y&_AOx~2R(S1whl)GqTs8Z^*Cx4xAT+veY z*tA;JXeqUj%FEO#rj({6S!gbX!T@f~R6@W+TS7o4tA+qQJ}C6n2p9!mK>h(E-`U&) zq94GZW2pCk``e-M z{f{seJ2SnC#Za_32iLM)G)}CjS)qs1(UtHD^hoaQ7{iqjZ6zlOWF)JmkSQ$CMt<36XaQC$>XWooK~laBSHy~zYY(ihesww zWh8L$#34XEaZ?l=iI%ODBUbtyC@;mD^7$Gx4HQsRge*!q=hf2^suJJWNA2sz1W@Fp z%#;`{TN-4Iihe*gnu{yW%+{K9Kw~c`7HYo3h%*V7Ma%mukdX{HhR5dPP(hIe&~_Cg zyseG$BM80q9_tv~w=x>U`UdF2vq4vtHi@??|86R1<_u@=_1$2mV6=#%`jr8_e7@Xh zATtK;1j9QBP0KDR{4)Qe@&WOsWOQ*h!Y4FvVC`+*}Be)T`OIt`OLh{AAH9bZk_mI$;{prQR zQDRlH?CJ>qKE!$3hCb6w;tC&DrvAo!d$~aYeCEMb0px0-KvgBhbj-@b{9oMphsHvT zB;s@-BD9;J;U^e9k#!;hEzJ&Rm>x{jPSPHw*i@a3N@r@Jn~jOqlEY#`|_5J}OO)JheeVp;_~h$eHK z^^AyC5e{ap2f~_U2OTqYsgR1wrNuY+5O6uTq_Xc*!8ZhmWCi`Rl=pQFx0{2H1M2vv zHbX5Mm5i?e>6kq@(Sev~LZxeYLG&_jFh`;2o$_0(&D4y|#@>7TEd+GHC__cbJ_3@Z zGk8b=u%&A9RN{ntCWNBLg1+*8S+Zty-b@bVAUeM=*L~%OsHl`Ndt`)5WH;}pON?R>4Ka7bDQrNqqh7LT?kITln&;~yoJ%J?|sk&#NTod?H73w+@FXVAQr5cB>SQ)_R)*D z=uOi=P^>sxi)%PW@QH3K$O&E{Nq=J~D)NQ%yzkeayz6-Mb-^8DN4Z#l6%X613Z`7# z>3Q`2SjG6SzOA!&|4T*S1o4`?W;WB`Fnu`=EZbE|1WM-BeU?q|BLpvoA!`%LBJ1(n zOh|Uka7}O9nQ)AF{jXZwtY-m-N%v}v;febxiH5DB%Ll<^3YvPaHu|_NnLF=dLDjiq zgkW=!0so##=cO!Yn_u6GoR(cY6PmW+r{$5;pE>`EZX{7x;I}3qQV|mDF@sbTQcVzS*ZQd{CNAEq z){pk4Sq!IS3WyvmnTI@h;8;Ll&$>~Igr6 zc@hQpX}eN8@g7tAp2iH7LIKsA?ionY2Q|t3*R5a5{Y6cF@j|H?tP7Q00?};QWW1Gh zieYno<=EhB0{*EM zYp$I`M#hPGou)PYK+0NOE*n%E{*a2W)kFz=N2DQyJVbEQKE2zAru_m3vgWHEi zZPwR)MP9`c<=YSHT#c;_XCx>ON(xSRU>fk;W1F+C+H+6Gg32!zj%klKmrkT7Xkn45 z*y+nMRK>JVZnhpRoZm#OZgH+IFS-G6(J#4MU{u`04;Zi2g&e+%cuXJt?bpO$y1g%T zOSH`uG@V~*<_0H)x@tY~OqOdVk8UDCF z_JBV}aFJ5nlxX>k5A2xYa$bI(f+^pFy+=mfy&3(os0ec%-?^C+LV6D4Awb=S(@wJN@b=WiIbDjwrLKvX zxw*619@M(`O*b1IA1OKDOcmySp%xT*O#94U_IgqOzpJF;t#&@ALRLqhoYP zwii!NZ$C(-Wynw!WvQc@!Q`QSa*L7u%Z0O=PIG1!%Vde&`E$*u^E;h`oQ2D@F0Cb6 z2#;1d7)l9MhoRZN&+_z+F*srxlv}<$`pMNwInJm`o0)AS(HBd%&-Fo&OA_$xCUzz& zSE5ilaLw*zmKvmp=x$ykQc;)9d*)BV^&=&K6eit|S9p$=FSm8J8{PmxxS z*BHmUS8l2Ji}y-vK70B_Qi8K>JtTc)hU0eLFhvZXgg`?1H@Uu39qzcVdJa6NcB0sSZi2`H-9;I>P!w{s}#Z9X8J29SfYLoSP=PEO1z^p||kcJCtxkuB?{b z9?z=WSX>O^`qmP60@Dj%V()P^P@y+E-r&v1G7?zxWqtQ`=5_f87>m^FG`ukYP(ez7 z3N*ZB03d*c5ZkD}NZaiI->XpnuYQuy5-rtPud%z7zp)$t_gro}f>;bh35apmb$a(X zsxFeaaXw%o!~jcR;+eUURo4B0jkzgd}#(FIrJMYUx4<-lG zfW06fR`Bm3LIUf1Yr6?W zkpaOCCzOR5{SF5Nl@NnL2!jR2s?61<*F8rP=~T}#_@-sC2C5xf4YF0v z^<&w}EMDD*J=HQTGj2!<_2RB=UTlm$GsE(`v{sE6TBWo4(-Jq}vlJj>xlWVxT@KvT6o z4PuFO(CJFfh;ic)trM+aUr5of2VrH#kthcEcA=qllqn&soFOw*5cnSxAEpmGt?OG; z4HetIVD~v_NAHAoQG_T1zfAlFx)1N&mWRy$;ZH`gFnl|gcPt-;9|A>!0&-J(YfCvH zB@E8+VDoMUO%!IeV3ABGd3QZF!gU@Rz;&+m^P3646V4S|vP6q@8KRrSyqHRpi z>mU{JGabxJIRWR{x%{zvB(bL5FlK}(qhhoLw&M=FKN)o7sg}>}#8k#PHml@o3Flm; zj_1qKxJiYIx2O34)i}B&`$>(oDjD^fGoOFXg)0;V&drIyD_289Ue}L;XdLteZC5^= zrZvPW*p#)4?m3NkEoQnIJz`I%z-J%Qz9+-u1u(&+MU{8#}F<+%*v0$FDZ`{j( z{#`QogZ@132gYThUSnK~Im{~{#;l?3Soa8eDgC>8u#1uFgzoci=%=w-vEW8}%H%xl zV0r`p#5jn~_#H2d%~(E$&@La0->)&Gaxq@?-PRaQScNkPd2aNipk(H9f$Hs6<6W!h z6?5>mgT5W4eF?$Rp=rjHiNoEQ49RYy!MGT-zf`bxGeIXgG4m#<3T|3&N0+07u8OWZ z==-}cP6%Zp$KlNd)veL*21_%Br2P`_`S;!_>E(GLiUDi^p3x>K#;L2x#g@)~I@k=} z7p8q1XKT7HbG^NKHIf?!i8ZSyX}($x7|=UwFP5#x!S6zNj#%zAhF#-DisOY&?*(Pb zxgu!#>$@*+j%xLniOGXa3u|p} z)CakOaPw2kkNw0M7L$rp30|GlE1z%Ov2~O8#f0icuqmufgLrvv9fY*ciyEJDsm> zCj_imjs~QKtYHo%ILPEbfGtrg7L1!S)S@NeyDbsi+MzrgkaAaAC^3>qCIVhARRNCn z9)QQu8~M=-?BLHHkB5V)7025fF2(sGM3kL)ZDhXCXXr=$g#BP%t>5B%?z9JbVSReYQOQ8 zAnXrwMFzG>37%tZty36iETJkGgJHU01>Z`uyKX_AmmF?b({=cA??02r^KKAR9Di*8M zoWdbaj~?LG&;qTUn7%^acy&r~=6GhiFxOp7zZ63DSn%hh$H&d~xt;tng+;p&(T^B4 z2Gzc0yQ=Pd&Z8rg9YkQ)$(|%Yn^V407_vD(=IFN#LEBS*a0mytVsr!3`xm~KoC-OD`9Q}3Kiyrpf9*=*)){od{SLNCwz7V5%16yO-%7Ztn<>j%?6JayYf zrZuXUdNgjQ0bi;T%jyQ*C~sHVZbF(wV(NCGJgVwdp!}(vwhnTqki^P6Y0-u6uu_Cu ztEwI0O~H`YCy>Tm{s8?lr(letU90kK&%tmSv`}8wvweYaopB2|5&{ahY*x#ggLHtI zq!gfr^qS>m!m!=PXaaN?g}hSDo0?{Tn=+$-KP!^ln0KAD6DDYz>_|f2gbMVDDu& zC|O|Ji;Z}=iDjeEd9;wE?C&F1`BZca(s7Ma+@tHm-Frg&cp8+Uk2Tao!|dvqr-zEv zG5i*q9Vr;mb6JhM1YST?z~aa~ndmEN-?WA(;T|KzhL# zhC%To6tSH&E0H^4*_-`IIjk&Rov7Fs@>XKpaQ-$CnDxpV!dVvNji`9zTe_eMHR&WQ zFF`u+6XG)EQ54#ILChqTf-aPc;)jDOI|-ead{@7tn6DXKUc9KTnB+EP{+Kbt-lx84 z+jSu9+$uMP^Px`#O;vlP@^hQ|Hda!1Q8|ydBj$ilB{1!%4=4JBB|HUy`=0`qfKd*i8O=WD;dP7~I_u$Qrr(?ZMN1i)PM153!`pPRe^o{*kTC90 zdTmTa&$;ft9iClGqxwZZuK0~Uu8T(&?9Aa8nj5D#N7R8`9~+c@*|_Nbq=eghKUPKd zFkRZc4CVIjMCy_MF$p1M6jmD&q5$ zSv?+|?z{*gkOe)8jXSX-XItWze$&pUE zz9)qmpW*cK!HE)6I}J?=-RhqkRDzx9{2&aTyveD`i?`2QU)@eh_e=6|x>(f@b2MErlY-1%hzV?z8SCK9E5Ehy#! z_#J{=hCUVx(p+N_+$4{dYx0J5_HDVVlxe&ueqMlk1xzsIu;7gB+pvIFX>nqX7f+Rz z84LaVW21>2vO2~oJ-yU~bITdN)w|02qE-oO+|nz6+~o??S@N_CC;-$-(&@EO?KQ_Q7DU`vpBkyy7GAR^LT_a+{(t z$h^(*VpcQlawTAKLMJElJ0mM->*)|QMCslODA2fu@*hVd^Z%vZ?teS@{t;cEXZtVc z!e0d7@Ah}O|F6pKU;F<^8vHjp@b~<`%YXOZ$NxeH{S?DG%{ASoP&>1m) zS~KjDs_VWq9vLG7u7iLF-zTK;m%Afik_aS%rzMRQbuGj1S%u_-*1l1W0K7O*FB0#> z7rzFvTHpo_Qa(G7hqN-QQi8ewStIz)T+ZAG%=LP2?YbQD-b(u$H<+2m=5Pqi3s5V- z5-O{pm6OrD)2*-o;2K*?6_MQROx?a$jRMeX+}ny;rFNjR>3P%)u+JQ0bawd> zb^#Ng#QUK%#*W&!c`NMoBdVsrd**i0DNEijQ|6lH_@TyBtCRIOeFp?ShbSG-$=R_A zFc8l4oE+;)v$duENMlxS_5caMjH9+OYJfczs@V?$*p{D~4}X(#5+qDmXnQKQ$H?>L zr}v%Y9x7?$MO(W20j>sE3hOJz7#;sC(gMTbd*O{!X7jfRgUlH~}#_)(w=9|@1A0&E<%O(BX*FJg~TTo8IaQLRXL<<2UDnX(tz^T`(b zi-|B*@HoQ_FVuEiz0Xe%sRR}}3jQXD?Ur`|aIr2wu#+qbnG&@JIOeBvTKyF{Y?vOo`mE2N1b< zDdUI*R3wWS2UZjnPk)UEA`)Vcp?58p4<65P+L(M~Vv3%ro~HuZUe;x-Adv8~Uj?<` zFVna|#KQnokYlmXW^&8Gl+-roc8hTV%g;X5Z z;oN9{kj!XYyS#i5BEAzM8q3)r%v9m0%DsvG`O>k4yZdw~r|(^tron0rDP1+Kj8bSq zYzQso*s-{BheWUR38xZH%3wdi=rX)=1D|sk8feG&aeYqejfg22t;JH z6eGkZv!tf6cXfb~_6=_7mU-L8U|jC5oMISZ5Ot}H@?=!6R+n0wj%mJ9@{mU)QUl9u zF}QJ=uDPBu+M(IoInpub$T-qe7enrBAvRw{6OH$duWgLEJF>W=@AfDhqGjqb~bOa|oVbzRg zfd2BQzsG0?uCa%*48%3X(EAhW(|j-&;MMy!4d4@8LfjlySMW?>3#rnr*B;`7 z{)&gb{fEXsm2YVJAe45F#Ehz?NP#jqc$1MiUMr?AXXN$DV73ypj#d9B&k&f7z)<5tb&#C2AdUGYu|Lq8}h z!%d_u=s@tg8Xar2A;-yCiAsSAHhcJ9Lu4TxN*8>HCvxN$70S!z+X?#Cly~}}1zYRK z{!9Xag(Ki(Rw7t%bf+!7nI%Xgn9S-qO7ss!CNNw#u8ES4c(w|D4`SjDeE=Z!RS5=9 zT3;L>R@>pj0^1OQPl46EWPz*@!$nIT$KB}xfm?uACJ*Ac&gI+u+Hb)+4J46Tga;i- zb+29Si|;fI>PzaXn5?7?QwPEQESlI05fvd-eeY7rn#y`}n~eQ^-XdCwa`#=Mu9{R5|HeJ)rBr zbcijaE%3Lvn>EtEU=UijHsv~Tu&5#^8@~oz6RtSFy`59e0(AS3{x}s_(GJ> zFQBq)+l}&j^Y*0=Y~eT)(NVA?JHt+j9PHQ~(e=?kLt0a9(Xa`Uq85BN$VXXy*WQj6 zzq18lQbSJ59UY_+qB9=m-5(dsk4}mw*crd4P0mF;S>GG2)^ZIkld@H|N}D5LR8Fjb zO`(rTy4jj^g5qtL%Bz(s>hs~8tMp-Niz9rishIgdkhn8hl5_Yur=V|u&A?W5;=&%^j5TznKyD3#Crok-pCThH1{ z%S!5jie*dDX)kkh1?9k~h`7FCH;o~@ScW2#-N?SDFsv2HzK&sbPJReX0kE?lc;F;#yl%I~C}0~3sNj#+670^j zK6%Wms^D&P|NBT?6CUGnL0E8% zgSpqT#rk?Pv!a74D|aa?1Gh95c<>{`shK^0BTsPgR?!W`@{Cr|FZp72%ZDcuybCwe zZU`uiI{h_&tEy(EQ+~af* zeCiVRfY0nP-Z2R8*O7_xx4%OJik zhUGatthG5c>BHc3VMIFG`&;E2?HgHDm!^ij23yAylm@$9iwsPspN+j+o8suNC}2Pe z9Y2f$ix2T&fYO2@1yEsuQ!$!gO>1M>-Ce8<3a>(fHi2>vjDwu=f}*f-aP3%X<1TmN z5FV59Uz*B0e~YBZA2ooCjqGY@JX{7LYq`)40cW|dn{4vd4i!B^IrL>N%BWA6X`C({ zFw!I1MH%O|=uvV>Z-%{5&U&5}LChkjbkU0Hr)J*jx=GwO7jwk{KaWf&sm1Z8N{r-3 z0#q@b{=WSTYwe#|<%iW!AIvH{0_AuJ)ExFXIB&_i`L%o}&m#>LjdrEtKiI1Ny0yu&OJMhSzawUal0LF)&`>Oi1F_WeV=uxo?d zuAd5?6?m-z4^K&!Ut=xZIDJMG;6Mqr9#7yy=&qNVK0nRyaS$<?JN82m|z2~WUkVwlg!9Vh&BDg-2ND2P>@ z3~my({f8}@=c8M)c*SrcU#(HKhuZZmE)N#|(5QU13Y?}tAE)D^UukFtXnDhCp&cRI zI)9n~)7rX)*{QF65KZ52`8kDzTn{ctLZ5>zBRa|62Ma3kVdpVBnRd-GH{RkngdNl* zKEt0u3_+pN8j6~F#=xH#T4(%v0HHl>X979Dl;F2Z!j*-iA;LF3kV|zU^ZC1Z=!NV~ zF*RO1=9?ZU)C{#2iJ9hFgof>@gjqq*2Z;bTmZKj*9nnY43TIO`gQRDhB2%r=E%SeYEG91{CnbI6fy`F9<^tJLye0z}S?%_VxEbS+Cyy=(~4< zOeSMZo8w^nAg-R73OVVj+J*%&1F?99B)Bh>OZSI>OmBb%Dfx6uJ$Qq}RlGK8_@Us>g4p7ueo{5OR zF&QdMx_(7pOj6pOgea702iT;t5Ac#}q=gHEhNK`n5}}0wgNA7D{n$J;en1T}R$5ft z?m?7h07H_lHOV(VCm9z#5OsQtu4f*BoelwdHNx$L^NaS8@}g0;zJY`a4L zpbBT@=Icab>t*wIkNTgwzV$qr!nJ*aTz&v!bMKgA`+=CoK@A0e>QJd_SPuOx5*;U1 z#kChU3Kq ze2{VDO8l{kEe!Sy46K|ZwBi+Gw3tU0^fE^D>DYM7VCUq+TZ3E0O_xpAOV-Wp#nt@~ zDZ!;6S_Lk1<8y;=i(8~xTt1eXJqzS`yxU7zX$v+Uwo!LFmtgiDdR)pYb|2@waNX1J zMx?DvXY93LL+13I)49q4rR&$I!s(dwW?F{=*1i`40{(yz+LnOPxC+0-od~3(=p2bg zzFwpYj9414k8zp2XLBS_5>xMMS0JI<>U5IeFWg2+suB z<%7wbDao{=mRwr8X1AXA%F1J03}tQU9|I71oGH7owg#hs#i`@SP`yzpkrbwF_cJB| zEY;eN=&em-%k{_72v6$DM7jVQ=n>v~R(nUuT3f68be|gMS887iHFXcMRW}%QOvKvH z;mm4`8GP}S$}2xx5l;KSnH)=1<(hYKDXbgxBT&4Q+jR$!lsQLlHk8|&rdoH*PzDI& zXwCFJ`MRSFkF+KA2a`azyq+B7(jQ+mET;xA}wQ* zaJD;5ha8;Ot%l4wG9s)Z<%%I-Y-Z7u*#Na4Q3OVVicaU#Z?Pifrs{B?T zIC1PRJ64*h-rZ7sR~=v=@2uFZKh;xOF56{BVS!ChDPs+oF)jm?YlbUx75@-L*;Kw^ zLg}j5RUo&PyU|8TVOS+m!at=%Nl=WGyKz9_QMzdeA5!8RAIXumqbS(Og_I(n)N7bi z(yI&?-FXamSN~8&d0SrBP|8!cQV#MY8Loi*f0%oxC{3QV+qcWMZC96V+qTtZySi-K zRb94i+qP|6r+SV3-(#I`ud~0s@6JVx%r_(6xXFkyGIP#n{uIT>>L_R1!IalWkk6F^ zSt@nub)hDlbQZ!e1`tqOianHl>reF!sPO0(nwbsA(*IFyx`(>oBA>igG7 z!Ubzq#_Ki1?`{+1=wE;+Tt{Rlmg2sWWfI!bzGmoe4;YOC%w4IPxro$*-m^jtG+I+L zv2F&?@j_I&6?cK`yPvmPmi0b=?*Z*SnX$ts*TXqvMcqZYA(H5e%6zr-pv=~n*=#mz zd{b2UJ{Q3P1T9&SXD%-bH!?*QM(itGJ>6SZ&Z8WR!gKq6qVA_Nn6akgV>dC$4Cf$? zA{>Pyl`CY7p(tWGv&Y*LG;wh8{5XvTET`c^Ep~H@kx^t z(SAgmVa-H6X}g!c_Tu>o$(Sjrr^^vzE53Bg-s6gsUEAj#87-DY+NgANx+Q6e*Ug)s za*SW1FsrJvb;g9V5y?uoXptFc`TXcaA)g=X3T#Y}v;4 zm0o^o~MK&F_uO4{O zADZWWBvw|etX7^57naoSr-4?{1)Jq4zg`&ETN=_)&pki4@MLIGQ7A_tq+bqKwPmsJE-ExB>)egJr;gqC7v@jlmU2tJC1;=#A$^k7V*fMRxw#!YYy7HHi8?gg1N6YO?5WinGOYX znkkc^?-M1O7b-#Flx~aNFKaXFhi{C)25*Em(<8_d8|V_UkQrDLikwRY11rlAjY_4V zx0;CfjvpTu#LZ$gXcjK2F|f-ebvZVuA?tAVIGL@FzOY9{02GX#f^f=&$D(N!GLwv>z}qenY{h|dS=r=}*(ZJ{mc zdhuy}y=q7EoO{k_sI6Rc%;kY*!(#?oDRs>FLgx|_wPO#e5}fXdT__W&U5830rv&3N zStq&|Xk5ochcS5*HVJK#bUGF-*ylsu-6P#nMTow0>_sG->!_6kC#uji;loDwv3%2+XKOn9d=FF zYdfG#n3#!6J)qE`8XKzIXOy%gh?N~lH@mP-sS2cHSu}PE%|$i^Y1^O-#bsV30k1r) z#`mG7n$Z6&ij(>MGwqnai%f&z2uAm(tAiK7?kgC@xZ$jG6)KlM-blJkN5`Ij0>3ubOXJM!2=#8xGw}2lg zTLO)mHvl|Zg9SoD8Hagb>|c~rEHI2iYO$`ohfj&D<<0R9ZtTBDNha?ghY;5~RL-jn zP>#lT?ZuB>UgVI5kfvf#0_#*fm%=)U#fcH9sR0>whaxPklq!pa-wGgrnAS{PF`E z5e3YD#j>484?g~2qQ{v9C!ZGF`dyQ0H4FPEJ9BC>naxf;+kB1orVPsoDE!#n0{i>n z;}<+y^W$H6T8EY(w+nZbBFxLmAsY?dd@a%qcfjVimCm|07xuWT?-2QKJ5Vi~s@Tt2#TzF89t)r66tTh0GQjLW{+NUwE1IbU|t0#znT(w#hZp@S-6@QBh zlw2-x$XHcn1YhQ_#rY^nn#@nj%w!2#>9|O0E~?%0h<1}re47wuzYIhhitHMb;C5q~ zSeJ2+eeN@H2fa-~Jaxghqzql6Y&LHJ9)~P+U}*(`^5yO#fAt|EkA-+h5iAA7%Nk;{1;i z{a0E3`}=SEM{)l5qWZu8{dZORpSk{-`=5FKzf|de?Xmx=O8HSPX?G7krG>?3og;eM$?;SSlS#S=2F8eX0bq>lPz? zl45<mFcagCD3=J4_TQd4(nWPIV&5=|WyH({DF@?+dj97KY905A)sw59mnBfzKsr$^bi_&we{M zjmQ*-&}Jv|J`W@;#FMxA4vG?0q)KNjOsAKSGC`V3ZGc;5WYnMk0hgs99eZicKm6oqkI) znE)5PmGVx6Nm!*diJDH(GuU>`2K^Kn#@ZwM08}f@(2uRbHe{P@XB>TaQ@~AQ*R~s) zMFdyKt+5-K==#V$&g9!dj8M#?Iw%Xt?%be7jHEqgjm%AW5_IX@6@$pe6%x@1#zkg5 zy^OWJ$R`*HF)0a|7iG=ouf5s060?WylbUy~7sY&N;wa1T1dRF_=~xEIS~0==@mM25 z(NV5YlF-=NAUV#iz;VL45$M{O`8ZT+*0>iVhNPo-lQ@yvYM$iz_%-E-L$S7Cqc{R1 z^39d5)vY{F17`er|J!0Bic@L^TT~3SG#|CdWgfIQk6~A8+J2qLrAW4K8(7j;rUq#escYAt)j(%9?^a804b9_41Nb|b2kg~c4PHQ@ECX-pvQeQ5h8-wXmQ{OpXKbagTo zY|~BB^yPzbeS3Jf&Pes(YFsTUSbk9CKNIJqvSq+&cI{3d6uZ5*$Zio_wlJeta*sD- zZHyrWM`X7QD@78266@pjbc>*_fy#@*A&&p37F9j5n1?T~jE_OjeSoXrij=vf%=bd{ z>kLJ;>2s_liWC62-h$g-vnRyD=GO|YlvxtZ4a*!FD)4{uXC0nnMns*PqG!?~$-jVq zwjE{XOCEGY2|0yxC-?RUPiFpMnZIkC4YTK#`t+UsLeJQvl-vD5Bn}c0*?z|- za^}#-=Gk_`o%XDc+$*|`wKl@ysnehCE5!gBWH2Uoo*H!pT|W1uC&rJ_b^~FHv>1<& z6>ya=#*J_W@hlTE?*(7AJM>*swj5=n0I3mSadPTFW@}G@eG>Gsp?4WHNQJNK+bDTP zYt_;J=}NGc`CESsr0|d{Kf8*3%k}rz%iPh6gDLkV?NpSV_ixgXkCjyV%Q-_CWlxTv z3j87l8Tulh3Nbu15IeE82wAvIhE2;UfROn3Odv>J{qL8D2=62Nbv!S+?vEX?(97+a z2T?d|;}fcYKt|D*$}AvSTqjuS{-?`4P{W*MkT~kz&n2Xd+cJth$OF~Ac*wSHDzZ+l zLFv{%HBhaO=<8;eM$x9x?PSeGzu;tb&z`VX9Q5I~KyX*o0*YxtbPXCi9rsBwmTq)Ef*`77X_i09ov4(+UyB=%gOxjL?KhXJhpsHBmCUU2X7J%>*=hdE!o{3 zNSjtzJ2kyVPf1%>@7#B7y_QYQJsrMoMqasZO>ktb1F$ZlhN2dtqF`Ust3oa#9Pmqi z?JN@1$f|9sjBuYKj!W;LK^wZRymjb9IsqEirpU_a=w~}q=e-*(8{s0QoLQyE}=mj(gWLGTL`|&gB zzPYWtcC271SzV^DNpl>0FT(3v<3Y3L%PO86_tGF66t(21gugHCqCE3j;DXIyvlu zA%9^Y3v~*Zvjj2i^!n_2EjhD2zXOyP!mIh9fCHlyAmpi_e9y$h&!0bu*9+JiQuAlY zSbp$`*M)_!A*arUz5DP$;K<;BDvckahUGi!2xXf`3Q!O{^dh9aeYr#Z{=|}%8{bJ@C%9R225P+u!uYtgo)UT-OR@F>kSyqIfi^*nb?p8&Ib~EQD@U#o< z%q2wGFN1OO%aSAPG=KB;C%~XL-SnXdra$13qVup9gT?7Rg20)4a%6#5k*OR|wMZkTY95>gSofge8W>MQ?O_P$q5EF2RtGfj3)9%w0!* zW)xZEaP9lppl!myokBVPc4~1|3|-T(+?4s#%tjZZHa|$g^Cc(2V{&kN+%y36?G{R) zB*FY9ztb+P3I>9Jr|0~xUYC#!XX|N88?Gdhj|t*XG5}R@{25-V^nP|sIi%LwhW6eK za?SsuL7Us^Ti~dx>QIDVO^1|Q&#y-mcYJ8C9Ymqj!U9kAhxt}PQ{4$TWE|C%jYGuA z@0sQafxTOJuE3L*W`h-9uhJmGKmz>{AKgpP7ad28I8+3zj1zZSgQZ+92<_d?@%w>) zjrne(dVpB{I3~mkxedq*CcurfDTXU?hTv4LuaTvajA3L*X(%>SR z1EQ=1Ye`e%9Yfkhk{4Sp^9HNOS}oWpSCQfk)ZJ>c^$`kRV4Mv$BF_ZW<)^#VT!;4u z<|04Nov$WHFsvRP^3%eaQR6oi;dm^D$?Y{cC>wjKr8-zKw$=V7_9!M-qSeIBed&C$~0-{As3I8(AP7JI#QHMlG?1QJR8Fv zO#9*3g^@dv#zqYGPY_V&C(=jLYYt&=K5@O2xt*(&&ZN{K1dxonj#J z>VtB5PfD*Q&w!nvvSKt9`3`xz@&(ukL6mnPm7`T<4@jtCp{_M8O#*=!lu}L)Xx3(a z8dqhg8`E4hO)GPicPO&fMg3j~e!%S6=!EkD2MDOim{7%nZ*-6DZaS-a&{l4lJZj2|6n&*5id0X#sNHp??e=Iu z?{sRcik=5UzfTujMp+4?P2X@U?5oH65DIiK12sWX2#^>#CgEF@ zg*iIQC+I=Pml-KmjQP$F?rA75-B~jnA_9)#$#^Ru-^m3zRdBsdrzNtZO_!y7&jVd4TH+%oW+B^OX z(*3==9b|a3LGXCYPqG}KNu#iOhm$%T%MEL>AQ(A{cKP%I8GV$p~k zikRXmPKYUepYJm$C1vcZ!kBUrEvBh8ee8#cme7p9A#5tA9jPc*RVhP4-+@$;s*;)J zz9AH3a(&ZpK>8_FHgMS>{yDzG1$2X1;Q~&_s;flfzq8BA>$KHwc9|#s$3~@baYA1| z-MSN|T@MU^Yn1=MrgcJ=#)aJ5lyfba=lVycEbL?v%u&49yHQpKCpm?9Swnh%4>i^5 z$~m<(!)+A-z#KJFya}KRQ;T#&7NdHFyfHx8N~+APB%pu0&}ude$;zKoLdFGwcSpkh z=(^&P%Elm+IbDiVOT@ampaTJCm0UQ}SUS}A)VPgYnQ?0aubWVD9_jkU{$&a?YNNhW zG6Aw^PX~t=E2M`}@uem(IPbK$olpl-vZ8^BEd6O}*z#;13a8%Vb~i(-DJxHwe%n6N zEq495Uw7Ug2PnpN%D#v6k^Ftko&w@gPIt(h0Y-(l9 z?tgeR@UH3*2^xvn92ev?M(cb6!3s@2=}yemdNg}9J)ffRE&UI*w+ z(5N{KD0YS2TzFbd@fd%kqw8~k@0`Xea#AGDRpgp2{+iGTHX<;o*qE%9F92AKsI1up zK*NSt*Z>|orahWbM4h(S5s1TDjPBw+6J*3E^sX9^QZJY^sQIP8Hhh%KwN>ExF*t5K zsya-?>Gf+)G`N+Feok+Da$%$DCOCop4%3}`51~Gve4j=zs=*96mjG6rHVOFNWHIPA zJ|^}L5bB>FL`k?9c%rOGRpuKya5=rVa-7$;(d60W5>57?T-~{9)Am>%(9}pp z?~*U*p#Gpu0wtrr4u)8r$>F;0F}dHg#S8iqW6%`{i{jDiRLbTC`Z*?8urx8NQ$gfK z&ovG8y{NT90_CU!#-wX(c^b157#x)OwaM}=dPgLrZU=og@M1AN!YT2yKzx~POUF|% zl?C3Gb15SI^S+`r6E!gos=!GOL_Xt21X}pYZdSEwL<9`1Fli1*t7dvQc1Hod?fItz zKM(EO(1DKZ*~iOssP*PxXBjkpQYCHz=cMEBk;d5g-KWz9;ZG4SWmg8ld%G`R&C4;| z2H^v;M71tnvl)4!zv|4wgKosEmO6t^kE{@jVvn^XX3@%cXW!R-Og0WU6fwPD-zVIW zZ9RWyy9WGXJUg?gF!%Bp0UmbS9dL>!U8g)6RyRC9F&--+lz`JaEyXX{)nC@;YfUErUGCscE$OZ~*3)FZ!Jt`Oj#tL`j#@5>hfU-FloM)f?b3bSWBW*`?!Nn(@ z*O5Kke70O_2lM;&6}D=aTRzCFW5l2#Z5og`cmQ8yk6|zwG~~<$)^!?S0=C%U2Za+~ z77zmt-7o+;1{3UaFwi91b_metzRw2i^5Ktj+b$ptC70CvT~l`a82}Y9TiE8iTp%8o zZZKF^$&2d*AQm>gKp<33y+WW-oR}(D_$|(ES=i@cfU9BxfLQM}IQ36vseQv_Ai^2L zz)|58GuMfR5-n&^8ragkreY^ICcw$kYE_);j8UY<2o*93>~1Z&qLwWHD|0bQ<&o3{ zmW%q+%`y}It%ew#C5>WnQAc}jiLNp{=LcpK8oAI?(4@4g7?RcQRF?3*8Wj-t=8VDxAH^H*U}Xjh{PgnOSJ;oG~K+O5CKT?%*f-Ww;s-@bmtfa7PceLU7+$l=M%~ zzC4=PwIe7&#@xCNXYe(Am?5pFw<}%!7J1dp90XUA!NjOxjwB)vP;;S~oQG@Cx< zaRbJ5v4qTr1YjogumvY6?Ve+E=MPFy6A|s-^vbueV}m>Oza!umhbcumqR-eKb?)kZ zMP#W?p2fgb9*+9gp`#XcEfu1S!d_SK%bU=YXrD@Wup)S73 zdn#<(=$`^2#9+n!(RTgK_j2{{<&5q)f<vU|mWl@EPxNPSYT z;w|8XUn|}#d7c!&RI(YAnHB+Rb}wKoV%-apKJ!4{OwyH#xXP=h^H^9e^Z2F~a$-|` za`nW)mfg(*H^3|^2)R)!Xd#PwCag+scsDp9_ULzJKljIUfi4y6%XNY~*wX0znJrz^ z-O=;GWZ=*qY*iYXH{6#CgPGmUW(7NNM<3`~(TaA$!W`uq79QNya#)ValzNF}N-NqH zK7YCwpI%@WfStcpm@n9z=;y7D74!z{D!sLr3e>E1xSP!j)~qrr7laFCE2ft!u|1y5 z06A#DU7h6!9xw{R;jV+6R1HL=o$B`c{aR}KdZ?KDjesY%rEnz|D9Jq@o;7$o3SmtA z#PHOg$Z;83XDUBi!KB(BLOZkG8;}YT>!=U`gk&)Ovpi033DM31x;)w!iXJ}9*S3WM zbaLoDcpXqFenjO?*v6o7DnKQm442DU?~oylSO@tTfmTnaC-U^S!i&L?Iq8?Vsz=`| z#1o5`Ysd=54dsN%eW}oG2-ifcScv9P6r+WcUF&|nYZ581i72%s5Hsxi(6RF!2F`Xbu+(N(r5?^@=*uWF;axlz_N(uv()Dh+uZ-YO3pUU$(S zEo~7jmg;LAiXXC2y%Qoh7<#qqf^bgE?)N4^k%%nd)1=*w`Z?VRP%&hDNk2jX)j+m^ zo5{P`0eIklz=BsxFaM;o>UH-mLDK*hL6rajjh&!8%nWfCVk4q!3v2>5Df>Yc!+?a0 zuS<@G9{qy_fCq6Ba-1Jv5lR!pB=84+j<_D-1qPr$IvSxE0l+ajR)7^C85Et^Fd{l( zw{4-g8InLRz>Z({5;z}l9X`}K0)VqZ=`{IwRLF@yWp_O!PVeuX_GzMPa6}MRK=q0& z1ZMbJ!-T1azIq;0KH&wawjS=^Cs@oMnL`E_ObdTC% zLUZp`<4+2oqredNS|S$Mf?Z;B>);^+M*H=c{=Rk9t!C-cvuBQvun?C?dQrd&hzVkJ z8h%U=T8QW*&&j}B61Eb4PbANI^{NIe64f$(i#qoP5QB2-N}>GcLAVeyVq0S4{17_v z+w2foF?tQaPZD&|yDH+)21OFv3HTCzREVCmJ>n3slGUoh-dcXl$S$e^BV#HPUhD(Ytv#~%STM2O`>v)Xse9nhOLc#Nnt z=I4GbAyw>qeQAod;_t#fMPr|Z!I)*n5p@PHE$wSnNt_@h{iRM@nsF#x{IoNpvss4i zp>u6~As0oV>u;Sr(tEI6seIOyu2Y)rRk=mA%HC}VP=0=Nj=sF|Q zkZspoBF|0I#Ob+uzt!rwK|3mKo`jU7*dv3U?tL|CeP;@jWj$Pec(8^kUEYvJ@pCd7 zsGDLjktS`7Zh^ZB=VdMDs??Pm4m8qUU5nwXZcbV9mO#+-xiZES~@n3O_)9+}v`&D#b2jE3Z z04qu)0vri^M2KOjP>Igg<67KwN1z;^+U%dT2r ztl|+Ayfw?|%vON-RzT2|WU;>dC`kr=gm>a-8YeOJ{;Dp*R2phRXyYH8Y+s4E`Aq=gaDCqu4!aUWxsaQR8XWVFm`QPG zh`{L>CBW#f_3^aGbN?j zC7~!ml;B}H24ID@d!p?;b@KcoX0))%d)IeCRq*|`42oBMZo+fZ>ZfW{Q-|SKFR8nxidP5pIz>+;xI-S^kqn<7524SA5@XX|@_5=XuC370k@vX+svwTxI zs7l_!$EVBqcP~*dCTJ2dM}K4)`#Mm4^xdipnqWKeQ8-oI7hAgHxW%8=#)!Jb-Ur0p zO&h#aLunSJN8^>LtpV(X`;yGBVbGs zgtds(8&_O?uZ^~UbhNPlS*oR<02n=^l_kK)9*Q%Dv2i|OnFMym(iR>BJnD25$Dn)5 z!%6P?1^^``t4N89m6B6s#`U-7SSl?t;<@<0r`&QL%&78wDeZXX=|BYO*PMU)X}0deda4kCpK(=TUJ7bW$y6~p^hbs^Sm!H?I6B9#Y1 z`_^t>)0UVDo%cvVR4--OS&PQvtN8-Gj#rDLiWSX}9=u_SL$8TYq7p8TOMy_aNm#Tf8nK!Q%dpS z^eWZzdeW63iLc{z^PzNb)Qc4pN#LHnf-7k8D=gldScM$t!%MzosGI)YcDzVL; zx?pCz;`jZeaKcFw&7Fb49@4Yn(zw)XO7=(Qd@M&}1)xFC=SFmem51x$AaOSDzH7?8 zQ~n@R3OCVTBhtc$8#j}jr-zF#PrQYq$Dr{KNvDDD(9h~oz(CaD8oqLndsKL8)Wx5z z=2{W4PA%)!^~z1B2DnyoYxyw0;LKj2E6T0rDwl9)ZaZ{TaqQZLUx0&7?>DP&$D$2l z4Y&FAX~!oeqyo=v`9F{uE#Wuf^bHK6foOzV+;^Z3a)EZWf=!qz+-+ZRT(%T7xs9V_ zULYBlh>AWNZkl8-0uryVd0!jv^gc4@PP>ZR3?;OyXCH5Mw_qxkd3?ePJ!&DyO=(9B ze_#-`8HVc3i@=4jAktX>NlF3fu^fa*Yani9r@rkhnI8-7#VsOyewbk7?NoBE|o;}Ym* z`BB!d_hK^GCIe}dx}=|39mggI!8w-VF2nS9H@siqc-iv5H^1TxPxHa9EZ(J2&bUe~ z)7(=JVeU0=&>uAIN-|`iLGzOK=zfHdP7D81%Tn_vObEjdpC%QE2L}^crW6PQ$ECM- zN9V~Mekd!&T#;4Qk#jJhC7#_wdknwiRZAR`8)VYp?~7c)Zhr;sdLO0g(j9%JZ3^l_ zyVd=(0B%+)e7kK{5o;F8xTseNfzBpV3Sm2=X_zDZk&he!!j8%?FDsp;c02*>ZG2Hf zpD{J}1OaVGY1pKG()v6AYrJJ=+*FHr*^Au@e5u02^?ArSBzxg~gnoBPHd-m825~@# zkwj@Faip%7nB|~rSbp$Hdl<;@pHUqDa>4vfRQdMju>8Ay?f*1gMM&`fXE2(7nSK6? zeU0^hO;=%}`|s^*6P4+7@FGuKfyqtbRM+Uek;nuEBasQh-t+ZveBI))nEu$c!)r@h zp7n{XKXBF<`!E<^E}MCS^e4LpYdBw4w1TEZz47pB?henZ1c%F0ejgti7hGK~%BW}A z%TB$W+YH%au%=UgB~4yBc%~&=@n(GlQ-2|P;04<<)ABkO)$?!{o+Nj@(4`YvV9vcs zmfn?Q@-(r2qiD`*WQY3RJ*B@0%Z>Rv=DYoiC-e85p6-9ZXZ}aWKLh@i_VRbK|CHjw@*h%M{vs81DK4!4eTvIBq=w<&!7vMLZhFYdjclH>KRZ-h9yBs#kXkvVS&+oU$O5=QhzUZ{ zfI|vH2_S$Yz)9gz(PFkif$7;Ub9=`Pv4sg>a)DWQnQ1HDKy3?K$yPBujO+SZ3dR{A$doAi}ybZ;_bi6iIlet1Z zvh_X+2Oe0Npz8X$sJ+8nwqIWd?gXPCcmx6{1KiqLN{lf8Zs{L0i#{eKk64>NRF-8! zcEkf#q>fhy`=A-ZwWJ*bh*;txNqTiBrw9QbZv(VDF#B%qpoY%K%vED33UQ7UCpV4E z4bZ`K_tc?|yq;U}jx;O|ZcIbAx;Zwp7E>g)hO8%txw7p$hvtpQ1=N5P5ZYXG(mg^$ z0z#rg^fN}$Jnn)=04Md~+3R$+k4fs|csLA~hQ%3+VtzmXYwdz*fCM}s>C58y0u6ly zTHsPu7G&V7L*J{^f_;4eJM27NLnL=I+fUh=zTkKHeMt=?|E6*bC+c78Z*sD@cY7U! zuFdJW4zMe_385Id_o(Z^rdvwojS~@D5(A^Ak!&zdcS^}1aT)e*QeMHWzo`8h)VEV& z|2$?pe2#VqyE8e;o9t><(58~(1b0(4BiL)9&&RRxGv?ile3pRYagrm1nuO0F8lzcod+M9>@+YNB45OcE^RVmkZ9!`Ja?eN1+gi#6_cOy$w$Y|L`}l{ zlY1|2ynKhK)*r9Tx0^IC*;6%7;n!QUvT8nHMQ^K+N^y% zo@*AJ7K%8t_@-72QQW+}oXAmDTcnc2VZ^gGVLski&6;p2K2@zVt3GDOh+z>e7Sx`^ z-z@YAOpFGDUt~A)D|`>=10C=KomAfnw^uvb7!rr?-1p1SaclVtrxaYXvM2p*=v?dyn_TCP>#KKVR8H)pjK`wH;UjtF?1m7ZY-5keD6fNnQ4fV()Wf(zaW z=@UP~3968~o?yo9yg}S+?jnxBXQrI>UyBK8YQaXffKo>@l2(0ohcZ&IXm{LFA%rh znQx)PpDh1H#`Gym&b}{K%B^fK^D~OdM`EMwa_$vJ`?HZfvVkfY;CJ1ulqG6N>55KNIs*^qk5w%aQxN* z%!8FF2sqPS8ZYnoYS1$ZyIcAukN;nHrm)1zS2DuKJ44NI!e5xJv`c6 zpR`LpYo+nGKu)zyT7)*Yx4Jjng3qfufJ8Pl?5Uank94A*7}_DFS+sReyE?yRRd9&JO`^! zb!{X1Z~YhCqF?Pow(g>mEDKrB;XH4yO}QJ(Uv&wpZ ztzTn_RP20_BpshJ)mJwdH^$$=u4}YtqYby1>M;kFEmGETYX^JwRpqq(jqwc81U4AH zDj-6~0TXSe6XW9nDIPG@=f_RdZV);?`SE|#l8q-4_3 zhm+-DizLE@qFB{`W(BBFEydeV7}#fP%Z*oV$Dcudjva?jt3sh_Xqcf*7ygD{T%WDA zTwy@OnvGi#U^qeI{E?j>2tWu8QrNuf&6gb@GgJrwx>6efSkp6h62D+ZCEQPwkyGI9 z9>o4AAy=d_6{Vy+nZ}-%+kPH4Mw|Zoq<*%9WI$MvR8|3sOa`6T`aER@B=1)A&?p)` zD={l6je$A=`<|@%oMpwFK0N}oLk8J3L0knN(rG}RheVII1P&$mR5I8UBn$~NJjfI! zSmHF-_mvM>SHQfuK7l##G|#t{j|oNh!9VRRmr{VH8C)Tfzw{$U4=RFe?*uuA?|vg! z?hlfQs*=8&;x{2A{sYnBX<%JyWy(8aLaG-qyCBK!<1_W(tCK)6$nj)BWUV;+;zK-&mX z5Mx0)^b}*XyCK1`mcE*^tn8vv1}jxFnSwb0^R%im?Dl)9MbcVZXreWkiZBWY&qSol z5O0qjiPi}S56nn~IlV@Fx)s7nLCDh&phDJX5;SK_!)@sL`{Tsa(!Gpu_uY>!;L4L`NE6 zMxM1RXQ%ycBWwsr2#%j8$!Hix2NfxdfI9!$+#lw0WV9FZ%7MJxAS{(Kb1vsFkCL`d zt1Y2&)4*7^9P?aVY$1^_KM@`sW`b7dg+gv!MimyzXBS9W`WSC(^K{wnbG1T8N*GHB zSuVx@Q}U;EnYkML!_o_;?bc6lr58VWWAQ!?zJFj(+VX! zvS>IZ-{^aOB8-^`zMJ=4HtY&rAEo?LrhZR=zB;yZ*nYA>1klm> z*-jcJ#btfIj|_#;G;&&`;#sg#fjhxiIYa!?B-NP)`A0gMq0}B%+~kFFD0BamKubdY z^UE?h6A?)UFaK1m%+b@2h1u}dq6a6iRp^`(#x?R=Icx&{(IR08WqqgmjUh{bjcJ1V z4gf6cUyV6y&SjJvc@|=lcOi8MJY*?exdeS&^oSNhx2Jkfy1R~{|JMHyOM^}`mB!Gi! z$YhvOb&6>qBFQZssRn#OOUcU!BdweeT315+a(j3~1k4D-D3}l+X2j(c%|7>w*_l!s z(Pkfe)!L#O&*X*q0kAj2YH6bZ7ko}j@vTSrcC$UL8u5zr3C)yNEp=fr-fj*+`dBG zM$JGimH_l3mjuTwDW-32U6>hw1bI^DEF*0t9cd^f;9R%?kVXG!yVVuTZeVZzd$U+z zcuEq6UJNJ&VWlxc7y0BW(Dvg^{5sD!tt`_w3}StO$FK-VU>(sT$r_)z@!+O?mBd z4>8kOh{!wM-G~f$N*wQ(`&nki6LDouo~-`zEHaW)+i_fBfP-@yG=7vBs9qL?#Sl3% zKiz>Cyod`G}PhfLTxzZ95}@Ian*)W&TLXGqB8`YbZ=OxKq1U2aQq(2Fm8E} zj_%;MQ)WdTi?)pV=aQ>t+{tSB7r~-ERbH^4aLqlay*~-4TPtm>H?*2a=k9GE?%et+teE$tyNuEtsEdE znC=t>rKx^-M2STG)NF@bfi=QmP)8aK5g;w402L+>lDSsHK0xpvARX=RUppI_pz; zI+A!wu2V(kr$-l7yA8^~2$+vO5si>5vTPE-EKJp-H!NndYty{A0HLT=jH@EjHvoWKO(c}z#UTH}p_CXjxr+_Nyg zEHFO`hMV@LK$Y2;-TO*+;eCgb8zrF={yZQE8?eZ9Z__rLocjk7+AHDg4?jDu&b5pl0;Ue9}s`8N00OQF`p zk%@6q%zK!g%a^Oq5|z|d_xe+ogS5pVRZP~A;x&A3 z4IBK`^uT9f$g4eg&FwfI@VT3`&wW}QsQxiF=Mw+SM&>Q{aSFZ(NK*JlPCK@0xi7W=&M)yPei7y zK~J&UwjPe`X2{Knpo`M^Puk0!YjYw}DK*+X5A0}*vQ~I(lMtK6wI0GR;+a@HMK}vS zXB$rX*GxK#u&G4FgLBIqvw*DSO5CKGIt-Z}Tef@N;SnC9!Vqg0#SMU}2+?7($HLoY zM86|OF|k%o@Dy6!^2WfLdCBhuTIgEh<)v3ySOTGmJ-a6-BQFZmq%T+lCh9u_Cx3-w z=Pg6U09?&4tZC3^e?67vNTt$s@WDjUthIP7-_~u~cS8m2fIW4MI(4>0GAB2Kq9K6 z|LHG~58Q-8-G#RT(m`$w``Ph*+k!nsrJXegwNDHCK-DPGjauCZ~c5fiO zd;iX!xO4Ae-BnJwHxIYq@$w3^OQxjE@U`9^&(!50%^Bwm@_smjFg{rgk5=&OY>G801o$y68r7=5e z@9~(yrYakJ8_Uyq`js@XbZfZ2IB}`V|p#{iho3Dc8}4sl3)xo`~#~2V1t8 z%n70{n@liPubm{Y0^G3VOdk7rrqSu4NxilkK_R5pUZ4;RnHby_Ca5a!!6-&dY#-Xj zo^w%^^(4_`*=W{VY-YL7f8xp1mMll)Y%M!w3+z|@2k?1yEmDB;io_W(i%;sU& z4?a*3tcb>$Wa#*d>L6tgfCJrYr5V2qU4#JK=>e}cv}{jED%AjHV3xiLy@55_yo5&= zW32dkI;1*}+fyk>>~@B~6zLVdVH1~X=X#-p=`sDdHED!clLU~O0?BXlD94KN!(vsT zE+;2anZ)R@^4;P(M`|>Qk}wFs7EpL{I1kOi{s@vGVKeq}ia*GFAYBVCktx zFg{p8bEio8sSD*8%!eA1r|L{+Kk6BDtof~v1VbismHSU0ohyXV#2xPFlc+OBSDC6QYq&LO5^PW`hcd+mzzUyqAm$<6)Zfa5s3a4JS)N2_Lcg zE)-|5V3>u=qV?N$!w}NUf(%6AqwLqf$yWBBFbz5pSQ|u`(G5}%h9CQxbBy@G-rHQc z&S?$R4as8~wz{o97>SghVOX(#jQETQKX)Ls)G6@(*0s-w_K0uZtIJH!D`|{Rho&R+ z&Tvs_bY()KaLcOi%AUhWq`(-uSj&iH|8&yTo8{`e@yU4(CCgy|!jPPuid;jmiMr{! zS!wqeFd~zc*8_obQrRF~v=R}yL>0!T6W$>~+?J>>Ys79W2$jnvy>KHCRu8*PgfOk> zeo$986OJ9eH9h5p&?HHtqDgMkK$K3wmkUsaz(xc|%u3mzg4jw0=pbI9y-aRXwP+k- z5uYHIO&%wK^|yh(!vy9bc9Gi9J#`6RC+)usORr4K6VD)V`0)pa`$?W1*A|Fqozfz) zaf!GnF@=bBJrmC!#Et?MA^E5CmfRXGLjcW% zP~ww3^?Zo{M~H@E!Ei02W+||uM8#TS!$j)W`uJrm4h=8`HqGGy`pHu?Xvsp?X_11r z_AE;7k}Tt_qRnrMTM$?ltCH8u4Fsx4n+r(=SYFp});hqYd}# zH-%Ytc5z_M66P$VVL8HrShZ8EIr%ijvIRF7pm3RXFB}Y@Nk4LkXV zJZhKiA9~YSWa5rW-F)j}edVrU`^6r>BfG5_M65d}+t_-KXO6Z8;sRFjO?M zKDb4fHQKrhT(V?liB@jz))`J0xL=U2T8KD~6AeB4fn7cDM}i%B*z39LH-I_v&`$!5 z!eoPejR0-pvLE>KdFt1KwRi!5ZJha=O#YbCzwy8ofAkv!3IIb{1@gV`d&hOphIz>b zySneY#8uz*2flz#Q0sJ+Eum6~eA0NYm8zu(Z%0y`>?!*nR zpC>NzL(w5_syAwB$zjDlpt<|RzZ64eg(-$6$=#3ZR|>{N|7>dtlQZjYwjEeYOFBm3 zKx_HSVAnH3sVApM764tWMx8&a`5p8gLt`22aZk)0y!2L*UuHLG#vNAQ#%Bryg(^LZH9|R`0mZfqM{w ze=(Ug5kT>6xIAH9RxWBBCaV<}si-FDB@cI3*vzpy;XV4j#Xb4Cep}^EmVdr9tHn;H zKdZgD^ge450G*1HJy*JTX4{2vX%G@~{9+X@ooFNeW!p$3LbtMOz*W^M6pk~zPA~^6 z$|df~b)D+n%sVObleZVo;;C@oCLH*|<;~PvdAk_R>7k-WrB}mOoX(JS$0YtcVWnaZ z1G?mN)f>>%qmM2}yttUWo*EzN&IrVHh23(c??-Z6em37@aL60y;`8*!(WBdR_uFvh znN>~4be?xLfZNH6Z_CG#8&2KTlpJjDF3*bnR8|Pxk&gFLR|nW=+iC*C!->{*0+iCI zy`B&16&#ZV{N!&Xc&WF0dlXm)d_G)~B8zYK?*T9keev}sLzvW##@*mTfbGW*srRq;h_5yb z*gT_=&Bzt=nG3=d>kv#1OEZZxN_2Cw6092ELH*1a?0~kwxABB!kzxw?FH2v@*2#0HL+!FHUpd|54ZF2#5^r&Lqcfrz`-k zM4Z~EWKnmJp80qbE7V6dL#5Xfwr}aF$NP}#)>DfbO_=7qrh##fI4Rpih1*5NemXzp z7TbPBgANG#VDG;y$i|Mg>HT$I+k4FSJ{Y+swDucKB5NVG{$7NwTwxGj0=6*l7{r+h zHBw=RC*oj-UCbtEKa70M0+Ri5+vgvaTX$_&~oSQwzI&^7`XU*eMw#`L_>iG0kRom-e#~Y9AS@?AjthwXprM;sL zyAX_tLf#u#8nkfEnDyL4piVrGZIlopjB6c`(wbD{W4z4xe9!K?Bx6I-4xX9G*$ystC5 zh>^8JbaA>66tpUhS)NcKbX&rS)vShyz{RYD?5!k%R9yVkQot+-jw}6^_y})VtF;$p z{Jw$_8E7JLG>paUITad&5^$WQGukHJ$n&E zOJ?mP7=r{j{i5(b-xGLrgD?0C;I^QGVM1i)-8$$U;QlT!uH`X)#En1WDJ${{!e{K2 z?5pm+z4YboKGv$VnMfRF6AEcXmt3tee9U+bD!?4UgbIDq+%>FFHpCG_8AH>=JSAT$ z#D?);6h00NDM<@G#$pq{6#>|F96m@{67gGPh6whOTG?I(b`}B&x0dU>qU~t|3a(~v z&}?~Vde9}d@Kb{1xx$INJm>}HE1$L7pp1ZrCcQLtk5!IP4sQ4pbdNV47O!OR@|2zm zXZX(xoU=OvPRoNz6! z`1|toBF`?Atm}iT?wYQL&NqbDR*J~?^fC%}!`&@elU?gfn`%+%W#<}bZi}z)#3SB! zd1-*Fb$4ZHApf<-!65M4xf

#@{bN5T-+6Ke`T1t)lWJHEcJv&R@_qt9N@a!QtxU|wnNVJA--0%ylK1x`Iv6z^+MkUr4aU}x ztz)If2`JPpinMpduJyjaoZOETkV^n#R3Z5SIx$$jF_pEd+mM|EQF9L?biQV&e1|Q(=*#Q^=M8~~$1IKac;RCmJ>G1@wIPSwK)L+Hv z;naVef_t6?#OK_m1;t(u zyu$@3l*+u3$_LX^DdQ@eE>S%lbrPrp8hZSpBp}P=!H9{*ne!tz>lC0D5IqqpnlneU z)HfAq!V$p45n!^hqf^;Yw&uC4M1FMPx*T{)Jv5UZJRk$`T{uVtZW(QyONLFOuPF@) zG0zLzA8t5=sRFS4U4PNfo%(E^$_-W-=r;_?1*ljQ2`iOMzj)Jw3-w z2<%~d`EQo_x$jHbhrDZ0Rc57D$aV=K3kD^B`bovAHViCZuDw*k4CYG52I@*jM|JJs zgPIvjGLE-`UCvgCtyWGcT`p^YPfn!%YW27QfE4phL&QY9Z}BFAhHLy+_;5BW`; zi=!;a6xVt|Fdr`YBu5MUfQw=}pd?5I*WFov4sfPd6u037;70os_>%&ZUl%+QZqg;r zMrqpe3u2;3-}Hw{P5;GmnF5V^EJop&t-=&lKg3R2jjT%fY)P!rnij6gDS@5^N0XLI zYO(a^TIED}e$f(ygnG+25T;h4Qen$^sRa!$a9)cU3ayrD+*` zg8Y*hzjsxMqIM>o;U=mI{;9Qv98PjMS<3QN&ym0Pe6_u-PLxb&kKTBBJ;u_#iV2K7E$Ij zAeuJ#URryk5)uG{+dkBja@3<72s$AvWo3yK8nI4d(^#9)n3a_2Owv!DHluiYut+;p~=?g^D^!NhYfT`Z(X2W)YA1DWQ> zOauB1+L2;Obo?bqeYuwjXdH$CV9*HAe|YTz)DDQpfilsSjRI)Lj@IdT6Dms5n9(3K zb4G!zXc^=L<&?(s!b~Qqw-cbi0A<-qaDZp+oJPaCh)&x-S)gghb< z4j0$5SIe8I2+5o830x4vU{*#|NuS8s&NFHws8E}2M4AQfY@>0 zfs2bef7@>-X;rGC>&dbK!Y`D3k7dj&fN$P?Blv1-o7TA3&d3IIjY4@;q?o^t>oZeA zToIb5k;RHJQ?oZSz#lg|(Ss)HjWko_ylUc;z9Va7#T0h5$igze4>x7~Bseib8WS0f z|1lCDmq1*cLdU>P$vA^*7=Zj{7*Qze+%ZLRENe+Ey?p#mU`2%Yavp^NpXJVh37_k( zQC=X)v!AUvX&m({tjN72Dy+brVW>aE-CPyP&)f1uUe305W4MW^IKkW0gL+|=*cSX~ z(=GrUowZQe()4#(YVIt%pT^g587Q>6bxAWI7Qf1CB4#I^X_&7YB`%z8XXQ^U-_z$C zWhOwH$M*p87ek~OOAplkZF?+Ou5EKx!4oE-Ux-3t=1fABh*VJumcib{`o8SN zJMnnO9tc%dqHg2Lxc(xX(;;9%0VZzVf*(~|qrqcYR`kYabO(5}1?(TX$Z;-`wRlhi zC~EnHT3Vvhj`|Cvy5WGKK zsa=+0#iS!En-|c)B2|v6Y8FqgE{tz1Ty0!My8xw4F91aqtw&%3tu@)e<~Hc+W%FkS z25Q);j9%IdK?Mb^IoD~b+PGV@bH-jI`J!5&zk@0nWM9DYV2?UBPoA-K#;Hbs=E>5U z=>Fq3J_lpBr~IPG<5b?!*%WEFpZ=2n&iP4HjaPR5>%_ffq~F3M;lcwC%R^&8m=k^! zDv@GZ4Rp0YfEv}@eQmtpyws%W<{A;6&?KxX85~PSsaO=J(Kl^Z=vqqZNJ#MM{wJ3*{jGPq9Af zx|`Ig=t)UL^l(YH;*_(u)Wb!}CL;B?ufvhspX!kt;E|v_ZFxf@Z@6d^T*hEYtP*xj z+}8XD!4GR*oB5zCg(FZ#ZNLIHc znz1Y%j+r=yqT?(FdK@43>Sjl=gY`3V*az&5PeRbU85XP!kGQv=IzeqC5+JkbAR{P@ zw4nnIVEI{BMz|QLCI?YCJF`l-L2w|l(ZZr+{9Ty96WT17z=B#%B|qhQ*dN!fVg^P& zmhfUv5bH(8;bJe46H`n$pgm8Y@hmR$i`nc}>vmQuV|1|E;(Xu3rOldnx=ij4X=CaK zjebQ8#ZXga*q`)8DHk6cZSk1!oWI@UoAL9z9iBx?HOXx~4w`Mf-`%G&UqHTPuX}I5 zm}TW}Lh&}5d{vn>g|IS+=cVKUd5;6qS`4yH zF;CgGER;yW*)=1mG~zW6fjnu0=v)`2sQxuwh{3TW3eC!)S~^cZB#=Doi!^s(nE3uu z8^Q0wQ+3@)EZ$BeCjZPK2-arLCvmEU5*HK(OvTB}t;t-{Jl`T@iE&<-iu-%pRvV~T zjI-)Je6!W!;uG_NXrs<(a=rfov;;UIbaShGYR>f)*kpkx($KrQ&g*7@Nqo1NB;dW$nyh{Aro3esmIIHJUPV zjKqr=?-2V$DV^m5g7wo&U#mcP)=>kB=wpR@%O%6~tcwHgFgj*mhsj-7wglTD5iHyx z(Q)LYFb9#G2zm%GA-KA=27EFHm746;iSRr^ln05~lf;swMv4z%&O@<)wV>b9C%MWU#vTMNfm+A_5rz@&F< z&@?3nlww8dH;IgnI1~=WjM+JrIJboBB3qwnR|zeQ6}9*}!OhvYCAA{WCRPH?cNmIL zr5P;NThvKZ=^&Fhuv-=e72q4JmVy8yEO$*{Y}OhrU{7#4 zR&xRv23K5|zIWcS)Lbc05-PKxNos;W8Rf>NE}5ib0s`fZp(Sbo2Ug#z=LRa3MJm;> zySP|!U^3WXd2-wnE^n1G|2h;3HX)Y@gJXW=Cl6jgUO7`8zLlLn9p_8#7b<7vN-*_- zFR$@E-klc817oJ~_9~k9{;+i!d2~6DwAj@JYlLTLxGah>3RgKEG}aOBSEvyompv{N zkoTVDLsD~*g2L{){nK*4v+D8y%^Q)D8%O4X)%2fv{D)DaVT*sDl~6}piaY`sEAtX* zIO;>~%W74hpEJWtz)R-GD8zgM-1m|jTvSi-62gWc$M6Y9U2=>p`n;baXAd~@NcuIG zRP8OXI`WT8GCKj+W2#Ta84_UyRM5sthB8>|uZf~cg3><{u2H6P{~27Ep1MW45geZn{iQuS@h3)t z-6J-`WdCbPoO~QH|6m@4P>b~upiodWFaA&mQ$c)k-XFPD5*WVExwI6hsJCHdR#|~d zd6mc}kdXmb%1lx_$GE9M)iMp%WEOdOjLwH*%8Xu#5N<|uK}}E}#*&=SGSt<}dC}a8 zMb5k<3CpShh<6!_WNbcfn{-VL5uMw{<|Sn-7qrT!3cl;8R#_Y;Nraf z4{+5Jcy#V)B@j9uh4u^o`~D)a6Nn$(J2N-4%FDmYcx7u2li-)REQWo!4Q;-IQ0|JQ ziOaaS{1Pxs=#7{?<$Y?^)tI!-r_xm-Aw5QWW<`F<*gh#f*dL8Yr zIeWad9r@`{iBsMl3#&+y?6QiU6NtmeHyVnPaeQ9MYfB*l*IPp`{>4^Yw~bz2&!)R)^s1aV;*1ZvliZM(v{OM2Mzf zMvuV9ii*-?Zx2LtRdzFh}RsQMB~v{J!H@1u;eTOrV;cq0%j%xsIfbK+RK! zCTLjKPDrN|;0MeDnTEPf2iStRP6fDvw3qN#?E^Dt7obPbZvqewX6#c5xTF?n0PqB) z;sSY=^Pd9cpzARJ%tF^s0FZdl1++!pHU+!|!65V3^Tz;m25Nx5t_1Ky-cA9icHT)~ z1)7Jj4+9_tibN%l96>740E`48__%}mNPzm_1>zwT2#WN|7C@z376!%e-$3w3*S{tO z>_Xpe03Zi(pzEONp#x-t@<*jnq8dSt0HIR0P)MB3*-8ZSsbZm64*ur{OEO<@JMsUvk zn1M&B>iv!Qz9E4=#s!VhhbJB* zNrF8X?_Qjcd!jOtBlxLXTMKM+Na$PdH}C+xx6|a_!ifhq+)vzZFlaA<4X22iGvV*W zq;C#JGsj|Cf&)8E79}pc6KJ!~g4w^9YL0Y)i+_$P+j1iqituB3-zaU&gSU8BIytx< z4>RcJyap39u@bS}9d)-q*h-Sj`6CHsdmhS#I^GR7L}yYE3Fa6wtEt_~cD!M;GW+GL zhPx7nm)=HAt2Evw%gg2M80OdWbg@bgN^O)Dt3mJP~V=|Z!nI5;O{gK5508zvwm0Rsyc z^;0Q=M?exLP9piA5U^xR*QX<}?s@c^(URORJ0rd9-9)|BD$Ko3zetxqQ#Y%wskI(+ zi^^@YUk{a7b%Py`_Z{*#uIF?go|&{=Q`sBEH>us$>7O|KCcmnUkcXSv59#?`i(g*v zF+%lj(Ap{K>n6zTc1t-tA|ZLTQak%@8bV5z=Vft)v%NnYe$#D`pJIj+-;dj50f5Af7D|gDKY;>6OEn7s62EGKTqMUGj9T|TRNw496gT( zemnjkU9y})brRTQ!ro^;*fBDdU z$XOjXGibl!{$ZQIpE5aV_RRK$PM7t?p!rZcJ3+6^)pOQ+4KjTdwYi5rG&*r>xjKe# zxte_f|0asJJKOh%-(0>I>>bm)*ratY_S;QBU2V+ZGx&*jn}6*OJGJf)TjPG@J=}Y3 z{PK-?;dtfE(m8HECccNxh<{H>n!dWB1M$_y;0W=J@gaTr-Uk1>Q1{a7{_vCX@spNt z_aKkS@TXuk&9pGV$EpL)cE_vJpZy0ToFif} zcvfC*`?3a|_wUE(4}aCyC+unx+Ry)dvHgE^ME^fsTK}2&W&R(;@BaXB|0j(5->~jK z9P2kz`@d?q40skd;|LX(w zm(>0D%>K6hcc*`l-tUp^9|rmRi2iQmAC&j+*?jNu@5y7?TnD0LWqGAtWY91c4$@ zq##8|w@?W$qdRHAaNGLA~h`OpaAucH#<~QnSPURQ3#X55RJ}`gNfirNq8)XdF=u}Ex!^ioXDQ!RveQN zq(>Ih zpO=bP_pH$oH51r^$wv`CDLoAu*ypd71*l!%n=S19t?I*~4e z))$`ooAqrEfZbN88lnGyOqEGppefwLkm}km9rI3ZfM0ogR22Rx!E-yzBT57GgSQeL!tPuB-qK3TwZ=eA zKD1Ko$v)kcs3&E$mEb#35MG!TxkZaR$pz{IywSWHqSc9=B2NqdsyWPiuV1 z9z0KzO;WwM2bU$iv=BQv>*|i5a^7ao2L?iIr=ro zB*4s)YngJ*)R2S@h>hdKeT?gve?7%X8lzE?s%m5j79}WZWXUOfL3J=eJ;XZ43wiEk zO4^Daiwww-n?OZ{-S@pcz1}+Cvmr4mj|dxMEt-A6HwYs#f!bl-Q79!5R>|bj49|t}W>cQ-jGB{<~17<~qP`jq6)QW2#D2N46MjXT&UrNBeJc0ttY+5ey8J4_p|c)og6rVLkRwI0C@qm}}qX zU+zSKP#-_FJ!}ER)&XTJYl=}NsLjLlK&Mon)qUNbk}@v8SzOOH`lIKK!QW-{ihYGU zza!`ubXJ|h7c%*m7CtW6Ic;~a5xPff+oziLTjZNEEL!J!b}@=bw%Obp|?%YdhdJ&D&g&l%c)O} z=3k4=2|Q`PNM0^&onP8=!@NYHMzKaY8)+vjFPh6uIp9O#VfmU5<(>*_!(DI|IN}9I zbUt|Xds!IJSMUJr02ANsxab$Qx?nc~nu@^VD+k01e@qB8{P8eyRz$eJXq>2S);YnZ0y2nX z)WHx2Jk9oBqXh{u%+=BdAymbn`-5;A%q*0i14VQmFEB;GkRcF-t6?6T1e-7Z^5GFx z))ePqpz3RYksOLFJ}U1p@XXN6A~O_8Ph$??PSxN(2*t!fN1wr0M(_g(eGK}yfnXvu zs|Ldm>+f1t9|cC`pgya92A3a<#&N0O?7rlu%W zIf5>vs76%t^7OFcw*-C~(cDYxtS+HV) z0$0yj&QnF3m?zrTk;9O#7;w!PjZr?Byh%BEk$Tl-9teAp{v~kxvXta8{8C8tY^#87 zlR|k(v+R*R6J3FR2a6VDcq12L^d4hMfQ<=1^XwoC`(%k>@aF-HQ-{6^WnGIF zdN(VX#wS9>pjM-6Q~VfR_pIyZg#W!{`kNqTI%f{<1~#?eB1s0Tfs5v}2Icay+}!^% zNPtpdqyCLuL1bdQe^;hndc5z27^*I;tz%Q6df&hx$gU)iNH~$Me?7KF>SUH#)ijgl ziG_rHfp5i}@WeZOz( zQb!!_-o6e#QlE9+%u(qFdhf|Ioo)-F5>|+P_>doBD@IXhuz_)ade;w5inG;KoNV&q zrh=x1+R8(|?BVY;{OL0*>N>@5{uZF!+}g{fl{(|jn8KQdGVC+=c;3lgRYME~Ex2?5 zN8W={=$iic?$OCZ7MJDa3sP2rh5{$CW{LWjfk(c?WxrI5sfb{~CE-)+BNF2RK9n*k zQ&B0d5jL@rdD8$Hby>--|D3E{NR6c z*8;;saV_C92G1Nrh8haeL|M|b6Vjq;Cno3g6~U<{V#+#-O~&TIpsn;pcN{LD^%ZcisF2sfa*{|*me3Mq z;o7Btn6zAyc|rZytice14f6&~5UN|r4fbqkLD_?#Y+`C7uuG)38+Z=%RTLwFXyqnj zd@Hi`2Cb4{H}^Mi$y-$xXgx+i!b030?G3cM7Vr*L4Y%xwqOb?0!oJ)N$O5vDbG0-k zUhhxfwJ=YVtJ<2Tq)wlUU4Pu=YxL7REaSDJ!zFNee zK+4Vi{xa4DL<~m8IV2_oh)x~hQ8FOJP0Gv=%HsO0P%^DpKWUg0$0IBiiBBoB99jKg zR)58+Gj=ZmtQ3Re{eWGzEC8|%P6LZNj!>8<2*KE~KS?rOb*4QN()X%l0kBOJK(+iQ2 zutUY7SOd&fEx2(MCfMZP2SiUNDgP&|N^4J4Q<>12s&VM9Q^C*y!vo%!zi+)EA z2T*hc1ADK=D&##j86hqIP8dxNAblFWow=_FMeLjp(;V0So3hG6$7jOGcM zQ!*t21L&+*alokAtqB+t^DD?KZWD~v?w#>#?7(b0h{O_6Z-@M1nuhWeN_qic4 zRk}HPMU$<~&V-ZZ4r`#VUVm_nBx*T&D%8$$epN|VjQ?3(d5_(gTw@V2j}9r)fEeyG zIaoo1rmdX@tcVGT&`3dOHJ!|0wL*g|0uN%!W=a^zeV0F+UPT2u@nNd9ba_@PvE>7P z-c4YVo$fyB%muZ2`d{?e^qtdVSI@m2?n_TUrWd<%(voZEOoHqrmM&xN4#xxJZ3oK1 zJFpY{?GGwA7AqDlr2Hs{!L$nzgVhmeEyK63k%m;`jh@D(#JIp8j%=if#Ss}w& z8nkK5uX*D###AQD$MkS5Ni6j@IjM_(=@Zl*pR}XbvGurdJ~#NzU~^$rdCyK8FFs-K zgtD#ihg|1g!W`QAD)Lr3v~8E!@n+~BM?-cmS0#tQ3awp(slXzwLS{*!GZ$$TNWBwI z#5M9r<=llC$`CjNx+p@ZRZVRbnMBW~T(jEpr`5kpHP(Go;uS{zynoL7vx4u906_+$ z%Katk({??U6jEt)35jLDWAG@m&Gsk5B9CZp@P}3*5lsGK4UiEq6BS5-EHEt=M}ENf zOD)zK(eL+UCDZ=c8?j(?ZbA!I`h{Sj)D-jGjuWRXM1R1!-7hmuM`4!DQO@f=-k$2S zGP(V%3|{qo;61R`Tm!;|J1xUA_ajEh{#N1Jm%+XeHpVp_hM9YHBCB!b4nd;IQ8);7 zAj)tCg!o&wWUtRF4f5@k|95>!J}9Ayy$g%8Qs*UtJCP!yKpY3 zei!D0>oO=>%#xm1kAD~Db3P24YMN=9JAkM1%qAp;1FQb%pj{k9-mPgT-LK3H34s_D|}bH`5CaaSMn=!4d-s=@Iw zQ`V5@;&K=20{-=ij$UmagU#sKkb;lA^>KOfQqDf9Z9CEVHrbIFz>AD*+g`7dWkkrL z++PaS0MTqt#L^GsLE8W-2b2T_K!U1MOr)k6psP*z4l?oZP`6m%aBni7mN}3>x@#V` zYRoh=BgWNh=6n)m2`#;x@{V9dlJ$;I`wMbg(mw$bLC!x(DwRawGZn;K%6|t^Asc`d zGByoB2o9{+($K6KWjwDqj(#d+YzWJg;Smc_C53~) z5@48OP~h4yq{y_r$Bs%M8GEo43j1(X$b7q`OC-YLok!%uSU{I;=Wv~D+!R_=3#pi6 zG+tfC87ZCQg_I$MikLK2+3VF^Ya*0uDAhs&kCXWzY=M&kgfx2&kG_4#cl^|*Q>j&6 zyr5{fUoA)e7+2HWV^ExQZ`04=*g{uo^nWOuE`Pk;`bJ~cm@kgpkV&{#3QCRQ=awRSKSG&k6RftIB)b*~8t!9t_b zu&*bmv2R5#%L+-NficUKwV18w;r_|ZmZps(J3o3ammsRxp3VEI4d&RN=cC)NvjTTC z!1havA+-?;mNo^mSl9-U1};I2;MhI<%z~W8UMLB9EQ4VdGAJ{4Cm4i0B~yTNhnzga zsjpa$<23j~Zuu~H^4^GhW)1a~G7hYmi&f)I58JsYtOj{{q0+T4&sCj$6;c@0?#Ll< z0WuPqjyrb{GMjq`_vkC4%kGyl4u0Vk!DY@l!P#*=Gi=he*l-GnWxg& z_)}_uqU<*^s^dI4$X*yvs7hVn@cl5R_by|41}EHH{o+;{T7Gr0$_J(XOk{y`De8s{ zE=hnq&?UFaR=R+dCn?3k$ToI32EP*NDR_~$d!WOW0n$sNkqUw;xh)s?ZKszEEGj5z zzgh+f!rkQCt;oYpWbaC!+C)TINz9?B)5Og2X^uDjU`sZ6bZQSUNNt$7<9EM`5=ZA} zAa8O0h;LQ#J`=AlZ#@GBB6u%PtNJ9Qd!Za!pAHew) zuIxhiH!H$i9cCD@4SE#*MyF@Y`6Le!#LHKL!IFVvb~#;VdOy$;y~0^A3YzMN!fv29 zdO_aF7UAlDPZ+gAXSNP{sRf7X$ z=q0D?@p63BgGWo^X;ec2cpdF6$HlJO^Q?!smZZC#NY5Ug+KH7QgzBL)#&TA?sBSZB zqRSz%;_zi_84>w!usrGI8!Q)m(Lh}HMR>pN{0o+|@^|cp9r&i4g<<+)j+mE$Z=0$O;dn_!CEP!nq{wgv;=M^4F)}B{*C3dtyoVmaSs^~7j2~W+k$2#pkL)&d6f*4Vi3mFpthdBQ{w=@QmE&XO zcjka&P=FIucPB|hiNrjCR@`q%`i-A797EP>bC6Y8MJixMgy&YOiK~u`&7_S@qo6LU zDwJxK(FNJW~(1iRmIU2Ar*(bI-`v~2pld~Q%@1!hp@e>>x9bXNTzy6WTDXQ!=1;ZXSCP%#`q&^pp4F#=0p;Eb+U~=-c&`fiQU)Be0yWHHs;bN=akR&q_oo`H^!SmL^sP@9OnXjqpo&K z%caA26|tkW1N>wy^kgk}Durn&hk6Aq9xHOTdE`M09xt-2SVAq8v7;8+dK7$%HbX!h zYwmBS_RJ*0W+^pw5%CMcBu*|(Kd-J=uYi_~a*9#-)TPD+TEp$QQDz*vdurSjCfnZgZ+bLkWb3J0P zG)n@FU?qm6z|wO7hcU_wtlXsqrY=}N34$LJVRyU|t*e9t!=)BoB?SrXLxEtL%shM$ zXDW={SfS|jfJ>qBG*3(lnVdnQaXnb{=LJaYcxJQEk||1Z0!7JGnmJPBHj9t>4I=VB zD{#fRqD62tbWo;vD?I7m;kMgSDVv!GRV1|N&cvqnyjth zO=c*i^voa3;+Damm0y?(#Pg1_`8`Mja_~I#O7>>H3GOL&mEZ1sgA!&sAV>jsyX#Np zkYljd#?Vm2q0-kY0T=4nGn0IF#^?V?;)Ja!ppm+r&Cu)j%YsoCej{tQP+>#q_)jFr zl`JLR_&Zf*Vwar_uE+9R$JKRMuV%Wm?y71sBwG%hbimR^JG=E(dgu1>#T|Z@<8Qqt z#*_?=4~d8M^V*O2^KdsbKCbs3?kN^`YBJS7Ek1t$y0;&n@Ohz$1D6yy?ii?&@4jsF zK=1rQ8L(LUZK(##eUu3yBDf_kGNh=(xi_mjKOWcafyw1{wViGYGGJ+yybm2Ble`@( z=95@4+{t$&g@~;3tm8Do$+fT#R1FJ7Ajta0L`oz;+=v=Tg`|mCNI&IkgiRB^~2los3&~*1WHRc)_p@I{dQ)M?K&CX!ue_R+I&xNI8~rzoz<=0tj*}8 za@U~8hNaO9?7Do4U()HLeK0tKa_Dz{>|485EOSR=zyt;%w}51?m=cJHmoMNQa1e>{ zMfboo5dp44PA3DxcPwI#2# z{J1+OUtIdWnc^~YdvUJ`OgLr!?Bcv$R2_fh8!F3ExVhlY&VJbg$44@^O6soy43 zFvQkJ`c{+y+sqtWAls;xS8&8@!E!Z5v1yn$0(c@<*6~`0jk;pkeCwv}YlVS|dR+C- zuM==TEyLW-YnI^F&TAIoq*p1!s1j3z99>gh>v6kk`;T1Rr{KU*+eBSUlq9&HMjD^o z6;5m$XE#*1BWrSMtDI6Pb>l&43g zR6$kg-RO&Ye{d&KcA72snuLvy0Suql=wbTC1;VZdFs%Y6h;aH-ea6t8is{XBhQ8XB zjz0*jAolPhgG7*}03pkmiYI%AqK|^?YmAAzF#}xm&kxd>(V@NcR($M7fymD8n4Tnb zhN(an*=>3f5BdM@cCtpDY@NDXb|uhRU)&x#a)I&cHmV5Nh(WW(y3dplanVkg zV+uiwRYE&C3}RGgTWdugb^*#o594-`jH&o+KWtQAtUL@|vOg-TUH9D>O(@mfXWn-v z-mW-s0NS2!1yL8^JW&#lB6`gx4(U%THjGv*p@!XmtC)m|U|D2o@R$=*2)AJJFl#DC z0P{!ZAEznCM6Xzy`eDc9){PIgg)RDHm;T&EjGIaG_k~wiGK}jq=2Rt5ZaPo6?RxF! zD!#%VCd`QhIk7O(DArq6M%U1zmyL>bD1T&bsO8b_(tD>e@Rz===LOEuZ+*>QNFQY; zKZX5vSdtxFsTrWfQs9FrB4)|`C7eP{`HN#!abKa=%IgK<0g(Hv%nJDxs`bKcSxs8T zIOiBqLyfbRMH^(j9yCC;6lM^^rp7b|^x96ycZKke>)v|7~PQ6Y+FW>E$$EHZV4A#s-3P7UJlr0yBC;DsNyXM$AWBk{EEuUqjp zXM{WP)V3s$2~&^+#qBc@i&5PRwuC@0+Yq~c1RPP?XX3pQ?Jh_C-2RG=gGrKwlu9=DYp&()QsEvJtoZ zu@Qz>6utX8z5SXpHc3dgO9npR7)jm6Ag8vlU}J`p!9}+?q`}G+q;E>26_#LBi58`1 zhX>|u^EcKdKA6Tryl2r|H^lb9rf-kB|?6T$lDa`<2^X9-wggIQWKz$T=2 zp5*7}e&u;s6)zuzmZ<6`&O9OEDHm1Woz*-hxDw;Kz&o-qLzD!E%D3Y&Owj^oL7n^T ze=PCwD7Ny3C^1*tzd52umt{WLZ={H}&bAz|GI|i}_gvnV=76EM^s+QIoLQ=7<1TF` zz0O?Gaxt9*C0EBJcZO1TWcb4K9J^S|iq7xsnrgrcQKo^&%b4>6fZ^i;TtI0P&r;c} z($zR6{%Mi;lNSZou9aVF1apLh(=!JbHYgM!j22oFWvQ51ty@~9&R(uPZ^;|#<;i<# z;rZ)ms5y8^n*FuH-H<8Xe|UErL(dy1$%5o~{vbBX)t6icc*kzqXQIeqwtYfR0-q0l zPXAg{qIFN;iavF#V5(rNK+7IftBLqAf>@wNR zmabPEo;)lEpfb>W$A)$Xul;MiA>J%s_pba$ZGm@DG za6HhD9LBtUzHc@g1rY~eZ^@>hPPh}c&5WiqjX1O~k>Dfqg#Uoy7OH#$JQkkn9n;hE zRKo%wU+ffswc;I^{Mwx`lOJs26W2@O?tV}|z|n_#MdP(>pvENeGz=G1SlhJ*GFZoR zspz#{R1N(@MNmu9L>1g_RI}$2o^idEQ!8*t@K_YN2|)FJw^{^}wbE8-`aH{-Wc4g2 zv)|2;-Tay`R;W|Tya0cp{jh8#J*>9C!#CIP5d;q(v)<++KGT-*Kp{%X){6@fBBnGz z$3tjYnN|oJ_zg}fkc;Fc%EnO~6iFO=78v%iP-9hg_&6pYRb>>g8MJ}gm=%`FRNnKh zcFWEy&Gr!nCh4Vt{yW%UhWLfYwQBwB0kHoEs1<1r9D!O)cja)h=1@TN=zI)iLbfE}Wn1TBcrJjK|>awPi#6k2G?!_jd zhu=xW`|?shz1OKaUEg&@WwCULU+5zK30ocbB2K~e{kP5BOHM`8XFa<+xq&}SsTWQ) zp`z^n$@F^fVf*QEqu)7T1C6?%>tTw&yqFP>_kKZpGCtw4M>T|s7cko$h|EW116~lc zCF9%idfvAp&F}NLIpRehDyDfh0_6~)1*`=Nw#k(lVTz%9br|#=_YmrQJZE~ z@9r?aypGfrT78uMY?{T}54=O{5_&Dh7locbg7(Q&t*DqY%VY`2YWEAr6AV{Vfdt&n z*LyMo=g+Q~nJv73XO2=JD}=efTtb#^gCBp!rwkStem^|C)WZ3VRUry}VN+EbVgp7I`wz+f$kY&}&}^ufa~clVUA+;{V?$l|x_*q)$F@X)Yq502q}O1#aT z9Rm0@<2KTFtt`5|fV~TEr0o8fgB8k*TWX`tJ^35IgN3sfdJm}Zp|K62BRZ4&DV~q$ zbwQ*paBL@K3XZmjFeE&& zyYHx--oH^h79A_8+Ec)!&EHWw?x|_QD?s^ql&s_kV@1JyQa=H1Q=_|;X;gWvlKs>a zj}T6i>mSZ~MRPuoF<_rr>hO$J-3zM==EM!U8cuz_qjrQXP-y~|3UgJMOk$ySLKd^N z_>(>yJ?{S~^CgU+e>jlIRSWZ;aIF5;kEk8uL8;TSh#FGBM7nqJlo~%}rCL>9aVWrU z`II{(YsS$`8l$7L(<&orEpZoAJwi^RQ}24-!LnB;;IK41?1EkMx~lYkd(Ul@KD6<+ z{cES+jU?&yVL&SCS30TiDt1-|TVC2+Ks%ej z08Q2hY|np5dX~$cQb;|^c;a-z{Zn*&Gm=pNetm<+I3?u|wrV8e69KR^GA)@GS`2KO zHA0hMC#tN4BxhJ%vtQ)k|0?aDk8^|TepyK`FVL&F zqwAKK>Q;MN1Sj7r90o$>;_hyrkl~eSxi~CaJM?<A{ zy(t%?Y36U^VxBUoQJLNxUq;#sAvMJoD zHw5~BX6e5?cYMAk`J%Ah7&cqA=8kdVaBhyy{r1mE9Vz!Z(t zG-bC;t)QAe0htPJNC51W{n0?0!UW;n+K8Vm{1t-E(Pv`1?1c}5c@Y&N#{bC#md0+i z4ldkKR2wTdpa&hXh|i1uu+`S->Pa7Dh0ee!t)eR{A~5~Z{#)+5{e_F8_?_Pxw&31liR7QF)*{* z?&Wp->eKi2PpVb;Th^NQbrQO6-X^r&MxEW#x5m3M&aW@sK6X`qcVyf5xCk!JkSRz7 z*;A2`BH@4`sFHQom zCN=qi$g0AZj|Pfsz=|?>O9K|?nKpcXBjP!X#Xi0WBe?B=%^^)T`63E(*6k4gtU`#Vf8)I7#;b4n(&+HZ<8910{!b&B?LKX>Tt(GKdwHkbw zhuW|PjTx|M6hvDWMmQ6rsBHH-UoYI58ut!0HyMpoA$|N}t|)kxB=VK9i?H$Re3M2f zebo8ke8Mc8%=`IR*@4lh#awJKZyD8F!8qQL5{Hf@8?~id!uECLVF9lMY%Ly66@A58 zT(Vu_On_9+^qZWA7baDcwNY*$`!S(O6^h+wfJ0wXPS&A84P3?n;+ue%xUY~QKhkXO z;!bjj9&M>`!LQdYwC5bX$2DYtJf`l4HPE*Os3tGu22oXAzk~j#5)b zopp>%aQ;kGgG<IUK4Cuq<6=r+n&L1DGa*H49?Hgi&e)Gu z$d&(pF?W_hk!%l^Z`|Eo8+Ugpv~hQLXxy!FcXxMp8h3YhXq?8~b?GZi=!n;_?~>)nFY@)Xc*6q3tLYzF93F#QE`+$`Q5>!lIej zrVGlsN906NXp3Q!)QDg;mZDrm+KqT~LZqz#MOqKfkWAm(j4AenRh%J$3h3*!eVGrW1Yk+MJ$M?J&~SJ>+m(y4;vA!QAl9w-|}w>+sz?CFb_;o!BT z;Ybh8HGaGCpsz~>>#um1Ef$Gf&sKUC4Twnys&YpB63fFU{$u4{s@sQTn>^CQ>}Q{FgxGw0{$U$mcq zl7pkYvA)&6ph|52!y>i+>W277tb&d8zrZT~CL;bO6aGH_SIXh9bD!kIC-v|jMQZ1T3e_sDiOMF6MOn+VJKhhBYPyw61PP-^CtKy62o7LX<=5lC31tk_tTJVWOwE7gli55Bt zazP#m`1pteheGBd!n1-h%9AitK$k~=zyiP!{i2ZZocawT%O`8OB$YB!Gm-k-Cm;(v zy2{Q2K=E9>+g_H?wUsY^EWP}4>3E>}cI?duZ~%`nf9TV@mwY~7DMt$5u?`&| z@r(C^Hotn6;|ynKks@Si-~vB!(|b8)K-;VJLw^JM&Zf3pkhV&PL>w!k&l_fYNeO&G zK(@eJ!h85J#pJ;F0A>MpA>Iz^0RLk%%T? z7WJ0j;zp3K+2+LIOAe4}JwkAEuI55GRc_hp=oeX8T z-!&ZzkWWn6k7iRZJlTgcVqX_RL-bu7&b-%|S6yVZrXwt86V=racQjrrPGoR5Q_O_W zklf33OhK85RwY2)zj8o)Ltv4l zT1q)sE-OOx5L09bcb*YKbBRQmcSn_&U)Dt51I~T{0f!NWkqhi$_u9Tz8G9*Te0!>Y z(K4Y1(-?6}ZVjvqYbfp5ZB7Oc6>_|PVl2A`D)a4$C)SC5*Dt1oHKO~F~ z#fN*h$Z;=Ze;+K;Gh~~@BMeWtFSLisN6`0N@B19bBb*qgJa2O}VFa@h<**Jj_6=t? zc*HA37M+;InO0bLL6@W7#Gi4RQBCPywV%jMrtB*{eV8v&w5@d>)DV zrU+MP=7Bg}?g3#&XeyWWSL!yA#wCi6rvlL2?nNr0!E1{?c<07rmV_q**mnF?168-R+9(njpQg{LZY(Xj zG{_cyoW4Rn6la6356TL>l6JT7pTjk89`P=%ElGxVbcXu=^67gU2rPL4er?&g$?H;i z{hF-w`jY*jbyZBPa3zN9^s(LQfcD#`>apqMFf978WbM)S1?02G%Z4Mv*F`4?qqVq2 z7uuxR`)S9_<(37s4ZAh1?Xb$M3G=LO4}87&wfG>TjKA?{{+FVdY$+aj?)Gx0;Vk2- zE9`1E*pWH|9}M=c-N#kuu4DZ?oqXIp?VY&ez5Iu~#U}GCv(f`I@08q1@LOKuFU8XWvhVl z67hRZs(HRvzk$+VB-O|VTHmYlCuNayDo^r#4Jsuwe6r*!wQv5uS)!Zb)dd%M%0Ir! z*ZvGZmc0H{5#nxVd|mt)xVziJ<*x5s3E;Y01ay5Y;J+k)FK6(t?M?Vzk@MwEw)>3j zIdWl8T6EBCoNbbE5oaypcZ7WYYIF@BiMFgBysrto(lppv=Dx9xja@RAh6YWk|IJsk z*x9kYVl{=YwY12kC26`VP?rLSS()4;ZiXC%V829#NwApoi;tXV53MZy1_#Mzmjf)Ago4QakAqxS?L^ZOCoMr^b3a0YSN?!`$0QC^XKcSf<$3V zuaN^_zH()@#j*SKK<5b3q$R@o`7$b^na^@er@;YFy` zH9O^?D=NR!!sbU!mo7~po6ycgj*(_^@v_}5$BDwgWiq&PQNUR^ZYRiunIZ4#+Z6?} zHY(H<)YWt!T?GspPbMJ;0)vT|P!fN2PAX6lz8*7P7=iC(u5Y`0r$bRky~nl)TusxY z`q~Qarg_x6xcEX^uZm5ADsFhrbJ3kjAIlWz2Np6nN z;3I%94Ha5;oMRWZmYAo3lAz|j-iOVp)YJq4CNP6*T67di$AD`8W_HGcseuDMvreiO zO5B<^b!IE!`yv!asRf}3xGIHUppKI`P?b<}_o*%ygnWPXGp(C5#4mt)Ct(q+&#{~C z#gDcO!qX;J55OyJDPUUsJwp7Ok=VpT2pKJjaRUTXGcdv&zZGd=QFqW@U|2_F8jtV3)KuZXV!P+QEC4TnX zE8BhRRzDEaI69xM0ApPSAeRC6qq5Q==LO5LMdaRW$RTx6=`fV~)Q41_=PbqYv$oaI>y+L!};y%w6U=FTd9B zAQ}WPBO&BLaMdvu`YdtIH_zUEmP%_n!nxmnNwZmi95Ak|vn8U7$vLS1N*r9nv2v

tMpZ=HPk)=q~b%DU@t-K}<0rrtJgTQm`cOa?Fr({dyChZ~#}RC6jzH zGHad+iyTx!J&MsZV*pR^@9(rRyWrwqOv&vNskZ3M&ga7>F-4daLKxvRsjA?$<(1vm z1!AP#b_AIBVfqH-gC_@#3uCGiLH4WUw7+wZlH+Vovp#}A1solc>2p!_Vv}ap7!?&@ zn2g$X!9EOaRluqmh5$nh|BC2DkRTN6ew9EdBF(m6T(M{+^q>k5DO|W+;b>R};xRSe zU6B-7Hz1D0Q-Nd2Ef?wsyz1;+rqjk`+uTP^U8Vjoi9YMbI(Aw(7@OoB4mY(}kRC24 znj3&^1P=)-qDTx{Gp{K}_Rqu01%AXaLUEsK7J z)D3a}Z7Ux|_r&{%W?fSUer>XhO?SKNCF(rVd_;jm0XiY<2Yj4?gt*i{pY;%r~ zAOzYw3s~^@1$Bv)(B5FcAnCcBn!!$t7xJjPLr6sm;hRs+ZwOE{1jyD?zZ;OcG;IM^ zirsL!OX_D7j1mkwxhjkO7A~*KP+hyd2P9QSp_qOfv751|2u|gn{2H4)`pE9uK9;ur z>I^S>pv%P|t$h_~<#_y5vvxKGR&~K9@Y+G#6-O0=#eqRKhkYKa(ey3Evm3!G3thH_ zxE+bScb!Q2*>y7)I;#PE-($O+nb_k=i9^WPC_@qb7fg(t$Oc2me{|`pkQRy3w30}BemB^VB!AxQ?N7sa%dp#m zM30j?M+Et1qLLYrk2!C$n%Y|zAVbz82Vhc5-7~7383q#)jZcCPYS4;CY0a#ef;y8i za|ZaM^vXbT5e-3=S7S@UdsnXjuJKkDU@-bMOB`tI4c+|{Z}w#}jEZ?kDCtQ^2v zt6#Vl`K2+*L_HI}xO8`2M$KoA0VSKsf5A)lCr@M#l$J6-lCsL})c&c3h_|@K3jQxk z9`h64SM$w*U&Z{6nKD}S?Ukw6Pi&qi=mGWNV|=XDs0Wv7rZ`m1JE&pLriOm9U(CM zcy3)^?%+Nbic15qD)w$t;dV~k?T-seCh?mez*J^FmLMsx0bqYSXR|Z(#g~;Ljw-YBZClvSjf1(V&=8nvzUp4xL^y<<*>uG&Jh;%#YT0eA`Hk#TR@5pIS zqUV);(QxPc%S2AL3&*}ekf)^$0{V)p+r7`5sc5t;l;ZEkeX+H9Ouzr31*VGps-cUit&k5#^*vA>|~U%VpdDS7)QImXm*ESRuq zvRH(3ae{rPv65dq1Z~JJB?oI&6p-)@AN|a-?Vx0UTu%muqhvT-?kTudzYb1Ar4G=F z<;1CiK}l#OY?`i6+pJ2zp5K_A%ErT~T5fphhgxT|)TwpnrhzTfHUd3Ib;bC`VqsazMxwvoKA31$dx#^1&PwOpzku4yIS^i@&`k5x zcbrIX(@J$9dRLNSuqZ6IJ7uVO;PE)pG2x)G$j%oxZTz-2M&PNker0Vq26?<*v=+_p zx};6(ke+s-W5xIQyfRlP%lFxkA`9HcNao)h1X+@rwLD@rW1$Zh@D*N*4qHfr)#^L! zHZc>Tt$#RLN{L#_?ViS=?@#)rUdYWKcMD4xKAtYy^ONM+UYFZHJh)4ei9JXj`moht zn|i~Bg<>}t@M&V{No+)aMZbb+nPu{t;gOtMJN8p+V8MwT!BGYEFhyxnVb(w{+G;V@ zL-dNV1rAk=N+z=#d+4d}88;u9r!=Lf-XG;o(Q!ME-Ko9D%TtajqoAbH%Sd%=2+UMS z2$|80S*#kc$t@813+Crx8N|{cHgt=?IBMo>0PDl-sS&OLgCGH3TfB6=?z^vjd%``&vF?*8gon*md7y%8r~E?2FZ2vU%QIQ?x&W>qWJ}}vYlG=WD8UppJvGp__d5R ziFda*dfhl)S`UAh-5vTG)ya()OFPTtFW9SrWQcN;PCIj-(goTVkIb8a@IORU=cNRIh7 zoaGj)>cJP1@dH|=cRZTtz>dngTT!rFLfXX4%86neKnVjzQ{#xNqT?78SIoiPk~efr=#1`) z0js%YSkAq>ee{h)fYM8SzJ!L$29RcI8`-38SIiC=c;&s8*{gngD=dD>+AW@XUgAM| ze`0QQ(*rp+=zoxWh!sP?r(VClj+pqKym1)l;dO_jmezR`C5;39TV`@=)+WXKcZR91 zTl3spO+l`g=zvo7%vq*FCz;Acs?z$zVa*OkXsU@ybc?Zz8FC`AO=9>fA4CIp&I%A5 zJR~>ap${%Zh68NX(+9`H-n=5TuXO2|)Nvs_ohMZG+Z#Ro`pqK>w+qkp9=wI{9*;>1 z#JG}70sXbzMf!#3ZTjZ@Fuvr#ZAy_l$V0A`fi?iSMnIlzaB!J36Nz0$O;mkd;A^dD zP0QKJJ?Fi}3z8md9OjQ$zP_bvXz7AXnixHq`YO_%FcGyRfW zvY6zkGTzaAPR49MEKV}`-DFHTc^+! zKC2VdU5R+Q_HC|!R5$Kb&$;6TkjA#_D7&{|SUhijAgkj}H1_sW_>KVArG_7ZT^WBU z7U%HkZG`?jM{$QdS9F}kcTW#4Z7doqUu6wBQk})Bp^%|vR}t@Pu2R%c4W=TpLit30 zHsoOcv#^9LIa%7ovmPY1p$3C+^SRb?GR#@abOsDu3*uy;9^D>AH<4o4*ZuBV;68Q_ z+Yx*X-#t&|$nkEy`@Zh7$nRw5WC>PLa1VS~`2oV1iQITqea0ERT0!Jls~^EOT^4Fh zUWxC?>NRJT-uT?C1B$jk&ODQ7d6XL;XQs>3e}b1)2D{*K^e`zlRZ>i| z)**gVSk7?LNZiEW?R^NTQlx|onHcfR79w_%lWt;%f{K@Rsw+`0+wB2ySCzTRVV<`i zr&7qS56TuzkNRG-V{)ob{*;zL}``mQDw>J^3>HB%!Y z^uh|@K8){ad{4z$?0mPUjZN$}n@TNaH-?%vpQzUEMN1ZowYHO~;$&}jgH;AEc6(cP zr)jZ=&NsIfOerkR=OX2vs3ErV8uSfEA-lzf+9UI&4LKo$O*1v1lR?a73Q;^m$SX76 zc9H!3#$UBnjv3h_`ebZ4D6lJ1;Q3;bmW?_T_kOzX^@PnGdbhp09k!D&Qy$wGI?LOE za5^Oqy-Hx^553UBU8OnEREN?!DHnm9I=1fHbr{d62ZDa^^!?zMRG<+Uo%g-LAbGJ5 zD)drvavZkr#$dr2FQ}7bB92!+7VgGA%a<=`9R_N}#0^6*N=>~h=Ycg9u_eK{^hO@O zu8Cm!m-TB1N-u%@n7#a{KS6rZ(oYC@>fNioyuvGAN7uZbeUyHIUm zW#e1mA|LQg5r9M#Zxghs*&A98M?6$7y!V#ZgBmqUNusOc@9!=O#&o(BE#5N*%S4)f zq4V7rc~W_JZp?TeZ(>EpRd0!KeB-q+83`G2J>?TA`^LK>d1v(x zt*)e)GEDvove0bD+e7g5(RZ6_ns557!HMYXCZq$Re!p7HP*J>*V0?m(Feh-$&FAg?8!?O3pKi0oBdw*ywilX4sz00p~@Mb%H<$1Oy zxubX%MXbDja7T+70*KmzfaNjfLyG@Y<%HNn@I?1`SH0X3BZ&O~wT*sA|7T_O?{r8u zPEOW;Rb2mH!y%Ra|M4FDr9A#;HS+%shh%1D{O`O6aTC`546r^IJR(sWxqj2=8c;-y z4dS@>`7^v*^IL&H)e3KBB8bI zg%qJ-E;lPJ^>cY=t>B@N3SNJJoSNH`XS{{ps z!}%BVKTtEjz7#73zoDG^8-1Yc*Lh2gzf;}|)_L3K9?)wKGH!7{VO7f^{C+h%Vw{n( zOUv}#d@z9sSHJxMqB&O$_s`q#f0U+ubo6!@7q|3%>cOOXE*+W$@u{96zIOWgkx z7WnVN``-flpYi|x`b(hyBf$SH)c+atw;=!5ywACR*8Q{Q-|>il=KhnM&i3a9`%mG5 zY=1WEf8N0V6fVg2--in_axifGqnuuUaeRI&!c|Z{_l<)qCWcJl%Mo%M8zw6rfDjJA zg(!mt96Fqf2+9f?TN09=W0Yw31&Iog=$P=T6Ku#Z!x)zdzuuulAJ56e$MHgSAu}dt z_teJaW7V(B{i5xc$L3w9DjskEB8fRE3skc5-oa+8ac^5d_X%X=WLIg?d9A-}ml)(7 zQ*XtMDr<=9hsTi{V0i)ZyM|iRVbLAy5KX{gIOO&k#zwQr_UB{58c^X)-k;gwDkxo< zWtkYO3UD(-C+TkX{}ukma%xJ~S*m|m6+N*lw?x4t_fJfjnMD&iD-%;!oo9yodndu zU-+S-grPEcUf2OCSEr&P^`xKSfOuty$?iU{iKXPsSJu5A;O(MmR~_nsND3hr+%ZK^ zYdpUy0REo-R~?WSCzm>YKM*=E1Yf20HscD8^mM}?C{~4BneVd|2Zh%8JhSz5NnVye z{d%^6MzMasZE^Q2HK_ElK-f47WoKZ=uKvjE3~8YC18D~J+!nuQ51F_aO!}A zSTLinr9^PcIxcZA!>;~TZT{*OXVd8eQagD#^c8O29{YhL~Y>}`U(1@8!j1@Mme~f!$8|Aj#3SF8+ z@-DiEF@kDYEnKw^B8nS|nHlzC?)J2tmXskDI-2cYSxTYLH%fTAcDlC57D30Yp3euN zHSTIa(xhkdtZ=05K4|j3*(y$~8tZKDhf7uoA}4Lc%ZC8(lSxV+OTMs3S9eC<&nE;= zs>wYpv{&UV(Uf2b;PkM2l~hH|{XRTpy9a*_1G@(cxmChWT5CFt!%qk8g*a99(szL`6Zvx|KJEsrjL=bVZjyF{t{x0=Y|l-n0aJNwGTKL9 zH6W3F018m?aTg z`M3gg!XVsvqF^6CO-HZgk|PBe#yVmbW#z5S*Lb>3U|JKb_9Sh&`ef$O^$v zU9J@@px@P=j7M~-J~CvfK5mEjRo=y*N?$=jJ@{I7 zEcexOsJ&glw4XG&&XM?TJ;h5Aj-->h9gjsgW;kbV032&KW50vlccuiI^Q-b}{m(;O zB(C7Udks7AxVE1@Uhm>WU2u9ma$InMJBevT4~v;?LpJchUC$Bnz+rEjU79_}x5`_` zQmUUP$c{OlZ*S?B-BCVKmfe)j$;!7t_(PQP$^w4-&Owyay{$bcw!9=?@VD@HX0LQm z&!PMR8*}L$$*f^!S-$CpFBV(gUa8d|y2rc1v;5UD+o81EA=lJl$GuR8$JLbmOD1tc z`^Up|+Yz^r-BcF0-c6BwR?u@!F+N6}=Zf%Dot!T@npUDeaFqm~fU14-EyYgRRVN?I zD6@x3s404<;&o*rO-q6=G#8lEj#AKBzJIkaO^LMFvxJTTMtW1)U5^?+`5?1eDbvs-xyb5eRYfLZF&bxD zI_y}*Qy3BBTG5270izcQ38k27QS|)0jL7OwWU_>YXuo%iq7bxjUJS~_jySahb{l2i zCCt+RVw|eF_x>lbu5dz^!rD@q6`AH4X6^1BaWEIP@lS(VT#d%&7LG870JaY6Fq=`} zjS$?~qB%pwXj-?D>MFP(8Q?}iI9mKoQKU$DEOb>3Ek)|!vGTC+bsZZc;Yp>HJ!%Dc zqcAFkgQDSfDg~`PsZ2|>HG8h~i*tr}H~FsIMO96QABj>+gf`6D>+7D$mRDnao+8&Z zjoEFs`!IXBy7INBss%|hCpg8ovoVDA>L%ICuXsKCkDbr7DNMpV?KbXA{{JHm7O?z)a7f%Ic`E>sy2Tz}a%iXc zK}$3uA*5Inv@}ZQ8e$d&{~p;66=`M;=G0~qlM@9$aM~cj_;)gnAz%V zKQ8W{(<;$x8QAk?!#K)I8zqHPCq%yzsd|Io;sh41R5(j*lI!TU*!ft7E7RQOVv5Mr zR&%7ic3ITIfQ6JCB765D6&aFI^ad%#H>lZZ>d@KG3(x3DhAA12wsdl>9Hg(CY8z_| zNK%z4C6T6ErW5a$vWqH z7@c)hnKp$--nm9ZTcAPW8nq-+gKfWRBVk@Mo`soOvZhm1$T9U?xdgRm;eLJD;8*fE z0g9cJik-3k$Fg-O#zUVvGD#8@d224e029y zj(H9^w-mMZ9Exwnw-AuVXfA0Av(%A_trVsbp=&r-WK~FpQ^ijg2rrkFUzOu z{`@-u8Li-OwveMkZC`UM{1H984z)^Qv8Y6B!CnEYY%>058eh(mta=IAry^z}CSQbm z-S$GQ+9`4N;3P>WBzqcgto{TDPd!T5X*IdIgxufFXi#*LY-jRnTqycw$ z!1hhS&+W=pB*ezTs=hkUB2OanX^-te!dfUQiE4?O?=3QtHS zH3tL2)a5VHJs#>?x8O_4vpaF2hm2X8oF_e~=(e5I`(&b zo>mHuFDp1R_xf0<2r1h812L$Io3`xfT?#a4Jq5Ft?*_s=GVjn+eT>KYFk*E>NO}A+ zWmjKnd_VsymynlA%)G11bT{tQAN;$;qX28CMxV!q9g4(Uqo$bYDIdITl*Av8xc0Jd zl!V8INDu|GXrD|%HeCumoDa7)<(mly05|h#It_vLX*vbL=?QTn*p_dvw40MU4g=~r zl=`qK#)rDe{X7E={kwkEroq=h=xOLlfMm|YW(f})Nt@nn3L$F58dj6#=hgfR}Dz|_B}J*zHL;7pQqi4iv;9eTa1F>Ka)>H)65nuoJf_Ek7eq)Lj2*xa82@aNbcv%r&Gc0u`?92uUHNx{Q300 zW6u}%Wb`*042&jHe`DasR8ozUq@y|2u3BPpC*1;^p=TT4#t3lj!%F%2)ETwfKHkNy zx%_m1@X4cepWXc)^_s!u*jF|qhJUsqs+xg0d4QU8BOj>`DKb?CPRvY9l5F1Cj~rzubvh9$Gc`x1eoBEul0~Lw zQ_8MPoh*aGm@!{4!)h<`cOC_1)lf7ULCe-?su`C|`zBaDs$3OzFLC@0Y5(eBEtC_hoxor;QSoWTU z6UI+{Iu6W9eQU(e72VNT&{RdCdXyr}BpzRmL#|fup32K=*L5=mSfslB7(_*5p;UW; zo|)sP{l+ekRCsW+bCCRkr@@^z|Jv%U+#EJl{_lxl&nfVYl(%u!+TldyaMOX#h#$`@ zW5xV$mzC^sSLcA}pK*eF=#&Wt+mC_)n6T}E&c*h)xe&3o{Tz^%<2iyFg0=x9e(I9J zBpO5J?2fIu3vjg7W{yIcIC<8PZA4RW)O67{U-J^b$o>QmV5?Hl9#SLUSI!v5Mx?zb zDm%H+^Kc!SSOe$PKv{D+?+xVdA)kV#b+K{}@$ce&IarTFvI9Pffd&`9aO+uk-6IJm z0O(Q*d-D$_#BCR-qzyY@-Lo{sA~t0jzH$!2uI^t4keJgkSHg(7uIf+rpg?Af`Bvc` zj{BOyvm6K@%0h8|11Uv^rWk{~RX5w(TUcji!$a=Of+3-lQp7Bg;X~^B>1bOtcU$|x z@pgc9fp03gn*^tP!*;&vc=uw1|AYUGQGc$k5zftlV7=4pSniVlwjXbehk2}6Ho3PH zOQ>2K{a5YxHxM@otkXu@IWYPuf@Oo&W8r&w4KhIq%*?>Gh9zJi$)YO2@D5phRjqAvRqk#3_~$-BS4*_x{PXHTJPV@~A?U5vxDVZO>8Z$k2vzxxQ8hFz38BvQk4GG=8Kazpia0o4Yj=(9H ze9XS217U(VVr_wdv|IY9fSh8A#}le02$0*pA(<+<0?sYb&ha5P%X%f1n}2;VM7mlO z5WehUkL61vK$g=7*~9L({9^FMtIA;d7zpdEu7S;EvOAz@2O`9a(Fq17g zV_*c$TgFSD%8O;PWLjdu1<85-DalFvN_G(R!zeM`p`&0p47RpUw-!c!IyYf#A((V1*~-BFDOpe$FHZcF9+%dXqnmd zMc)snRi}pX6tRB}m>gg1ydRiCyThjhy-q$tFF0>66yK|oTA-OQ96;i!P=8g33YgG{f5=9mwRonQM>8V;dIU2%PbQt z11aX1;>NL#rpN(wif>v~KdyeZzO{b7zPkrzikKdpNuFt%xgp~PVEtJF`k7ICTLW#H zIUBKxT+BjREbMr|nJ=SZzmxX*&PhE@+)QQs(!<1ATjxoWpiI3-jkRieg$6p$OCC!J zA=LdDP2_!Fzji@`$J?vQbM)i+aiH|}O8UFYy7h7%l(ud?lxjl=;$ZhV>5)+!X%SL< zPA$Sun1HL0G?w^9T54yieoDXZ&QV9`c%IJUNw8I+UdCR>!@F z?+d}&oZ4bT(L0b~@R<$0*Q3Z$)WdQ5Y-v4?HJ|%MWfR+knOVD4pz+6v4G;5K?GXI< z4~Bx%t)*NH*xppQuQ!E&EQjJ1X4n8DqtrPL>*Y(D@D&5=5H&5zCf#s^W|bPB)JDCI zovY?!68x;i#ob9hik9!>WEmxVuN}(w(Nw-5ZDs4l$e zP=L9rpSm;|O}kgzY#a7n+vZx!5Z-Pz`|?=lu1EP8GHR2XEiGQV=aJN^mKtsAxAWoE z!!BpCrZi%<#*#{zHl*%MT6N}_NnbT+88JguimgHpIl%AMZD3R^D=^i>R(_aJyTY_b zZ10l9>UseM;q$(6%;9mPe~&tIg%YYnK9^(p9h#kQyY!V!fyNlyimijt3~NxIy%t;Q zr@J5wxCXU3=xCWzQ6zjyJY@2`iyW`-uUM3#8PPUt zS~bsiOCUYt#E~c5$ExX_PbP}8`%Z%K$kY!M#iSL{+N4y{HJUUka5no8g*#EQnr_@y z{S0i`GJ%syF^bz6&k@A{RjOMcO~qWgySigxY2L-&t?p}5H`41nZW{euzr-{6rjl3!C4*znUH2w_QB!ONFSDqmu{@KcBGNKi|XFoH%qVe2>@}Phg2*3JnYz z3T=6^4GI)DOiVd;YOQLlnlc+1+0s{N?47hoT+&W<)Ou4}N>1;$`tpZ<~X>n zmubAfH}(je<9_&r9MQVQ50RY|8Xzg8{S;=09y-;A;6LG8y6aDEFqNWCwzMhuM6)^` zMf=W8dq61lfTnsNr0bLTsxy|Gaz?A=rbKzv$r7h|WHCkajll-t3y6V9s{-k0GZp~F z=ti`sGk|>b<9ro9LW&H3qdBc&*gJf)JXjo@pS4f1HM>Io3>ls7q8!^f_>3I5R^M8~-s3F6eLTLxl%MN+*$VHLNV zQ*Yz}{&60NfMZJ;0$1^fjXOv1>luCQNusM1T9Xi3Q$G;1`v6hTZ}>-XJY@4M+irA- z3db+P3yjPQ-y&aB@Q#g@Mf162NEh2Owi7C&iCXDPS* zXoc55Yg3|_${Plh-W<{i&csh%0ld(Q)Qyce=L9SlHbu*mHn`6gT`i{W?W<2zAXw0` zM*cMom`)own5Fz|=ygY6E@ScG2%CAVC=6F+6p=fxEOW%n79)6M!gUGTU}S`z6bn1% z3LsyJF`v}JlI15hcVuzosX7UwVcryC{z{5zLfkOjE@ysHvCk4Cw(r*nUOP}&E*Sl) zxXZUktyBtoIq5~6-w(jmWUi2AzLVh_O)VXGlkEiQ&=;r5yHo6tL5w+`C5R4GK z!7%F4wK;nOL<-OcB0&rq>y9o{q64Q{aDHji_Ae00Y15Zp=Y`2Sc{UtQ$2%a!GNs-~ z7$0#|N)DEzcF@J)F{t||h$ULikY7&SS$_5pI%i^_J^v`_(pvwmz7s3O;%qDQ`7pvG zS`3$$oPK5AmrUSc;0EYv3W3Zo@<6@7gzYxMG}FLu2yY!^_Y`PWU+NNN z;Scx7l6Wt3yaBEa<#@ zzT6*dc)xLIJevI?-G_DAFJ&TWIK2&rI#G-5mz%e3UOph8ECve>C&}DTrAcHN=%8}g z00>9RQyH!YRH3;`=Xm--mFRXaP*OkvYj2kI7bo9Stq8*K3>h+eK-Rx)x-atIQ=z@W zu@;&Bbe=A8mT={wg>xRVKqF!NNL8`(utaTFh1>_xS{I&u5Mu=~`YF^jLw( zQCr7Ne!cy4rr$EU=wC$gFM{E8>9fd~kB={S(q0=lynaWX{4Ac?y{}k)>5j#6Drb`q z##g6p(s{1T_gZYnu@1v`lPoHd?F4|uB2{nNgIlx-xrCtoMI0Z$ndaN7UA z^qZcttgHK`OZolqx$6tIQ61JlE8~BsL$I@QaQ>@7+`!mg-_hL0 zT2bHH0h(UaQs2~pkmd7(jkO~HVB@Mu%gVw`NXyLdsa-QOFcN-dLTS@~bJVvqHx#rs zwKOJVfTkC8Ff_JyB;;iHMCSiAh5k99WnyH6rWew;6*o3FHFG3n<6wuTS9CPCQX%B{ zobqRRQFBXUrq6;uwtonRVE@0vAwGLb|3?N5VklIVzYQ8r9$%n~gs_3Y+_%m!)>g>Q z1a809W=8kx%V$!}(QlXIrNhwVok)Mz$G`XqWEZGu16(Mw#vYzMvkZHO8bJ3@Hwgoq zcr-`@)63q(PVbDCF2t>~O$4cH3GqTMhm7Uck#(|B<>dYpTY*AAPAAjSM`Sn3MQ!Ozp+7fE)D zOsE!&uWUdD@#{yTimgV^;Z!u44NG|IF^QYQO0q z->W&n%zC9$rO<=~um8p@&g0o{^9+XYP8)Nso~wV{sqFv5kcR(iWB#LugZXn)shS%( znmK6xTTSJE_%{CCpYb0d4S$aR-NW(Mm_MKYx2NMjG7|pyGXBHk@p=C5`G0#l{;g5+ z&xucy$KL}#-=Y6Dfc!Cm{Hdn=bMBAn;~z~P|B#vR*M|R3CJ*+1%1mJY?=urvSULZX z%!CCG4=tsI`$wJMtjr9ilkKK@KB@X&2>g8_^h!8~AOhq$ODI5yavm#C!D#DIJquHO zMVx;WDBVwc!b*g76x7w#6|C+4s%pyl-OFa^0!FE%1Enzwu0sxqO?Y(7AR^hgO zm;5CHSWbq@U7V`Qa99evCgB1QFQMd_r8@ksTj{d=0c!rgn0u!nTe?QUc9(7JvTfV8 zZQHhO+qP}nu3ffv+3xyI_ka3y_kW}BeY+3qWktq}e8`nKbImcwfGGqQ=c0F2aTg|9 zN9$`LxSPz1{M8fB6ZVK!MLN}8`Ihrz)O5pg7k^1~rO8tNx>&#G+W(GvTz5_0Q55Ip z_YFMi-M>_m^I5HGdv8cV8qo7xQ*W=U=qQc0;@>oe&~0~9F@E;XB!Mr$iZ#R$Eov_w zBw`5OlwlJ%K4LzjMB3t7LPMZ zf8ck(07vepr%0_B_5IF^OeZ*oNimIFV{KDKffa7qubr@;xTKI%SN;O`JOpPh#3{z@4n!>s^$?2sJ4@o)u`BD`vJFwa|3(*b3}`^^X67_*4ZRme@8eO+ciGTr2N3 z;#L%1VF;2Md}eaXT*A-NbLSptSZnp{7`%ab$%$xFqgfK6XqB+-THXDOdk|TZ9D8TC zs_e@g-XIYFZD0O(1AIr6qK@mgE$;q#-M0Z1FKKHzf#Xe2^lL_um{(mFkS*_aVv$c= zi2P6j#F%NR5N)lC*o+00gFm2BR6HVb3RN-e8JlpJVSU6q|JvcaZF288rgcq?9-9*A#2^ zccAV%G}fek&0P# z!FmU{3)n1bX{{zm&5^~4GXJlJo8w*^msi^xXP-|G!@$$JFd^v!5m*G)NUP0QVT$3>i#>(=w&_4yijvOZ?p z8{X*qsT`yI0y$LcF!e;s>G-#llapiCb-|#Nl(Cd?)Z+VAEA|uXqkGrt3JzqaG%>*U z6iA+aI{mu?io(!P5390giqMDhBbSsZm8-Xk#*gB;&ojg z=e+I{v=f!l#fi(h>{71qA*J`~QmuDHz??q(YsJ*gRO zr9N=U9j1FZ2c8~Jbet`uYu+3Bd#G$ZAT5VOe{ijv)`CSm4<<(5Msh@0tQ*_XE7qZt zADJJO1i71*TUQm4aH+iQ_5vR|vT)vOn(Is+%lr5X3hQ+cFeM&nDdv*ll~1!*7r!N*9xVRucSs! z7Z67wg^UXMC=fbFNY(|;NjnDxAw?u-2#TtapYJ|l)XG__JkLl@n|Ff(rss`k-|&EZ z9|^em_KTd1g2oEeZLHU2k%WPTg*J|-olXw*VirnCk3R{LS#55FC39TOSJ_$l5wX>Y zzl^V9>#BmH99Ur*dtjW{P8A{KX}rgSs;l42G+;qO?>3^p$j`$n=Ns$CK%;EO?mR(m_cm@qtRyTUwPQHl zBvlpI>os`&z*?r%SL`mxZ-iM#B;KB3@)s*jUYt6|vA7^9Cie8WwKH6c#ds7$YsY}H zypN~8AI)fsvy+SV77#!-C*2q`58dguZ57+Sy1&ut%U?gu{S4Of+T1QmZ2>>&r4B6T zZxS}_)%HeJ!HGe58u@oy0u-R7wqH^dP7@n>};QhI)M_v8LOONv} zUz|+*^?7!312UyVA<@+Y>Hf)l(mwiSS9pw8pT|ahOK68$#QtFku=#!T&OFIFt6DK3G zTJ8lnk6tR0p;Tq!4b?fZTH{ZUxgK-C#|)Qkq3opk(BxQ+fW{&_)p8X>D9Iy5iv_9Y z>M~fLHZNZ*=z{)jwe99k4^&<=;Wy9RV(VP2Z#9)m3s6bi>0B8j2C0>IsRioI^DTghZ1gp}-DK zihd%@M+Jz%qG}t788GFGFB{T~1We6O*pISvNZ^Q` zygV8aS3YE6kLrnf}D@B8(kA1hhT2~9c%-MAh9%S-_9VGP^_%rA)0TQGI^bG z0Up*PU~ZG(W<(wuj6ccMrJ`6<@rZ;P5zdCNr5PWDK4!o12E}TZsC8X=tP_|s7wNJ@ zuI-+9L3CV0Cm2AO3y+ro8OeHT)j%5u{U^9*HD5R}1*W4$9hulLhf}~{KSNIbBtwf$ zqC~{kq4!d!oR)h$u#4O8> zJs5rgbTLsGa1sSEhh=00-+%~I^L>j3t+tdgbrFx2O6}jsC^#xeRwTUjh4l-;=LXr4krLaP6CebiL9U~HUwZP9IyIH`hc@Y8Vb?z`V-L$_FK#_lQ z%oAbHU$31vofIYC6)Ki5`fvght1LLCye4l2yw8OSb!H5Wv`t}6W*CAeW)!SoOtm8| z`8;wlQ8wKJ^bvO(tZNNV~B0}F(v7|fF zPXs&S*f$Tl|Cp4N)E2&cL7uInK#}b1k&qmmjM%CLAel0Q^v22r6BV_{4;Kq0>tQld zhit(InN3gCpKjAarqG8nn;vDa7jBrY8(131>*OlSacsux^%whkKihcrs&?!blAJdG z?Bo;B#uL_r&f{C!pJGQF`@uh~Ji+VXl-D}#T%PAJ;>Saok=NNT1L2|>*~yI*zhlVJ z&QSkeAt<6sx_it1W=@kH!RBj8>%)wW56Ck>``MmDp`OdDAg9k!j+hKs{QOGJc{qh{J67(tNQ)0czzYI(S>owW!mi|F}^7RJw$B59_SC>JWphYpdr$?)v$W;IJRXJ zEOa~%gSkte$yF$Cf~uH6M6@NisY85Fla(EpMpxlL&h25ZZMi?Pjvy=AwX)z1>8ZBi znDPbS`e(jK39DR{f)JmAfnub@bec`NF`czMYz_y=njSvPpXPNP2`HO>4$LaTe*%pR zLOT+bTBj?`ZLxb>2f^r(x@-nbL#hgcnnp`L74$a)xgZ!J=+2dHb8+wn6teVuVXlVD zskw>Ja}j3>EN4?@wH~gbY=)^kt#=3S8({d-TNy0 z9Gf-(8#)i@y=Rn3_+EGTC`n{iY422<2*%o`!f*~Rc%TSD!AyfrnDI=|u=@2&tugQv zIq>ciGC0PK8P5#WqD)MqaQ=+I+b27P=;dQ*GX+-qY%UI#`AjYy*7$5!GL#8D3<{w` zK*QbGfx}y+!paq~u6J1c-pHOg#MlGhFs+Cy0>FcbRZ&b?x~x}xAp%AW7lt)JKrlVZ zWbJk7YS#Bl^@w{CRj4Nm&{D>m&dsRF@k1JA7%SDg$?TZrJCjoNBnji$+Px;&P^3nk zt6U5Wf|eOf(d#ur;<9Vv{g_Mc;l+qouvq!Xc^l~Z)Kj2t5y|qqKsw*uh<8T69$S0vq|(1XBt>>;j^md1V6eD5^P>ZF2U##BL_DckCKJC5Dv{*a$a95@GgRwE_5zAtu}E);m0=t@ynt9NQQHowL2AyF#P7kyZ9gR_o zZqU_)O+Q4?WX(E++C*(tl3k}nNcd!m?%I^m%Qz%<`X&WEX3WWQN{%}5>16EDfs3Hg zGHHF9ad1h;MeHUTohP|dD8qEBt{*yb`j@doF2r4)O&gx8v%s-D;d7#C2orkrK|4`l z2GNrN1{JGd@RWBNx|PXk0;k(7G_}LCrBT%?bkYj+5VQnzTQRX}rhu3e2qr*UO^6sYm#XV!@(jL($6##HZZ2_QNljIhq@^sgym?Sbh&WO&%{|{$!^r z?w7s%!U*6k)!Oa^17BV~=WFzaQcf5`|3!~ola4md^W|tKr?o(hd5T9gfGbo-Xbu^K z;CT7!x%n2e7(yrH+t*jJDZGLLr2xIBSCDE*?e2+9n?M#!ruDpjP-d30CjN97FBY-J z{sKxxaCfP3Q3)DGt+(hup#p$M(89o>GfaFct{`Zw%rxuB&5c|yTC2s9-+r^Y$QU?k z-N}KvI$o|GS8+Ii_V6$kVHZBr8qXgGV6FR6EO`n$WIriPb-d#RNDzIr;s_Kpq_}Y( zJZ_3pCH_#zKT7a&{WK^`WqAr9E>fb(|NJ0|0zgrulH^w%ofZzeyxYg;m;^&cg$}Y< znwpyGV!YpJTo?#xx`PcfA{1hT$wlEA$59fUwTSk@hAWm{J|w8EA70HJ6Hzd#|ksEbpQQ#>YVA_eN)>fPoXHW&2SZRMk=h7nN)<^dCag3eXKSsL+{ z2w+4f98~5hKU-`x>||_Q#|e}?vyxS1ZUz`7N8>Oi?u)QSiI3bGzM+iu>UZh;yHa>R z^p`uj^aZun3waA-b#nM{wY_Z*C>}wLXD*K_1?@u|^Q*x6A?rsu!r)6M&gLkUVUs=W zqzK+DtCdBiNJdUr&6fidg{4qz#vB7xIdXtHF@ae>c0*uUyADH6w(90#Cb#P5U$@Qg=0XNUMeX>@FhkUx6?4iS z!=qZO5RdnYku@iU9w-dmtWPG|@>1hPN6blqb_>o1cDiIPkNf5f%?%b?dv;S4|7Ws#g*kiu{%g+9f;YL5hk1Vs_XgB9g4(L{iGeN!BkN^u`0GCw?-9g zOh3l-fZ(5?TIzPtnJ-bb5{HptWJ**uJ^%$$U(PRXh`ID%))0HYwq|vWgQYqDmKIry zvKc2oN#_9UO<^(!Y*Qv3&!eBo?!13l-pt)?ogps|To$5i`L>_6Kr(Vy5trdF3U)4iDRfV~O4?9m-Lg5M*L-CH&el!s z;Vi*YW6vBBvB!c^%I<%Q<1cf}0E=ADP3*BxD^YC`PyC12jUJ}gt3wpBXvWok4h4Q(k+c4erbH#bG z{N>73SA_@tp>eY@eQIt!BqCuqc=trNTgS=kg7ms4T;m}w$+VDIO+1}#p=j|e8M2#z z0^_-}Nk_lHeaY9_lc`Bq5D%l{&{USmNkkC0j{FC2trc%PDpJlp>h{3&7*2R!ux5ga z+F*@wJOAoyq=lxTRBLZ876x#-{747mjiYk89nkj|?NqHV4WP8qLctf;jfiH`fx6n4 zd$X?+8PtT3MMPA`Hii_>B}E{aAeZDt=n9{U15XaulMTudI+Y4)8TuA~!s}P=lM$QZ5HgV z@2cwNnth)qJ`+LtKnR{gHtA)k4S{dk=OYY083-tBd213#A?SDM2Qgw|DFt`9o?$t<%H;zH1De%q-DW8*ifbDonI1qOVAZV?J zN_5hej-&VF8ir{3q{;Mt0DPipb`r8#XwEeOiRSSYzrY=TA=q`kQ2y*}i5oro z%&l|mCps`G(UsfCS*WkSo+C5IPq!=4Z$Q3Dmc7oZKRrnbSJdVLdPS?lgUAh#!j&or z(B$l>gcqv>P{!}hnYpNr^tpJ0Yj@A2a1h<-?)z?T7~ytqZph(C-Pi^OZNhD^dxJ|U z-FDp=bB9iD6y055NqdQ>G(So|T)P}7ztLs!=as^#A56*feUHzO5Ce&LkT4krP48!K zM2*-cC4DL5-4E6_sc`n)e+^i=p98{KdvWfJFH&`t``z zVAQrAv)uBDUwU~F)5<$SX3-VqkrV$Eh=?$Abr2cFPWG>SROF5Cd@AoZqEt(- zHS@LuiBQ^6PrY__#H$Pa+_&#Oq7^?zP0}slLB#@{=N;_$tzU9B(rQ8I? z%QWdAp-~RVU)L^$d;Q46ev<$uM$`t|!E^Y5(2r+BpDWzY0%F zpb!q(MZqJq@y;R_I#=ZsB(2Tp5kh}-YT*GryE{i4dW zn}dY!6aNu@a;Iv%PiXdoTA<;2Q7J<4`m?@ZxZxJXYV{o9zuDMyCwUgcZQI)uOB4V?re~fu-qmbV{>XXrrMVk- zXzN^D2s9^hOxD3MgdvT3cfcR9LUO}9@Ga~esDL^jE z?iS_s1$!Mv5Ol(QP~k@Ae6roJK!p>Ok`_sYzyA`Rm&*EdL1WR39(tpPwqBxpl$}T0 z#@eX28oVP>sxS|{WB21t`H9#4S^?{Ax`y&W|4TvBeRH;Aic{`g@rgIn7s_b22Td@~ z2#p-=AtTLZ5Ul{A?1NQtHX&E*nvjnhyD)KR4hSgcsHajy~VfG-M=-Ccz5vgiIOUUnX2ggu*Oj zZ`oygQgNX$>3R>{9}Y$J&I*-QCMQEPL}hD86372?cU~~)Jj&?KybDdlR2KrJET%E-QKvNz_89%t)(ar$M zb;Z<4a-Ot5pJ+4rXn)GXm59$XwU-bw^y%C0uLBnOibIwvW(|m~DMFy;wwI7+&J^Z# z)T~P58Th0^%mpOLHN~#<>qB8j>FGITvW0|OFZ*(eT6QK|Oi1jFYS;)C@|f1FqC~xpA^X>WS>|vee%Ol|-mpcjzp44$ zjPJshajXGn>U_mj!(4Uy+E(47X=!m0vb9unp78RUBp{cz6#oHS-ord9K@S8_4 zLj=`xCedSb^E6DRb1gd^l&`e2uKB{?*wvrE%0s zX{Ma4i=UoY`rH`D<2B)qEw0PH$;x@O2*_&mfQ|Vu9jchN?-%~|*eUB-MN8sW=i_db3vwU$)4h+ggsS6*wEt$)8|`hz_U(RT>4 zH6kauLs=vME)3238IWOBA-WPq72)UU)&y{&L@DtHK+=@&a-(z=1zb|Bl?5bI36Z6Ka|~DQ9Mw zDGg9UpQmv&kL!f(kB=7w(Lf23v50-f8A=%^tBo4wC-ZS6a_*k`(3omo=Q(@tvKa3` zq|GL&_*~Y5Zj5<-N{D`S8HFXsnC#@7YA!-!;$xbF1QA3_PSAHnHVoU5hYqlEDgV^2 z^dh3Ve%*Ep>^XQin~crjp;iZBFal<8=oBU_EGiRYo}^&4MKCvAK z&f;Jply|`5)RVb!ZhJ&eYe`0beB8FB{kcg{h<%hT&X+C969F`a0@4ppufLiJ65xY2 zMx3-Y<(nvf^u72?eh6@(wY|7$R7R`suTq@iOTLYUe1Sc!t{z6|rw=kJ;5~R#RCPSi znc9*u1`TAW+~LjletlNvWKUMEeBF0H`PNkW=uQ6akEC3+Y4bhOSpD(gwe;tSV(1NG z`o2C)ccex~RsXQXq_SL{Rl@^mvGO0Uo>w|84&u*8%a#|{a6Uca30p+3LwTs)BTn>! zwsjg&A_j7HDnJ^w>wQ0K^7Cj{1%>8?^9q-&4hi`)@_y<7$`Q+u6h@82dx_=SW?Qxq z3cKK*B8Uv@lqu&7yCE4VB~wG7zOQ@JxmcnNGdA0w4}nN3Zb0Qtjh@ZcM2aa_?$0S#MaGyiTs%e`>v)Fxf2!;4-?p+g=G}5tAy((%Tt(j%Sb1`En5(S z(iXs-_Lqjza5tUh^TQc?h%E|M;7GicBwRiI{K1;`zz+@7r4unE^M~9UMsT)|ye^H& zuiUr5b2!mfetWiT>dHWQX%1Dw4W9(m1(ocm4_BGkqsS zBdY(}*sp#3#i1xP#94D-X5P6tL=@v)kQh7k!70Jmm*(RYgKEs3sF34FigyIwY76QT zq+4q~^UlFixhu*W3?ZOLL32A-UQwZoW!$1vUSyJEmXNP=!D5sDhq&1*nk(e>^ z_W?_jl+p7SLrh5RG6D>|6F=uuU!mlVj*yO#d!OoQ@gNwLF}?@(hsNZxJ)Miz+Q5Dp zXw5%DYkRhTH7Aztlu**x`h2Y}CzL#x@F56^iiE@Z8z0dcdj$H$OTw!K3n)g=|8~U& z?b8(>^L$pf>rE7AFXflbnIUUZz&Yq+I#w{X8?mSO#-g+CU)j}AgCY1_A8*_;UHd~T z<6rZB@C_K~>qzwPvA2I2wAh(h{wtu#`v2CTCGroS`Tq|<^FKJW|D8?yKMh(;jI94R zKr?YNW}oh--t`Je>xo)Dbi{=LAzY9juQnwpI13guG#8&i#Gv256KlIQ6~zQKN8h8& z4twO}xrQC$BjX!X&;7QZ^_~y)#j-7UF+HyPhF@>c(UYB;?9;kxBC(+6>9uL%%ME>E z-z~F{Yk$oW8tVSX>0sPfeBnC(J|CCn_-yqFZFTgW$@{Gm*CdhH*_y*Q#=D1QvGw>| zvD$;j$-XleceegeclYzBklfLtWIj5xQ&RGnk50p*ZmMp*^6s7elK*Z?t6P8QWW$>Q zU&tcTSY_vIBwyoDM+n76`g>QPeaTC_WjCS=q?b$*ROj}Y^(Mr-=_y&uzjey&8`~Yk zhw!@J(*b?mGi>{llz1O};OzX@0L_N$+4M%G+-{ z__t1&v@h82)VZC0-Ip)y8On!Rrp6b_Mcp|22im06*Kwz{l=6YKJ)IA9)5UGUu)?ZT zQB$--n9`QzLA z7k>E<$N4Yc)_>tOA^*WI{}-O~e=wo{`tCn`UH^>tKR*AR?fg0DKL%rC{GU+h&jJ5B zfqzN9{u$(-KGuJp#D5w5pJRTaTK~y}{yPTrKkftno9K)6--y0`Fj6`^TLTM7E-qRr zV;fT^vmYjv_5UvVVq;+WUqoNaUjKBnR!+H_A2qbQT}iX&w$6zBV2Fyu@%)N_5d*`3 z!Q)7f67>9b0sVzQI+PR@hH+JkPfqw<(DDz!Ai$^~L*N#LdxNtI?lu@ay37jaj!fbeKIR z{xS#AC4)C^yLm=mcl5?^i-yqw>sOj8Pxm;Bzv4sTi^RZ7-~ZJ_o%ZnXtn~?6(*TFK zEXz{Y(Z{nSTvHYCQBMYkXRRB(;Q_c#q_nM8PrJ1kzznK$Ew9r&-ctM8Dt8QC`-0lX zw4v5j8^$q^)ql5jdpl0q;b>gdUXO&>`-)=Ps5n+$b!th74(rU@uSA`yI$v|WL=$ML z9&<#N&%M-06N4Pj4dX*IATeJfn1T%<#4=D9h@JUO7Q2JHrpqYM?jNs+Am)Rc57F-LT0?ElV*pyej)N-ESW zB>r344b`v{ah56RfCuY@-RgIlYFp^Qi5cQJT31Rrofj8OdPoLb7VO6RoIs&9Uia95 zS|&b;gJRWm0zVPu%CX1X4N(r!&>ukg&zVOf*mQoA1dyVvB|T4^bV(9@Pnv%A!kcrn zB~ga4UkvcGQVuWzwnBfk6!^`(igW2!OZJ0hqCdMK2M1-y&k1l|?U7v)Esth6JQ?%{o*o}T07*L3{4kF^>0e+`|I zU&X!}>scB|#d!Vc#cdhE3&o5F+uPaO!FMfewT}~icuok!l=yoH`%tx0>H6C@x%}If zn(W+CoYCF?FKKjYo=PI|OXmsVn*1H4dqzu3oU?D=6Zkr|r8QpW825Ml6-fiu>)*ax zTAUx~ROl;;ur;o9Zk(7OT(p}#gqwf6i-Gb5ahnJ(E?eyN7kgB%=G<>86CVONI;3%} zW5t#jDniQ}^wvSF2As3WF<3H^MFb4)k^_zD&M0$kb3EBRmSr8_Tj%U=g8kq?vR915 zMub&go|+=3eAN?KBlu@m0t(#t!m8iKJEA%H+UFf_7I*8JX@by;w|Lu|u^x`RZ&9;y zGtPgRed#j4S3~7Sl@U3gTPg&zaVaF_pOD%FX7{bIdPiiP;sk#+j2odLbjARqKh8p} z?k-2J>JtGABJcp$hLJLu&BeN%UAycaZq5Axc<1zU^7Hv{zsU~Gg?e-IowzvoG~HI- zW_0JbJv%EdML&CZSu8GH&E59RVD)mWNPWJjz3W;~#q&)*dGJ}!IpN;+Y4~*Fq-+gs zxchUzxsB_4`tbGZJMF{!%Qf*QftmQd%KJtC+Iu&C*qbPGeWIG1<%PI5uB$aI^M$!_ zmdx|yGc||%O5I9&{W5tYaA9{X2~xg&qWkyYk_!-oTA8|;+Dg*2a&z5adh8x&hMnu{ zrfX0V&X%|9yKclAyaJX9Ck#uk7-;SWS||tL41G=l%n>$+XKY0TgFuRC0mD49&ivi+ zi80!f&@+9d+uAd|iJRRs?NV0o;;Ln9>P7p)mEOGb_>!Ze((!kzvt&c79@NwZxG3wu zNSDyHAf}d%d7$gy0?Y-glg;kpb?UXL#dT=KWAjBis1)N$Yqjafj;6 zRf+CdI5dITUQkdXE^MCMo=~2@Cv$V=$W^8E4k6M7?+zizz3*av_oZ>=vf{GqMxK0e zv8B6m#FkG3nT8lPfE0}%Lz^=IwS571>5z+3B&SGZ5fzIc6uU2YoE0sRn>)a}Xc(Lz zpvmT~KyWqj2`cUt>DgIDmN@ROg_Y#70ag-{Cobu_4>X0EqEo)S#&tGT5yV~hVY;KD~W`~)u%1}7W3~nPkmE*pL zCV)c; zO&%dl?NAj?7H7uU+gqnERaI9-cY8sdlW><2>^w7Sv-3+A#Vpte~gm_j}JqG?`65=J)?IXn|Bj7uTgpHbwoo&#kmjlL9(})4c zmY^9aqTrc_VbIZU%)H+&h+L7rS{rTD2t{!t`0PptwXYeQv%HLFpg5`={BS?2mO6H6 zhVQaNTszH=-QaEb0B%W>y60~bUGZbSk|D+M3xOAf4C2%II>4jIrGjkSq8ewGOTHtZD%NjXxh1i z{`{J_e?3wO3QRZzqHx*YwxrF;HQ)#?d6qIi1nYZcw-N``_y+gZ?XfuT- zw+#CMawX->_(5;sU3~2&GLfQMTG!)Kv}_Mw2t^&`Qu$ZyP2fk=*Qkl-m%*>o`Ci;W zGhHjazxm3f%)LSWe>uK0R)fhOK16wSeeiqC-uz~ zU57QJMte*Myak;$OXE?8Al%%ftPa%O*FDp{(A*_ZmzAraBQh1L>0k!X5E48j0!k<; zp=w-9zso?e`F1WC3&j-!YitvYm(wUiE2IDoDb9fsBSI7{9!On1uarg`66)z%?2NvB zPXCCofapC2k)W%{W~ev~MLlkx4-^1F(-lsLgk&Zls5%XahDqf|K`Q&7N6U80vr7+Q zQWrmC#Y2=AtHl7GenwOCkx6ZU067>!AqA%w0u?X#8&%!fSnU;{wMS+Mrm;X)7Snk! zAvjMMano8FXImKwjVuCFocfufMy(6aBE}(=>C~WNPz9nt63#9SF%E&~D)Z4< zyeKKa4+WuK6=w|*OqCBUX{P590WUVu1k4~9C>Rq*s8`P^h#r_;IY8{&?kRjcz`6~7 zL=?Ny;cLR9_O&QG$w{&kXE?x6)F@aDvg`r(G@i{zh0#xzu9i77rlHoUVhGl$SP2ri z(w|{fr2<$Oyv%0fIzUB=%z-}(p)Mol60iu7g*3ylcP+zq90Vk@wigsFqqZ!}o}ut3 z8;?zYhF0cf0s@zPkC_y!z=<+TPEg#_0l9+>r{FJ(oQq=?U2!BfVwS@Isgqcd`vQbz zR$U*2oXDtDi2w|{WGIJ-`_h8_2nT+4fqc$A@^V>2>Y{!<5(U{azswEW9yIo@H7V2L zo~oQx3=&!+QW#RxqNId`q`~M%jUoRty>@V_PRzCVytJ|dJ7&TGKlp7Ni6GBzD zW@6Ojb`ly~Qhv6?Nls&5?}Afu_L}wzPREiK-_cd7So*OEY6Xl=aL#{Z$;{^yDrf>W zk?;`#%*^e~0GrAB+2|PpDiQNNckC%7WJP6V(JU4<%tO?#dFJb>r@L9Yf`HO>1Cf9e z6`N@2ohti6(r&C#{ry?6T0@AX<+uT^Ss2Q+#dXrdRP@%rhC0P>7*$PX>K^RE%|>|= z%}RDtpn+>Wck)22jeRCk%MOCHMkVI9|GU2Xj}aug#td@D!6zIaI9F^D8604j{)L^izn0ogbiaQ zEx4sxX*=!;zFi4!({td8kK78J#XCRj3gjoA306fPzVeBHl8LgJs+1YWy30dgc{iA- zsh6Au2DBanm~s9tjr4=N9lch!+S8nGbGP$A@7A(hr-}J|R0s8B?fXr2AT9|P33aZg zsr1`J=qfGxB3M9Xj#m2&r?Y^5z7~A$lIiAV0E}izRr5(~r_Eo#J=J0&U2pC7ifZZ! z_A)}!uo$9o)EH6?ok~`CAJh62tA0l6wM8BUEEj&Hu?c7+0d0%i;l)Uqzg=fNmMXOD z{uHt$1;Q!^kYHjQ?Xo%h7aOXD_Gv``RFv%d?NR>WR}gzbrEu8JI}rNOBLWUmc>Qo^Cb;D8#Py<8j8357fb8!I8=yJ&b#^`c!0B6W+gX2QcsX?3OXv)W$RqN(X zDut-t_BzV#?n`U@>g;ji(He^z=c>v6A?RS)j!Wm;=!Cl#b7^Qz?rTpkF|)p}s>=hy zjxe)uekj*nuFG|9-=~(85g_=_H%*wVp}N`~T(wI*rUx@7{+KNW{XZ!PF`pW7bU8YH zMdKVr=heTSCEN_;Q}kyjND&hTI{e_fe;IxPTjNcp1>Wc04ZZpk{tGtRQ!OG?Drcyv?A~R&ozs zVOkggd-xHsex#s#el}@NS0pQN0Vh8^;@to{7I4xIHN&0EK)SF3yWPL{$f4Yr=Cc?R z9#*Mlpd5ToFW5>oWGjN{Eg=5p#oU+yawQT(n0Vaa=KOg47-0pXXT#tQ!|;oQkkh>i zz~>8*Q8c-6-t+C19(vO;BDj)Ejk?E}9oj;9P_qf-%FC6oBU;0A(XCWExqYzfJieoX zU?oahJz49|V*_Fb!nN|;)+5ucDcEkEgwn%*eVYumr${#2&Q%aauj1~lQ2CKUZ~MyX z>vG)q@vN}O!6WVUPWJbm5)P@rB~7q3;aAuYP9ameoZ{J|Eu>AoE> z<(hSv1Jo{DZG@xs1GS@cVXRl;X7^8}x57dHX0mR!(Aa3UH{4IX-hV^!_^Md6nZA+$ z_;^26pS^x^LOcYx(FxSXq;5XS@;3M)@ytUqU)^29|Ni41&eitYmLc4RAmI>sdw>lK zN7+}nz?II2`8l$PPX>~yh))^PO)-xV=Ps0<`A((W=E!$`2wg=b zcIj80*YgWQYhCY;r%bcNE{5c8KYL_F;`YUoWGaz$NYdQvMld+*C~ukl}~L=7+{5I&bey_i&y<$`r_ruQrl= z!41TZGeX7V08(7@SM+DVNRAI3QA+@vrfNyz)unEWk5=1ov=qjL7Axe6FwfwC%PM*mI-ep=sNR=#~uR-{tk$OZm?sG`Vd`lKnMPkgOglqaHRfV?96F! zXabVQqMvhpPLd@%`NE)crPdX=`Hgx&T6N z{FGptcKgMfrpT|G`4gMp2P!AvpPErbO?&3Qh9^;G_qr_U0zX(aDsjGuzCnfN&eTH? z9;*7w42b}+G^6SUz>P)7mH3Gs>}()snW5hO4%#2Aoo&0G2C^w_b&?uA7AuA=(A;$1(-4n?OB>q!8?8`? zC)AJbYjtv5oEArIyCX83b2T#cq#P3&+|vdP>MfX6q_wzIY^szG^n|HMrmb)1FyP|d z*aUq!5AwNjI{c$=}mNm^n`t0?rLm=Cup-%m&rG9_L1{W4Iotl#LTpZ7gh7 zerdWcM!)YUHim=Tg9_uEtBv#)Qnh=e^Ju&`l*;RniW4Xv`iBo0C1?c@5rI{~BH@Fo zR6fo#u<1|-VkYEMpm&YWr9)qi&t+g`x;lh+pJ-utDH^a`sY-XbR=ovInb9Cf7p_ZT z*zENTb26ZnsILuLaC~*#Z&5z6O z$Qja_i2T(|Lm!S;cc4MIWZ^HY54Y+X{YwGvc`12$aY=fawPnNd9z>C^VuthwBnR9i zKWe*$fUnRHguiywN&Nc3o7csumr?$WtBnP*SxZ8xb0EhNs^_`&vkaN#GJLe%)>^5IiGex|Co_4X0Sr8a`moC zoZ++;6|>0fvx!-QH$DeN{O5_3fbKAKUZxsjTO7P(Z*p;aP(Nk|YAUL|r|gg~X<46! zq|TU$Mdul1QXNgoRYPq{!qcpikl3FJJ(`H001eY5oj{K`*Nl@39X&3W32i=(M?9Er zWL6kWDH^~#2i~KJZGgk8haMH*w?c>+sCeC6z`}*mC_Nu>=I~y>_X+vWo;`K7aN!rWvq}nV{F`A1|woKKoozsVip%#7s4R^_v zb(o(V3J$m7UUrlp0Y>{1MKkrt!*Dm-)#rlCZj!_dc16-Xf1&A+MD(6HRrzns-E)v7 zPopRNp0;hr z#L4`8Q)*aYYrMg0@Jd$T3&gjTkMoe9X8mcD6c_NTW_= z5!DQ}(%VgVho#R08AbmEc9g>0#+r0*2&=UelPu%G0 zdSGn+WjzdqtZymSHHAFOIo`F-ES!zRRjoslu13hvRC+NOSdE`enG#X&7a+6*fG*%N z(l+&m6GsvN7dgK)U;@cc0up*MfMkF=S@~a%fL7VZfUA6|yVZB1$PKw*TjX=(Lk;zk z2{^NGB09ED+bG6H#|HOWXn8f-5tC%yCA2yX5Zy{MHwVDOIk)kFml1XM(Lg(R%JIpU zCWCP!Y7`h1gkX7dus_q%>ayX66h>G8K?R3BjzY~)=Y%d5RKUdpUA7<2WjD3&ggJ-V67+@&EI?=&%+!B(0b>Ygn-~*xh6ygm%a;03rQ3_Yz@h^`gPy|&U{aDUJ z=FL8RwMu6r(y*js6nG?okI#1^<|OWQ^I8RtU$7{K)nq9~C(a>_kmoJ($P3*j(lLn4 z8AtG3qLpV>T9eN1;p;MVKJFQ6T#C}|DE=PCANV{Q!}DQs?t5D(^I#xL-gLhHY`fIp zDJXord`Z%_ZFe);HH^N<{IuI7O*jWFPu`QO<=q$I&?It2#1OQ1l9PBokr-d7(xJ;0IU0kIGwWf~%db{QA~TSqm`vb}7+ zVZLFrVWS{ci(`5Hiu087g!5t+nrZ+QX`lu%?y|^klSH;;*kz>S?ch!8q>>Shs!eVe zKgso&ds=m0jwZ^Q+gJ!@1aUO<)E=Kn=*cnf?LEwQ$4BNa>#N;rVVVB}sD+5v2u>Hh z4_C+gDA;(AWwo6zk#0y`glMh8%lM8MtWHtUqfmRex6?XHWP!!CHX}zGE1op_s zF3h*|!sERJz?lY>;#&VDNW=qlVVr{UybOe88ByyC{`V}zKU=~rFB{X#`-o4%e?~(w z6NMk}I+(QYBd5|1gP?|tBZhGh8*0q|oH|(sy-Qp|yj~%3cA3_mM6AFvp^L#AC^}MP z|32`A?@qEF#pc@bo-CTR?Q6LD?QTV6xbK~--j^!BCdDmzU!ZSr$C1780|AVgGkfue zGAy~{Pd^~|%@1^KEG{%wGmq4ec4@+TY~=luaV-FDrVh0gu)c&m+| z2f7aXcc;eP-VfQY#`OMj3sQ+ZLATct+OQ?0%w-(^5@zb^_COOO<&aM}jnKs%qVm*x zkOiso_wkKUtgBz|NW8x4)wRkm3LJUwNWnHQHV}^QRim+9o*A{`ZlGSV7oKw#uT`v} zuLK`L*t!9)D%7U>&8&>&-IfrPnJRr6G$PFAdGeAK90Dw-(Av&QfsCaZ=Gs)UY<3O; z_ZMI+)(^^5pf+W#Tl=OTryeReX23A4iXM(NXvVMiyLDrM5U5`A&~mdxzBn5ajipwFTo zx=m#*`KJwK@gmw0gXNQH(QhgoTb1MNqf*a5npFyf)gi|CP53@1)LNJ#yVCS*On$?Tu`rOA0^rIt%l_i#jF$?+UqRc@^r5kw3 z@BF6F9s<-5C%-m=-J+Lm&nDa3xFr|2rC6RqNe}CCAP2pmM)u%fzOG~I!T}evPGxnA zkeTAk5{`7|dE~C2D79b_)v7TYFiuhV& zA2G!Z1iC4%+nN0A%z+|I{xJDOFj%D<8j%nIYj92tuYiw2NkNQ5RvQ3Bq%(964!lV8 z-60aBgBv3Gk%VRMA?>lNsAAG0>E#0CfxxR_NtYZ~>M$2jhIQf$(<(o`N=W#3Nn*2i zMPIl-u=}3y_)cIaoOhis5KVm)VykwZ9jCbVFF9Wi|KWgYOyp^3B1FROPL~&$@_4e| zbi#L88C#m);C*||PUG(`PbhGq)=uX#7As{sXx-%+;&VLjA@x}pFLhgz!bMGJ@5u>* zveN<0E@UZt1UR`h?Y4s^<<(%3RRY3PLp#$ehYdc#R|h+Cx^9Hcoa;fwDPQPOz@aZ- zb`e|h%MVydQNk_~A8^MJLQqhx^}GIHhAYV>Pd&scqJZ?fBd0H-;=_mtWlc+-SJ3i< zoh8qWbTH2r6x|sfE&{59&Bd6SsSGTP$aNq`4Vr$xG+w|%A-VG)bU%NNye&byqtiu1#wW_XM}o zv8W8_PZ!%Eiq1+y;5&z*MJU}NN}Wy7Ar1VtdHed5AUn{!Vt!kVY(HlhGY!tJ1hC~n z>e=!c`Z&;f%*ZHC^(^oTLLnhD0%YIdJ7$b(d4-Q{p(Y7s zPlO95b_c!OHLc4T_zJYhYfJKk_yw7&B`qC8 zx0`7Z^j;7~ph68m#tsxGQTrHi|BRRv4f@ABM>!;ibUMwbN1h>JV65 zAiAp)7DN0o&zQndM(cou+GADMItrtPSjY0K07^JU7+q-TkF#`b>zzCbdV{W?8cFFq zpvpwI`S&W}VuAL*%B;YZ;IPhXuykAX`0aY`aTnj;o|aK9p9(#aK~M0r6;ZTlYClGg zM#GYGWLmzQ=>2Y_54_btrl|5#y(7dag2^~1ojtQZ-^MG^D;B{z#0t3Q_2yQ&aVPT! z7G@pFmrSb%)^=-tiZwFq5o1*oS#|F$jIUW~sg7*-tgpGZ_$MSKOm+z*h6xL*3O&8e zSDfhpXp;n3%Q2JnUVrYbL>WT+F+D9_a_7G{I|Vo(SFYS&10OILQz>2920Bf3MTgB? zwz-h4Mt8S>FeSt;;ADKMG#guZ=l2z*_0bXH?vhLmC#Gl*+S};Njh*dl`Li6wa=s+M-IuEOj}GdyOX5-yeAdww+eIQEJv*awW0Fk;i(Vb@+KN-?+l-_@7NG1dS zyRaYB>NLy=FVkByRCx-%_k*?Z5p+AjHvCXV9+xs*^Z78DO$)%%)dpc*i@E8z54Hus zrq!=QTHWWj0)&ssl&0P%%}kPl+wWT3?~lGKAw|qws1BV4dlZ`6h*3B8Pf2>F_-tkW zP~b?!c?mfZ{#0NPMEv#;%{(}-7QVC9|yJWPmaonY;rInvgmPHJUS%JN3n3KuTgY+oe8e9^_L3SvYlfvBdyI zxO80aS)lr}9BH_rd-xf_JY3e}90@oS_m~Iz(74rypZD}f<+w6Vv8-qMA-I_La%CZy zJP9W`KjHEow)5tP(Zgb|M72F#B$w#uyYB8Q-yA3E{n{rj>v(|ClY4-=I-%NcAnZ2Rm%@(y-uFmo_3QvQRyyf zLP|Qr6uJi6n@259WN9$aQaN!{o(zUm@w#$pZ!FPdACPT26Ai2bCJ znJQSB(YR{Nftx18Lgz^B!a8;fPVGFoEt}dbdY$<<>eDKI6oHnqj4>b;y{Xn%)r)#f zTl1EZGgJxWuBoEAR80F%>&!=QHwzssUFm#`xRAfSy}5APU!{<6#4;~#)vPP3=WJJ=tos7X+so{py!jc$iI9!~`TMIz zG!^J!oX>~($M8;pgViNBNu%9zcv(ONq`6eF z0_}xo<;zm_{t&Nvmeu5#c_e3Qs zX;A)7F|P7DYSUY1p+gIQm}Brg0vbY1Qnc_D`SV^_Q`N5==$A9BnF=h?Y3^-- z_JK0A^|)&xr0FD;X7%`iA2-@2?2w;0P{gD;T32kfKBFeIX--8A0ujHl)t8bNk_tW; z5{>_iZ|nqqwV#n>JI`lh^~U7sufo%6BQ0B_`*s8-DP5;0qr*?PI?D!VqmK4nKwC(8l95noW^>$*D^7bQk2T~}#yJqEsJR@X9*|#3PxY}|URl=4 zeAAZ}A&li(+go3+ODlN3?{6V}XSg1>pI}%`wt6}O`d2|9c0}vYY!qu-0CAX9YkTrrShZ$$$d79O*x2;BN0m9>|sy8I^^uj_jH zX0Oy+wYJI+GfW+k3`;G-rycHlP~Tyb=EU6_yqaLT%CR25;#GJI`*l%Z;+@B#3Ml!tFbr6a#Jw%io^M;%Hvz+Qw82fX(u7QHk2@=(j%C_#bF$~9z4 z%1E^XQrcn4WL>Z5JLf)~KIy`rz+oj0CO={b(h>5<8ko@EbzyNo1IPI%{X%+8OeJeL zLRBaG!&Ne#Au<@5oLoGfqsA&$jPFB@K3Sz^Q`uYMq977 zddU@8n zAF<^uedaWLL(B4di~janI$|8p$Blxrd^gB<8v8XDU{Aa?NU9CcEMwtm1>S!Y$l;ES z2gHK>B&usEN<_N$MP&)xNCx{Q5YOyfOZ#A7lLmqs;bD}LJq~TEW*({!FUgG?W~LyH z12jFFBN4jQ6Lo#OcOSMh%adr@T6pP}+FehZrn=rvmKP7kSZ&HX^cUC<9S;Z&CL1)l z5w898S1Y=`Ufn`!4Zkk;q~UCNT`w3Ha<_%mdaXn?za|Q+uCf2{F{GA66`91yp|1{)W;r}5+^KU}V+5hK| za~4LH|DBrJNMYJksC|^Pt=O&S4*aT>Hqp)6 zLHw%}xM2?y?W6AUvo6OB76+W#37B+J)9>w7a{oAFPUs7Z@uT2zR=3yWI2niWgK8Q2 zgJe9|yXfJbD@FR=lI}x==>xDUN4LJogvX?22ClB4>t{!SPe=>(2ke_|p3iTou7#@; zx@y}b?+^@{57;`jSE%!_NN??|Pe4{lpPY_+E-%y%SV#3&oJYlDmu0-1tsZ>)uLjar zhUES&xJ;6Ju6G&Y*BUZ`u4B%7sQ2%ew40{@^+506UqRD!%d%l_(dvPpK!+%=G?{0| zXytpyXya$cO~o$vzYmW?CT8%|*tKqucT#qEu8*DzFAOvLW)_lzGS$-;y{~;_V?I7@4v0i-}e70`>y}Doxkt@RrYuPU-ka$J^gL}zdhLZeSBa0pZb6Afq~;c zp5fd3{PQgT*82RLy!U_l{{PR^vH!cg_wVb`<174|FNOc)Kn(bd^i0hE&H7BVx#=M* z-?D#}Mz?reQk?{*j(m^X;u8o0{vf7_K^G$a=?l$3+yl__TP)X~9HIzbD3_2nBt}^2 zXVeeE>`pmPz>>}_7C>dJzT3!AM-HzlfqfN5V51(pb=d=$1>yf1xN-@6GgKM`8J zM3kmB(U9}$5%nj?hBlfXR@|8YUpjbyBecjE0;j~eF{%h7W8^c5VY)KJAHA55z@22< z9u5cAERIyxema5GmheeFXXk%LlWE%=e>7P)={y)J9_Xpg!h zq~0AatI{-W?YSALMZ!wy9@U9dorTV~Rr$bUj(rY)axW~nJ#m7TSTk_Ja$*?Cz?D(E zKtXEB;|2S`+vkg*hlaj=tYL7$xuBxSEyLzihjZ`9B6w1p4E@*8B1sxSA7Wk{;dX+L zRvkBQ)~>9#Ud4-Obel!*h$_wmfey7x2s3re?1`BqP7ItiR zVHqFO_>ZbodVjHgJl7N-U0g8z3;J*1w0&HXWv>N#2Ss6xC-DmK0=EuM*ZW(Fur5BM zk9X`d;B3tLknQGcRxIry&2g2fHLUfeb?)`^Rs5ABI4AfGS`)em1}!3SkY#YA@SqSb zh+6;8iXN_2rnODYeO}`M!1{W#pncb%VE5uL0U~aTLw&zH^3dY?TfT(D`7gSX4~? zQZ`YE>Wscd1EX4On3?_Yfokf4$ZJkKsltV^Cq}RoWGrf#(l%sB)t+2E-U_Zx(5GY= z`FJPME~G7>Icx-v&fje4J)ds#n+;v z9-5UF(-uC|*A6O0%<-Ou%IPSHI3YLWOsW(vT?y?%IeQxcr=IH3sG~El!83qHej0jb ztY+pV73Hni0+Y;(>60D~xE^}h+vlxUqh*Z#)N!y62#a5jTzt_yxY(`Ye)(EkC|tFi z6^e@C$-hCIckuT>SaE|mCwB|t7L>#qF6GmE!(Kt4svtNU{^*i)VN#FqY&3X7t6~hC zB=YG^f{QJh!6)TM*X$q(<`Msg4FP_$p?GVt7kZA+E-X9Cw5j)>xF&<~YpKyIK}HDb zeqQU)b;B0lVfC8dVfDhr?$QG-XorGU zpgw&s*bdT-5FpJe0^Y|4bb38d+M2#BSBx`#Li%hv(O1+o%gG-98nM zqDOE|I)+E#{rD-9$K>^3mCV!i$Hy3V)`4V9=eXlrr`g0^h8fR-dtqXtMxVM{g`3XS z)0We7XMm6N`*%=in67C|*wNDKz&fEE^B9iP6uA95Em9VT@9Nw{zPB@HL zqK*TUJDGN9(X^0P9ZIo>j%x!t-62OgyW&&14T48G(Yks2;mp`0t`tYR^PUwjOE??e z=A-OI4}j7~mdoFlhBR;4JL;jlP)VlzrYl)>U*q15w5#yOxig1mk_pml@pU2Q>nIxo z%vTmG1Nj%@DVBXLr_-sH9}+)_JTuVo^jJjg8i&V^u{JD}|gzE=!h+EV2v*{{u5BmgzC)dR|9;y z-rYxFwyeocfQ;!Sw#MN7OG#4>JLq600b;a@*+xRCj$49MhJtn#>RQj}q*U3+b@$k8 zFfi9l-N5KqJs?z^LUErzi+$A}=xC)SrKNQY1zI_%d{X4nN)?Ih_8=u}8vf$HbSUw< z;7~>Zr}V$jq40zMq(hXhsv!pNZS49$iht=)v9yw^VTZ-OB97%GSEASws%#jtG<77U$BIpalqA((O(}BY z6F)n=($4VMjY#H31q#FUgkK_sFc5-ngnt|$-r1F(o!$9= zw!hZC-U{M4&G~yew^S#Aj_PmC3@PhnsB9)5 z&EJ2a8bk2^a`gA(H|SmzR=oBw^Y8Ny2q3}+C-1AijS(u`k*v4TihaE#o9p)yl7JG< zl}hzDzwB=&rA%p+ky3rA#h<)5h7f*`r!`g*rbiR?G_Ho;wD&_V^^lXk>v%sjcpI8ky}DzTKJfq&`HF+|VIULi6}I7BWXva^=N zrLmR2aPBB53Vo^M%*$`|*hu^wlpmkm)W9Kd16#$c!XZs4VCe!V130|%y1;}d`63Lt z9Um=oqJzf?Pm(FO;hd4fSNnzXIRw{@(p+Bl95eC)5f6tf*XY@#jELZwpuV?9{yA1pTll2tHL9Ll2*vQsI)nAb z_G&Xcpz+Y!o!E|%P%8IH*-)XJ2tZ}u(cijm^6ZgAXgCFES@GZ{d4K89n{H@44ddiJm7d{T2r{|`|TFpeWY8qi;KOiyQXuW z%ZYT3KcktO%CQi%dXu9&Xr66Lwp+<<#|M_En=E&)@YG%hL@BWf9xfN}>*4_l8x%_@ zTTG8DhSBFRO43znUK9NU1i$H+o9$p_RuSD8lZPV-aO0#T^8Ivn>rQ^T=gLag*wYS@ zpbBZJ#0!%#ii02w;z$RjE18J}l`35q2#8YF7YaC1>XBVF??rJqibc&{rDHb?Ezeo4 zDmLUT{#2OCUNeST1-*xU($m_}UpD39!e>b_0!^Na1nMRkb)1ylZ6Teo*>Ps| zs)=WJXi7M$Hm|o2Op(&Sq8hYh4vZuMK_Gw~;-;0ct!aiLL@mpt2|eDRA!PnNO)d}s z?nI~*CPymZ2Yy1hKI2x>k>_<+xbtgJqAOLdfDDL73O}qy{X)NMVj+GqA2t1KX}yKg zMzN*+;yeiI3n+`n*1Xy0h7;!f{asV>?#&tg_{WW&Kkfyc+gX;I=M?ApOn#K@3KP0x zt2kmR@q;X}N!>tRjagiPb8;#?QQ6!J6d@-SmM*;h2F!?DB6O$Gu~_h;k(o@?gcRFI zoxOJ}DQHolk9#;qfuKA}2Go;K_8ihVSgHjdW8%q7bt;DJ=G~;^_4a!O2J~G1%813< z7wglf)5AkFO;%Hz10;LTn2~fUE1XX2_Hw3i#rHhiPw(>pMZB&X(n_9mlt-HMM0Q5O zQ4;^ToEZxoKrA{99uS1Ay2X}Nx6u)f43#on#e$N#dNU0c?in=}#;~OZAQx4>Hca%=R1`Pz)=TOqo^9N%^EJBALA>FMKYw^rkPBZ zPB`)ROG%@e(pXtwc_+HDQmgDV@SAO18amKV1RpV99_L`#e@k}$Vw$n3!dQe5ZY|qHyFBj%(oPpe9YG$ zTCAu$Zp^8?&XLu_Ijz)`BUuBsAj?z1;?TDoTaHY8Z7xdjxRxRV?|2qCUcm!&Vw%Yi zT?X(QK9p6{eXrMKA0-pi0inKbG|RPq#VXBya`kb2spi)U=sDOY&?nFq3k9 z_*oRY_zvZk0VEn-P*wi*46+S#&rhLtFABmVA2Knt30sI1K&1TCZVNyIKpKrlM2nM! zg+-@{lh(qd1g-E-m|QGamzZ?Brjfx^SYU#4I}e9$1d~D^@~w^c4tnzCyP~}HZRe7u zGMCMP_VRr*&Phhq8?Vs#eTfnOIrDn8d+a0UR5u)5cm|DoJ%fk1$=+oY%_w`1uu|)r ztL(xclng~^AeJSJaQA|2SmRay)Ez&4)nybVl;16s3b#vdNk)@idy#fBw_+JjqK6#= zeDBrGUb`?HCc;EXRAeU2I@rYI>MXEetGY#FxeRI?Q1%Gx*u6WnZ*zP+0GZ= zm37?pw|3znCb)*2%j1GLaLU}zuGQCejaa3zA)vT}x6}M(^>VIgHwE$b_K;x~Y|1%( zjjyDi&o4j7rCBwafY!|%P)cHgEm*`Wf#vaqewjDR;Sq#AxO;)b0#_xnAPd!(udRX+ z`LM5nX}tY(#3Mfga`M6l%e&)cP?UC=p?AV-b)J!ILTjz%!U>*dJ|4505tX`RuYszgjTkVDUUFGpr@o`0FRYTqRk(qX15?!G~Uy2sE$kXsE(e}2vSD*x+ zw`3`04QkO!t$H?<@33{|BUmRf&hx?PfPER~LIPHEM6u7N1ZaBRTnd%xZ2FHH*4eyy zhMAocC;h2b`AW|zwO(zF#*(&8%1*EV+kOkd7|e)`U7Md{9|zleAI#8t^%RWeM)e}h z7$=3Of&{Y?CfoSy2#jv)4iH=WVK7jFO&FF1xw%VJnoJZJc^gZ@1~r@1sZj`U(Z)4` z_4hj74t;r5plI@R8aB1sc3X=EQ6t!%~TmL}HqJOR7b2s#(($=i- ztcJYDl-d<-48C8Oxpuc zT~`>@jezDW&uB(B@l@u1^_76CoEX8#u&T_TknFA1Y3M`Vbk!_eYhu@BLmgMOhuCRt6WXg`_mqP_~`s zQ@X2jDI1O^aG|`bouPcRbj|Lu{0-sfPi824;w{Ycl}lJ+?F2ef)oo!|)^%%?YS8@) zaR3t#@Y23@_f14UG?22PmQw(fA(A1|5$+o35ZI@swwUc z^h04e7~VJcr;6)0h9&VGr6G7XZ1 zI%kHTqiVSuy!enaLg;e#vldxQ8Elf}xzd^)GFsC=HzDQ)M-?$LEm*hFj*hpNMb(jp z?yX~?is5W-1_Iyz=+Z8FIuleS$2E+wNCvS(T}+3nsu;1qy~_oIRzlnKoOq|1XCfK62y~Y zIu@{kVmkeHD_3omh9q3L3scocZb8MmiC)E#Aw1(tC}oDBjYS$;RIrH1@#<@C^x!OM zO3{CLSh`a~aMAgai^D-${s8$!^JT0H&+_HAuW75l6I;Zx4L~)+z}iCsw!RU2=bkfa zOD)2Ca?qe!?qybBqn+G>^oI9+l&Y$_DV=@h@fhM@k0>s*b6M__=DtluGSMH1xtpGL za=>jJ6e?K3ES?!%frA;F8p?#_<3#p%QY7v)TME~?*Q4GV`Q!Gk$09Xj#q`03TBllMNQ;ox7HCB1T>&p^ zWUN`}P#M!4{&ER$Po$37wLk`1X}jEE5FlN?$4)|%W!DL0a9c23xfC^Erxos$&({Xr z^#YwPp8D%^p)wWTMs3^)K~_>%Fu-!kua}5-;j=X|-jCxwRO52Ekt2Nq4)J?p@mhQC znH$c4*z2`$CnKOu^PEeX!e%8HNR!^)d7Zl=)QolKyMQHXT|0#xC1{#)wL(aiZn72d zv74W;qO5%odb0q&`8xoLDx>XSS_5{l8kkr$nP5kEmh;#|$P$tL#dBEf(-yA5#%>jx z!%&4Jfa@gPVmWff@XORc;y~=6x*@LP0g6DL#e59`vElW~1e&DOsH#x_&48O?DEA@a zfxQcZc~QnKdqsQsfHydIko@>_#UVA1Cl&a=h^vQ?K}!TsRrWzA;WH6o<=E-SP3zQE z;ijfg@x{A!AXNM~$bQyM6V^jo#RE7A1Wc1xvj8SQ$dUUJcLV%*2b?YyceeG;1Bg(l z`dN&KY!QjdHEymhQd9#!DBMThw#yX|7XszPWLe;N7+5TQ=KntUxpJ<(qk7a>8j_9M zd(Vdz!xt~x2@Q1+q65a1vYCMAZ+z{~9e2`~a$w+E`=RybA@PVG32%en1^=)hQ=9LK zWowF#bG>bcI60^+`im6{odxrn(h|h*!>*~L#?ldb6)@G;7>%_61PXYW%eh{aQ8K!n zl#Lz;_@#!L>K%aNRx;)ez$5$O6>O!AMG@y>OXo>RRM{$7WQl!7H8`V^(LSeJF44u?^#M&Ly9M`Z_oR}#AXoV&P8+hCyO6jo7_ zlIXq0qB4&Dp0|(nO5(tRx=6qXh|kBA&!iXF{?Zj}r{cE3Hj zJ)J{*Ld8NTL?tm2fjGq?aAe_=G6iedga%TL8_w^+ZMcY<p+ByX6{v2>EV#6)BC*jJ+DeR1h75}cyw{#NMFWm+LIkMssj zIa33A8LsGb88>mRWqMI;WTE9fmet#|hei{R-%3ott0x>7Jrw{5u{#T35|~{yW55ui zIuUW6_@_D`vI3wbpdAGL9kVbSNjGLVZT3Sv1uT`%PkG7K8|BneY}HvxO0Imo(Du#C zK}lnybE99)@iWIVB4~))zsRauf%Qtw-V^}$$hf=;zQ zOYvCX1tanEcjA-@;kvKA(V>Mrx@Bjdt|+S1Nvm*?^;&SywrxzqMM4%!PDP!M27^E5 z)`%P3hA!%&>rrl@)@Fm(xAK;dXdgH38rV&lx)k zOcd(}j5NBMzGom5#V?#qEFk$%HBVKt zlz||L$j&S-UcQiKXWC!-{Uf`w5$w!o(p+1?|75Ob!Y6mf$FSE63;xS_AjcW%BaNE& zGug*$rN-R*Vb-T>Y1*cgTY)o zUzh#)iJLU;x=(pg)sI7OJJq~xsa=WIFo(LxtRTg1qm(HnlnDFXU3Y&a%~Pe z%}5RBd|Ic6KYQR^oB=6-&fUDPerI_>*8wpG38g>sr@8Q>>vOb`Ql)KA(;Ii8xkQ20 z@}b$uy<=^3@HZ+Pc=^ztUCeA~01;=I%q+?==C89nsJR2Fl*X$#I?;3D*cy>bes2wH zIXj3R{ds>7@TNF3GSfm8^Qox*(rt!(kuKa7Bh&}IrSdIvYjI>ltjHg9g!*_3Z~NhwPp1VFY;zFdxozu@Ey(=4nRp5P!v!>2z4bxf}+Z(^&!<9O@H0RvZm8{-u*P zXKNB>!Ys@)pQgG_$#Kw(pDZrtO~#;Yc+o*Kae)k&O|>=au?9bbf5f*xb>L2|9;y0v z2R?0lyza1Vm27TC&)83yJs#0z%5`m5E+8fEkHxDl4n*UE2|BAz0^)>@%CMBR4`Es_ zMm4G)@Sj^sM#=c(J=`TzO>6qn%2%}h6f=)N{C9)d>85wAlipi}jdcc)M z$g}hlWS-GdX+wHn@j94;!@>?1K0Be{ILXFnE#=?xY`+1qSI4YsWtQ1 zlqj?S)`|GQ|5MkXHUX40Uhqm!eKG=6ciORh$kG5C8dC&p$IZh$(3HbK^$yg#Dh@^= zE>YE)!~j)z#nvD^*9tt_2o8|dxXlY7FIiqRb*K~UaXTw#C%FhOIi#B_nr9@!f^V4a z{d!=CPr~er>Fwe@=%Q=-L+ORe4}R1FIOD!lrb_6dfA}_?-*z6HR34p%o+_xXmkn?X zLPp=0USZotrT3Yn7c)nkoirVF5~7uW`I8mW*Y92&bDWQ;8}r8@lrMuj=Kx~3b@mX+b2*)5L&jq2C3FILk{e=-W=cthm{(? z!d?>GYCjAMUEb;+g>nZ8wJ@WIWIf~5!{8Ft39QV85X2ZFYJw_pcR{q)q8M$jq3A-f z7C5%ksw+`v3uJwF>{MsY=zI5(7XaQoc_J43;CYD8cKmTS7yI;itfu=Uc;-+2C<=}+ zPu#1I^$#g{(zC&E%O0``sWZR`z_$da#7fB}a!mmfx&ym{0fvNH#>J6D_K)q{+i>(*x2?^24kzbn0q5HF3c6_{RgmlM zA-RT)Jthj;q)%+0{_vF}KrtGc;%pd7iYiP=%{O5CR_B4`Vb}Yoa=2@YSaey>S=|O6 z&b7a|vtG5K9n>PQl<>x9A;)gRWW+dwKrP61*Uw@9%=I%>>A7ePKY+C zBdt|o=q;^Yr8oY(nh1m5xn-{xo8bg`P1o%QlXS*d+RRw6tPmCKc3X%qIyT z!I&f!grd<}@puoQ>wm7C>NC(Wm}Z6V5EX4I#*q0cz`#x4N(t)Rp|wUDGO~8B`m8~5 zJfhK5y#i)yPAwmq^Wg47LQI zN7AT`u#ppWSU8{a!s#FMks0Xu%FK15c$&v{fHXqzfSYzjkNKjFkY!(PUl*HUT|O{` zy9cp{VHhHBnQ$-ADKBk-SoNSr{#hqS{)>7(bw}x49~0PUR+cx3u=kG)4&iIoJvQFEp`h}pI4=Fz^ybem`STEFx{Ow8U%D8mSsG~3) zSt-C7ta}!OfZ)5hOR&uFrEF{;bad5e3sr3N$@?X*ShP++#kN>4e=Z`tn6m?xs_av# zz=E@5@h7zO=rWN{{*h4(X3k*vfG8BHW?6G_tyP?4U^Cctru%JvuBs3){j_y82u1x< zYB#8ai5RvqtR2o2P-CWp2{}&5O-i;&e}@-)7L^6sKAnF4Kl3}0a};1OG8HhtRI1g# z$55a|RH(8)5ZO(!=?1Hi{|9sL7-UKJ?rnD2?y}uw+qSFAwrzIVwr$(CZQHir>gSy2 z#2fGYCuZi`e8^aP<<5KM+7UZ5GWK5A@8YxQY+~k7_b(F+M(V-qts?NC`iA=2iF@%8 zl!DLtF9Yxb=JV%66?~TVk0WR&6tCN<25%Ew#sg%g&0?liqeMxu zAeY5LK2|25Hde%I0E(J}T^Hey<># zVxfnY!uXn+J8J*fjpL9WJE&@Ul6p9Nceozn^XfwT5D~=FDMp zKHez8?Zv3_0pA9PtxjLMmwpK3g?w5a5S&j6k;jvn!W+wI6ef0jaEyw21W$>PJA$=+ z>5Zr7h)doWcs-J$+Ee+29bEk?Fkk`6ZQBJCpjT9vGsY9#PDB$}hF*o88uN zskXZXQl?Kasw?8*-K0}rEbmeA;sb+UD)iXr# zNmLW7M+W6-jnrr6g00J@Ay>X()VtkhFgw#sXch#>k=`IshmCPAYZT_N}k2C60rA*n3-uwY$8@bhw$%9}O;6o<40Jv5`pO#a)OS)uG!?m|0*YI$FbK;_^AsI)CNdbQZos7!S5ec~8D}#R|3c)9Sj`s%bcMaW?o^2c{s# zp(5W9qrO5-xXiGHohSaK=So?>(*|RMf_kFciKRx9ttvYeW^5QEIV5nsu(02VA36L5 zBX$S}?e%l-$N>GEXFjV%n%am&X(UMdUD9fEvK=-~_p)$9)!sUZ=lj27o>6Pm!?6gv z_w=`1T6UCyYcGrz6j9TD9r${zA*l8N3t*Ts!uAboA0sG1s%aA+b|^*F>~TO46keAl zaB`)Bx1q!gh}mU2{?gkQK$hl>@jDJ~ z*#eiONqz@GNm{*Xi+xsWQ-AyUnY`%OSc>#_wiGKi{x&7sxrG~o_o1}|eOzyXuGgRA zK4;v4IWk=YUv7_JFQ~L$&af_r4^#Vvo^oh+Y~ev~nK4;kSoPB)pA?bvQ@3f;%3KTk zhYp!$h{9}yUtR3qo#LWY3*|-85yX4Egjta+l{v=_n5m=+} z0f2K_Fq`N4TUfBA6jpd!%JU@SONp68?VWtu#2`gGNr0|fd{wwVll?yWe`2jihj%*@ zyLgto!tre$eOfKS_ODnfW1@PQ&TueEt$Q@v-DC*M-F7s+a@zMgu|p=0w%^lDZ?8`u zBee~!m^3cxqjro8gq1=;zPQv>PPmz(u(K+1&L6yS1 zV!O}83CmnvGJ{;-Av0^Pv-M{T0lV=qFG95-=0EsZdh`TTbEUPFFTn(!c+WQnA1vLI zCrsX;-q5;i@;Dtj&gFAoq>9@*(yFo^?H8gm?xl>!4=X`@!i+P@iY;6F$@e$41_w5<|sgCE*Ft`yO4%BqdP|fAW z*8&#l_DQ9BuKRJCu!NgyXOA)RmR}Sjag}=L9V-?FDrp-A{n&qqQ)h$w0mFO>vQoX) zA&5Ahf1gz)f7Zv2$>2R#@?gjVTK@#zj9-dkwa}JTV?!l9%9zTa9a63{apX#CtD$`G*A_^L?E@m0$A zbx3l9dm=pGoiVJ}!b_0a*D|V`xloeYKLf5$y5)LDn#(2lR5vjmoHOdOERYH9!~v*k zAy!3ru~_oOn&C;DVU$u|fjW6)b*+V+RLpv4Tl5dfpmvO0faK(7_d48r?| zpZugd)ycLnMw_B*rHjfT3OI#Tsd9h=;$`eevn&$EltL>{%d18UnEmLPBy`DRM+FDU z7G&mDgUEo&zhU9PIcOca?t%Hq1dU0Gg0RmvWwq4{MfI0Tp%-?xb*h@G^-{&!!cixLvD4~i<>%&$+j5~Wa40JcM(w#O8QGt+GIp4bYSK3$4wR|P(`WaX zp=|2}bG@gjErPAQM#~GX=58J9PSq_JE6UQd5;OWpkBkmAMU=*j@RQm1@zuKrTp$#A zFh(nuN`=PDmQ8f{=ciC@PgqK=W(hJQK$~80n;>wX?pGUob}FB~pTtOS7z!d>Y!!$S zwc$#ACim>G0YSnq{eDTlDDz4`AiHl(K3owYucvv`4r~!oBgy#br(l zNMdf}v;N@jS#)H;cko0)k*pz6zG89d8$EbsdF$6q?SuF{kPu}9rFEBx;F zG973tv+Gj!(T@xVROas_S3(i)kJ27=)%n{oey zTNf$%Q(xs3Fc;BTrnesrus~=}0cBTPcNZEsu6okMUkH+pfu4!+?;-z9 zM6&+BwTemq|A&bDhj#iu38??8Rg8(5^*_K*+}x1%4t9pRmXI##n)73kX}s_t(_Gp; zcC~1H%BV(^s)_Gt5?cE?F1G+^tFZFW;ClgI-0Q6wf}_2QAf+dBAP$Wjmr7l@>$bBhX`wO_AFFk;M6TwGTQ&Dd=cU24QDPcN&U7+EJ{ zg(p)x3bCHpX|#6W5PAl#hc=-&?&^TosH@TiXsf77y+Zm^L zMP;(S4p`i~mygX{&EV!}R}1H8(7u-Hr6YdOir!ntoIE9-(3EF-4Yc!jiFo^e z227ZHHPyEq(`+X#d*6#6)078(0)onPGcjITR0$0BeEpS{o=48aRSPeZ3CsCWHOxc12R?q1tB5`#sN z9mi8Bv5Qf*=}mBJ)fQXbkuy#^SDDQBK*B3mENZ_<-v09i_Wx0A_J4Od{YQ9`mG*yw zC;wp&|2OvWFH88BMf`_7{BHmD|8j@_2+h9xf7!%;UNbTO!yf)U?q9I-@3H^XfA9OZ z?%%QgM{4%(0pEp?f1$;Hi~k39{O^Ype>)6J zjQ`-De~bSUYWdHN@c+=NvHl0G8tZ@3selsgm+}tV#hhKQY5Pl8LG# zijwn=Cni&_w4bnDj*lI9Dp#Ic*3Wy++v#;vTIN+9Ppjrtx>ueM@ciNVsnz*9=l=EK zZJ*{QoZm_{dkO3IhVtD;rX?l#UdkcPwTLE~Yt6f*8B_=aCEd-^G;*W$*8KD61b6{_ z{M{b(I@FE(u>t(&i)nbfh3J&IvC#(uDd4eDQR{BWaoWYYs~u-RFVd-Y$DZD`I8S46 zUEINT-xYgn%C@Sl4L{lsRR62?1`Az~H*$BBIq$wTmZI~;la0$!ElKbvnf?`dx+Xh5 zb;6ZD)hNhXblz@4XPIRp2)PY-69|GA>iFTL0sC$Q@PR6#7=_?<5j)^PvWe4W7rzQ_ zhM>m61QO1M!8dv&WHj3qoEZw1U&!;>B-aPs;XGG5`Z@QFcNb*4({8V#im#?no*XsZ zN*@3=CU!}mOL~+9X%gK57ZEbyKM}b7e~Znq*d37!7Ve;Tbkm&<{6<=xoP8vJgGrU5~QKZe&Sd;okRC6@(&VzjpfHV!imG;XyxHhE~ZqqPU~ zPV}kzS=Gy_TVW$jiO3QXk?JDVaHwwS>(q7$@v-rm1g#_xqZ_pYwui8HRA^b)m8Leq zHPLXWO*N9ZkSMX}ai+lBdVcuu5(hX!RFsCH1QS{od#e3%0)^=onI=V|) zGzUT%mhQpNI%owJ0w)kvf5YG9DamJqT(nK3mip|Cbl3{Ygo_&fQ-inM^ni1BHS_D# zOpijrk-eV`9ng{yQ4NGyiB}ONa-uuJ`Fzhv?+|t@W;CR#f?1l>R`9mb9I^`DMP8a- zD2u7QQjF%4fn=k^B1l)q(|W8FWgHYW_w zXIBCrv^&M=oDp2n%(?k9g$I&>@yj0)(e4>t&!)tBP_I#1vMfZ#-$ryjtoRukl3zmB zr@~3?lVz4MYU9fXxvc_J^I>l!Mwq{aZL}a7hQg0J@8q{0URcHs|tYfsU}rZK6HwR+s{4EDjgYPj7|55HK> z#LzuLFEqwr-{98YyE^?btVnEau8QgBAARHlEO{ab?>X)E=4p9m_+bHtNsx3+&bhD* z;(2JG`CtPu^;I^7vyMnx>->JXWLbeRP!OMrK@1DpvcvGT648ORUfkfBfnujiagdGepFwo26I9k8uRIMN84|&12nSNQuhiHM8p(=r!}$| z)67+K)TWuPZ`7xWttHfjt-L}rURtbmOHyoJZ!#}zP7$Lx^a7Uv}cxR!67>?IF7fno0OZE>JM(J+g+`@uOzryZdOp} zywrMaf+VnQik}a!vkA1@-~RQrrJ$kSO8yi$7X)b23av(LubMhLqRNcD%J8*z540qR zI^UsR4yjY6trl1|U-@1#M(J&(k*2{N88c)EJ03Swa6ioP`$yib$EHWV2h*ne-L~Xr z3uhraoCf-2h!U+#y+JZ5{hvLV?lY#NWQpCOdVIFPN<#7^#WJqz@xj&P{QppwU^dz=1FH>xsY}ss z>QdBH&&44h3#m4>c+j8NHY*GoPMNw`!$w`U11}X1%C~1!oC)MqnHDmS3|xr-ZKfLD zi?TUF5tIUz*YE&PzU9Xqg)T-R#Z0T77qJdPJB1(JD|URfuc;_FE>!MsMJ2r!@q7jJ zvPp)BVP#Yq2-JVE%dUV_z8{z&+&D(`3Jq6Fchdq#M!Cyz4 zFAX(Qbe2pVpdo^G$+xkss#bcX00EuOx)o3%Qg0MHS?2}@Utpdbuyc4M24D*znZ6Qy z&Rhhvw{!T3kt?vUMe$X)AFc~mVp^%B;^9E})+$RiO+D1o-_va)xI6fymAhkvI|C=q zO;X4WnEa71Du)+IGHRXg4K0_hbU{zXer%KwWG~cCMBK}}S>{R(K|zNIBnBbgKoAW9 zflO?Rvf;!a!dm-kaMe9ZWf-P_Prx<1{roi-Ts_dxd}V!g)tz8bC*N4dApa=Cm}~j& zBxchmrX?m@-0}W7(oI;q5|O|olz6-BBSpsFki)s#EiZ-Jh=Orpvz6n zb@*&`wfzrGRbzeQxv2UB3_upKY}=GhamBEZQ=BgWk!}9P3zIz2)uv6qJUbF-fWGEb zpQGN!7lJWfMu*y!7N5 z>%3N`=Uz(N zv;+=wegrB+3KZM$V4Zke3S_adg1gZqb#-?7Rp10A9gP`#qWQ)4sxBFJc>W?*PI)1x z2wpOYi3%$6F0x&!gi+hgjeUyPcLcU@LM+&yQgCH@L0G3NlyW=Z3aVt37+m81YjGFN_ig1tQ3Qm2GZ#Sji`0!yrOtTZ)ge)I-U9t~i)wz0}$n$pIDwUcgP> z#pB<`wt~s=2o0$&4(_|EA5mstZ)QwBGe~B?cYV5`&M0d|ajURy4} z;1hH8M*uB1;7Aj7f)wHf$RlWb8!rA~{QFK8uy>3z?PjSWRi1)|Ddl^H5#)$wK8K(Cq31%1E>--FD-1fk1r+j*%!ZDz$!5 zXQr?&n<&|N9+YOUBWJ3rVcX%Xt z2g0N+hya4Bb7O|}<_lhoD66qQM0OJ9Em7LwQ5C6(mLOhnaK=4M7v9_KhvpspQ{KQsSK!sXyHl8Y!7FBv4-gvqMgS`of9SszKl$uIWfEgskG(QFi zqa68}$-;~W(f*vT5R|2w$zm@Z6{D=bJmhGOtc5?TrP&Zu)ki9BTj2)>I7qxkU`%>? zc7lI7vyHL3z{hM4M)fPhhjifyF|m*Oh^MF6Zs%sZs^(H_6#OGtcra(aBF=fenkB{z z$>6{m-5LqIsy<(G1LJ^zKBwUB4yR}1fPg)xg8_5DfDorcJ#?CYoo{~}Qmx^U4peOo z$SH24-Y2r#(N?!|SFy-jEJxn!=p zqsJ)D<<@wf9Lg565?CPzyEW(Zpti_mQLGk$$vSlqx=46GzWQ4Ec073I$e;cZw%gmg zyY)tOWq1~iCM&+xiwi`2dB8A;<#E|+zK{^&#J{5PG<{*cgw=8CS4A61D-2fWRmca2 zBO_Tga>4;rtp>CDRj3-Hr%unM-HCknVqVu42QA4I8ne0_PP(@1jwa47RU760XlHr5 zt9fNH18(5qKu=dZ7|L8QL|twKbE#EI9wN5T^-IN$D6)+Sv@1mjOVpQ;DNT;B7Wh`r z>Cu`Kk(A(#Hxv9-vz*E25FodgOeaE4luRc>&cs+Bc}myAtzf@LHLmT-oZ?R7al`&Upj1x6*uYRGcG-6oI?F?-!Vn%CTv^xr^4JJ@A$jx5y`{bH9o?4&ez8nj3M zK;+u%4WY)Z3FR6J5SkIUlhk3JV4`zdG^ka30MRWAAVT#IDWe0zpx4m^RW#-m0*yD~ zi`{k6VcMtq6BXVDq4RQeQqc< zbR~||#B9X<+1xXB6qThHu#fsk<_R?zf}E&|>^#wIJer5pixsEEU0JM{Vdd@hEGlC9K0ewmqy(E@2`% za_U(Wp8~PMrQL@7qs4WDOaH)-As1p{^TQx7Kx6=AV))?X3$ zGI4vW_bzkSd^j!5#WZ84YCH66g7b*JZgyQs=RnPuaWA$SDaL_cf1VF**c{n|ggWqj z&d1R|{5fx}Ry!EObC^2g8FVi$)_IyrZ@`}7ZY?4?c<13{Ky*sCe9_h?c)HKe&Zmj9%OfnhF47s@Ehb=KTgynO90jT`IGedwaGfN z(#2tHl?jf!Gw`G^{dXq-k4lQeLHe!G9Y@jU;|;0QFC@!~)6?8mUvT$*=I#)Dv~iNf zN~UV1#T*3eVoOGhY4h~*kx8?0o$A`$J15ljeP4dm{T7`+P_dfZb^u4dP}I+DP?2pq zT~LiJI(|?}jrx)K$-fqTp^QFueQ7kWbD*@Gd+)>jDf_h zj^OvIqIyV{38oimRIh^EY5pjvpEOu4_CYoIJwz5^rZ#1Qny*0Hc!UVTRJ?!SpmOeO zC*IKoMTDAOgxtR8>sSAJ@H2G}aBvrJ7jU8;w(p4)w64ss9r`MKW}w=MkC5#W{!<;F z(Xy1uziuc4do;UL@#8A#60Ukfsv8u~z`3RF9KY>#o4MVYQ8n#VBkp$dvrn(~VJs=? z@gXyuwu0mJ+(haKwBeP@y;3(!eNRiRZ%~8@@C|6xkdrJfXt`buJ7j4<0)iRU_69^Z zX~1L=!B_#1@gM2poUu9}BPp+lkpQ3~k%sOc20fJQFn~vA`u6!B%>s0B<3>-d-0cp1 ze7dBxlzp(CdNZG-DO^qKo8DiEiw)=gc^%K2EBX|LmmD*xftHm*HQ1rtKoV9-Lp8rT zvE0%o?>HInYyyLXaR}{}M+KPRkU3pkY42=0k&1nS967@eaw1Snb6>EBHAQxA@p9ZO zly%I!&ERfHsUL?@Ixy7jku4)c4~JjMe|T3ZyP9wy4{PNWcX>V_^*;AAgrgn2!ggPqcNiJ9oa6%t1`098Q&%8O46i zGTJxl-ra43aH#aOo#D^Lr)lT5jb4`q(k>B*yp4!Ke?ujGM*U+xHQfa}=G5l_4BdX| z7~^2EDCz7pr6_L&>N6Abcs>hdC5yjF>W-#spQqio$dleS)GCtn-Bl{elcZ!MXxkiN z;|L4z&a0DsL3HE7f{!n3@NBY=g8f&!lVFQx@nd1nOj7d&dQu!rB!g;BrlVmOPNEZG z4;@&uV5Jyy$o-QMLZ!?d35<1O$*-KQV6pwFmBb8n$4OqwQ+y^ZpdWCfk$c(nb@l5+ z3?F25VxKy;E>zcc?P%$9KAhWcdeuoOobL@8*BN?!-VKq=lGe3YY+XC)U8Nk~k8NpP zrp8sX0U%OLGJY>sXkk^?9WWW!Ab>~M!WV3O-FSaioBEkb0`zKf zCQe5>c2#f5Z{ct0>R1}SWF(OK6Tz)Xhj&)4@~)lgmzx}0N7Bwot*R>#_ME}FVRpH~ zlKAXhxsO~x*kbHM47oiD-zFKZ_+r#VaD38n_UG$93FXMkEz)R?d!R>~v0snZwH#+j z7)s{1l{D&wmreB<#UIOihiyjd{JA zs-E?q*-4&iaOZ0HW7wsdh{akQxa96Iv2*P-p_&hqw9olwe5Cv&71s9wNb!*@7Q3i% zQJXorVA*1^Scsh?FF^ifxRslQC?me#!yF+DXZzX=JTj;ta6r%A(EsjjQY>0*o^9eN z=-HQ?PPcL5FF8@)o6WOIR$sDLQlLk!?xfwQIPPv{QQ+pu%CL?WrDL%W!IBb=qenDC z)}CN1tWQBW!-mciQ6c{gi!K)M3jLKqQh}r@WYZmVg;nIP0_jtPALX7uzEd?5okr+QhMrqJdb8yBJ5S!(PsD+P=2j zxp0a+vOHynAWiURTT9Tj8kvuE?Q!zXQJ6LuQ=)=Gy;lF=W|F-E{j;Za}|F6}HqpVneeU@v|sVM4a031G4F8Am(syxrY6caN=^^$XO{ z#n6213bIKtfxJQatVUUlo>v+;6O}Q%`}Dp$!5_e-{(urV_IcsX`nW{DgJQm$kMh9=K2wOqy2d`S;aKVSORP-76^uLIAO@z0S@ie9|T zH>qz^@!Bf;C!6-1&(Y>+N>{=C$;Ls-Ht=CPqT5W{Q4IE*eNrcV`Qan_5~%J#8E&5x z*>7h>s>uvS+Jyu=shbS_EUwRPZtsEn(3U&OL@-^4Z|iFW^EGG>mcO;14GB06@J@nhkMDag!e(~Of)4kGdDPvF9sL=9Ab`39j{oLRqU6kx<~ zH+Tho+rF%Rs3gAt>6`v`9sn6jI70f>j+^tb9eoEkp*?VOMG=ji1ZUPGA177`lHZDz zobU=>e$3-q8cE%->)#?mzcz&~%cgMY=eTLp0cB3#&>uRlE*pAp2GXd_n=9-up z)_{I@R5*iUN1Q~PdW-{Uh(t<9GbuBEPSqr9t-vbMOpdOA;gWwoA+{xchyZG2m%h6I zTI7L4A!aiIK$7_q12h9C+a~(Lk??YXpibY8{0t1>$I2H~OCacU5%H4_`c-8tXlWp8AzmM}D<5%@Ek3OrFr* zP|o0!o^z8jvg7aigOVz8Qxh+C#=4ib0)XWH2FZ-3`)}x%DWA)(|SPw zwS0Agus^LDwRfLJUoPeh!|D8(?fiY(qQ?6bUcHrVT@u9K;&aTTh8;xE%|JO8rp9H> zNXgQBA&h29$(V?#8sB9=*c{%CzJp4b&eS_3%xdK?AiP|-Tft~WTYDf3A5N`bN;kzi zGCT!;tRB#A^}hVs`vj#3E96lC~LTKLu9x+IwUl+q=;%~OcuJ0G4spBtC_!EPVnctgmTQXlL&LZ<2 zl#te}1yDagZU9B_oR+jUT$mPVFeFk?dblu>x!6mvIxdAQg@=dLTg{m10nFW?pV=U0zhdYp2 zX01<{;OvDOXu@Na4!*Hu5F0D{ZabiOjO^(jgfQC-fBJr;1Ej)S=Kw@vX7~Jf(6r43 zXUN_DRnD-@I1x2VRrn&m^v=ZS%{Z}yi3bI6HuTdvIJv zOqt`Bv@GFkhSiadFDHFc+^14>wqY2nIw<)Bm&}>*QwIy}_;ZX7I%XfIYQu@+4l>dl z;I$9nKInn<%xxIC+^co-iWRXWg0hGXfavSBdGjhKeB7?m027!4X_iT-oTYY3GmpV(}O9d-y) zu~?|bpW7=KftZjx$9OBqlF5+gG#itBzRd`o$8wD|1nCP3P6>`AV_^vmW2fdmT`qsE z{xbe^q17@FeUyUskSw`92J?1dvWmu)xfGEbi47qrU3N(da$hI+H|?<;(g_H2v_*t- z{}zf9t4hV9B04kXDRg)w3!a}x68g{9b^(_F-$*mzQDVf~l>tR2u?@Bwdrq{cIT4=v zfBYQF39j2Q*dLF|ov#l&Fce7^ZWbf6Zmh1ta@(M2aP~S0w*k&crD9_{tIz+O=d1 zmnmvj#ltIi_1c)sa4l*)r+7j@X{^_q-yJ4M!Beoqc=Vo%>q3o&S74f(w?02@(~e#Y zXscH1uTIjeioNv)YB_LdInSOS65zT#O*RsPF^bwf=G|-$ZbwrmnC_0K+UzS$pD3z? zE>~tE>Mli44{hbCCG~|%Yg#Q9k+jBW+hbT#m_QdrXZcAP*!| zc-7&GterD4kStmlWk{FMpXYnng~1yx%u_T4a|m~4NTDDaB2B_^XtLy)i!@3L@cd*= zyFHtd=|os(+8!><(1=n-X?J*Rn(F*Gc|J6hV6iCfv^w}^$qx7qCYjRm!Jm0~4wSaJ zU&?>4G&&!Zc*1enZJ!pfI`=Nextxn(JrYGo#=n7rRMBl_@&|7eFhTOW@gy;qh{#8+ z^&S#cLwz&f1F<&h%LKKaR%e`J9M(=>FUk5~we)ZN4oTF^!xrA&ZFp;WTy@2jBJb8; z@3od=yQ9cFopbklqaaTf&l)W%4SEk7zbOFImMXn~-MFh^mAYP+qO{9AXL`iIP`Mrt zZ;DW{9qcQQ(DqsaoGD9ND;f+}LR;GsrWQ->y+J2EfgWox(aGNs!6E-J%$Sm-FBOT= zu2{T|R!a#UWfIk&`79q+k&kLB4GW|Qyigb{026$^Wiar=adtMiotFwFz+psX2YcN= z9KSx1D?92>DsDLGd}umXq}FG@Ub-$~TX#OJKM&zKt5ndfP_?7k}*&+2I+IUGoYly zzF*?m2y%X0S$C>q6Ea%0)%h5ACHv@n*2Lv{Rs3HjqE^XPOPRT<>&X=6+B1kFcXyV` zBHdszk~(O4aN%y6!A&%NDq;ks|>$!Dck zP%p|-jGhGiR(c52YW=2wu9ACxvQ$qKwkl3|MJPzsy4@84&$1G9I6s-^soeTMCyE1k`5G6>VK68d41-hl%IDw$b1nb}P3) zr*vKUj`&!f6`PCJ4YvRm#oM8al`oZCE`u7*`QON2E;Jga&(;eG~A0Li#c4XxgpnF6UFQfs~L z38Voq%ue{^Yx3B!)X>i2(AT8^cnYhJ#?-pdP$v< z`P1;ww;U>t(fEf{YbF0R%j{lK#j%HoT&Bh>SAD|k=uv+>&0~UCOcRRC{4Meem?}1; zKhA+|vxE)_CI;~yt@n&=QUUSse8FMF^t3B1DK0ie5~@ddO>p5iUTIu1-?NOr;@jUwq1L3GxFw*h(Sfez@TFyt=f+Ly z*AJPiWGog@Ow*mT%5Si1=3TZ&%FOu;S930#(E3FjgKOnA>KH5vtc@*JRH`> zXOcz(GfN4MlHPf@(?6hel5^Uj*`8{H)G9loIs}r9tUoR>g4~RtiQNz$2#ZEm$_FlH zO&X}7FlkaYbPP7E+A`w(FR*4*7;6i8>!jzhO=lU+r;&%&@Z$YpoV1saAd?g{8xY;{&*?(_w4&0c_ zu2XZN_{J7ncMA$>Cxb_1Mt3C&H7N-KOeWr{@=Ha6C=ZN~n8viHf;z0Q=7i}L`a6YX zNE@nH%{LXKQCJ*S&6R-H;fJ zkbq+~Ai9&#xo+aVtUU95wp>{Aeau|*XSyvoD91S(Z~@6{lL5pB1lD(^x+l&Hx7@8}%^@RHS2A)eOOk?GxnuO{A7d3PYI5 z`OVdff*`qmP6z6jabO2fzgRb^Ro)YPZVS}YAUHtGFBJ1K*+^e=kkTrsYwJ^0L@^|k zt;{o(seiT#V@KpYH-}{wwJTHSQ6QWAvY>}M`Hqu>E|P2GdwhAcmqO-@zm@IwlLR>8 zz;+EaQd_-yniY`ExbfuFSV^el>evOIpV2=>bUBZoaJ^)2jjWNUcE}s7CaOhYX48p* zMv2Jy%X}v{!DqQuDoKNC)2E9Eiqea;L3Ik&^>P~zz$5rdFnHh~}fVJ@n zL#K6hYXW=j+0w-L5!AFHKMwt|-560FM#Yn!fO4&rV2+T0zydOOjB|9H?>xrBW&s9L>%&!{jQG-+7*oPXZ$52z ztZoF+P!GY`N6}C9=(rHh3psX)<>*4iJS07)eFy7}goV%7p^7ngOR(Q|!sBoRZJf9S z*HK&N4ninj`*mvD+_Ez+JL#4#KSkXnUw1E4@$Hkit6gWY50K}+Za&?M;&ANC)w;|+ zt_R>cZpU|33+#FR&@2jCRiJY=9Y(r8BT*=r@;AE9xUKt54m~eQ?fKl&|njlG&4ZZh1$~JcCjQAf|0V_*t0rw^P)uOMCRP+tafhOE&f!#s zCF_n|{YFO0ACEZ<(JnyFa&Gb9*iEL5I+2pkslr-xT|rZ64xsKLjj%4ySaj^C^lez} zjO>sqNvjz54_a_%9#6xTB4aS7aHN&VU^$TO*PQSYc@nbQ$05MvvI-S2IqY~>!Rl4u ztP&_v0Bc_o~*+KtUI1m==dr`UN0;K6QBvq6 zqUcT0O`qEht_H(3K~p^RB0ySX12RiSKOOf{tNx(L z_-!WY==nuYyaR}kDg+-@?}`7^5T!;`iGp4Sjv+TcOC^@o+5mz;$XLvwF3acK0D0`C zRFv&wbuw#Dednt@2&=^N;HCI5n(+P3y9VcAxn+f5mochBFbO3FLoh{6fbBFvlj|-c z`4CnvG9ya08mk5LvFc3moLNLVVPw+ok|-Y2=I?|oDn1&BU7~5R>o(vm(d!amYY7`c z--TIW)1ZFpc%4LGzkKt2GQ>3nUl#~y#Bqy+Z4Ll%-w8y#g*KvQA>T7% zG^!qW2wy}Z(d?vbAzy3a=QQ9e(Q7>pZdD4t1mGrdS%Mxkh}nFs5V81fN`P4&k&~?4 z`zP`1Vqiz|%MC;Y$r><;X|OhYA30y2B%)_6-#Nte`2Ch^U*I<4Wg-Bj>SQHaHS)w{ zi`8t_)$A=(Kkhu3P^WFVRi8Ffu)`vkdzQ^@yJ1L~Sc7$7o7JziMvlYEp+JP!j7xEXa$o zYQ_s0Wh{qB0)fUYaQ}MvR=95@z?Fd6R%x(Z)q>9BEYuG@0@c$0e#3!rGvWmae*yi5 z=P_=3@=$+*Snw=lwh5Dc!}Gsr+V4|loBdpYNcwuI+(9YR^E^s5y1~CPdOZsGLbAM_ zfrUjbpms8|-yW8R`C$4&)nfEnGccZoz2QO-@(YQe8_iA?lH{8(@_dsqU00AV&!L|D zos5&+cV|(Y#K+hk(-Mo87aZ5~C(Nn23QjwR;;RPOjv4l&&xR4%WDwn9+gB+^Yu7Gr zkY=a!afYL)wh?(C^>qxI5BI7)`-gO!HY~0&(74R2G0#7Opv7KTIsIu*boOT7p04g} zn|X5DjE4tdt?46#BXe{ZL*Msm?JS4`s0ob+CIcUvN{;LWRPj-;K^%jK>v-a@M0cmP zt8H___sb)b=Y*5Ao+rE9#xU?EOE^^>_bimqK2O_4oR{A1FHUawpC+K>2a(t9HwZ_M zs^0V0-*)n*?;ake%ka%RYTc)z{ugua7^OM4u5EXhZQHiZF57lhmu+|1wr#7+wr$($ zvcIZlueHY7`+fGe_xtbtk&&5X=9+g#W|Dg**KwXBf`eW`hu!ytlJ1pv*ZcN`yc`94 z1FdOpO)1{oRUXw9#bnmkJBX83evd^k%w#>kuj*H_aM-?0fbuF^wLjMWIZm98qa;Da z^{egl1Kl98oTLcqXgj_rrA?&PynD}E+(pxwyxoIXx;UDjOlZJpT@@^E?M)@Li~v;# z+A{|ufoVegbG*8jZQb0s5G`Zax@Irhp;dM09Mt|?^IYNPRM`Qc<9=6kdw6T0h)8m2xYhRvMPD-zVm3dGRdAEJTO`mG)xuQChGO z;F&mq>wr01=j@S{Y3dko?Fs#UihGt&I&$bx7(Z(4C2+bQ>nKO-=!^yvojw7&R8*%! zU+_?t>9~qILRG2!gmRrx%hKZi=%MPUZD1ZumFwP&JyKpE0X}EBI3T~rr2bApWgqD)BL@YfvYOokqK%nM-cNRfMvP4zo-$s)Fd#jorF7uE^pv$2 zgBez*9~OIFG&3KEqC5|Jk$GGQ8mEk_*<>R*Ftz40t&=OctXif;xHq|%N%R4kWGM;y z_Z;BAtVbDHS^g7f`@gjw6%zO&Z7C&9^^NTG989gP6!fg@A!vmy^o;HCm_7^ERt^FJ z)-D><%%7YbH6tA(9z7!+Jsvw7offT_gPw({0l$^8g%RE-G{|pnU}WWh$4cL@xD02ukgaA7Divt;9soUe*tYj0l@zt ziMhD`;UoCvu=qcLw(;YYX>@P_4_v^btNq9>7J>vqqRqn*@j~8nYyW0Fn#4G0SwMq; zJvR23X27_7(ZqYdp6;0zcz|?pJ9BrAbkS>ICg29MSSb^pZ@D;jE%uS7!A-Ip>+ZI3 zXv2A*q*o_adpX+r!o@wCWXYM{TSWB2(V%zoZ2p;b;jEr16J`5~Y?YHW`(-@}om4P! z==LhVDNv|ZHR$BJK0difysy(WXb)9#REIClSijA)AeDUbju9)Bsd9W^Qye?h_|dMR zR+vsZKIx_|FpiLVxS~+q-+7ip*f|j0NtR96SsABOofPtVv>4sV zM;rJm&HR+HfZwT-IaKu$yIDfgIh(rYd?jvops$ttQB1q%tc&<3i479Jb6r5hiw^9G zdO~1`_DXih+L88;Mg(amaNRGRG4%)H&VWe)ud>e&W>8Pm$<$9m%uf<~D@VL`pT$K& z-ie`D9UD-pC+o1Iz2KX zUZBz_@!pP;&eQ@d^Ba@4U z5;i=(WK{*EUi;hYjs5@9wDiBby8fQKW&C&U_8-*hzsS{3ko2qk1(*Ki<@pbi^^0`< ztIj{U+fUEZf3dXx#8SV|*MCl5mA|GhZ1tb`>K9}CHT|`Yg$3d(=s%|~)b-!d*}uyF zi18KYUyc1U@aG)jFPG8J9sfdMKLdYl``YoVfv@sE_}c$i{&3*DIW77Si85ITW+a5oZ%N0wbvNE^VehBloJ*^*p z85#nfd_!E{CPGy84u5-H7jC-pe4l!Qtm%gK#B5x{bnEzr_k#Lu;E?6sV)JT)<+QAr zGx+{|T`R{1<}7!}Jw|6bf^UP4_m>CzpRkt3+szbMO(0X0$vMPhD1PATYZ9+O>Kx9O z$5mI77dYM;*7^eT?R947?^hJhE^V|3=Q2`V-}CKL+N{l0HUo@RSoLV==WIy$ zP3LZM2rt8Nyj%0I%V@s90wa;Sl6cyTs=2FZO+?D@KI!RQPK&6j?T-F2YjZAoIO0g886i2fu$(ZsiYtyVrB-O2c8dLW*$w9oiQ&9t4z116Q`_fq+W!b zn`;FP3R2Eo57u{=A4>>o3>^z{lkZ@-lj(0YRZMOoL7;_uuh~Mip z>4*bVJfi2IZ_5a^7? zGzA0c4nn-4c`fa|N`RsS32fs}ok&GQx1%4p+1_Fw2y1@WT)4l@Z_;zh0$i`4J;0Y* z8O=K$8$K+<@;5M4iv6k&A=hw%2+_^ZZ~{PiRje7; zqP#Nu9+QOOrq8di!_)n7cZ50u6%FR9jPtF#!m`do+oZJp->wMf)?jzpaQlp}Xf6ns zCL8!@A2>P@FZ_aRqJoA<&;;J_xWqVgsdJBOP$nttQfArC0&sO%`)T-}GfKSqHi`_A zp9GH%zh!=$yTl&IpceA@o;q9#ZuF~c6Vd8kO!m-^8y?nIOpjY1WF9s_!V=cOzWl`X zAF`&O=Y zjMt~UN2%)$q?n{3xT+6%9}khRgV2`+mw@95ETxH0m=Nz?yFk>%T^?RqDl;WLb#H(8 z1bHN1p6d0^LYz&4=gRx&J?vvrY3pMa6!-Dt2;}1>8eya5I{X7_`>nGx=Am=A23HCaEBq)FsTBf9fg`|0oX3|?)YxZwa>NS zF5&LL&Y{@m5n`T0JZn^J`yd#%@YrDRFxLVd3-iF>k@koj2fvI$hh4s&zxxgu_lOi0 zw`Y8r$0zQ7F*R$rGjvR-d9$6g|u5ahVY-6VR2LlgtJxA)zSPYsWY!J|MI z2PF_RFep%=_qpI@&r{O^_ZTx2+SWa2sBh{RztZ)gWj$v7wJ|>4=IHyM@s}j@o&E7c8 zj!2!}hb!lsXL22Jm>0I?Mvnr57x9sJ1?`#qU|&q`ssZaC_{Q<%0b%6%#bzCyzmne) zDeAHaUEcYCy4Q5IjdAEs#}35p>)7>i7_#V##s)z83dcNvL>Z%1;iuz>meWkk8&_E?$0ywXcBdWro5cClcnh}!7)afELT;R{>0?y@B{-ov>SF5 zG7Ty@cng^h0SX!ZFmb>d@_uev)2f=>MQl7CvLHdfkP1cfItEcdF&dI7IZck5P4q}{ z^EQJNdj(M(cCY%hIhz ziq)3gO1uM61omZ^;pqe|Ihc?$EF$~pTqAiM-}rgNh^-OL25=ZjS1PKDIb;ZhDj7%i z3Kos*+_sPd327-Hm~moewqiMZax49kUK)Bi9{A0S`mX0}ZdR%Q!N!t!*c{7r=l)&T z830Z#D)!?Y>`Rp0iB^;5JqJ0?n-kNNklkjaSXs{yA%xw-Z;gZ*Z#FatBY1%bqwKmw z`&jYy#Vjo45DOeQDnxQ2pu0JS6u-3*sO7n0(l9UW^_siOa>EQy0<@G%&tPOFs?+8` z7&KI_lr^_cWyw`?gV*tYTvhScR@ARR{B&r`A_bv>L;H-R;hbn zt>H}10ar6+<=z6naz$)IfFHy_dLT|!@gqzY4%Q-0fm2eQ{BGJ+es}D+wq7$$U1^-i zK=_cI?7ehMby_^hsp^Lc*O6pVmxmb79h+hi3F2bNLXFw0jBVF&d>Gb}Q9)%B#s*(N zHgoucm0z*Cr6SB0I4DsAW!DM}WxdNxDYFRQX#&gqEQPDb`N@bRf$4YqB1&7W17Z0J zQ`V(lbBg*1N)scXveUl5it+H77q%rMh)G_lRBfP*B34yNOZy;ZSCT=rs9IXm(YXgE zZA*>M_iSX`8R<~o*w&Oax)O1a_pw6zC3WlRYI0kOgdQbxy8PdcZOq}yjOfkX>5iWt42vwK~ZgJ{H0|AzNtO!tD@dIb?r}8H@c=Znl37}-85IaV7l^{ zxn{85t&i**5L%}oQ?$5bK>$kf3o#@8!3i{>*ylr%tf%vYd}hwi=WNaAQ3!c|cr zI2J#kk5L!_R2@6K?6yo~Jlw2_V=M&+5<&0>;5~POfG$zuPcXBOGBrjzQO%D;3n}wH zSwU%Iwxdp@mCee?eSlO`Mi4VYz}12#OjyhhHQNxq1u|#Qmfh}etroKWB7WZf2qidC zVx@yWpqiO9PR#PznCZT1{6k2L{eLSYCj0-6kQn>dIO4yKE&fj-vCrYk z|MY$%y4$Rm4(`Y0b0~LV5Z-E|FMWLcFIZEFb+2Dk71;c32E?1qDzGMCFkxDQ~q&wT`rANGhr?S!EzfQ$y!eCcGnAOSkoG z_Xpr)#d{eCotgUgK9nG?Q`2wQ2#CuT7k&@&TeGLX9+xa;bhOA=;;9hfzwoJICQwvM z>Qu6@VG}WkfsQCX$u-I5$-)fS!)ePn0(EDm8kUb$deYoK^JRrR*)q8XbZn#`oM zh;r}wxD3I!Qq<=Cb!O@F_r2i%Qts-%JL!Kr?EZW`rDAI6U}CTFHFo?QX#U+;@vr{& zU-X*T|3W1bdM_a+E4u&;iSM zBOGI(Xo3rGz3Sq4A~5{Y^Riv%(#!LbyQow;gG0&jL~-5!8~zWG6L1LuMu}Sci&c)N zB>yf-un4M_yxO0e+d;61J@xow^(KxPrvsbFS_J-8!m7QosRhpEWgZ)%!9TI^Lw^@E zR;m4YN*SiYFYJWdc@(_c8KHn)rrB17e~PFQ{#BXs27a$llcnt`vd$f~R?O+!HdC|d z@G>HlV9xA_3Fi%aZ{3#nClljd?w--&1IKzXbc7QHww2cU)6?=)^hR-fWTizFbUC1j zlA+`0{7!0S9_tvN8V$j?13?uv_RpB*J>^f?CNxo&-(YoFpR!G=@roV##^h0#I&9kX z%x;h=l~%2qvV4=DjWfHf=)7Z&PSBD3LV7K#6oQ}?}LbcUUnM{%9FB=T@~9k zug%XW{Pe>0(fL{`{#@$YJe{J`^1>$8q-5U&Li*w{XViyRgt6CaG0Q?ZS$;0p5wX>JJ2SIWEeJYMuE3Tyg*tN8wN1|ANv6i}$AH!Ao=_nA!&9 zN!YU*BSW~KB@#G-vF{Ck%AXGKLMuDkx0uski1&b=xCdNm;mG3?(evB*cw36DQsWL+!)P6*fyq0qyeX_|3z!r)qc73DBc z(IXxjT$-Po#s$XY?NFPsU&`JgS3LZw`ZWgD!W5N)%W~rrmel>{m<^#L6G}?qPY4L% zIATYR1kpX0TE3Sk6TCZNT@uXJv_ptgU@`5Y^%av}1U>ERnkx%L3UKg1YA{Ck672Cm zHa;~DzcuAi8A5x)v^0tJNnQ+y-Uh6ldJ7-EZ?=eXvBa?0C1Md$`^iEZV)MYl`eqRF zl1@%9;MDGdE@hGgAyoXVO|y7Qau^*Z*{j1nyzC?Z(-QRZXKai!wk0g`!8LBD_TiS< zXkwv!1lquhiUt&Y0%H%DXm>XvjN82nmPL ziIQERtleK9!xWR1%R2wy_Xu6%P1-_9D*MNB$QX6yL%KfyeWZG6(Gcr_eOibY?@*O= z#4A2MUy$WCQVFScH02Pz@KElIE8OdbuEc*cH=T3o)SEBu<5Xg)O9_$jA@NMG<{|G? z?*7Ikhp{-qxyl(483>FIhdiJpipP!iB&WU^h69=K+8e z$2Ss(Z2>SJbdHhM@07s<6Jz_8gRST3VT`xS|6Y%*&RYy2`r+;U>m)v9HOy@!LjQ7i^ zj`)YBs5mbv{o&xE7Fqi@i(}kM0}1cRf%IqC3|xj={0>uRpiNKo2`5S04x|x>_p^TZ zi6v98hQ<2D#=7$w82SaP5pWI}VCiwLhCg%93hpx4oCVdqu*`!RkrIPyZ?+~J;4@-V zSwIEvKem26fdAGui$xPViRK_?c~RX$o3;DF(c^%1%++Iv3xoCZo(B{=hy#|M5%z*> z$Pic4^=hqqiT`4l`*>msN3|Jp;@nrOJ7;1r)l%BCuFlo-X5v7V@zPz@Be}#I&CF@x z(lFIYU~pn0V{8nR5=Zk1?}d-fdo7te0#2Br(;A_T%HlVsf?#N+2+E*NO)N^-RhVBq0|U<`>rY6KI23 zMKcyPG$)K!0-uc*t1U8#5ByDYjP zzP_++13^Z^^PKD`%q{FRsqZPIB~Ntx3T7RngiT7Aph;1}N_8C_XGLCT%exBH<$u35 zT@3RnS(HlQBDdc0ziu}0Ts9CkjzG&yiMbH7=RE(J$DfryTdX7=&}C0$#x_dKpUO$Kq+2zCo1wG?E=;6SAkl-GbWBc_k0(>G7uu9B>7fMkO@s)ZJV{fC ze1N>4N1U#yFq-@i5>C>sbjRR4%znSo(nicBcxyhb>C4ZR0|rx(AnZm=5!n($Q!QY5 z8%IY2ejp=%qd> zBj6{PMoumc^T7DM;UY4IjM{kr<5=HX2kK8p^i{12HS7r3-1n#t*BiIbHM5@|)?9M_l7Zkv1~Vg^R;Ro?`nMz>5Y=FKq8A!OHy6a%SN z42*ws=5P7w4a1cLif#AJ_%%_+h8-q|NeWLXX@$VO0&`u2z%Q@yvlJul4w07z9`%O(jZXVwhj;@pm zF!y*bUgCxCwrICZhvOH2o6&o2%7m`vx~wODDp2xMk=kC&gMdfGujOv0mHCR z<6$n0KQD|Na&|fRhAbHA_^2cI7U#e36Ya``o6AG={q3mr3sXhRMd3FbUoOj zKG=*!`kIUsbYrA{>Tp{-<~vYZN*v3$Q{3a(*r#-0BcYE*jddSS4Kr#Lgn_ZVpFIIz z*ul_RUfidksHx3Dk1vXaU$@HuQK`R6Zr`_@4JbYWRZtdgTdhaLgx7`K7iheKu9yc%8kS+@{@7kkW z2YLrW208y@E%@SmXe8cOdysEmoOQ}er}2v7He9>DOzxPR!Zbd`9RUhloJWwL7BUPm z0tN$e5BmJ`&kMs=Vj*TrjS|xSxF)yQ&z%&x`6F`jG=UQO%(crVmCVr}q1h()_0wu- zD}@bi**Q&0{(-T7YG*;plQDlLVGo9Sxf!ZAF=N{r6>0egBaIBnqLVpm+KC)6L2`I^iJ|o&|hYo15s<3w->M;y?gz-X?UeiA8uXZx5z0pm&EPT%}A< zCaHLnE3X)$EdG#~aC8wRw_NFs!RtSQs0DH8;# zcVKcrLy|Ar5SMKmOmLzeu(p_LqymL%oZP6Qe}g*lq+K6T3Z9{F7|9VGe;hag$yEy8 zD$vcb<6#C-G~(5vk$VJ5^*;I=yKv`bd{R(n4G-(-Pwp)h9|2iiMtio#8&dAm`}P4K zuhHrHh#K~kBk)c_qaif6hkpo1~cioPd-jA7t?rr!W+ zVemaidh=(=9M3X$nQql-|E7*Fe^)pBDbBu)DUO|Fz`L(16vD<~glo1HZ|nOgNrpHp z77%-6A%#&q%@+(2DNl(qcG*rLyZN?Qx>1&erx(XrB_>RZaD@@FDV+r#hyG^Z`L1bm z*h0PaJD%>wUE2tn`z3kB(&FIWQpKwg8}L!P?W436X2)S5L|+`HFUro2=a=U(YXU(5TH8N)ZQ;Q&^b1|onO&_9xz97@C5|5 zk|eRlla;EUs*NXzbTmZC74PZSh!)|T5%d_6UP;po9#AebQ>XAzTck4sj!RUl8J)Rkl@E{ehh5`uN5|eQP>D^7X35v3X0(*n za2vcJ+{w(-Hr%yEaHRQhV1_alha@0{stNN80_oRL#tG;Z_%H|clsELlNI4ZZ3PxZP zIDsv98wWu;C3`pMKLdRS+>J@PoS?7SEyhx$T$_^6@qA)_JNp>( zJ~E!}^u-Jhr(MB*%RjAf_Wqcz;@r0myvdGB8J>enWD_E^a{p40XuqPNTmHsZd$@_7 zWmis%-mZ`10hmEe&1NNW26=DYM5-Vfz_y&PXr>$dQ?#_VPvc2;$!uOt@yBKg1a=8t zL66XLc!7Wl#qDr}1oS~B=X=;p}ca{h3O&QBoQxN3& z^6C2WC38eV89SoPYT(E#*|TLK``%jSoP_|(EtXSBU&n2u;={X++yI%WrC z3C+f!8Z)%^GQVE~@SS4#!RX@v=(UVl%x~S02i2&>+7OHuj+9iFN`Dk&(g*Z}BbQA) z*@}p{g0e<+s-&QWn)2bCj>2)7HVeyCdi!qfG`fV>j!NnZ2ONGJypK0Ow7s|RMrT&l zdyl@F%`WO#pOCi`M-yE>Ssqa=UR&oSkqtrAL@SYj;lNR7PpZ*z1^M(TP8Ikkojv9j0o{*zC!bZft01xUTFCqII;5 zqv=FxEZpKZkMwucALEIIYaBUeAXlPEa8@)ECQCP~#tiB23FtxbQ@&J&mqvPDKtGu=GKmS;Hp=MilyCj7GTBE z6qUUUc@5qc|LdlQRhw;d6k~AXAix$W0P#?(kPT~DKx9E~L8v{~^a$ws_(7q0M-|j) zh?*DAp|0+V)?}ZFc-0`1BtbIo{pOQ9H?CwIFM=B@zp`VRC{s}PODi|FG9mG5l<{!t^dD`l!((wjd ztY+sxmGMgpiO7$8+5zIU#YhlGmNWW^#tvy<+sCbZtUI&EO>HEAhnUKcLxPx|0ypYk zDS0#`S)Ok_N~Ap77~coKEsc3mbDu)uW!4`RMs&nYn_EURzTM2SO=iP&=x)|~T9wi6 zJZfnU0NskKI+}mi_Qbf~VUmoRi9H#i6|2e2`z&?4E2Bfh+xXJUs?+Zo{PgXGA`zHF8@#F&x+xrXbhkuG>Z56pmb--SrWeoK5A+=V}$C78wY06DW zQJgaMs@i1!TVdU^(|Me67x=I)(BbxA+(=RL6G&?RSLG3QBczP)X$(d zCp1xB<8SQ=Gqe$~k0xEew0PNc@wi2ux*T$rVba`Z1D2hJiqQQ=VSaMjI5n9B(Qta` z2C1BVP(OofIn{9SZ;GsmEQ>4*+T%eZp6m;qO--i_;nA(afIyr{Xzd5f+eUcYmX0~v z*pxnc-9P&g?r8L611--AM*(qFcBLx_Lz~f zxEe*xi#c}W%(Z9M2=I1kUdt2|>;1@fPcUtNU5W7{K4PA)p?ze2rMZ0q*2gigR!g8r zKsJaJvH~% zR|EbeHh3A2d>2r^GH~9OO!O~rFRp^Cykz7XP^_6>-$BYi;+HM;?s+h%{Q#qSzLOwi zKnLOa^iD5F4cXHW`_JYs1jdx)QYH``CnV;eaBrv67@5Tm=a)dIA@RwJnQ0|2iRFT2 zhtlsu&xGGL94x|G?yf#xyyhUBCYVg+SNu3+CINk7SzhB7+jbF~QKpD`K zcb@k3KCCa&6;-~!b4Gybc<yKs@Id~4M8;sGMY_{VAgiiwLcFeWi4;9pKYg%i)?!>t-sr%n{dHWar)+%8G+0b_2h{V zhD{Zt+N11WA1fk%VxddOq?eOP=`8(g{?(hlHzM{cO`5nis&#$^%@sPHQ>1j^Y9qvs6 zk?jiFNk={X%J|VYjFLB&fGj#tq2^^V`U3erQP1X&D4>K2#GIVTA2$1TdQ^)Ez$0`8O@K_&KPiG zJ~if@R<0IHD^51JsJjJ6aS=_}Mqri5s>a?Ls482FZQBjNG@u3rscZEWqp4vjlZ6vS z13l*_h=m7Ai8Ej@=gc_-_SB(67B zfN&JF@!*)3+U^2QoraJ!wyCS*sTTL@uQPKQQ)mWhM0Brx&vbM97~FhwALlk#Ht}Ks zVd;Dum5WnYdty}_NA&kq*bmDD>um8-sP6G_S@dD?No#YRFSjGi@0`NGMx;=lLra`r zYjiMwijwHT+AX7?2^)eZfVB@fY$jj`RSZ!;5=bSU&mrv7gJ-i&q={%)%}8Ecg)Zmt ze;GF$0*D1ht;?o_GKhJNeOkGk$Z?{=4wm@^lo%YN_sc;9-;N+Utbptq}QP;R%JS!+t#+erx@0ED?L6 zxxdahEGNDmp0x^$`BJTMUDM?zLdc*nd_=VKVnDn(afQkok}s+u2Uho z-}p*+0Acdo;fJN(8q>?@FFp_^BR(mR&$PS5?^@_nG1odq-nZ3pb-eW$ZpEb9+>{{s z-U8nDjrXR!d4`b?mRKUQD|HVRZ`1dI>(J(Vd$ess&Dz}Wcn?NQ9-eBBuD4-FMcy6- zH?y#tZh+_W!Tdh`4JJb{wk7hwW(lc;7!khvY#9qBkVj}wayengZ0$4~npL|u4*J9C zJ;9aQi@B#$jwg?h9C$4*S%e&Z6Ge!KLp19`8RFf)1jfnlxVEXTVHv(_;7~=gmZ4^q z?F4`&HWqHk5L(Gjjgx!Z8G25OurxMwKRx6zZY5pKV^80`uBuR=YcaJR+G+~A-y&Gx z9fpMFhX_ZKifflFz`b{I5ik2W|90MDO@flpOv}_}|Ors)SA! zFWZ~fryr2W1Qeh`yKHf3n!<#B_#-Q0kQE2Y9TA8M0wfL<#^@S6jh}(EA;QJR1w)kr zDl{;_@_sI{yC)9LxN5Z~LBkMuhFrGBk}xt{LwL1hSKD!>*e+!$&0vsW!9wIJxJ^{{ z3O(QB)8!LL2r~^zgWGlrI1DOPFXaK(vgHSZJ9f(-27BTeeGb?TOY@u^$6~Ud0%yhl zA);q7L&lW7gHaD_nu@p?Kx0s8tgC*B%j)He3s~VJ<*&JHTm%re@m!`yDrH{AH~1K} z`4)z{;Zk(Jg(f`AO`1-&ra04NbvH3H9x&a!da4i_s`qF5#3&mGl;mT;D4{nMc^6j8 zm6;>P)AFXdzsmV-d%qNQ%1O}HTzY%+$HNM_@@Xvpx4;@3l!>28r!`dFyFa@A5UEZ1 z^_!0V9b@&`rJ<{~8s$}g?UQSp z#|^BoX)L3a(w({DV0|Ne&SG@6Fe3Hm1E6Oz!M_>2-L^b=K-QQGaUl-@fDW-a4S0EgR0BacDlYVHq^)je3=8k{HZhP{tL-#mS&D-(qJXCfB0f=?}EwGui5 z{z64xVN<=hShWJ2dSPWfHOl~EipF$jCNW&Cbq<;P&@ z5T;30y)#LEKsY?$2JDFBwwF~ zAH2^;CY~qLCaJD~`p|+eKje=zG95~NfXa+cZA&)qt>_+)-v2SBLuzAkT z9tCMf= z0%qJMAPCRH7P@I-qwgTE>G5u#CsJq1FCzfzk0#U9vwh~tb@K&&ex>F7*;owB(Qr}z3xbnU4 zEGvJg%zH;IuQL&%Q?AZC6n*@i+-ONB>lk5iwdb_Y^#vVK?_%N!J|`!CxvS)e>UH9r zEqqC+(>gq&o#XF1=oixSuM?=~YGd>lnM14KsPFK#plIj#X$bIH5YV$X`eJry|GWP9 zw<#8kpFq&RaxA_WfPV)H{s}4kbN*M#{ui+DnYRB0CH$51|HT1(k_}%Rz+c3}KT`Yu zx$Qqe3IAjTKIdQS|KUdVRsIh$;;#Vz*!Z`(9{(p7@E`sAzxMgR;R3$+sQ-ZrVEZ&h z{P$FkeRg*br2U4&u`!-MD=PQ&hJc*IMB6cZJv86_e6YowpoNVD!OlQ@pn<@G4TpuR zw*0>F>=2Nc^bw#mJr>I*fM)|$5U&i%lqjn={BB5`Gt1H^>#*uS+hD9qgR?NdymY?w zet0<_OTTRExXD=bi2DY>hvM)J(#Q3#;6mfltq<^576MD#PGDj1OKk|kx4K_8iO8%C zkScF2_Y&VCGHo)-2+FLR+K<^8`JOetwHTBwT58@6G-oJ(lOQFpuuxl`OaH!V6sslj zT-XRlP!@Dv=t?sVscvP`>3Six!p-Czey~_+t~E`RjwNx5uSItpSeQn5#&Qqn>n)g6 z-Ekb4uxnM(1pLbqKeIh%!Nffm*9rcdG62U^10CJ%V5Hd)vZ7nrr7V|{zb5G-tn|YF0)zH?1j_`;LouOt*k7anlaTU^rqX=E6SNoT;xqz3;~!-t{~Pf1;{Lcz1I$-LOLrkGbY7|E_E!?;_-vU zVM-J|NE4%bQg(n?&)?R@9^z-$H**C#$XQ)Chy&qk6Fg8mg=&;|j+WF~YxZ(~78745 zUkyF(Im9*`GF(LurU2wFXa!vgt$iC{Jry`dZl*FW0LjAB&pU{4{W$4}Srh!ibldsn zl``T_W-SOKL|bOW3blO;0ux|c5naMz3QcN%WL*F~(o&YhmGUBmeKphE;gZ~a|E6}`xJ7hIu$tv#tQ14tTib%+M16zR3&qVBml4m( znFH=Ej%){ z=4+hh;cI`TkzX1p`tDX+w~?QgUl1rWI<6Pp#NEro8_n(uS^H&Us~78*b347wPhFc| z2_RgvZ-8DKch}4Qr&RAH+j*&-EU*30(BAK}Ss3rCfY9E*{7>7}j4>wgR zyyvCn9lz!Ed^Rn-CBIY{>$uea$e9gFb_H#PVJHCAfX(DeW*g$JE$TvE@~ddQ+M`+9 z$31nc9^7WQZ*pUKaJVCKV>03_$dDYA4tH>LpkLQo+H`CQOL4xeb=SBwy8J_``Et?6 z`CH1dL8QHs%rf86v%tK)b1lKoqZbW!GyA3*T7AVW*<|#nWrBCUp{ZtIUUbr;@zd+) zVtdV^mji`yS^VD23EQ2r482@JblfpqQN=4or<5{%%bW0*{hM!DAd`;Rq8@MNHSBW= z4DZVNZxBWdOi3#PKtCjJzNFD3W*SB-7CT{s65Gt0`6VMR14%ST?H*{1W#ul{U)WA_ zy0}gcG{v+umFKg>2a6*uUHa&p`#o;^H1o_OE7Ir~7^tA`@RyM5%?6-j=o*x*r+#)W z7RAa|Bz*F}U;rcy+|TJqku0NrZ>p9{lBYp0zogx~#-3skX z#Xu%z85U5D4m+A=g1}BB-qE~cmL?x2kFd!VEA|&R0+)-{sX__hu_%W`Xv<442Fw&BY&nyW_uH z3@`??Ii?MAt2RXsB04Y z%@`q`+8OBhJvgLFne zxTxeU%uoI3Qz+9{sh)>+SJU<)_v5w96S{&)mam!_Wy00Q1cXdQ_mPK6o5#(8)D(V5 z6O>E?#IuHqj^#^3Ws%|EE6aO=L=8oB6@DmT^6EbB7yB4rS2FKP-RQYO8uIP z4^6IWERQEj?Ln$$o;Rc0m!~Jt=w~OKRvP#GpuvXCvis}hKeT)*9$te;N$+4=j2>XNphfC zk4h+U2#KVW3^Q6;2=)98%H-nwdEdx8@mTpWYDQ$@owb8xRorsL=8~+ngSGr}BOmp0 zEjnX6@P^;)?F3E>cj8NGn`m5mxwLZ=z}Q1J z#*0F<_nkANzi}v16cNllh4KI)7vT=zmme9#vMJc*5IEw8bX+Un+KPoTjg7-gXZ1N2 zvpL@S?l6#R5gr#Xk;e_|r?(epN5UPrK6gk9vZaQo9i0)EJW;g3a=OhD8OP@NvE}Cn zWI&@PS(!lLi~}JkT-Hxv8OgR>Q6pKnRXqCtB$l&+Zs_a5FIei5&dI?YA)6?q3#l81 zvn))fsZ1v{4z@>XQ|We(bE)uMlnc$p7Hjj<$BmX7{?*^jMInZNsAN3mpd6wYoGLzc zyKVSqNtMG@#Y}p-Q6*_EHX%aY5Dn9K7wg`^052Y<%WRpCOvC&~+Nj(~@}cU%(%F&S znf*~X46RQPHYv6-mLJUe{wSD!EYpH$d5}`u06w)vrLE|$gc*=X3gMY9F(}QltY({` z;OY!9X4mN*Qd)Pfd)PQixlI~1^3SNjGg}#I!8#L^QSSZO!cUPPjhN&r>@Xb6O7{B- zYs>aBrxn9#))%S!M-}_!-Bp%5i}5y*%g#zu%w~7J9YQNAXKK%^l^kITdUM93xb^N+ zXrJhfK6OC+g^sL2D;!Rj@GGNBQ( zl(hK^TbaWFuczzfo5yYMxBY(3&UWX)dr(*lKe(A`eV$6?IQr+&_& zgwD@bMBbf*qqY3;k}GExza-1Xi=|j?h5vLWX>Ox8K%oot^zw)!DvRI*uypbg(#L-Zv+*jZDcW&$&d&Qj9ig_=y%_ie=o0&g3SA z7q2IoCuozJ&&rU<7hE$7?kHwg#$H9M(eeY@U!RWwAQU=4oxW2uqz5W$+yHTWq|lfy zPC&xTbrpM`wewHtpoVP4A+Lc~$NB4%r6Ba88}tazWnTGHatEj@m_*X**v~L++#2JKCvZ~nlM#@ zIhnA<2Y#QV+XTcZJ|#iAi=en(1@x_0I^kRKg37Dkbv&uiu5oytt|V=ivSO$LT7%U& zj1~jcg6$F7VD04F^=*ms%{_Yy=fyIXW{1(_@3IXp$J_BWzyZrs)`lc%&*IbjX2fcj z2Sn!!8|qSxYCed)J(;GTp4rBe=c^UWi<(ND1$Od!-Q{Je#nD@lj|8PR=3U=`tAC_+ z`;C?_7V)dg<)qZnvl!zUG8$prNt5UdJ2r#03oU8+#}GIm>*a!hhE{iyHFR&k{&5qz zPa3OHyguBILXA^n*|5>7EZ&YZ&El8tO*miwGZLk<=5Tn2*`CpQ z+4BD}_l`lXHf*|fujXpowr$(Cz1qfVW3_GDwr$(CZF~3g&b;%zGy9ou&;GZ6B$c`= zl}aU7QptUs$8`pb;Bq{0fB3kF<$X*#TA!Al9}?fl?p<|!+3$^P*krxR*ILy0DKR(gI*OMBAD)bFfvZ5+J>Qd>E$tZM!VzL#C2$#Y`9vawlQ60)9LO^^(c2V zXrh5J6UxQdVfP~9aU$^8b5i&yj`s2ayTx%gNm-?^*ugj8&Djn~#g^}g@@EdT^PRv&b%&X^qd)QWew zNq3Xo-{qr;#Ctk2AbPT}>dgqQ0Z|Tt^aOQ=hc6&^L#T0c2?4u;0-ykWclN}#k7Wr8xlWGzX5~0 z8mnlj2E~D-jw(K-WT_fU?H42{mCK~>2g-S)-mpg0#(5hQwI{| zv8ejwVGV)XtX$Coyc$#H+3Qr!8ti-W&1Yx|Pd%H}_`G5G&C0r#2hnEv%I$&irA^C& z=APn>NvCj0lV1F(ygx&;tOl7>dhy%})F3&PMg4*8Wr^WS>$F-Mfbm9uYqX3BXw{F1 zKoy|49t_-Y@RHM8`CGCK)-zDN~ zL~L$|ZBPq5m|ukNIc^MU#ZLw`Z{uf`VF_6tCI~TX+ud0v1u}ecyu#gaJei!eK^h@u)8j(vd}J?dk$f$a$&vB99n%EEfzU(yGo5|dx@^Si!h?JPh(~w z6we7uSr9Z;cDCKDJ>W-@TU0^@CRht^>dVGB4|Y?GfT=H9R>&k}3>>maNAe9DZCGB^ zX4crNl#LLX60X8rm|gr)0n&(LDVKQz&yh52SiN`TV9Q_AmU%d4uJx=cavVAK)a8Q9 zPi;Kd9Yd<)yaKnO=P|)_Ieqdw0?l+`ee%z-l2!$a`x=|0K4ZXj#nDcVFm~jAh|aOQ zctSoy-7_=BLCl#vj~$P9!Z2UV@q7klCe#QXw2p|GHJmq zS1p%hqIu!E-Q5}Knt_=IYy6;au z`uPzj%flUZ{Zu#PjOAi*1oZawKu0_9G;lS*qUE!+xBC2Xv%!W;eRFq{)xVW{=XvQ3 z)M}{f{P$;>|EY5_GcvONe;9<3?tiLtstW$UvaX7e?tj-g8R`Caos*G{?f-;9V#fZ$ zAi__d5VUi`{?M)xKgp$i`*UFv9)W_n^AkgB!t1uL&TjH6=0T339IjlNCnG0HYDj)n zc_~o?Z(^2lOYKciZlgA1jQyHykl)cMXu{wasy((kBJZAF_M(zZ>=@WYdLKsx9I5g?EHhuN0uI%NA{mFvKQJ#`W30zAt!BGe+=Du&Uhz{}Q;jqOn z@@*g!=RWlfhO1~QpsQ^9A+A_ldz9lP`is~WVwPB!C=aQQ_W8_#4^ud))p{0D_)@iHYuC3B-n*yN8l;>~+c8vFht1_e*hjyYjEG6SY<~5cxyZXq?qDoFSwzhbE-W!WD{&r z=(F59pnUhU%u9b@+_tW(wXVm)tBxb3g%SrDCh=hPP}T&!#1r1WuI9bHm>Q5U z#@qbyyT5ACt;Z^%%wi>I4%yhNDJfDwcd9JIo0E+WpgL1q<5h>eit`jas_%j?ITVt=g+D?EOHxv8 z9eO1-@*ZI>3J9Yd-G(x#@_6PBbU=Y{!kf9QDlnC*@l1P&tKceqO#PP zGD5pfX)myZ!rQ7cK7{&!MUx*HaMplb{E1}Zs&KbByg}!FtYUZP3S$_m17+t$Qw3oABrvcsU5t0kRLkRJR;Wa9huY?wMNxX zh2IpXS<`vjl`|ncjL>d0o*||j&`cqr9#Rk>rY*UsrsOmD6{E#%LFwqSgvi zVh-m`+2|a1qNWp4VGerF20@Pz`u5vPT`d(ESM-NfR%8MCus;4R-h117+rG7yG)iqC z517_EumOp4HrT63rM9QY;pbJV*f{C8XBvw|LJcIBHp1b7g9X4TtV29CJxf!2p}Nc? z3)3&THX>vHMLSDPh#51bbN+RTEOLx-i+SFX@eYG(&+uHcr1>4{+pown0mhf zM_V}iARYIYe?ri-H4Z_-wJ6pRi_>Y9*xlEgg5`E;NQ%2?W6p6<%mdqFU2zVo9EM4u zTQCM7FkVo6cl@(EMzE(FWY9}cIuN2?2iw9w5aJCTdZtfI!cL-Q^9vI9Mz{!t^ z@v-erC|=;8md+{M!`G*G_)5qZ9#!F|S8I_i?z*k zxsfA@$sd}xMA)eusx5fkAY5& z+^hH^Ytrg3m3uyWj8RKiy51gBH+@41kA`yY`3J#{{tLK1hev#FF!xhF65dR<^>1Wr zc+8UFM8Cx{5Yp_}Y?IAgRooI!TpI7xN%r)%@#>DykJ+YO|D?2ZneoQebQyn@e9U~T zUuu(AZqpR6{3vqjj(qQ_6m4A7dcCN;IBR0Hin2ap#6`e0z`*fcEes?nYysy;Oy0Eacuyq+n8;mU0F{P?Xn#5Q6 zV!);WsEhrrnquk~d^#x{m6Zjxwz?^(MqP6xM;)|sC8@p!mA;am{3w50S6iMy6l-AwipI5&@ zK25vzNrzTd8}?|ZL`^&O5~jW6*6eLW{dmDl=pW=LFwJTCk-XnW;xA>Tn0LKwBBs*Q zQcp6v`RFM!8qDBoQr)pk7u?DUA`BWQ{2RU&Smy7q2gJ7%BzxbDnDsEMjH?=5g5|$fdCM+!5bs1js zcQ&=mo->Xv*zSZCT3K0TZ%4EsO&%T;T^b~#pOyTUHlBfcPF%V^jPddrVC3^LyzuPh z4GJac%H|1mBR%4O8ya>lWo)5Yms>`$P+hk|Zw{(deE$!E9gy}d7|!K>?Ntei|Kby4 z@GsLBAme%P&_WIoJ}5wlOTDVB6#DrIs59jfG_)*=_)g5kUz2Ss*NYQH67mh)g_GNCP7+L)VHF^yOTJa0ty*$pmsE7YpJ# zXN@eU1N{SPYMKJ+-3g*QNAS8O7fN!%s7gv_9YZ!&mT%d#srcS?H_E9Y(vCCom7Wad zV6=bn#MH>lHKIT{j97J9KH$u{rfq&FrXS9nZJ$~jDRu&en5lh?KY~!m%E-w~zox0W zxZKgh1CQQ+JW_I^Eij^K9^TLol&nNS`BGu9Cop>Y4}dcdO8Xqij-o?+M!8TsUJR6i*GpCKbb-GL?tlsF@AyY?oQkp;|LDmTfjO;LCn@AyJu+tFdAPR+g zF-QlQqhhdJj;9Q5HlBK-)5`Hi!KUzLbkizzNRNW6b*)Qr{9bQ1L)4l1;~x!P{hZ`r zu$c~I^<;(4sTtko+28e>s?^P(+!d3s#}Vc z{U-*D3Z^jWG&i~0?tcP}-(T0uSwQScWH}=eD3B2f8f9zQjUlJZQI!{yb?mvuM1zJN zko{RiZ3N1pQ>KL)?sXyoW$fpW$iogv-lMiWGC-?IN)=3|0JX@bb8A3Q|CJkvs+_Aa z&j7e*3WDwkj8CiQRRLQ0)lb~3|Nuk3LWFFS`sHdJvn^|L40!T@OrT?DRrAIy4tYyM{9CKIO zo9v?<0bV6Y*a@!jeSC>DM+Fz$oA%`f0;h`eHZi6U2C|DD} zI53FSX;7*qh0u9?mC0D=q;uBBjZQYiK4Y_N9iNv#6vw`M+#^&pw}#{<9J=&t20pgCROwPr%t$ZJ z7k_az9k79XTf)Z_!m)r)6r$g8aZ1Wzw>LBjDJZqH&xb+8k95rZ4pumO|k0J1t^gm0w{JaFcK5ctI4jMl?F9V7iB#8+|Fyh&?n+d zIdKw6PeH?*cb|Q3l>!d!D~5BE?Pk&a_nRm^#U=W{jBmt1#F+|nf+a=p_{M6qk{U1~ ztTHVKGFvyuU_pXlBV`?|mMb+wrl;pziLi~+$4f#fB0Z%MOI`vALY1i;5eY&rv*tZS zgwGrzBux{5hHKU5zHrc@QG?uGt@>>70CzE4_O3@mn4*Fr$wH+;QMsk1>_)5FUHsue zW2Un_u0!6ItLoRywwurPQHBfFH^cClw+k>v&BHH7$tF30mAKuVCODk!h$i*q z3`v;d8)BA30po~6D$-0vzvz5BZXa-S<>_6X6VfQ936|T+DP%u{?RcZuz8BwmY$e|q{C~iv|Tt`uB4ae zKvrE|*Q1W6a&XH{*DVc_mZJ+)WmzGC_Fd(iItKJ+^!D{4LX5#WyQh47gK%#?U2861U?NqkMdzM{n&v+1Ud+}t zCHC&=#T@2Tcuf+;>^Af|^g`_wwnQfDjKxU6bftT_$>j8qX7dU}-a_#PJATFL_4P5B z8Jh;k;fe%1>V@i=oZ&wZ@|QTJkQ(n7J6)?DH`@zw+ya#$J{<$Bfbjh#VT#~L%#T}5 z3~nA*Y1ekk_EDJL4bTK>M|yT~xV>f6KHGowsHTCT8E5_pZKF>ovs6;a(rWOfiPf!mw2@v-KfXHw&R4vK==YCCm zpYXAzS&x&SooGsbS45j=oEkNeFCf}ruR2?ravp3TQ!^Cu@dhHYTXnM~dSgl>#G0FC zoK=)`NfSu5%d*p`)8wL+qD*-=0%Bi{8xgnEPP=rvqOu-hP$6owRZ3%Z1hN$wWR}e) zgc6YiA>Vh9lRzB}YKRLW6eNk%Ew09}*4??PXS=C;wsGtIF!K#!DE$6V`X!p%%Lvrn zV=rUvYQ|drU`wlozSD0ZC$gO=j*D#44i>jkNyG6~o~o>g$WA>EY%NT{ufkfZwJ%IS zq96v?F?(s@I2l-_h!F$P7eCJ@A6T$|H3o!QeL5WkT3s#;WQXdfF+T~&BV$`KhaJRi zU?U)DH2m7aarkS%OR*UbJ9H7^AGPg#Ec4sp&tMJ-NVlo_95fZEfnj|Hvfoe|Ke#^k zH)XxwqgEFz5=qda&PCk=FaADNPd4 z@ehP7ekoOpsavRNxG%-Njp0zj*hv{H%bTe+#$kn{N9ngS`znRR#fSb1o5P3OttMmB zZH*pZy{Ob1He;<$)e{@hfIi`y-4Wn$ED#js@Sn@U#|Aw4bKQv-nhk2?yw*ervp#y# zzF!8h@i^_H#L~`n<;b%;T-xB&woCf9!P@1k=f?{j;wr%Inxepv&$h z<00h^*Ghxc5bfEC$McG1X+3wPZa`1rwi+`Dtm#UlX+g_&?;#+MBtlF&#ly3JYY6dK zEy$fn(lQsc4tt$MUzHsny|dF1c` zhuB(?92U!Ga4yTk9;PA2UULFVNR8mD-SAn4SGz$tf*X3qv60g9*l{i5+@QAE0D$Z(BJUd_E7w=jdsWt9-F)3ZN2T33+qzyyH2zRS^OnIM<21m% zZkzq1Vo}%#%P!hkrp-QkFq%H>L#2SxsE|$A^PY_fQB-aCl}Mm53mKi137h6v`kPPu zzRdb+TrG_#jy~qe&Ik(+ICZK+_@4A)ISND&mJ$7G5J8$dH;1^F^c2Q4Xwarym6_~g zB}a0XTspGe`c)r*f>dQPCJaYwjIuF)zqC$atZS3;vo5!)!2FNC&*UG1yNA>}(65Gx zm43R8y2oy><#(DqE!+AAmAk@ZsAW&&8FNE0VPXE7N6O5%fZU<0zV9 zz7iEXd?nlHi3bU%z&g{`2zm(sExndG5_)wYV({G*0Hobffa$2)h(BZ?ZKeKnFl>1C z!uV7m@ZqZkFyZSa6JARq-BTOtpz^?f5)?Z@cq5?^r4rT!d{PN5>_baX=TNpsZ8WqVLBt;{qAKXDAQJ(zXzo#7pdWdKYV_y(Kx68B3;6P$)^yNd^ z_Ol_-gz3S2WGhXltfvf=_oF6rxG1KssylF4(B=VCrzeeomlo?Q4ba)znL(8@M4l2gDC0 znB@|N!VAGm8#Cvlv<__>L9XT_5k8{V^d0$+5O3Y_-Y_=y7WiI=pf^`QP9ocR!D;5(2aU>3C zET)A&I4^fd5C3GY3BA-tf=N~803Gh|mkDrH=}!lsOZL2hUx7H)9dNUItFYZt+YHyr zKtVfL*La055OjEV+7ZLyXfY+xJt!}F;ny)O=g8^YfwKlo@&&A^Oym26EKG7(FR~~*l3(u^?k0K@0&1H%Q+J;?ibTpUNXFH zlm)s77T4lkpqyR}{N8TS=YM>zoH#8{p*>92@RJ5RtvQn~x>t65v0KltW3Xk`zC`c$ zTF(vC(>tS@qiUndqfERpNS4RKmUHr0iA41Rv7wNb+n(etDxOn>4E0j&CPIs!%`|Sy zsCUUr=sWozHQ_@>1c!Dv5dgH%1ku>Ze_%VDzLwX^zoDQJ4qzX-Za`apHqi?A&Yd+0 zACjzBJ-0<+t~v0r-cR*fNSz%lWo_tUaqb+bjprc_v)F}5Kx3>ci3#||92L0cb^2P*>!Xu{WQ2q_oZ_?K;eHperNx|?NL5CLc^&Dw>M0)^SzXU;ZLV#!4bGg z2sRstCHrxMJPMCTWq80yTw3U_ZW8W~83vOo{b#12ilLRrL z5Q7`ez_9jbQl~mv2ziK4d|0zMJar$13vwZ2y2MCoxJDsC{z9osA|c5LFh8Oe9e)sb zsx%x6e<3)7)HJ25YtNe*l}rJ3kuMurT>Beoid5wA_8uv@A8#?<_kDb%oqAnBIlM&Cw%YO`Py(*IB%cXj&bl^MvpwtiT{9! zM^c@NTjV)0Q5we=#vGc-TaGKc`kGjr;AE|eiHZ||)IMWgd26vD%qb~YFp9Au$nv&p z{a#qG6(g%_mLJ|J!V`i^K~gJ0EXvjpy62pf2ehUQ|Y>=U1pVaG+s)xkf+@E2%g9k2wuyq*DD{425}Hb?B?&9e)g`68$oa z#WJ6g?4sus^XW4E5q9SEoS`>q^I-h@j-k(=yi#31=N00`WIx-;A$*vT(2hS8=wRS8 z?jiYs20#{~`QbC-B^fU`Kt)BYnbGFq-X3jBpXLOTa{-aU{NyITQWieK()L0>gT3A; zPS!##*=wwZE$+8Tx*a=_?ChWHbak9;zkk9oAppI!xyboR`sk&a&z>bD zmr!8Wz>=6q*^UBBli-mQpRn+gRsc)EG56>rp64uJv>FJt%B_>InE+}DQae73=l7%6 zfBdqXlb$gl1IfjdlCuCz0>6(pg!&y?8_6u9hkDVFhkL`P|1JSllM9P6RT%Pd%9>W5 zJb->6WPhxNxlz_sU!mh^xESAlQ)9zs#EH3}i43x^u1Mr0Qh9A)O^3A@BL8M9z-1n_ zmaovZ&jU`Pn5W25bY# zdGl|pxH#-ClYvdq$ng^Gh8XtyW%H7Q)2FJ|T9Br76rS5i51zN>)4s3ux{EFVdI9Z% zzYhldhkMJ$_Aec2jCB7~_g3ov8TXcv?r+@N@6MzDP&cKFtW6wE@%|_7?ZkKJ1nSQf zmvH1p4goll3OIhlv;@{;-fwpR45=U{K#2S9HQL4%k_&*>-Q`IzJ2fREqc(Pr-+nFK zRI8ILFoippP{EYz9GGO(sgvVpp__$OLwNTMNk*wTRhFE;)Zsc`I?SBHma$TIAYl2Q zcQI@;IzlEy?QYJ-Jh8g6!8jq&_ghS}A3XnEy)FHxdb|G}fJEmv)u2?N7X+G9!?bPa z;Exth2|M?g1gf6s>L5FewXoo^I8scIO<-7TF_BJ38iv2aVK?T%ze+aT_g9nLkKZG| zCA5>^e!|+q$7Pdw5v?q!gNRg|ioL^)k;N*?G>LqR8jasyMM(LG19p8-9X`(z89uL? z(XH{8YBy>rLpe7Zx=|f$(n&XyiCW1#1KjaOZT7by(*9F3s%1O0FvHq{aZPlk^cYE zz5Vaz@o$qWjNc`G|Jl9$_G#Qv3F`DaSyUsEannsE7tF8kf-^`DY1|E|ye!_WP` z{a3=}AD8dP`?o@{e?Rcg!msZKg!rqR>z`8Z-^O14#=HH;Z2y`)RZ5 z@@dUs(u47Cf_3Y!Yg7PWfFBxv0SvtAhiJmRZo4we=MMvs(Lf#~RaR}!l% z9(8WdLTvz^v3eUS!?k~J47QS%0|eFqtX7s)ZdBhEv{&H(S7QT*ot>5A1#%~y3b+PP}q^u(fu(8zZ^x3)geHzs@}Z*NWS zX=^)ggu=5XtpDt`CeHk*87w>$f{o^4tC}{nQ0;$(nMjdrjs85hlv`whI_d*#Mqujx z0|N0A@DQ)>ho}j=!p()qUIXK9QlF*ZQVUpDOHZ{#0$Ve*;+DhqP;nnZdqhh- z`^Y3w2CoV4QD_TT3!CmFZ@KtV_{@4a^#aIm_uo?|n#$zk)g77KNtqhvnUBaWj-|;< z`QGb!9=z>#>3i#bp1gGBvtE4K*k+lQRL;DOK(dcrL9o$om-|T{?B%~KIWp<>fTcL?Z~;@%=2*M zIYnwpBd#XH@W(sM8`8Rvtfmk2fji$$5f7+SRDEBuz?X3Z@4;2z3V@c@>QFGhmr?X9uYs^Rf5hE8Yg#5}zH+>=Tk&Vj0?&bz&WwH-~R> z5MaVhI@Aq8=gV6DS`m%AZG3v4?A7lQ^qi%=K}HcZ3MKeDqIX zaJyGMc8h|oxV}RR_-SuL=3^_${*9l$v6bk**>H<;2|K!>+C zl|0C9tUVA$!iBW89lRy@bD7(m?y%@jk9tz>AEuotfNddmNavD{@p2Mx zgGD`Y`DEPC++VM4aRqQO4l@L=*xxC*M(>18An4DMe&1)&Y? zvXJ;6vcB$%PI(grA8_$@UTj}WP8Ek$zK$FlUs)}C4ZXJRc=9^)S+y>wIcc}+xaYX0 zg6_DcxL8NyToC#;YAzG48#!kPiCe9N@uzCGYg~sgqO3TawwHGdHI@+3IA`sIczRQ# z^xpQ!cq0#oe+RoF76u zi?<~wYNM^K)W(@@g>ZAqp8q=BsQHc;a4xS2fKT~o+MOldWF8h~7lokAHoK*o{5JI^ zMbuvC1FST+`cttuJ)0+_DKV9hbCW6DaFD6-T8O$*>dFAC+OMKJmK%~Ws57cFw%LDc zLV>8@Nld6LlZ+Kh;UF5Y3BI=;y`(4;do=C9=3gXWA|NIC6I=MvoHDhHTKLc$)a>gA zH;%(}`+WPzc*+FGq3fZ@oy;A%LvJ}W!ew}h6>vJ(@-81vTTO=pf5MjTOojA#RdmCS zFhk>Li}I*uX?X&31%uKOP3ehHJEQ$sBsQIeOMsS=GNEXYD8;@TAQ6Zuem6t=+>bf} z=?UqHrMHAYae&m1&q5Lqfr2C1S*4#J(n2IG7(y9`XE`HV^WoCc6Vvlj&m>v`wu*>M z@SWghKe?qp*3SkCACO z()*%=o@d06cGeJh+Rso^>0#N~G`=9m!O|rKC2VAqp;`!=M4FgXMph&9DAHk%<9Q02 zlUFO0PD#5PADcc4K&sI`r1LaS%z@bY_$eX3e%tX?C9!s2*hL#f!+Lg;Dr##+kOoRer1NTTpZp<7Mo zvSxVbLUOS*PAX3<969B&jh`(X~o>=lY8XgV_b(C4wP; zBG+lQQ^rfC&CHjpY(kSD7EM?tMGZyzW0AR8>`xMu_N6RGFW@L}0kGKb5^TydDynSw z-}0-~Ws(aM@sRz=>G+cv%i6*fwPj`I^Y|tD_Jfuw*4ZWU_WUUrs++Zqre+Et!ij3? zcSOJl9T@{UPcs0>12GB#EA(mYR8{2_gpbeA(=ej_smSmH7sCR1 z?i-~JLQ1;oa*FcADoTrsQZnSO2Qh>)nx^%m=LZiG zVfG`}{mNsL>;XgepbJ?dbD?e_>u!`Zk@Z#-0t-RDH~zN@4O z#LwI8`=bn6Qlk8ag`Nj~Rz@7FPdAikwrMVeDcj+`;mvc#kgnTpi*wc*@|Wh|U>)pt zyXKY0XM?kj(b?3M+y0eie3x!LV0D&V(wtSBOsetR8FaMSu%pQe*7kNEe2_35X15ms zqn{a_u%0U*VqYEpxxvAK1v$o{PJ7WX2ZfDl-~f z40OJBpvHRH!}?9N#D$OFacXSL?eUr`JeKBCkf=lI;JhfdmV44r-XgpoNbS&+T zG(AD;J2dxHZTniry2072)l&BXqm+y0{CH@WMpw|=II(IprHnJuMqotIg3^O}xGbmo z)NtHx>0*L9T$kg5N^32UjF2q~?>n-qOX)Q{RtSZM{`5L2>tkxBlNuN0Xfw02a&THc;5W1dKnS2ejmMm9k`+0!}Yos zxVY)K>bkMF2TN92^>KtF4kO{l?vB@IwSYpIlzMVdprUgr0tzLF%*T;&&orOlWQILs z^R_gSv7W47=5QKZZMu4?Az5yCGjWv#0w5G+N+MHuw|6w$;&WCwQ{g z*q^XGuP@+&#?nrIJU|{bP+RS|SCK|ZD!)EI=G_m8ACl0RQ6-Gl2u8Y#x%1`x9`*UH z%lr1c;^}g$=A-hhk&$p0cZioCS0knOIpx{LeaqvBwd;M`f7esvw$s~IvHOpzgtzGQ zF*H@5DOCAPzR5U7{T1Vm3ZjT~bGLs1$$ctapMN^IyYzD=xGsZ!5V$k!fjEO!0uia1 zA2_M9hr%TR@)b_#R^=|GQu~a5T=4awoWONg(J;VN6MAwrIHZ&*J$|xWn@~tv!N-n6 zP6f89B&fg_!7PA29JKEbW>z*uyCUGsU?0aI#^945#shCRR}UT}QYfNcp6O|`q!Df} zufNA3_lZ@blbB+Xg5`cXOF(wyiCQ!~GTpHyGOsbV-bX(qfz#n!Uu*`%L&8GGMvc4*&HM=*^D3CS1wdxFUvny$vMWW_Qed8hqa3SMy~9ALsyX96n9V~N1$?f-bf zeZM~I8eFtdv+^*;vi3YPF)TEGl{lKGoi!beT%hS9y}d1@&M82Tx_mK93QTMLeeteA zdC0Yaj*D73KS-rIWZioDxawX;fTgIWtBwdld_+dy-zCg0s$@U;lZRT`)(3#Sr9GjB z)FBc8lygtfL52p@p@tjPu;qAUK#1n?&_MRGYmEKWn4CRGB|c{+bVR8VJKm8A)qXPK z3G5h;C;w!(ITy^@2TqFmoE+vC)91{U4#&;R zn@y*T{E|oxtKVX8FGoo%$TmBN(S?cY`N0iR6gSs~&^165HP@ia{k7@g%{iU|SkwjA zx;8Ci+rkQg-yL*jdXTRSP@{+@T=~5@$A|00JT(C1r zMCx#)%XMZ`(9?D)sbRFz=Nay-6zH=1g=XdQHDYqLXSg$n{{C2lDqA4GF@& z_6{o$7rP!c@RAiv9Xto#4aQJ)El|dVrn?ko0PH*jtxB-g?`v;N((OqEW*OV5}3>Zscfp-x|C?KSjNt3adgBi2e1+o zQZpVFiuyaIF?Kt87euijl&v^e{*;PZCd>Y7;#L+17qMcqzNt(y?EFx94EC$M>JNK; zV~tv8*%iuX#q;1v6WZ`nQ_Sq#!th+Lc}e<$xzC=X+vfNRd?~?CaPG&`n|K`ed$O~e zKRBBAXmfc%p?chz*g~h%hHW@UCHYqnEX+Mn&oj@x_uxM`B{6KyPEU6`qIh1VX;}}} zDMwEKa0_y<<6)vm>;(yGq^^|ETi6SwWnhgApE~L^$PFt&1UzDeA7qn)A+j&0`Ej$8 zKcOL+5SN6|=GU(?Ni9IYnCRsjuoSVukP_w5q|C2?n#UM9|2!$? z53F%;nmnOP?t=$v@e5=SX&nrAimK~opK6(J8EKhw?Ji;F^aI9m!g0>D&$Kfyq9G0z zpH*Y5!BM$sSJY6`y^I$*Omfc%m@JM)WNKbr~5BW#WWO61S5Vjl1O-bej#m)m%Vj`I-QwTPl|kVLFkl zodoFU3>Iv&4LqNZH(tnWAkW9{NZja2zdS;gtTP0)nqCZ(e0Hm^K65*anmsMGMO|ug z0eqbJP#xCtFU&wIm&HJC7Rx66`M>}cs%m|2D6$22KJJJGfFdNJ9ytMC3L-UJ$p(L% z1`KK+SHhMI-pe(YElR0t)$#sL;aB~=Hnh7vb@k^w7w)c?H0kvtb>*i1p}ncG%4}xk z8J=~)g-o0z@gww5tY6F=lcv+vSQjuRBGjfOUe#SD{Yl}=M*V&w>sI|0B9<%$9q_1V z@(czw@Es!KbOdNOl7x>KG0*Ow;NB&pS%yNB!MIOh7ji2aonh312kG|(qZ#vi*%UQ8 zM!WOmU}AnnQVc{U1R2SQr3$qinr-lgB2ZvMr*TImCv9{zfqL~1bexVPsx}3 zAdRwiagNir>++^_2B>pqHrg?-otQZUvA8Jd3J{`yxM1?BD~T| z6(YQ}g!fsMZ}k(}N20>s*`}hbc{A_!(0^Y8^jY(gkY1NTrQ>$p?~QoPFg;@MqLdM( zHt(lPYDIbzG=Vr z(4%Y{rmu5(X?%a$GG?SzUdoO#2E>9=@h?a?Zx zvvExvY&pBjQcOV2Z_Q-Er^+Nwn)ReK^CWEt?fc=hQE|ts*S_yJoWISB(&(*A4$8P! z!K_reh;f&0ubQeULbd7*rl%6@?(4c0)+Yz7keN^&GXIu4Pw7O7K&pB--e&9Zgn~i0 zC0wJD2bn$(%#RJW1@M(=SD!0@OTzWjJ|9cG zM18@?BPYRtPZY3`T(s)en`K$n^a1H!0SxW88<1#8xQSZg(rHAy-YUg5#}CN>5P^lPeG1$q06N6l(h?WTmFCt4(s+H z*Nd_hI7VKk`Jw6Wlq0B@1`-a@%*Q9*ZSbQ5uvDO#`$R{o7Y}o~V2k7VN#D=MC&0sc!eg zjdqm*MygKKRqxt$iVJz%RQ>?!4v?qr;|Jg;WSnfj%il7?f8d0_x`tWkng5j;(*I9I zb+P|d0g#v~Ttgcr11-iOQ*3}-S%?!ZB-U`;3XsIW29o3zRz(J9ks){7Dz#N8+ zWFz_a!>Pg*YDp8coZB03+m!)3g$Kkt*Y{4@8x*#aT8Y#^!-Og7>q?El?}{VfJ$_*V@0??~@I1opo$ z;6L%*Kak)zBK#*B{0AQVi{^db=bPR^{Oj@m#ok+hN0Dq{!^j4A7F$@z2Di!d_~0a1 z65Jg^j6fg|!GgQHyUXJ4!QI_mg1hTiUDZ8PQ&m&lGi3R`|9|d%p1p)nQ}gzzV|C73 z)v&t<{!PB7*Vg)DwfCUu5U1J&`W4E0fpw}~P;shVYcLR}+MCzE@ZnR-gqha2y~_vo zofeoR-+;a&+b*>h&bIgPADKfuV`|smP&Z~x&xyD7`>PjQ*Rw#&jYC3v)UJQLM30;P z+s}OMz50^p^0==J2VZJGW`^!Z_~(FBJ4RjJy&|sIzg_cLz9jvpe(AgE*1uTU`R0cy-&&;}ciwYV z@0#zg$2Dx2=UmqL_fw7AbaH3hm6;>Tm;agepTWll?r-vMXrubYj#SC})oWI|)jk8a z4ZAtuxh0}-gB$hM$2>hZeO{6Fo}1?Eh`Y05_>gN)3RY`1{CVjY%NjSWQNCuML9bf9 z?xI<`C9KQd?^lYanpeKc>{%Dm4lv&7vd$yCkw=gBcduM1n=*1%-)*gD!RJk zx~};HnQcANzQ8SHtS{HWV#*bnDSW z6IU<4+aT$M{Qs6;K4f__ga5?J`nb8{<~}cQtw8^j&&Qd+C!OY-D>#e)5#7X2!np45 z>)o9^zw^NSHP=;tQ)g+W9cP73mI}8got%?8u!?`f)RPM?EAh|EI$! z`(y6p?O6*Py880fFIW7%wPmv%sFn5B;ryr9&N|Vie9wS2E&BE>{QB?RUo-4TsN6l@ z!P0+6mcQAc+o{SwQuKUT;mXEOs}A+fFtC9ozVDn8qY7tzwX^2T8o}j?{n2DzjWZ($ zPC9x&%_9GmuuFI(V(ry7ZJu|2ojSvk;%8<}{5IpqkDm$S$JZU%vGx5_`Sw2=y}Lq| z!)J?B2k+0xHz-^;GToTH)ywBiJ*;5oI#UbooxXF{@%;KD5$mSbAN$w99OFF}rn=+t z$M+(42h>?HA$YuDa(dO$0ULW=Rz|Z6_ zyX`-IpxN9KRaf~hY%qLgwp1s4UbI`6?rWOn4Ti>!?>F@C0#By)D;4S)Q#`67&yJjNC3ke(Z&+BQ($$y8j#X+KRewyGwS9{DWUalo#KejP!U}{PPQGQ`@huMr zCdB5@kDq$D*VIYzIhtOYJum!cq;G-oOR5#OR?aZ4#iemij`;V?yCuWNG~H*F@%FiP zWbv)cas8i-@u?A9y!)&2Z%!0$U;AXsJU_2ytME+sroiayRc0R;G&5DpJnvT(d0(P? z!sg0*a=gmWePWp*Gsld-_v!j*Yq&@3urmHN3Z-3o>QRGW))h2+_Fgi zbD48*UA$m*_M4s?`WePA5vD&`HLt+(y*~>)SzfjF+w?`k(thcaxkueN<3Bxzr3?B| z<@~%cM~ZJq9$UWJ=;19IzW*@k;hDF~g8sblHm51?o-N%6*5BP{-s~BVn)N<3{pY0| z+lMdnD)eweCf_?_d^Rrq+T`8o5p73(jO?7*`YBZfQ_;b~nU9TcYk%bFls5kFPkAfv z&}MmUzLWd$z~n_AW*+h>BE{fq8K0N!v@~V4j718yNScuC)7sW^@6~-#;ZXUgl%+Kf zd*sn?Yrnl{hmQ+i&+Cj0AM&Ar;{QMZ2nP`62;bzrEFPm+7 zIjPs3UdKm2_Z!{+`I=5?cdYZ+bpK%KypiilZKyGN@zupAYTPO0cQF6;{Pl0-uf20? zNBxB@xvrYKMl3E=)^Km$pp0jpM(-Q_Fm_Fs6Sb?>IGvE{Zl&y+oX;DNo$+9(N3+f& zFEuRj{_KGgHOuzhkgMCRX{FECX*fNh$brqmx!NTvzST~Pd40pM^wF7j&)?m6_kF{{ z?b_oFve#`Mc(KOT=7Dc^U;mi4wa=<^hrs_rC zbbE7c;hiI&cWjtDa!8MITdQ@88nmcPvr`l9o?SOSd*317pPD*&1>Jl5`S|=SJ2ZHBYX92L3f{j-dyAGcB47$+m^MPJH-p;F?o%;uZuJ9TR%8- z;ga-ze_K2YT93?n6m_{exW#ut!(#V!MT-Zv-O%aZ2}3Gms8zW1`>oktN| zy|Uf6vFG!2?bx?#LreFbLeUpH%4B}9>h`Rph3E8NJ-qj$B&YsKn3>zMHC5qDhf`Sw z|Gnd9zV0&RSos36$yRqBlfy4~W|8q>hhEpMvoL!0+)`H_Mfi1y?6$N<)zB1W{`Kz~ za$r`$GA;V`%Kd2O;K3=o?;Mive%@0VoBo)TKCIk~TCrnJ-6`Au{I?^$?%i*dJ4-je zbtj)kd>VH<=6a6!U3u$eKCtGpAxs#>O*5S^! zX&slGym5MdU1R-(xm*1zrXJol?XE3nP9~JQpAg%xSkA{|YL?B>Xwcd*-VM)R(^D{ial%<*6sVow=ic$EbljzjWQbCG$4Jqy2*)E;;*OhWVKd>ZW=+mRq&SlD(uTI6W4>TpL6?fcOH94^6 z*UaA85BzwYY<)$o~YQiW-f zy;!^8^^t%HA>E#yzU|d8)%CRtW)*B*c<1${Das$7rjP46u~yqwh3k6_Jhx>>%kUMa zD_QTod8XM_JSx54qOs3LW*AvExPP6uJ^ZFF9_zX9Q=Tb>=l{{{>cF{OCvOSc{{2O< z!l$xLOckB=c>bo=cInPMtkbH-)>RiyHEvtzu1{2?ueQ*fr)xi&HGP&}e$oEnjS90* z2XrwVzW%v#`u?4|*7vMEt&e?Y4P??hQQ9+*mi$ z-PyIT+}0G!RWd55T=(7a&2t{B@aCUBM=$@gZ0gm6o3_l0Uo&XHm-%PMXDwC6dZ|`X zYxSf?jrQWL@dG!E&sx*Rk~tuC8`C1X43YMxwH#26Bp zG-+&3!Tav#rWx)JO>@3=g@Aif)^L5iUuJAe3_H~(uZ$Iy7xijgy_3vBm?Ams4qw*1bFSYqF zI%(wM3p4N53_kH?$d&K9gZ0W5E@};rdB5}B)C1`|9Lb$fWOlL-Hjjdsm%U|Z&N>cvUC2ngC$l^t8&7;Vbdo6nJF`TyLw>cjyKt# zY%m!{4GSxjrMREhgX^hscYYM2vyNL*;At*>$fg{hPL!I|Fmy?lwBIHUs+F`=8pGlW zv1u%IJ!d9sQS|ieWDg!Zn4j!Szg~HF_M5w?$BBafCMnhEZj#LhR&-6fIAE^VCjIGs zr@Q^7ol)ZE@jKOfwtc<2Ucu_c<1*z;Jz;J1g=>+sn#R9a6%zkt<@ z#^XcN)+0PFevBHO!&)PI>yp}+h11U5y!%l0ZpV@xsuaI5`_jx!z6J!x70jFUfN|K} zhfP|yTWtycbTrwV;l5s{^w}@B?sp)2-@ca6@k`Iwm~vp=%}gQZhuyp1%+t5Pf^v;2 zoz+wcH2&**Zi-sC=zY0iZa<;!+kGUe&c2R#NDugxf$V(_cn)9*x`OKzyJeNX!h zjjQI|S>?{2h|jm{k1z9}Qq@NT_8nT}_vP*C+5XWx4%I9Bb?~#NM_O0;uq7g88 z+O)h`?NFN%wbJ%Eb-9yA?rQ0_jyYz0(j+_`xL~|~t!qI|CZ84-?p{4QY1XV+^IvLL z(xdmvnl*ELdN4Ud=?&Kcr~Lib^V(rU+Liv}S?$(C(v>c;&E)aVrUQpJEjl%|W!^#B z!zBYMp{{$vWohfaxYLB{KxcKT@y&;?0)GRh2L%S>uM&;VK=W*&2)%wnS zXWsXb9i&dwZ9_UEPj{EGYQ0&CRRF8!Q{OJ6Yvvx%5FB zi)`@d?eULc#9Pz4rrOdg9$)mknCoKGr=jiFH~4qsmrP}XdmXqM{(O^v)e{wl4nN(t z?XOV^7WdVgN^ z4H-3kx=uE?zx?Xvj$9dA>W{bY_2$Thr*DU(@0>95^U!j=W0UpjdE(9D3;(8HcfEA+ zi*qBF%#XZxW^j3zg~WMVkS20G@#^aEq9%`?=QF>*!ycJcH5D3K5&~`xIN!q~W{@Q;L+xPcwejmMNNb0ryqSs{EykrO_Kum51ZBW%k2rdpKiYBopqD;XNjsS+k7hb(C2WU0KW#|kG`J% z?0xHz<>!&dUFTa%3_WwdP_Fxd9si9!?YZpyiZhGPY`B=`Sa60>o3nknSF+~yt0}U5 zSyZ>|>+lN8n=Az{!K||2Qt`Ckdi?EI>{Zr)xI)j44m?9K)Ns;3I}saPm@=;1fH zqnk#qx#Lq~%zfjWgl+o^?VnXZ@R+hPqnBnv2JKwm6FmY?=0Coo*_sh4QUs6gH>2Xe z^G;`96MgDtbouY=dpus}adgVr*3YMvPoFp2)4%$(56N}xVIJ>U8&CWUeLnqquEoil z&2Aqutxo$KwKNxe&mNgvyJ^LF8=9I|fBCUBdPhTai0|>r5-|@7+{!;FkNEB^3*GJz93a zz44uUmzptq>WS(Lb9QYmU>%A;kpN0N8R*!*>c&6l^1nRg`lxTIyglHcohEU)ES@xQ*_ ze9$k@D|O0~@m^=wBxu%#jVe-VS0m#hONHQ1JLk8|{BMgtuii=eZ;NMF9`)-xH}jH# z=`t>i>)UPrn*vcMwlr+?E%eTr)utxzn#KA>J_`%|@+GR_%T1o$T3%ndrqG$H4Pzts zw99sBTfr|`&SWlCV{yZjm+}REOkTO(yTc{BuD+b7>aFuT+P=IuWJ$wY+Xt8WbiQuy zMyCpGdvw0FU%L+rTR%R&{YC0Li*OiZ(x?=YC=}G4-zqPRG zsvYn5#(P~anWod`P63-T6@1YvxphLn27wKN_uVx;ni3EmeR@p6mwi_BNau6qT-tku z4sQAR{#2iq@k#Et$}u|f>i6UM$2@P>aemA6#vyt8AJC?K(f{+js=mzvi{$i~;Bn9CB*H>N#bLpU<82{N=uX zrA;_dw0YD16^~x6KlOFGn5f}L7sYyfZ@SVqAiV9$$p=@Keq;^^?|wRRQvdo>M{NFf z&}-(qrkl)RPlrxje`Z^X1&zB8&2`Ol=cdYIk{-KOb4OJ34&r9eTUH(Yw$4 zV_RQpcCHON@uXz4CodY8A6F_zk1`v2-s@62W6j!=Zzew;me9i|F5~k)yB-YdP_<)= zQE%guhh-U{&s8n7^Od?6^M=*jKdeBZr*|87Iej~LXU)~iUq9_|@IuztNwaS3aoX=) zw=A745fyj&&TJ6uzq8qze@Yb`W7+w0O|~Y@@7BEEZ$!*KMU022HpD1gcWy6~Pv-)-M#s{nP zj9+?ZP)U91x|fR=S>G%yNu$Xl>U{sa=I5f-6)!AU_)&XfUeLV3wa-1xdHcYcj>~ID zw^{Y{V2kU1XRGNOJzjV8L59J3haT*FI@jaQO_%RoDBP(Tb~e0>NAFRCs&!r+)HkHU zkxZp$x1W{kRqqt9a-A-+w?OhVrSx;lCNC7SdsVun$NIiYcRFf((jwR_~Eg+>*ZIOTOR*jsz>7uSu!Ob;ot7F_RiA4 z$_1AeeR=2Wk8W>feeD1BY>`}V%fuCktWjs*(KvIC)hE){xYjtwj8>@@WN3IId9KTA zOI$j&yjt7w8E*A?f8unFcg@<6jn3icl8D{q!tY5pU zXLPw8(Um*hC||Sdx1e4p1~e;|@q5$l9rq>Q`1E>dQ-{kb2IOr&F#YPE56+o$x6}zA zE{62!_H|U)l#NYu9ZT(hW9Pg0kc|O7R;_BX`E$S1TWk2_T{%27-<|8=En9cruka5) z@b<5O?GxUuU3|GuA2Uv^)nG=0i9Yl9rr*=OgYT=BU;Cf&9`Yf@&Z6szXIg!( zNR7-D1nt7_wXDLFa-Z)MTk~k({NzolZhD@(hvn0kONTbEXtniw#^R04d*Wuz%XEC; zku^!4__nfS7#N?bMTr@mKc8E9s?4T#wGRC=^6Az#ldt$>Ih^xkuBwKn)2ugF6dbv) z=!kPU(;c0A%hMxE)w}J!<@_2SA2e)ik=3O$#;reCt>fW=ISrqprzP~rckM=V^RnJe zUUiPmU1)c$_>+-eJR8rc5PMN$eRKHx$4j+VE{khDuJ^5LFK15O5%=-O<68IAv}*BJ zZT(-3W{!`z`|#-fxnqmxoL}ERd~luRdD_jHo+YpTe4uebgQ~lmZY6tuJwM>o zfpI!Ry)oxTb-b<_I~=@`D{8LZXUwV&9R{t>H>l9=Ik$(z`D`4oZ&bWUY>}M_sb}uX zFmph|=7aK{+<&-;|FZGgbSKv7JWsc;b0_VnOEX@K>Rlvz^~Yxe){WoUV@mgw$J6gj zk#$bL@MhD_w0iOL%kU1%JC$0Nep2nyXH0*MKm2%MrDbz-88WQAQ1scBk6(LU>V9yk zzV-M&>n^@9%ja?XG4V04?$66n^Y1bj{HN{q2&>t3U|ru^gX7aK>J^jXX7Z}{-}(OR zyJSw|x_d^M4nIhfW%nk0iXO$d)t4Qeq&sIlksw^9|dcc&6@BX}X=Ul4Ub>@YYxm3ni@9z=#XaBHN9d@13H%{(hwocl}vN*6swX8!s8Zp&3;v;X*5C{vsG zBK=;S?$<80rNG=tyASkU*dj~YiMtD=9(X|Ww^#g_&tINw{aHTr z)0Z@U_2cI?h))E8Ggc_#}&Ai+%f5X7+`H$@{yuVGZJh=mA zRKNJwu3_2ItlC<4O|^fDw8+0`{)6D|Kl{Jg7MXYNn|(vZrvF;(=*{8BI>bzCv0?1H zX`fD?y0x?Wk?tkFey#eU-1pN1CzUHVtIPWpYrU7b{3Z=Jh!CzpBh@!M5$1?vj#FA{Y3S&Cs>Hk53)erDUB8K;;Zs*+=ePUEIG>t1X*0Ez6kdbF1N@K5N*l1Ka6%D>&pE z(lJzaEi(cuK4CC?B@%;;wT#?16UHCZ5v#LRH zN&KnP@ek8>o;oGrY?tUyPX;gU-(Ps`E5& z+?7@fW(4=C(fPxrZki)M{+jjRNgtokKBIn&|DNr_svnOQH|+Cl_2pLcmNfm@XY`IQ z_xdz^wtDE5@>kA1ShacRrIFE}^yTJnEuBzx$lyt#DV8i+|Elzs z94F2V{!C;09_jln7AyRBCZC3JTplVrX#m)9`6_~^0L&a6l^3r8&`Ljpmsp@zfP*9S z22Y(}m40soW6aXefd3;{q@TfSwe^DZv&B=V)xn?BcvrE4znH|e0uzbn{RQ}9aq%yL z_!0SEz;_GiGvHsqm+Rz-0ZT}ym1hR@Ae~Nz0)8o25f<=0z|_Hq;IK6o5fjog;TmZ8)OfRkg!UMiYe36JC3rG(z=;SBT12Jf20f8?B0+MGii(e!` z(8Gn0|3zE~8Jt0s0{CjMD&UKNIKcW!A2Nt5A>%fPVvvPzFo?sX=?vmZ*ghj_g8W1V zAO)?gMg|cAlB4)V0D~SHE?9q~h(TUU@P*)eBnU9n%wOrkQ#8e|e7$O1Bn`Y5l2SyTr3S;4~q>ylub0R*j08O#8K*7i>TgI-cd zz;Lt2XrQ*Xn_sF#c8n3l4c|)S-MChpb|FDAWy*N_bpZB-ZpQ$h^{yQi8B{MMrkQu` zN>#k;hjfbp0|LWBe9<3%=ntP3X|V4R0g6chn5=&HP9fg4!Fce0Q6b<9fj2?;>xM)} z#zqB&M2i^ET^uz+g2MxSBD*ycBe2z^)o8U=gTZ1EfLF748V%YO-t`09M0?ju<6SQ% zDmEwv%^B^fP2=qgo(9YhW)qbb0|RX4H=_|SG{8n3;Q-D1&2V%W9H8osa0Hw@`Fj>i zkKqvzO%q!#!Hf}&e-9u)bhT-ZWcnB&9q@&}dBuzvASp26fULh=qfIyL?=*1Jc>@5HKIx6W z9~17r1wGoE;cKV=zp!?=0~hr4+8Lneey={^E?h9^;1#8~oD6US{(CXuPFyg6@*Sb$ zM%xL`-?pvQ;cgt%TqwIUf=bBm_5}`p+z6^Kq?}+OyLe%wTDQv~zx&kOVpe%wsw1?Jx$gY^!6+)T${GjtEXRiE?@e%x#( zcXWc~_s2W&reJ%nVxi-m72ZDmd*$Td$F1n))>v!-#YQY0_l=ANHA%e*boZj7VKo%y z%HGuj;or~>3@>ehk7H>C`ncA+et1koNSP{?t5gA*wH_q5zTiJA_|E|T1Lvecy&F{1 zK{2&bNOVjyRR3(;q$zNnps48y%9>JfR03DmyC$f!igi&ktC4;sNz|pU*U4WG3!-(s zx!+$qDx{0}{YV-z8Zst=0a#tbB{X^&4XlT%M1yMYG8)nX02<7rx`f6cqe10Rm2jXq zY)eHtcmN068@mL@XoCZbp(^1(dB|`;56Lku>-6wf?r4rlhJ#9>D&atR*x-=c7yw87 zl{;|EHaM^lstyj4hYb#yApkg7Cgsv%S!6h}wWu*Hsi7mOGSwsiaIj6DOK_|!L6TqQ-rdp>59G_N(gvy{QA;HAb1_?SM2R9CSobKrlC_dPIu>n>cR6>Gs zu|cAJGwj*9qtYTIn}0@?P?eAz95k#l;~R6ZaPB&25t7YGqf)3!NDf|_syqWo?s{nv zlFd`2TBu4$4xXAy#Q`LDJ+%nQ=B-gNR3#(_Zw*si$A!bQUY8tz2+8KLQ8iR0BnOX8 zCA~U5{FS@vZS&fw9I6fyl8bC{fO$a1H25obA=x}P5E-%~HLe>8#)gU9{{k_&>$=62 zv$=0jjzlV=3X_BX20RQ(PVV||kh$9Zw^R>Rxpoc?9MC~1Ozt{x5tBWcM)gpYm>fJf zB40QJ75FQ6^hv~I^WaiFR3#<{7Y@iVl$_jk;UXrR3kT&$7j)dghXaldg~?qXE@HCz za8Qm!N}@`~9h^8|3Q?Hcb>bo>n-d4+Nc8ZkFgbW}Kv*I%!C$$nPc|TZ& za#v1vKW>zWVQTaVNya88+K(IE_2Zx@f%$P%4^=5A2S1LSh@(Ea>&Hb*c0Z2lp(-&s z_;IT41lCTp7kAc9#ANs5s2-{klY<{eWerC;x$DP8Om;tx>Y*wzIrwpk`~~FXt{)dM z+5I@GhpNQn;Kw1yg75|I`f(AH-H)Sss7g!@ew^Z605YcN+TGPByB|mOP?ebI2nV^f z5(-F-s}T|t7#tWA=v^Kzm8wW%z~9PxwT+4CRLa}C zYuB!tu6j*mRG2s5Bp6XLx_if%z-}cwM&}o&Ky!fVXi!vmrPu-2rSztf z;QzJSGTtIgFamuV{!RWk{3hGfBBB_vN{oF`#fTWkFA|@#R77CMu(Dp=N(P67f{Pzw zyvo(?6w|G0NJsyAo}ell4W1M}4C|}q3E@}UzfJt^AfN>?kx|&U%Q&T{1`lETBG%mU za5y(Un>c&0ec`Zgkm1$ytlQK6Z$;b|-GB7K z|EBv`kn{ojhXExP~cga2ER|46WIPy4?Waa(l%(FgyxBL9(K-JTXJ&4P3KWxcwJ zg;&OnCRpbO)_&0m_{yO8>0J-dSHohXLcn2tK-vUX6M!}(_&LlIvhLD&SM&r&7HR=f zYj;pj^{yQe7z8V(-aZkLLG9Cc`-n&G4F-)4UcVroy4QPJ1*66YSYLp=sxe#5@OZy> z063WMaL^y?{QMV~OaPPB6Er_H0w5BbjT(bi=V`E*HQ+J{8Iji)03<>Z?G~$IV1SY*BCalFmW2!Cc5$uTIN)*t{ z;5Y}?I-=*O6yC*|HU>0aTw77-!vGa|Gu@E4I4 zw{XrkJs*@7WU3=r=0G<@MP!JdmV&bngYm8QKd7%~Lk@k>C7&oNzI(3C`6%h*= z`Un{hm9D`tPNz{2ALLt+;!^fq@?~>idt}E&q((Rmn3lQ%E|&9x6k4YbnuyB?q9eGt z&$f!-7C3zSM&xcdJC%M+w2CZSB=RH3#dZ2{d}$$OD6xt-8`KFd#NH&di^#ZeqANX( z0xp&@;_OR$LMSi9BPDQ=ITy|>rRPxq22*lUVBDk`0fXj30wcUVb%a z6l2X{LI_McV=I;`5NVPT9dY`dI)T}zLKjk!xWs(SsmdZ;_A-inGD2Rk)Z%m+)NqMe?%yRXZ*s zRfYF6xQPowAhnCSV$H!C6G6Mc{iqsnWv8c3QZI1bjK*TIkm^N5TX1!$lDN?2Y|wkz z#YIFjIL2LZv5b+riPAw_a2d_e8xy#Q@Ce7ai;I=yP?t|IaM?F#c6kxuk-B5YVH&>L zqw8@9bA)$NIDODWP%oI13gFsXNxh6FokqMdgz#QuxP^DbImLz6k!6l#8G*X%&IN&? zd+G>*!Ha76z|i3lLcxFt94Y|RjXSOYVtXR0&mK}${t9}bj^!0qpj z#Z=;$}Gkf8kRIL|MbB)aO7(0ugf1^!Bc&J@F2fC9S%G&IWTO#HQ!UI0OX`>*X% zUQN9LG*Hd9)dcS?&{(zhtr6k*1+B7O3ZNoGyI4?sCxph$gO!BFGEMYS3a9G;ZBsyZ zACSsM_6I@|MEQ7Lr3g}ufQf7q5Mx)%8 z87pd{*L^sh5-lj!C%4BHpqkJdud(|vm!zPnnpF+k16-`}m4~Na~;u8|0bEB^-y*CW#n}FV3LZRbo zos%rmhS7ye9yr3Su>uUTcQb&B9=MKKHV0Bo_+43PP3;P2Lqrwx1pFREbv0RRGU-)L zl+?H>jf4(vb4ZONqcVOmp&RJnmWV`$U!X_NDWQX2n?y%Odi-WUdL|`%KzG?u=-eV+ z$cpTW#%aO+1vp1pQAQNdj10_hlv8J^C^hKg zDXDSYri2crR^*zJ!5+UNkA6-?bkOD~bleO{LT4}aVX`BmJ)fqahz{Bug^p{jiRj~W zMx90vs^K<`1Ef@wS&%i3toPBk_A>$(x&@A0RI=nJxZdE$76AdF;;IVnf$dFYjut@{ z{?IVDO@hfd5co}bq;!y$ATe$121_!N1U`E~N$U`UfYdrZ^&k-=&w}!MH~m1?kfHYzD<;Wo~Tv z&3Xh6kp(DtWSrpB8kFE6W&x>nd`d!MYMsHTv0%F!!D`Te{M_d2$grb=lOSNE1YA$Z z7~wiC0iO*5p?n>F5fdF1rDbL2n`C)VaGjjs0FPOM!0$hD3Xhp^l2ttcZnd47zoOPL zGfwIaR1SeG5w#8+WH4fz2oSh5;ObCW>B!0>dULANXGN=u@j9?|0AJ$h>RhLGV6#cD za3zfuQY)0;F}H?*Z%e@AmNb-BmC5lC_&f+ndd!@YDj9;8jdCh3itw08C!g(+h)Soo zLJo=?U%|N^qkS6zHmbx*aI;NGYHU*_1BB~@4+ouNnyL!Uhk#zVLB=n@NG6_?TYywr zR3`I7;FBYe^w8U5DR^X<;Il{+;X%72Etb!bkl@*ibl7rG^*%nEL=hgeI|?3mMI?Aw z?u4%GXH~-Z90?{m`(y%SAW$)u-~LDF5dQ$JEjA&cd3>gX5<0|HAZ5oVIXH7CGDcVG z65zCxSm!r@dcOheJOG92vq?a2^DV^nhsz8NpmLPzQSOa+?)DNNI3)SrHIY>A-!5 zituz=>5#oxsl(tA`y4dPeR!;_Nj&Jm$^H{X>m)}9ZVHBK2Xl3iaY?8h5p;Cad*o=f z(?Q~dR71oBM??rT%zeSJ%oVWSd4hv#rvp#Pa3&-#4h?f39yC3|6NA&ND&R5GO;C$< z*h}ajQNew9(DX=neC9x+s2y^4g2ALQ0Cz1rJJ8nz;nqg1A{lZzKK}qMD`GB?5e%Gy zc9a_&=C<4*5XzgVU?S+yrTQd1vKEGhxet%E$UUYb9fZ8%z#dY)otAAG4R>G4oEc9Hw)F%7MdU=AA02 z96CtSpz;%vM~8+vT?i$UWpX*hlX<@>v6Hk;c6Ef-k;OIerZgTfw5&Lp!@dwCn-07l zlQ)&3+?dA7zePcU$J`_WG>!>?8*~o)!-8~~-; zPke#{%P_FXbm)2mGK%rJ2~MJzZCY1~uz-$e0Uac{sbdgK;`u449dr>^ym5}BCz90c zDrZAP6=8L*a}u!05DqH#tKcN)FirwCA!!e;QxdSrbWBPDIAx+NJM=;+Qsc!ot5d;8(4lu+kwPQ0CfDf*4ok|+npDsc zz#VU*`ytU0C0I1dZ5M~$6h$s6zg3Tn6}``iL`Uwf`Md@tg+t5&3Y|MSlWj>{aB(0B zg6H>SjnjbxLOS~a1hNFHQ^7{iVRv@gAc5Wx85&%_ZvvYQ0im3n3O0fc(hMk_BTKMo znA=JRfza?ob$2=^QaZ?3py81@6Q9_isCCSo3BF|8DZE5I5kHou(^)hotQJN_ z92I;79mYq%^n_$1qG3*-QAuu0K7vjKA3=xl5pZ~wf9gdEvaeN|yv*0REt5)MccsOZ_FzpoJG}~OgC4tF0kc-3 z!huG)FEv&IrssG4krG4p1EqRIg+t7lmA0*V_Dp2`01b2dK@s1<6EJpI6e7UmQGpZN z=-ADcEKcYlb-{g{tc{Ky@(rBMnTTp^0x$N!v{*2UZ&0w6t--%I^f&Z;GJ>Qi&@f4F zN1ln`5X{?^#`$sqt0fAZ((jW5)6G|(BxC#_J6?TRQ2oyZhg~DM@S40sW)6V!85=hds zuQK30qrgVxi+Xc&E2yVW?PU#Vr%kN-IQnR~u8zMSiRMA$~LuLR9i`Y!0H{tUK6y-1J zuAbRm*Q;o+>(RRsNHrlnm713)uxBEDiRFrEh|)YY?^3|-IlyKneG0$z zue2`mn+zNy2mcifiD(x(gQHylAB5WlnK<$} z)dTc=jzS{pu>h_)!Tq~V@1Vz2=BR?6pvUM5xZFs;%x3_gC2%qo={sGgI$*#+nO96K zfX7431u_%hvkD}f%$P@ptsXSYsY_N88Ax2TXoz5w(2-D6;n!Mh1q5<}>di2T)((|cknCr2IxgHV|Kt4|I&|L4AW0UD2 z4$4WX=$GrUemOQFsm-pp%dyGy5C@Hpn^QUSQL+SG*r9>OjK%_Xi3agCOu%u&ioX5I zDAL?ibj$Tvw;X>^!UwqCEXOBSE%CX7x%vQOc-$zEy1h&(q9kZ-!0nNw_B){p-banR05Dsc(`E6tg9k$1? zPe^7y{MNf7I;JD$cabG@m?O5MBX@fI&buNyX1>GkM@#6iN|_xUnW69-?~3S{R>AKj zCyJ*7uk~*r$*irO{0-q6Qp(?cMXLs8Mt7vFFp^Oms*seXg& z9er#zJ$i8hvAE#P+RCXNdLsh0xMZ;4lLHhM7XcxqC%)8PDf0wx^kzzrh!$v=Q!7)1 z$7&H8RP^J;7l*PImk1YVnEUWpreWY;HIOJS&wfA?%k97cHjNc?ctr34_i(FNRc06? zsgMSE6@mL=W5qFpiblKvYs6zlM(&T%F!$lH0+~TYC*FYd&vAIfjsgvH8=l-hcgQ{s z{9ZQ<7Y5XDCp`*i4-!*xtzXGxArQ)=@Z07RJZ6GNG9nFpf`B4CR)%L#(LDz{CzeMc zx7ui!`-{so4HeyU1J*sqb&m)aXqfx(n5Mz+$R~=!lX5)qa4_(Aa*9WIJdzV>P|<=n zU@drDZe*Zvy#5K0~o z@eoMq@i_tr9st6~p~!>j1~|;Ao+!d&rg$o-0tURnZ{M7e2h-6o_u(;9Jbn{hk{;7E z$b;zyK9xaHdQ8*cH@Xo#)FOwvXMj8fR%zD2Z|JkoVUrmk4$7~nXm=a1b~iR5X&A1z zyRpf{8#!TT&(YZUjb$gn%r@*dD$q){DO}R}sN#pF! z7@v@EcCI(f@yTH4oNEa8IHmQz+M)(%3>64?9+kl;)$CiVn2ccn37eWypGlHpTYa6h(HV%(yYolTA!(;BX`FGt*i)-I&aS%PP8r(hG3$J7llDRnxP;^Hz*gMW%XQ5>GC;vyM(RImXI z7#jc=8yP2DrvqTK8JKhcg9IpJ1dnMNRB4t^GEjua+-mcw0g2-9)D;)W;G=>SU_b}l zsbwX16|U0)Fb`lrXW=QWQ<37qhCh{nQe{-vc>xaam~o8XGDlj69aqODB#)~b_!I*r ztz#ZnH}G5L57rx;bVw~bhP+kkrb4CW#l!+ZNvDVmB)-Kn%hR~0(~1Isyv29xi2-g#wj~vBi7r-&=FAwjdC9y%NH9}w6={{ zYa2sH1Tr+rZFJ1ewoyfATRbQ&cPcP+#2y8WavvSb6B|`DwvDi3ErnUT`4ih7G|GK+ zEJtiq(bqO&eQiv3L=;1#+(*Z>3KeZ_Bi7c&&=ElljdC9y(<pp)*@ z;*#;g^|rPHJf>-=I0SE$TiJx=k^zH%GM~AvuxqlBdHCJPKeFzuQXx;PDqyH7&5|WI zqY5^F5oZI)h``NoZUrxqx}V`g~#cDV!(S`%rqBy-WoXBa5LW2Sigez^n>>!x6fONI;np?pPn z%p8y3Fh}sPQ|9>MlA8-Y$3O|5e9GLRyKUrm%q4ittq)lwGxA9Witw0wA3il8Q5>GS z(kvN#_$_luZqS@4qaXtXpJ1RUH)b5;x6CDY(40tkWO>ZUrx_^1W2SigmbnBEniB<& zj2Et7tB;wOk$F(v$iH$P!NX3MV-u3hMI)bQpd>xi;wS3vM%Y<*8iOQw%oLAgE*kkf z14Veu-4DNIF2Q4(2FYAB@_7b|@R+8-Z<$N**rq|o3qH?45gyYt_}zC29@{j?c){lx zD8XaiVQ=L3ei1y>fQFW9lpFHYk$fY+U(P~@O@?q#i5|c8E1|QeD%gaiTj96M711#> z6@HJ~NiefvbRl~OSH5X1pnz|*i0?}P@7LqLkjSW_xeZRa+O~`~M08=>_1-o%8NxxW zsfymV5$kPZ6Ox5(*IV1zWJYv!o%F>jTH8kK9(H^}M^-8t<j!#AKD$y{v-HCkU9U-E8GTcKuojeiE zOfZ-=VEJV$gRh~&UOPp2c$11l??&v}ad?C~M#J2P$J$qz_-~z; z;Mw;TI6T6~oA^uv#l>aqD@?Gv?(~r*c+3byR(nnSWAsY!P@A6!K_)&yATd0OU<5Ch z23u$G%{n^p2!a({;t#enf&hMRyY-x`N1Ie|156k<09#hFc8rEOT>?e9u?l1+732UD zMh?K?5fK9oa~~e7c5H&3f2VUM=CX=qJm5P8@IHGB$O5fqjaI9JwNCLBtYn~oJ%6XT zkTsxd=E-d==-WC}P)&RSgyO0)jg?Ofkl?Y6l}Ox8dH(qksW00i;K;xMQ7rTEB_&6?0V0Z1iO zL0m9l#0C73NtevODF8h(dn)i-$47RZ1AzfE$yXwPH|2^oTk4UyDkR}#R#U*l1Wr+( zoGuM_Dx&fsR;f8tDXlVMM|gUg(Dek=BXd|ujM)!!eN!6 z^@xwmj7og2fb_`B3=)XY@sU+97)+3aLB$d9gpO13xf)8VEZ=QlVp17QDyR%5bi+RB zW&y*+5htH^fTZVSD$;$q&WgZS8C`n-R+)J>fk_1m!360A^wN;-%XJol15Rd1LUidYR&TkwFDme7}u&bQUJP_8Tb4FruQlZ$gLip~l$& zQLRig%BiDQ6dF_ERP^>u*!$@*bY$#sy|s@`W@5JXO)AdUo8)#r;J#uLk`cr8QEdifop$JdP;|b|;i%OF8pfyp_BSQtB5}^nW?h#1o@w@y8 z9(J@ImmV1__{<0;c+8{qCVrP+f@d!+;qb^E3!fUH2#;A>QgO6iOnKzfYdAbITJWh6 zitw1JoeC0y3Go9c-6O*VpBkYEk7*kG#=Nw+Y||j)1)s*C2#;kNW}qs& zhYW8jrX4cbwKc0CADA)n0j70iPZSMvH;^G9WPWE>aVLQpBOl=Kh)n<*<~}@Vd4$I^ zs~{hkG4cTpkBAp&nEUWpJ8iQH@_`w$3Q!>cheyN%>Bh>nudy=wHfPKn_)j6mlwM>4;VIna!}RO&Kd0Ciu;7MRZI*tfISY#=6Ux z?1)_j8s&5q6wxuQg5PqLR@UAx!_bjIg5Ua9LWerUq*bWsEt^r>7bYkeI~ zW=q-3Z{9lD^VmSD@x$&Fq0A~y)|;`D^)^W08XIy`;rhvXY&HahawjTI)|+9Mm@-)M zo)|RD{bgnCw9Wi(EwTyJeWtXI3>B`Qtap$ebEmDMFKtFGV+tM_EBM`VC7Wfoqs=PX z(PpfNj6E+&_D93qUR=3{>`-Ae^ZUJMaZ%Hmk{%f?_}y|v=`m9~{;6{b9*i>-JThGH zo8*e{n5Mx$buPh!frf%d#tVLvToE4AH2CN16T>09S%l&@%8}f#ll7QUP(>)$Pu63z znVBc+MXIq9yT^>W_2jaWWPdcwZJUKasCdC|x=Yft@3gV!B`=CG^VtPTS_gs9@I-}o z`p6PIXik*$$avxU$$IQz&FEE4WD=#K`)C&vC18;`Cbt=;Xn{3Cx?RpE|0UmSr!|x&^%SHWXWVv!Xo-RW1+vQBluuo=2C*?`Q zRnbZ|W36PfeL}Jb<$5O>o6OAYB%4)qlFe8r8Jm!74DdU((kfs%kNr7G_sKsb&YOwM zjQQkb%Kh} zb_@UDH5V1B?P@x*PEZlrj_k{+wmaKw%E3!b_0M1c*BF^R!EHuHP-`%PqrT!_MFyH+ zG{EdgJmU*ahe~^OGmNAlg*C%KDE=T{d}5XvLuMF7;6F(u9W&zGh=Zk)1oEG7j65^C zJyIMaZM>1+0ArBV*Y!+;Y!?1Tni<&({Eaj-(pC5yX=bD+@Hg_z%x1O4t&{S$tV4~s zOn;4|GE`3sc2wSm39KCvn9(q&I;D_CA|ODN z4vDjc-&~g9!OaDkE%?Z_f*h$yxN8s(D4 zVUr;o^qO+Ly0oVDx)e4cX|AsK>`BS(9iMifWT^5j!8-ETgk%{94RiXS zio%n&1%&Xp79k0b8NbNVkj3?mJoc~_bl#f`5h^kRCMGm zSVta*M{ep}Kh=%RhJaA+PDMxFf_3Dv3CY*;pkeMykGVfl(UG^nRy!4E!N$u`ja~1^ zV-IUVC&nq=Q(+pg)lS1B-Mi}@c?WnR5NdJx-D+gH=rVfJDT95EqxAUAeI>_&y6dF$ zR2+r3U`OGx=cUSw{1(3=JZ8|~cdI4oF@px!-VxH{?obrrF-?Qtt(M@~8#kErsB$R3 z#jgmD88!Ha#U*&ms6mxj`RD5u;W15v->pXQP|KV&4U%+W;kWpe;Gyn1X&U_F;u1Xj zVM2Uy$*93^@hie(ng+jHEx}`&26-u>h2P>=gvT@ueqTN@a(HpqsPh!Sp*ZnAZY#J` zP7C6pPAA^aZNJu&3_&Uy@)oQikFP2jE?jTOW3yQx5Gq{2vW?RKo_JydH}Q$NoGdj# zCvL&@&|``vbu#quZ_r1|hK|uwrb_ND_^1ArlntG@CrwoajlqJ^7_eoaD!i`q7_iwa zOdf-HEve$x2emMe^khy0U47zn4w%BTPlj+%DyO2;Z$a%85*=B6;&Uj`Jr8?KIuU*T)a9FT$n7H0=RThtFa2U;y77_R>2^!piUDhH8MExSq+L(V+IEm zR00cj(%d4;E1(%!ed6;Q6wxtbg9=K41v_Ytp(DLHpXZ>6j+r&7pcYtAH;`OeGC=V8 z4vOelNt0Ct#lQ*~4bpbUzOsZ*N28pE8U=I+2kA?!DyRll$ZVj{5n%$2avvSbDy)3= zLSp(7lTKp*8NKXF^x(>I@OpbWyb@azs|x;t6+2^&*#lX8Lc`n_8{Df9(bcMg$N=i6 z;^vn$2(tKuhPe+9?o~*5Di{qQ&0)eLoH`ojHauqA-m2n^xfQndY3UJ>0u6H?9_v<6 zE5D-+GdnBn@6+&zP=SWI4-f8DsKr%oqkwxbn2Srq3N*}pcudpa_o5TiqkwDh#LG5q z9tGT;ZNgrNLWCYG|MqujS+T9ZJxq{gCo7)?pcE&VZFVca1&!drRy?hBWT4=8?3Li5 zmN}_)D$eFxQ6HT0c%*NK!`#+7=HYy+io^L<>~KC7FUYbJ8srYlb#X!k4n8)<3d}2Uix)c+rnSf%f>{4`AL1T4J zFtn;Trf0@#O9>k$egoe3o6iiZpRRt-)ijxBDYD|(A(J=SnF#{Rw#XFr-(mH02 zN3sj8Zjb`(4{K$T0<0=X0alz8V4o1Ua>vDW{g^(cDhLSWQdE!vtT-tEgGa^-H%I~Y z*{sZC`c@UB04q)kuuljaJRv>TJD>L15D;2=+O2 z5jdR_!NZQ}WAH#=bcDwzJ}60#c}(A`;+Vb_JEo7rBjW|1_@D@nS*+mq@FnRnixnVl z6VelhIb8@vc+6r2zlSfuW10p?AP9KkF!$jx>v(*&LSncS@d|74e2dO%f`nsG*#sA= zf){FmRu5>7!AAt$PmpmqKCKF(f)yt!*u5vnD~PGIzals$QNgN$s9=Q*1ZqPIk{1FV z*Z!5%4qXF4`V>C9AW_#MT}#M@zo##meU>AcKH33tDhw6^hTsP!t@~Uitij z#1x!(76oL~Ho58a8k5e}t^nyBp(WhNBgu`Kc!ETZn2H;&73IdPpr{}#SaGs~-CRM+ z>IlybvVwg!E0e5XRY6v;;$#K;gdoo(;Qf-hGRX>76_+kpWk`-03P@xLc)yf*GH+e5 z@);0F_ptM;SPB5LWda^IhgH%&`OGOXrwY?Rhg8WT8A$kvseZ{c!wIi~j2gd0tgy-_xE-V?X!-X*Bu2QE z&bdj^*0!T{jJ9CkL;~Y_dAP0sy#P#y%2U8LikwUw=hVa$%ocnNv_2xm1Ha~~vkVcE zGDzJ68!-|h*9#~^WbMrWL}m2@gX~!g_=~hvqiA^2IG3b|PX~TxB`$)Nf5cxBBcd5V zF*;02ijmKYV2ROTIs}HaVt|pWjZVzOKC>%~Jc~|a0!c7@(FLdp8U!nN6`Y;gOFgg} zCB(Q4c2LxjQ1F;8D?8}Y{TqaKCHm)-t9Ogr4=tWjRegGit`Ip0_ zR9tbL|KKp67NTK*pnq<9uR+tcdJ176dUO zgWwZFl*NbG7?k+<^acqex_N1MNcQfJ zfB%6DlD+#QX@CGEH$_$f5@x2fB)KM?sMewdeOPRR502ewwAcop+PJ8A`vMRPJC`Mi zfYQKUaEXYgLCKF#e2^9yyYj)oB~n`#K4C<8kr7*i2B{_jVi!N)AVIQDI3Cxpe!%Aw zw1~4ogXAi>EJ=0*1UU-{lItNyzt&}`e~laxasRW+$B zyut$2jqx=h8G>Lou3JzcK4_NI8uM>%kk^1`1eD|AlVU+WDo+7To0jQk)d5u=AwdtA!soM z2JU!B9-%bw7s51?g#o5j69F+Eh66|{xVV081U{dHhF)$+v~c?DiE1udt;T?D^aVZO z1YsN?YU=_?7EYl_nus_Yluso2gy8wPp<~y241pwnF$6NN;SjWZK7|Alu{vl;Qgtyt4@Fs$=w=OSx%roJB&NCOt)ObG zvjrE65x;bZBpL$gan)Ov9y^l-)e9nlaQ#jWM`y#Nj{uNr0w8uT2RAwL0J9QX;Q zN$@8rj{(1vHUL1IMcaVmpkkw+q>U>0SLp1-&&s?Q@Dp5f_&d0%f%80{4uQKKT;lN)JAlsJt z%n=zR44gDbDszQomtO(Nj?4VJG-Qy{{tFxy!Z%~&9kU=u;3obmR8dMs2(QS0U4#S@ zaWIf11(}}0*fH{%BTS6gbU;iTLBn!g$)+H1`y)p6*hre zl?8f*sRD)?W`}??JFv&43K(#l)5la{A8Z>|L+lW6Vh4tiu8X)q?7-&}*u)NjPsK=- zB{lDrBo+dDvZ&bDHTmyHJg!XiU7bmGdVb4acRzLtbGV~XxA z@ZVpetcef}1LW@s#UQYPi;Km#R2s~{szL6fGl^v)+Z};mZ$%^t{7XD!@v&nuU0dNZ zSX79QZOD8Ah6ED3&;z7xu(~C_50Wm~@L%r1nXO#8G$9>>MHNmX2z42MqbFi{+i0qo;V;=59jGQ#aVXb+LfNCg!>2Sb*rldPJG zDtzjS3YoHFGM|GXgM|ApDz~AFHfs1G0^*i9&gP(u{A({1Fj4yvKCwoH;Gnw@%fslU zw&G8SJwm0hd>V!X91%hsA73m7Q5y?Bk3|_cVvo=ux!zj_iEZr#bW;c8b+kYb;y9<~ z$Hr)%4tySJ7L8H#oKB~eG2(t4o0JR+{P&-zz{qyveBOjp&m#j>yCDTpP_NZ##4}!c z@NywB42t^|?1CIhanz6{1e`2^tuWPAa)T>@O$Ry(OuC@JClN?1jPAjZOhpzP^^y?q z9o%xC@(QC{Gbks1JHDn0^Cre;!LTyRi;7VZA z0r9hqSjC%6z-QS;Or;cXoKugWVu_hXtaIzu4dAw0fwf>4YfTtg}Nw9M{SMURfAFqW6!UjC&&nHu6UW;>*U^z z7AjBdwYY)|#s`BhgP+eVAVS0i5eohxi8zN3r#(=UQx7$1T>as@96i+Mo*%x-jI0Ds zd!Qy3z&8{@$D^!C>x1uMa+n%9mEHY^&3=d)duf#;yR`=T4MS}Oh|jN}Us;m^p?}-Y zoeEYamU}Vt2)nc0uMa-#=$krHxfuv~hciC)rAvAtXMa*3d=HajMJ@ntt10AAH!;x20UTQ7P{M%A6{YLYd4ir9Su`CdaB=KpPL6 z{SYaKxeh6Zxz19~9_q9Qe{-rePjmpq`p>aJJET1H$)`EMqlR6lf_E7h9Pui1V9X9C z$GSYnw0ww^-C$pq&$!`+qh&es`PkjwnNOOX3UuV*{PW@Kfh?UVl>PqG6F`=(Jy12n z(E2nlHxf*aowBS#Pjgtz+cK`GlpI@gPwEcO*VhFB1y#cc-4;E~K^;RLyXF|k@jNUj zhPR|$VG>oN`$p3$^fU)|yv<{URLD5a;TVr}r7^lEfOTDo8!F>O)BJ`?;8fGhoKM5P zC`e;U_eD=n)CB38b7s`(&4CHQW0#`q8WlG`2X^d`@GwJnn&T3J$71ef7Up&1X^!jI z!J{X`wt4r{yii8)ShP%dl-OYo>v)^Tnk^Sinb*m19=mH&FbmD%h_j#98QH<(*mTeF z9UrRAen(_zbGjo^;*mM3BSx=8Z6aZb8|Ijp9ivwSqY~b;nkrZq4{kaM>uPikirK;B zI0D-P7Xc~wx@7K|%*pNg;6oR+gUPYy+k+Q5^>~qCNMDZ+7U`Dn=9(%tzm?DHc z_I|QidYU6pb_jXw{^nqcU~=sKWcTzm2cx{rWW|7pb(}*poXHC75DPAd^wS)TvV+O7 z1Kb1B0L3+)lj3K}gYU48W01ya4y*V`osLTqn@*=o5@mrpFOv~M?l$*29n=Nt9F4L= z$YU3{2c^O8`cTNaKz#zG(RG@024fev2c>c9Q5s#Nf^)AAzGEf^A|l1zdte)ALv`A3DGZ%Xw-idD zEvmT9#1i8-c7XzOw}Myi6Y|&v?!j!FDu6<~i`2%d558k2j`10%J@^c8g=51b-sK5= zMrR{9i8OYB^P(Fr?$`#hQw?3CB$<*H zj1Sf6)0LxLuLT)Sc1_@GB3X)KaW&Me$1#3m7bwNCPER0Qx=F)K9J|0d^uw8~1v^Bi zr8w4Uj+c4ca96;Jc$Yc$BbY3zrcBBzW)7X%!Q|Kl&XFX+N`szHpPHzwdK_m7Bpx?uZ@FwX}j*N0QNbB;OZ1o-0TsoAC3zO3H`o zG&!-w&J}n)@#CGcV~?-id$2!kJDsSs_#fZrG?Wvez2(*U6 z>UBDAztjgGc6kSrLl=0SLpNe1$1YGRTAk-;l^skLz0{GMPViuI>;k1w*7*qxM<*F3 zUs=;BlRIL^bC2QZ8fES$oqhtt(X|JY!`Sis9EPK{{1yAs6vwE?GeW1=%je7MAt5#& zQjhn5IL0y1-gmMAT&t4so5X)k4GtvU`)6oLYz-;vS|?&U17~FgcDL*?GxKPA$aS zOjb;bjN}{-^O1fU-0YRTn&zfK#b58Oata$)sl#)4h!0<_6{0fEoBQWEre??RjXmHT z9}-NCJs{g)xy#cSc?XkY4>$*i1e0SA$i`J>a%v!UFgf;sbA(7RInJK2JC>Q8#>hLE z9DBeyM8uhllby*pvb&a;oFi=BX0ifXM5X7Tn2%Ixt{vT!Eal#-*8~}Rlexds=^dk2 z6iqamZNJ>wJN@6dZpwKM5eX)XqA8Q&TILX&9U?ATsUn_E?_jbhnldS_Wq#w%4kpL` zZw?U&CddAd98DJYJN;ipw#Z1%AtKIX1*_;tikF#VY~CKpifmCP_b5&Ut0o?foW<);E{oxw7mf&#bZ(# zdJi1r+`~b-;gi^XURY$u@QqhNd(a@~9u3m*C?MVQ9Jb*T=22@BWY4h?Z?q;h@Cl?! z-`8^>Rk#bI>2s}}p>uK?<9+Cybd8QFnKW8z5S{k`L(V-gq#pw@v-1KYZ%@iGFyy=k z7;^4`Aw82~zMlYw^zBt{bT4NPXYkosZzA!4Bv2xQplJ@^7!TxYa}>)QJC;4T28w*Y zZbh8CMspDH1gxQNj@sGw9Z{A!5aFS+1f%=5u!g9(FX`wkx{;NwcIK{Pi6 z%qh;kgm(_WmObc-rAJqE63GEreeUZvV!P%j8aKojE_-Tr*rDjQTiiFAlc)8;_b@qB z;bjho_&^DJI1>UsE))!Xt==saJC;2aJC?p;M?ZQwM63_KXY__%c$ot#stzxG#g3lI z>`Cf_?_qMR#B(sjhlu!c}r8Gu_;Smv7=`)yOU)PKI36)+3+uO=!4I|N4$wy-BViQA#Y+==ZJ?nZ&LDb z*;7&D5pQzhhBNig%T$iW)X%FbuSJ(Ap?TG~6%e>~r zhiW}LPF7MXl%Cqep9y+e%2MpyywZiwiwf6}^_3i^S>`B{H$$JLVmtEUWhrQihawFo zi`I#^MS1Q$JdQ)PIjrH4vNT5bu2ikWB+60lopKP>a(tmu{=@wKvIons^p}`4rZm4_ zpL@^fJ(z_S96f9{BwWmy%tDC>=9NNr%))V`wg<_ulnNj^3q_mfl|pv#I1be2(1Q=1 zg)eoNDR4h$On7&fj>If;^ny#cA|FJ;60*$k8gGwYVMguDyw*kV*e$m@O_4xoULRx! zkK@(W9vs6`;0ZblCA@k9#?W<{D{4HzNFn(gc<_<4u-(q)w($J2d_Hxzv&kBGPf3rZ zFX_?uH*Z4J2OpZP9Wowk@f>d8^RNORL|0gVBx%g?8gDaMQ4$i}%;5&XWQSz$M^Xa1 zIbLH2lfxaX<++j`jnl92%9LH@UcgrzBqdiq->>%wVRhZQr=-UcPzuAGHLj>9D*oh& zk{*3~%lI0YWscVfAs2tCGf{lN6D2*iO#0d-P4GEjBbY3zrc8=&d7`AphRF&u5p|hE zR6f!s9v2SIFyF#6Q}g-r_4O`87JzyDOm`kyF(1DIUs0DLH&!lloXgvqT#+U^eik^s z$z->f>V%@1fO+kb9ZZhH*geRSrC?cfBsIY>2fgfIavaA#2PkR7`4u+NSM_lv3~6ql z;jgQD@l<;tCBRdd2!r7hm+}Orq;(P#yNod<%X65L*7C05^IJ^%tO#2`dHiJIvj`wNeNR}Q&(uqWj!xJcy zt~q!ddy_fv<3l7I#oR;0QOrGwSPa-4JQ51hyOUR$NbEXC%e*b(T6;snnmss@rN@zU zGLg{r2^>k+9C70~wK<1=e29pnl6#1_#IEJyjSY5LLf1Kb#6?`MjPcO_Rm74Fnd4=4 zhC zdSF5hOPz(BozptUkW?YB1r4L#Sdt<2!G|5)F@8fuUiaWh)*esN3pqQK`rvz*9Qxz6 zKt&!l`yoOOW9>o?Tb+fR-P1Zpk%W*RXmU3F>l`@qwvY=qs>yrcBx?^R>4ltKN`3I5 zGIM**S%;w>FR_NDzin{ z+k;ne6gqg8Y%!*c-3hlto5-icI3 zi1_}(iFnrRNsGlr?ZK_AJ#M8NK5->a;8wckKu3>V$)2lU*w;tZJ@V=s&8w68;KND% zwu~zbMNG{c!tx<9_SK1O<6XHXu-nEH&$Ooq$6636gYgr){{(cUm&aPT(V%7zbY<AEXEZ;mk;t4j3Ie89LU$T}~V z5j>X2&5h1$qvEBWKwflRhR5NC?K;Owe58ae`_KJ<4Y@*PN{;X~3Cj{{4v>h^E6F?= zJ#lzX6!O@P-Vh79&e0aZV_AL)kK&`|_>vv7aIC{~q{K%`*bZsht=oZ3;<8AvNln7C zf|`RZeDo?%Lq<>B-5g%>_UMf-c3JnJF4i7((N7y$K|O)G=-aE@s4JNlxcEpBU#_36 z*l9=@9LV%u^|frsJ@lO+;;|CT{*Ij>oCHWaWIWd5J@>k-wcv-$L#eY-AAH#59ZZhZ zcn@f0?LjO3#1U`u1Zt&kZylpn);+k3wZ~no0bv=WuAjhNbj=+rGpa0ma2IQjyXYIu zan}>Li@v#ajJsH$!(FtNzb5XwTne%#H2RJ6M(ZBf#aioqxURfPSmLfHU>B{A7I%$d z7aSpvcgoOLmd<@(l zzLQXCj^NlK;o)NbQa}q2eO2Z0QZO2^27To*M_~22hmF0-V`WC{^<|E}_{dqvRfnZa zP}o9lU|?Ub#jjHI?6RkV$EB~}(a)QlY_1Q!XZ(g*eA#n7%B8;^rDrm)yX%ARVR9IP zUFN`sn27yC>UAPY2zHrcId;s%VF-4agAzV;CQ{`SqZ|{Fi&j71&r2jj}3w@ z_|S<+3BOV~$j<^FS-13ckHt~V!67c>dYy_2Ij>YMPt@n=-6(rgLq*;L6S+i54m&2= zH7b=(>VxkQ@-UFS?7@j#HaL-PCg!zDeegX@jw9J8fg-`_SFBY?a$2a?&b`@$?9RQ3 zquPTLxomJE!4&7V#G^a|6nSm$co_plF3%M>X|r+H@Wr{eI(|{N*0yf@@e`LahljW# z_Z3bWSB#&ylsO{iZ9`bNQER;iD00~VMP7x%)J?I?GeD8o_7L(|k@o-{EPLI3hj$8~jw1|6f4woM1&^MaL@d=ni-`r&kbGSSQb7(E^QWnm=0W4iX8)9o- zfu-9sw$fjd`$L(ys`My_#*+d|FHe9Ry5n6`7aR_z)2{V5y7v6ng@oZXL@=3c54pBBoUzQ%-d?CmlK zbNKL8REcP@Y>QszSeUnmuU7vMHJ-y)K5+P2C4@S9TxH7Lz^9gEkj2;>%n_s4B08VL`l*pBqGkb2!JC$JgeL zmx`j0X*dTr1drWL*`;eVM@Vxx#||FH-ee9;c&IAD=$?SVERCJ4<>G8xesb9sl@T~| zbSG1MX&E_5gA~}pTk%31o3;!?H+rIYb2P_}(R(lp_k@H6q~VF-jzX?G%N)w#Ivn?U zBpxLqoR=Nh!Q)tm=h&5xk?`#TT#9v_i@0Rf*`G@ymWXfO4r=+ zGKQ>N=2(spaUT(e^#jQv8IU;uW`~T&T0BRue2k21#hUxMtlT3@c%qDNcNe-wgnKY6 zmmagy%S5dH6QGs8y~`N1a@hk~xfC#p8fx98eFC+zWpb>?&!JXY%e#EB>#++t)Hn-S zeA6D-%B6>`G_D3Gjp~E%u+?MS%4H93=i7ce-*T`PS>aZpcR}GD|tC zAGZxqB;Q?tE(eXSbF@k|gV(-RNZ;FR`0Inui@0(#7(LURhs_3)4?^nfP_D!(lh)p} z)z@12Bgg&MIocwa>>SE9KrFg8xz6z=Lms>4*aaW@lj|Pb#kI#>^o^#qNqz32RNm%s z47<3_ffA2Yr7^n89#)@sdO3Q!`bL46J13}@%|#E=;XTu^;xy{&voWRV>gya`vSajy z-h|Ssv&rqVu);Ki$87(v<+=ArcnF@l?g3qJ4ug)k#qP#2FDL4A@8Pj%q-gUTCGjD% z(8?2CS)#@?wE9FZR%UEF4YP~7&MRoRh$~7%M4T5V*EzQ2?P*vM9Ws1#phfUlw$y}2 z@hWp@$qpWiXHXt@K&P%f;-Z^|;#8ghTy)JHuVcW)bzVy2L#82n`DAKdr(r~gX&TC! z=Q=N;5i%aDu`DRAb7;v9A&>QVjgE!2sU-#fGu07tOXHq=C6L^chy~@pC8k!nB?7QnqKrAflwP`d*OHUN$*t9n; z%yHcVxVZL!i)$(6K_oEu@hjoeJ(ftiTZN!UB6wHb3xaFad>MM_Lrh`gD1A|3BX0y9D_Gr zN6moa=OC50=W&$II9>zB!7q>O z_%&V*&EW(w9K~k4`;Q5(hnlW)491Qgj5TqNpZE|>q}poPg$rH0b6?gAyKzYuC92&6 zo4D3m95Q?wFg^jB==25|kCkx_CvX|}r8W9S^Qorm9E9<pgGH&)|0oFJGStFcCmb3DclCdYa_ zM^Jo-h(nKE$f3qr$l?{|H8o=V`rPEJ8I+?;*Rm1z&_nMSzp)%T>(cm*c3DmVL$63CBMnAvSXq6gY*FCV4YrO?UO|=;QCvYd* z(HraW9FP$r9_z83WV+5#EN{=l!i`4nb4-CVSqn<|NJg!V!3txJ!x;1U+T8J4fe_+O z=9q%uvCpq{jph}`9Fehu$8mmb4p0al%S4fJlvTkTk+Flv@w$8tE_kTUF_`;TL1(Kx zL!=je`Fub18*}1F=YWD3ygs=WJpJK2Sz*la7&``UoLift6N1NL>_x!hqUI2Qd1JD0 znS(pt?r;?9I9@A+LG*79fB3+8bGlzX-}`y9*k$A`9VTvX59Hz6LmsZL^F|WyPaqz; z<`}%O7nm2Ya8VanMC|{(@+h8a4@TkopfO#d%)uBtCXR=O^nHjOyw33jA31RrE9(Ag za7wx8*NGzu_}d(!5Kd;{d~+u)=-OYCOjXumaS}k z@GHaO*NJ_seWT0#8amY5ynM$8&YdGh=h)Akaz3CL9f5n__SEdS1>B)zj=PR$>s=pw z&p-~>XSX?~qE7HzDaD~?M}RZ8J#deQtz}DFAAC=j-9s~XhKs9P0d+k5z(>xVW&M2Z z=FXAJCYVoc|0Wyo8@A(ZC9GHxi6Uf8a4Qc!?C0Bdr*NZoha|!dCi^VO>janT2Ddq` z!e`qhLJ}lTXy~ z*cit#sO0t}s3bVulYa>*pE$O8w$e~9*Uzg-8MxhRX(%$RxyPnh4{f9C%LZ2xT!Ho! zyImf9*xK9rGOpus+k-2)ZEz(WlM;A8gDZJ$kC`}J^WNr2kPlU5LkqDL95Oyp{O0&t zhz|aekjpm5L$QRA$Ih8dh$TF7Hw0T0BLT7z|f#ZMqiNF@VEV^lc8v@u}DsL+PZ< z49QHIe|?XX$ErNX(|o9uNtl-syCLM`8IXP}vCDKzJ)YxBV*JLcjOk4)(s%`@@|U`pRyWk%!d=YT1#}JmN{`#zo&ctF%^hzACNmhlshJHPyCquJsMwq*a4B7L@HozU zK8H(bEq{%K!7NxBB_w>lUbxz>*=(_2dtfQIICgDm3EcI^9fK&*BrsE)lDAoI8C8G^2)8{uaNNR&QZBehtFp!zP&D$B_iH~O1br@l*ZH_ zzLUuH2~bMc9HTe(Bzr(9w}LhC)qLNm#F|flQu_98V^E4kLX&vzLq*Iw`K43h6Q{;W ztC#o)483j~&hKV%8`zga>H8~z>l5`*`u1-1N*M{f_JDeBJ*cN|RAS91P(6Knw=t^c zHV5K-sE|2aV?|uUQlHk-=jBz81v7b@<8NHZeMOYMzhe03@TFl)*tb`?Q9C$C^8}M) zZ!9p!+Z?~NgUPWwo`Z2dQpg-%;KaqNJ3bwcX!ffni+h^GW@7xxR-;5*f=Im0Axt~Q zZ@iPa2Pkx_03Kyhe99cd^fr@Ye9-MVd{CR?U*VCDXOh*OCLcLI*Ig~EJEVIMLbo0v z)VQKPu_QyDfCzQ%9dBcZ&}|Py=+>ipx<=V^N{jg?P(59HFge!bIrQiwHJLs1rBmeN ziDGK6imV%>bM%cXa$ggr>#lB$&M`|nbY!T=_dS>S-22OXdM2|=sSiFM{#S0qrQGL$ zqmPvF6z?rtxA!?1sEYVrfka{kWk0k&_@2=lhSv8zxS)HF3+hFj7Zdft_b@qBGQk##Igf$RalkoP)Vh-8wdtfp5T2DnJEQs&=;Cng^I9C`AgGQMyx7c(tneB=Zp5c!r=5|hn}X5yn_c>taZ5=7x zs2;YUG8XqVhnx8D z#cAU_d}90Oc%8S0ucBnc+0DT#!Q@zxCBt%`<8^j0IacI(<(rR`v2ECPA=mZBX^<9i zNe<|~r`*lGFL%?$NRq6n55B|KjqBaq=g5^1@mP%|!E&F2b#{n&tj2pVEcaTIM!cTn zZR&&X7|9`q<#c|482B$gfBEvqpMLrU#(wzu%a6aEFO`qDkR(6?Wh0+<%g*~BKOJIgIks=(=7`vkj6J`) zN%rYe>|w<=QQcShL=AxYy|T|i`z6E9f8)4nl)*8TMm?kR+Oa^W6M6&t{ShaR6o+@}!tDa3sWai2n&NoM&c%J!QH zCB%Jl1%?vOgmBNH!sk%ovup0*9GZI$%{_;3&!MpAkm)&W>p3z#=dh~h$PAp_j($4F zQG;^`?p&5Y@XY;iwHN15&$FxN;auV&dnW8G#Sr}Au(0Ql;5lsUxs>j*w^a1A z4l$9CtQ}lJ2dx5Lne$5+*u{15u!NYF5YrL{))HRztq>ESIj4l`Eg`QZ;^KE+M!j1h<55TtaZH{`~cj;1UvC!kjLlaZ5;W z2?;KtaZ3eNeL8^p^#((xOUQJorw{Pd;WC%-nM)|z5{kBjOqY=95;9%Ft}bC$m$0jA zNRV?Rg^j}+5?sTIt|7rSB)EnI*O1^E5?n)qYe;Yn39cc*H6*x(uUtcdYe;Z)2_Dvv z;2QDG8alYTOh2t5(=}wehKpRoimsu9Ygo}WtmqmtwTc2|{;Z*lYbfJdBfU?DHNu}Y zJfj`Asc%KZvxcc$LmAhQpkIgk>C+k#Ttk9u2yP8ixrQ>Xp^R%NTxJ7>^M0p8OUP6?YaESF%b?k=UaBe?biRx9{mm z>}tF2_!17W-Hm$*QTlDNycH5`*MhbSE-xXkOUSEU;b-3pjk~zU9WEiJONi+b8g~he zyM)GBD!vX~$jk3)qUDfRy*|ml6&gncI)5uPu3gzuFKMxdUE_$B{o#6R27mGkDC~y| z&MUtk=*uubs&=p>eH_-7 z8lv>tU$hgVbkshSaDUfuf7cLPJx@oNWE!(CE67S8IX9-Ov6g+aT8LA!-PyG01m zPAk^)iS|~E>MiV8J4belQN6|Fzl9yU#i-sw3vNEDhg;aOTa4;0?AR?v)lUZS^>9hI za7hj7-SE^7LFQq1N&Il_ZkW=B+pI`U>z6CY!C{3^Vw`Ug^4-FY-NKGlB-AuNJBM5B!eQmqXcgT1qG^K6`*;}D0 z_mF8#uiKx(H{C;~H7Q&E6#Rrt?-3u~Bf`IjOlwBT-U>~*hfMDw(|gGD9x}D_-$gI( z;hXNEDfiHnduYl%H02(ea*uf99>Tqcx4MVQ+{60a!)@IohP;Px@1aEZP@;QSzk67} zd&C>}kn%m^jeAI$MS5M59r)!M&o4KC{BkRKe2JigUp^oB)t(C>L-X)Rn0W3rVRMYY@_20WJvR9s!xSH1Gcv~}Ul3){ zas-;k$jTh})xH*S3ADyl%gnqcVvez5I38okz_0d=&}Q;RF%OPSs;~K&*M!WmN%a^8 z`xrSH_zU&sSICI_S))g>qBv5pK4_uZ(ApcJ+zezJ=g?tjjr-7IhPgf0GGlGO% zlm4k`_bHb6Oq`JZscHAAY4@pVH@8R!S7FhaYM}wArrj-VLq;&<(GoY%+8eIj@Yl5a z6sDc2+rHMMe`?Zi2^^klW6-1zh9o#`^PnYfNKeMhIYo?%dbF=alnbp&3;vo;pV};G zNgT4A?Q2b^DeD&;yv~hSryS31Zk(H*M|y|+XSjBLg&l7h9+Wn|cJ}qk0a|>mWqHW7 z#@M%n544cXpGMvNDV;I`knq_FIApRO-&yAtp-0r6yHY3h$9-Nz%Z`q>bxy^`kn-P&Gg1)BUCCeq@c{ulazb`GBSA{?dHF(saKi zk#Hn2|CXlvOVj<9NFw7OcB7?|poL_XCi9j}!ZFpk3x7@KEsun=YNOFKp5m=8x0Xxd zx%Q0+=8sGB3rmxJ%PjHNCc#(fH{EafCUz`BKi0_GLyLD?&Iwv*%G#vgvQ2E<27fKz zgwu__wTu(A@I7mb3fATq*5(&l{t1WO_-lS)ZL?%;v!o@Vj%%AGYnvr&)BTo_;M*Us#(JSlf)S0zqX}K`_$3+w8&G zX2jYeg_flvXldVQGomG`*klU-v9=izNh>y!nmxG0Di2!NgO<307F@N|)$!8o!KHbF zmc8PyF^5|I3R-a5GFXIHA>Wq6g4W(`bE9Ri2*YAtU77`HnJoU=-fi=sC9~L9i*asw zEodO89|CSEp=IJ(mE&6X6G5*^4YhJ@IOvZ2dt;FS5`)->hEki~?8|I zjz0ROp5<3~?8uKD_-lN_uU#z9#f+$T1eR8s|7dwLZmx?_Ynd}>?cFvbTJnrd(XgU^ z0S7NZ;{p%L{nnKx?H*x~1@-#aOj$9<-3OmlCsgo9^G5 zq`d}J`D@Jk$nL>kvj??|Q0Y-yIkXHP&$V}3bm-MmtmU?XY}q|(BE&3d`8{ZD44U+B zP5Ld_$2NR@7yg=G@U!anwPp`m#t&NKug!>-^kV_pzT50U%LMY*_HMHWEg8rWwDxYB z8#mu=gufO?wA3I61wzmL6c9YO>>vx*7?*k)#74Xx;HbYs(iP}mXl=dFQi7lnVvalB z+FEJrg}cvIyGyZaA#WzT0es1Ednj&yh$D zhx$MN{y+cr-Qnfacb{qxYJaOGt31?s?E#g0Q8$zBKWkuh@Nw~w%6+D*ijRd4#P^3I zA9OG12G4uv=`fFq-0*pieA2tUtHJWYBV~7Y-ycr2?B30%w?{wj)I1XQDeeBvQ(Hch zYj|_;IqVbIb6IZ8eBQbla~tNNjoY!h#67sL@+s=JEAm$XJ0j6n>@_Jk|8E_8QeO4n zb@)l})LBCc$?5g^?+<6X9XYPZa7BJAvRjecV!2%3cApekEf$kbFJEW5S2dBPR;9ae zSjPCj{dt!U0ez)=WD+?o!dRnQ`00Q5c6}W~&{gR!u*h8zh&bJ!ccI}{YkMA>et%f3 z_a2Kz&I*%U-*%B3)%HA}aCRGc_W00BOPkC+cXWu%Q3bR@cbP`c=o~r~8fhVHaY~{*xgOM9LyDTFww7C8)F2l$O zEmi+O5!IO#8KB7jEbg@I{J8a=EC1?$-ET!sC^AAz)w{#u>$}CR=Hi|^GD49L`u=cd zbRz!~*`LL2<>Io6yia6(BIgqspUC%kw&xJJp2+kpE}O+8w#8)=d7S_YMNTI&I!j%v zfg!4zvAA1X+)749CqP1rOKEXEi%d@BaTd3di#s-4x&;xd3nvma0TEi=I7W&l5;Uua z;j0U0b>Xa4IEP5ptgcwAD^{dtA~6$bnbn1}x@tul#*;8WgsRXZ0Tbz$NWQG@%2qdv z9Iz~tWOcI`K%ljn#lz}mG14xPbcvM9>Mm^cuw!*|$Z%N*6gid1s00XTb?K}woye|4 zZe?}pM22OpkunIMsza;mP~=lqmr`U^BBv4=m5b}r#qHsx?zJ2ue-ap=i;L^x;))E) z#dB*H*QLmvMCOD#95#%H$eLVSrvmsB`I5+%T-+i?rsU$0$i>BXsq3V}#Uql7Ygyz@ zE-uE%n?%+mawd^6xwvJ#xHuzMf_O?C>B@F-WsAhf#ie<1Xr*5#t{$AkjYyHwxO$KhSN%j->pZsza{##9Z+VMlpw4@0aJ?+p4X0AfswI@d_@42B3BVGrKm~fSqhsS7FtGH!Ye8PORAnB5)xj7 z2fDNAQjK~lk$iB3(xH|F`6KwGDz!*NL>eNJ5Vd9l&$(xeL_{2^x5LC{S|jlgX@^KU zM9Lu&4tMvAcVB+r-Lyt}!IKMz03$_WA<_zwRJgmc-rY0aJ-7@M(%n7d-4_gZPdeUR zS?{i_cekYhKf3#p{O)V=z#iRQX&rgQQJy*t@4jHTyY}8)d+%;j?`~7?u{!r<-ra5L zy~^uwcX>6m5kGtZ`Qm1knPA~Of2f~k6<_B7KjP6?n7>4B^#YXm zM6B3fT>1D@e4;Me3H#hh@*_SG>-HCyE$E>5gnLRT@d;15K#9Ir${ z#r%sKWilo436Fsqgoq!(n9CYL+DZoSkc+M5UjjtLpM3QXU&R?h@lo!QGX6<$M*fVi z^&vRNubCPTnfOz*>>3RvT8=3K5XBpeDdLC5ECUq$cnrm-f{mCWEuqsgI_%tggFZzV z0$Z-Yp35Olib07-Bb~$6slOZ(#4|c}+$}m26U2{<85Swta?OVl---zm*d+es(~2z^ z|CZ}NxTJW?moQLbygW>10PA>>{)8pu-n}?m7$oT+1 zDjy&$bX(9MO8oF|MdF7voPWzxKbDyDZv{9BO1$MO9Yh{)h43Q-M6YbIz!Sk%B<=#6 zM8+>Z5Juq?M&T6MyHlj^PM%#QKUl+Zegrom^T-D=?|dc)9tn!eJaR(h7lZW>wI3j3 zyuv6%VhBp~B19br$e7x$sDVL(;-ZcNWaKbI5@8D>HPp~Y{D@Du(Fb}Iy$E5)NizQA zpYW4p#K2(|B25IvyBsIUp!g>|Rb)pu{S%QSg5sm=XPMblAUU+pI12tk z=5Z{IKlxjcR3gRg(<;(RP<%3aVhL1QlCbjeR@i$_NWEf`cqRrV?0p<% z1AF8{xrFY=Q8wI0^dTmQr>JN-bU%_*4Tpq}iMPTCEMf2COdCD7n>WV+bTVVoLM-c!e^0xwe1jPj#sVpci&p7OcpDb^&WI&l= zJ+fK+@S%--7F#3nfiM7pJK|5yRvdK$VB|yK=`9vwVejM28b*`qQ$@oY5)4T%^MY zv|J965QE}9k27*?viYQn!*WpKEnmw)DR1$mkohIlJb|p(R7U?mf zz4$=b{y0U)pQ7KP?vX45AQW$fx_iD1w!->f!urQ?Is(dgE3AKH&-jynEAnR`g2XLGcen77dEe^=qj6)i=Z0vGhUmoEj_1@b{5b1N!3viM$$= zcq{yUoU`LkwGS{_{B#_yBU<+P7uhu^(cQ4?amo(tP<$XnAII(ZlZ)Q7ZU!5QA&4k1 z5^ww|KI+>MP~xLu;3Ern^fVkp;Ob5vic`PFdM`3@1p2NYk&A;8V;mBX^c(xXA;b{9 zXWaNw=zZkezzO+aN0tqWk45C!pm?t$vj)X`<+(N1kr4sJ?Xggt=g6-?ah@Zy#&X4n zGIDHCd^Xj~F4-)P`ylxdug3(5lv_g>@gqJOZXnWcV1=Tm)eX>c#Q&ax1H%*6A%I0- zh~lk?4CCw|(kc;2_x05BEDTbT=PicQ#hwEyH}qrj??4Z zsf+C;Vz`$G_!_h&s?V^~}xKYa7;Pk;aM7xv=2fd{%}J?RJj+aG@U;a`9H7fMYb0N$@A-bzpX z%a?!r>Ca!j`LBQb@}K|y%fJ5hN0ZP0_17=|@0Xwd>E|zh`9ftXJD{_X$y z*B}1(Z-4sJfBEY#|N7+c_Kg z=$!HLtKa|b_rFJI900{tlQQIg7;Y91%ZG6yaPKIi|38rx##FP{nx_f-H`_Qy{@R+{~`$jbij zl}7$1(ePAdsop;atsIv~v;q-NwBq`-B^#KYD=?Ex z8}L%rIO(~9sYtW}D$g|Yy*&-3FS}%08fPsDZF(AW?QCIFPeX~y;h-%Il#KSbz;^rR zD6UWEf`1B2SG|M(odaeMg5{;G4mOtJt0KfEH0k|bvIp~#W1zC`3mXb)c5+Np9 ziQf~ggehAZ0kQO#t!mLD_LD__35%t_up;~+xhzW{GUnwIFXf2VhDL>}{4QtW%Vu)_ zoEg;o9H{!TI_jT;R*tkJS}8h_XeBaFv{DdZOJnNFTqCNK{u1k@c~4|sLa5YU!B{0) zfu|)}0SzTuS;8b*SzsnwsU4AMC7|BWNV>>e!=G5}lAbFd+eBL|KbB}ESWC2$JIXZ6 z*(F+8WhYuWAeCrkg}0^Et=BD$Dg}9tlP>rRL$0JslBpum1t#|l4%x6Ct5kQmuRK-*M`P1YgXs@>phvwpWM&=b!kYqF&g*XeiF7Ly^>?^4iC`oT&>#)_y`zCkc5y3aY>Bwr9s z1z4GQF4de8t*j&ytt0`qG?LT83)yYqg`BhS!r52pFAh`mgZgZu=X6BIg^aKAQkL+E z=YlXwv;x}N(#X&XFNF8P3!e~PKqyLoK{ZN$IZdhGC>uhFms0&K(F%Gr(MpP9O9S2~ zwv+s&@JtR-<~=`S%?9v60Ab1ceYv!JN@eh}!D1ZGdO2+5Lk znWl0hW25{PIA?mdWW}~Lih0SqgBT8N)9;dhlQ97JCgaShJn07jIQ?!(X(#?lrM^Tf z$NUnl02((masZa4%ru$1EGsMxm7dczG3p__4@q{ukV_#ON%0B#s4- zEO9J5Ew+EO{YFLNWSaO;4shG%WO}Yt7*4dZ51wcR#hqxSBtfDT;A^6lGKGm&s+?t7 zUYl}~7PZMdbGA$5hU>|5jYH?Q4Y&OUQ&+~1Ff8jn8&INKNYXYxQhNnCnrH=#yQKk% zk#EqAtP5;vi9MyDr0oHx_R8_7L@Vbj60ICnNi_8zfYPduJ1q54^F zgh*MwX8&vprRNV#D1|0^8k-)aP*$ed22rAwqCbgNj?E=nNs}j9*`!UhQj{an%EnZp z={k=cMN6@#=gOh#Esgj}#uPUrV?W57p;@*ak=iSV&l0U{*e6;+Oe9(7W)7VHTi0DkSozg@rM~f3p<1jL5$}`)dd9DGakTo%SWLSdq zjZ$k6P^b#7j>s?^Lsl6gE)-Q6Wzy>sEijyP$3!ozLQb25(W+0+O zD8koG*bVfGr(76{?T3eE=R6q41kRe-j#hEHo|oL*wj>BG%jI-Yrso6 zg_inO4)$$n>_1CC*yogfpl&iZI9n|GLG3o_FW5-w2gn1NS5!{R^95Lx>NL|pc`k^t zZQlt0WZna9lzxCc6rNcqOW%ML3eT2A&hw8`yV4Kbw0@(&WfRY(Tz#UI8vR=u4p?La ztWUm+6A&8#NKeL(GpaH!7;PCpR7=JM+?@1{OuCEP>IIt<>2b@U8ne7D`Kf(>^8)-|XAbe3-vTcxU1UpI+>#?NEc`m+N z?p8_PEho>nG(tz=g=)@O_kpVF_)(`v<}THBWc&X1$ zNRjfa>o-z|+5eio%5f8@0>v(1iX|Q*nv~d#dqH#_aQ;a83)oJ025%;P<2EdPSJ!Hs z`&S(##i;MGP}QOfxGm`$KoTP&AOcF)pK?p4UAe@wMa0EiXQ3@t)=a3~m@>t<|WL&5tqTeki08)FpFGT1g zd2djv>XUI?!ZQw7`i7Aeo^c>Dejxaz9~9OQ83A<>Sq}1M?CRz<4&hp7? zj>A*iakh8!8V6b_kJq;ctsFK_v{GO`)AAaJcgsKREG{^p|%_3BL4=a^5`AO0ly< zD`#vIt<;N3v~uJy(IkfqFQw|y_BmXN^aJxOG9q6mG6xHmY#j!+94blQ5G*#&4dy8a z&Gj4QOigNUFi*kri`@9E^aI?Ne1pT+(hs&ElWk|$rIc~p@&epKzfsN=r{`pih+^?b zo>zc>WeyR(h;EUlNj~1ZR`wOpO1Y3NFDwUzXZ9dvjvJsl^Jkzop_K!E=^LeZexeZ| z%cPP#7qo$G;*71#7u(R2ehh35?pMZ-jVqBQ zP&yeGs@aQ-*epuC;NK*tfG?9hb*c;v{5cx}%3rxfd@DCwr^alcVG0w;O%)#q2;&}MoO)T^bLtcd9ED2PtTQ0RT8aS zT##txRQQ$#(^*DT=gRxZNL=s?M$5ToXo6>eCU^#Df?a^7aVINwu|uH8b^L%q$v6X3 z&GU+P#ST$PD&yfOAqZeE*%z#2Ufc6rxm0gErliNS?F2EA*Y>B38ZXynYwxWcvsF5Koe{zw1F;AHb~|R z+xyw~ST!J-gFdx@K@0FUrOj!bL>7&PI9)2+gb9VE?qL`iAU zOG@I1zHua6#-4i5GA_h_(l;PL%CqdDk(L)-Fq+ym(zenMQjx+7ah>o&RT$v~Qzzq0 zxD!(VtG!QZOVCI%y_99y`A^iFztuzzuC5n>JdXK{UygEV}(1CesGRJ#-2Q!@WOVU ze1i}~#tPh=exuZg-}aYHfBlA>>9VaNwUdK5kqRJ#_T)SXj;CDfyyb;xMaGJe7XDan z>2sxsUusYGlWiMp&|axEpWYq#1z27r!Lu8+xE+P4w^x1qJ=(r{;^vox&STfwC(O^jG^gZw&Paq zQgo2#L_b(HiXMTA)N!e0qPAntgw8s{bugm8NK)oK);S>G+SoVNYFmK!t@tz6QVXytOnEzQ8#nLj|~GS`?^GEWKrq`v@KWlYJ#iN7S4 z%JBi0Y2`g!Hlj8659B!S;qn}yyW9`*X39&cPo zD1UM`oD&UF9|T&tq$2TLsv9O6W;^l1Q8!uFl2Q=6OUVJ5yX+8%-NiL#3;_w+QpO_nmBM7TgMdYyf8(!Km~DGnDn0D&MlMWs6VNwZQacT}wT1 z9X}k6@}hZxGxgJjKPIJ&A7x^)&XCg5@#BoA^v#&g<3gTG#t+jZJQJ>EJty-jHU~(X z*c_UZ*pUnKB)>*+GKmwJY5EPqV&R4Sm5dAby~+5Izmjp`W;dCC42z5v`LJxWZL3$t zZ_yk8W!uGHa$G}ln}oX=>te`X@s~gXb^NG4Eq%i^3eVKCkiHG}G|39cI>3&E$UFsB zFY;jPM8=++meO=j)25p6$Cg9-E`hw*4MWW4wGfK0d#M~T79Ryo?L8NXDbJ<+_0|rt zjV(HJQNK#0C+kc2ll3{$m7ENy|BUe`pwV0bSpgkq*po5kz^?Gj{^fwCc8Fz#$ea|n z%s=)oliWZ?4fLD?QqmsgUU()~CG**;XJxy_cCEfEHNfDdl(F8*5)o;8vabP@U2v~P z%h*e39F)uaae0%Bi-CXTIo*#kT)nne>P&C@!FH6kr#VKfz3Lly{`{`xMYArjG8JqE zc^b)OKodX6nn`pEB)j+{MoMfux-a_1vRh(SG+JyYRW&5`;mDcHajqkk*paJAWqrxJ zl9wkfAoh<{fW#_zEy-2k-UOdR6ejw?ZoKT5vCAoO4p&vkngm+5lPGk#rgk%jxMEe} z64DkD3y^IW-N$u{jiuz2#G*it#5Z!ZPkb3PiA71PNbEyKLE-~A&i(@wfZ%P7rsINR z$aW0`fsPAX?>UYDEu~`xKvCN()vUK;&m_-wh@D=EH=yY};LKyT<5tEi@rMsBmD2m~ zn!n6(tYu@xUa@);JIQBttVo&X*o-un*hx6nvByp4_=?C|^@9{>wx%x z0X8h-M=nVE0iG+(EA*(`qPWo`bSC=%oaJEdl1Gwp0Sl(QDE^9zb=3djK13dcBF4C5oNA%k|*0@CXmLlb~HTeCGY05 zBX&Ae>=nyPiF1Huh#d!cF0m9*w)i|~5)W~lU2H6-SmH^lrsVld_EhY+=~0fU`6>Zc znrm!1NFD^5%2I1ea4bveeL>UQHQ6DF1y(kTvR@#3pX~wdNewzX4kj_7E$y{E^^HWG z*)}o3b$^Bvg`!7Py_9vRxxs9k*q#xa4Ndk4fZgcWR3^(Ph`YWNaV{_mdqEn z@}<9A$0g%O>PBqwg~JM2ZXAOYo^g#LU&4s&dsqeJb9V#+a#oN7cAA4?NM)VE4v_dV z3loKBj#9`NupKJ=v9&GZjQh~`G`CJ#Py34~)7X|(sO;rKQ{MwTQFs9*EPZ1SP5Oq&g%?BPOP-TD1fbE;OnI(c zowJE)*v3}dK8S~y;W94JWL&5&C374kv5XZb5F~%asXK|0*v*stm2Jo8zF7&sJXTmH zS-;t_7F|t9R>gkXCUcxMW$!Q-VqSJ_u4-+XH`* z7#Qy+`7mgb4`B=Ite*0L3>q}7_1zFSm{rZCHWj}Pil#B zz*Eorfp`+XKsHwU3$#LH$xWY$KSQhJ*vDeRoVNu%DcEVNDkOPeu2Ylsprw{%?y~2V zV>sATzd(LS#*h4vwpVT)-Okg&**-!zS;G<$stz)IX{;z8q37uFP1Ou2Pg z8yM7HzJWxfZ=|Yn%*tv@^2&^WZHexni+7ja9(AdKM40RQ@>qAY*{3%VTQW z*m(}I>=9j{RI&J4cKF2CVwpr9WJbi-LKB}%1R`rX>bHx&VO}KX!IrwjKF}maqAt7m zRZ`rt&%{g<+r(DB@3SFT)BGQnQ^#cx4}rqV@eu84 z`~tjK?GVOYbOxms9mEi&`G=khz670?vwfU6D_7%g{1&rH;$o`Is(ujX$T$-Z$^7Hm z7SUhg9FZF}5yajPu+v-stMdSeoQ#!45_#?tH>I&+7l(wRJU5u*RkuG+n?%8TY+5s}DMFsWD3Nm*fGdFE}$92r`Q#ic@8m6Wn8Sh zSsp7wd0Cs1?vy+OnSWV}vmGTd66tp3k7!x=s~6XA^qei>0nbvq5;T|M^f?MDc{oz9 zvJQYII%xILb9_KnO8dsK%B;V@`UFo1O=3c;nJ({Y-E%fW396G`;*CUCDI=_MBRti2 z<$f@}q32w!?zzlypkP^7Eln+Ri0$q)PXU0l$lmtD=6xQ6Bj#DwF6@>`TxUgLgcn?V z`o`S?%52v->MJo4S|o8D9!hnPW98D{WiZEqS_yv)v+}2P1Bj$l&)J|Bd9XK@?KmDv z`D2q$<{B!f?a3Mz2cR(=)gC0DV~|wQX_CIeAD<9;aHVqgTbxqL@?Z`L?$G8-9v6Tl zQZJm{09}`H`;5ds=0ellwRd%_xW+{LCU-pWu3{26u_E~#P6w!N4bGCWjU($fcEu$( zVi*00Jg$mA1<90s6osu>#cp*Kn&5q)X-zDSxJyh(xS{&X_M7mB!pWF&j8EnZ6Hw+0 zN~7&bT}dltE49ancGcEV`ABpXsF3JB&B<5++Z5fRw6MObdnZTN!l3B=u!j9j<7f6I%ZT7!_ZA72#TEk(6I)C{6j?J_k(4ZRn^w{{ z&VLAvvf_zn0#QQGEH?@pmp4u0tMxHP5C$+-xWpdXHb4c*_Y&t{D za$|2y_yfi*eFLPP?E$-Q;>)Z&oPI;&L_ocQM*^~_He%5@k#x5BEf8J<{^VW-fbqIl z1h7we*1KVNSJ#}j43+T%up)DfyJ2MfSc=O$)q7j`uHr1&1kZMZ47}Xc4NdEyvCAa; zLTornt^l_nF)K8+5o`iwe`y;6+BdCzVs*e}Tu8Vl8^Iix^_$J->@PtlYW)MNAujw` z0y^sgSImjrNP>z^ldzM%QM*Xy0VY=Th(v?-SI;kiNKknUa6!0ai9uMX$hl7zX0i?? zpj5kd(|T?eoXHpvWXhOQ4n@Y#svzcZ9@HPgsi;jP1uEl*fzkE`{uf2pcZu+Y7r8@_ ziK{qEj&vnEj@_iV^|rl2%XW>qOC-DfuFcathqxG(!&;l?ti62p#%Os@$!HnBX0%+> zt~{4wWuxW#Qbx;XH=)UyE_*K59W1n@M@Gv&+1lgqf9|i;J+kNWTHI(kPi?e(Uy;!$ zRwv)cXJzcUe4fN;`94i(b9sGfv|O{nXds)Vy?jS_ zc~{3CR^(mId2RKVcPZ{5ed7SX%yG6CwtXXUNN#D{9(ij(^yxW()46V=eK*%hFdB!+ za(_{C(GSTt*}EBsVli zp%NPbPFm&$XXZt|#6$88!Z7_tKBo!Kd$%lKV;F8d1e3sQgAd_c}$;X(6$x3$M%Rp|$d zPNC(q`u1*Ka~dtLFO8P5E`^rX#L%n=cK$A*ZMKPoNXZY{yZPQ+qvd^0qp?XZ-^ll* z+jBafNmUK_%XgylE-`6b^4wdh+ z6cYVlv5{?#l}^Yu%uf}d2Q!`(d68=y_&J{MoV(z-CJH0Lqk>N8@Sc&ykf=9K4N|jTAD-l zoSaDqgqQ6V&w*Ro%8jIt=8%mh<40tX<{Ix(-Zt|`8a??Bf;F~GvcCmQ?tOr! zGUBXdvWa9eIAE6hW+hhh+Ox&T|)BY08Zl(29dQX|56d<$N*Kb7Zd# z8cBfkE?d#bpA$2271&nyjV85Os0a-JE4PQz$h(B&@?AEsuaX<03RD=iqsl&u0FpUzc^dl#xp|k;HmlJF`Q;@;vD;w7dpmhX8D7<_}uRZQ4!(x|!s= zl9$ipjAhDp9Fv`MzP4K-`=-!x&V%PjnJ2peP0sv~v=v`wv>YShDp=>FegJD3JSS&X z>?V7e2UtJhk27a!eaQ|BaPKTPAeGrikr`pKCH|nvJRmB`=MAXzC-=iz^*^x(q^?C5 zh{jXyhJf`VF$lXyIGV&C-aPFOT1H0d6B;e$s_Z#At3}dQ>Ucqu`!#S{ScEMvM$6|2 zpvnCP&_oBB;ZjS*N`R&O6+t_IhGgq3i&&l{% zIjtO{Lz6RhB=7{=W3-$%GsJt=X=r&Ln_W7^CsImE&NvxOWQ3Zhy+GPyoswmVR_65< z$!v)Qj3#5idNu0|NGqwk3XN5E8hh5OB421(_nDEL2F>rvz8b+AelY1iPG9ccGg`*} zLL)>?-@yIKck$AB&1s*_>tv&43?DQ?^7IYXx-u?=@+nt}El?Z^G`YJAnqWww$z1_t zPdS{Cc(Ju<<_}uRhf$Y>1z~>9j*{i}>>}AbrXb35ego8;-0K0237z-@BPaR`9xm%I zlSMGc&?LTxCU*k@nv*yOTE^$t1!Sxuw)`0_>k%}RH9to@p2iBHNlj>0Wm5CZXn9<0 z^_FcS20}0$(B$k0Wp-G`CjOvh+hpZ0)v4YDyGw(Ca*2$R{XgQ9+d9Wl*?GYrUJs|cZ zDV~tWfOT@#HO!7U@qz)wEBwi|ggAh~t2DV$;gO+_QCY$IswZ~3YwtrTBBg+FXBWnX_ z63dgwkhLx}9M|@{R*oXu1IjZTwi?X2VQ5mDim;e0XyOH0UQb`S zj%7g0dywST`2S?X@bGd6l+kir0!?BS4!bZ~i9ahFmFAkc#ytOwmh}Ug8hzfhJ?m4(@;^`2!q))Un}^m#kNf zCi39?$ABj5c#gmzk<>R%GRi!Fme(#NE@)CemPyZ{ z(cB(IDU*yi7M=I)$v%_!NwyQ3tihnk*%oMW2Og*27+S{L@*Hj^*$sBpNRlO9pyhnAo#GqN$Xe!iv5N8yg4X;E zk~ew1fOV1c*wAoO=^I#~touC2N;yAAo-5B6wykoWhJ}@!A%Z6U4Vs+UgEruWxtqs@ zw5Z4kOPu$BI7J}-(vCLg^Mlaj%o((d*|FjR7|J{@6p_sHh15gd#{tzR`@hiSeiuXk z<$Mk_PKxC3Ql>*>q-Rk$%$aeA^n=Bf^aGrNeuJto@?FX0lAw^fBpZy zd}1T>^35NA`so+@1uviec=+Z&{rH!k?4PJ)`R8vxefQ~C>+v7HJ^qFh7calHwbb&P z)3*dwzyJ1l{5@0ce*g91?MFU+fB5FNKmF<3|NiAq|Ko3e|DT_}x%}?(Epzf8&g<`g z_wDc4p8MVJf6qVv;WxM6{O)r3_H_95uMdCw<%get`CmVO`OCpN$VK&sfAepC_n&`% G`2PVke{c=} diff --git a/present/ripwire-showcase.pptx b/present/ripwire-showcase.pptx index 9d59e8ea5ce7e3d392af9aeddc72cba6451dfb72..9d4f92049dd68a1face20a362d80f61e82730b1c 100644 GIT binary patch delta 6100 zcmcgvX;_uj7Ctka5jKh_C^)5pC}N0W5F9}8LW+dR6)_Yv#1zq-2hzgIG*C~uOChn0 zYffocA6H~l0pTDZvjH9u0YwlkA8!tLzq9r_Jut@Xa|8umVWU1|@! z)E=mw6D*b}1jhe%#rVRjC4LCTiwbG^Zx|bB?>2_udS|JXa(y;p>b_a%X5giu1{nRn>Hd7i8DgKgpSs_ zlOkZ%6^R(w6VZ71<|uYG{>@w{?(v7kteNkNH2luO(fKQ6{&Io9Urq~@WLD<*U>mu) z@xzZz=ln8*<|c_0FVA(IJbm)C>8{gs4fBFu_$OUV$UvPod^kWZ30DDm<~#9E+0L4j zAOfHA(u0M@g--odJ8b;&RiqdeVHw>bSZKdZ6oR1by4d%&xC#VYTiD0NhQvix9{8lg3PkJ{bP-IoPMBCE&{8@d`?7NR*J`PVjJOM z8SHJespY_v+Fd3J&M-_^3;tY|gD^810OH@{0bsQ`N%$|86A#t=MsVXYl7)>v|4!<2M&9y(CkMRkzX~lGvhIm6c{$kiNv9lUzQJ#a z+L)F}%S5Hj<5|22JuFv?o<#tPrCx;khF%pNk;7hoy~(b+QMADW_GS$bV+)8t%I=0KVBUTVbMO`m~&bq`BA9bK2=5!D2WwKFq0?%eRz*Ja4@Wv zOC=cTHZ#damYdG1@6XdS>8g>O!x~9D%Z+TUS{)0#X~8n&Juyr6xfOVrZ8Bfac(+~S^HmWngM9JiFGtH5`GRO! zM=Tg;g`BGfa%xdclr;NS;Z$Av%z2z;s@zdXo%OO&a7L_!NroI-fgs10lC_c%T%um4 zw*>{WI%QVuVDB9fyZhldf#Bgd+WJ&Jh(50-GH%yx+4FHM%*!t&($JamQ8cKAuS|}W zSq$_0*DK4`CG!_TvYcUuSevD>?z(4hW+hS^?*>uCpctE2fxw1V-INa2y*ot8jUQn) zf+Zp3BQs3G!jH}Ltbv{P@vb0Ht26Vs2CHpL&Czamtht^wm#@{|Y*>2t#G7M!S0$Jy zq=M%8J$cJmY2<1-mZnc5M~*yeIfm|~&l}@EiNDh(LFn8=8~NNx!1>xt`Dg4_FrU=C zfbNYoAIn(=%hMUV#TWbI82hbIIkK08$v+4L-_S#H)Ekv%;+%NY#M%CdyLM%;K(I2{ z$Z5kZ&5&;m2Gu&zW{pwxC{Fc2ev@4b(>%8J8QI5h!+wz;VL2*z%wqvl-BbD2>)@Mx zV#z%4rG_THdEyO<-}=ExNK+aWQu>*a41kja4izz>F$_4lQE`NYwRmZSE#0wJIe}YZ zP+(YrZ3A?#bPTs#jD^r>58S%Ap~K0r;K@`V6(={_&q z6kz|r?(BR}Mhylcqgm^fo?JqOGKxjoIVz=uMJlON8d$1xHcWQq?$#@*TI4>7miKEs z=%Po;8Pums6vwUTP;OvpXrB`8%|g)(QehgT+SF2(28Y2aOy+2f-4>vrtJ?T;^W0ul z$9SI4_$ZTW9y%SYhcaVN@PGBx$h*-)qKuUaKRoJ~2@ha@z z?UimNVSM+7LHF8l0ST&T*1advRZ7;q@1Bj!V`)fk=hG0Jpz`D@8x<-kH~E+Z_32_zGvU<@O-xOsT8;7cY^EDoyelo#Savc0PvZz-oR^(Y8#PM8{lbBN$UpgR_SJS5x9M<6isp{|3~HZ;AH zNVx@(YT^Sft5Vf?#iBGB{yufYIZIfGk zw^|LaEH3ehiSy7uWj}X#3j`hBz{&maO#RRs{7!jkkS^O>gIAE(`!VZ`mw+>u9iVAt zWjZ`lv++F${Z`yHKM0-EmTB+`8dt7Kf~OhxU9AS=bdQhCN(>hW62lp1N4@6O7`_2H zz5z2P0aT&tQ6Ym$bFpY=H47cNA&};KYsPYCTrKccdfYPo3KNtx%>o52n{E;H8lc?y z=Jr8W3uP}+a!)_7Xk<_s8zX7}ibnPFIhOCTIK&Q#$IquXGrWcr6W)b=7S;B|c(u6Va-n6qxkGu+o20!8siy{8x73ctC z@HEn&Ooi22{R)0p85bd8unro#@ML1x3EYX-tQ1>j;u00}pK;)(*uCKi_O zOfu3Am^@t*Yv_uYzp${;uZ|V{-@>~6kHWec=aN5LSkV_GlEuCFpsBE)spKwQ2n~~|#3)pR7opS-L!RVpMs4ACtamyhv zvr+z=Cvd}dmT8+Ou%pj-aQFrQ?zzYU;OlZ<1V3f^#-sRx%b+~+3M}U45iKk}&%-m( zn1|x6^6@ws=U)0oJ|3r(d=dm6P5F32>y%3-9hb-(;O};s_&^!c!_8f2zMl8r zf0@8TfnL6h*f6jH_JktDUS5RQr-~5!NfBD-1?^E%u#PE4>-1t|fdLg*`iqgpwFEUK zmY}A(60~kOIlh=_a=U`ou~(4AYwM?VjHTiVvZzW?Q%EUlN-ITc7*Iizv<$6%%aFyr z(C!@n`IZC4d@g#+uvv%eQQSs7l4R?VL|%?0u6HjMF-fb-k>qqa(L#D* z`gE<}A=Ov$R@YuNRply~2gT^RijC!5ftBA>b|CM=4Z?J z3k-4)!R01ofl&=|Ft8a}U{V8}(ap$`-i!z^rvbwAW<-E74G?Cx;3?bDg3Q-jP@Vk^ zRJZa5s>{BCHUkZa4O19kzhgjb7_%sa7mg zYb%nvv=KJ(8RsPWNgLjs`ZiQ&cN1&1)S6neOefvM92DL}or(poFJ!eE(T+M{5(1qG z?Whw5AJCcGjtEcNF&jR&kT2yH3TwWF*>LYb78q|JjL$ldMP9brk#$6S2eR~^epJMC zPVPjOgib_A+kgKA(^=7pi4b=oUuYNd9q&R-yo(f#zKu5Rw-Iswo26?QqTe0V z)W0wCJkzxQ4r+qg1tK@xK~47EhyardM76COHJ$B7zUSRo9OKwR7v9C l^cV~m%lgP%xOemVFaq^`{7ry2>Kpg*`^o$G{qcPg^lwq2$;to# delta 6105 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 From 6bf9d9d024e0c50a13dc78c77480f7fcd02faf10 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:15:59 +0000 Subject: [PATCH 020/170] sync: re-pin the parity manifest for the train-25 help lines --- test/printf_parity.manifest | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index a9101cabf..d15742510 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -1,4 +1,4 @@ -flagless 0 19a4b1530abe359fb650db91a12393aae9b8269d1508fcb3bd399976eb4bb59a e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +flagless 0 22b42aea387b3ba54ac0921ce1caed39aea23a4947579eec2276b72ad77330e6 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 lint 0 a3d29cee7631f2904dfeaa947f091d3e73181d325e6ddedd87a68580d5717ff5 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 lint_sarif 0 805e375b06b7755bcc6423607378e557ee82fef787cf09c34ba5c066639f4d7b e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 lint_select 0 740876ae2df8dbdced07c406c223f545e92a719d8bc3dee1cac56a85207f16c2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 @@ -10,14 +10,14 @@ callers 0 31971e419a6df4d59c4464fcdd1afa56c6125e4fc76d096f0203c80cedd9a4a8 e3b0c impact 0 c48ba09589d13cad6011bd6fadbb2898d68e2f5bd97a10bf44324901cfa08d49 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 clones 0 a082f92ce84cd952ced8341e3faff33a23685729bfa4ab6f0eda0f7867e41d7c e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help 0 c74a677dde09eb76a71bfd0b9e2c75d074c47ecfac838654f84df80af6725b02 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -expand 0 4ac45b31a8ed0fe3c2520e9e24aaa4dc8e73e0479ee326499f4b760bc4ab10cc e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +expand 0 f3e60d37ab964c4772d794326420441e0b027d1970ea5fb0c60efe6c89f808b8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 callees 0 cbd133fa53917bad05926d46615d4d8b4457ba9285513dc4b193fa57a1ef8b85 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -around 0 77cd3047066cce2bc45aeea739008ebd5a0045aa64cd9fed59ed4a277c62ad88 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +around 0 ff0b962658da396e7bf3360584de21ab2f0e8bebcd8c0e4e997a77994b004a9f e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 uses 0 5442767b638c8cac9eb0327cbfd5fde73f92fde5b85e2a995670a3982d903711 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 path 0 fe032ed241174b3892bd9369cbc3d3b904d9e6f681cc307c27c476b6a28ea59e e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 connect 0 6ceb99d6c5ecb9f57a1b7426da2ed2118705cb7cc6b3d0aa4456b84c0ff715f4 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 grep 0 ea5c66f3062d3ba805f271d18b146ee411f3b132f75d0c77062ecf2eda13f36e e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -pack_signatures 0 ca92eb613ac5d24a45eaacab5cc418afc45f0a8d8741e193067db2e509ca3d88 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +pack_signatures 0 91446d0fc143c8d08605da1a3e7430834de105b15f0d2658f675c6bc3bf641a8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 pack_task 0 f3de4fc3a28342cf515fedb8a02de0fea7f149ce8e948d39a7037c955a9eafd1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 arch 2 45b12ab02e3e9f2e32bf92fe0e1cef7b489e70711386e0749d50281743aeeec5 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 seams 0 069a723391d0a5687bdbe23d250a5dc901791fbcc676e5ac7435e9017cc30478 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 @@ -38,5 +38,5 @@ safe_delete 0 8b99d83566b89332ac4b2967abbb39284db775bbed601628b97b8c7909f04ac2 e verify_layer 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5288c345d7d6e335f88b9c1daa8935db22e1dcf89c0c8bc1f6140d4cb5af0b48 graph_query 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 bfa4319feb9dee09cfbd3991cf6fbd752297e99d14de9e75768820d2a9c8832f callers_limit 0 e40ee1ddba0fbf548c58c98b91fe052af8c34f8913edb3edd85245b12a6967b9 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help_all 0 ef34f3b324720e3266c083e867467a968fc17339ba67b1a53c8ad8026adec791 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help_all 0 88a30964a99f00d7b2b95c56f64983abf558dd21a6a9b24fbec165ae258cd1d7 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help_one 0 938ff4f8802a8f5d9cb10f9a1f3804ac755be44c8ab00e24e45bfe8255224a21 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 From 0a8432a15d2e780f551e09f24defb646f9ef5cf6 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:44:07 +0000 Subject: [PATCH 021/170] sync: regenerate derived figures from the merged binary (train 25 rebase) Re-capture the showcase against 2026-10-07's merged tree, regenerate docs/COMMANDS.md, the deck pptx/pdf, and restate both [Unreleased] tools/list figures to the live merged-tree measurement: the paging schema takes it 46,732 -> 46,916 B, ceiling 46,750 -> 46,950 (the valueRefs entry re-pinned to the same live figure, per the gate's mutable-[Unreleased] contract). help_all and four more pins re-pinned for the train-25 help lines. --- CHANGELOG.md | 9 +- docs/COMMANDS.md | 1021 ++--- ...-03.md => COMMANDS_showcase_2026-10-07.md} | 3275 +++++++++-------- present/ripwire-showcase.pdf | Bin 545141 -> 545141 bytes present/ripwire-showcase.pptx | Bin 1176132 -> 1176132 bytes test/mcpmanifestcheck.sh | 7 +- 6 files changed, 2244 insertions(+), 2068 deletions(-) rename docs/captures/{COMMANDS_showcase_2026-10-03.md => COMMANDS_showcase_2026-10-07.md} (76%) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd423ae19..568864bf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,8 +96,9 @@ call-shaped references, so on such a function `--callers`, `--callees`, `--impac - **Cost.** A cold default map costs +3.6% CPU on this tree, +6.3% on django and +5.2% on webpack (median of 5, `sim/refval_cpu.sh`). - **Cache.** `kParserVer` moves. -- **Manifest.** The `tools/list` manifest grows 46,591 → 46,732 B: the two find descriptions name `valueRefs` as - not a proven call. +- **Manifest.** The `tools/list` manifest grows 46,591 → 46,916 B — 46,732 B of it here (the two find descriptions + name `valueRefs` as not a proven call); the rest is the paging lane's schema (#362 below), re-pinned to the live + merged-tree measurement this branch ships. Gate: `test/recallshapecheck.sh`. It has 177 arms across C, C++, JS, JSX, TS, TSX, Python and Go: - positives; @@ -1111,8 +1112,8 @@ the CLI verbs page `verbs_for.h`'s `computeLensRanking`, `ranking="for-lens"` rank slightly different candidate sets, so `ranking=` says which list a window pages), and the MCP pages carry no pasteable CLI `next=` handle — a CLI continuation would walk a different list; the machine attributes (`next_offset=`/`limit=`/`next_tier=`) carry the programmatic continuation -through the same verb. The `tools/list` manifest grows 46,581 → 46,765 B (measured against train 23's -`main`, re-based after the sync), and the ceiling moves 46,700 → 46,800 B +through the same verb. The `tools/list` manifest grows 46,732 → 46,916 B (measured against train 25's +`main`, re-based after the sync; descriptions identical, the delta is schema), and the ceiling moves 46,750 → 46,950 B - the one sanctioned case that moves it: the paging schema properties (`limit`/`offset`/`next_tier` on `for`, `pack_task` and `explore`) (`test/mcpmanifestcheck.sh` arm `(1b)` asserts the figure against a live measurement). diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 752260367..962820a34 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-03.md`), trimmed to the first few lines and +Sample output is lifted from a real recorded run (`docs/captures/COMMANDS_showcase_2026-10-07.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. @@ -68,21 +68,21 @@ _Same map, capped to the 5 highest-ranked symbols._ ``` $ ./build/ripwire . --top-k=5 - + - - + + - - - - - - + -... [8 more line(s); run it to see the whole thing] + + + + + +... [7 more line(s); run it to see the whole thing] ``` **Shaped by:** `--token-budget`, `--for`, `--recall`, `--graph-query`, `--pack-signatures`, `--expand`, `--from-trace`, `--run-trace` @@ -104,12 +104,12 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ ``` $ ./build/ripwire . --max-tokens=1500 - + - + - + @@ -136,7 +136,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`, `--recall`, `--handoff`, `--pr-context`, `--from-trace`, `--run-trace` @@ -181,19 +181,19 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` - + ... [18 more line(s); run it to see the whole thing] ``` @@ -225,14 +225,14 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -257,18 +257,19 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -... [20 more line(s); run it to see the whole thing] +struct FileHealth +enum class AstWalk : std::uint8_t +... [19 more line(s); run it to see the whole thing] ``` **Shaped by:** `--for` @@ -290,16 +291,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] ``` @@ -322,17 +323,17 @@ _Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothin ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --adaptive - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth +struct FileHealth ... [20 more line(s); run it to see the whole thing] ``` @@ -354,7 +355,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 - @@ -386,15 +387,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 - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) - +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - + H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… ... [21 more line(s); run it to see the whole thing] ``` @@ -417,7 +418,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -447,7 +448,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 ) @@ -476,9 +477,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" — 113 relevant of 209 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5489 +ripwire recall — "quality delta gating exit codes" — 113 relevant of 210 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5489 -━━ README.md (relevance 7.944) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 212219 B; lines="2688-2700,2701-2708"; dropped_by_budget=23] +━━ README.md (relevance 7.966) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 213934 B; lines="2707-2719,2720-2727"; dropped_by_budget=23] #### 6.2 Exit codes This is the part to wire into a script. @@ -541,25 +542,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 1440129-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 1438117-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.127) ━━ [sections: 1 of 155 selected (185 in doc), section-granular; whole doc 534551 B; lines="504-529"; dropped_by_budget=154] [truncated: 1854 of 4257 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base +━━ commands.md (relevance 10.246) ━━ [sections: 1 of 155 selected (185 in doc), section-granular; whole doc 533554 B; lines="3654-3658"; dropped_by_budget=154] [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` @@ -575,7 +571,7 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - + @@ -646,7 +642,7 @@ _Stable (path/id) emit order — provider KV-cache hits across re-runs._ ``` $ ./build/ripwire . --order=stable --top-k=5 - + @@ -656,11 +652,11 @@ $ ./build/ripwire . --order=stable --top-k=5 - - - - -... [8 more line(s); run it to see the whole thing] + + + + +... [7 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-stable` @@ -677,16 +673,16 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable ``` $ ./build/ripwire . --no-stable --top-k=3 - + - + - + - + @@ -708,11 +704,11 @@ _Ego graph around one symbol — depth 1 BY DEFAULT now (the root's depth= says ``` $ ./build/ripwire . --around=rankGraphTeleport - + - - + + @@ -743,7 +739,7 @@ $ ./build/ripwire . --callers=DoesNotExist ``` -**Shaped by:** `--no-route`, `--callees`, `--uses`, `--impact`, `--expand`, `--edit-check`, `--slice-flow`, `--at` +**Shaped by:** `--no-route`, `--callees`, `--uses`, `--impact`, `--expand`, `--dead-code`, `--edit-check`, `--slice-flow` **Caveats (stated by the binary):** @@ -762,8 +758,8 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - - + + @@ -793,10 +789,10 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - - - - + + + + @@ -827,19 +823,19 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - - - - - - + + + + + + - - + + + - ... [17 more line(s); run it to see the whole thing] ``` @@ -864,19 +860,19 @@ _Names referenced but never defined in-corpus (stdlib/third-party surface). The ``` $ ./build/ripwire . --external-surface - - - - - - - - + + + + + + + - - - - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -899,12 +895,12 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - + - + ``` @@ -921,11 +917,11 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + - - + + @@ -947,19 +943,19 @@ _Transitive blast radius — everything that reaches SYM. NOW carries shown/capp ``` $ ./build/ripwire . --impact=rankGraphTeleport - - - + + + - - + + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1030,8 +1026,8 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - - + + @@ -1068,7 +1064,7 @@ _Which symbols a TEST FILE exercises — the reverse direction of --affected._ ``` $ ./build/ripwire . --exercises=test/regression.sh - ``` @@ -1091,10 +1087,21 @@ _Mid-task situational report for the current git diff — recorded against a DIR ``` $ ./build/ripwire . --situ -ripwire situational-awareness — 0 changed file(s), 0 symbols in them +ripwire situational-awareness — 1 changed file(s), 1 symbols in them root: . -at: 52c4a428f+dirty - (0 changed files — working tree is clean, nothing to analyze) +at: 6bf9d9d02+dirty + [1] blast radius: 0 symbols across 0 files transitively depend on these changes + counts_floor=1 graph_ambiguous=10271 graph_unresolved=12861 graph_unindexed=237 (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 missin … [line truncated: 46 more bytes on this line] + [2] tests to run (1) order=evidence: [changed] you edited it, [partner] named after a changed file, then hops asc (1 = direct); "(n): a, b" = n runner-less files sharing that evidence; a (run: …) is relative to root: + test/mcpmanifestcheck.sh [changed] (run: bash test/mcpmanifestcheck.sh) + script_gates_unmodelled=729 — 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 (24) window="18mo@HEAD" commits="3919" (showing 8 of 24 files — shown=8 total=24 capped=1; next: --situ --limit=24): + src/mcp.h (co-edited in 54% of commits) + src/mcprefusal.h (co-edited in 50% of commits) + src/mcpverbs.h (co-edited in 50% of commits) + CHANGELOG.md (co-edited in 42% of commits) + src/cli.h (co-edited in 29% of commits) +... [4 more line(s); run it to see the whole thing] ``` **Shaped by:** `--affected`, `--test-gate`, `--legend`, `--limit` @@ -1111,21 +1118,21 @@ _The continuation packet for the NEXT session: disk truth (branch/sha ``` $ ./build/ripwire . --handoff - - - - - - - - - - - - -... [1 more line(s); run it to see the whole thing] + + + + + + + + + + + + + + +... [25 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -1146,8 +1153,9 @@ _Pre-PR gate recorded against a DIRTY tree, so the obligations below are the wor ``` $ ./build/ripwire . --test-gate - - + untested blast radius ; exit 4 while either exists. tests_capped=/untested_capped=: 1 = cut. counts_floor=1: every count is a FLOOR, never a total. graph_ambiguous=/graph_unresolved=: resolver gauge. graph_unindexed=N: N files no grammar could read (the map header's unindexed=); their calls raise neither gauge. at=: commit+dirty+shallow. root=: p= relative to it. next=: the one pasteable follow-up. impacted=N: symbols that transitively call the change (changed symbols excluded). shown_tests=/shown_untested=: t rows and u rows printed, two independent counts. script_gates_unmodelled=N: test/*.sh runners in the corpus, a path count; not call-graph modelled. script_gates_registered=N: shell gates test/regression.sh registers as suite members. script_gates_mapped=N: registered gates with exact dependency evidence (literal paths or RIPWIRE_TEST_DEPS). script_gates_unresolved_dynamic=N: registered gates with no mappable deps; they may cover the change unlisted. 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). --> + ``` @@ -1172,7 +1180,7 @@ _Literal trigram-indexed search. Each hit carries its MATCHED line as the $ ./build/ripwire . --grep=DISCLOSE - + - + - + - + @@ -1474,7 +1482,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1508,12 +1516,12 @@ _Body-elided decl skeletons — recounted on this corpus. Measured as element by $ ./build/ripwire . --pack-signatures --top-k=10 - - + + - + @@ -1541,7 +1549,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)" ); @@ -1568,11 +1576,11 @@ _NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map ride ``` $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport - + + - - + + - + - + - + @@ -1744,15 +1752,15 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - - + + + + - - - - + + + + @@ -1781,17 +1789,17 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - - - - - - - + + + + + + + - - - + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1809,17 +1817,17 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + - + - + ... [17 more line(s); run it to see the whole thing] @@ -1840,15 +1848,15 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - - + + - + - + ``` @@ -1873,7 +1881,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1908,18 +1916,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] ``` @@ -1939,18 +1947,18 @@ _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] ``` @@ -1976,13 +1984,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] ``` @@ -2007,7 +2015,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2042,16 +2050,16 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - - + + + + - + - + - + @@ -2079,19 +2087,19 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - - + + - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2114,11 +2122,11 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - + - + @@ -2147,16 +2155,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - + + - - + - + - + ... [22 more line(s); run it to see the whole thing] ``` @@ -2179,16 +2187,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] ``` @@ -2205,19 +2213,19 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - - + + - + + - + - ... [17 more line(s); run it to see the whole thing] ``` @@ -2234,19 +2242,19 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - - - - - - - - - - - - - + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2272,7 +2280,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2302,7 +2310,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2328,7 +2336,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2355,19 +2363,19 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - - + + - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2464,19 +2472,19 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - - + + - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2550,18 +2558,18 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2581,7 +2589,7 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - ``` @@ -2606,19 +2614,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] ``` @@ -2643,16 +2651,16 @@ $ ./build/ripwire . --report # ripwire architecture report -2381 files · 23348 symbols · 33292 edges · 2252 modules (10730 call-graph isolated) +2484 files · 23824 symbols · 34201 edges · 2316 modules (10908 call-graph isolated) Root: `.` -Call-graph isolate provenance: 2850 declaration, 1549 header, 1842 source, 4489 document; 0 connected Louvain singletons +Call-graph isolate provenance: 2886 declaration, 1578 header, 1922 source, 4522 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 2252) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1243 symbols -- **src::append@elixir_resolve.h:111:4875 [resolve,add,collect]** — 1086 symbols -- **src::formatTo@infra/emit.h:141:8856 [read,close,run]** — 661 symbols +## Modules (call-graph clusters; showing 12 of 2316) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1366 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,collect,add]** — 1126 symbols +- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 563 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2669,13 +2677,13 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - - + + - + @@ -2700,17 +2708,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
6867"] - n102["src/infra
1275"] + n101["src
7050"] + n102["src/infra
1276"] end subgraph sg1 ["test"] - n103["test
4792"] - n294["test/fixtures/recallpassage
314"] + n103["test
4844"] + n328["test/fixtures/recallpassage
314"] n280["test/expandmodefix
151"] n282["test/expandsibsfix
149"] - n424["test/rubyattrsfix
85"] - n361["test/massfix
77"] - n326["test/javamethodreffix
62"] + n458["test/rubyattrsfix
85"] + n395["test/massfix
77"] + n360["test/javamethodreffix
62"] ... [17 more line(s); run it to see the whole thing] ``` @@ -2729,18 +2737,18 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - + + - + - + - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2751,7 +2759,7 @@ $ ./build/ripwire . --owners **Answers:** list internal functions with no caller anywhere in the indexed tree internal source functions with no caller found in the indexed tree — a name-based graph reading, not a confidence score (dynamic dispatch, reflection and macro-generated callers are invisible to it); -=DIR scopes to whole path components (dir or filename) and REFUSES a filter that names nothing indexed. A symbol whose definition is produced by a SELF-REGISTERING test/benchmark macro is never reported: doctest TEST_CASE/ TEST_CASE_FIXTURE/SCENARIO, gtest TEST/TEST_F/TEST_P, Catch2, Google Benchmark — a static initializer registers them, so a name-based call graph cannot see the caller and every one of them would be a false positive. Extend the list for your own framework with `.ripwire_config`'s one key, `register_macros = NAME[, NAME...]` (one directive per line, # comments). The exemption is DISCLOSED, never silent: register-macro-excluded="N" rides the report and prints even at 0. Exempt from dead-code only — such a symbol still participates in clone detection. A LEADING ./ anchors DIR at the repo ROOT (=./src matches only the top-level src/ subtree); a bare name (=src) matches that component ANYWHERE in the tree, including nested (test/fixture/src/…) +=DIR scopes to whole path components (dir or filename) and REFUSES a filter that names nothing indexed. A symbol whose definition is produced by a SELF-REGISTERING test/benchmark macro is never reported: doctest TEST_CASE/ TEST_CASE_FIXTURE/SCENARIO, gtest TEST/TEST_F/TEST_P, Catch2, Google Benchmark — a static initializer registers them, so a name-based call graph cannot see the caller and every one of them would be a false positive. Extend the list for your own framework with `.ripwire_config`'s one key, `register_macros = NAME[, NAME...]` (one directive per line, # comments). The exemption is DISCLOSED, never silent: register-macro-excluded="N" rides the report and prints even at 0. Exempt from dead-code only — such a symbol still participates in clone detection. A function a table, field or argument holds as a VALUE (--callers' rows) is not reported either: value-ref-excluded="N" counts them, absent at 0 (matched by name, not a proven call). A LEADING ./ anchors DIR at the repo ROOT (=./src matches only the top-level src/ subtree); a bare name (=src) matches that component ANYWHERE in the tree, including nested (test/fixture/src/…) **Try it** @@ -2760,7 +2768,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2824,8 +2832,8 @@ _Recorded against a DIRTY tree, so any row below is a real regression in the wor ``` $ ./build/ripwire . --quality-delta - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). --> + @@ -2861,8 +2869,8 @@ _Recorded against a DIRTY tree, so any row below is a real regression in the wor ``` $ ./build/ripwire . --quality-delta - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). --> + @@ -2899,7 +2907,7 @@ _The Delta Maintainability Model scalar for the WORKING TREE vs HEAD — recorde ``` $ ./build/ripwire . --dmm -

@@ -2968,8 +2976,8 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. r surface=: the api-surface tier, new-symbol or contract-change. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. r sev=minor: a small numeric delta, counted in minor=, never gating (absent: major). r origin=new-symbol: the finding is on NEW code, never gating (absent: preexisting-worse). sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). r members=/tokens=: a duplication row's clone group (member ids) / their shared normalized-token count. --> + @@ -2999,14 +3007,14 @@ _Fast per-symbol post-edit contract check vs git HEAD — recorded against a DIR ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - - + + - + ``` @@ -3053,12 +3061,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":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"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,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3074,12 +3082,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":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"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,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3125,12 +3133,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":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"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,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3189,11 +3197,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":9974,"graph_unresolved":13331,"graph_unindexed":231,"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: 485 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 486 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":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true}}]} +"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3238,19 +3246,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] ``` @@ -3275,17 +3283,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] @@ -3329,10 +3337,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:4647 +$ ./build/ripwire . --at=src/graph.h:5567 - - + + ``` @@ -3355,10 +3363,21 @@ _No-LLM review-evidence bundle for the working-tree diff — recorded against a ``` $ ./build/ripwire . --pr-context - - - + + + + + + + + + + + + + +... [18 more line(s); run it to see the whole thing] ``` **Shaped by:** `--test-gate`, `--from-trace`, `--limit`, `--map-diff`, `--index-out` @@ -3381,16 +3400,16 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - - + + + - + - - + + @@ -3465,10 +3484,12 @@ _Which refs of the one branch `main` (no ref family left to select on this check ``` $ ./build/ripwire . --stray-content=main - - + + + - + + ``` @@ -3494,8 +3515,8 @@ _Select the genuinely-unmerged refs of the one branch `main` (no ref family left ``` $ ./build/ripwire . --stray-content=main --plan - - + + @@ -3530,7 +3551,7 @@ _Cross-branch ABI-break gate over the one branch `main` (no ref family left to s ``` $ ./build/ripwire . --stray-content=main --abi - - - - - + + + + ... [25 more line(s); run it to see the whole thing] ``` @@ -3583,7 +3604,7 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat $ ./build/ripwire . --flags - + @@ -3664,7 +3685,7 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - @@ -3699,17 +3720,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + - - - - - - - - - + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3734,17 +3755,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - + - - - - - - - - - + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3768,18 +3789,18 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - + + - - - - - - - - - + + + + + + + + + ... [18 more line(s); run it to see the whole thing] ``` @@ -3804,7 +3825,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -3839,8 +3860,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:4647 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4688 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:5567 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:5608 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 #3 0x102e01a44 in main src/main.cpp:3983 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) @@ -3969,7 +3990,7 @@ $ ./build/ripwire . --pack-task="add a new output format flag to the CLI" (run= when derivable). 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). root=: p= relative to it. : rank N in this ranking, rows in r= order. : cy … [line truncated: 546 more bytes on this line] - + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept claude's `mcp add … -- ripwire --mcp`: the flag goes on the end of that command line, ahead of its newlineinline std::string wrapCliAddPost( const AgentTarget& row, std::string_view toolsArg ) @@ -3996,13 +4017,13 @@ _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 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 ) @@ -4029,7 +4050,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - @@ -4104,14 +4125,14 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - + - - + + - - + @@ -4138,14 +4159,14 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - + - - + + - - + @@ -4172,7 +4193,7 @@ $ ./build/ripwire . --skipped - @@ -4258,16 +4279,16 @@ _Rank by git change-frequency prior instead of PageRank._ ``` $ ./build/ripwire . --rank-by=churn --top-k=5 - + - - + + - + - + @@ -4293,22 +4314,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~9 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~11 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 + - - - - + + + + - + + + - - -... [5 more line(s); run it to see the whole thing] +... [7 more line(s); run it to see the whole thing] ``` **Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` @@ -4369,8 +4390,8 @@ _The same gating report under --legend=full — same rows, same exit 2, but the ``` $ ./build/ripwire . --quality-delta --legend=full - - + @@ -4398,7 +4419,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=3eabbad50f0ec36c entries=778 +ripwire legend dictionary ripwire.dict/v1 dictv=ebae13b851e3a641 entries=791 : 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=) @@ -4450,18 +4471,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] ``` @@ -4484,21 +4505,21 @@ _Drop matching paths (repeatable) before ranking._ ``` $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - + - - + + - - - - - - + -... [8 more line(s); run it to see the whole thing] + + + + + +... [7 more line(s); run it to see the whole thing] ``` **Shaped by:** `--skipped`, `--in`, `--index-out` @@ -4515,21 +4536,21 @@ _Full map re-ranked with teleport toward git-changed files — recorded against ``` $ ./build/ripwire . --map-diff --top-k=5 - + - - - - + + + + - - - - + + + -... [8 more line(s); run it to see the whole thing] + +... [7 more line(s); run it to see the whole thing] ``` **Shaped by:** `--json`, `--limit` @@ -4549,16 +4570,16 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ ``` $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - + - + - + - + @@ -4597,16 +4618,16 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co ``` $ ./build/ripwire . --no-cache --top-k=3 - + - + - + - + @@ -4626,12 +4647,12 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses ``` $ ./build/ripwire . --no-ignore --top-k=3 - + - - + + - + @@ -4658,16 +4679,16 @@ _Skip files above a size bound before parsing (note the corpus shrink in the hea ``` $ ./build/ripwire . --max-file-size=8K --top-k=3 - + - - + + - + - + @@ -4738,16 +4759,16 @@ _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 - + - + - + - + @@ -4768,7 +4789,7 @@ $ ./build/ripwire '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' '{"jso {"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":{"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: 1111 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] ... [30 more line(s); run it to see the whole thing] ``` @@ -4861,14 +4882,14 @@ _Self-eval: co-change recall vs BM25._ $ ./build/ripwire . --eval ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 2.4% 6.8% 14.7% - BM25 6.9% 9.2% 13.1% - BM25sub 11.8% 16.8% 25.7% - BM25body 16.6% 30.3% 41.5% - fused 12.1% 15.4% 27.0% - anchored 16.6% 30.3% 41.5% - same-dir 1.2% 5.0% 6.4% - random 0.2% 0.4% 0.8% <- floor (random ranking over F=2381 files) + ripwire 2.9% 7.0% 15.3% + BM25 12.7% 14.9% 18.6% + BM25sub 17.2% 23.2% 27.6% + BM25body 15.1% 28.4% 41.9% + fused 13.0% 16.2% 24.9% + anchored 15.1% 28.4% 41.9% + same-dir 0.0% 8.8% 9.8% + random 0.2% 0.4% 0.8% <- floor (random ranking over F=2484 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 / @@ -4891,19 +4912,19 @@ _Known-item retrieval eval: MRR + recall@k per ranker per query mode._ ``` $ ./build/ripwire . --eval-retrieval ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=5624 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + sample: population=5818 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.697 56.3% 86.2% 91.1% - subtoken doc-phrase 0.922 90.2% 94.2% 95.2% - name-exact name 0.912 84.6% 96.3% 97.7% - name-exact doc-phrase 0.011 0.4% 1.6% 2.3% - anchored name 0.705 58.2% 85.9% 89.8% - anchored doc-phrase 0.918 89.6% 94.2% 94.8% - routed name 0.914 84.9% 96.5% 97.9% - routed doc-phrase 0.921 90.1% 94.0% 94.9% + subtoken name 0.693 56.0% 85.7% 90.7% + subtoken doc-phrase 0.920 90.0% 94.0% 94.9% + name-exact name 0.905 83.5% 96.1% 97.4% + name-exact doc-phrase 0.013 0.5% 1.8% 2.6% + anchored name 0.699 57.6% 85.2% 89.1% + anchored doc-phrase 0.917 89.5% 94.0% 94.5% + routed name 0.908 83.7% 96.3% 97.7% + routed doc-phrase 0.919 90.0% 93.8% 94.7% ... [6 more line(s); run it to see the whole thing] ``` @@ -4949,7 +4970,7 @@ _Version + short build info._ ``` $ ./build/ripwire --version -ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=c9c1df83b) +ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=66e5c3e10) ``` **Shaped by:** `--verify`, `--metrics`, `--deps`, `--naming-locals`, `--arch`, `--dry-run`, `--doc-drift`, `--from-trace` diff --git a/docs/captures/COMMANDS_showcase_2026-10-03.md b/docs/captures/COMMANDS_showcase_2026-10-07.md similarity index 76% rename from docs/captures/COMMANDS_showcase_2026-10-03.md rename to docs/captures/COMMANDS_showcase_2026-10-07.md index 9ab3237a9..532f56d18 100644 --- a/docs/captures/COMMANDS_showcase_2026-10-03.md +++ b/docs/captures/COMMANDS_showcase_2026-10-07.md @@ -1,9 +1,9 @@ # ripwire — every verb, run for real -- **Date:** 2026-10-03 (regenerated capture; supersedes any older `docs/captures/COMMANDS_showcase_*.md`) +- **Date:** 2026-10-07 (regenerated capture; supersedes any older `docs/captures/COMMANDS_showcase_*.md`) - **Lives in `docs/captures/`** — a directory the crawl/retrieval lenses SKIP (`kCrawlSkipDirs`, src/ingest.h): a generated doc that quotes every verb's output out-scores the source for any query about the tool and was measured at 77% of `--recall` on this repo when it sat at the root. `test/argvdiffcheck.sh` harvests its `## `-heading command lines as differential vectors — keep that format. -- **Version:** `ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=c9c1df83b)` -- **Repo:** the ripwire repo @ `52c4a428f` — **DIRTY — `git status --porcelain` reports 1 entr(ies)**. The diff-aware verbs (`--situ`/`--test-gate`/`--quality-delta`/`--pr-context`/`--map-diff`/`--edit-check`) answer a question about the WORKING TREE, so that condition is part of their answer and every one of their captions below states which tree it recorded against. This run recorded against a dirty tree, so their output here reflects uncommitted working-copy edits rather than the empty/exit-0 shape a clean checkout gives. They appear a SECOND time in the final section against a throwaway `git clone --local` sandbox carrying one KNOWN, deliberate regression — that is the reproducible gating demonstration; this one is whatever the tree happened to hold. +- **Version:** `ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=66e5c3e10)` +- **Repo:** the ripwire repo @ `6bf9d9d02` — **DIRTY — `git status --porcelain` reports 1 entr(ies)**. The diff-aware verbs (`--situ`/`--test-gate`/`--quality-delta`/`--pr-context`/`--map-diff`/`--edit-check`) answer a question about the WORKING TREE, so that condition is part of their answer and every one of their captions below states which tree it recorded against. This run recorded against a dirty tree, so their output here reflects uncommitted working-copy edits rather than the empty/exit-0 shape a clean checkout gives. They appear a SECOND time in the final section against a throwaway `git clone --local` sandbox carrying one KNOWN, deliberate regression — that is the reproducible gating demonstration; this one is whatever the tree happened to hold. - **Corpus:** the ripwire repo itself (dogfood), via `./build/ripwire` - **Sandbox diff** (the last section only): `.ripwire_notes | 2 ++ .ripwire_quality_acks | 1 + @@ -23,13 +23,15 @@ *The default ranked symbol map — start here when landing cold in a repo.* +**wall time: 18.86s** + ````` - + - - + + - + @@ -52,35 +54,36 @@ - + -… [797 more display lines; full output is 22194 bytes on 1 raw line(s)] +… [800 more display lines; full output is 22318 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --top-k=5` *Same map, capped to the 5 highest-ranked symbols.* +**wall time: 2.17s** + ````` - + - - + + - - - - - - + - + - + + + + + @@ -90,12 +93,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: 2.89s** + ````` - + + - + - + @@ -163,56 +170,56 @@ 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.09s** ````` - + ````` stderr: ````` -ripwire: --token-budget exceeded: withheld_est_tokens=8943 > budget=100 +ripwire: --token-budget exceeded: withheld_est_tokens=8992 > 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: 16.60s** +**wall time: 42.95s** ````` - - + 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 - + the cached index for `root`, rebuilt only when stale (otherwise returned as-is, no parse, no gra…inline const McpIndex& getIndex( const std::string& root ) -inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) -inline std::uint64_t contentHash64( std::string_view s ) noexcept -struct CacheEntry +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline std::uint64_t contentHash64( std::string_view s ) noexcept +struct CacheEntry inline void prewarmTagsQueries( const std::vector<std::string>& files, const HashMap<std::string, FileFacts>& cache, long long cacheWriteNs, IngestFileScan& … [line truncated: 12 more bytes on this line] -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::uint64_t blobChecksum( std::string_view s ) noexcept +inline ContentIdIndex contentIdsBySym( const IngestResult& ing, const Graph& g, std::string_view root ) inline bool ingestCommitTree( const std::string& root, const std::string& sha, const std::vector<std::string>& excludes, std::size_t maxFileBytes, IngestResult&… -inline std::vector<CacheWriteRow> buildCacheWritePlan( const std::vector<std::uint32_t>& orderIn, const std::vector<std::uint64_t>& pathHashes, const s … [line truncated: 16 more bytes on this line] -inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, … [line truncated: 54 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::vector<CacheWriteRow> buildCacheWritePlan( const std::vector<std::uint32_t>& orderIn, const std::vector<std::uint64_t>& pathHashes, const s … [line truncated: 16 more bytes on this line] +inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, … [line truncated: 54 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 ) struct IngestFileScan -inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t>& f … [line truncated: 17 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 ) +inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t>& f … [line truncated: 17 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 ) inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, cons … [line truncated: 91 more bytes on this line] inline std::string spanTierMemoPath( const std::string& diskPath ) inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) -inline NodeId resolveHandleAll( const McpIndex& ix, std::uint64_t idHash, std::vector< NodeId >& matches ) +inline NodeId resolveHandleAll( const McpIndex& ix, std::uint64_t idHash, std::vector< NodeId >& matches ) … [46 more display lines; full output is 9078 bytes on 1 raw line(s)] ````` @@ -220,22 +227,22 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > budget=100 *Name-shaped query: the router picks name-exact BM25 (header says which/why).* -**wall time: 1.48s** +**wall time: 5.09s** ````` - - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -260,39 +267,39 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > budget=100 *Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* -**wall time: 1.60s** +**wall time: 5.05s** ````` - + - + 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", bo … [line truncated: 11 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", bo … [line truncated: 11 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::string didYouMean( const IngestResult& ing, std::string_view name ) -inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = nullptr ) +inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = nullptr ) PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double& … [line truncated: 37 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 = nullptr )< … [line truncated: 3 more bytes on this line] -struct DataSectionsCut +inline const McpIndex& getIndex( const std::string& root ) +inline std::vector<float> churnDecayTeleportWorkspace( const std::vector<std::string>& rootDirs, const IngestResult& ing, bool* outHasChurnEvidence = nullptr )< … [line truncated: 3 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 ) -inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 -### Fixed — `rank_by` over MCP no longer answers a different rankin … [line truncated: 57 more bytes on this line] -### 2. Gates for A -#### The convergence disclosure contract +inline std::vector<float> anchoredLexicalRank( const Graph& g, const std::vector<float>& lex ) +inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 +### Fixed — `rank_by` over MCP no longer answers a different rankin … [line truncated: 57 more bytes on this line] +### 2. Gates for A +#### The convergence disclosure contract … [102 more display lines; full output is 15166 bytes on 72 raw line(s)] ````` @@ -300,17 +307,17 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > 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.48s** +**wall time: 5.86s** ````` - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -321,39 +328,39 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > 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.61s** +**wall time: 5.10s** ````` - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar… -enum class AstWalk : std::uint8_t +struct FileHealth +enum class AstWalk : std::uint8_t +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 -inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept -constexpr std::array<LangEntry, 51> kLangTable = +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::array<LangEntry, 51> kLangTable = constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept 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 ) std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] -bool jsonNestsTooDeep( std::string_view bytes ) noexcept +bool jsonNestsTooDeep( std::string_view bytes ) noexcept constexpr std::uint32_t kMaxYamlNestDepth = 64u SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) inline bool hasMainGuard( std::string_view source ) inline void runParseWorker( ParsePoolShared& sh, unsigned t ) inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) -struct LintRule +struct LintRule enum class DisclosureWhy : std::uint8_t -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) … [55 more display lines; full output is 9170 bytes on 1 raw line(s)] ````` @@ -361,13 +368,13 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > 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.50s** +**wall time: 5.99s** ````` - - + 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 ) @@ -376,7 +383,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > 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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) @@ -387,24 +394,24 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > budget=100 struct Bm25Params 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 reads the W … [line truncated: 57 more bytes on this line] -struct CandidateProvenance -std::optional<int> runEvalViews( const MainDispatch& d ) +struct CandidateProvenance +std::optional<int> runEvalViews( const MainDispatch& d ) inline constexpr int kLexWeightDoc = 2 -inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) struct RecallSectionPick inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) -### Graph-Ranked Retrieval: It finds the right files more often than the alternatives -… [56 more display lines; full output is 9508 bytes on 1 raw line(s)] +### Graph-Ranked Retrieval: It finds the right files more often than the alternatives +… [56 more display lines; full output is 9449 bytes on 1 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: 1.59s** +**wall time: 5.94s** ````` - @@ -415,7 +422,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > 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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) @@ -427,24 +434,26 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > budget=100 struct Bm25Params 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 reads the W … [line truncated: 57 more bytes on this line] -struct CandidateProvenance -std::optional<int> runEvalViews( const MainDispatch& d ) +struct CandidateProvenance +std::optional<int> runEvalViews( const MainDispatch& d ) inline constexpr int kLexWeightDoc = 2 -inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) struct RecallSectionPick inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) -inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept -… [59 more display lines; full output is 9624 bytes on 1 raw line(s)] +inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept +… [59 more display lines; full output is 9616 bytes on 1 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: 2.92s** + ````` - + @@ -457,12 +466,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > budget=100 *The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* -**wall time: 1.22s** +**wall time: 5.03s** ````` - + f, A&&... a ) @@ -484,6 +493,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > 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: 2.02s** + ````` @@ -498,6 +509,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > 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.08s** + ````` @@ -509,12 +522,12 @@ ripwire: --token-budget exceeded: withheld_est_tokens=8943 > budget=100 *Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* -**wall time: 1.10s** +**wall time: 4.07s** ````` -ripwire recall — "quality delta gating exit codes" — 113 relevant of 209 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5489 +ripwire recall — "quality delta gating exit codes" — 113 relevant of 210 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5489 -━━ README.md (relevance 7.944) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 212219 B; lines="2688-2700,2701-2708"; dropped_by_budget=23] +━━ README.md (relevance 7.966) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 213934 B; lines="2707-2719,2720-2727"; dropped_by_budget=23] #### 6.2 Exit codes This is the part to wire into a script. @@ -538,7 +551,7 @@ refuses by default. -━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.296) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.319) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] ### Root spelling (maintainer-owned; reproduce it once so you recognise it) ```bash @@ -547,38 +560,19 @@ refuses by default. ## `./build/ripwire /aux/kbcorpus --recall="field affinity cache line data layout which fields are read together" --top-k=3 --max-tokens=1200` -*The directory-as-knowledge-base pattern: --recall pointed at a 1440129-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 +*The directory-as-knowledge-base pattern: --recall pointed at a 1438117-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.* -━━ commands.md (relevance 11.127) ━━ [sections: 1 of 155 selected (185 in doc), section-granular; whole doc 534551 B; lines="504-529"; dropped_by_budget=154] [truncated: 1854 of 4257 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base +**wall time: 1.78s** -**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? +````` +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 -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: +━━ commands.md (relevance 10.246) ━━ [sections: 1 of 155 selected (185 in doc), section-granular; whole doc 533554 B; lines="3654-3658"; dropped_by_budget=154] [truncated: 1865 of 7738 bytes] +### `--field-affinity[=STRUCT]` -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. +**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. -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)) ````` @@ -593,9 +587,11 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *File-by-file orientation map (top symbols per file).* +**wall time: 2.18s** + ````` - + @@ -624,14 +620,14 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas -… [366 more display lines; full output is 11121 bytes on 1 raw line(s)] +… [366 more display lines; full output is 11120 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --html=/aux/map2.html` *Self-contained HTML force-directed call graph.* -**wall time: 1.66s** +**wall time: 3.09s** ````` (empty) @@ -640,15 +636,17 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas Artifact written: ````` -158472 /aux/map2.html +158952 /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.09s** + ````` - + @@ -658,10 +656,9 @@ Artifact written: - - - - + + + @@ -669,7 +666,7 @@ Artifact written: - + ````` @@ -681,12 +678,14 @@ Artifact written: *Ego graph around one symbol — depth 1 BY DEFAULT now (the root's depth= says so): ~6 KB where the 2-hop neighbourhood is ~64 KB on this repo.* +**wall time: 1.64s** + ````` - + - - + + @@ -712,19 +711,21 @@ Artifact written: -… [224 more display lines; full output is 9291 bytes on 1 raw line(s)] +… [225 more display lines; full output is 9305 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.08s** + ````` - + - - + + @@ -750,18 +751,20 @@ Artifact written: -… [3043 more display lines; full output is 80474 bytes on 1 raw line(s)] +… [2993 more display lines; full output is 79377 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: 1.09s** + ````` - + - + @@ -795,12 +798,14 @@ Artifact written: *Who calls SYM (1-hop in-edges).* +**wall time: 1.10s** + ````` - - - - + + + + @@ -812,13 +817,15 @@ Artifact written: *The prose legend restored: compact is the DEFAULT (bcb796959), so the pair above already shows the compact posture — --legend=full opts back into the ~3 KB explanatory legend the default trims. Rows and completeness attributes unchanged; only the legend differs.* +**wall time: 1.11s** + ````` - - - - + + + + @@ -830,7 +837,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: 1.69s** ````` @@ -847,10 +854,12 @@ ripwire: --callers symbol not found: DoesNotExist *What SYM calls (1-hop out-edges).* +**wall time: 1.10s** + ````` - - + + @@ -865,14 +874,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.08s** +**wall time: 1.81s** ````` - - - - + + + + @@ -886,128 +895,136 @@ ripwire: --callers symbol not found: DoesNotExist *Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* +**wall time: 1.07s** + ````` - - - - - - + + + + + + - - + + + - - - + + - - - - - - - - - - -… [28 more display lines; full output is 3648 bytes on 1 raw line(s)] + + + + + + + + + + +… [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.09s** + ````` - - - - - - - - + + + + + + + - - - - - + + + + + + + - - - - - - - - - - + + + + + + + + + + - -… [73 more display lines; full output is 5571 bytes on 1 raw line(s)] +… [73 more display lines; full output is 5563 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.10s** + ````` - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + - - - - - -… [73 more display lines; full output is 5431 bytes on 1 raw line(s)] + + + + +… [73 more display lines; full output is 5441 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.12s** + ````` - + - + ````` @@ -1015,13 +1032,15 @@ ripwire: --callers symbol not found: DoesNotExist *Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* +**wall time: 1.07s** + ````` - + - - + + @@ -1032,44 +1051,48 @@ ripwire: --callers symbol not found: DoesNotExist *Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* +**wall time: 1.60s** + ````` - - - + + + - - + + - - + + + - - - + + - - - - - - - -… [47 more display lines; full output is 5229 bytes on 1 raw line(s)] + + + + + + + +… [48 more display lines; full output is 5280 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --mentions=rankGraphTeleport` *Markdown docs that name SYM in a backtick (doc<->code edges).* +**wall time: 1.13s** + ````` @@ -1087,9 +1110,11 @@ ripwire: --callers symbol not found: DoesNotExist *Test files that transitively reach the changed file.* +**wall time: 1.23s** + ````` - - + + @@ -1113,22 +1138,39 @@ ripwire: --callers symbol not found: DoesNotExist *Mid-task situational report for the current git diff — recorded against a DIRTY tree (contrast with the sandbox run below).* +**wall time: 2.91s** + ````` -ripwire situational-awareness — 0 changed file(s), 0 symbols in them +ripwire situational-awareness — 1 changed file(s), 1 symbols in them root: . -at: 52c4a428f+dirty - (0 changed files — working tree is clean, nothing to analyze) +at: 6bf9d9d02+dirty + [1] blast radius: 0 symbols across 0 files transitively depend on these changes + counts_floor=1 graph_ambiguous=10271 graph_unresolved=12861 graph_unindexed=237 (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 missin … [line truncated: 46 more bytes on this line] + [2] tests to run (1) order=evidence: [changed] you edited it, [partner] named after a changed file, then hops asc (1 = direct); "(n): a, b" = n runner-less files sharing that evidence; a (run: …) is relative to root: + test/mcpmanifestcheck.sh [changed] (run: bash test/mcpmanifestcheck.sh) + script_gates_unmodelled=729 — 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 (24) window="18mo@HEAD" commits="3919" (showing 8 of 24 files — shown=8 total=24 capped=1; next: --situ --limit=24): + src/mcp.h (co-edited in 54% of commits) + src/mcprefusal.h (co-edited in 50% of commits) + src/mcpverbs.h (co-edited in 50% of commits) + CHANGELOG.md (co-edited in 42% of commits) + src/cli.h (co-edited in 29% of commits) + README.md (co-edited in 25% of commits) + docs/EVALS.md (co-edited in 25% of commits) + test/compactlegendcheck.sh (co-edited in 25% of commits) + next: --test-gate ````` ## `./build/ripwire . --test-gate` *Pre-PR gate recorded against a DIRTY tree, so the obligations below are the working copy's real ones — the recorded exit code says which way it went.* -**wall time: 1.76s** +**exit code: 4** — **wall time: 2.84s** ````` - - + untested blast radius ; exit 4 while either exists. tests_capped=/untested_capped=: 1 = cut. counts_floor=1: every count is a FLOOR, never a total. graph_ambiguous=/graph_unresolved=: resolver gauge. graph_unindexed=N: N files no grammar could read (the map header's unindexed=); their calls raise neither gauge. at=: commit+dirty+shallow. root=: p= relative to it. next=: the one pasteable follow-up. impacted=N: symbols that transitively call the change (changed symbols excluded). shown_tests=/shown_untested=: t rows and u rows printed, two independent counts. script_gates_unmodelled=N: test/*.sh runners in the corpus, a path count; not call-graph modelled. script_gates_registered=N: shell gates test/regression.sh registers as suite members. script_gates_mapped=N: registered gates with exact dependency evidence (literal paths or RIPWIRE_TEST_DEPS). script_gates_unresolved_dynamic=N: registered gates with no mappable deps; they may cover the change unlisted. 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). --> + ````` @@ -1136,12 +1178,12 @@ at: 52c4a428f+dirty *Literal trigram-indexed search. Each hit carries its MATCHED line as the element's own CDATA (the wrapper is gone), plus shown/capped/hits_capped and a pasteable next= on the root.* -**wall time: 1.53s** +**wall time: 2.13s** ````` @@ -1319,11 +1361,11 @@ at: 52c4a428f+dirty *The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* -**wall time: 4.85s** +**wall time: 5.91s** ````` -if data.get( "schema" ) != SCHEMA: raise SystemExit( f"{path}: unexpected schema {data.get('schema')!r} (expecte if not str( data.get( "tasks_lock_content_sha256", "" ) ).startswith( "questions:" ): train_repos = select_tasks if train_repos: raise SystemExit( f"{path}: records from repo(s) that re-derive to LocBench @@ -1359,20 +1401,20 @@ at: 52c4a428f+dirty *Raw BM25 ranking (debug lens; --for is the real verb).* -**wall time: 1.05s** +**wall time: 1.25s** ````` - + - + - + - + @@ -1386,12 +1428,12 @@ at: 52c4a428f+dirty - + - + … [28 more display lines; full output is 3515 bytes on 1 raw line(s)] ````` @@ -1404,10 +1446,10 @@ at: 52c4a428f+dirty *Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* -**wall time: 1.56s** +**wall time: 2.78s** ````` - + @@ -1419,75 +1461,79 @@ at: 52c4a428f+dirty 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] What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure -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 … [line truncated: 7 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 … [line truncated: 7 more bytes on this line] inline std::uint32_t sliceRdIterationCeiling() noexcept struct PageRankRun -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 ) -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… -COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "th … [line truncated: 61 more bytes on this line] -inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window -inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept -inline std::string powerShellSingleQuote( std::string_view s ) noexcept -inline std::string path_prepend_hint( std::string_view dir ) -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` -### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] -### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] -## Background — read these, in this order -## Design space and constraints -… [106 more display lines; full output is 13943 bytes on 72 raw line(s)] +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 ) +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… +inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window +inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] +### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] +## Background — read these, in this order +## Design space and constraints +COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "t … [line truncated: 62 more bytes on this line] +… [106 more display lines; full output is 13945 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: 92.7% fewer bytes at top-10, 88.7% at top-50, 87.6% at top-100 (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.12s** + ````` - - + + - + - - - - - - - - + - + + + + + + + + - -… [138 more display lines; full output is 12056 bytes on 1 raw line(s)] + +… [135 more display lines; full output is 11971 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --outline=rankGraphTeleport --top-k=0` *Control-flow skeleton of one symbol, payload-only via the new --top-k=0.* +**wall time: 1.11s** + ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1511,12 +1557,14 @@ at: 52c4a428f+dirty *CHANGED: a line range on --outline is now STRIPPED with a stderr note (it used to refuse).* +**wall time: 1.10s** + ````` - + & p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); @@ -1546,12 +1594,14 @@ ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — *Full body + inline callee signatures.* +**wall time: 1.09s** + ````` - + @@ -1736,57 +1794,59 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Fan-in/out + complexity annotations on the map.* -**wall time: 1.21s** +**wall time: 1.94s** ````` - + - - + + - + - + - + - - - - - - - - + - + + + + - -… [21 more display lines; full output is 5523 bytes on 1 raw line(s)] + + + + + +… [18 more display lines; full output is 5451 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --deps` *File->file dependency graph (god-files, cycles).* +**wall time: 1.09s** + ````` - - - - + + + + - - - - + + + + @@ -1794,11 +1854,11 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - - - + + + - + @@ -1806,46 +1866,46 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - -… [885 more display lines; full output is 23053 bytes on 1 raw line(s)] + +… [886 more display lines; full output is 23094 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --hotspots` *Complexity x recent git churn (maintenance pain).* -**wall time: 1.76s** +**wall time: 2.73s** ````` - - - - - - - + + + + + + + - - - + + + - - - + + + + - - + - + + - - - + + … [14 more display lines; full output is 5753 bytes on 1 raw line(s)] ````` @@ -1854,21 +1914,21 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Token-normalized duplicate bodies.* -**wall time: 2.87s** +**wall time: 4.86s** ````` - + - + - + @@ -1883,7 +1943,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan - + @@ -1894,41 +1954,41 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Files that change together in git (hidden coupling).* -**wall time: 1.65s** +**wall time: 2.23s** ````` - + + - - + - + + + + + - - - - - - + - + + - - + - - - - - + + + + + + ````` @@ -1936,50 +1996,52 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Hotspots scoped to RECENT churn (the regression lens).* -**wall time: 1.11s** +**wall time: 1.85s** ````` - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - -… [13 more display lines; full output is 5124 bytes on 1 raw line(s)] + + + + + + +… [13 more display lines; full output is 5109 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --arch=test/archfix/rules.txt` *Enforce layering rules (exit 2 on violation) — run against the repo's own test fixture rules.* +**wall time: 1.08s** + ````` - @@ -2007,54 +2069,54 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan -… [529 more display lines; full output is 77247 bytes on 1 raw line(s)] +… [563 more display lines; full output is 82067 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint` *Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* -**wall time: 9.77s** +**wall time: 10.97s** ````` - - + + - + - + - - + + - - + + - + - + - -… [700 more display lines; full output is 69581 bytes on 1 raw line(s)] + +… [699 more display lines; full output is 69602 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: 2.63s** +**wall time: 3.14s** ````` @@ -2073,7 +2135,7 @@ 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:438, rw::parseLintRuleFile(const std::string&, std::string_view, std::vector&):: — logged once per site) +[math degraded] lint-rules: malformed rule file skipped (lintrules.h:395, rw::parseLintRuleFile(const std::string&, std::string_view, std::vector&):: — logged once per site) ripwire: AST query did not compile for any grammar: (this_is_not_a_real_node @x @@@ ((( ) ````` @@ -2081,6 +2143,8 @@ ripwire: AST query did not compile for any grammar: (this_is_not_a_real_node @x *Join MEASURED heat onto --lint findings — runs in a tiny fabricated demo corpus (one cache-pointer-chase-loop finding under a PROFILE_SCOPE site) because a real report needs a RIPWIRE_PROFILE build; the finding inside the profiled scope gains heat_* columns from the report's #PROF_TSV row.* +**wall time: 1.02s** + Input file: ````` @@ -2134,167 +2198,177 @@ The joined finding — past the display cut above, extracted so the join is visi *Cluster the call graph into cohesive modules.* +**wall time: 1.10s** + ````` - + - + - + - + - + - + - + -… [187 more display lines; full output is 16546 bytes on 1 raw line(s)] +… [187 more display lines; full output is 16517 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --zoom` *Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels_shown="2" of levels= BY DEFAULT over the 40 largest top modules (~8 KB, where the whole tree is ~220 KB); a module AT the cut carries children=.* +**wall time: 1.11s** + ````` - - + + + - - + + - - + - + - + - + + - - + + - - + + - - -… [139 more display lines; full output is 7160 bytes on 1 raw line(s)] + +… [169 more display lines; full output is 8237 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --zoom --zoom-levels=3` *The restoring knob: --zoom-levels=N prints N levels (0 = the whole tree); here three, ~11 KB.* +**wall time: 1.12s** + ````` - - - + + + - + - + - + - + - + - + - + - + - + + + - - - + -… [269 more display lines; full output is 11755 bytes on 1 raw line(s)] +… [691 more display lines; full output is 27204 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --report` *Architecture summary (modules, god-files, cycles) as markdown.* +**wall time: 1.62s** + ````` # ripwire architecture report -2381 files · 23348 symbols · 33292 edges · 2252 modules (10730 call-graph isolated) +2484 files · 23824 symbols · 34201 edges · 2316 modules (10908 call-graph isolated) Root: `.` -Call-graph isolate provenance: 2850 declaration, 1549 header, 1842 source, 4489 document; 0 connected Louvain singletons +Call-graph isolate provenance: 2886 declaration, 1578 header, 1922 source, 4522 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 2252) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1243 symbols -- **src::append@elixir_resolve.h:111:4875 [resolve,add,collect]** — 1086 symbols -- **src::formatTo@infra/emit.h:141:8856 [read,close,run]** — 661 symbols -- **src::kindIs@infra/nodekind.h:46:3271 [emit,collect,push]** — 257 symbols +## Modules (call-graph clusters; showing 12 of 2316) +- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1366 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,collect,add]** — 1126 symbols +- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 563 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [emit,collect,push]** — 258 symbols - **docs::`--top-k=N`@COMMANDS.md:59:8772 [pack,allow,insert]** — 175 symbols -- **src::assign@infra/svector.h:342:19905 [scan,resolve,sort]** — 67 symbols -- **src::relForHash@model.h:1396:112049 [compute,clone,resolve]** — 58 symbols -- **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [erase,find,count]** — 52 symbols -- **src/infra::active@profilePmc.h:845:34910 [print,ensure,report]** — 43 symbols -- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,call,compute]** — 41 symbols -- **src::buf@infra/svector.h:125:8942 [insert,pack,render]** — 39 symbols -- **src::ASSUME@infra/Diagnostics.h:386:25510 [write,gather,verify]** — 30 symbols - -## God files (most depended-on; showing 10 of 423) -- `test/lib/clean-env.sh` — 176 dependents +- **src::realpath@infra/os.h:199:13067 [parse,split,read]** — 64 symbols +- **src::relForHash@model.h:1438:116507 [compute,clone,read]** — 60 symbols +- **src::assign@infra/svector.h:342:19905 [scan,run,resolve]** — 55 symbols +- **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [erase,find,count]** — 51 symbols +- **src/infra::active@profilePmc.h:845:34910 [print,ensure,report]** — 49 symbols +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,verify,make]** — 35 symbols +- **test::no@decltodefcheck.sh:127:11708 [run]** — 30 symbols + +## God files (most depended-on; showing 10 of 445) +- `test/lib/clean-env.sh` — 180 dependents - `src/infra/emit.h` — 86 dependents -- `src/model.h` — 82 dependents -- `src/infra/Diagnostics.h` — 57 dependents -- `src/serialize.h` — 38 dependents -… [32 more lines, 3555 bytes total] +- `src/model.h` — 85 dependents +- `src/infra/Diagnostics.h` — 58 dependents +- `src/serialize.h` — 39 dependents +… [32 more lines, 3520 bytes total] ````` ## `./build/ripwire . --seams` *Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* +**wall time: 1.08s** + ````` - - + + - + @@ -2304,11 +2378,11 @@ Call-graph isolate provenance: 2850 declaration, 1549 header, 1842 source, 4489 - - - - - + + + + + @@ -2317,31 +2391,33 @@ Call-graph isolate provenance: 2850 declaration, 1549 header, 1842 source, 4489 -… [104 more display lines; full output is 13578 bytes on 1 raw line(s)] +… [102 more display lines; full output is 13421 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --mermaid` *Module (directory) dependency graph as a Mermaid diagram.* +**wall time: 1.09s** + ````` %% 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
6867"] - n102["src/infra
1275"] + n101["src
7050"] + n102["src/infra
1276"] end subgraph sg1 ["test"] - n103["test
4792"] - n294["test/fixtures/recallpassage
314"] + n103["test
4844"] + n328["test/fixtures/recallpassage
314"] n280["test/expandmodefix
151"] n282["test/expandsibsfix
149"] - n424["test/rubyattrsfix
85"] - n361["test/massfix
77"] - n326["test/javamethodreffix
62"] + n458["test/rubyattrsfix
85"] + n395["test/massfix
77"] + n360["test/javamethodreffix
62"] end - n73["docs
825"] - n2[".github
596"] + n73["docs
829"] + n2[".github
598"] subgraph sg4 ["bench"] n4["bench
531"] n5["bench/agentloop
331"] @@ -2362,26 +2438,26 @@ flowchart LR *Bus-factor: recency-weighted author ownership per file.* -**wall time: 1.79s** +**wall time: 2.78s** ````` - - + + - + - + - - + + - + - + @@ -2395,16 +2471,18 @@ flowchart LR -… [879 more display lines; full output is 97394 bytes on 1 raw line(s)] +… [886 more display lines; full output is 97855 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.18s** + ````` - + ````` @@ -2413,9 +2491,11 @@ flowchart LR *Which symbols a TEST FILE exercises — the reverse direction of --affected.* +**wall time: 1.07s** + ````` - ````` @@ -2424,9 +2504,11 @@ flowchart LR *Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* +**wall time: 1.11s** + ````` - ````` @@ -2435,11 +2517,11 @@ flowchart LR *Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit.* -**wall time: 62.92s** +**wall time: 76.00s** ````` - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). --> + @@ -2462,13 +2544,13 @@ flowchart LR - - + + - -… [95 more display lines; full output is 12147 bytes on 1 raw line(s)] + +… [104 more display lines; full output is 13026 bytes on 1 raw line(s)] ````` stderr: @@ -2481,18 +2563,18 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Fast per-symbol post-edit contract check vs git HEAD — recorded against a DIRTY tree, so the verdict describes the working copy, not HEAD alone, and the recorded exit code is the working copy's.* -**wall time: 1.11s** +**wall time: 1.72s** ````` - - - + + - + ````` @@ -2500,10 +2582,40 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *No-LLM review-evidence bundle for the working-tree diff — recorded against a DIRTY tree, so it is populated rather than empty.* +**wall time: 3.25s** + ````` - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
````` @@ -2511,11 +2623,41 @@ 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: 3.84s** + ````` - - (t=modscope): a file's MODULE SCOPE — where a top-level call and an anonymous callback body's calls live; a CALLER, never a callee, with no body to expand. --> + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ````` @@ -2523,24 +2665,24 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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: 38.27s** +**wall time: 43.12s** ````` - - - + + + - + - - + + - + ````` @@ -2548,14 +2690,16 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Which refs of the one branch `main` (no ref family left to select on this checkout) still hold divergent authored work vs HEAD, with verdicts.* -**wall time: 1.35s** +**wall time: 1.82s** ````` - - + + + - + + ````` @@ -2564,13 +2708,17 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *A second selection, the one branch `sync-rebase-backup` (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.17s** +**wall time: 1.28s** ````` - - - + + + + + + + ````` @@ -2579,12 +2727,12 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *Select the genuinely-unmerged refs of the one branch `main` (no ref family left to select on this checkout) 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: 27.95s** +**wall time: 31.68s** ````` - - + + @@ -2612,14 +2760,14 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g -… [59 more display lines; full output is 6685 bytes on 1 raw line(s)] +… [61 more display lines; full output is 6813 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.04s** +**exit code: 1** — **wall time: 1.19s** ````` (empty) @@ -2636,11 +2784,11 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Cross-branch ABI-break gate over the one branch `main` (no ref family left to select on this checkout): 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.* -**wall time: 1.37s** +**wall time: 2.02s** ````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + … [34 more display lines; full output is 16096 bytes on 1 raw line(s)] ````` @@ -2688,39 +2836,39 @@ 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: 14.73s** +**wall time: 19.87s** ````` - - - - - - - - - - - - - - + + + + + + + + + + + + + - + @@ -2768,11 +2916,11 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Blast radius of turning ONE gate on: live code, symbols, transitive reach, covering tests.* -**wall time: 1.22s** +**wall time: 1.91s** ````` - + @@ -2803,7 +2951,7 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Unknown-gate refusal (exit 1) with a did-you-mean from a real edit distance (one character off RIPWIRE_ASAN).* -**exit code: 1** — **wall time: 1.19s** +**exit code: 1** — **wall time: 1.33s** ````` (empty) @@ -2820,10 +2968,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.08s** +**wall time: 2.89s** ````` -{"v":1,"verb":"plan-lanes","at":"52c4a428f+dirty","root":".","task":"add a --since filter to the doc-drift verb and cover it with tests","source":"partition","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2381,"symbols":23348,"edge … [line truncated: 344 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"6bf9d9d02+dirty","root":".","task":"add a --since filter to the doc-drift verb and cover it with tests","source":"partition","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2484,"symbols":23824,"edge … [line truncated: 345 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"}, @@ -2834,33 +2982,33 @@ ripwire: run `ripwire . --flags` for the gate table {"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":2,"cx":8,"ccx":8,"churn":31,"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":2,"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":1302,"ord":0,"overloads":1,"amb":0,"cx":4,"ccx":4,"churn":46,"tested":0}, -{"p":"./src/main.cpp","n":"scanReportVerbPrecedence","scope":"","key":"9ab981e987e8108e","id":null,"id_addressable":false,"id_collides_with":0,"l":2863,"ord":0,"overloads":1,"amb":33,"cx":8,"ccx":11,"churn":413,"tested":0}, -{"p":"./src/gitmine.h","n":"resolveSinceScope","scope":"rw","key":"9caaa3dbeaa688d0","id":"./src/gitmine.h::rw::resolveSinceScope","id_addressable":true,"id_collides_with":0,"l":317,"ord":0,"overloads":1,"amb":3,"cx":7,"ccx":7,"churn":57,"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":2863,"ord":0,"overloads":1,"amb":33,"cx":8,"ccx":11,"churn":414,"tested":0}, +{"p":"./src/gitmine.h","n":"resolveSinceScope","scope":"rw","key":"9caaa3dbeaa688d0","id":"./src/gitmine.h::rw::resolveSinceScope","id_addressable":true,"id_collides_with":0,"l":326,"ord":0,"overloads":1,"amb":3,"cx":7,"ccx":7,"churn":59,"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":350,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":18,"tested":0}, -{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":685,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":413,"tested":0}, +{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":685,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":414,"tested":0}, {"p":"./src/taskroute.h","n":"testCoverageTaskChoice","scope":"rw::taskroute","key":"bf72991aa28355c7","id":"./src/taskroute.h::rw::taskroute::testCoverageTaskChoice","id_addressable":true,"id_collides_with":0,"l":1365,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":34,"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":5162,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":299,"tested":0}], -"files":[{"p":"./src/darkflags.h","symbols":1,"churn":31,"ccx":8,"hotspot_rank":44}, -{"p":"./src/docdrift.h","symbols":3,"churn":36,"ccx":12,"hotspot_rank":31}, -{"p":"./src/gitmine.h","symbols":1,"churn":57,"ccx":7,"hotspot_rank":18}, -{"p":"./src/main.cpp","symbols":2,"churn":413,"ccx":12,"hotspot_rank":4}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":299,"ccx":2,"hotspot_rank":5}, -{"p":"./src/pageview.h","symbols":1,"churn":18,"ccx":2,"hotspot_rank":131}, +{"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":5229,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":310,"tested":0}], +"files":[{"p":"./src/darkflags.h","symbols":1,"churn":31,"ccx":8,"hotspot_rank":45}, +{"p":"./src/docdrift.h","symbols":4,"churn":36,"ccx":14,"hotspot_rank":31}, +{"p":"./src/gitmine.h","symbols":1,"churn":59,"ccx":7,"hotspot_rank":18}, +{"p":"./src/main.cpp","symbols":2,"churn":414,"ccx":12,"hotspot_rank":4}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":310,"ccx":2,"hotspot_rank":5}, +{"p":"./src/pageview.h","symbols":1,"churn":18,"ccx":2,"hotspot_rank":135}, {"p":"./src/taskroute.h","symbols":1,"churn":34,"ccx":2,"hotspot_rank":40}, -{"p":"./src/testmap.h","symbols":1,"churn":46,"ccx":4,"hotspot_rank":35}, -{"p":"./src/verbs_change.h","symbols":1,"churn":69,"ccx":8,"hotspot_rank":22}]},"blast_radius":{"reaches":51,"files_total":17,"capped":false,"files":["./src/darkflags.h","./src/docdrift.h","./src/editplan.h","./src/flipimpact.h","./src/gitmine.h","./src/main.cpp","./src/mcp.h","./src/mcpedit.h","./s … [line truncated: 203 more bytes on this line] +{"p":"./src/testmap.h","symbols":1,"churn":48,"ccx":4,"hotspot_rank":34}, +{"p":"./src/verbs_change.h","symbols":1,"churn":69,"ccx":8,"hotspot_rank":23}]},"blast_radius":{"reaches":51,"files_total":17,"capped":false,"files":["./src/darkflags.h","./src/docdrift.h","./src/editplan.h","./src/flipimpact.h","./src/gitmine.h","./src/main.cpp","./src/mcp.h","./src/mcpedit.h","./s … [line truncated: 203 more bytes on this line] "tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":51,"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":12,"files":9,"module_span":1,"max_ccx":11,"sum_ccx":57,"ambiguous_calls":49,"blast_reaches":51,"contract_touches":0,"conflicts":0,"untested":51,"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/gitmine.h","n":"gitWindowBoundarySha","scope":"rw","key":"18627516699d7062","id":"./src/gitmine.h::rw::gitWindowBoundarySha","id_addressable":true,"id_collides_with":0,"l":1605 … [line truncated: 69 more bytes on this line] -… [73 more display lines; full output is 20434 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":13,"files":9,"module_span":1,"max_ccx":11,"sum_ccx":59,"ambiguous_calls":49,"blast_reaches":51,"contract_touches":0,"conflicts":0,"untested":51,"tests … [line truncated: 135 more bytes on this line] +… [74 more display lines; full output is 20829 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.72s** +**wall time: 3.23s** Input file: @@ -2871,11 +3019,11 @@ write regression tests for the new filter ````` ````` -{"v":1,"verb":"plan-lanes","at":"52c4a428f+dirty","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2381,"symbols":23348,"edges":33292,"ambiguous":9974,"unresolved":13331},"carve":null,"core":{" … [line truncated: 10 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"6bf9d9d02+dirty","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2484,"symbols":23824,"edges":34201,"ambiguous":10271,"unresolved":12861},"carve":null,"core":{ … [line truncated: 11 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":639,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":299,"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":3176,"ord":0,"overloads":1,"amb":0,"cx":0,"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":639,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":310,"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":3183,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":433,"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":12,"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":12,"cx":26,"ccx":38,"churn":36,"tested":0}, @@ -2884,24 +3032,24 @@ write regression tests for the new filter {"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/mcprefusal.h","n":"kMcpRequiredFields","scope":"rw::mcprefuse","key":"c5f1ad5fc3a12368","id":"./src/mcprefusal.h::rw::mcprefuse::kMcpRequiredFields","id_addressable":true,"id_collides_with":0,"l":67,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":38,"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":428,"ccx":0,"hotspot_rank":6}, +"files":[{"p":"./src/cli.h","symbols":1,"churn":433,"ccx":0,"hotspot_rank":6}, {"p":"./src/docdrift.h","symbols":6,"churn":36,"ccx":54,"hotspot_rank":31}, -{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":86}, +{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":87}, {"p":"./src/mcprefusal.h","symbols":1,"churn":38,"ccx":0,"hotspot_rank":56}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":299,"ccx":0,"hotspot_rank":5}, -{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":51}, -{"p":"./src/verbs_change.h","symbols":1,"churn":69,"ccx":4,"hotspot_rank":22}], -"symbols_total":3116,"symbols_capped":true},"blast_radius":{"reaches":12,"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":12,"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":7,"module_span":7,"max_ccx":38,"sum_ccx":62,"ambiguous_calls":26,"blast_reaches":12,"contract_touches":1,"conflicts":3,"untested":12,"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":106,"l":261,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":7," … [line truncated: 11 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":2800,"ord":0,"overloads":1,"amb":0,"cx":0,"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":3176,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":428,"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":2879,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":428,"tested":0}, -{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":106,"l":865,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":17,"tested":0}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":310,"ccx":0,"hotspot_rank":5}, +{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":52}, +{"p":"./src/verbs_change.h","symbols":1,"churn":69,"ccx":4,"hotspot_rank":23}], +"symbols_total":3146,"symbols_capped":true},"blast_radius":{"reaches":12,"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":12,"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":7,"module_span":6,"max_ccx":38,"sum_ccx":62,"ambiguous_calls":26,"blast_reaches":12,"contract_touches":1,"conflicts":3,"untested":12,"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":107,"l":271,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":10, … [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":2807,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":433,"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":3183,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":433,"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":2886,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":433,"tested":0}, +{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":107,"l":865,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":17,"tested":0}, {"p":"./src/slice.h","n":"sliceRdIterationCeiling","scope":"slicev","key":"449132b302bb4324","id":"./src/slice.h::slicev::sliceRdIterationCeiling","id_addressable":true,"id_collides_with":0,"l":1372,"ord":0,"overloads":1,"amb":0,"cx":9,"ccx":12,"churn":51,"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":38,"tested":0}, -… [64 more display lines; full output is 19299 bytes on 1 raw line(s)] +… [64 more display lines; full output is 19303 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --plan-lanes=99 --task=x` @@ -2926,8 +3074,8 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out ````` - - + + @@ -2955,7 +3103,7 @@ ripwire: --plan-lanes=99 is out of range — N must be 2..16 (1 is not a fan-out - + ````` @@ -2964,7 +3112,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.64s** ````` (empty) @@ -2980,27 +3128,27 @@ 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.33s** +**wall time: 1.15s** ````` - + -
- - - - - - - - - - - + + + + + + + + + + + + - + @@ -3013,34 +3161,34 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped -… [239 more display lines; full output is 24840 bytes on 1 raw line(s)] +… [243 more display lines; full output is 25217 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.86s** +**wall time: 1.08s** ````` - + - - - - - - - - - - - - + + + + + + + + + + + + - + @@ -3053,7 +3201,7 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped -… [265 more display lines; full output is 26039 bytes on 1 raw line(s)] +… [269 more display lines; full output is 26416 bytes on 1 raw line(s)] ````` Tail of the same output — the `` section: @@ -3062,13 +3210,13 @@ Tail of the same output — the `` section: + - + - @@ -3090,28 +3238,28 @@ Tail of the same output — the `` section: *Same report, with git history splitting stale mentions into deleted-by-commit vs never-existed.* -**wall time: 2.95s** +**wall time: 1.51s** ````` - - + + - - - - - - - - - - - - + + + + + + + + + + + + - + @@ -3123,14 +3271,14 @@ Tail of the same output — the `` section: -… [221 more display lines; full output is 25035 bytes on 1 raw line(s)] +… [221 more display lines; full output is 25131 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --from-trace=-` *Map a pasted stack trace onto indexed symbols. CHANGED: in_corpus= now reports the real count (was 0).* -**wall time: 1.77s** +**wall time: 1.10s** Input file: @@ -3138,8 +3286,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:4647 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4688 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:5567 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:5608 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 #3 0x102e01a44 in main src/main.cpp:3983 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) @@ -3147,25 +3295,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)" ); @@ -3184,6 +3332,8 @@ AddressSanitizer:DEADLYSIGNAL *List all field notes (write-side memory) — the committed .ripwire_notes at the repo root, each with the sha/branch it was recorded at.* +**wall time: 1.27s** + ````` @@ -3207,7 +3357,7 @@ AddressSanitizer:DEADLYSIGNAL *ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* -**wall time: 1.34s** +**wall time: 1.98s** ````` @@ -3220,7 +3370,7 @@ AddressSanitizer:DEADLYSIGNAL (run= when derivable). 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). root=: p= relative to it. : rank N in this ranking, rows in r= order. : cy … [line truncated: 546 more bytes on this line] - + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept claude's `mcp add … -- ripwire --mcp`: the flag goes on the end of that command line, ahead of its newlineinline std::string wrapCliAddPost( const AgentTarget& row, std::string_view toolsArg ) @@ -3236,7 +3386,7 @@ AddressSanitizer:DEADLYSIGNAL - + @@ -3247,16 +3397,16 @@ AddressSanitizer:DEADLYSIGNAL *Fan-out form: one shared core + 3 per-agent slices carved along call-graph communities.* -**wall time: 1.40s** +**wall time: 1.89s** ````` - 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 ) @@ -3266,10 +3416,10 @@ AddressSanitizer:DEADLYSIGNAL 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 +inline bool printUsageTier( std::FILE* out, HelpTier tier, std::string_view want ) noexcept @@ -3280,17 +3430,17 @@ AddressSanitizer:DEADLYSIGNAL case McpForm::Json: case McpForm::JsonMcpKey: return true; -… [98 more display lines; full output is 24526 bytes on 105 raw line(s)] +… [70 more display lines; full output is 23531 bytes on 77 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.87s** +**wall time: 2.04s** ````` - @@ -3302,32 +3452,32 @@ 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] What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure -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 … [line truncated: 7 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 … [line truncated: 7 more bytes on this line] inline std::uint32_t sliceRdIterationCeiling() noexcept struct PageRankRun -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 ) -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… -COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "th … [line truncated: 61 more bytes on this line] -inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window -inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept -inline std::string powerShellSingleQuote( std::string_view s ) noexcept -inline std::string path_prepend_hint( std::string_view dir ) -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` -### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] -### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] -## Background — read these, in this order -## Design space and constraints -… [60 more display lines; full output is 9620 bytes on 10 raw line(s)] +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 ) +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… +inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window +inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] +### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] +## Background — read these, in this order +## Design space and constraints +COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "t … [line truncated: 62 more bytes on this line] +… [60 more display lines; full output is 9622 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.90s** +**wall time: 2.35s** ````` (empty) @@ -3336,7 +3486,7 @@ AddressSanitizer:DEADLYSIGNAL Artifact written: ````` -359097 /aux/ripwire2.cc.json +374999 /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 ````` @@ -3344,7 +3494,7 @@ Artifact written: *One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* -**wall time: 16.09s** +**wall time: 16.72s** Input file: @@ -3359,34 +3509,34 @@ 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 McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +struct FileHealth inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vec… -struct McpIndex -inline const McpIndex& getIndex( const std::string& root ) -inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) -### See the map — not just the numbers -inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… -inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) +inline const McpIndex& getIndex( const std::string& root ) +inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) +struct McpIndex +### See the map — not just the numbers +inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… +inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, const CacheLoadStats& cacheStats … [line truncated: 59 more bytes on this line] -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 respect … [line truncated: 29 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 respect … [line truncated: 29 more bytes on this line] +const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) static bool cachePathIsDirectory( const std::string& cachePath ) inline constexpr std::array<std::string_view, 8> kCacheRuleNames = -… [58 more display lines; full output is 24119 bytes on 1 raw line(s)] +… [57 more display lines; full output is 41180 bytes on 1 raw line(s)] ````` @@ -3398,18 +3548,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: 2.45s** +**exit code: 1** — **wall time: 2.70s** ````` - + - - + + - - + @@ -3468,19 +3618,19 @@ 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: 1.75s** +**wall time: 1.94s** ````` - + - - + + - + - + @@ -3494,29 +3644,31 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s ````` -## `./build/ripwire . --rank-by=churn-decay --since=HEAD~9 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3` +## `./build/ripwire . --rank-by=churn-decay --since=HEAD~11 --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.* ````` - + - - - - + + + + - + + + - + - + - + ````` @@ -3542,12 +3694,12 @@ 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 -1104,4686,5332,171,1380,1419,1629 +1165,5606,6252,171,1380,1419,1629 fn,fn,fn,fn,fn,fn,fn 0,0,0,0,0,0,0 @@ -3558,24 +3710,24 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf *Flat top-K export for an external reranker.* -**wall time: 1.36s** +**wall time: 1.22s** ````` - - + + 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 ) @@ -3602,9 +3754,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":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1104"}, -{"t":"fn","n":"rankGraph","p":"src/graph.h:4686"}, -{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:5332"}, +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1165"}, +{"t":"fn","n":"rankGraph","p":"src/graph.h:5606"}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:6252"}, {"t":"fn","n":"runEval","p":"src/eval.h:171"}, {"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1380"}, {"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1419"}, @@ -3631,15 +3783,15 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Pagination: 3 items, skipping the first 3 (deterministic seams).* -**wall time: 1.61s** +**wall time: 1.17s** ````` - - - - + + + + ````` @@ -3648,24 +3800,24 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop test paths from the corpus before ranking.* ````` - + - - + + - + - + - + - + @@ -3676,25 +3828,24 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Drop matching paths (repeatable) before ranking.* ````` - + - - + + - - - - - - + - + - + + + + + @@ -3705,25 +3856,24 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Full map re-ranked with teleport toward git-changed files — recorded against a DIRTY tree, so changed= counts the working copy's files and the teleport is live.* ````` - + - - - - + + + + - - - - + + + - + - - + + @@ -3733,19 +3883,19 @@ 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: 11.57s** +**wall time: 8.44s** ````` - + - + - + - + @@ -3755,19 +3905,19 @@ 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: 11.98s** +**wall time: 9.00s** ````` - + - + - + - + @@ -3776,7 +3926,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m Artifact written: ````` -16621462 /aux/warm2.ripwirecache +18629044 /aux/warm2.ripwirecache ````` ## `./build/ripwire . --max-file-size=8K --top-k=3` @@ -3784,16 +3934,16 @@ Artifact written: *Skip files above a size bound before parsing (note the corpus shrink in the header).* ````` - + - - + + - + - + @@ -3823,30 +3973,29 @@ 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: 9.33s** +**wall time: 8.66s** ````` - + - - + + - - - - - + - - - - + + + - + + + + + @@ -3856,19 +4005,19 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Self-eval: co-change recall vs BM25.* -**wall time: 26.08s** +**wall time: 23.50s** ````` ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 2.4% 6.8% 14.7% - BM25 6.9% 9.2% 13.1% - BM25sub 11.8% 16.8% 25.7% - BM25body 16.6% 30.3% 41.5% - fused 12.1% 15.4% 27.0% - anchored 16.6% 30.3% 41.5% - same-dir 1.2% 5.0% 6.4% - random 0.2% 0.4% 0.8% <- floor (random ranking over F=2381 files) + ripwire 2.9% 7.0% 15.3% + BM25 12.7% 14.9% 18.6% + BM25sub 17.2% 23.2% 27.6% + BM25body 15.1% 28.4% 41.9% + fused 13.0% 16.2% 24.9% + anchored 15.1% 28.4% 41.9% + same-dir 0.0% 8.8% 9.8% + random 0.2% 0.4% 0.8% <- floor (random ranking over F=2484 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 / @@ -3884,23 +4033,23 @@ ripwire --eval (co-change recovery, averaged over 80 historical commits) *Known-item retrieval eval: MRR + recall@k per ranker per query mode.* -**wall time: 156.31s** +**wall time: 118.34s** ````` ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=5624 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + sample: population=5818 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.697 56.3% 86.2% 91.1% - subtoken doc-phrase 0.922 90.2% 94.2% 95.2% - name-exact name 0.912 84.6% 96.3% 97.7% - name-exact doc-phrase 0.011 0.4% 1.6% 2.3% - anchored name 0.705 58.2% 85.9% 89.8% - anchored doc-phrase 0.918 89.6% 94.2% 94.8% - routed name 0.914 84.9% 96.5% 97.9% - routed doc-phrase 0.921 90.1% 94.0% 94.9% + subtoken name 0.693 56.0% 85.7% 90.7% + subtoken doc-phrase 0.920 90.0% 94.0% 94.9% + name-exact name 0.905 83.5% 96.1% 97.4% + name-exact doc-phrase 0.013 0.5% 1.8% 2.6% + anchored name 0.699 57.6% 85.2% 89.1% + anchored doc-phrase 0.917 89.5% 94.0% 94.5% + routed name 0.908 83.7% 96.3% 97.7% + routed doc-phrase 0.919 90.0% 93.8% 94.7% note: routing chose name-exact on 3986/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 — @@ -3913,7 +4062,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: 1.59s** +**exit code: 3** — **wall time: 1.28s** Input file: @@ -3926,9 +4075,9 @@ sync-upstream merged ````` - + - + ````` @@ -4023,7 +4172,7 @@ Reach for it BEFORE blind grep + whole-file reads. First call ~1s cold; after th *Version + short build info.* ````` -ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=c9c1df83b) +ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=66e5c3e10) ````` @@ -4031,27 +4180,27 @@ ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=c9c1df83b) # navigate — seeds, claims, slices, shapes -## `./build/ripwire . --at=src/graph.h:4647` +## `./build/ripwire . --at=src/graph.h:5567` *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:4647` +## `./build/ripwire . --callers=@src/graph.h:5567` *The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* ````` - - - - + + + + @@ -4072,20 +4221,18 @@ ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=c9c1df83b) stderr: ````` -ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph.h has only 8498 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 9418 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.06s** - ````` - + - + ````` @@ -4093,20 +4240,18 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A claim that is FALSE — the refuted shape, with the references that refute it.* -**wall time: 1.06s** - ````` - + - - + + - + ````` @@ -4114,8 +4259,6 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *A literal-scan absence: refuted only with complete= evidence, never on a partial scan.* -**wall time: 1.33s** - ````` @@ -4126,7 +4269,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. *An unparseable claim — the refusal names the accepted shapes.* -**exit code: 1** — **wall time: 1.16s** +**exit code: 1** ````` (empty) @@ -4144,21 +4287,21 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ````` @@ -4168,17 +4311,17 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4190,26 +4333,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 )]]> @@ -4221,38 +4364,38 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + - + - + - + - + - + 0.0 )]]> - + - + @@ -4296,17 +4439,17 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < ````` - - + + - + teleport( pw.begin(), pw.end() );]]> - + - + @@ -4316,12 +4459,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: 5.53s** +**wall time: 3.71s** ````` 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 ) ````` @@ -4329,11 +4472,11 @@ 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: 5.92s** +**wall time: 4.56s** ````` -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 @@ -4342,7 +4485,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < DISCLOSE( ex, Exclusions::DisclosureWhy::StmtCrementSaturated, "atoms: the statement-crement exclusion stream spent its DISCLOSE( ex, Exclusions::DisclosureWhy::StmtAssignSaturated, "atoms: the statement-assign exclusion stream spent its wh DISCLOSE( ex, Exclusions::DisclosureWhy::InnerCommaSaturated, "atoms: the inner-comma exclusion stream spent its whole b -DISCLOSE( st, Type3Stats::DisclosureWhy::PairCapHit, "clones: Type-3 pair cap hit — first N compared (both-gate-surviv +DISCLOSE( st, Type3Stats::DisclosureWhy::PairCapHit, "clones: Type-3 pair cap hit — first N compared (both-gate-surviv DISCLOSE( scan, CommentCoherenceScan::DisclosureWhy::UnreadableFile, "comment-coherence: an indexed file could not be re DISCLOSE( st, StreamBlobStats::DisclosureWhy::ListUnwritable, "crossref: cannot write the blob-batch list — cross-bran DISCLOSE( st, StreamBlobStats::DisclosureWhy::BatchNotStarted, "crossref: git cat-file --batch failed to start — cross @@ -4369,7 +4512,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *A pattern that collapses to a bare token is REFUSED — never reported as hits=0.* -**exit code: 1** — **wall time: 1.04s** +**exit code: 1** ````` (empty) @@ -4388,47 +4531,45 @@ ripwire: --pattern: this pattern collapses to a single bare token, which is a TE ````` inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar… -enum class AstWalk : std::uint8_t +struct FileHealth +enum class AstWalk : std::uint8_t +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 -inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept -constexpr std::array<LangEntry, 51> kLangTable = +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::array<LangEntry, 51> kLangTable = constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept 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 ) std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] -bool jsonNestsTooDeep( std::string_view bytes ) noexcept +bool jsonNestsTooDeep( std::string_view bytes ) noexcept constexpr std::uint32_t kMaxYamlNestDepth = 64u SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) inline bool hasMainGuard( std::string_view source ) inline void runParseWorker( ParsePoolShared& sh, unsigned t ) inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) -struct LintRule +struct LintRule enum class DisclosureWhy : std::uint8_t -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) … [55 more display lines; full output is 9066 bytes on 1 raw line(s)] ````` @@ -4677,120 +4812,120 @@ 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.48s** +**wall time: 1.14s** ````` - + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar… -enum class AstWalk : std::uint8_t +struct FileHealth +enum class AstWalk : std::uint8_t +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 -inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept -constexpr std::array<LangEntry, 51> kLangTable = +inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept +constexpr std::array<LangEntry, 51> kLangTable = constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept 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 ) std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] -bool jsonNestsTooDeep( std::string_view bytes ) noexcept +bool jsonNestsTooDeep( std::string_view bytes ) noexcept constexpr std::uint32_t kMaxYamlNestDepth = 64u SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) inline bool hasMainGuard( std::string_view source ) inline void runParseWorker( ParsePoolShared& sh, unsigned t ) inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) -struct LintRule +struct LintRule enum class DisclosureWhy : std::uint8_t -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [90 more display lines; full output is 12641 bytes on 55 raw line(s)] +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +… [90 more display lines; full output is 12638 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.62s** +**wall time: 1.32s** ````` - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) - +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - + H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… -struct DeltaBasis -inline const char* kAcksFile = ".ripwire_quality_acks" -DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) -std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) -struct QualityDeltaOutcome -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -inline std::string qualityAcksPath( const std::string& root ) -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at -inline std::string gitHeadSha( const std::string& root ) -inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +struct QualityDeltaOutcome +inline std::string qualityAcksPath( const std::string& root ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) -inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) -int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) -inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] -inline std::string qualityBaselinePath( const std::string& root ) -… [52 more display lines; full output is 9268 bytes on 1 raw line(s)] +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) +… [52 more display lines; full output is 9269 bytes on 1 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.51s** +**wall time: 1.13s** ````` - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) - +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - + H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… -struct DeltaBasis -inline const char* kAcksFile = ".ripwire_quality_acks" -DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) -std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) -struct QualityDeltaOutcome -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -inline std::string qualityAcksPath( const std::string& root ) -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at -inline std::string gitHeadSha( const std::string& root ) -inline gtl::btree_map<std::string, AckRecord> readAckRecords( const std::string& path, std::size_t& badLines ) +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +struct QualityDeltaOutcome +inline std::string qualityAcksPath( const std::string& root ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) -inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) -int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) -inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] -inline std::string qualityBaselinePath( const std::string& root ) -… [53 more display lines; full output is 9441 bytes on 1 raw line(s)] +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) +… [53 more display lines; full output is 9435 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --safe-delete=rankGraphTeleport` @@ -4799,14 +4934,14 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? ````` - - - + + - + ````` @@ -4830,22 +4965,46 @@ 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 DIRTY tree.* -**wall time: 2.04s** - -````` - - - - +**wall time: 1.67s** + +````` + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + ````` @@ -4854,19 +5013,34 @@ 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.84s** - -````` - - - - +**wall time: 1.49s** + +````` + + + + + + + + + + + + + + + + + + + + + + - - + ````` @@ -4875,14 +5049,12 @@ ripwire: --safe-delete symbol not found: DoesNotExist *WHY a file is not in the index (oversize / excluded / unsupported-ext / gitignored) and which indexed files it cannot vouch for (degraded-parse, minified-suspect), plus the per-language census.* -**wall time: 1.86s** - ````` - @@ -4908,22 +5080,20 @@ ripwire: --safe-delete symbol not found: DoesNotExist -… [391 more display lines; full output is 41837 bytes on 1 raw line(s)] +… [399 more display lines; full output is 42579 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --no-ignore --top-k=3` *Crawl paths the repo's own .gitignore covers (default honours it and discloses ignored_files=/ignored_dirs= only when it dropped anything — this repo's crawl drops nothing, so the header is identical to the default map's; --skipped's ignore_mode= says which rule applied).* -**wall time: 1.82s** - ````` - + - - + + - + @@ -4938,16 +5108,16 @@ 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.* ````` - + - + - + - + @@ -4966,15 +5136,15 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o **exit code: 4** ````` - + - + const&, float) src/graph.h:4647 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4688 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:5567 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:5608 #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 #3 0x102e01a44 in main src/main.cpp:3983 #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_DESCR … [line truncated: 480 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_DESCR … [line truncated: 480 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.03s** - ````` @@ -5025,12 +5193,12 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o *A command still running at the cap: its process group is killed and the run reports timed_out=1 — an honest timeout, never an empty success.* -**exit code: 4** — **wall time: 2.90s** +**exit code: 4** — **wall time: 2.53s** ````` - + ````` @@ -5065,59 +5233,59 @@ 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: 10.76s** +**wall time: 6.68s** ````` - - + - + - - - - - + + + + + - - + + - + - -… [186 more display lines; full output is 15835 bytes on 1 raw line(s)] + +… [186 more display lines; full output is 15833 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.40s** +**wall time: 1.02s** ````` - - + + - + - + ````` @@ -5125,15 +5293,13 @@ 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.08s** - ````` - + - + @@ -5145,39 +5311,37 @@ 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.25s** - ````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + … [21 more display lines; full output is 11207 bytes on 1 raw line(s)] ````` @@ -5185,11 +5349,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: 6.18s** +**wall time: 3.33s** ````` - @@ -5218,58 +5382,58 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir -… [41 more display lines; full output is 7592 bytes on 1 raw line(s)] +… [41 more display lines; full output is 7593 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: 5.85s** +**wall time: 3.97s** ````` - - + - + - + - + - + - + - + -… [36 more display lines; full output is 7416 bytes on 1 raw line(s)] +… [36 more display lines; full output is 7433 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: 5.15s** +**wall time: 3.79s** ````` - @@ -5305,14 +5469,14 @@ 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: 5.10s** +**wall time: 3.23s** ````` - + - + @@ -5320,11 +5484,12 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir + - - + + @@ -5340,35 +5505,33 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The corpus's OWN case-convention vote per (language, kind) group; off-convention names get a mechanical propose= (a suggestion, never a blind rename).* -**wall time: 1.06s** - ````` - - - - + + + + - + - + - + - + - + - - + + @@ -5380,11 +5543,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: 13.13s** +**wall time: 9.11s** ````` - + @@ -5404,53 +5567,53 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir

-

+

-

+

-… [108 more display lines; full output is 12164 bytes on 1 raw line(s)] +… [119 more display lines; full output is 12984 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: 10.61s** +**wall time: 6.82s** ````` - - + + - + - + - - + + - - + + - + - - - -… [643 more display lines; full output is 72219 bytes on 1 raw line(s)] + + + +… [641 more display lines; full output is 72400 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --lint-catalog` @@ -5496,7 +5659,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=3eabbad50f0ec36c entries=778 +ripwire legend dictionary ripwire.dict/v1 dictv=ebae13b851e3a641 entries=791 : 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=) @@ -5526,24 +5689,24 @@ ripwire.uses/v1 : resolvable use-sites of of=: : transitive blast radius of of=: reach set, importers; defs= matched, reaches= their transitive callers, radius_tested= non-tests an indexed test reaches, radius_untested= the rest; importers= files that #include/import a def's file ripwire.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, -… [749 more display lines; full output is 77207 bytes on 779 raw line(s)] +… [762 more display lines; full output is 78760 bytes on 792 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: 9.55s** +**wall time: 6.48s** ````` - + - - + + - + vector<std::vector<double>> @@ -5566,37 +5729,37 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal g_pool[ g_readOf[i] ] g_pool[ g_readOf[i] ] m[ g_pool[i] ] -… [497 more display lines; full output is 60049 bytes on 1 raw line(s)] +… [507 more display lines; full output is 61305 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: 9.93s** +**wall time: 6.32s** ````` - - + + - + - + - - + + - + - + @@ -5606,14 +5769,14 @@ ripwire.connect/v1 : minimal joining subgraph: groups, terminal 20 delta < 0.02 && delta > -0.02 0.02 -… [729 more display lines; full output is 65885 bytes on 1 raw line(s)] +… [729 more display lines; full output is 65783 bytes on 1 raw line(s)] ````` ## `./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: 9.82s** +**exit code: 1** — **wall time: 6.51s** ````` (empty) @@ -5629,7 +5792,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: 10.00s** +**exit code: 1** — **wall time: 6.35s** ````` (empty) @@ -5645,7 +5808,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: 10.10s** +**wall time: 6.24s** ````` {"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] @@ -5678,13 +5841,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}}, -… [9873 more display lines; full output is 1382873 bytes on 1 raw line(s)] +… [9999 more display lines; full output is 1400831 bytes on 1 raw line(s)] ````` Parsed summary of the same SARIF (past the display cut): ````` -sarif 2.1.0 rules= 39 results= 4932 +sarif 2.1.0 rules= 39 results= 4995 ````` ## `./build/ripwire . --lint --sarif --limit=5` @@ -5707,11 +5870,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 DIRTY tree (the sandbox section shows a real delta). UNAVAILABLE is a stated reason, never 0 or 1.* -**wall time: 5.32s** +**wall time: 4.28s** ````` -

@@ -5722,11 +5885,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: 24.62s** +**wall time: 17.85s** ````` -

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

@@ -5752,81 +5915,81 @@ 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.85s** +**wall time: 1.31s** ````` - - + + - + + - + - + - + - - - - - - -… [708 more display lines; full output is 46546 bytes on 1 raw line(s)] + + + + + +… [727 more display lines; full output is 47863 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: 1.65s** +**wall time: 1.68s** ````` - - - + - - - - - + + - - + + + + + - - + - + + - - - + + + + - - + + - - + + + ````` @@ -5834,7 +5997,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.87s** +**wall time: 1.26s** ````` (empty) @@ -5843,14 +6006,14 @@ ripwire: --sarif always emits the full result set — drop --limit=N/--offset=M Artifact written: ````` -158477 /aux/map2.html +158957 /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: 26.09s** +**wall time: 18.93s** ````` (empty) @@ -5859,35 +6022,33 @@ Artifact written: stderr: ````` -ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (16621462 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (36381791 bytes, rich family) +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (18629044 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (39954556 bytes, rich family) ````` Artifact written: ````` -16621462 /aux/ci_index.lean.ripwirecache -36381791 /aux/ci_index.rich.ripwirecache -53003253 total +18629044 /aux/ci_index.lean.ripwirecache +39954556 /aux/ci_index.rich.ripwirecache +58583600 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.06s** - ````` - + - + - + - + @@ -5898,16 +6059,16 @@ Artifact written: *Eval-only: a per-call-site census of WHICH mechanism resolved each call, and the canonical id of every surviving target.* ````` - + - + - + - + @@ -5924,7 +6085,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. -110357 /aux/pin_census.tsv +114767 /aux/pin_census.tsv ````` ## `./build/ripwire . --plan-lint=test/planlintfix/wave.md` @@ -5935,7 +6096,7 @@ Artifact written: ````` - + @@ -5957,7 +6118,7 @@ Artifact written: ````` - + @@ -5969,18 +6130,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: 2.31s** +**exit code: 1** — **wall time: 1.48s** ````` - + - - + + - - + @@ -6023,14 +6184,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: 14.18s** +**wall time: 8.96s** ````` ripwire situational-awareness — 1 changed file(s), 13 symbols in them root: . -at: 52c4a428f+dirty - [1] blast radius: 190 symbols across 45 files transitively depend on these changes (showing 8 of 45 files — shown=8 total=45 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=45) - counts_floor=1 graph_ambiguous=9977 graph_unresolved=13331 graph_unindexed=231 (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] +at: 6bf9d9d02+dirty + [1] blast radius: 193 symbols across 46 files transitively depend on these changes (showing 8 of 46 files — shown=8 total=46 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=46) + counts_floor=1 graph_ambiguous=10274 graph_unresolved=12861 graph_unindexed=237 (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 missin … [line truncated: 46 more bytes on this line] src/mcpverbs.h (32 dependent symbols) src/slice.h (30 dependent symbols) src/serialize.h (9 dependent symbols) @@ -6045,9 +6206,9 @@ at: 52c4a428f+dirty test/includeprecise_unit.cpp [hops=2] (run: bash test/includeprecisecheck.sh) test/verify_csr.cpp [hops=2] (run: bash test/a9disclosurecheck.sh) test/rustimport_unit.cpp [hops=4] (run: bash test/rustimportprecisecheck.sh) - script_gates_unmodelled=725 — 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="3814": - (none — 3814 commits were mined and none co-edited a file outside your diff) + script_gates_unmodelled=729 — 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="3919": + (none — 3919 commits were mined and none co-edited a file outside your diff) next: --test-gate ````` @@ -6055,41 +6216,41 @@ at: 52c4a428f+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: 2.87s** +**exit code: 4** — **wall time: 1.28s** ````` - - + 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). --> + - + - - + + - - + + - - - + + + - + ````` @@ -6097,11 +6258,11 @@ at: 52c4a428f+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: 64.53s** +**exit code: 2** — **wall time: 43.52s** ````` - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. next=: the one pasteable follow-up. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. r surface=: the api-surface tier, new-symbol or contract-change. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. r sev=minor: a small numeric delta, counted in minor=, never gating (absent: major). r origin=new-symbol: the finding is on NEW code, never gating (absent: preexisting-worse). sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). r members=/tokens=: a duplication row's clone group (member ids) / their shared normalized-token count. --> + @@ -6130,7 +6291,7 @@ at: 52c4a428f+dirty -… [104 more display lines; full output is 14236 bytes on 1 raw line(s)] +… [113 more display lines; full output is 15115 bytes on 1 raw line(s)] ````` stderr: @@ -6144,11 +6305,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, but the ~4 KB prose legend the compact default trims is restored: the posture a human reads once to learn the schema.* -**exit code: 2** — **wall time: 19.86s** +**exit code: 2** — **wall time: 12.92s** ````` - - + @@ -6177,7 +6338,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap -… [104 more display lines; full output is 18831 bytes on 1 raw line(s)] +… [113 more display lines; full output is 19929 bytes on 1 raw line(s)] ````` stderr: @@ -6191,10 +6352,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: 19.82s** +**exit code: 2** — **wall time: 12.95s** ````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":122,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"52c4a428f+dirty","renames":57,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 187 more bytes on this line] +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":131,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"6bf9d9d02+dirty","renames":58,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 211 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"}, @@ -6224,7 +6385,7 @@ ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: ap {"kind":"api-surface:new-symbol","key":"79625906f9f71ad0","why":"target-gone"}, {"kind":"api-surface:new-symbol","key":"8ecb190954dce18a","why":"target-gone"}, {"kind":"api-surface:new-symbol","key":"995375dfa4e63104","why":"target-gone"}, -… [101 more display lines; full output is 12942 bytes on 1 raw line(s)] +… [110 more display lines; full output is 13709 bytes on 1 raw line(s)] ````` stderr: @@ -6238,7 +6399,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: 11.08s** +**exit code: 1** — **wall time: 7.44s** ````` (empty) @@ -6248,7 +6409,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, 232 re-derived, 1 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1328 ineligible (no cur … [line truncated: 198 more bytes on this line] +ripwire: ack provenance backfill — 0 clone row(s) reconstructed from the current tree, 224 re-derived, 9 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1328 ineligible (no cur … [line truncated: 198 more bytes on this line] ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written ````` @@ -6256,7 +6417,7 @@ ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written *NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* -**wall time: 11.13s** +**wall time: 7.60s** ````` (empty) @@ -6266,7 +6427,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, 232 re-derived, 1 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1328 ineligible (no cur … [line truncated: 198 more bytes on this line] +ripwire: ack provenance backfill — 0 clone row(s) reconstructed from the current tree, 224 re-derived, 9 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1328 ineligible (no cur … [line truncated: 198 more bytes on this line] ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already acked) → ./.ripwire_quality_acks ````` @@ -6274,11 +6435,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: 19.39s** +**exit code: 2** — **wall time: 12.82s** ````` - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. next=: the one pasteable follow-up. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. r sev=minor: a small numeric delta, counted in minor=, never gating (absent: major). r origin=new-symbol: the finding is on NEW code, never gating (absent: preexisting-worse). sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). r members=/tokens=: a duplication row's clone group (member ids) / their shared normalized-token count. --> + @@ -6307,7 +6468,7 @@ ripwire: acknowledged 1 of 9 finding(s) (8 left UNACKED by --ack-only, 0 already -… [103 more display lines; full output is 13939 bytes on 1 raw line(s)] +… [112 more display lines; full output is 14818 bytes on 1 raw line(s)] ````` stderr: @@ -6337,11 +6498,9 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6353,11 +6512,9 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row identical to the compact default above, but the ~5.7 KB explanatory legend is restored — the posture for a first-time reader.* -**wall time: 1.40s** - ````` - @@ -6369,13 +6526,13 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - - + @@ -6391,31 +6548,29 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire + - -… [86 more display lines; full output is 8379 bytes on 1 raw line(s)] +… [87 more display lines; full output is 8459 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.07s** - ````` - + - - + + @@ -6449,21 +6604,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. --> - + - + - + @@ -6478,7 +6633,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - + @@ -6489,11 +6644,9 @@ 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. unknown=N: refs that could not be analysed (v=unknown, e.g. no merge base); never counted merged. --> - + ````` @@ -6501,11 +6654,9 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row rather than silently dropping them.* -**wall time: 2.06s** - ````` - + ````` @@ -6513,11 +6664,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire . at=: commit+dirty+shallow. available=0: no score at all (dmm=UNAVAILABLE, reason= says why); never read as 1.000 or 0.000. combine=pooled: root dmm= is summed good over summed good+bad of the 3 properties (ripwire's own). low_loc=/low_cx=/low_params=: a unit is LOW risk at or under these lines / cyclomatic / params. base_units=/base_volume=/target_units=/target_volume=: units with a body and their line span per side. --> -

@@ -6528,11 +6679,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire with a do-not-ack banner and never gate — scoped-out-gating= says how many would have.* -**wall time: 19.31s** +**wall time: 12.73s** ````` - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. r surface=: the api-surface tier, new-symbol or contract-change. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. r sev=minor: a small numeric delta, counted in minor=, never gating (absent: major). r origin=new-symbol: the finding is on NEW code, never gating (absent: preexisting-worse). sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). r members=/tokens=: a duplication row's clone group (member ids) / their shared normalized-token count. --> + @@ -6561,7 +6712,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire -… [106 more display lines; full output is 14206 bytes on 1 raw line(s)] +… [115 more display lines; full output is 15085 bytes on 1 raw line(s)] ````` stderr: @@ -6574,7 +6725,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: 11.04s** +**exit code: 1** — **wall time: 7.76s** ````` (empty) @@ -6594,12 +6745,12 @@ 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.39s** +**wall time: 1.46s** ````` - - + + @@ -6623,11 +6774,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr - + + - - - + + ````` @@ -6637,7 +6788,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-03 keep this branch-free — it sits inside the PageRank sort comparator 52c4a428f0ea2e42ff4e0a855bf5d5e206647b8f sync-rebase +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-07 keep this branch-free — it sits inside the PageRank sort comparator 6bf9d9d024e0c50a13dc78c77480f7fcd02faf10 sync-rebase ````` stderr: @@ -6662,7 +6813,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort - + @@ -6714,7 +6865,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-03 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 52c4a428f0ea2e42ff4e0a855bf5d5e206647b8f sync-rebase +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-07 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 6bf9d9d024e0c50a13dc78c77480f7fcd02faf10 sync-rebase ````` ## `./build/ripwire . --expand=lessByScoreDescId --top-k=0` @@ -6758,10 +6909,10 @@ src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-03 chose the flat The element on the same output — past the 30-line display cut above: ````` - + - + ````` @@ -6786,7 +6937,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: 18.06s** +**wall time: 12.01s** Input file: @@ -6803,12 +6954,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":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"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,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -6821,15 +6972,13 @@ 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** - ````` - + - + @@ -6843,16 +6992,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.89s** +**wall time: 2.37s** ````` {"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":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"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,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -6865,7 +7014,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.88s** +**wall time: 1.00s** ````` {"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] @@ -6881,19 +7030,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.38s** +**wall time: 2.46s** ````` {"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":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"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,"tests":5,"script_gates_unmodelled":725,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ````` stderr: @@ -6906,7 +7055,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.89s** +**exit code: 1** ````` (empty) @@ -6922,7 +7071,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.88s** +**wall time: 1.03s** Input file: @@ -6940,21 +7089,21 @@ Input file: ````` ````` -{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false," … [line truncated: 301 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false, … [line truncated: 302 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.93s** +**wall time: 2.18s** ````` -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9974,"graph_unresolved":13331,"graph_unindexed":231,"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: 485 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 486 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":9974,"graph_unresolved":13331,"graph_unindexed":231,"counts_floor":true}}]} +"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true}}]} ````` ## `./build/ripwire . --edit-plan=/aux/edit_plan.json` @@ -6977,11 +7126,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: 19.87s** +**exit code: 2** — **wall time: 13.15s** ````` - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. next=: the one pasteable follow-up. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. r origin=new-symbol: the finding is on NEW code, never gating (absent: preexisting-worse). sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). r members=/tokens=: a duplication row's clone group (member ids) / their shared normalized-token count. --> + @@ -7009,8 +7158,8 @@ ripwire: --edit-plan requires exactly one of --dry-run or --apply - -… [100 more display lines; full output is 13303 bytes on 1 raw line(s)] + +… [109 more display lines; full output is 14182 bytes on 1 raw line(s)] ````` stderr: @@ -7024,7 +7173,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: 11.09s** +**exit code: 1** — **wall time: 7.41s** ````` (empty) @@ -7043,7 +7192,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: 18.79s** +**wall time: 12.80s** ````` (empty) @@ -7053,16 +7202,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 22641 per-symbol rows, 23350 indexed symbols total) +ripwire: wrote ./.ripwire_quality_baseline (snapshot of 23111 per-symbol rows, 23826 indexed symbols total) ````` Artifact written: ````` -3793533 .ripwire_quality_baseline +3871406 .ripwire_quality_baseline # ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit -producer 4e28d3568d96e30bcf8d8eded82296e879ed635c73572e9fe01cd38908126ee3 -head 52c4a428f0ea2e42ff4e0a855bf5d5e206647b8f +producer 85ecb2524b1660dc253d710de89497b18141d2cf6cf5740e726547090d74c7ae +head 6bf9d9d024e0c50a13dc78c77480f7fcd02faf10 dirty 1 absorbed 2 ccx d19d7c13cbc7 2 @@ -7075,11 +7224,11 @@ ccx 7d8862be4e *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: 19.27s** +**wall time: 12.93s** ````` - -, acked. value-ref-excluded=N: internal functions kept off because a table, field or argument holds them (matched by name, not a proven call); a floor. at=: commit+dirty+shallow. stale=N: ack ledger rows whose target no longer applies (sa rows); never gating. preexisting-worse=N: regressions on symbols that existed at baseline; only these gate (when major). new-symbol=N: regressions on NEW code; never gate, but the debt is yours: read them. register-macro-excluded=N: symbols kept out of dead-code as self-registering test/bench macros; a floor. api-new-surface=N: new PUBLIC symbols; a count, never gates, not in regressions=. renames=/rename_window_commits=: git rename pairs read over that many commits, to re-file baseline and acks. acked_by_rename=/acked_by_content=: acked= suppressions matched via git renames / an equal body hash. renames_window_truncated=1: history is deeper than the rename window, older renames unread. acked=N: findings suppressed by the ack ledger, listed as sa rows; never gating. sa key=/why=: the stale ack's ledger hash / target-gone (names nothing now) or finding-gone (no longer fires). --> + @@ -7102,13 +7251,13 @@ ccx 7d8862be4e - - + + - -… [95 more display lines; full output is 12168 bytes on 1 raw line(s)] + +… [104 more display lines; full output is 13047 bytes on 1 raw line(s)] ````` @@ -7124,9 +7273,9 @@ ccx 7d8862be4e {"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":{"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: 1111 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: 874 more bytes on this line] -{"name":"grep","description":"Trigram literal search; each hit annotated with its enclosing symbol (which function/class it is in). pattern = the literal; in=any lifts the span tiering (by default the tightest non-empty tier is served and what was held back rides as suppressed_comment/suppressed_str … [line truncated: 827 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] @@ -7160,7 +7309,7 @@ ccx 7d8862be4e The manifest, summarised (name / description bytes / required args) — what the host pays in context every session: ````` -tools= 33 manifest_bytes= 46799 (~tokens at 4 bytes/token: 11699 ) +tools= 33 manifest_bytes= 46950 (~tokens at 4 bytes/token: 11737 ) explore desc_bytes= 1322 schema_bytes= 958 required=['task'] batch desc_bytes= 1235 schema_bytes= 962 required=['queries'] edit_check desc_bytes= 1180 schema_bytes= 889 required=['symbol'] @@ -7173,19 +7322,19 @@ situational_awareness desc_bytes= 865 schema_bytes= 581 required=[] whereis desc_bytes= 701 schema_bytes= 714 required=['symbol'] affected desc_bytes= 790 schema_bytes= 610 required=['files'] from_trace desc_bytes= 829 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=[] -find_symbol desc_bytes= 682 schema_bytes= 590 required=['symbol'] rank_by desc_bytes= 758 schema_bytes= 481 required=[] for desc_bytes= 445 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=[] -find_referencing_symbols desc_bytes= 434 schema_bytes= 590 required=['symbol'] -grep desc_bytes= 435 schema_bytes= 562 required=['pattern'] +grep desc_bytes= 445 schema_bytes= 562 required=['pattern'] memory_recall desc_bytes= 447 schema_bytes= 500 required=['task'] quality_delta desc_bytes= 772 schema_bytes= 193 required=[] path_between desc_bytes= 357 schema_bytes= 506 required=['from', 'to'] @@ -7200,72 +7349,72 @@ quality_baseline desc_bytes= 407 schema_bytes= 193 required=[] *MCP `for`: always bundle=sigs (never the CLI's compact route), the same ranked signatures as --for.* -**wall time: 1.72s** +**wall time: 1.59s** ````` {"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":" - - - + + + + + + + + + - + - - - + - - - -... [7 more line(s); run it to see the whole thing] +... [10 more line(s); run it to see the whole thing] ``` **Shaped by:** `--token-budget`, `--for`, `--recall`, `--graph-query`, `--pack-signatures`, `--expand`, `--from-trace`, `--run-trace` @@ -104,18 +104,24 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ ``` $ ./build/ripwire . --max-tokens=1500 - - - - + + + + + + + + + + - + - +... [1 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace` +**Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace`, `--limit` **Caveats (stated by the binary):** @@ -135,8 +141,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`, `--recall`, `--handoff`, `--pr-context`, `--from-trace`, `--run-trace` @@ -159,9 +164,9 @@ _The honest half of the contract: a task with no ripwire-shaped evidence ABSTAIN ``` $ ./build/ripwire . --help-task="write a cheerful release announcement" - - - + + + ``` @@ -181,20 +186,21 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - + + + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` - -... [18 more line(s); run it to see the whole thing] + +& p, float alpha = 0.85f ) +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--top-k`, `--max-tokens`, `--token-budget`, `--sections`, `--signatures-only`, `--auto-bodies`, `--no-route`, `--adaptive` @@ -225,14 +231,14 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - - - + + + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -257,19 +263,20 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - - - - -inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) - + + + + + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) - +TSTree* parseTree( TSParser* parser, std::string_view src ) + Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -enum class AstWalk : std::uint8_t -... [19 more line(s); run it to see the whole thing] + +errNodes/errBytes are a PARSER-STATE fact, never a syntax verdict: tree-sitter error recovery fi…struct FileHealth +enum class AstWalk : std::uint8_t +constexpr std::array<LangEntry, 48> kLangTable = +... [18 more line(s); run it to see the whole thing] ``` **Shaped by:** `--for` @@ -291,18 +298,19 @@ _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] +struct RankedGraph +... [19 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -323,18 +331,20 @@ _Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothin ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --adaptive - - - - -inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) - + + + + + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) - +TSTree* parseTree( TSParser* parser, std::string_view src ) + Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth -... [20 more line(s); run it to see the whole thing] + +errNodes/errBytes are a PARSER-STATE fact, never a syntax verdict: tree-sitter error recovery fi…struct FileHealth +enum class AstWalk : std::uint8_t +constexpr std::array<LangEntry, 48> kLangTable = +... [18 more line(s); run it to see the whole thing] ``` **Shaped by:** `--for`, `--detail` @@ -355,16 +365,16 @@ _Same task with the anchor disabled — the contrast the flag exists for._ ``` $ ./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25" --no-mention-boost - - - - + + + + The name-exact ranker AS THE RETRIEVAL LENS SERVES IT: whole-name BM25 plus the definition-over-…inline std::vector<float> lexicalScoresNameExactRanked( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) - + P11.12: the interpretive footer for --eval's ranker table, pulled into its own function so the 9…inline void printEvalRankerNote() -inline int runEvalRetrieval( const IngestResult& ing, const Graph& g ) - +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 ... [20 more line(s); run it to see the whole thing] ``` @@ -387,16 +397,16 @@ _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 - - - - -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - + + + + +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) - -H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… + +WHAT this delta is measured AGAINST, and on WHICH tree — the one place --quality-delta decides…struct DeltaBasis ... [21 more line(s); run it to see the whole thing] ``` @@ -416,9 +426,10 @@ _Interface -> implementors view: every existing impl of the named interface; the ``` $ ./build/ripwire . --lego=Vehicle - - - + + + + @@ -446,11 +457,10 @@ _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 ) { // A fixed char[] must arrive as rw::cstr( buf ). printf's %s always meant "bytes to the first NUL"; @@ -460,7 +470,8 @@ $ ./build/ripwire . --exemplar="format byte sizes for humans" // 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> -... [4 more line(s); run it to see the whole thing] + && !std::is_const_v>> ) ), +... [3 more line(s); run it to see the whole thing] ``` **Shaped by:** `--compress`, `--metrics`, `--doctor`, `--limit`, `--index-out` @@ -477,9 +488,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" — 113 relevant of 210 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5489 +ripwire recall — "quality delta gating exit codes" — 107 relevant of 200 document files, best-first — total=107 shown=8 capped=1 truncated=6 generated_demoted=3 max_tokens=8000 share_bytes=2337 est_tokens=5372 -━━ README.md (relevance 7.966) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 213934 B; lines="2707-2719,2720-2727"; dropped_by_budget=23] +━━ README.md (relevance 8.863) ━━ [sections: 2 of 21 selected (60 in doc), section-granular; whole doc 180569 B; lines="2426-2437,2438-2445"; dropped_by_budget=19] #### 6.2 Exit codes This is the part to wire into a script. @@ -487,7 +498,7 @@ 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. | +| 1 | The command refused the request. A refusal names the reason on stderr. | | 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. | @@ -542,20 +553,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 1438117-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 1870195-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=2041 est_tokens=951 -━━ commands.md (relevance 10.246) ━━ [sections: 1 of 155 selected (185 in doc), section-granular; whole doc 533554 B; lines="3654-3658"; dropped_by_budget=154] [truncated: 1865 of 7738 bytes] +━━ commands.md (relevance 9.817) ━━ [sections: 1 of 151 selected (180 in doc), section-granular; whole doc 703917 B; lines="3641-3645"; dropped_by_budget=150] [truncated: 1865 of 7256 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)) +(capped: 1 of 2 relevant document files omitted — raise --max-tokens or narrow the query for 1 more (~2548-byte budget)) ``` ### `--tree` @@ -570,20 +581,20 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - - + + + + - - + - - + + - - + ... [17 more line(s); run it to see the whole thing] ``` @@ -642,21 +653,21 @@ _Stable (path/id) emit order — provider KV-cache hits across re-runs._ ``` $ ./build/ripwire . --order=stable --top-k=5 - - - - - - - + + + + + + + + - - - + -... [7 more line(s); run it to see the whole thing] + +... [10 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-stable` @@ -673,19 +684,21 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable ``` $ ./build/ripwire . --no-stable --top-k=3 - - - - + + + + + + + + + - + - - - + - - +... [2 more line(s); run it to see the whole thing] ``` --- @@ -704,20 +717,20 @@ _Ego graph around one symbol — depth 1 BY DEFAULT now (the root's depth= says ``` $ ./build/ripwire . --around=rankGraphTeleport - - + + + + + + - - + + + - - - - - ... [17 more line(s); run it to see the whole thing] ``` @@ -735,8 +748,7 @@ _Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distan ``` $ ./build/ripwire . --callers=DoesNotExist - - + ``` **Shaped by:** `--no-route`, `--callees`, `--uses`, `--impact`, `--expand`, `--dead-code`, `--edit-check`, `--slice-flow` @@ -757,16 +769,18 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - - - - - + + + + + + - + + ``` @@ -788,17 +802,18 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - - - - - + + + - - - - - + + + + + + + + ``` @@ -822,20 +837,20 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - - - - - - - - - - - - - - + + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -859,20 +874,20 @@ _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] ``` @@ -894,13 +909,14 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - - - - - - - + + + + + + + + ``` @@ -916,12 +932,14 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - - + + + + - - + + @@ -943,19 +961,19 @@ _Transitive blast radius — everything that reaches SYM. NOW carries shown/capp ``` $ ./build/ripwire . --impact=rankGraphTeleport - - - + + + + - - - + + + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1000,11 +1018,11 @@ _Markdown docs that name SYM in a backtick (doc<->code edges)._ ``` $ ./build/ripwire . --mentions=rankGraphTeleport - - - + + + - + @@ -1026,21 +1044,19 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - -_test, test_, Test — listed by convention, no hops=), then hops= ascending (caller-walk depth to a changed symbol; 1 = direct), then path. order=evidence says so on the root; partners= counts the partner rows. run= is the command that discharges a test row; run_unknown="1" means none is derivable for that harness (a guess would be worse than none) — a or row carries one or the other, never neither. is 2+ runner-less rows with equal attributes served as ONE row: n= how many, p= their paths verbatim in list order — a path holding ',' is never grouped, so p= splits into exactly n= paths. A shown=/total= over these rows counts test FILES: a row is n= of them. A run= command is relative to root=: run it from there. counts_floor="1": every graph-derived count here is a FLOOR, never a total. Call edges are extracted from source text by NAME, so dynamic dispatch, callbacks, macros and cross-language calls can be missing; read a zero as "none found", never as "none exists". graph_ambiguous=/graph_unresolved= are the whole graph's resolver gauge (calls split over several defs / calls whose in-repo defs were all language-filtered), the map header's ambiguous=/unresolved=. graph_unindexed=N is a third gauge: files no grammar could read (the map header's unindexed=), whose calls raise neither gauge above; absent when zero, and so is this sentence. --> + + - - - - -... [5 more line(s); run it to see the whole thing] + + ``` **Shaped by:** `--exercises`, `--test-gate`, `--edit-target-file`, `--legend` @@ -1063,8 +1079,9 @@ _Which symbols a TEST FILE exercises — the reverse direction of --affected._ ``` $ ./build/ripwire . --exercises=test/regression.sh - - or row carries one or the other, never neither. is 2+ runner-less rows with equal attributes served as ONE row: n= how many, p= their paths verbatim in list order — a path holding ',' is never grouped, so p= splits into exactly n= paths. A shown=/total= over these rows counts test FILES: a row is n= of them. A run= command is relative to root=: run it from there. counts_floor="1": every graph-derived count here is a FLOOR, never a total. Call edges are extracted from source text by NAME, so dynamic dispatch, callbacks, macros and cross-language calls can be missing; read a zero as "none found", never as "none exists". graph_ambiguous=/graph_unresolved= are the whole graph's resolver gauge (calls split over several defs / calls whose in-repo defs were all language-filtered), the map header's ambiguous=/unresolved=. graph_unindexed=N is a third gauge: files no grammar could read (the map header's unindexed=), whose calls raise neither gauge above; absent when zero, and so is this sentence. pr_iters=pagerank-power-iterations(stop:L1-residual-below-tol,else-ceiling) pr_converged=0-only-when-ceiling-hit-first(absent=converged;no-such-attr=not-pagerank-ordered) --> + + ``` @@ -1083,25 +1100,14 @@ Backtested on two histories (EVALS.md): a named partner is edited within the nex **Try it** -_Mid-task situational report for the current git diff — recorded against a DIRTY tree (contrast with the sandbox run below)._ +_Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below)._ ``` $ ./build/ripwire . --situ -ripwire situational-awareness — 1 changed file(s), 1 symbols in them +ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: 6bf9d9d02+dirty - [1] blast radius: 0 symbols across 0 files transitively depend on these changes - counts_floor=1 graph_ambiguous=10271 graph_unresolved=12861 graph_unindexed=237 (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 missin … [line truncated: 46 more bytes on this line] - [2] tests to run (1) order=evidence: [changed] you edited it, [partner] named after a changed file, then hops asc (1 = direct); "(n): a, b" = n runner-less files sharing that evidence; a (run: …) is relative to root: - test/mcpmanifestcheck.sh [changed] (run: bash test/mcpmanifestcheck.sh) - script_gates_unmodelled=729 — 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 (24) window="18mo@HEAD" commits="3919" (showing 8 of 24 files — shown=8 total=24 capped=1; next: --situ --limit=24): - src/mcp.h (co-edited in 54% of commits) - src/mcprefusal.h (co-edited in 50% of commits) - src/mcpverbs.h (co-edited in 50% of commits) - CHANGELOG.md (co-edited in 42% of commits) - src/cli.h (co-edited in 29% of commits) -... [4 more line(s); run it to see the whole thing] +at: f8d431396 + (0 changed files — working tree is clean, nothing to analyze) ``` **Shaped by:** `--affected`, `--test-gate`, `--legend`, `--limit` @@ -1114,25 +1120,25 @@ Empty diff is fine — the packet still carries branch/sha + heuristics. Compose **Try it** -_The continuation packet for the NEXT session: disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a DIRTY tree._ +_The continuation packet for the NEXT session: disk truth (branch/sha, changed symbols, blast radius, tests) + labeled suggestions. Recorded against a CLEAN tree._ ``` $ ./build/ripwire . --handoff - - - - - - - - - - - - - - -... [25 more line(s); run it to see the whole thing] + + + + + + + + + + + + +... [1 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -1149,13 +1155,12 @@ exit 4 if either is non-empty agent self-check before a PR (pair with --quality- **Try it** -_Pre-PR gate recorded against a DIRTY tree, so the obligations below are the working copy's real ones — the recorded exit code says which way it went._ +_Pre-PR gate on a CLEAN tree: no obligations, exit 0._ ``` $ ./build/ripwire . --test-gate - - + + ``` @@ -1174,24 +1179,24 @@ every hit comes back with its enclosing symbol literal / regex search + enclosin **Try it** -_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._ +_Regex search + enclosing symbol._ ``` -$ ./build/ripwire . --grep=DISCLOSE - + + + - - - - + + - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1206,21 +1211,21 @@ $ ./build/ripwire . --grep=DISCLOSE _Same search with one line of source context either side._ ``` -$ ./build/ripwire . --grep=DISCLOSE --grep-context=1 - + + - + - + - + - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1235,21 +1240,21 @@ $ ./build/ripwire . --grep=DISCLOSE --grep-context=1 _Boolean grep: hits where BOTH literals share the matched line (--grep-scope=line is the default)._ ``` -$ ./build/ripwire . --grep=DISCLOSE --and=cache - + + + + + + + + - - - - - - + + - - - ... [17 more line(s); run it to see the whole thing] ``` @@ -1264,21 +1269,21 @@ $ ./build/ripwire . --grep=DISCLOSE --and=cache _Drop every hit in a file that ALSO contains the --not literal anywhere (file scope)._ ``` -$ ./build/ripwire . --grep=DISCLOSE --not=test --grep-scope=file - + - - + + - - - - - + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1295,21 +1300,21 @@ file requires anywhere in the same file. Second occurrence of --grep=/--regex= i _Drop every hit in a file that ALSO contains the --not literal anywhere (file scope)._ ``` -$ ./build/ripwire . --grep=DISCLOSE --not=test --grep-scope=file - + - - + + - - - - - + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1328,21 +1333,21 @@ code (default) serves the CODE tier when any hit is code, and otherwise comment _Span tiers off: the exhaustive view — the comment and string hits the default tier held back (suppressed_comment=96 / suppressed_string=29 in the plain --grep block above) now print alongside the code hits; hits= grows accordingly._ ``` -$ ./build/ripwire . --grep=DISCLOSE --grep-in=any - + + + + + + - - - + + + - ... [17 more line(s); run it to see the whole thing] ``` @@ -1364,21 +1369,21 @@ Ambiguous or document-only rows get no handle; a later edit must refuse after an _h= on each editable enclosing-symbol row: a freshness-pinned identity an edit verb can target and must refuse on after any file change._ ``` -$ ./build/ripwire . --grep=DISCLOSE --handles - + + - - + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1399,8 +1404,8 @@ _Tree-sitter structural query WITHOUT a capture — a bare node query gets a cap ``` $ ./build/ripwire . --match='(if_statement)' - - + 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 @@ -1409,7 +1414,8 @@ $ ./build/ripwire . --match='(if_statement)' 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 -... [21 more line(s); run it to see the whole thing] +if rw is None or native is None: return None +... [20 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-redact`, `--sarif`, `--limit` @@ -1448,19 +1454,19 @@ _Raw BM25 ranking (debug lens; --for is the real verb)._ ``` $ ./build/ripwire . --query="teleport pagerank" --top-k=5 - - - - + + + + + + + + - + - - - - - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1482,18 +1488,19 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - - - - - -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] - -... [20 more line(s); run it to see the whole thing] + +What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure + +What a rank call hands back: the vector, and the power iteration's own account of itself. Struct…struct RankedGraph + +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… +... [19 more line(s); run it to see the whole thing] ``` **Shaped by:** `--max-tokens`, `--for`, `--signatures-only`, `--auto-bodies`, `--compress`, `--owners`, `--plan`, `--abi` @@ -1510,24 +1517,24 @@ test/showcasecapturecheck.sh (C) re-derives this range from the SAME repo every **Try it** -_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: 92.7% fewer bytes at top-10, 88.7% at top-50, 87.6% at top-100 (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._ +_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: 91.8% fewer bytes at top-10, 85.2% at top-50, 85.5% at top-100 (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._ ``` $ ./build/ripwire . --pack-signatures --top-k=10 - - - - + + + + + + + + + + - - - - - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1545,12 +1552,7 @@ _Control-flow skeleton of one symbol, payload-only via the new --top-k=0._ ``` $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 - - - - -& p, float alpha = 0.85f ) +& p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); const std::vector pw = biasPrior( g, p ); @@ -1559,7 +1561,12 @@ $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 std::vector rankDouble( N, 0.0 ); PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun if( N ) -... [8 more line(s); run it to see the whole thing] + { + ... + } + std::vector r( N, 0.f ); + std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); +... [3 more line(s); run it to see the whole thing] ``` **Shaped by:** `--top-k`, `--expand`, `--compress`, `--no-redact`, `--limit`, `--max-memory` @@ -1576,11 +1583,11 @@ _NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map ride ``` $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport - - - - -, with a BRANCH-FREE size(). Both, not one or the other. @@ -1660,6 +1663,10 @@ $ ./build/ripwire . --pack-top-n=3 --top-k=0 // canonByName / shard maps, and ~100 more structures after the conversion wave): WRITE-ONCE during the // parse/merge, then READ-HOT during resolve. // +// Three things matter for that shape, and the layout below gets all three: +// • no per-list malloc — the N small lists that would each allocate are inline; +// • a BRANCH-FREE size() — `return sz_`, because the size lives in its own field; +// • 16 BYTES per instance — because `inl_` and `heap_` are never both live, so they share storage. ... [17 more line(s); run it to see the whole thing] ``` @@ -1681,14 +1688,14 @@ _--no-redact: emit bodies verbatim (credential redaction is on by default)._ ``` $ ./build/ripwire . --expand=readAckRecords --top-k=0 --no-redact - - + - - + + - + - + - + @@ -1751,20 +1758,20 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - - - - - - - - - - - - + + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1787,20 +1794,20 @@ _Complexity x recent git churn (maintenance pain)._ ``` $ ./build/ripwire . --hotspots - + + + - - - - - - - - - - - - + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1816,20 +1823,20 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - - - - - - - - - - + + + + + + + - - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1847,16 +1854,17 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - - - - - - - - - - + + + + + + + + + + + ``` @@ -1880,8 +1888,9 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - - @@ -1893,7 +1902,6 @@ $ ./build/ripwire . --nonlocal-state --limit=8 - ... [17 more line(s); run it to see the whole thing] ``` @@ -1915,20 +1923,20 @@ _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] ``` @@ -1946,20 +1954,20 @@ _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] ``` @@ -1983,15 +1991,16 @@ _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] + + + + + + + + + +... [22 more line(s); run it to see the whole thing] ``` **Shaped by:** `--limit` @@ -2014,21 +2023,21 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - - - - + + + + - + - -

-

-

-... [14 more line(s); run it to see the whole thing] + +

+

+

+... [13 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -2049,20 +2058,20 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - - - - - + + + + + + + + - + - + - ... [17 more line(s); run it to see the whole thing] ``` @@ -2086,20 +2095,20 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - - - + + + - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2121,16 +2130,16 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - - - - - - - - - + + + + + + + + + ``` @@ -2154,17 +2163,19 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - - - - - - - - - + + + + + + + + + + + ... [22 more line(s); run it to see the whole thing] ``` @@ -2186,17 +2197,19 @@ _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] ``` @@ -2212,20 +2225,20 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - - - - - - - - - - + + + + + + + + - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2241,20 +2254,20 @@ _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] ``` @@ -2278,9 +2291,9 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes ``` $ ./build/ripwire . --arch=test/archfix/rules.txt - - - + @@ -2308,9 +2321,9 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes ``` $ ./build/ripwire . --arch=test/archfix/rules.txt - - - + @@ -2334,9 +2347,9 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes ``` $ ./build/ripwire . --arch=test/archfix/rules.txt - - - + @@ -2362,20 +2375,20 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - - - + + + - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2398,8 +2411,8 @@ _The built-in rule registry — one row per rule with sev=/category=/rationale/l ``` $ ./build/ripwire . --lint-catalog - - + + 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) @@ -2408,7 +2421,8 @@ $ ./build/ripwire . --lint-catalog 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 -... [21 more line(s); run it to see the whole thing] +a non-trivial numeric literal inside a function body, outside a const/constexpr init +... [20 more line(s); run it to see the whole thing] ``` **Shaped by:** `--lint` @@ -2423,8 +2437,8 @@ _User lint rules (YAML, ast-grep style) from a directory._ ``` $ ./build/ripwire . --lint-rules=test/lintrulesfix/rules - - + + use LOG() instead of printf @@ -2471,20 +2485,20 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - - - + + + - + - + - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2557,20 +2571,20 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - - - - - - - - - + + + + - - + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2588,9 +2602,10 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - - + + + ``` @@ -2613,20 +2628,20 @@ _Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels ``` $ ./build/ripwire . --zoom - - - + + + + - - + + - + + + - - ... [17 more line(s); run it to see the whole thing] ``` @@ -2651,16 +2666,16 @@ $ ./build/ripwire . --report # ripwire architecture report -2484 files · 23824 symbols · 34201 edges · 2316 modules (10908 call-graph isolated) +2039 files · 18699 symbols · 22330 edges · 1313 modules (11420 call-graph isolated) Root: `.` -Call-graph isolate provenance: 2886 declaration, 1578 header, 1922 source, 4522 document; 0 connected Louvain singletons +Call-graph isolate provenance: 2633 declaration, 1162 header, 3670 source, 3955 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 2316) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1366 symbols -- **src::append@elixir_resolve.h:111:4875 [resolve,collect,add]** — 1126 symbols -- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 563 symbols +## Modules (call-graph clusters; showing 12 of 1313) +- **src::assign@infra/svector.h:342:19905 [resolve,compute,collect]** — 652 symbols +- **src::emplace@infra/svector.h:408:22477 [resolve,parse,run]** — 617 symbols +- **src::emitTo@infra/emit.h:51:2620 [write,run,emit]** — 377 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2676,20 +2691,20 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - - - - - - - + + + + + + + + + - + - ... [17 more line(s); run it to see the whole thing] ``` @@ -2708,17 +2723,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
7050"] - n102["src/infra
1276"] + n98["src
5422"] + n99["src/infra
630"] end subgraph sg1 ["test"] - n103["test
4844"] - n328["test/fixtures/recallpassage
314"] - n280["test/expandmodefix
151"] - n282["test/expandsibsfix
149"] - n458["test/rubyattrsfix
85"] - n395["test/massfix
77"] - n360["test/javamethodreffix
62"] + n100["test
3546"] + n269["test/fixtures/recallpassage
314"] + n255["test/expandmodefix
151"] + n257["test/expandsibsfix
149"] + n315["test/massfix
77"] + n297["test/legofix
60"] + n335["test/optremarksfix
59"] ... [17 more line(s); run it to see the whole thing] ``` @@ -2736,20 +2751,20 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - - - - - - - - - - - - + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2763,12 +2778,13 @@ $ ./build/ripwire . --owners **Try it** -_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._ +_High-confidence internal functions with no caller. NOTE the filter is a path-COMPONENT match: 'src' matches any .../src/... segment; use ./src to pin the root directory._ ``` $ ./build/ripwire . --dead-code=src - - + + + ``` @@ -2828,24 +2844,24 @@ Each kind is measured against the baseline (complexity/verbosity/nesting/params/ **Try it** -_Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit._ +_On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below._ ``` $ ./build/ripwire . --quality-delta - - - - + + - - - - - - + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2865,24 +2881,24 @@ $ ./build/ripwire . --quality-delta **Try it** -_Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit._ +_On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below._ ``` $ ./build/ripwire . --quality-delta - - - - + + - - - - - - + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2902,12 +2918,12 @@ thresholds and arithmetic from PyDriller's deltamaintainability reference implem **Try it** -_The Delta Maintainability Model scalar for the WORKING TREE vs HEAD — recorded against a DIRTY tree (the sandbox section shows a real delta). UNAVAILABLE is a stated reason, never 0 or 1._ +_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._ ``` $ ./build/ripwire . --dmm - - +

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

@@ -2976,8 +2992,8 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - - + @@ -3002,19 +3018,19 @@ A contract is PER DEFINITION, so a SYM matching several definition sites REFUSES **Try it** -_Fast per-symbol post-edit contract check vs git HEAD — recorded against a DIRTY tree, so the verdict describes the working copy, not HEAD alone, and the recorded exit code is the working copy's._ +_Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree)._ ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - + - - - - - - + + + + + + ``` @@ -3061,12 +3077,11 @@ $ ./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":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"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":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +{"p":"test/verify_csr.cpp","hops":2,"run":"bash test/a9disclosurecheck.sh"}], +"order":"evidence","partners":0,"tests":4,"script_gates_unmodelled":675,"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3082,12 +3097,11 @@ _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":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"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":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +{"p":"test/verify_csr.cpp","hops":2,"run":"bash test/a9disclosurecheck.sh"}], +"order":"evidence","partners":0,"tests":4,"script_gates_unmodelled":675,"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3133,12 +3147,11 @@ $ ./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":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"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":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +{"p":"test/verify_csr.cpp","hops":2,"run":"bash test/a9disclosurecheck.sh"}], +"order":"evidence","partners":0,"tests":4,"script_gates_unmodelled":675,"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3197,11 +3210,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":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 486 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multi … [line truncated: 536 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":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true}}]} +"graph_ambiguous":7928,"graph_unresolved":4925,"graph_unindexed":219,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3245,20 +3258,20 @@ _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] ``` @@ -3282,21 +3295,21 @@ _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] + + +... [23 more line(s); run it to see the whole thing] ``` **Shaped by:** `--slice-depth` @@ -3337,9 +3350,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:5567 - - +$ ./build/ripwire . --at=src/graph.h:5565 + + + ``` @@ -3359,25 +3373,14 @@ The bundle is BUDGETED by default (8000 tokens, budget_default="1" on the root; **Try it** -_No-LLM review-evidence bundle for the working-tree diff — recorded against a DIRTY tree, so it is populated rather than empty._ +_No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - - - - - - - - - - - - - -... [18 more line(s); run it to see the whole thing] + + + + ``` **Shaped by:** `--test-gate`, `--from-trace`, `--limit`, `--map-diff`, `--index-out` @@ -3400,21 +3403,17 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - - + + + - + - - - - - -... [2 more line(s); run it to see the whole thing] + + ``` **Shaped by:** `--plan-lanes`, `--plan` @@ -3483,14 +3482,8 @@ _Which refs of the one branch `main` (no ref family left to select on this check ``` $ ./build/ripwire . --stray-content=main - - - - - - - - + + ``` @@ -3514,21 +3507,9 @@ _Select the genuinely-unmerged refs of the one branch `main` (no ref family left ``` $ ./build/ripwire . --stray-content=main --plan - - - - - - - - - - - - - - -... [17 more line(s); run it to see the whole thing] + + + ``` **Shaped by:** `--stray-content`, `--legend`, `--json` @@ -3550,8 +3531,8 @@ _Cross-branch ABI-break gate over the one branch `main` (no ref family left to s ``` $ ./build/ripwire . --stray-content=main --abi - - ``` @@ -3573,13 +3554,14 @@ _Which ref's tree defines or mentions SYM — HEAD first, then every local branc ``` $ ./build/ripwire . --whereis=rankGraphTeleport - - - - - -... [25 more line(s); run it to see the whole thing] + + + + + + +... [24 more line(s); run it to see the whole thing] ``` **Shaped by:** `--with-history`, `--limit` @@ -3602,9 +3584,9 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat ``` $ ./build/ripwire . --flags - + - + @@ -3684,9 +3666,9 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - - + + @@ -3697,7 +3679,7 @@ $ ./build/ripwire . --field-affinity - + ... [17 more line(s); run it to see the whole thing] ``` @@ -3719,18 +3701,18 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - - - - - - - - - - - + + + + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3754,18 +3736,18 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - - - - - - - - - - - + + + + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3788,19 +3770,19 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - - - - - - - - - - - - + + + + + + + + + + + + + ... [18 more line(s); run it to see the whole thing] ``` @@ -3824,8 +3806,8 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - - + + @@ -3860,10 +3842,10 @@ $ ./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:5567 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:5608 - #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 - #3 0x102e01a44 in main src/main.cpp:3983 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:3949 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:3447 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1441 + #3 0x102e01a44 in main src/main.cpp:3475 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) ==41337==ABORTING ``` @@ -3888,9 +3870,9 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc ``` $ ./build/ripwire . --run-trace="true" - - - + + + ``` @@ -3949,13 +3931,12 @@ _List all field notes (write-side memory) — the committed .ripwire_notes at th ``` $ ./build/ripwire . --notes - - - + + - @@ -3963,7 +3944,8 @@ $ ./build/ripwire . --notes 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.]]> -... [2 more line(s); run it to see the whole thing] + +... [1 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-redact` @@ -3980,21 +3962,19 @@ _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 - -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 ) -... [17 more line(s); run it to see the whole thing] + +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: 104 more bytes on this line] + +knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedBy makes in full above, which applies here verbatim: firstFlagOutside() walks the rows parseArgs itself minline constexpr std::string_view kInRideAlong[] = + +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 +... [19 more line(s); run it to see the whole thing] ``` **Shaped by:** `--top-k`, `--token-budget`, `--for`, `--test-gate`, `--expand`, `--compress`, `--no-redact`, `--partition` @@ -4017,19 +3997,21 @@ _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 - -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 -... [19 more line(s); run it to see the whole thing] + +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 std::vector<std::string>& verbLines… + +knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedB…inline constexpr std::string_view kInRideAlong[] = + +... [17 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -4050,18 +4032,19 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - - - - -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] - -... [20 more line(s); run it to see the whole thing] + +What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure + +What a rank call hands back: the vector, and the power iteration's own account of itself. Struct…struct RankedGraph + +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… +... [19 more line(s); run it to see the whole thing] ``` **Shaped by:** `--partition` @@ -4097,7 +4080,7 @@ _One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE dedupe $ ./build/ripwire . --batch=/aux/batch2.txt for:incremental cache invalidation callers:rankGraphTeleport -grep:DISCLOSE +grep:DEGRADED_PATH_ALERT lego:Vehicle ``` @@ -4124,17 +4107,16 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - - - - - - - + + + + + + + - ``` @@ -4158,19 +4140,20 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - - - - - - - - + + + + + + + - - -... [3 more line(s); run it to see the whole thing] + + + + ``` **Caveats (stated by the binary):** @@ -4189,11 +4172,11 @@ _WHY a file is not in the index (oversize / excluded / unsupported-ext / gitigno ``` $ ./build/ripwire . --skipped - - + + - @@ -4230,9 +4213,7 @@ _Scan a single skill file for injection/exfiltration patterns before installing. ``` $ ./build/ripwire --scan-skill=skills/ripwire-orient/SKILL.md - - - + ``` **Shaped by:** `--scan-skills` @@ -4249,9 +4230,7 @@ _Scan a whole skills directory (exit 2 = CRITICAL, 1 = WARN). Explicit-DIR form ``` $ ./build/ripwire --scan-skills=skills - - - + ``` **Caveats (stated by the binary):** @@ -4279,21 +4258,21 @@ _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] +... [16 more line(s); run it to see the whole thing] ``` **Shaped by:** `--since`, `--in` @@ -4314,22 +4293,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~11 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - - - - - - - - +$ ./build/ripwire . --rank-by=churn-decay --since=HEAD~7 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 + + + + + + + + + + + + + - - - - - -... [7 more line(s); run it to see the whole thing] +... [16 more line(s); run it to see the whole thing] ``` **Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` @@ -4386,12 +4365,12 @@ full restores the prose output legend posture for EVERY XML verb. The DEFAULT is **Try it** -_The same gating report under --legend=full — same rows, same exit 2, but the ~4 KB prose legend the compact default trims is restored: the posture a human reads once to learn the schema._ +_The same gating report under --legend=compact — same rows, same exit 2, schema="ripwire.quality-delta/v1", ~4 KB of legend down to one comment: the shape an agent's edit loop should run._ ``` -$ ./build/ripwire . --quality-delta --legend=full - -, acked. at=: commit+dirty+shallow. next=: the one pasteable follow-up. --> + @@ -4419,7 +4398,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=ebae13b851e3a641 entries=791 +ripwire legend dictionary ripwire.dict/v1 dictv=04d7833c60d2d5bd entries=784 : 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=) @@ -4462,7 +4441,7 @@ $ ./build/ripwire . --hotspots --json **Answers:** paginate a high-cardinality verb paginate a high-cardinality verb. -HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's rows; its rows repeat on every page, complete). --edit-check is the same shape for a different reason: its rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --token-budget the same way; --for takes --limit/--offset as its FILE page, where the budget flags are refused in turn (bare --pack-task --limit refuses: it has no page without a budget), and --for/--pack-task beside --token-budget take the window as the BUDGETED-BUNDLE CANDIDATE PAGE — one window over the ranked candidates, signatures only (bodies come via next= or --expand), their resumable continuation; the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all. +HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's rows; its rows repeat on every page, complete). --edit-check is the same shape for a different reason: its rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --top-k/--max-tokens/--token-budget, not a page (--for's bare bundle is shaped by --token-budget the same way, and takes --limit/--offset only as its file page, where the budget flags are refused in turn); the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all. **Try it** @@ -4470,20 +4449,20 @@ _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] ``` @@ -4505,21 +4484,21 @@ _Drop matching paths (repeatable) before ranking._ ``` $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - - - - + + + + + + + + + - + - - - + - - - -... [7 more line(s); run it to see the whole thing] +... [10 more line(s); run it to see the whole thing] ``` **Shaped by:** `--skipped`, `--in`, `--index-out` @@ -4532,25 +4511,25 @@ this is NOT a filter to only-changed symbols. changed="N" in the header names th **Try it** -_Full map re-ranked with teleport toward git-changed files — recorded against a DIRTY tree, so changed= counts the working copy's files and the teleport is live._ +_Full map re-ranked with teleport toward git-changed files — clean tree, so changed=0 and it degrades to the plain map._ ``` $ ./build/ripwire . --map-diff --top-k=5 - - - - - - - - + + + + + + + + + + - + - - - -... [7 more line(s); run it to see the whole thing] + +... [11 more line(s); run it to see the whole thing] ``` **Shaped by:** `--json`, `--limit` @@ -4570,19 +4549,21 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ ``` $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - - - - + + + + + + + + + - + - - - + - - +... [2 more line(s); run it to see the whole thing] ``` **Shaped by:** `--doctor`, `--index-out` @@ -4618,19 +4599,21 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co ``` $ ./build/ripwire . --no-cache --top-k=3 - - - - + + + + + + + + + - + - - - + - - +... [2 more line(s); run it to see the whole thing] ``` **Shaped by:** `--doctor` @@ -4647,19 +4630,21 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses ``` $ ./build/ripwire . --no-ignore --top-k=3 - - - - + + + + + + + + + - + - - - + - - +... [2 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -4679,21 +4664,21 @@ _Skip files above a size bound before parsing (note the corpus shrink in the hea ``` $ ./build/ripwire . --max-file-size=8K --top-k=3 - - - - - - + + + + + + + + + + + - - - - - -... [3 more line(s); run it to see the whole thing] +... [8 more line(s); run it to see the whole thing] ``` **Shaped by:** `--skipped` @@ -4759,19 +4744,21 @@ _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 - - - - + + + + + + + + + - + - - - + - - +... [2 more line(s); run it to see the whole thing] ``` ### `--mcp` @@ -4786,11 +4773,11 @@ _initialize + tools/list: the manifest an agent host loads at session start — ``` $ ./build/ripwire '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | ./build/ripwire --mcp -{"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":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"ripwire","version":"1.0"},"capabilities":{"tools":{}},"instructions":"Map before reading files. Start a new task with explore; use from_trace for an error; use impact plus uses before changing a symbol; run edit_c … [line truncated: 368 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] -... [30 more line(s); run it to see the whole thing] +{"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: 1111 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: 874 more bytes on this line] +... [28 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-stable`, `--no-redact`, `--agent`, `--mcp-tools`, `--lsp`, `--listen` @@ -4865,9 +4852,9 @@ _Labelled verdict-accuracy eval for --stray-content — three labels over REAL l ``` $ ./build/ripwire . --eval-stray=/aux/stray_labels2.tsv # 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) -sync-rebase merged -sync-rebase-backup unmerged -sync-upstream merged +no-such-ref-0 merged +no-such-ref-1 unmerged +no-such-ref-2 merged ``` ### `--eval` @@ -4882,14 +4869,14 @@ _Self-eval: co-change recall vs BM25._ $ ./build/ripwire . --eval ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 - ripwire 2.9% 7.0% 15.3% - BM25 12.7% 14.9% 18.6% - BM25sub 17.2% 23.2% 27.6% - BM25body 15.1% 28.4% 41.9% - fused 13.0% 16.2% 24.9% - anchored 15.1% 28.4% 41.9% - same-dir 0.0% 8.8% 9.8% - random 0.2% 0.4% 0.8% <- floor (random ranking over F=2484 files) + ripwire 0.5% 1.5% 6.7% + BM25 10.9% 13.1% 16.7% + BM25sub 10.7% 15.1% 20.2% + BM25body 22.3% 29.9% 34.9% + fused 4.2% 14.8% 22.0% + anchored 20.8% 29.9% 34.9% + same-dir 1.6% 5.6% 7.2% + random 0.2% 0.5% 1.0% <- floor (random ranking over F=2039 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 / @@ -4912,19 +4899,19 @@ _Known-item retrieval eval: MRR + recall@k per ranker per query mode._ ``` $ ./build/ripwire . --eval-retrieval ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=5818 scored=4000 rule=smallest-key CAPPED — a SUBSET, not the population + sample: population=4164 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.693 56.0% 85.7% 90.7% - subtoken doc-phrase 0.920 90.0% 94.0% 94.9% - name-exact name 0.905 83.5% 96.1% 97.4% - name-exact doc-phrase 0.013 0.5% 1.8% 2.6% - anchored name 0.699 57.6% 85.2% 89.1% - anchored doc-phrase 0.917 89.5% 94.0% 94.5% - routed name 0.908 83.7% 96.3% 97.7% - routed doc-phrase 0.919 90.0% 93.8% 94.7% + subtoken name 0.714 57.9% 88.4% 92.9% + subtoken doc-phrase 0.932 91.2% 95.2% 95.8% + name-exact name 0.924 86.0% 97.4% 98.4% + name-exact doc-phrase 0.013 0.4% 2.2% 2.7% + anchored name 0.718 59.3% 87.4% 91.7% + anchored doc-phrase 0.927 90.5% 95.0% 95.8% + routed name 0.924 86.0% 97.4% 98.4% + routed doc-phrase 0.930 91.1% 95.0% 95.5% ... [6 more line(s); run it to see the whole thing] ``` @@ -4970,7 +4957,7 @@ _Version + short build info._ ``` $ ./build/ripwire --version -ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=66e5c3e10) +ripwire 0.6.1 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=f8d431396) ``` **Shaped by:** `--verify`, `--metrics`, `--deps`, `--naming-locals`, `--arch`, `--dry-run`, `--doc-drift`, `--from-trace` diff --git a/docs/captures/COMMANDS_showcase_2026-10-07.md b/docs/captures/COMMANDS_showcase_2026-10-07.md deleted file mode 100644 index 532f56d18..000000000 --- a/docs/captures/COMMANDS_showcase_2026-10-07.md +++ /dev/null @@ -1,7493 +0,0 @@ -# ripwire — every verb, run for real - -- **Date:** 2026-10-07 (regenerated capture; supersedes any older `docs/captures/COMMANDS_showcase_*.md`) -- **Lives in `docs/captures/`** — a directory the crawl/retrieval lenses SKIP (`kCrawlSkipDirs`, src/ingest.h): a generated doc that quotes every verb's output out-scores the source for any query about the tool and was measured at 77% of `--recall` on this repo when it sat at the root. `test/argvdiffcheck.sh` harvests its `## `-heading command lines as differential vectors — keep that format. -- **Version:** `ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=66e5c3e10)` -- **Repo:** the ripwire repo @ `6bf9d9d02` — **DIRTY — `git status --porcelain` reports 1 entr(ies)**. The diff-aware verbs (`--situ`/`--test-gate`/`--quality-delta`/`--pr-context`/`--map-diff`/`--edit-check`) answer a question about the WORKING TREE, so that condition is part of their answer and every one of their captions below states which tree it recorded against. This run recorded against a dirty tree, so their output here reflects uncommitted working-copy edits rather than the empty/exit-0 shape a clean checkout gives. They appear a SECOND time in the final section against a throwaway `git clone --local` sandbox carrying one KNOWN, deliberate regression — that is the reproducible gating demonstration; this one is whatever the tree happened to hold. -- **Corpus:** the ripwire repo itself (dogfood), via `./build/ripwire` -- **Sandbox diff** (the last section only): `.ripwire_notes | 2 ++ - .ripwire_quality_acks | 1 + - src/infra/sortutil.h | 31 ++++++++++++++++++++++++++++--- - 3 files changed, 31 insertions(+), 3 deletions(-)` — one preexisting function made deeply nested, one function's arity changed 1 -> 2, one copy-paste duplicate helper, one new 8-parameter public function. - -**How to read the blocks:** ripwire's real XML output is minified — often ONE long line. For scanability, long minified lines are displayed re-wrapped with a line break at every tag seam (`><`). Header COMMENT lines (the legends) always appear in full — they are exempt from the per-line cut; any OTHER display line over 300 bytes is cut with a `… [line truncated: N more bytes]` marker, which can hit a long root element or row. `--plan-lanes` emits JSON and is re-wrapped at object seams the same way. Long outputs are cut to their first ~30 display lines with a `… [N more display lines; full output is M bytes]` marker giving the true size. Exit codes are recorded when non-zero; wall time when >1s. - -**Not run (and why):** `ripwire ` (network clone), `--listen` / `--mcp-token` / `--allow-remote-edits` / `--mcp-tools` (the server posture — `--mcp` itself IS captured in its own section as a one-shot stdio JSON-RPC exchange with the default full tool profile, and `wrap claude` shows the wiring), `--arch --baseline[-update]` (state writer against the read-only repo — `--note-add` / `--quality-baseline` / `--quality-ack` / the three edit verbs / `--edit-plan` ARE shown, inside the throwaway sandbox clone; `--index-out` / `--pin-census` write to scratch), `--eval-mined` (needs a `minedpair.jsonl` artifact from `bench/mine_traces.py`; none present in the tree), `--refetch` (git-url only), `--max-memory` (the guard is silent on a corpus this size — its disclosed partial-answer shape needs a tree past the limit, gated by the memory-guard lanes instead), `--lsp` (the editor transport — its one-shot dialogues are gated by `test/lspcheck.sh`, not by showcase blocks), `--sections` (the / stub-and-restore shapes are gated by `test/forsectioncollapsecheck.sh`; the flag restores what --for collapses by default), `--force` (wrap-only modifier), `--scan-skills` bare form (would sweep `~/.claude/skills`; the explicit-DIR form is shown instead), `--help` (205 lines — read it from the binary). - - ---- - -# understand a codebase cold - -## `./build/ripwire .` - -*The default ranked symbol map — start here when landing cold in a repo.* - -**wall time: 18.86s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [800 more display lines; full output is 22318 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --top-k=5` - -*Same map, capped to the 5 highest-ranked symbols.* - -**wall time: 2.17s** - -````` - - - - - - - - - - - - - - - - - - - - - -````` - -## `./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: 2.89s** - -````` - - - - - - - - - - -````` - -## `./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: 2.09s** - -````` - - -````` - -stderr: - -````` -ripwire: --token-budget exceeded: withheld_est_tokens=8992 > 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: 42.95s** - -````` - - - - -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 - -the cached index for `root`, rebuilt only when stale (otherwise returned as-is, no parse, no gra…inline const McpIndex& getIndex( const std::string& root ) -inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) -inline std::uint64_t contentHash64( std::string_view s ) noexcept -struct CacheEntry -inline void prewarmTagsQueries( const std::vector<std::string>& files, const HashMap<std::string, FileFacts>& cache, long long cacheWriteNs, IngestFileScan& … [line truncated: 12 more bytes on this line] -inline std::uint64_t blobChecksum( std::string_view s ) noexcept -inline ContentIdIndex contentIdsBySym( const IngestResult& ing, const Graph& g, std::string_view root ) -inline bool ingestCommitTree( const std::string& root, const std::string& sha, const std::vector<std::string>& excludes, std::size_t maxFileBytes, IngestResult&… -inline std::vector<CacheWriteRow> buildCacheWritePlan( const std::vector<std::uint32_t>& orderIn, const std::vector<std::uint64_t>& pathHashes, const s … [line truncated: 16 more bytes on this line] -inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, … [line truncated: 54 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 ) -struct IngestFileScan -inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t>& f … [line truncated: 17 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 ) -inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, cons … [line truncated: 91 more bytes on this line] -inline std::string spanTierMemoPath( const std::string& diskPath ) -inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) -inline NodeId resolveHandleAll( const McpIndex& ix, std::uint64_t idHash, std::vector< NodeId >& matches ) -… [46 more display lines; full output is 9078 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: 5.09s** - -````` - - - -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; - } - if( teleportMass > 0.0 ) - { -… [12 more display lines; full output is 4315 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: 5.05s** - -````` - - - - - -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", bo … [line truncated: 11 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::string didYouMean( const IngestResult& ing, std::string_view name ) -inline std::vector<float> churnDecayTeleport( const std::string& root, const IngestResult& ing, const SinceScope* scope = nullptr, bool* outHasChurnEvidence = nullptr ) -PageRankRun pageRankDouble( const sparseCsr<float>& inEdges, std::span<const double> weightedOutDegree, std::span<const double> teleport, std::span<double& … [line truncated: 37 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 = nullptr )< … [line truncated: 3 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 ) -inline constexpr std::size_t kChurnMergeBombMaxFiles = 100 -### Fixed — `rank_by` over MCP no longer answers a different rankin … [line truncated: 57 more bytes on this line] -### 2. Gates for A -#### The convergence disclosure contract -… [102 more display lines; full output is 15166 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: 5.86s** - -````` - - - - - -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.10s** - -````` - - - - -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 -enum class AstWalk : std::uint8_t -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 -inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept -constexpr std::array<LangEntry, 51> kLangTable = -constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u -inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept -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 ) -std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] -bool jsonNestsTooDeep( std::string_view bytes ) noexcept -constexpr std::uint32_t kMaxYamlNestDepth = 64u -SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) -inline bool hasMainGuard( std::string_view source ) -inline void runParseWorker( ParsePoolShared& sh, unsigned t ) -inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) -struct LintRule -enum class DisclosureWhy : std::uint8_t -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [55 more display lines; full output is 9170 bytes on 1 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: 5.99s** - -````` - - - - -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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) -inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) -struct NameCorpusStats -struct LensRanking -inline bool hasIdentifierShape( std::string_view w ) noexcept -inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) -struct Bm25Params -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 reads the W … [line truncated: 57 more bytes on this line] -struct CandidateProvenance -std::optional<int> runEvalViews( const MainDispatch& d ) -inline constexpr int kLexWeightDoc = 2 -inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) -struct RecallSectionPick -inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) -### Graph-Ranked Retrieval: It finds the right files more often than the alternatives -… [56 more display lines; full output is 9449 bytes on 1 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: 5.94s** - -````` - - - - -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 runEvalSkills( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& labelsPath ) -inline int runEvalMined( const std::string& root, const IngestResult& ing, const Graph& g, const std::string& path ) -struct NameCorpusStats -struct LensRanking -inline bool hasIdentifierShape( std::string_view w ) noexcept -inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) -inline int runEval( const std::string& root, const IngestResult& ing, const Graph& g, const std::vector<char>& currentDiff ) -struct Bm25Params -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 reads the W … [line truncated: 57 more bytes on this line] -struct CandidateProvenance -std::optional<int> runEvalViews( const MainDispatch& d ) -inline constexpr int kLexWeightDoc = 2 -inline std::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) -struct RecallSectionPick -inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) -inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept -… [59 more display lines; full output is 9616 bytes on 1 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: 2.92s** - -````` - - - - - - - - - -````` - -## `./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.03s** - -````` - - - - - 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 ); -}]]>[[nodiscard]] const char16_t* c_str() const noexcept -````` - -## `./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: 2.02s** - -````` - - - - -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: 2.08s** - -````` - - - - -````` - -## `./build/ripwire . --recall="quality delta gating exit codes"` - -*Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* - -**wall time: 4.07s** - -````` -ripwire recall — "quality delta gating exit codes" — 113 relevant of 210 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5489 - -━━ README.md (relevance 7.966) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 213934 B; lines="2707-2719,2720-2727"; dropped_by_budget=23] -#### 6.2 Exit codes - -This is the part to wire into a script. - -| Code | Meaning | -| --- | --- | -| 0 | The command completed. | -| 1 | The command refused the request. A refusal names the reason on stderr. When `--callers`, `--callees`, `--uses`, `--impact` or `--path` refuse a selector that matches no indexed definition, they also print an answer on stdout: the verb's element with `found="0"` and the name to retry with. | -| 2 | A policy gate fired: `--arch` found a layering violation, `--scan-skill` found a CRITICAL, `--quality-delta` found new debt. | -| 3 | The output exceeded the token budget that you set. | -| 4 | `--test-gate` found an open obligation. | -| 5 | The memory guard stopped the run: over `--max-memory` (default 65% of the machine's memory) with no partial answer possible, or a verb other than the default map facing a partial index. One stderr line names the limit and the override. | - -#### 6.3 JSON output - -`--json` emits JSON instead of XML, with keys mirroring the XML attribute names one to one. It is an -allow-list: the default map, `--for`, `--pack-task`, `--callers`, `--callees`, `--impact`, -`--quality-delta`, `--test-gate`, `--metrics`, and `--plan-lanes`, which is JSON-native. Every other -verb refuses on stderr with exit 1 rather than silently falling back to XML, so a verb added tomorrow -refuses by default. - - - -━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.319) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] -### Root spelling (maintainer-owned; reproduce it once so you recognise it) - -```bash -… [159 more lines, 14007 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 1438117-byte scratch dir of DUMPED TOOL OUTPUT (a git log, this repo's own generated command reference, --help text, an architecture doc, a fabricated JSON access log) instead of a source repo — no index to build, no daemon.* - -**wall time: 1.78s** - -````` -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.246) ━━ [sections: 1 of 155 selected (185 in doc), section-granular; whole doc 533554 B; lines="3654-3658"; dropped_by_budget=154] [truncated: 1865 of 7738 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.18s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [366 more display lines; full output is 11120 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --html=/aux/map2.html` - -*Self-contained HTML force-directed call graph.* - -**wall time: 3.09s** - -````` -(empty) -````` - -Artifact written: - -````` -158952 /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.09s** - -````` - - - - - - - - - - - - - - - - - - - - - -````` - - ---- - -# 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.64s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [225 more display lines; full output is 9305 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.08s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [2993 more display lines; full output is 79377 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: 1.09s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [31 more display lines; full output is 4330 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --callers=rankGraphTeleport` - -*Who calls SYM (1-hop in-edges).* - -**wall time: 1.10s** - -````` - - - - - - - - - - -````` - -## `./build/ripwire . --callers=rankGraphTeleport --legend=full` - -*The prose legend restored: compact is the DEFAULT (bcb796959), so the pair above already shows the compact posture — --legend=full opts back into the ~3 KB explanatory legend the default trims. Rows and completeness attributes unchanged; only the legend differs.* - -**wall time: 1.11s** - -````` - - - - - - - - - - - -````` - -## `./build/ripwire . --callers=DoesNotExist` - -*Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* - -**exit code: 1** — **wall time: 1.69s** - -````` - - -````` - -stderr: - -````` -ripwire: --callers symbol not found: DoesNotExist -````` - -## `./build/ripwire . --callees=rankGraphTeleport` - -*What SYM calls (1-hop out-edges).* - -**wall time: 1.10s** - -````` - - - - - - - - - - - -````` - -## `./build/ripwire . --uses=rankGraphTeleport` - -*The resolvable use-sites (call/read/write/import/extends) with file:line; count= is a floor.* - -**wall time: 1.81s** - -````` - - - - - - - - - - - - -````` - -## `./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))'` - -*Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* - -**wall time: 1.07s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [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.09s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [73 more display lines; full output is 5563 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.10s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [73 more display lines; full output is 5441 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.12s** - -````` - - - - - - - - -````` - -## `./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex` - -*Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* - -**wall time: 1.07s** - -````` - - - - - - - - - - -````` - -## `./build/ripwire . --impact=rankGraphTeleport` - -*Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* - -**wall time: 1.60s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [48 more display lines; full output is 5280 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --mentions=rankGraphTeleport` - -*Markdown docs that name SYM in a backtick (doc<->code edges).* - -**wall time: 1.13s** - -````` - - - - - - - - - - -````` - -## `./build/ripwire . --affected=src/graph.h` - -*Test files that transitively reach the changed file.* - -**wall time: 1.23s** - -````` - - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --situ` - -*Mid-task situational report for the current git diff — recorded against a DIRTY tree (contrast with the sandbox run below).* - -**wall time: 2.91s** - -````` -ripwire situational-awareness — 1 changed file(s), 1 symbols in them -root: . -at: 6bf9d9d02+dirty - [1] blast radius: 0 symbols across 0 files transitively depend on these changes - counts_floor=1 graph_ambiguous=10271 graph_unresolved=12861 graph_unindexed=237 (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 missin … [line truncated: 46 more bytes on this line] - [2] tests to run (1) order=evidence: [changed] you edited it, [partner] named after a changed file, then hops asc (1 = direct); "(n): a, b" = n runner-less files sharing that evidence; a (run: …) is relative to root: - test/mcpmanifestcheck.sh [changed] (run: bash test/mcpmanifestcheck.sh) - script_gates_unmodelled=729 — 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 (24) window="18mo@HEAD" commits="3919" (showing 8 of 24 files — shown=8 total=24 capped=1; next: --situ --limit=24): - src/mcp.h (co-edited in 54% of commits) - src/mcprefusal.h (co-edited in 50% of commits) - src/mcpverbs.h (co-edited in 50% of commits) - CHANGELOG.md (co-edited in 42% of commits) - src/cli.h (co-edited in 29% of commits) - README.md (co-edited in 25% of commits) - docs/EVALS.md (co-edited in 25% of commits) - test/compactlegendcheck.sh (co-edited in 25% of commits) - next: --test-gate -````` - -## `./build/ripwire . --test-gate` - -*Pre-PR gate recorded against a DIRTY tree, so the obligations below are the working copy's real ones — the recorded exit code says which way it went.* - -**exit code: 4** — **wall time: 2.84s** - -````` - - - -````` - -## `./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: 2.13s** - -````` - -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: 5.91s** - -````` - -if data.get( "schema" ) != SCHEMA: raise SystemExit( f"{path}: unexpected schema {data.get('schema')!r} (expecte -if not str( data.get( "tasks_lock_content_sha256", "" ) ).startswith( "questions:" ): train_repos = select_tasks -if train_repos: raise SystemExit( f"{path}: records from repo(s) that re-derive to LocBench -if base and ctx and base["status"] == "ok" and ctx["status"] == "ok": paired.append( ( instance_id, base["re -if not repos: return 0.0, [] -if base["localization_hit"] is None or ctx["localization_hit"] is None: return 0.0 -if bv: ratios.append( cv / bv - 1 ) -if not ratios: return None, None -if rw is None or native is None: return None -if not paired: out["note"] = "zero complete paired (baseline,ripwire_cli) runs — nothing to analyze yet" -if contaminated: print( f" ** {contaminated} baseline run(s) invoked ripwire despite the no-ripwire contract " -if "note" in out: print( f" {out['note']}" ); return -if out["n_pairs"] != 27: failures.append( f"expected 27 paired runs, got {out['n_pairs']}" ) -if out["n_incomplete"] != 2: failures.append( f"expected 2 incomplete pairs (the orphan + the contaminated-basel -if out["n_repos"] != 3: failures.append( f"expected 3 repos, got {out['n_repos']}" ) -if out.get( "n_contaminated_baseline" ) != 1: failures.append( f"expected exactly 1 contaminated baseline run co -if not ( out["resolved_delta_mean"] > 0 ): failures.append( "expected a positive resolved-rate delta" ) -if not ( out["resolved_delta_bootstrap_95_lower"] > 0 ): failures.append( "expected a POSITIVE bootstrap 95% low -if out["tokens_out_ratio_p50"] is None or abs( out["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( f"e -if out.get( "n_resolved_pairs" ) != 27: failures.append( f"expected all 27 pairs resolution-scored, got {out.get -if out.get( "substitution_rate_baseline" ) != 0.0: failures.append( f"expected baseline substitution rate 0.0 (n -if out.get( "substitution_rate_ripwire" ) is None or abs( out["substitution_rate_ripwire"] - 0.75 ) > 1e-9: fail -if out.get( "n_substitution_ripwire" ) != 27: failures.append( f"expected 27 substitution-scored ripwire runs, g -if out3.get( "substitution_rate_ripwire" ) is not None or out3.get( "n_substitution_ripwire" ) != 0: failures.ap -if out2["n_pairs"] != 27: failures.append( f"evaluator-none: expected 27 pairs, got {out2['n_pairs']}" ) -if out2["n_resolved_pairs"] != 0: failures.append( f"evaluator-none: expected 0 resolution-scored pairs, got {ou -if out2["resolved_delta_mean"] is not None or out2["resolved_delta_bootstrap_95_lower"] is not None: failures.ap -if out2["tokens_out_ratio_p50"] is None or abs( out2["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( " -… [73 more display lines; full output is 16840 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --query="teleport pagerank" --top-k=5` - -*Raw BM25 ranking (debug lens; --for is the real verb).* - -**wall time: 1.25s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [28 more display lines; full output is 3515 bytes on 1 raw line(s)] -````` - - ---- - -# zoom the detail ladder - -## `./build/ripwire . --for="pagerank power iteration" --detail=2` - -*Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* - -**wall time: 2.78s** - -````` - - - - - -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] - -What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure -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 … [line truncated: 7 more bytes on this line] -inline std::uint32_t sliceRdIterationCeiling() noexcept -struct PageRankRun -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 ) -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… -inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window -inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept -inline std::string powerShellSingleQuote( std::string_view s ) noexcept -inline std::string path_prepend_hint( std::string_view dir ) -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` -### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] -### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] -## Background — read these, in this order -## Design space and constraints -COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "t … [line truncated: 62 more bytes on this line] -… [106 more display lines; full output is 13945 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: 92.7% fewer bytes at top-10, 88.7% at top-50, 87.6% at top-100 (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.12s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [135 more display lines; full output is 11971 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --outline=rankGraphTeleport --top-k=0` - -*Control-flow skeleton of one symbol, payload-only via the new --top-k=0.* - -**wall time: 1.11s** - -````` - - - - -& p, float alpha = 0.85f ) -{ - PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); - const std::vector pw = biasPrior( g, p ); - const std::size_t N = pw.size(); - std::vector teleport( pw.begin(), pw.end() ); - std::vector rankDouble( N, 0.0 ); - PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun - if( N ) - { - ... - } - std::vector r( N, 0.f ); - std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); - return { std::move( r ), run.iterationCount, run.hasConverged }; -} -]]> -````` - -## `./build/ripwire . --outline=rankGraphTeleport:1-10 --top-k=0` - -*CHANGED: a line range on --outline is now STRIPPED with a stderr note (it used to refuse).* - -**wall time: 1.10s** - -````` - - - - -& p, float alpha = 0.85f ) -{ - PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); - const std::vector pw = biasPrior( g, p ); - const std::size_t N = pw.size(); - std::vector teleport( pw.begin(), pw.end() ); - std::vector rankDouble( N, 0.0 ); - PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun - if( N ) - { - ... - } - std::vector r( N, 0.f ); - std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); - return { std::move( r ), run.iterationCount, run.hasConverged }; -} -]]> -````` - -stderr: - -````` -ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — outlining the whole symbol (use --expand=rankGraphTeleport:1-10 for a body slice) -````` - -## `./build/ripwire . --expand=rankGraphTeleport --top-k=0` - -*Full body + inline callee signatures.* - -**wall time: 1.09s** - -````` - - - - -, with a BRANCH-FREE size(). Both, not one or the other. -// -// ── THE DESIGN, AND WHY IT IS THIS ONE ─────────────────────────────────────────────────────────────── -// The shape the host tree leans on hardest is `Map>` — many tiny id-lists (byName / -// canonByName / shard maps, and ~100 more structures after the conversion wave): WRITE-ONCE during the -// parse/merge, then READ-HOT during resolve. -// -// Three things matter for that shape, and the layout below gets all three: -// • no per-list malloc — the N small lists that would each allocate are inline; -// • a BRANCH-FREE size() — `return sz_`, because the size lives in its own field; -// • 16 BYTES per instance — because `inl_` and `heap_` are never both live, so they share storage. -// -// The union is the whole trick. The previous revision of this file paid 8 extra bytes (24 B) for the -// explicit size field, on the reasoning that a branch-free size() was worth it. That was a false -// choice: union the inline array with the heap pointer and the struct reaches 16 B — ankerl's size — -// while the size stays in its own field and size() stays branch-free. -// -// THE TRADE THAT REMAINS, stated honestly. At 16 bytes you can have: -// • ankerl::svector's 3 inline slots, with a size() that branches on is_direct() (and, once spilled, -// dereferences into the heap block to read the size — a dependent load, not just a branch); or -// • this type's 2 inline slots, with size() branch-free. -// The measurement chose the second. See bench/SVECTORAB.md; the short version is below. -// -… [1088 more display lines; full output is 66245 bytes on 1114 raw line(s)] -````` - -stderr: - -````` -ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchanged behavior for now) -````` - - ---- - -# assess quality / structure - -## `./build/ripwire . --metrics --top-k=10` - -*Fan-in/out + complexity annotations on the map.* - -**wall time: 1.94s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [18 more display lines; full output is 5451 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --deps` - -*File->file dependency graph (god-files, cycles).* - -**wall time: 1.09s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [886 more display lines; full output is 23094 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --hotspots` - -*Complexity x recent git churn (maintenance pain).* - -**wall time: 2.73s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [14 more display lines; full output is 5753 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --clones` - -*Token-normalized duplicate bodies.* - -**wall time: 4.86s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [307 more display lines; full output is 16180 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --cochange` - -*Files that change together in git (hidden coupling).* - -**wall time: 2.23s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --hotspots --since="2 weeks ago"` - -*Hotspots scoped to RECENT churn (the regression lens).* - -**wall time: 1.85s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [13 more display lines; full output is 5109 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --arch=test/archfix/rules.txt` - -*Enforce layering rules (exit 2 on violation) — run against the repo's own test fixture rules.* - -**wall time: 1.08s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [563 more display lines; full output is 82067 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --lint` - -*Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* - -**wall time: 10.97s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [699 more display lines; full output is 69602 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: 3.14s** - -````` - - - - -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:395, rw::parseLintRuleFile(const std::string&, std::string_view, std::vector&):: — 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.* - -**wall time: 1.02s** - -Input file: - -````` -#PROF_TSV_BEGIN one row per scope, aggregated across threads; counters are RAW integers -scope file line calls total_ms l1d_mpki -walk: chase pass x.cpp 9 12 48.500 7.250 -#PROF_TSV_END -````` - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [13 more display lines; full output is 3428 bytes on 1 raw line(s)] -````` - -The joined finding — past the display cut above, extracted so the join is visible: - -````` -p = p->next -````` - -## `./build/ripwire . --communities` - -*Cluster the call graph into cohesive modules.* - -**wall time: 1.10s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [187 more display lines; full output is 16517 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --zoom` - -*Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels_shown="2" of levels= BY DEFAULT over the 40 largest top modules (~8 KB, where the whole tree is ~220 KB); a module AT the cut carries children=.* - -**wall time: 1.11s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [169 more display lines; full output is 8237 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --zoom --zoom-levels=3` - -*The restoring knob: --zoom-levels=N prints N levels (0 = the whole tree); here three, ~11 KB.* - -**wall time: 1.12s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [691 more display lines; full output is 27204 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --report` - -*Architecture summary (modules, god-files, cycles) as markdown.* - -**wall time: 1.62s** - -````` - - -# ripwire architecture report - -2484 files · 23824 symbols · 34201 edges · 2316 modules (10908 call-graph isolated) - -Root: `.` - -Call-graph isolate provenance: 2886 declaration, 1578 header, 1922 source, 4522 document; 0 connected Louvain singletons - -## Modules (call-graph clusters; showing 12 of 2316) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1366 symbols -- **src::append@elixir_resolve.h:111:4875 [resolve,collect,add]** — 1126 symbols -- **src::compare@structlayout.h:166:4708 [read,close,parse]** — 563 symbols -- **src::kindIs@infra/nodekind.h:46:3271 [emit,collect,push]** — 258 symbols -- **docs::`--top-k=N`@COMMANDS.md:59:8772 [pack,allow,insert]** — 175 symbols -- **src::realpath@infra/os.h:199:13067 [parse,split,read]** — 64 symbols -- **src::relForHash@model.h:1438:116507 [compute,clone,read]** — 60 symbols -- **src::assign@infra/svector.h:342:19905 [scan,run,resolve]** — 55 symbols -- **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [erase,find,count]** — 51 symbols -- **src/infra::active@profilePmc.h:845:34910 [print,ensure,report]** — 49 symbols -- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,verify,make]** — 35 symbols -- **test::no@decltodefcheck.sh:127:11708 [run]** — 30 symbols - -## God files (most depended-on; showing 10 of 445) -- `test/lib/clean-env.sh` — 180 dependents -- `src/infra/emit.h` — 86 dependents -- `src/model.h` — 85 dependents -- `src/infra/Diagnostics.h` — 58 dependents -- `src/serialize.h` — 39 dependents -… [32 more lines, 3520 bytes total] -````` - -## `./build/ripwire . --seams` - -*Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* - -**wall time: 1.08s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [102 more display lines; full output is 13421 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --mermaid` - -*Module (directory) dependency graph as a Mermaid diagram.* - -**wall time: 1.09s** - -````` -%% 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
7050"] - n102["src/infra
1276"] - end - subgraph sg1 ["test"] - n103["test
4844"] - n328["test/fixtures/recallpassage
314"] - n280["test/expandmodefix
151"] - n282["test/expandsibsfix
149"] - n458["test/rubyattrsfix
85"] - n395["test/massfix
77"] - n360["test/javamethodreffix
62"] - end - n73["docs
829"] - n2[".github
598"] - subgraph sg4 ["bench"] - n4["bench
531"] - 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"] - n62["bench/shotgun
96"] - n45["bench/locbench/results/r1cpp_anchorhop
94"] - n11["bench/arb
85"] - n56["bench/nestcal/r1-2026-08-07
84"] -… [21 more lines, 1606 bytes total] -````` - -## `./build/ripwire . --owners` - -*Bus-factor: recency-weighted author ownership per file.* - -**wall time: 2.78s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [886 more display lines; full output is 97855 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.18s** - -````` - - - - -````` - -## `./build/ripwire . --exercises=test/regression.sh` - -*Which symbols a TEST FILE exercises — the reverse direction of --affected.* - -**wall time: 1.07s** - -````` - - - -````` - -## `./build/ripwire . --community=0` - -*Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* - -**wall time: 1.11s** - -````` - - - -````` - -## `./build/ripwire . --quality-delta` - -*Recorded against a DIRTY tree, so any row below is a real regression in the working copy — the recorded exit code says whether anything gated. The sandbox section below shows the same gating shape on a known, deliberate edit.* - -**wall time: 76.00s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [104 more display lines; full output is 13026 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 — recorded against a DIRTY tree, so the verdict describes the working copy, not HEAD alone, and the recorded exit code is the working copy's.* - -**wall time: 1.72s** - -````` - - - - - - - - - -````` - -## `./build/ripwire . --pr-context` - -*No-LLM review-evidence bundle for the working-tree diff — recorded against a DIRTY tree, so it is populated rather than empty.* - -**wall time: 3.25s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-````` - -## `./build/ripwire . --pr-context=HEAD~1` - -*The BASEREF form: diffed against merge-base(BASEREF, HEAD), never the ref tip — here the previous commit on the current line (a ref with NO merge base falls back to a disclosed two-dot diff: anchor="ref-tip-two-dot").* - -**wall time: 3.84s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./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: 43.12s** - -````` - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --stray-content=main` - -*Which refs of the one branch `main` (no ref family left to select on this checkout) still hold divergent authored work vs HEAD, with verdicts.* - -**wall time: 1.82s** - -````` - - - - - - - - - -````` - -## `./build/ripwire . --stray-content=sync-rebase-backup` - -*A second selection, the one branch `sync-rebase-backup` (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.28s** - -````` - - - - - - - - - - -````` - -## `./build/ripwire . --stray-content=main --plan` - -*Select the genuinely-unmerged refs of the one branch `main` (no ref family left to select on this checkout) 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: 31.68s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [61 more display lines; full output is 6813 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.19s** - -````` -(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=main --abi` - -*Cross-branch ABI-break gate over the one branch `main` (no ref family left to select on this checkout): 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.* - -**wall time: 2.02s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [34 more display lines; full output is 16096 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: 19.87s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [366 more display lines; full output is 22644 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --flags --flip=RIPWIRE_ASAN` - -*Blast radius of turning ONE gate on: live code, symbols, transitive reach, covering tests.* - -**wall time: 1.91s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --flags --flip=RIPWIRE_ASA` - -*Unknown-gate refusal (exit 1) with a did-you-mean from a real edit distance (one character off RIPWIRE_ASAN).* - -**exit code: 1** — **wall time: 1.33s** - -````` -(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.89s** - -````` -{"v":1,"verb":"plan-lanes","at":"6bf9d9d02+dirty","root":".","task":"add a --since filter to the doc-drift verb and cover it with tests","source":"partition","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2484,"symbols":23824,"edge … [line truncated: 345 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":639,"id":"./src/mcpverbs.h::rw::docDriftText"}, -{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","l":1641,"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/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":3,"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":2,"cx":8,"ccx":8,"churn":31,"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":2,"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":2863,"ord":0,"overloads":1,"amb":33,"cx":8,"ccx":11,"churn":414,"tested":0}, -{"p":"./src/gitmine.h","n":"resolveSinceScope","scope":"rw","key":"9caaa3dbeaa688d0","id":"./src/gitmine.h::rw::resolveSinceScope","id_addressable":true,"id_collides_with":0,"l":326,"ord":0,"overloads":1,"amb":3,"cx":7,"ccx":7,"churn":59,"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":350,"ord":0,"overloads":1,"amb":0,"cx":3,"ccx":2,"churn":18,"tested":0}, -{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":685,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":414,"tested":0}, -{"p":"./src/taskroute.h","n":"testCoverageTaskChoice","scope":"rw::taskroute","key":"bf72991aa28355c7","id":"./src/taskroute.h::rw::taskroute::testCoverageTaskChoice","id_addressable":true,"id_collides_with":0,"l":1365,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":34,"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":5229,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":310,"tested":0}], -"files":[{"p":"./src/darkflags.h","symbols":1,"churn":31,"ccx":8,"hotspot_rank":45}, -{"p":"./src/docdrift.h","symbols":4,"churn":36,"ccx":14,"hotspot_rank":31}, -{"p":"./src/gitmine.h","symbols":1,"churn":59,"ccx":7,"hotspot_rank":18}, -{"p":"./src/main.cpp","symbols":2,"churn":414,"ccx":12,"hotspot_rank":4}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":310,"ccx":2,"hotspot_rank":5}, -{"p":"./src/pageview.h","symbols":1,"churn":18,"ccx":2,"hotspot_rank":135}, -{"p":"./src/taskroute.h","symbols":1,"churn":34,"ccx":2,"hotspot_rank":40}, -{"p":"./src/testmap.h","symbols":1,"churn":48,"ccx":4,"hotspot_rank":34}, -{"p":"./src/verbs_change.h","symbols":1,"churn":69,"ccx":8,"hotspot_rank":23}]},"blast_radius":{"reaches":51,"files_total":17,"capped":false,"files":["./src/darkflags.h","./src/docdrift.h","./src/editplan.h","./src/flipimpact.h","./src/gitmine.h","./src/main.cpp","./src/mcp.h","./src/mcpedit.h","./s … [line truncated: 203 more bytes on this line] -"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":51,"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":59,"ambiguous_calls":49,"blast_reaches":51,"contract_touches":0,"conflicts":0,"untested":51,"tests … [line truncated: 135 more bytes on this line] -… [74 more display lines; full output is 20829 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --plan-lanes --brief=/aux/lanes_brief.txt` - -*NEW VERB, explicit form: one line per lane, lane boundaries are the ones you wrote (the defensible mode).* - -**wall time: 3.23s** - -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":"6bf9d9d02+dirty","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2484,"symbols":23824,"edges":34201,"ambiguous":10271,"unresolved":12861},"carve":null,"core":{ … [line truncated: 11 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":639,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":310,"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":3183,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":433,"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":12,"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":12,"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":1641,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":69,"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/mcprefusal.h","n":"kMcpRequiredFields","scope":"rw::mcprefuse","key":"c5f1ad5fc3a12368","id":"./src/mcprefusal.h::rw::mcprefuse::kMcpRequiredFields","id_addressable":true,"id_collides_with":0,"l":67,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":38,"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":433,"ccx":0,"hotspot_rank":6}, -{"p":"./src/docdrift.h","symbols":6,"churn":36,"ccx":54,"hotspot_rank":31}, -{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":87}, -{"p":"./src/mcprefusal.h","symbols":1,"churn":38,"ccx":0,"hotspot_rank":56}, -{"p":"./src/mcpverbs.h","symbols":1,"churn":310,"ccx":0,"hotspot_rank":5}, -{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":52}, -{"p":"./src/verbs_change.h","symbols":1,"churn":69,"ccx":4,"hotspot_rank":23}], -"symbols_total":3146,"symbols_capped":true},"blast_radius":{"reaches":12,"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":12,"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":7,"module_span":6,"max_ccx":38,"sum_ccx":62,"ambiguous_calls":26,"blast_reaches":12,"contract_touches":1,"conflicts":3,"untested":12,"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":107,"l":271,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":10, … [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":2807,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":433,"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":3183,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":433,"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":2886,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":433,"tested":0}, -{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":107,"l":865,"ord":0,"overloads":1,"amb":0,"cx":19,"ccx":28,"churn":17,"tested":0}, -{"p":"./src/slice.h","n":"sliceRdIterationCeiling","scope":"slicev","key":"449132b302bb4324","id":"./src/slice.h::slicev::sliceRdIterationCeiling","id_addressable":true,"id_collides_with":0,"l":1372,"ord":0,"overloads":1,"amb":0,"cx":9,"ccx":12,"churn":51,"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":38,"tested":0}, -… [64 more display lines; full output is 19303 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** — **wall time: 1.64s** - -````` -(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.15s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [243 more display lines; full output is 25217 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.08s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [269 more display lines; full output is 26416 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.51s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [221 more display lines; full output is 25131 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --from-trace=-` - -*Map a pasted stack trace onto indexed symbols. CHANGED: in_corpus= now reports the real count (was 0).* - -**wall time: 1.10s** - -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:5567 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:5608 - #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 - #3 0x102e01a44 in main src/main.cpp:3983 - #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 5100 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.* - -**wall time: 1.27s** - -````` - - - - - - - - - - - gate scripts' claim (~line 1305) is NOT enforced — the derived-vs-stated sibling loop here covers docs/EVALS.md only. It drifted 407→451 unnoticed (fixed 2026-08-23). To close: grep both files ('file:line:' parsing) in the gateCountClaims arm.]]> - - - - -````` - -## `./build/ripwire . --pack-task="add a new output format flag to the CLI"` - -*ONE budget-shared bundle: ranking + top bodies + caller sigs + notes + tests_to_run. CHANGED: rows now carry n=/id=.* - -**wall time: 1.98s** - -````` - - - - -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 - -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 - -knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedBy makes in full above, which applies here verbatim: firstFlagOutside() walks the rows parseArgs itself minline constexpr std::string_view kInRideAlong[] = - - - - - - - - -… [47 more display lines; full output is 9435 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.* - -**wall time: 1.89s** - -````` - 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] - -What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure -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 … [line truncated: 7 more bytes on this line] -inline std::uint32_t sliceRdIterationCeiling() noexcept -struct PageRankRun -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 ) -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… -inline constexpr std::string_view kPowerShellPathPrependScope = "in PowerShell, for this window -inline std::string powerShellPathPrependHint( std::string_view programDir ) noexcept -inline std::string powerShellSingleQuote( std::string_view s ) noexcept -inline std::string path_prepend_hint( std::string_view dir ) -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` -### Same answer, a fraction of the tokens — read this table first if your ag … [line truncated: 22 more bytes on this line] -### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 … [line truncated: 25 more bytes on this line] -## Background — read these, in this order -## Design space and constraints -COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "t … [line truncated: 62 more bytes on this line] -… [60 more display lines; full output is 9622 bytes on 10 raw line(s)] -````` - -## `./build/ripwire . --export=cc.json:/aux/ripwire2.cc.json` - -*Per-file metrics as CodeCharta cc.json.* - -**wall time: 2.35s** - -````` -(empty) -````` - -Artifact written: - -````` -374999 /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: 16.72s** - -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 ) -inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) -struct FileHealth -inline std::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& implementors, const std::vec… -inline const McpIndex& getIndex( const std::string& root ) -inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) -struct McpIndex -### See the map — not just the numbers -inline std::pair<Snapshot, bool> computeHeadSnapshot( const std::string& root, const std::string_view* cacheNever = nullptr, std::size_t maxFileBytes = kDefault… -inline std::string receiptNextFor( const std::string& fileIdentity, const std::string& symbolName, const std::string& foldJson, const std::string& firstTestRun ) -inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cache, const CacheLoadStats& cacheStats … [line truncated: 59 more bytes on this line] -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 respect … [line truncated: 29 more bytes on this line] -const char* cacheArtifactVerdict( const std::string& path, bool captureValueUses ) -static bool cachePathIsDirectory( const std::string& cachePath ) -inline constexpr std::array<std::string_view, 8> kCacheRuleNames = -… [57 more display lines; full output is 41180 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: 2.70s** - -````` - - - - - - - - - - -````` - -stderr: - -````` -ripwire --doctor: no ripwire resolves from PATH. To put this binary's directory first (for this shell; put the same line in your shell rc file), paste this line: -export PATH='./build':"$PATH" -````` - - ---- - -# 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: 1.94s** - -````` - - - - - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --rank-by=churn-decay --since=HEAD~11 --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.* - -````` - - - - - - - - - - - - - - - - - - - - - -````` - -## `./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 -1165,5606,6252,171,1380,1419,1629 -fn,fn,fn,fn,fn,fn,fn -0,0,0,0,0,0,0 - - -````` - -## `./build/ripwire . --for="cache invalidation" --format=candidates --top-k=5` - -*Flat top-K export for an external reranker.* - -**wall time: 1.22s** - -````` - - - -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":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1165"}, -{"t":"fn","n":"rankGraph","p":"src/graph.h:5606"}, -{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:6252"}, -{"t":"fn","n":"runEval","p":"src/eval.h:171"}, -{"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1380"}, -{"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1419"}, -{"t":"fn","n":"runDefaultMap","p":"src/main.cpp:1629"}]} -````` - -## `./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: 1.17s** - -````` - - - - - - - -````` - -## `./build/ripwire . --ignore-tests --top-k=5` - -*Drop test paths from the corpus before ranking.* - -````` - - - - - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --exclude=present --exclude=bench --top-k=5` - -*Drop matching paths (repeatable) before ranking.* - -````` - - - - - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --map-diff --top-k=5` - -*Full map re-ranked with teleport toward git-changed files — recorded against a DIRTY tree, so changed= counts the working copy's files and the teleport is live.* - -````` - - - - - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --no-cache --top-k=3` - -*Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm cost.* - -**wall time: 8.44s** - -````` - - - - - - - - - - - - - -````` - -## `./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3` - -*Explicit incremental cache at a path OUTSIDE the repo (first call writes it).* - -**wall time: 9.00s** - -````` - - - - - - - - - - - - - -````` - -Artifact written: - -````` -18629044 /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).* - -````` - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --scip=does_not_exist.scip --callers=rankGraphTeleport` - -*SCIP overlay with a missing index REFUSES (exit 1) naming the file — never silently serves the name-based map you named a precision index to improve on (it used to degrade in silence).* - -**exit code: 1** - -````` -(empty) -````` - -stderr: - -````` -ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather than serving the name-based map you named a precision index to improve on (generate one with scip-clang/scip-python, or drop --scip) -````` - -## `./build/ripwire src test --top-k=5` - -*Multi-root workspace: ONE merged graph over two roots, paths labeled /.* - -**wall time: 8.66s** - -````` - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --eval` - -*Self-eval: co-change recall vs BM25.* - -**wall time: 23.50s** - -````` -ripwire --eval (co-change recovery, averaged over 80 historical commits) - ranker recall@5 recall@10 recall@20 - ripwire 2.9% 7.0% 15.3% - BM25 12.7% 14.9% 18.6% - BM25sub 17.2% 23.2% 27.6% - BM25body 15.1% 28.4% 41.9% - fused 13.0% 16.2% 24.9% - anchored 15.1% 28.4% 41.9% - same-dir 0.0% 8.8% 9.8% - random 0.2% 0.4% 0.8% <- floor (random ranking over F=2484 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: 118.34s** - -````` -ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) - sample: population=5818 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.693 56.0% 85.7% 90.7% - subtoken doc-phrase 0.920 90.0% 94.0% 94.9% - name-exact name 0.905 83.5% 96.1% 97.4% - name-exact doc-phrase 0.013 0.5% 1.8% 2.6% - anchored name 0.699 57.6% 85.2% 89.1% - anchored doc-phrase 0.917 89.5% 94.0% 94.5% - routed name 0.908 83.7% 96.3% 97.7% - routed doc-phrase 0.919 90.0% 93.8% 94.7% - note: routing chose name-exact on 3986/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: 1.28s** - -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) -sync-rebase merged -sync-rebase-backup unmerged -sync-upstream 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.690 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.690 0.667 75.0% (th=2.958) - 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.685) - 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, 3621 bytes total] -````` - -## `./build/ripwire wrap claude` - -*Print the recipe to wire ripwire into Claude Code as an MCP server.* - -````` -# ripwire -> Claude Code (CLI-first) -# NOTE: nothing on PATH is named ripwire right now, so the command below is this binary's absolute path; -# put its directory on PATH (the installer printed the export line) and the bare word works too. -# 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: -./build/ripwire . --for="" --token-budget=2000 -# -# ...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 -- ./build/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`. -… [16 more lines, 4420 bytes total] -````` - -## `./build/ripwire --version` - -*Version + short build info.* - -````` -ripwire 0.6.5 (dev, GNU 13.3.0, emit=std::format+fputs, built_from=66e5c3e10) -````` - - ---- - -# navigate — seeds, claims, slices, shapes - -## `./build/ripwire . --at=src/graph.h:5567` - -*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:5567` - -*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 9418 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: 3.71s** - -````` - -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: 4.56s** - -````` - -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( 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( 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( out, CMakeScan::DisclosureWhy::SymlinkEscapesRoot, "flags: a CMake file's symlink target leaves the root — f -DISCLOSE( out, CMakeScan::DisclosureWhy::RootWalkFailed, "flags: cannot walk root for CMake files — cmake gates omitte -DISCLOSE( r, Result::DisclosureWhy::BaseTreeUnavailable, "dmm: the base commit's tree could not be materialized or parse -DISCLOSE( r, Result::DisclosureWhy::TargetTreeUnavailable, "dmm: the target commit's tree could not be materialized or p -… [73 more display lines; full output is 17672 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 -enum class AstWalk : std::uint8_t -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 -inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept -constexpr std::array<LangEntry, 51> kLangTable = -constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u -inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept -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 ) -std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] -bool jsonNestsTooDeep( std::string_view bytes ) noexcept -constexpr std::uint32_t kMaxYamlNestDepth = 64u -SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) -inline bool hasMainGuard( std::string_view source ) -inline void runParseWorker( ParsePoolShared& sh, unsigned t ) -inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) -struct LintRule -enum class DisclosureWhy : std::uint8_t -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [55 more display lines; full output is 9066 bytes on 1 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.14s** - -````` - - - - -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 -enum class AstWalk : std::uint8_t -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 -inline bool astroFenceTailIsBlank( std::string_view src, std::size_t from, std::size_t to ) noexcept -constexpr std::array<LangEntry, 51> kLangTable = -constexpr std::uint32_t kMaxKotlinStringNestDepth = 128u -inline bool hasPhantomScopeSeparator( TSNode qualified ) noexcept -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 ) -std::vector<std::vector<rw::AstMatch>> builtInLintCaptures( const rw::IngestResult& ing, const std::vector<rw::AstQuerySpec>& checks, std::vector<std::string>& … [line truncated: 17 more bytes on this line] -bool jsonNestsTooDeep( std::string_view bytes ) noexcept -constexpr std::uint32_t kMaxYamlNestDepth = 64u -SpanTierBatch spanTiersOfFiles( std::span<const std::string> diskPaths, bool useMemo ) -inline bool hasMainGuard( std::string_view source ) -inline void runParseWorker( ParsePoolShared& sh, unsigned t ) -inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) -struct LintRule -enum class DisclosureWhy : std::uint8_t -inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) -… [90 more display lines; full output is 12638 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.32s** - -````` - - - - -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - -runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) - -H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… -struct DeltaBasis -inline const char* kAcksFile = ".ripwire_quality_acks" -DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) -std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -struct QualityDeltaOutcome -inline std::string qualityAcksPath( const std::string& root ) -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at -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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) -inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) -int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) -inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) -inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] -inline std::string qualityBaselinePath( const std::string& root ) -… [52 more display lines; full output is 9269 bytes on 1 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.13s** - -````` - - - - -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - -runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) - -H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… -struct DeltaBasis -inline const char* kAcksFile = ".ripwire_quality_acks" -DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) -struct IdentityHealing -inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) -inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) -std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) -inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) -inline bool atomicWriteFile( const std::string& path, const std::string& blob ) -struct QualityDeltaOutcome -inline std::string qualityAcksPath( const std::string& root ) -inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at -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 rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) -inline std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) -int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) -inline Result computeDmm( const std::string& root, std::string_view spec, const IngestResult& workingIng, const std::vector<std::string>& excludes, std::size_t maxFileBytes ) -inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which de … [line truncated: 76 more bytes on this line] -inline std::string qualityBaselinePath( const std::string& root ) -… [53 more display lines; full output is 9435 bytes on 1 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 DIRTY tree.* - -**wall time: 1.67s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./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: 1.49s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./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.* - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [399 more display lines; full output is 42579 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).* - -````` - - - - - - - - - - - - - -````` - -## `./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.* - -````` - - - - - - - - - - - - - -````` - -stderr: - -````` -ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable ordering the server turns on) — it changed nothing here; the CLI map orders important-first unless you pass --order=stable -````` - -## `./build/ripwire . --run-trace="cat /aux/asan_trace_now.txt; exit 1"` - -*EXEC-MODE --from-trace: run a command, and on a non-zero exit map its captured output onto indexed symbols in the same call — the whole fix-loop entry. ripwire exits 4 here because the wrapped command failed, which is the signal, not an incident.* - -**exit code: 4** - -````` - - - - - - const&, float) src/graph.h:5567 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:5608 - #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 - #3 0x102e01a44 in main src/main.cpp:3983 - #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: 480 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.53s** - -````` - - - - -````` - -stderr: - -````` -ripwire: --run-trace: TIMEOUT — the command exceeded the 2 s cap; its process group was killed -````` - -## `./build/ripwire . --run-timeout=5` - -*--run-timeout alone is refused loudly (it only modifies --run-trace).* - -**exit code: 1** - -````` -(empty) -````` - -stderr: - -````` -ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwire --run-trace="make -j" --run-timeout=60) -````` - - ---- - -# assess quality — the wider lens family - -## `./build/ripwire . --quality-panel` - -*THE single wide-angle quality read: six families in one pass, an eligible/ranked shortlist rather than a firehose.* - -**wall time: 6.68s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [186 more display lines; full output is 15833 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.02s** - -````` - - - - - - - - - - - -````` - -## `./build/ripwire . --comment-coherence --limit=8` - -*Functions WITH a doc comment, most name-restating first: c_coeff (high = the comment repeats the name) and cic (Jaccard of comment vs identifier vocabulary), both reported, never collapsed.* - -````` - - - - - - - - - - - -````` - -## `./build/ripwire . --context-ratio --limit=8` - -*The local-reasoning lens: to understand this symbol, how much must you know that is NOT in front of you (ent_ratio= edge share, read_ratio= token-weighted).* - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [21 more display lines; full output is 11207 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --nonlocal-state --limit=8` - -*Per function, the non-local MUTABLE state it can reach (transitively), most writes first — unsound by construction, and the legend says where.* - -**wall time: 3.33s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [41 more display lines; full output is 7593 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --ensemble --limit=8` - -*The family join: per function, which of four orthogonal evidence families fire, ranked by how many agree.* - -**wall time: 3.97s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [36 more display lines; full output is 7433 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --field-affinity` - -*The cache-locality lens over every aggregate: fields READ TOGETHER but declared FAR APART (split-line / straddle findings, Chilimbi separation weight) — advice only, never a rewrite.* - -**wall time: 3.79s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [505 more display lines; full output is 45388 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: 3.23s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./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 4383 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: 9.11s** - -````` - - - - - - - - - - - -

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-… [119 more display lines; full output is 12984 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: 6.82s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [641 more display lines; full output is 72400 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 7764 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=ebae13b851e3a641 entries=791 -: 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, -… [762 more display lines; full output is 78760 bytes on 792 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: 6.48s** - -````` - - - - - - - - - - -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] ] -… [507 more display lines; full output is 61305 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: 6.32s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - -0.5 -1023u -main (90 lines) -20 -delta < 0.02 && delta > -0.02 -0.02 -… [729 more display lines; full output is 65783 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --lint --lint-select=cach-` - -*An unresolvable PREFIX refuses (exit 1) with a did-you-mean from a real edit distance (one character off cache-).* - -**exit code: 1** — **wall time: 6.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: 6.35s** - -````` -(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: 6.24s** - -````` -{"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}}, -… [9999 more display lines; full output is 1400831 bytes on 1 raw line(s)] -````` - -Parsed summary of the same SARIF (past the display cut): - -````` -sarif 2.1.0 rules= 39 results= 4995 -````` - -## `./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 DIRTY tree (the sandbox section shows a real delta). UNAVAILABLE is a stated reason, never 0 or 1.* - -**wall time: 4.28s** - -````` - - -

-

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

-

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

-

- -````` - -## `./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: 1.31s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [727 more display lines; full output is 47863 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: 1.68s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./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: 1.26s** - -````` -(empty) -````` - -Artifact written: - -````` -158957 /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: 18.93s** - -````` -(empty) -````` - -stderr: - -````` -ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (18629044 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (39954556 bytes, rich family) -````` - -Artifact written: - -````` -18629044 /aux/ci_index.lean.ripwirecache -39954556 /aux/ci_index.rich.ripwirecache -58583600 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.* - -````` - - - - - - - - - - - - - -````` - -## `./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.* - -````` - - - - - - - - - - - - - -````` - -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. -114767 /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** - -````` - - - - - - - - - - - - - -````` - -## `./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** - -````` - - - - - - - -````` - -## `./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: 1.48s** - -````` - - - - - - - - - - - - - -````` - -stderr: - -````` -ripwire --doctor: no ripwire resolves from PATH. To put this binary's directory first (for this shell; put the same line in your shell rc file), paste this line: -export PATH='./build':"$PATH" -````` - -## `./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.96s** - -````` -ripwire situational-awareness — 1 changed file(s), 13 symbols in them -root: . -at: 6bf9d9d02+dirty - [1] blast radius: 193 symbols across 46 files transitively depend on these changes (showing 8 of 46 files — shown=8 total=46 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=46) - counts_floor=1 graph_ambiguous=10274 graph_unresolved=12861 graph_unindexed=237 (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 missin … [line truncated: 46 more bytes on this line] - src/mcpverbs.h (32 dependent symbols) - src/slice.h (30 dependent symbols) - src/serialize.h (9 dependent symbols) - src/quality.h (8 dependent symbols) - src/verbs_for.h (8 dependent symbols) - src/verbs_navigate.h (8 dependent symbols) - src/main.cpp (7 dependent symbols) - src/verbs_quality.h (7 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=729 — 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="3919": - (none — 3919 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: 1.28s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./build/ripwire . --quality-delta` - -*Every row carries p="file:line", the gating rows are marked gating="1" and now bar= (the threshold each numeric row is judged against), and the exit-2 refusal prints a naming line on stderr; a gating row carries a pasteable next=.* - -**exit code: 2** — **wall time: 43.52s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [113 more display lines; full output is 15115 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, but the ~4 KB prose legend the compact default trims is restored: the posture a human reads once to learn the schema.* - -**exit code: 2** — **wall time: 12.92s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [113 more display lines; full output is 19929 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: 12.95s** - -````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":131,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"6bf9d9d02+dirty","renames":58,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 211 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":"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"}, -{"kind":"api-surface:new-symbol","key":"995375dfa4e63104","why":"target-gone"}, -… [110 more display lines; full output is 13709 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: 7.44s** - -````` -(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, 224 re-derived, 9 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1328 ineligible (no cur … [line truncated: 198 more bytes on this line] -ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written -````` - -## `./build/ripwire . --quality-delta --quality-ack --ack-only=api-surface` - -*NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* - -**wall time: 7.60s** - -````` -(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, 224 re-derived, 9 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1328 ineligible (no cur … [line truncated: 198 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.82s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [112 more display lines; full output is 14818 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.* - -````` - - - - - - -````` - -## `./build/ripwire . --edit-check=nonNegativeFloatDescKey --legend=full` - -*The same verdict under --legend=full: every row identical to the compact default above, but the ~5.7 KB explanatory legend is restored — the posture for a first-time reader.* - -````` - - - - - - -````` - -## `./build/ripwire . --pr-context` - -*The review-evidence bundle with an actual changed file.* - -**wall time: 2.00s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [87 more display lines; full output is 8459 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).* - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [37 more display lines; full output is 4185 bytes on 1 raw line(s)] -````` - -## `./build/ripwire . --clones` - -*The duplicated helper the sandbox edit introduced shows up as a clone group.* - -**wall time: 1.93s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [307 more display lines; full output is 16180 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.)* - -````` - - - -````` - -## `./build/ripwire . --stray-content=zz-orphan --plan` - -*CHANGED: --plan surfaces those same refs as an row rather than silently dropping them.* - -````` - - - -````` - -## `./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.15s** - -````` - - -

-

- -````` - -## `./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: 12.73s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [115 more display lines; full output is 15085 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.76s** - -````` -(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: 1.46s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -````` - -## `./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.* - -````` -src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-07 keep this branch-free — it sits inside the PageRank sort comparator 6bf9d9d024e0c50a13dc78c77480f7fcd02faf10 sync-rebase -````` - -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-07 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 6bf9d9d024e0c50a13dc78c77480f7fcd02faf10 sync-rebase -````` - -## `./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: 2.37s** - -````` -{"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":10271,"graph_unresolved":12861,"graph_unindexed":237,"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":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"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.00s** - -````` -{"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: 2.46s** - -````` -{"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":10271,"graph_unresolved":12861,"graph_unindexed":237,"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":729,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"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** - -````` -(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.03s** - -Input file: - -````` -{ - "version": 1, - "edits": [ - { - "op": "insert_before_symbol", - "target": "nonNegativeFloatDescKey", - "payload": "plan_note.h" - } - ] -} -````` - -````` -{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false, … [line truncated: 302 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: 2.18s** - -````` -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":10271,"graph_unresolved":12861,"graph_unindexed":237,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 486 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":10271,"graph_unresolved":12861,"graph_unindexed":237,"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: 13.15s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [109 more display lines; full output is 14182 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.41s** - -````` -(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: 12.80s** - -````` -(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 23111 per-symbol rows, 23826 indexed symbols total) -````` - -Artifact written: - -````` -3871406 .ripwire_quality_baseline -# ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit -producer 85ecb2524b1660dc253d710de89497b18141d2cf6cf5740e726547090d74c7ae -head 6bf9d9d024e0c50a13dc78c77480f7fcd02faf10 -dirty 1 -absorbed 2 -ccx d19d7c13cbc7 2 -ccx 1f7edbff929e5 10 -ccx 6a732f6699bdb 0 -ccx 7d8862be4e -````` - -## `./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: 12.93s** - -````` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -… [104 more display lines; full output is 13047 bytes on 1 raw line(s)] -````` - - ---- - -# 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: 735 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 ceiling (the CLI --for --token-budget). The header carries route= (which ranker answere … [line truncated: 1053 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: 657 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: 806 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: 2117 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: 1226 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: 1250 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= 46950 (~tokens at 4 bytes/token: 11737 ) -explore desc_bytes= 1322 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'] -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=[] -whereis desc_bytes= 701 schema_bytes= 714 required=['symbol'] -affected desc_bytes= 790 schema_bytes= 610 required=['files'] -from_trace desc_bytes= 829 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= 445 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=[] -grep desc_bytes= 445 schema_bytes= 562 required=['pattern'] -memory_recall desc_bytes= 447 schema_bytes= 500 required=['task'] -quality_delta desc_bytes= 772 schema_bytes= 193 required=[] -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=[] -lego desc_bytes= 360 schema_bytes= 467 required=['type'] -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/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.59s** - -````` -{"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":"I>tA9O$pB>M{s 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

LkwL3gw4SI3Lp zc`w#jteBYM!b?zQ_+-1tG)g0bon-cFLwmxiJFMGbOFuSZm<;y*-&)Kr zQ>O~SL}D=btWINl0RnIC?ZDuV=nn5sR}X(jJau>)85b9~$|=URk&zD$wY0O~G9_vk zEq*CW25XJf!^r3-O@Z5+>%_(Ud3xhj%b8S*OPKnzUg4mW@SycpEU+^QzTZ#W1_f30 zE0u06^}Zj(?UfTH6%5o&<4s(Oa7oe3Hr&|@e<@UY{~%D!LEDQdhrR-1q0&)XhxfHR z36Hio9fSfI5VI87LtGxw?5#`h`?8{>OVJMzUx5O=15A}%DPbP57%G8M9JI5;B6hFl zt1c<8w3Nj;5ONhYB7ohH_@InJarI>8M{Ino6xg5V)8yaH}s*8Y`ieG_NrE zGycd?rZU6X%)&gOV@u^aQyHhod#*Kn#?nF)ZHWxDv)v(Z`^48UFj);GQM=u}pP2D{ zZvN^4#+#i4uHlXoefJSL;JB@)ce#BCop%4gnU635MiDsB&8a@o>+;R-qNKzNJ$F}m zQIN#t)~jZc$>cu0exei4fTeM+Il%!K~to9BE5phd@Qr zVy1aHEmB;@@VAWTiz51*%W2v{2>~)++g36-hVB_Ot1ReshAhP40w@dLW_Ar}V9u0-t5du*Z4IET^9D%Q|<2#3*w1!0;v$8-SUVNk)K<_=gq~Xc^1;Bw~#ep{9_uJvMm<>%}jD8z0 z9TA&0+;LZ|B$7+!x;$&<7$f~Ge^8gwRm}GyVkPOM8e?QMSOG#Vo64v8*;`5skEihI@Xbf�!)Qqe5fFv$N>scusV&uD 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 diff --git a/present/ripwire-showcase.pptx b/present/ripwire-showcase.pptx index 277d5747219ac2e114f67b2d62a75f437d2945ec..9d59e8ea5ce7e3d392af9aeddc72cba6451dfb72 100644 GIT binary patch delta 6104 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 delta 6105 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 0 ? pageLimit : 0 ) + "\" tier=\"" + ( below ? "below-cliff" : "head" ) + "\"" - + ( rankingName.empty() ? std::string() : " ranking=\"" + std::string( rankingName ) + "\"" ) + std::string( extra ); }; // THE PASTEABLE CONTINUATION HANDLE — the file page's next= contract: the argv that walks to @@ -335,9 +337,10 @@ inline std::string forCandidatePageDoc( const rw::IngestResult& ing, const std:: // no hint) and dropped when has_more="0" (#362 review, item 3: a last page handing back a next // that returns an empty page carrying the same next= is a loop with no exit). const auto buildNextAttr = [ & ]( std::size_t nextOff ) { - if( !pasteHandle ) { return std::string(); } // MCP: no pasteable CLI argv — the machine attrs carry the resume (#362 review, item 1) + if( !pasteHandle ) { return std::string(); } // no surface passes false today (B1); the guard stays honest if( nextOff >= candidateTotal ) { return std::string(); } std::string inv = nextFlag( verbName, task ); // THEIR verb: pasting continues the same verb (#294 review) + inv += rankFlags; // B2: the ranking's own flags ride the handle, or the paste re-ranks a different list inv += " --token-budget=" + std::to_string( tokenBudget ); if( pageLimit > 0 ) { inv += " --limit=" + std::to_string( pageLimit ); } inv += " --offset=" + std::to_string( nextOff ); diff --git a/src/mcpverbs.h b/src/mcpverbs.h index c705133f7..8269fe539 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -1963,12 +1963,15 @@ inline std::optional forTaskText( const std::string& root, const st if( budgetTokens > 0 ) { const AdaptiveCut cut = adaptiveCut( lensRank, 5, std::size_t( forTopN ), /*scanFullDistribution=*/true ); - // #362 review, item 1+3: the twins' shared inline pipeline ("mcp-lens") — ranking= names it; NO - // pasteable CLI handle (a CLI continuation would walk verbs_for.h computeLensRanking's list); the - // compact dialect emitted in-doc (schema= present) so the ref posture reduces it like any other - // spec-keyed answer — the outer layer sees AlreadyCompact and passes it through. - return forCandidatePageDoc( ing, lensRank, cut, ForCandidatePageReq{ task, "--for=", /*ranking=*/"mcp-lens", - /*pasteHandle=*/false, /*compactLegend=*/true, + // #362 review round 4 (B1): the twins' inline pipeline always scored the full distribution + // (pruneTopK=0) and the CLI pages now force fullDistribution under a window, so the five page + // paths rank ONE list — the page carries the same pasteable CLI next= as the CLI pages, with + // no_route echoed (B2) so the pasted continuation re-ranks identically. The compact dialect + // emitted in-doc (schema= present) so the ref posture reduces it like any other spec-keyed + // answer — the outer layer sees AlreadyCompact and passes it through. + return forCandidatePageDoc( ing, lensRank, cut, ForCandidatePageReq{ task, "--for=", + noRoute ? std::string_view( " --no-route" ) : std::string_view(), + /*pasteHandle=*/true, /*compactLegend=*/true, routeNoteOf( rc, shape, noRoute ), mcpRootArg, redact, gitstamp::stampAt( root ), budgetTokens, page.limit, page.offset } ); } @@ -4269,8 +4272,11 @@ inline std::string packTaskText( const std::string& root, const std::string& tas if( budgetTokens > 0 ) { const AdaptiveCut cut = adaptiveCut( lr.rank, 5, std::size_t( kForLensDefaultTopN ), /*scanFullDistribution=*/true ); - return forCandidatePageDoc( ing, lr.rank, cut, ForCandidatePageReq{ task, "--pack-task=", /*ranking=*/"mcp-lens", - /*pasteHandle=*/false, /*compactLegend=*/true, + // #362 review round 4 (B1/B2): one list across all five page paths — the same pasteable + // next= as the CLI pages, no_route echoed so the pasted continuation re-ranks identically. + return forCandidatePageDoc( ing, lr.rank, cut, ForCandidatePageReq{ task, "--pack-task=", + noRoute ? std::string_view( " --no-route" ) : std::string_view(), + /*pasteHandle=*/true, /*compactLegend=*/true, lr.routeNote, mcpRootArg, redact, gitstamp::stampAt( root ), budgetTokens, pageLimit, pageOffset } ); } diff --git a/src/verbs_for.h b/src/verbs_for.h index a2733a1a9..4e5bbd649 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2227,6 +2227,19 @@ inline int emitForCandidatePage( const rw::IngestResult& ing, const std::vector< return 0; } +// B2 (#362 review round 4): the argv fragment of every accepted flag that changes the lens ranking, +// echoed into the page's next= so a pasted continuation re-ranks identically. The bundle-shaping +// set (--adaptive/--anchor/--signatures-only/…) is REFUSED beside a window (cli.h), so it never +// appears here; these four are accepted because they select the ranking, not the bundle's shape. +inline std::string forPageRankFlagArgv( const rw::Config& cfg ) +{ + std::string s; + if( cfg.noRoute ) { s += " --no-route"; } + if( cfg.noMentionBoost ) { s += " --no-mention-boost"; } + if( cfg.noDocMention ) { s += " --no-doc-mention"; } + if( cfg.cochangeBoost ) { s += " --cochange-boost"; } + return s; +} std::optional runForLens( const MainDispatch& d ) { @@ -2453,7 +2466,8 @@ std::optional runForLens( const MainDispatch& d ) { // W3-N1/#294 review: the page redacts through packSignatures — the tally must be reported, // exactly like the --json early return below (a redacted credential must never be silent). - const int pageRc = emitForCandidatePage( ing, lensRank, forCut, rw::ForCandidatePageReq{ cfg.forTask, "--for=", /*ranking=*/"for-lens", + const std::string rankFlags = forPageRankFlagArgv( cfg ); + const int pageRc = emitForCandidatePage( ing, lensRank, forCut, rw::ForCandidatePageReq{ cfg.forTask, "--for=", rankFlags, /*pasteHandle=*/true, /*compactLegend=*/cfg.legend != "full", routeNoteRaw, flRootArg, redactPtr, flSingleRoot ? std::string_view( gitstamp::stampAt( std::string( root ) ) ) : std::string_view(), @@ -3782,7 +3796,8 @@ inline std::optional runPackTaskPage( const MainDispatch& d, const rw::Lens } const AdaptiveCut packCut = adaptiveCut( lr.rank, 5, std::size_t( cfg.packTopN > 0 ? cfg.packTopN : kForLensDefaultTopN ), true ); const std::string packAtStamp = in.rootArg.empty() ? std::string() : gitstamp::stampAt( std::string( in.rootArg ) ); - const int packPageRc = emitForCandidatePage( ing, lr.rank, packCut, rw::ForCandidatePageReq{ task, "--pack-task=", /*ranking=*/"for-lens", + const std::string packRankFlags = forPageRankFlagArgv( cfg ); + const int packPageRc = emitForCandidatePage( ing, lr.rank, packCut, rw::ForCandidatePageReq{ task, "--pack-task=", packRankFlags, /*pasteHandle=*/true, /*compactLegend=*/cfg.legend != "full", lr.routeNote, in.rootArg, d.redactPtr, packAtStamp, cfg.tokenBudget, cfg.pageLimit, cfg.pageOffset } ); @@ -3809,7 +3824,12 @@ std::optional runPackTask( const MainDispatch& d ) const std::string task( cfg.packTask ); // ── the routed+anchored lens ranking, shared verbatim with --for (all existing boosts apply) ─────────── - LensRanking lr = computeLensRanking( d, task ); + // B1 (#362 review round 4): a window pages the WHOLE positive-score distribution — a pruned ranking is + // exact only for its top-K, so total= and the far pages would depend on a performance optimisation + // (measured: 931 vs 1232 candidates on src/). The rule --for's bundle path already follows. The un-paged + // bundle keeps the H2 pruning: its consumers read only the head. + const bool packWindowPages = cfg.tokenBudget != 0 && ( cfg.pageLimit > 0 || cfg.pageOffset > 0 ); + LensRanking lr = computeLensRanking( d, task, /*compactCandidate=*/false, /*fullDistribution=*/packWindowPages ); // Q3 per-file churn (mirror --for): only mined on the --for git pass, so here it stays empty/zero when // --for wasn't also given → the churn= attr is simply omitted by packSignatures (nullptr-safe). diff --git a/test/pagingsweepcheck.sh b/test/pagingsweepcheck.sh index a60dde490..13fdccba0 100755 --- a/test/pagingsweepcheck.sh +++ b/test/pagingsweepcheck.sh @@ -873,5 +873,137 @@ sys.exit( fail ) PYM [ $? = 0 ] || fail=1 +# ── #362 review round 4 — B1/B2: one list across all five page paths; the ranking flags ride next= ── +# B1: CLI --for, CLI --pack-task and MCP for/explore/pack_task must page ONE candidate list, with +# pruning in its DEFAULT state (no RIPWIRE_NO_PRUNE): a pruned ranking is exact only for its +# top-K, so a page that reads past it made total= depend on a performance optimisation +# (measured on src/: 931 vs 1232). Every walk runs to has_more="0" and the five row +# sequences must be identical, r= 1..total, each exactly once. +# B2: every ACCEPTED flag that changes the ranking rides next= (or the paste re-ranks a different +# list — measured: --no-route page 1 total=563, its flagless next= pasted gave total=1). +# For each flag: page 1 with it, paste the returned next= verbatim (shlex), and the rows must +# equal page 2 of the SAME ranking taken directly. --cochange-boost needs RIPWIRE_DEV=1 on +# both runs (its gate), so its env rides the paste too. MCP `for` no_route: the page's next= +# (a CLI argv now, B1) must carry --no-route and paste onto the same list. +PGCORPUS="$ROOT/src" +PGCACHE="$TMP/pg_b1b2.cache" +[ -s "$PGCACHE" ] || "$BIN" "$PGCORPUS" --cache="$PGCACHE" >/dev/null 2>&1 +PGBIN="$BIN" PGROOT="$PGCORPUS" PGCACHE="$PGCACHE" python3 <<'PGB' +import html, json, os, re, shlex, subprocess, sys +BIN, CORPUS, CACHE = os.environ["PGBIN"], os.environ["PGROOT"], os.environ["PGCACHE"] +fail = 0 +def ok( m ): print( " PASS " + m ) +def no( m ): print( " FAIL " + m ); globals()["fail"] = 1 +ROW = re.compile( r']*>' ) +def rows_of( doc ): + out = [] + for tag in ROW.findall( doc ): + r = re.search( r'\br="(\d+)"', tag ); n = re.search( r'\bn="([^"]+)"', tag ); p = re.search( r'\bp="([^"]+)"', tag ) + if r and n and p: out.append( ( r.group( 1 ), n.group( 1 ), p.group( 1 ) ) ) + return out +def attrs_of( doc ): + root = re.search( r']*>', doc ) + return dict( re.findall( r'(\w+)="([^"]*)"', root.group( 0 ) ) ) if root else {} +def cli( extra, env=None ): + e = dict( os.environ ); e.update( env or {} ) + t = subprocess.run( [ BIN, CORPUS, "--cache=" + CACHE, "--legend=full" ] + extra, + capture_output = True, text = True, env = e ).stdout + return t +def mcp( verb, args ): + reqs = [ '{"jsonrpc":"2.0","id":1,"method":"initialize"}', + json.dumps( { "jsonrpc": "2.0", "id": 2, "method": "tools/call", + "params": { "name": verb, "arguments": args } } ) ] + p = subprocess.run( [ BIN, "--mcp" ], input = "\n".join( reqs ) + "\n", + capture_output = True, text = True ) + for line in p.stdout.splitlines(): + try: msg = json.loads( line ) + except ValueError: continue + if msg.get( "id" ) == 2: + return msg["result"]["content"][0]["text"] + return "" +def walk_mcp( verb, budget, limit ): + seq, off, pages = [], 0, 0 + while pages < 80: + doc = mcp( verb, { "path": CORPUS, "task": "how does pagerank converge", + "budget_tokens": budget, "limit": limit, "offset": off } ) + if not doc.startswith( " Date: Thu, 8 Oct 2026 05:54:31 +0000 Subject: [PATCH 023/170] =?UTF-8?q?docs:=20the=20#362=20review=20round=204?= =?UTF-8?q?,=20B3+B4=20=E2=80=94=20the=20LIMITS=20table=20re-derived;=20th?= =?UTF-8?q?e=20derived=20artifacts=20dropped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B3: docs/limits_build.py re-run — capped='s disclosure moves two lines of the generated table. limitstablecheck passes. B4: the showcase capture, the deck and the wholesale COMMANDS regen are dropped, restored to main's train-25 state. Our capture was recorded on a dirty local tree, was stale on the integration branch (its --at seed resolved to the wrong symbol there), and published local branch names. Maintainers regenerate captures and the deck at merge time; the feature itself needs only the 2 help-derived lines docs_commands_build.py adds (--limit under Shaped by, the HONORED-by list), which is all this keeps. The recorder's caption edits and the showcasecapturecheck (H) grep fix stay. Known consequence, by design of this ask: the (I) dictv arm is red on this head until the merge-time regen — the in-tree capture is main's, whose legend dict predates the paging specs. --- docs/COMMANDS.md | 4 ++-- docs/LIMITS.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index e2b0a2fe2..eb15861e2 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -121,7 +121,7 @@ $ ./build/ripwire . --max-tokens=1500 ... [1 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace`, `--limit` +**Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace` **Caveats (stated by the binary):** @@ -4441,7 +4441,7 @@ $ ./build/ripwire . --hotspots --json **Answers:** paginate a high-cardinality verb paginate a high-cardinality verb. -HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's rows; its rows repeat on every page, complete). --edit-check is the same shape for a different reason: its rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --top-k/--max-tokens/--token-budget, not a page (--for's bare bundle is shaped by --token-budget the same way, and takes --limit/--offset only as its file page, where the budget flags are refused in turn); the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all. +HONORED by: --deps --callers --callees --tree --lint --hotspots --clones --cochange --owners --communities --community --doc-drift --whereis --grep/--regex --match --pattern --impact --uses --exercises --seams --zoom --external-surface --dead-code --mentions --graph-query --stray-content --test-gate --biggest-first --ensemble --quality-panel --context-ratio --nonlocal-state --comment-coherence --naming-consistency --safe-delete --pr-context --edit-check --flags --situ --for. Emit at most N rows, skipping the first M; N overrides the verb's own display cap (40 hotspot files, 30 co-change pairs, 60 whereis hits, 100 grep/match hits, 40 impact rows, 20 seam pairs, 40 readability rows, 40 ensemble symbol rows, 40 context-ratio symbol rows, 40 nonlocal-state rows, 200 graph-query rows / --top-k, 40 unflagged --edit-check caller rows, 8 --flags read sites per gate, 25 --flip context rows per listing, 8 --situ blast-radius files and 8 co-change partners). A verb NEVER pages the rows that ARE its answer: --edit-check's flagged callers, --flip's and --situ's tests-to-run rows and --flags' gate rows ride every page in full, and every verdict/count attribute is computed over the full set first. With --offset alone (no --limit) the verb's own default page size applies and the root discloses limit="0" — on OUTPUT that 0 means 'no explicit --limit', never a zero-row page (the flag itself refuses --limit=0). A BARE run whose default cap cut rows (capped="1") carries the same limit="0" and the whole paging block below, so you can page from the first answer without guessing. Deterministic seams (rows are already sorted) so --offset=N is the exact continuation of the previous --limit=N page. On --for, --limit=N/--offset=M do not window the bundle: they select its FILE-GRAIN WIDENING PAGE instead (one row per positive-score file, ranked file-first; see --for), the answer to "the head missed it, show me more files". The root element then carries shown= capped= total= has_more= next_offset= offset= limit= — loop until has_more="0". capped= compares the PAGE to the total (1 ⇔ shown < total), so a page past the end reads shown="0" capped="1" has_more="0": nothing was cut, the offset skipped everything — EXCEPT the verbs with TWO INDEPENDENT listings, which carry the noun-prefixed form instead (one shown= could only describe one): --test-gate shown_tests=/tests_capped= + shown_untested=/untested_capped=, --communities shown_modules=/modules_capped= + shown_bridges=/bridges_capped=, --ensemble and --context-ratio shown_syms=/syms_capped= + shown_files=/files_capped=; the window takes the PRIMARY listing (--test-gate's rows; its rows repeat on every page, complete). --edit-check is the same shape for a different reason: its rows split into the ANSWER (callers flagged incompatible="1", with their complete sites_l=) and the CONTEXT (unflagged callers). Only the context pages — shown_unflagged=/unflagged_capped=, with total= the unflagged count — while the flagged rows and the overload census ride every page in full and status=/defs=/callers=/incompatible= are computed over the FULL caller set before any window, so a page can never make the verdict say less than it knows. Any verb NOT in that list REFUSES both flags (exit 1) rather than accepting and ignoring them: budget/top-k verbs (--recall/--pack-task/--from-trace/ --expand/--outline/--pack-signatures/--format=candidates) are shaped by --token-budget the same way; --for takes --limit/--offset as its FILE page, where the budget flags are refused in turn (bare --pack-task --limit refuses: it has no page without a budget), and --for/--pack-task beside --token-budget take the window as the BUDGETED-BUNDLE CANDIDATE PAGE — one window over the ranked candidates, signatures only (bodies come via next= or --expand), their resumable continuation; the rest (--path/--connect/ --around/--exemplar/--report/--mermaid/--map-diff/--metrics and the default map) answer with a single fixed-shape result that has no row list to window at all. **Try it** diff --git a/docs/LIMITS.md b/docs/LIMITS.md index fbeb13a73..134d14e84 100644 --- a/docs/LIMITS.md +++ b/docs/LIMITS.md @@ -17,7 +17,7 @@ it once, marked `×N`. | total caps | files | caps whose file discloses | caps whose file discloses NOTHING | | --- | --- | --- | --- | -| 236 | 94 | 135 | **101** | +| 236 | 94 | 137 | **99** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they are not counted as caps, and 236 + 7 is the 243 constants this generator parses out of `src/`. @@ -323,7 +323,7 @@ Discloses: `capped`, `hosts_capped` ### `src/forpage.h` -Discloses: **none** +Discloses: `capped` | constant | value | class | note | | --- | --- | --- | --- | From 5b7562aa2f84d7eddb8b45ca416653b6f71b80c1 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Thu, 8 Oct 2026 05:54:31 +0000 Subject: [PATCH 024/170] =?UTF-8?q?fix(changelog):=20the=20#362=20review?= =?UTF-8?q?=20round=204,=20B5=20=E2=80=94=20the=20manifest=20claim=20resta?= =?UTF-8?q?ted;=20(1b)=20reads=20the=20last=20[Unreleased]=20figure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The +184 B is explore's limit/offset alone (774 -> 958 B): for already declared the pair (777 B, unchanged), pack_task is a dispatch alias, not a listed tool, and next_tier= is a response attribute, not a schema property — both sentences (CHANGELOG and the gate's ceiling note) now say exactly that. The REFVAL entry's Manifest line is back verbatim (46,591 -> 46,732 B); arm (1b) now reads the LAST claim in [Unreleased] — several entries restate the figure and the last one describes the live manifest, so ours (46,732 -> 46,916 B) is the checked number. --- CHANGELOG.md | 28 ++++++++++++++++------------ test/mcpmanifestcheck.sh | 17 +++++++++++------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 568864bf4..05f637187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,9 +96,8 @@ call-shaped references, so on such a function `--callers`, `--callees`, `--impac - **Cost.** A cold default map costs +3.6% CPU on this tree, +6.3% on django and +5.2% on webpack (median of 5, `sim/refval_cpu.sh`). - **Cache.** `kParserVer` moves. -- **Manifest.** The `tools/list` manifest grows 46,591 → 46,916 B — 46,732 B of it here (the two find descriptions - name `valueRefs` as not a proven call); the rest is the paging lane's schema (#362 below), re-pinned to the live - merged-tree measurement this branch ships. +- **Manifest.** The `tools/list` manifest grows 46,591 → 46,732 B: the two find descriptions name `valueRefs` as + not a proven call. Gate: `test/recallshapecheck.sh`. It has 177 arms across C, C++, JS, JSX, TS, TSX, Python and Go: - positives; @@ -1107,15 +1106,20 @@ its own ``. MCP `explore` declares `limit`/`offset` (the same bounded pair `for` takes) and answers the same page shape; previously it silently ignored `budget_tokens` beside a window and served the budgetless -file page. The MCP twins page their own shared ranking (`ranking="mcp-lens"` names it on the root; -the CLI verbs page `verbs_for.h`'s `computeLensRanking`, `ranking="for-lens"` — the two pipelines -rank slightly different candidate sets, so `ranking=` says which list a window pages), and the MCP -pages carry no pasteable CLI `next=` handle — a CLI continuation would walk a different list; the -machine attributes (`next_offset=`/`limit=`/`next_tier=`) carry the programmatic continuation -through the same verb. The `tools/list` manifest grows 46,732 → 46,916 B (measured against train 25's -`main`, re-based after the sync; descriptions identical, the delta is schema), and the ceiling moves 46,750 → 46,950 B -- the one sanctioned case that moves it: the paging schema properties (`limit`/`offset`/`next_tier` -on `for`, `pack_task` and `explore`) +file page. All five page paths — CLI `--for`, CLI `--pack-task`, MCP `for`/`explore`/`pack_task` — +page ONE candidate list: the CLI pages force the ranking's full distribution under a window (the +rule `--for`'s bundle path already followed), exactly the exhaustive scoring the MCP twins always +used, so the `ranking=` attribute the first round carried is gone — there is nothing left to +distinguish. Every page, CLI or MCP, carries the same pasteable `next=`/`next_tier=`: an MCP page's +handle is a CLI argv now, and it walks the same list. Every accepted flag that changes the ranking +(`--no-route`, `--no-mention-boost`, `--no-doc-mention`, `--cochange-boost` on the CLI; `no_route` +on the MCP twins) rides `next=`, so a pasted continuation re-ranks identically — the +`test/pagingsweepcheck.sh` walk arms pin all five surfaces to `has_more="0"` with identical row +sequences and pin each flag's echo. The `tools/list` manifest grows 46,732 → 46,916 B (measured +against train 25's `main`; descriptions identical, and the +184 B of schema is `explore`'s +`limit`/`offset` alone — `for` already declared the pair, `pack_task` is a dispatch alias, not a +listed tool), and the ceiling moves 46,750 → 46,950 B +- the one sanctioned case that moves it: `explore`'s two declared schema properties (`test/mcpmanifestcheck.sh` arm `(1b)` asserts the figure against a live measurement). ### Fixed — the Linux G1 sanitizer ritual completes: five string_view comparator lambdas stop wrapping, and the GCC ASan path builds (#342) diff --git a/test/mcpmanifestcheck.sh b/test/mcpmanifestcheck.sh index 58affcf7a..239ef3a66 100755 --- a/test/mcpmanifestcheck.sh +++ b/test/mcpmanifestcheck.sh @@ -285,10 +285,11 @@ tools = json.loads( line )[ "result" ][ "tools" ] # byte moved, no tool added). Headroom after this line: 28 B (the final review tightened a first 46,800 anchor to the # #214-sized margin: 78 B of unattributed headroom was wider than any re-anchor here has taken). # PAGING LANE (#362, synced onto train 25): 46,732 -> 46,916 B, the ceiling moves 46,750 -> 46,950. -# The +184 B is the paging schema alone — limit/offset/next_tier declared on for, pack_task and -# explore — measured live on both trees: train-25 main's binary answers 46,732 B, this branch's -# answers 46,916 B (33 tools each, descriptions identical at 22,411 B, schemas 19,585 -> 19,769). -# Headroom after this line: 34 B, the #214-sized margin. +# The +184 B is explore's limit/offset alone (774 -> 958 B) — for already declared the pair (777 B, +# unchanged), pack_task is a dispatch alias, not a listed tool, and no tool declares next_tier (it +# is a response attribute, not a schema property). Measured live on both trees: train-25 main's +# binary answers 46,732 B, this branch's answers 46,916 B (33 tools each, descriptions identical at +# 22,411 B, schemas 19,585 -> 19,769). Headroom after this line: 34 B, the #214-sized margin. CEILING = 46950 manifest = len( json.dumps( { "tools": tools }, separators = ( ",", ":" ) ) ) descBytes = sum( len( t[ "description" ] ) for t in tools ) @@ -311,11 +312,15 @@ check( manifest <= CEILING, changelog = open( os.path.join( ROOT, "CHANGELOG.md" ), encoding = "utf-8" ).read() _, _, afterHeading = changelog.partition( "## [Unreleased]" ) unreleased = afterHeading.split( "\n## [", 1 )[ 0 ] # [Unreleased]'s body, up to the next dated heading -m = re.search( r"tools/list.? manifest grows [\d,]+[^\d]+([\d,]+) B", unreleased ) +claims = re.findall( r"tools/list.? manifest grows [\d,]+[^\d]+([\d,]+) B", unreleased ) +m = claims[ -1 ] if claims else None # #362 round 4 (B5c): several [Unreleased] entries restate the figure + # (theirs pre-paging, ours the merged tree's). The LAST claim describes + # the current manifest; earlier ones are historical within the same + # mutable section, kept verbatim per review. if m is None: print( " INFO (1b) no 'tools/list manifest grows N -> N B' claim in CHANGELOG's [Unreleased] section (nothing to check)" ) else: - claimed = int( m.group( 1 ).replace( ",", "" ) ) + claimed = int( m.replace( ",", "" ) ) check( claimed == manifest, "(1b) CHANGELOG's [Unreleased] manifest-growth claim (%d B) matches the live tools/list measurement (%d B)" % ( claimed, manifest ) ) From 535c50f6e88c789dc2e1ca07a64d7f61c2000c17 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 07:41:59 -0400 Subject: [PATCH 025/170] perf(ingest): skip the markitdown shell when no markitdown can start The doc post-pass ran `markitdown ` through /bin/sh for every pdf/docx/pptx/xlsx on every run. On a machine without markitdown the shell answers rc 127, the bridge reads that as "", and the empty result is (deliberately) never cached, so every warm call paid one shell start per binary doc for an answer known before the fork: ~25 ms of a warm call on this repository (two present/ decks). docparse::markitdownMayRun() now asks the shell's own question without a process: os::which walks PATH as sh does (an empty entry is the current directory; a directory or a non-executable file is not a program). It answers true whenever it cannot rule the shell out (PATH unset, an executable ./markitdown, an exported shell function), since a false "absent" would drop a Section while a false "present" only costs the popen it always cost. It is asked once per post-pass and never memoized across ingests, so a markitdown installed under a running MCP server is seen on its next ingest. The content-hash cache is still read first, so a blob extracted while markitdown was installed keeps answering after it is removed. Output is unchanged. test/docmdcachecheck.sh arms 6-9: absent then installed between runs, a directory ahead on PATH, a non-executable file, an empty PATH entry. Co-Authored-By: Claude Opus 5.5 --- src/docparse.h | 20 +++++++++++++++++ src/ingest_docpass.h | 21 +++++++++++++++--- test/docmdcachecheck.sh | 49 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/docparse.h b/src/docparse.h index b90ff7a1e..b7313e03a 100644 --- a/src/docparse.h +++ b/src/docparse.h @@ -616,6 +616,26 @@ inline std::string shellQuote( const std::string& s ) } // namespace detail +// Could `markitdown ` start at all? The bridge runs through the shell, and a shell that finds no command answers +// rc 127, which runMarkitdown reads as "" — so a machine WITHOUT markitdown paid one /bin/sh start per binary doc on +// every warm run, for an answer known before the fork (cli-floor 2026-10-08: ~25 ms of a warm call on this repository, +// two present/ decks). This asks the same question the shell would, with no process: os::which walks PATH exactly as +// sh does (an empty entry is the current directory; a directory or a non-executable file named markitdown is not a +// program). It is asked once per doc post-pass, never memoized across ingests, so installing markitdown under a +// running MCP server is seen on its next ingest. It answers TRUE whenever it cannot rule the shell out — PATH unset +// (sh then searches its own default path), an executable `./markitdown` (a Windows shell searches the current +// directory first), or an exported shell function of that name — because a false "absent" would drop a Section the +// shell would have produced, while a false "present" only costs the popen it always cost. Gated by +// test/docmdcachecheck.sh arms 6-9. +inline bool markitdownMayRun() +{ + if( std::getenv( "PATH" ) == nullptr || std::getenv( "BASH_FUNC_markitdown%%" ) != nullptr || std::getenv( "BASH_FUNC_markitdown()" ) != nullptr ) + { + return true; + } + return !os::which( "markitdown" ).empty() || !os::which( "./markitdown" ).empty(); +} + // Run `markitdown ` and return its stdout (Markdown), or "" if markitdown is not on PATH / it failed. // NOTE: this performs a subprocess call — used only for binary doc formats, only when they are ingested. inline std::string runMarkitdown( const std::string& path ) diff --git a/src/ingest_docpass.h b/src/ingest_docpass.h index 0c98717d0..8ca45d50c 100644 --- a/src/ingest_docpass.h +++ b/src/ingest_docpass.h @@ -64,11 +64,17 @@ inline void publishDocBridgeBlob( const std::string& bridgeBlobPath, std::uint32 // cacheEnabled=false (--no-cache) bypasses the sidecar entirely. tmpKey keeps concurrent workers' // unpublished temp files distinct; the publish itself is a whole-file rename, so a concurrent // reader sees every byte or none. -inline std::string docTextViaBridgeCache( const std::string& path, const std::string& ext, bool cacheEnabled, std::uint32_t tmpKey ) +// `bridgeMayRun` is docparse::markitdownMayRun(), asked once by the post-pass: false means the shell could not start +// markitdown, so a bridge doc whose bytes have no cached extraction is "" without the popen — the answer the popen +// gave (rc 127), minus one shell start per doc per run. The cache is still consulted first either way, so a blob +// extracted while markitdown was installed keeps answering after it is removed, exactly as before. +inline std::string docTextViaBridgeCache( const std::string& path, const std::string& ext, bool cacheEnabled, std::uint32_t tmpKey, bool bridgeMayRun ) { + EXPECTS( !path.empty(), "doc post-pass: every crawled doc path is a non-empty spelling" ); std::string text; std::string bridgeBlobPath; - if( cacheEnabled && docparse::docKindOf( ext ) == docparse::DocKind::Markitdown ) + const bool viaBridge = docparse::docKindOf( ext ) == docparse::DocKind::Markitdown; + if( cacheEnabled && viaBridge ) { if( const std::optional docBytes = docparse::detail::readWholeFile( path ) ) { @@ -79,6 +85,10 @@ inline std::string docTextViaBridgeCache( const std::string& path, const std::st text = docparse::detail::readWholeFile( bridgeBlobPath ).value_or( std::string() ); // miss ⇒ text stays empty } } + if( text.empty() && viaBridge && !bridgeMayRun ) + { + return text; // no cached extraction, and no markitdown the shell could start: the popen's own answer, "" + } if( text.empty() ) { text = docparse::parseDocFile( path, ext ); @@ -120,6 +130,11 @@ inline void runDocPostPass( IngestResult& result, std::vector& rawDefs, // 2) extract in parallel, storing each result at its OWN slot (no cross-thread sharing of a slot → // order-independent). A per-doc `hasText` gate distinguishes "not extractable" (skip) from empty. const std::size_t ndocs = docIds.size(); + // Asked ONCE per post-pass, and only when a bridge doc exists (a PATH walk of stats, no process): every worker + // reads the same answer, and the next ingest asks again, so a markitdown installed meanwhile is seen. + const bool anyBridgeDoc = std::any_of( docIds.begin(), docIds.end(), [ & ]( std::uint32_t fid ) + { return docparse::docKindOf( lowerExtensionOf( result.files[ fid ] ) ) == docparse::DocKind::Markitdown; } ); + const bool bridgeMayRun = anyBridgeDoc && docparse::markitdownMayRun(); std::vector docTextOut( ndocs ); std::vector docHasText( ndocs, 0 ); std::vector docLex( ndocs ); // B0.2: per-doc Section stats (rich only), own slot per worker @@ -158,7 +173,7 @@ inline void runDocPostPass( IngestResult& result, std::vector& rawDefs, const std::uint32_t fid = docIds[ di ]; const std::string ext = lowerExtensionOf( result.files[ fid ] ); - std::string text = docTextViaBridgeCache( result.files[ fid ], ext, cacheEnabled, fid ); + std::string text = docTextViaBridgeCache( result.files[ fid ], ext, cacheEnabled, fid, bridgeMayRun ); if( !text.empty() ) { if( captureValueUses ) diff --git a/test/docmdcachecheck.sh b/test/docmdcachecheck.sh index 7b516f9ce..cc40e00cb 100755 --- a/test/docmdcachecheck.sh +++ b/test/docmdcachecheck.sh @@ -10,6 +10,8 @@ # 4. changing the doc's bytes re-invokes (the key is the content, not the path) # 5. an EMPTY extraction is never cached (machine fact, not a byte fact): a no-output fake is # re-invoked on the next run rather than wedging "" into the cache +# 6-9. (cli-perf-068) no shell is started for a markitdown that cannot run, and that answer is never memoized: +# absent-then-installed, a directory ahead on PATH, a non-executable file, an empty PATH entry (the cwd) # Does NOT edit test/regression.sh (the orchestrator wires it). # # RIPWIRE_BIN=build/ripwire bash test/docmdcachecheck.sh @@ -74,5 +76,52 @@ PATH="$TMP/bin:$PATH" "$BIN" "$TMP/corpus" >/dev/null 2>&1 PATH="$TMP/bin:$PATH" "$BIN" "$TMP/corpus" >/dev/null 2>&1 if [ "$( count )" = "5" ]; then ok "empty extraction not cached (silent bridge re-invoked each run)"; else no "empty-extraction arm: expected 5 total invocations, got $( count )"; fi +# 6-9 (cli-perf-068): the post-pass asks the shell's own question — could `markitdown` start at all? — before it +# forks one, so a machine without markitdown no longer pays a /bin/sh per bridge doc per run. Absence is not memoized: +# each arm below would go red if the answer were cached, mis-walked PATH, or ruled the shell out when it could run. +# The PATH is pinned to the fake's dirs plus /usr/bin:/bin so a markitdown installed on the host cannot answer. +SYS_PATH="/usr/bin:/bin" +mkdir -p "$TMP/b6" "$TMP/b7dir/markitdown" "$TMP/b7" "$TMP/b8" "$TMP/b9" +cat > "$TMP/fake" <<'FAKE' +#!/bin/bash +echo "invoked $1" >> "$FAKE_LOG" +echo "# Extracted Deck" +echo "hello from fake markitdown" +FAKE +chmod +x "$TMP/fake" +newdoc(){ printf 'arm %s doc bytes %s $$=%s' "$1" "$( date +%s%N 2>/dev/null || date +%s )" "$$" > "$TMP/corpus/deck.docx"; } + +# 6. absent, then installed between two runs: the first run invokes nothing and carries no doc Section; the second +# sees the new program (a memoized "absent" would keep it at zero) +newdoc 6; before="$( count )" +PATH="$TMP/b6:$SYS_PATH" "$BIN" "$TMP/corpus" > "$TMP/absent.xml" 2>/dev/null +if [ "$( count )" = "$before" ] && ! grep -q 'p="deck.docx"' "$TMP/absent.xml" && grep -q 'a.cpp' "$TMP/absent.xml"; then + ok "absent bridge: no invocation, no doc Section, map still produced" +else no "absent bridge: expected $before invocations and no Section, got $( count ) (map: $( wc -c < "$TMP/absent.xml" ) B)"; fi +cp "$TMP/fake" "$TMP/b6/markitdown" +PATH="$TMP/b6:$SYS_PATH" "$BIN" "$TMP/corpus" > "$TMP/installed.xml" 2>/dev/null +if [ "$( count )" = "$(( before + 1 ))" ] && grep -q 'p="deck.docx"' "$TMP/installed.xml"; then + ok "bridge installed after an absent run is invoked on the next run (absence is not memoized)" +else no "installed-after-absent: expected $(( before + 1 )) invocations and a Section, got $( count )"; fi + +# 7. a DIRECTORY named markitdown earlier on PATH is not a program (sh skips it): the real one after it still runs +newdoc 7; before="$( count )"; cp "$TMP/fake" "$TMP/b7/markitdown" +PATH="$TMP/b7dir:$TMP/b7:$SYS_PATH" "$BIN" "$TMP/corpus" >/dev/null 2>&1 +if [ "$( count )" = "$(( before + 1 ))" ]; then ok "a directory named markitdown ahead on PATH does not hide the program behind it" +else no "dir-ahead arm: expected $(( before + 1 )) invocations, got $( count )"; fi + +# 8. a NON-executable file named markitdown is no program either (sh: rc 126 -> ""): same map as the absent run +cp "$TMP/fake" "$TMP/b8/markitdown"; chmod -x "$TMP/b8/markitdown" +newdoc 8; before="$( count )" +PATH="$TMP/b8:$SYS_PATH" "$BIN" "$TMP/corpus" > "$TMP/noexec.xml" 2>/dev/null +if [ "$( count )" = "$before" ] && ! grep -q 'p="deck.docx"' "$TMP/noexec.xml"; then ok "a non-executable markitdown is treated as absent, as sh treats it" +else no "non-executable arm: expected $before invocations and no Section, got $( count )"; fi + +# 9. an EMPTY PATH entry is the current directory to sh: a markitdown there must still run +newdoc 9; before="$( count )"; cp "$TMP/fake" "$TMP/b9/markitdown" +( cd "$TMP/b9" && PATH=":$SYS_PATH" "$BIN" "$TMP/corpus" >/dev/null 2>&1 ) +if [ "$( count )" = "$(( before + 1 ))" ]; then ok "an empty PATH entry (the cwd) reaches a markitdown there, as sh does" +else no "empty-PATH-entry arm: expected $(( before + 1 )) invocations, got $( count )"; fi + [ "$fail" = 0 ] && echo "ALL PASS" || echo "FAILURES ABOVE" exit $fail From b77675367f9e99da3332b5cafa7d367dc5f1fbd1 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 07:58:58 -0400 Subject: [PATCH 026/170] perf(whereis): block reads, one ls-tree per distinct tip in parallel, one scan per blob; refuse an unread blob stream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --whereis on a checkout with 137 local branches took 12-14 s against 2.3 s on one ref. Most of it was reading: every `git ls-tree -r` (one per branch) came back through popenTrimmed one locked fgetc per byte, 55% of the main thread. Three changes, output byte-identical (all 337,743 rows of a --limit=400000 listing compared, base vs head): - popenTrimmed reads the stream in 64 KB blocks. The answer is every byte the command wrote, then the same trailing trim: identical to the line reader it replaces, and every git one-liner in the tool reads through it. - computeWhereis lists each DISTINCT tip once, across the crossref git pool (parallelIndexed), and assembles the sites in ref order as the serial loop did. A listing whose worker never ran forfeits complete= like any other suspect tree. - A blob is line-scanned once however many (ref, path) sites point at it; its rows are stamped per site in site order. Measured on the 139-head checkout (dev builds): 12.1 s -> 3.7 s. Honesty fix (cli-floor §4): the one `git cat-file --batch` takes its sha list from a file under the cache dir. With that dir unusable (TMPDIR naming a missing directory) the stream never started and the answer printed on-head="0" hits="0" with a near-miss note retrying the same name, on a repository whose HEAD defines it. The diagnosis attributed it to a fresh one-commit repository; the trigger is the unusable cache dir. WhereResult::blobsUnread now records a stream that never started or stopped early, and both hosts refuse: the CLI exits 1 naming the cause, the MCP tool answers -32603. test/crossrefdegradecheck.sh arm 6: a control, the refusal (red on the base binary: exit 0 with hits="0"), a genuine zero still answered (negative), and the MCP twin (red on base). Co-Authored-By: Claude Opus 5.5 --- src/crossref.h | 44 +++++++++++++++++++++++++++++++--- src/gitmine.h | 22 ++++++++++------- src/mcp.h | 9 ++++++- src/mcpverbs.h | 7 +++++- src/verbs_change.h | 8 +++++++ test/crossrefdegradecheck.sh | 46 ++++++++++++++++++++++++++++++++++++ 6 files changed, 122 insertions(+), 14 deletions(-) diff --git a/src/crossref.h b/src/crossref.h index 05e88df39..e996162b4 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -1468,6 +1468,10 @@ struct WhereResult // what tells the reader which of the two zeros they are holding. std::string seedSpec; std::string nearMiss; + // The blob stream behind hits= never started or ended before serving every blob (an unwritable batch list, a git + // that did not start or died): no row count, zero included, is a measurement then, so both hosts REFUSE the answer + // (exit 1 / an MCP error naming the cause) instead of printing hits= — the honesty bug of cli-floor §4. + bool blobsUnread = false; // The --with-history lane: a non-owning view of the caller's oracle index (nullptr ⇒ not asked for) plus // THIS symbol's verdict, resolved once at compute time so emission stays a pure print. Views at the seam: @@ -2260,10 +2264,29 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym struct Site { std::uint32_t refIndex; std::string path; bool replaced; }; bool anyEmptyTree = false; // T1: a zero-row ls-tree could be a FAILED listing — it forfeits complete= gtl::btree_map> sites; + // One `git ls-tree -r` per DISTINCT tip (HEAD and its own branch share one; so do branches left at the same + // commit), run across the crossref git pool: a listing is a pure function of an immutable commit, so a tip is + // never listed twice and the order the listings finish in reaches nothing — each lands in its tip's own slot and + // the sites are assembled below in ref order, exactly as the serial loop assembled them. A slot whose worker + // never ran reads as an empty listing, which forfeits complete= like any other suspect tree (T1). + std::vector distinctTips; + distinctTips.reserve( refs.size() ); + for( const RefInfo& ref : refs ) + { + distinctTips.push_back( ref.tip ); + } + std::sort( distinctTips.begin(), distinctTips.end() ); + distinctTips.erase( std::unique( distinctTips.begin(), distinctTips.end() ), distinctTips.end() ); + std::vector> rowsByTip( distinctTips.size() ); + ParallelSweep listingSweep; + parallelIndexed( distinctTips.size(), [ & ]( std::size_t tipIndex ) { rowsByTip[ tipIndex ] = lsTree( root, distinctTips[ tipIndex ] ); }, listingSweep ); for( std::uint32_t i = 0; i < refs.size(); ++i ) { - const std::vector rows = lsTree( root, refs[i].tip ); - if( rows.empty() ) + const auto tipAt = std::lower_bound( distinctTips.begin(), distinctTips.end(), refs[ i ].tip ); + ASSUME( tipAt != distinctTips.end() && *tipAt == refs[ i ].tip, "whereis: every ref's tip was put in distinctTips above" ); + const std::size_t tipIndex = std::size_t( tipAt - distinctTips.begin() ); + const std::vector& rows = rowsByTip[ tipIndex ]; + if( rows.empty() || !listingSweep.isDone( tipIndex ) ) { anyEmptyTree = true; } @@ -2281,6 +2304,12 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym result.refsScanned = refs.size() - 1; // HEAD is not one of the swept refs StreamBlobStats blobStats; // T1: the degrade census that decides whether this scan may claim complete= + // A blob is LINE-SCANNED once, however many (ref, path) sites point at it — a file no branch touched is one blob + // under every one of 137 branches — and its rows are then stamped once per site, in site order. A row's content + // is (line, def-shape, text) of the BLOB, its label (ref, tip, date, path) of the SITE, so the copies are the + // rows the per-site scan made, in the same order. + std::vector blobRows; + const RefInfo blobOnly{}; streamBlobs( root, shas, [ & ]( const std::string& sha, std::string_view bytes, bool isText ) { if( !isText || bytes.find( sym ) == std::string_view::npos ) { return; // cheap reject before the line walk @@ -2288,6 +2317,8 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym const auto it = sites.find( sha ); if( it == sites.end() ) { return; } + blobRows.clear(); + scanBlobForSymbol( bytes, sym, blobOnly, std::string(), blobRows ); for( const Site& s : it->second ) { const bool head = refs[ s.refIndex ].name == "HEAD"; @@ -2295,9 +2326,16 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym result.onHead = result.onHead || ( head && !s.replaced ); if( !s.replaced ) { - scanBlobForSymbol( bytes, sym, refs[ s.refIndex ], s.path, result.hits ); + const RefInfo& ref = refs[ s.refIndex ]; + for( const WhereHit& row : blobRows ) + { + result.hits.push_back( WhereHit{ ref.name, ref.tip, ref.date, s.path, row.line, row.isDef, row.text, false } ); + } } } }, &blobStats ); + // A stream that never started, or died before serving every blob, leaves hits= and on-head= unmeasured: a zero from + // it is not "no tree holds the name". The caller refuses on this rather than print that zero (CHECKLIST 13b). + result.blobsUnread = blobStats.startFailed || blobStats.endedEarly; // T1: exhaustive-over-text iff every sha streamed clean AND no ref's tree listing was suspect. An empty // sha list (every scanned tree empty, or none) trivially streamed clean — anyEmptyTree covers that shape. diff --git a/src/gitmine.h b/src/gitmine.h index 44352c90d..da90fa025 100644 --- a/src/gitmine.h +++ b/src/gitmine.h @@ -19,6 +19,7 @@ // gitmine.h no longer carries its own copy; see jsonesc.h for the dedup rationale #include +#include // popenTrimmed's 64 KB read block #include // the join's once-per-process disclosure flags #include // std::popcount — the recurrence mask's set-bit count (Clio sub-windows) #include @@ -238,10 +239,13 @@ inline std::string sinceUnresolvedRefusal( std::string_view value, std::string_v // popen-trim shape in the tool: the quality.h git one-liners, the doctor probes, crossref.h and binstale.h all // reach it as quality::popenTrimmed (a using-declaration of this). It lives HERE — the lower header, which // quality.h includes — because resolveSinceScope needed the same reader (N4, 2026-09-04) and a second copy in -// this file was a --quality-delta new-clone-of-reused-helper row. Built on readByteSafeLine (infra/stdinline.h), -// the G3 byte-safe line reader every git pipe in this header uses: no fixed buffer to split a long path on -// (churnjoincheck's G3 arm forbids fgets here), a high byte never sign-changed. Lines are re-joined with the -// '\n' the reader consumed, so multi-line callers (crossref.h, the rename map) read exactly what fgets gave them. +// this file was a --quality-delta new-clone-of-reused-helper row. It reads the WHOLE stream in blocks and splits +// nothing: the answer is every byte the command wrote, then the trailing whitespace trim. That is byte-for-byte +// what the line reader it replaces produced (each line re-joined with the '\n' it consumed, an unterminated last +// line given one, which the trim then removes), with no fixed buffer to split a long path on (churnjoincheck's +// G3 arm) and no high byte ever sign-changed — but one fread per 64 KB instead of one locked fgetc per byte. The +// per-byte read was 55% of a --whereis on a 137-branch checkout (one `git ls-tree -r` per branch, cli-floor +// 2026-10-08), and every git one-liner in the tool reads through here. inline std::string popenTrimmed( const std::string& cmd ) { std::FILE* pipe = os::popen( cmd.c_str(), "r" ); @@ -249,12 +253,12 @@ inline std::string popenTrimmed( const std::string& cmd ) { return {}; } - std::string out; - std::string line; - while( readByteSafeLine( pipe, line ) ) + std::string out; + std::array block; + for( std::size_t got = std::fread( block.data(), 1, block.size(), pipe ); got > 0; got = std::fread( block.data(), 1, block.size(), pipe ) ) { - out += line; - out += '\n'; + ASSUME( got <= block.size(), "fread returns at most the count it was asked for" ); + out.append( block.data(), got ); } os::pclose( pipe ); while( !out.empty() && ( out.back() == '\n' || out.back() == '\r' || out.back() == ' ' ) ) diff --git a/src/mcp.h b/src/mcp.h index aa31220ee..1c8f0be32 100644 --- a/src/mcp.h +++ b/src/mcp.h @@ -2297,11 +2297,18 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo resp = pagedResult( [ & ]( McpPageArgs pg ) { bool seedFault = false; - const std::string t = whereisText( path, symbol, kind, crossref::kWhereisHits, pg, &seedFault ); + bool blobFault = false; + const std::string t = whereisText( path, symbol, kind, crossref::kWhereisHits, pg, &seedFault, &blobFault ); if( seedFault ) { return errResultMsg( -32602, mcprefuse::notFound( getIndex( path ).ing, "symbol", symbol ) ); } + if( blobFault ) + { + DISCLOSE( Diagnostics::answerRefused, "whereis (MCP): the blob stream did not serve every blob — a -32603 error names it, no hit count" ); + return errResult( -32603, "could not read the trees' blobs (git cat-file --batch did not start or stopped early; is the cache " + "dir under TMPDIR writable?) — no hit count rather than one nothing measured" ); + } return t.empty() ? errResult( -32602, "not a git repository (or no HEAD commit) — no refs to search" ) : textResult( t ); } ); } diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 7cc680277..0f52b8089 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -555,7 +555,7 @@ inline std::string rankByText( const std::string& root, std::string_view mode, i // returns "" with `seedFault` set, and the dispatcher speaks the shared refusal triple over -32602 rather // than answering a question the caller did not ask. inline std::string whereisText( const std::string& root, const std::string& symbol, const std::string& filter, - std::size_t maxHits, McpPageArgs page = {}, bool* seedFault = nullptr ) + std::size_t maxHits, McpPageArgs page = {}, bool* seedFault = nullptr, bool* blobFault = nullptr ) { std::string sel = symbol; std::string seedSpec; @@ -576,6 +576,11 @@ inline std::string whereisText( const std::string& root, const std::string& symb { return {}; } + if( res.blobsUnread ) // the CLI twin's refusal: a hit count over blobs never read is not an answer + { + if( blobFault != nullptr ) { *blobFault = true; } + return {}; + } res.seedSpec = std::move( seedSpec ); // The tree zero stays an answer; the near-miss only says WHICH zero it is. Computed only on the zero, // so a real hit list costs nothing and is byte-identical to before. diff --git a/src/verbs_change.h b/src/verbs_change.h index 9da3174cc..9bb4643f6 100644 --- a/src/verbs_change.h +++ b/src/verbs_change.h @@ -1612,6 +1612,14 @@ std::optional runCrossRef( const MainDispatch& d ) rw::emitTo( stderr, "ripwire: --whereis: {} is not a git repository (or has no HEAD commit) — no refs to search\n", root.c_str() ); return 1; } + if( result.blobsUnread ) + { + DISCLOSE( Diagnostics::answerRefused, "whereis: the blob stream did not serve every blob, so no hit count is printed — stderr names the cause, exit 1" ); + rw::emitTo( stderr, "ripwire: --whereis: could not read the trees' blobs (the one `git cat-file --batch` did not start or stopped early; " + "its sha list is written under {}) — refusing rather than print a hit count nothing measured\n", + quality::cacheDirLadder() ); + return 1; + } result.seedSpec = std::move( whereisSeed ); // The tree zero stays an answer; the near-miss only says WHICH zero it is (a name this repo never // had, or a keystroke away from one it has). Computed only on the zero, so a real hit list is diff --git a/test/crossrefdegradecheck.sh b/test/crossrefdegradecheck.sh index f22196105..a9a9d0a0c 100755 --- a/test/crossrefdegradecheck.sh +++ b/test/crossrefdegradecheck.sh @@ -22,6 +22,8 @@ # exactly one; and at exactly cap+1 items `size > shown` went false, so the element vanished ENTIRELY # and one row disappeared unmarked. Both the general case and that boundary are pinned here — nothing # in test/ pinned any more= count before, which is why the bug survived. +# 6) (cli-perf-068) A blob stream that never started (an unusable cache dir for its sha list) is refused by +# --whereis on both hosts, not printed as on-head="0" hits="0" with a near-miss retrying the same name. # 5) Two adjacent ways a real hit is reported as no hit at all: a symbol defined on an UNTERMINATED final # line (the scan only evaluated on '\n', so hits="0" — which the help text says means "this repo never # had the name"), and a branch whose name legally contains '|' (the for-each-ref parse split on it and @@ -273,6 +275,50 @@ REFS_BEFORE="$( git -C "$C" for-each-ref --format='%(refname) %(objectname)' 2>/ [ "$( git -C "$C" for-each-ref --format='%(refname) %(objectname)' 2>/dev/null | cksum )" = "$REFS_BEFORE" ] \ && ok "read-only: no ref was created, moved or deleted" || no "read-only: a crossref verb wrote a ref" +# ══ 6) a blob stream that never ran is not a measured zero (cli-perf-068; the cli-floor §4 finding) ═════════ +# --whereis reads every blob through ONE `git cat-file --batch` whose sha list is a file under the cache dir. With that +# dir unusable (TMPDIR naming a directory that does not exist) the batch never started, and the answer still printed +# on-head="0" hits="0" plus a near-miss note whose retry= was the very name asked — on a repository whose HEAD defines +# it. Now both hosts REFUSE (CLI: exit 1 naming the cause; MCP: an error) instead of printing a count nothing measured. +# 6c is the near-miss negative: a GENUINE zero over a working cache dir must still answer hits="0", exit 0. +H="$TMP/blobless"; mkdir -p "$H" "$TMP/goodtmp" +printf 'int whereisProbeName( int x ) { return x; }\nint whereisProbeCaller( void ) { return whereisProbeName( 1 ); }\n' > "$H/a.c" +git -C "$H" init -q -b main && git -C "$H" add a.c && git -C "$H" commit -qm one +TMPDIR="$TMP/goodtmp/" "$BIN" "$H" --whereis=whereisProbeName >"$TMP/b6a.xml" 2>"$TMP/b6a.err"; rc6a=$? +H6A="$( attr hits < "$TMP/b6a.xml" )" +if [ "$rc6a" = 0 ] && [ -n "$H6A" ] && [ "$H6A" -ge 2 ] 2>/dev/null; then ok "6a control: a working cache dir finds the name ($H6A hits, exit 0)" +else no "6a control: expected exit 0 and >=2 hits, got rc=$rc6a hits=${H6A:-none}"; fi +TMPDIR="$TMP/no-such-dir/" "$BIN" "$H" --whereis=whereisProbeName >"$TMP/b6b.out" 2>"$TMP/b6b.err"; rc6b=$? +if [ "$rc6b" != 0 ] && ! grep -q '"$TMP/b6c.xml" 2>/dev/null; rc6c=$? +if [ "$rc6c" = 0 ] && [ "$( attr hits < "$TMP/b6c.xml" )" = "0" ]; then ok "6c negative: a genuine zero over a working cache dir still answers hits=\"0\", exit 0" +else no "6c negative: a genuine zero was refused or miscounted (rc=$rc6c, hits=$( attr hits < "$TMP/b6c.xml" ))"; fi +if command -v python3 >/dev/null 2>&1; then + M6="$( TMPDIR="$TMP/no-such-dir/" python3 - "$BIN" "$H" <<'PY' +import json, subprocess, sys +msgs = [ { "jsonrpc": "2.0", "id": 1, "method": "initialize" }, + { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "whereis", "arguments": { "path": sys.argv[ 2 ], "symbol": "whereisProbeName" } } } ] +p = subprocess.run( [ sys.argv[ 1 ], "--mcp" ], input = "".join( json.dumps( m ) + "\n" for m in msgs ), capture_output = True, text = True, timeout = 300 ) +lines = [ l for l in p.stdout.splitlines() if l.strip() ] +d = json.loads( lines[ -1 ] ) if lines else {} +res = d.get( "result", {} ) +text = ( res.get( "content" ) or [ {} ] )[ 0 ].get( "text", "" ) +if "error" in d: + print( "ERROR " + d[ "error" ].get( "message", "" ) ) +elif res.get( "isError" ): + print( "ERROR " + text ) +else: + print( "ANSWER " + text[ :200 ] ) +PY +)" + case "$M6" in + "ERROR "*"could not read the trees"*) ok "6d MCP twin: the same unreadable stream is an error naming the cause, not a hit count" ;; + *) no "6d MCP twin: expected an error naming the unread blobs, got: $( printf '%s' "$M6" | head -c 200 )" ;; + esac +fi + # G4: every emitted document must survive xmllint if command -v xmllint >/dev/null 2>&1; then g4=0 From 9a26dee2515fa565ccf4b47b113a8b6595a18e94 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 07:58:58 -0400 Subject: [PATCH 027/170] perf(graph): dense candidate views in the resolve loop; skip reachableByName for names with no function-local def The resolve loop went 22 -> 79 ms at 24bb6e5d (built in isolation with the profile flavour, snap, median of 3). That commit gave every module-scope call an owner, and the newly owned calls are the intended consequence, not a defect: an instrumented build shows the +7,563 unresolved sites are module-scope Bash (7,327) and Python (208) calls to names whose every in-repo definition is another language's (`head`, `git`, `mkdir`, `set`, `join` ...), counted at the language-filter site exactly like the same calls inside a function; every reference that had an owner before keeps its edges (the commit's own verification). The cost is where those calls land: the test scripts' top-level `ok "..."` / `no "..."` lines, ~28k calls to names with ~650 same-named shell definitions each. Per call the loop walked that whole list several times, each step reading one field of a Symbol at a random id. Timed per disposition, module-scope Bound calls cost 1.9 us each against 0.55 us for the rest. - symLang / symKind (beside the existing symFileId) are dense copies of the fields those walks read: the language filter, the namespace gate, the same-file and same-directory tiers, Rule 2d and Rule 3's filter. - nameHasFnLocalDef holds the byName entries with a function-local definition. A name with none cannot yield (localDefOutOfReach is false for every candidate, and every list handed to reachableByName is a subset of the name's list), so that walk is skipped with the answer it would have returned. Resolve loop on snap, profile flavour, median of 5: 128 -> 77 ms. The --pin-census of every call site is byte-identical base vs head, as are the map, --callers, --callees, --impact and --for on snap. Co-Authored-By: Claude Opus 5.5 --- src/graph.h | 53 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/graph.h b/src/graph.h index 78e9c778d..51e083c5d 100644 --- a/src/graph.h +++ b/src/graph.h @@ -5728,6 +5728,39 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null rw::SmallVec reachScratch; // reused per-call buffer: the candidates a call can reach by name (reachableByName) rw::SmallVec rubyReachable; // reused per-call buffer: the candidates a Ruby call to self can reach (RubySelfReach) + // ---- the resolve loop's dense per-candidate views (cli-perf-068) --------------------------------------------------- + // The loop below walks a name's WHOLE candidate list several times per call — the language filter, the namespace gate, + // the same-file and same-directory tiers — and each step read one field out of a Symbol at a random id. That is cheap + // for a name with two definitions and was the resolve loop's cost for a name with hundreds: since every module-scope + // call got an owner (24bb6e5d), the test scripts' top-level `ok "…"` / `no "…"` calls (~650 same-named shell + // definitions each, ~28k calls on this repository) reached those walks, and the loop went 22 -> 79 ms on that commit + // alone. symLang/symKind are copies of the two Symbol fields those walks read (symFileId, above, is the third), so + // each walk reads a dense array in id order instead: same values, same order, same answer. `nameHasFnLocalDef` is + // the one precomputed fact reachableByName needs to say "nothing to set aside": a name none of whose definitions is + // function-local cannot yield (localDefOutOfReach is false for every candidate, and every list the loop hands + // reachableByName is a subset of the name's byName list), so its walk — a Symbol load plus a binary search per + // candidate — is skipped with the answer it would have returned. Keyed by the byName entry's address: nothing + // inserts into or erases from byName past its construction, so the address is stable for the loop. + std::vector symLang( N ); + std::vector symKind( N ); + for( std::size_t symIndex = 0; symIndex < N; ++symIndex ) + { + symLang[ symIndex ] = ing.symbols[ symIndex ].lang; + symKind[ symIndex ] = ing.symbols[ symIndex ].kind; + } + ankerl::unordered_dense::set*> nameHasFnLocalDef; + if( !ing.fnLocalScopes.empty() ) + { + for( const auto& [ name, ids ] : byName ) + { + if( std::any_of( ids.begin(), ids.end(), [ & ]( NodeId c ) { return ing.symbols[ c ].fnLocal != 0; } ) ) + { + nameHasFnLocalDef.insert( &ids ); + } + } + } + ENSURES( symLang.size() == N && symKind.size() == N && symFileId.size() == N, "the dense views cover every symbol id the loop can hold" ); + // ---- census arming + the ORACLE side (eval-only; src/pincensus.h) ------------------------------ // The oracle rows are a straight transcription of the overlay's own (from, calleeName) → target table // into the census's id space — the same table graph.h consults below, so the two sides of the join @@ -5943,7 +5976,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null if( !scipPinned && r.lang != Lang::Elixir && !r.qualifier.empty() ) { appendCanonicalCandidates( cand, qkey, r, CanonicalScopes { canonByName, canonFamilyByName, specializationsWithBases, narrower, chaUp, ing.symbols }, - [ & ]( NodeId c ) { return langCompatible( ing.symbols[c].lang, r.lang ) && sameRoot( c, r.fileId ); } ); + [ & ]( NodeId c ) { return langCompatible( symLang[ c ], r.lang ) && sameRoot( c, r.fileId ); } ); canonical = !cand.empty(); } // ── L3 fn-pointer/callback binding resolve — BEFORE Rule 1, because a local variable shadows a @@ -6238,7 +6271,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null std::size_t found = 0; for( NodeId c : it->second ) { - if( langCompatible( ing.symbols[c].lang, r.lang ) && symFileId[c] == aliasFile ) + if( langCompatible( symLang[ c ], r.lang ) && symFileId[c] == aliasFile ) { only = c; if( ++found > 1 ) @@ -6287,7 +6320,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null // whichever same-named def competes, and yielding would move them from external= into declined=, so such a call // resolves exactly as it did before local defs had bodies. const bool localsYield = !scipPinned && !canonical && !narrowed && !builtinGated && it != byName.end() && !ing.fnLocalScopes.empty() - && reachableByName( ing, *baseIds, r, reachScratch ) + && nameHasFnLocalDef.contains( &it->second ) && reachableByName( ing, *baseIds, r, reachScratch ) && !( r.role == RefRole::Call && r.qualifier.empty() && bindingTier.empty() && externalVeto.isExternalBound( r ) ); const rw::SmallVec* nameIds = localsYield ? &reachScratch : baseIds; // Ruby: Rule 3 and the ladder choose only among what self's method lookup can reach (RubySelfReach), on the side @@ -6318,7 +6351,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null { for( NodeId c : rule3Out ) { - if( langCompatible( ing.symbols[c].lang, r.lang ) ) + if( langCompatible( symLang[ c ], r.lang ) ) { cand.push_back( c ); } @@ -6356,7 +6389,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null { for( NodeId c : *nameIds ) { - if( langCompatible( ing.symbols[c].lang, r.lang ) && sameRoot( c, r.fileId ) ) + if( langCompatible( symLang[ c ], r.lang ) && sameRoot( c, r.fileId ) ) { cand.push_back( c ); // same lang, or ObjC↔C++ bridge; same ROOT (A10) } @@ -6364,7 +6397,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null for( std::size_t i = 0; localsYield && cand.empty() && i < baseIds->size(); ++i ) // nothing reachable survived: the whole list { const NodeId c = ( *baseIds )[ i ]; - if( langCompatible( ing.symbols[c].lang, r.lang ) && sameRoot( c, r.fileId ) ) + if( langCompatible( symLang[ c ], r.lang ) && sameRoot( c, r.fileId ) ) { cand.push_back( c ); } @@ -6378,7 +6411,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null { for( NodeId c : *baseIds ) { - if( !langCompatible( ing.symbols[c].lang, r.lang ) || sameRoot( c, r.fileId ) ) + if( !langCompatible( symLang[ c ], r.lang ) || sameRoot( c, r.fileId ) ) { continue; } @@ -6430,7 +6463,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null std::size_t keepCount = 0; for( std::size_t ci = 0; ci < cand.size(); ++ci ) { - if( namespaceCompatible( r.role, ing.symbols[ cand[ci] ].kind ) ) + if( namespaceCompatible( r.role, symKind[ cand[ci] ] ) ) { cand[ keepCount++ ] = cand[ci]; } @@ -6510,7 +6543,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null { for( NodeId c : cand ) { - if( ing.symbols[c].fileId == r.fileId ) + if( symFileId[ c ] == r.fileId ) { tier.push_back( c ); // tier 1: same file } @@ -6521,7 +6554,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null const std::uint32_t rdir = fileDir[ r.fileId ]; for( NodeId c : cand ) { - if( fileDir[ing.symbols[c].fileId] == rdir ) + if( fileDir[ symFileId[ c ] ] == rdir ) { tier.push_back( c ); } From f08453751ac4dd76fd0b6756619d5a660fe4390c Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 08:13:29 -0400 Subject: [PATCH 028/170] perf(ingest): run the git ignore probe beside the crawl, on the previous call's answer, checked before the parse Every call on a git checkout ran `git ls-files --others --ignored --exclude-standard --directory` before the crawl could start: ~60 ms of a ~400 ms warm call on this repository, most of it git walking the tree the crawl walks next. A cache keyed on the files that hold ignore rules would not be exact: the answer also changes when the untracked files change (a new x.log under an existing *.log rule, a file added to a directory git reported whole because everything in it was ignored). So nothing is keyed. The crawl now walks with the answer the PREVIOUS call got for this root (a sidecar under the cache dir, header naming the root) while git computes this call's answer on a worker (std::async), and ingest() compares the two raw -z streams byte for byte before the parse pool. Equal: the crawl that ran is the crawl git's fresh answer gives. Different: the crawl, the cache load and the scan arrays run again on the fresh answer (the crawl's memory stop re-armed and re-recorded with it), and the sidecar is replaced. Every answer is git's own answer for this call; only the wait moved. collectGitIgnored is split into runGitIgnoreProbe (the process, classified as before: non-zero exit outranks overflow) and parseGitIgnoreProbe (every DISCLOSE where it was, on the thread that applies the answer). Spawn conditions are unchanged: same point of the crawl, no child without a .git above the root, none under --no-ignore; under --no-cache there is no sidecar and the probe runs inline. test/gitignorecheck.sh arm 14 (every earlier arm runs --no-cache and so never speculated): 14a the sidecar exists (red on the base binary); 14b-14g a cached run after each change equals a --no-cache run and shows the change: an edited .gitignore, a new nested .gitignore, .git/info/exclude, the default global excludes file, core.excludesFile's file, and a new untracked file an old rule ignores; 14h repeat runs; 14i a lost speculation under a crawl memory stop. The census gates follow the renamed popen site (crashsweepcheck) and the sidecar name buffer (fixedbufsweep). Co-Authored-By: Claude Opus 5.5 --- src/ingest.cpp | 33 +++++- src/ingest_crawl.h | 219 +++++++++++++++++++++++++++++++++++----- test/crashsweepcheck.sh | 2 +- test/fixedbufsweep.sh | 3 +- test/gitignorecheck.sh | 50 +++++++++ 5 files changed, 277 insertions(+), 30 deletions(-) diff --git a/src/ingest.cpp b/src/ingest.cpp index 840d28e7f..b1c23fa9c 100644 --- a/src/ingest.cpp +++ b/src/ingest.cpp @@ -58,6 +58,7 @@ #include #include #include +#include // cli-perf-068: the git ignore probe on a worker (ingest_crawl.h IgnoreProbeSpeculation) #include #include #include @@ -315,10 +316,18 @@ IngestResult ingest( const char* rootDir, const std::vector& exclud // #350 layer 3: one memory watch for this ingest, shared by the crawl and the parse pool (memguard.h) memguard::Watch memWatch; - // 1) deterministic crawl -> sorted file list (this list IS result.files / the fileId space) + // cli-perf-068: the git ignore probe runs beside the walk and the cache load, on the previous call's answer, and is + // checked against this call's own answer before the parse pool (ingest_crawl.h IgnoreProbeSpeculation). No sidecar + // under --no-cache: the probe then runs inside the crawl, as it always did. + IgnoreProbeSpeculation ignoreSpeculation( !cacheFile.empty() ); + + // 1) deterministic crawl -> sorted file list (this list IS result.files / the fileId space). A lambda because a lost + // ignore speculation runs it a second time, on git's fresh answer (below, before the parse pool). + const auto crawlInto = [ & ]() { PROFILE_SCOPE_DESCRIBE( "ingest: crawl (collectSources)" ); - auto [ crawledPaths, oversizeSkipped, taxonomySkips ] = collectSources( rootDir, excludeSubstr, maxFileBytes, excludeLabel, respectGitignore, &memWatch ); + auto [ crawledPaths, oversizeSkipped, taxonomySkips ] = collectSources( rootDir, excludeSubstr, maxFileBytes, excludeLabel, respectGitignore, &memWatch, + &ignoreSpeculation ); result.files = std::move( crawledPaths ); // #228: record the root once, for rootRelPath (model.h). A directory crawl joins it onto every path; a // single-file root IS its one path, so the root-relative view anchors at that file's directory instead. @@ -344,7 +353,8 @@ IngestResult ingest( const char* rootDir, const std::vector& exclud DISCLOSE( result.memoryStop, MemoryStop::DisclosureWhy::CrawlOverLimit, "ingest: the memory guard stopped the crawl — files= is what it saw, a floor of the tree" ); } } - } + }; + crawlInto(); // Win 1 (PERF.md P1) — lazy grammar compilation: load the cache FIRST, then compile only the // grammars needed by cache-miss files (new or hash-changed). On a fully-warm zero-change run, @@ -373,10 +383,25 @@ IngestResult ingest( const char* rootDir, const std::vector& exclud HashMap cache = cacheFile.empty() ? HashMap{} : loadCache( std::string( cacheFile ), rootDir, captureValueUses, result.files, cacheStats ); - const long long cacheWriteNs = cacheStats.blobWriteNs; // per-fileId scan arrays (language classify + hash/stat-gate + health slots) — ingest_prewarm.h IngestFileScan scan = makeFileScan( result.files ); + // The ignore speculation is decided HERE: nothing past this point has run on the crawl's file list but the cache load + // and the scan arrays, and both are redone. A lost speculation (the previous call's ignore answer differs from git's + // answer for this call) re-walks on the fresh answer; the crawl's memory stop is re-armed and re-recorded with it. + if( !ignoreSpeculation.crawlStands() ) + { + result.memoryStop = MemoryStop{}; + memWatch.rearmForParse(); // clears the crawl's stop flags: the second walk is measured on its own + crawlInto(); + cacheStats = CacheLoadStats{}; + cache = cacheFile.empty() ? HashMap{} + : loadCache( std::string( cacheFile ), rootDir, captureValueUses, result.files, cacheStats ); + scan = makeFileScan( result.files ); + } + ENSURES( scan.lang.size() == result.files.size(), "the scan arrays describe the crawl the parse pool will read" ); + const long long cacheWriteNs = cacheStats.blobWriteNs; + // lazy tags.scm prewarm: detect the cache-miss set (prefilling scan.hash/stat), launch the async // grammar compiles, and hand the compile/ready state to the parse pool via `prewarm` (ingest_prewarm.h). QueryPrewarm prewarm; diff --git a/src/ingest_crawl.h b/src/ingest_crawl.h index 88420139f..968ef3e28 100644 --- a/src/ingest_crawl.h +++ b/src/ingest_crawl.h @@ -1379,49 +1379,99 @@ bool underGitRoot( const char* rootDir ) } } -GitIgnoreSet collectGitIgnored( const char* rootDir ) +// The probe's PROCESS half, split from its parse so it can run off the main thread (IgnoreProbeSpeculation below): one +// `git ls-files --others --ignored` popen, read whole, classified exactly as collectGitIgnored always classified it — +// a non-zero exit outranks an overflow (the overflow break still reaches pclose, and its status is read first). No +// DISCLOSE and no parse here: the parse below runs on the thread that applies the answer, so the disclosure ledger +// sees exactly the degrades of the answer the crawl used. +struct GitIgnoreProbeRaw { - GitIgnoreSet out; + enum class State : std::uint8_t + { + NotGit, // no `.git` above the root: no git child was started (nongitqmetricscheck) + NotRunnable, // popen failed + Failed, // git exited non-zero + OverCeiling, // more than kMaxIgnoreProbeBytes + Answered, // `bytes` is git's whole -z answer + }; + State state = State::NotGit; + std::string bytes; + bool operator==( const GitIgnoreProbeRaw& ) const = default; +}; + +GitIgnoreProbeRaw runGitIgnoreProbe( const char* rootDir ) +{ + PROFILE_SCOPE_DESCRIBE( "ingest: git ignore probe (the git process; off the main thread when speculated)" ); + GitIgnoreProbeRaw raw; if( !underGitRoot( rootDir ) ) { - return out; + return raw; } const std::string cmd = gitCmd( " -C " ) + shSingleQuote( rootDir == nullptr ? std::string( "." ) : std::string( rootDir ) ) + " -c core.quotepath=false ls-files --others --ignored --exclude-standard --directory -z 2>/dev/null"; std::FILE* pipe = os::popen( cmd.c_str(), "r" ); if( pipe == nullptr ) { - DISCLOSE( out, GitIgnoreSet::DisclosureWhy::GitNotRunnable, "ingest: cannot run git for the ignore probe — full walk" ); - return out; + raw.state = GitIgnoreProbeRaw::State::NotRunnable; + return raw; } - std::string buf; - char chunk[ 8192 ]; - bool overflowed = false; + char chunk[ 8192 ]; + bool overflowed = false; for( std::size_t n = std::fread( chunk, 1, sizeof( chunk ), pipe ); n > 0; n = std::fread( chunk, 1, sizeof( chunk ), pipe ) ) { - if( buf.size() + n > kMaxIgnoreProbeBytes ) + if( raw.bytes.size() + n > kMaxIgnoreProbeBytes ) { overflowed = true; break; } - buf.append( chunk, n ); + raw.bytes.append( chunk, n ); } const int rc = os::pclose( pipe ); - if( rc != 0 ) + raw.state = rc != 0 ? GitIgnoreProbeRaw::State::Failed : overflowed ? GitIgnoreProbeRaw::State::OverCeiling : GitIgnoreProbeRaw::State::Answered; + if( raw.state != GitIgnoreProbeRaw::State::Answered ) { - // Not a git work tree, or no git binary — the DESIGNED degrade: the fallback (a full walk, no - // ignore-set pruning) is safe either way. `available` is already false here (its default), so this - // changes no output — it disclose()s the fact for the self-check ledger instead of leaving the - // degrade to a bare `return`, matching the GitNotRunnable/ProbeOverCeiling sites just above/below. - DISCLOSE( out, GitIgnoreSet::DisclosureWhy::ProbeFailed, "ingest: the git ignore probe exited non-zero — full walk" ); - return out; + raw.bytes.clear(); // only an answer carries bytes, so two probes that failed the same way compare equal } - if( overflowed ) + ENSURES( raw.state == GitIgnoreProbeRaw::State::Answered || raw.bytes.empty(), "a probe that did not answer carries no bytes" ); + return raw; +} + +// The probe's PARSE half: every degrade disclosed exactly where collectGitIgnored disclosed it, then the -z entries. +GitIgnoreSet parseGitIgnoreProbe( const GitIgnoreProbeRaw& raw ) +{ + GitIgnoreSet out; + switch( raw.state ) { - DISCLOSE( out, GitIgnoreSet::DisclosureWhy::ProbeOverCeiling, "ingest: git ignore probe exceeded its byte ceiling — full walk" ); - return out; + case GitIgnoreProbeRaw::State::NotGit: + { + return out; + } + case GitIgnoreProbeRaw::State::NotRunnable: + { + DISCLOSE( out, GitIgnoreSet::DisclosureWhy::GitNotRunnable, "ingest: cannot run git for the ignore probe — full walk" ); + return out; + } + case GitIgnoreProbeRaw::State::Failed: + { + // Not a git work tree, or no git binary — the DESIGNED degrade: the fallback (a full walk, no + // ignore-set pruning) is safe either way. `available` is already false here (its default), so this + // changes no output — it disclose()s the fact for the self-check ledger instead of leaving the + // degrade to a bare `return`, matching the GitNotRunnable/ProbeOverCeiling sites just above/below. + DISCLOSE( out, GitIgnoreSet::DisclosureWhy::ProbeFailed, "ingest: the git ignore probe exited non-zero — full walk" ); + return out; + } + case GitIgnoreProbeRaw::State::OverCeiling: + { + DISCLOSE( out, GitIgnoreSet::DisclosureWhy::ProbeOverCeiling, "ingest: git ignore probe exceeded its byte ceiling — full walk" ); + return out; + } + case GitIgnoreProbeRaw::State::Answered: + { + break; + } } + const std::string& buf = raw.bytes; for( std::size_t i = 0; i < buf.size(); ) { const std::size_t nul = buf.find( '\0', i ); @@ -1460,6 +1510,124 @@ GitIgnoreSet collectGitIgnored( const char* rootDir ) return out; } +GitIgnoreSet collectGitIgnored( const char* rootDir ) +{ + return parseGitIgnoreProbe( runGitIgnoreProbe( rootDir ) ); +} + +// ── the ignore probe, SPECULATED (cli-perf-068) ─────────────────────────────────────────────────────── +// +// The probe is one git process on every call — ~60 ms of a ~400 ms warm call on this repository, measured, and most of +// it git walking the same tree the crawl is about to walk. Its answer rarely changes between two calls, but WHAT can +// change it is wide: any .gitignore in the tree, .git/info/exclude, the index, core.excludesFile and the config that +// names it, and the untracked files themselves (a new `x.log` under an existing `*.log` rule changes the answer while +// no ignore FILE changed). A cache keyed on the files that hold rules would miss that last one; this does not key on +// anything. It SPECULATES: the crawl walks with the answer the previous call got for this root (a sidecar under the +// cache dir) while git computes this call's answer on a worker, and ingest() compares the two — byte for byte, the +// raw -z stream — before anything downstream of the crawl has run (crawlStands). Equal: the crawl that ran IS the +// crawl the fresh answer gives, so nothing is redone. Different: the crawl and the cache load are run again on the +// fresh answer, and the sidecar is replaced. Every answer is therefore git's own answer for THIS call, exactly as +// before; what is saved is the wait — git runs beside the walk and the cache load instead of before them. +// +// Spawn conditions are the ones the probe always had: it starts at the same point of collectSources (a directory root +// whose walk opened), only under a `.git` (no child at all otherwise), and not under --no-ignore. With the cache off +// (--no-cache) there is no sidecar, so the probe runs on the calling thread, as before. One per ingest() call. +class IgnoreProbeSpeculation +{ +public: + explicit IgnoreProbeSpeculation( bool sidecarEnabled ) noexcept : sidecarEnabled_( sidecarEnabled ) {} + + // collectSources' one consult, at the point the probe always ran. On the redo pass (after a lost speculation) it + // hands back the fresh answer the join already holds. + GitIgnoreSet setFor( const char* rootDir, const std::string& rootReal ) + { + if( fresh_ ) + { + return parseGitIgnoreProbe( *fresh_ ); + } + if( !sidecarEnabled_ || !underGitRoot( rootDir ) ) + { + fresh_ = runGitIgnoreProbe( rootDir ); // no sidecar to speculate from, or no git child to overlap: as before + return parseGitIgnoreProbe( *fresh_ ); + } + sidecarPath_ = quality::resolveCacheBlobPath( quality::cacheDirLadder(), sidecarName( rootReal ) ); + sidecarHeader_ = std::string( kSidecarMagic ) + rootReal + '\0'; + pending_ = std::async( std::launch::async, [ root = std::string( rootDir ) ] { return runGitIgnoreProbe( root.c_str() ); } ); + speculation_ = loadSidecar(); + if( !speculation_ ) + { + return parseGitIgnoreProbe( join() ); // nothing to speculate from (first call on this root): wait, as before + } + return parseGitIgnoreProbe( *speculation_ ); + } + + // ingest()'s check, before the parse pool: true when the crawl that ran used this call's own git answer — the fresh + // answer itself, or a speculation byte-identical to it. False means the caller redoes the crawl (setFor now returns + // the fresh answer). Publishes the fresh answer for the next call whenever it differs from what was speculated. + bool crawlStands() + { + PROFILE_SCOPE_DESCRIBE( "ingest: ignore probe join (wait for git, compare with the speculation)" ); + if( pending_.valid() ) + { + (void)join(); + } + if( !speculation_ ) + { + return true; // the crawl waited for the fresh answer, or no probe ran at all + } + const bool stands = *speculation_ == *fresh_; + speculation_.reset(); // decided: a redo pass reads fresh_, and a second call to this is a no-op + return stands; + } + +private: + static constexpr std::string_view kSidecarMagic = "ripwire-ignore-speculation/1\n"; + + static std::string sidecarName( const std::string& rootReal ) + { + char sidecarFile[ 64 ]; + rw::formatTo( sidecarFile, sizeof( sidecarFile ), "ripwire-ignoreprobe-{:016x}.bin", static_cast( fnv1a64( rootReal ) ) ); + return sidecarFile; + } + + // The previous call's answer for this root, or nothing. The header names the root in full, so two roots whose names + // hash alike never read each other's answer; anything unreadable or misshapen is simply no speculation. + std::optional loadSidecar() const + { + std::optional blob = pathguard::readRegularFileNoFollow( sidecarPath_ ); + if( !blob || blob->size() > sidecarHeader_.size() + kMaxIgnoreProbeBytes || !blob->starts_with( sidecarHeader_ ) ) + { + return std::nullopt; + } + return GitIgnoreProbeRaw{ GitIgnoreProbeRaw::State::Answered, blob->substr( sidecarHeader_.size() ) }; + } + + const GitIgnoreProbeRaw& join() + { + fresh_ = pending_.get(); // a throw on the worker (allocation) surfaces here, where the synchronous probe threw + if( fresh_->state == GitIgnoreProbeRaw::State::Answered && ( !speculation_ || !( *speculation_ == *fresh_ ) ) ) + { + if( !quality::atomicWriteFile( sidecarPath_, sidecarHeader_ + fresh_->bytes ) ) + { + DISCLOSE( Diagnostics::answerUnchanged, "this call used git's own ignore answer; only the next call's speculation is not saved", + "ingest: the ignore-probe sidecar could not be written — the next call waits for git, as before" ); + } + } + else if( fresh_->state != GitIgnoreProbeRaw::State::Answered && speculation_ ) + { + os::unlink( sidecarPath_.c_str() ); // git stopped answering here: a stale answer must not be speculated on again + } + return *fresh_; + } + + bool sidecarEnabled_ = false; + std::string sidecarPath_; + std::string sidecarHeader_; + std::future pending_; + std::optional speculation_; + std::optional fresh_; +}; + // Sorted-vector membership. A binary search over a contiguous, already-sorted vector beats a hash map at // these sizes and costs no allocation, and — unlike a HashMap — has no iteration order that could reach // output (the G2 container rule's standing caveat). @@ -1490,14 +1658,17 @@ bool pathInIgnoreSet( const std::vector& sorted, std::string_view r // §N6-C — the probe AND the mode it implies, as one decision, so collectSources reads the answer instead // of computing it. One fork per root, paid only for a directory root the walk actually opened: the probe // is worthless on a single-file root and must not be charged to a root the walk already refused. -GitIgnoreSet probeIgnoreSet( const char* rootDir, bool respectGitignore, IgnoreMode& modeOut ) +// `speculation` (ingest()'s, see IgnoreProbeSpeculation) lets the git process run beside the walk; without one the probe +// runs here, on this thread, as it always did. +GitIgnoreSet probeIgnoreSet( const char* rootDir, bool respectGitignore, IgnoreMode& modeOut, IgnoreProbeSpeculation* speculation, + const std::string& rootReal ) { if( !respectGitignore ) { return {}; // modeOut was already set to Off by the caller, before any early return could skip it } PROFILE_SCOPE_DESCRIBE( "ingest: crawl (git ignore probe)" ); - GitIgnoreSet set = collectGitIgnored( rootDir ); + GitIgnoreSet set = speculation != nullptr ? speculation->setFor( rootDir, rootReal ) : collectGitIgnored( rootDir ); modeOut = set.available ? IgnoreMode::Git : set.rootIgnored ? IgnoreMode::RootIgnored : IgnoreMode::Unavailable; return set; } @@ -1554,7 +1725,7 @@ struct CrawlResult // finished walk's list is, so a partial corpus is still a deterministic one; ingest() discloses the stop. CrawlResult collectSources( const char* rootDir, const std::vector& excludeSubstr, std::size_t maxFileBytes, std::string_view excludeLabel = {}, bool respectGitignore = true, - memguard::Watch* memWatch = nullptr ) + memguard::Watch* memWatch = nullptr, IgnoreProbeSpeculation* ignoreSpeculation = nullptr ) { std::vector out; std::vector skipped; @@ -1612,7 +1783,7 @@ CrawlResult collectSources( const char* rootDir, const std::vector& return { std::move( out ), std::move( skipped ), std::move( skips ) }; } - const GitIgnoreSet ignoreSet = probeIgnoreSet( rootDir, respectGitignore, skips.ignoreMode ); // §N6-C + const GitIgnoreSet ignoreSet = probeIgnoreSet( rootDir, respectGitignore, skips.ignoreMode, ignoreSpeculation, rootReal ); // §N6-C const fs::recursive_directory_iterator end; { diff --git a/test/crashsweepcheck.sh b/test/crashsweepcheck.sh index d2b1cc35f..67b7de013 100755 --- a/test/crashsweepcheck.sh +++ b/test/crashsweepcheck.sh @@ -317,7 +317,7 @@ infra/os.h spawn_sh open 1 closes in the forked child: dup2 onto stdin, close, t infra/os.h read_small open 1 closes one read, then ::close before the only return past the failed-open one (Linux /proc and /sys files) ingest_cache.h openOnce open 1 owned ReadFd's destructor closes it ingest_cache.h saveCache fdopen 1 closes adopts ExclTempFile's released fd; fclose on its own line; a failed fdopen ::closes the fd -ingest_crawl.h collectGitIgnored popen 1 closes the overflow break still reaches pclose +ingest_crawl.h runGitIgnoreProbe popen 1 closes the overflow break still reaches pclose ingest_docpass.h publishDocBridgeBlob fdopen 1 closes adopts ExclTempFile's released fd; fclose on its own line; a failed fdopen ::closes the fd lintrules.h loadLintRules fopen 1 closes skips only a failed open; fclose after the sized read main.cpp dispatchMain fopen 1 closes returns only on a failed open; fclose after the read loop diff --git a/test/fixedbufsweep.sh b/test/fixedbufsweep.sh index ddf94a920..1ecf118d7 100755 --- a/test/fixedbufsweep.sh +++ b/test/fixedbufsweep.sh @@ -233,6 +233,7 @@ NUMERIC_ONLY = { ( "src/infra/profileScope.h", "buf" ): 5, ( "src/ingest_astquery.h", "suffix" ): 1, ( "src/ingest_docpass.h", "blobName" ): 1, + ( "src/ingest_crawl.h", "sidecarFile" ): 1, # cli-perf-068: IgnoreProbeSpeculation::sidecarName — "ripwire-ignoreprobe-" + 16 hex digits + ".bin" = 40 B against 63 usable + NUL. One uint64, nothing escaped. ( "src/lsp.h", "hdr" ): 1, # 2026-09-16 (--lsp Phase 1): lspWriteMessage's "Content-Length: {}\r\n\r\n" — ONE size_t, 20 digits worst case: 40 B against 63 usable + NUL. No %s, nothing escaped. ( "src/lsp.h", "buf" ): 1, # 2026-09-16 (--lsp Phase 1): lspRangeJson's range object — 61 fixed chars + FOUR uint32 line/character numbers, 10 digits worst case: 101 B against 127 usable + NUL. No %s, nothing escaped; the strings around it (URI, name) are composed on std::string by the callers, never through this buffer. ( "src/main.cpp", "hdr" ): 1, @@ -594,7 +595,7 @@ if not bad: # 238 -> 239 calls/sites, 104 -> 105 rows). noteCacheReject's new `char detail[192]` names both version # numbers on a refused cache blob; one formatTo of a literal and two std::uint32_t, rowed 'not-markup' # (a stderr notice). Re-derived on the train 20 merged tree; no other train lane moves the population. -EXPECTED = { "mentions": 351, "calls": 241, "sites": 241, "rows": 105, "widthforms": 0 } +EXPECTED = { "mentions": 352, "calls": 242, "sites": 242, "rows": 106, "widthforms": 0 } # 2026-09-30 (#325 ruby_bases_unscoped=): +2 calls/+2 mentions/+2 sites, rows unchanged — graphGaugeAttrXml/Json (graphlegend.h) each format the absent-at-zero Ruby gauge into the SAME local buf[160]: one size_t, no string argument (21 + 20 digits worst case). # 2026-09-04 (capture-audit L6, H9): +1 call/+1 mention, sites/rows UNCHANGED — re-read, not # re-counted. packConnect gained ONE snprintf into a new `char connectCeiling[32]` for the diff --git a/test/gitignorecheck.sh b/test/gitignorecheck.sh index da2818dc8..ffd85e6ce 100755 --- a/test/gitignorecheck.sh +++ b/test/gitignorecheck.sh @@ -237,6 +237,56 @@ grep -q "rwGateMainSymbol" "$TMP/probefail.map" \ && ok "gitignore probe failure still maps the tree in full (the safe fallback, not an empty map)" \ || no "gitignore probe failure produced an empty/short map instead of falling back to a full walk" +# ── 14. (cli-perf-068) the SPECULATED probe stays exact. With the cache on, the crawl walks with the PREVIOUS call's +# ignore answer (a sidecar under the cache dir) while git computes this call's answer beside it, and ingest() redoes +# the crawl when the two differ (ingest_crawl.h IgnoreProbeSpeculation). Every arm above runs --no-cache, which keeps +# the probe synchronous, so none of them reaches this path. Each arm below changes ONE input the ignore answer reads, +# then asserts the cached run (speculating on the stale answer) is byte-identical to a --no-cache run of the same +# tree, and that the change took effect. 14a is the red-first arm: the base binary writes no sidecar. A binary that +# trusted the speculation without the check fails 14c-14g (CHECKLIST 1 mutation, recorded in the lane report). +SC="$TMP/spec-cache"; mkdir -p "$SC" "$TMP/xdg/git" "$TMP/home" +SPEC="$TMP/spec"; mkrepo "$SPEC" +spec_env(){ HOME="$TMP/home" XDG_CONFIG_HOME="$TMP/xdg" GIT_CONFIG_NOSYSTEM=1 "$@"; } +spec_run(){ spec_env env TMPDIR="$SC/" "$BIN" "$SPEC" --top-k=400 2>/dev/null; } +ref_run(){ spec_env "$BIN" "$SPEC" --top-k=400 --no-cache 2>/dev/null; } +# same_as_ref LABEL SYMBOL want(present|absent): one cached run against one --no-cache run, then the symbol's state +same_as_ref(){ + spec_run >"$TMP/spec.out"; ref_run >"$TMP/ref.out" + if ! cmp -s "$TMP/spec.out" "$TMP/ref.out"; then no "14$1: the cached (speculated) run differs from --no-cache"; return; fi + if [ "$3" = present ]; then grep -q "$2" "$TMP/spec.out" && ok "14$1: cached run == --no-cache, and $2 is indexed" || no "14$1: $2 should be indexed" + else grep -q "$2" "$TMP/spec.out" && no "14$1: $2 should be ignored now" || ok "14$1: cached run == --no-cache, and $2 is ignored"; fi +} +spec_run >/dev/null +if [ -n "$( find "$SC" -name 'ripwire-ignoreprobe-*.bin' 2>/dev/null | head -1 )" ]; then ok "14a: a cached run on a git root keeps the ignore answer for the next call's speculation" +else no "14a: no ignore-probe sidecar under the cache dir — the probe is not speculated"; fi +same_as_ref b rwGateGeneratedSymbol absent # warm: the speculation matches git +printf 'gen_out/\ntracked_anyway.cpp\n' >"$SPEC/.gitignore" # c: an EDITED .gitignore un-ignores generated.cpp +same_as_ref c rwGateGeneratedSymbol present +printf 'int rwGateNestedSymbol( int a ) { return a + 7; }\n' >"$SPEC/keep/extra.cpp"; spec_run >/dev/null +printf 'extra.cpp\n' >"$SPEC/keep/.gitignore" # d: a NEW nested .gitignore +same_as_ref d rwGateNestedSymbol absent +printf 'int rwGateInfoExcludeSymbol( int a ) { return a + 8; }\n' >"$SPEC/info_ex.cpp"; spec_run >/dev/null +mkdir -p "$SPEC/.git/info"; printf 'info_ex.cpp\n' >>"$SPEC/.git/info/exclude" # e: .git/info/exclude +same_as_ref e rwGateInfoExcludeSymbol absent +printf 'int rwGateGlobalSymbol( int a ) { return a + 9; }\n' >"$SPEC/glob_ex.cpp"; spec_run >/dev/null +printf 'glob_ex.cpp\n' >"$TMP/xdg/git/ignore" # f1: the DEFAULT global excludes file +same_as_ref f1 rwGateGlobalSymbol absent +printf 'int rwGateExcludesFileSymbol( int a ) { return a + 10; }\n' >"$SPEC/cfg_ex.cpp"; : >"$TMP/excludes" +git -C "$SPEC" config core.excludesFile "$TMP/excludes"; spec_run >/dev/null +printf 'cfg_ex.cpp\n' >"$TMP/excludes" # f2: core.excludesFile's file, edited +same_as_ref f2 rwGateExcludesFileSymbol absent +printf 'scratch_*.cpp\n' >>"$SPEC/.gitignore"; spec_run >/dev/null # g: no rule FILE changes, only the tree: +printf 'int rwGateScratchSymbol( int a ) { return a + 11; }\n' >"$SPEC/scratch_new.cpp" # a new file an old rule ignores +same_as_ref g rwGateScratchSymbol absent +printf 'gen_out/\n' >"$SPEC/.gitignore" # i: a LOST speculation whose walk the memory +spec_env env RIPWIRE_TEST_MEMGUARD=crawl:6 TMPDIR="$SC/" "$BIN" "$SPEC" --top-k=400 >"$TMP/spec.i" 2>"$TMP/spec.i.err"; rcSpecI=$? # guard stops: +spec_env env RIPWIRE_TEST_MEMGUARD=crawl:6 "$BIN" "$SPEC" --top-k=400 --no-cache >"$TMP/ref.i" 2>/dev/null; rcRefI=$? # the redo is re-armed +if ! grep -q 'stopped the crawl' "$TMP/spec.i.err"; then no "14i: the crawl:6 seam did not stop the walk — the arm tests nothing" +elif [ "$rcSpecI" = "$rcRefI" ] && cmp -s "$TMP/spec.i" "$TMP/ref.i"; then ok "14i: a lost speculation under a crawl memory stop answers as --no-cache does (rc $rcSpecI)" +else no "14i: a lost speculation under a crawl memory stop differs from --no-cache (rc $rcSpecI vs $rcRefI)"; fi +spec_run >"$TMP/spec.a"; spec_run >"$TMP/spec.b" +cmp -s "$TMP/spec.a" "$TMP/spec.b" && ok "14h: two cached runs in a row are byte-identical" || no "14h: two cached runs in a row differ" + # ── 13. the flag is in --help (the deckcheck allowlist row for --no-ignore retires with it). "$BIN" --help=all 2>&1 | grep -q -- '--no-ignore' && ok "--no-ignore is documented in --help" \ || no "--no-ignore is missing from --help" From c3444591571b028a1b619c59706a209bc33f3e5e Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 08:30:36 -0400 Subject: [PATCH 029/170] docs: date the 197 ms warm-query figure and add today's warm CLI per-call cost, with bench/warmcli.sh README said "warm queries answer in 197 ms" in a sentence about this repository. That figure is the median over 48 questions across django, webpack and this repository, measured on 2026-08-22 with a dev build at 7eb638e, before the crawl asked git for the ignore set; a warm CLI call on this repository measured 344-476 ms on 2026-10-08 before this lane. README (summary and the method paragraph) and docs/EVALS.md now date the figure and name its build, and state today's warm CLI per-call cost on this repository: a median 342 ms (Release build f0845375, five verbs, 10-core arm64 Mac). The other server's figures were not re-measured and no new comparison is claimed. bench/warmcli.sh reproduces the per-call figure: a fixed argv list, a fresh cache dir per invocation, two warm-up calls and RUNS timed calls per argv, median / min / max per argv, and the environment printed first (binary version with built_from, git version, root and file count, machine and load). CHANGELOG: the lane's performance entry (with the before/after table) and the --whereis refusal. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 37 ++++++++++++++++++++++++++++++ README.md | 11 ++++++--- bench/warmcli.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/EVALS.md | 5 +++- 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100755 bench/warmcli.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d22e195d..bf464a702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,43 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Changed — a warm CLI call costs about a quarter less, `--whereis` on a many-branch checkout about 70% less, and `--whereis` refuses instead of printing a zero it never measured + +Every answer is byte-identical to before except the one `--whereis` refusal below. Measured on 2026-10-08 on a +10-core arm64 Mac, Release builds, median of 5 warm calls, against main at 60dd3b3f: + +| call | before | after | +| --- | ---: | ---: | +| `--callers` on a 3,100-file snapshot of this repository, no `.git` | 319 ms | 269 ms | +| the same snapshot as a one-commit git repository | 437 ms | 341 ms | +| five verbs on this repository's checkout (`bench/warmcli.sh`, median of medians) | 448 ms | 342 ms | +| `--whereis` on a checkout with 139 local branches | 12.3 s | 3.7 s | + +- **The git ignore probe runs beside the crawl.** Every call on a git checkout asked `git ls-files --others --ignored` + before the crawl could start. The crawl now walks with the previous call's answer for that root while git computes + this call's, and the two are compared byte for byte before anything is parsed; when they differ, the crawl and the + cache load are redone on git's answer. Every answer is still git's own answer for this call. Under `--no-cache` the + probe runs before the crawl, as it did. +- **No shell is started for a markitdown that cannot run.** The doc pass started `/bin/sh -c markitdown …` for every + pdf/docx/pptx/xlsx on every call, and on a machine without markitdown the shell's "not found" was never cached. The + PATH is now searched the way the shell searches it, once per ingest, so installing markitdown is still noticed on + the next call. +- **The resolver reads dense arrays for a name with many definitions.** A module-scope call has had a caller node since + 0.6.2, so the test scripts' top-level `ok "…"` calls (about 650 shell definitions of `ok`) reach the candidate walks. + Those walks now read per-symbol arrays instead of whole symbols. +- **`--whereis` reads each git listing in blocks and lists each distinct tip once**, the listings in parallel, and + scans each blob once however many branches share it. + +### Fixed — `--whereis` refuses when it could not read the blobs, instead of answering `hits="0"` + +`--whereis` reads every blob through one `git cat-file --batch`, whose sha list is a file under the cache directory. +When that directory was unusable (for example `TMPDIR` naming a directory that does not exist), the batch never +started and the answer said `on-head="0" hits="0"`, with a near-miss note suggesting the name it had been given, on +a repository whose HEAD defines it. The CLI now exits 1 and names the cause on stderr, and the MCP `whereis` tool +answers error -32603. A genuine zero over a working cache directory is answered as before. +`--stray-content` and `--abi` read blobs the same way and still answer without saying the read failed. That is +known and not fixed here. + ### Changed — a Ruby call to self, or on an instance the code builds, answers from its own side: an instance never reaches `def self.m` The entry below left one floor open, (i). A call to self, or on a receiver the code builds, read a class's defs from both diff --git a/README.md b/README.md index f7523c566..152a73ba6 100644 --- a/README.md +++ b/README.md @@ -149,11 +149,15 @@ contributors named there; this release is largely theirs. alt="No API key. No embeddings. No index server. No daemon." width="470">

-One process, no server — indexes this repository in 0.25 s using 6.6 MB, against 46.8 s and 391 MB for the graph-database MCP server it was measured against; warm queries answer in 197 ms to its 1,082 ms +One process, no server — indexes this repository in 0.25 s using 6.6 MB, against 46.8 s and 391 MB for the graph-database MCP server it was measured against; warm queries answered in a median 197 ms to its 1,082 ms (2026-08-22) Measured on 48 matched questions across django, webpack and this repository. Across all three, ripwire indexes in **0.25–0.45 s** and **6.6–16.5 MB** against that server's **23–52 s** and -**391–623 MB**. The full method, the wins named one by one and the losses included, is in +**391–623 MB**. The 197 ms is the median over all 48 questions and all three repositories, measured on +2026-08-22 with a dev build at `7eb638e`; it is not a per-call figure for this repository today. On +2026-10-08 a warm CLI call on this repository took a median **342 ms** (Release build `f0845375`, five +verbs, 10-core arm64 Mac; [`bench/warmcli.sh`](bench/warmcli.sh) reproduces it and prints its +environment). The other server's figures were not re-measured. The full method, the wins named one by one and the losses included, is in [Against the leading graph-database code-context MCP server](#against-the-leading-graph-database-code-context-mcp-server) and [`docs/EVALS.md`](docs/EVALS.md).
@@ -434,7 +438,8 @@ spending **~77K tokens against its ~486K** for the whole sweep. The 48 questions span symbol lookup, conceptual search, blast radius, and one-call task orientation. ripwire indexes the same three repositories in 0.25–0.45 s and 6.6–16.5 MB, against that server's -23–52 s and 391–623 MB, and answers a warm query in a median 197 ms against its 1,082 ms. Its seven +23–52 s and 391–623 MB, and answered a warm query in a median 197 ms against its 1,082 ms (2026-08-22, dev +build `7eb638e`; today's warm CLI per-call cost is `bench/warmcli.sh`'s, above). Its seven wins are real and named one by one in the method. Both arms warm with a pre-built index, median of 3 timed calls, stdout to a file rather than a pipe. diff --git a/bench/warmcli.sh b/bench/warmcli.sh new file mode 100755 index 000000000..f1f78d21b --- /dev/null +++ b/bench/warmcli.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# warmcli.sh — the warm CLI per-call figure, reproducibly: what one `ripwire ` costs once the cache is warm. +# +# bench/warmcli.sh [ROOT] # ROOT defaults to this repository's root +# RIPWIRE_BIN=build_rel/ripwire RUNS=9 bench/warmcli.sh +# +# A FIXED argv list (below; never derived from the corpus, so two runs time the same calls), each run against a cache +# directory made fresh for this invocation (TMPDIR, so no earlier binary's blob is read), two untimed warm-up calls per +# argv, then RUNS timed calls (default 5). Prints the environment first — binary --version (its built_from= names the +# build), git --version, the root, its file count, the machine and its load — then one row per argv (median / min / +# max wall ms, exit code, stdout bytes) and the median of the per-argv medians. Wall time includes process start and +# every git child the call makes, which is what an agent calling the CLI pays. Builds nothing and writes nothing +# outside its temporary cache dir. A Release build is the flavour users run; a plain dev build is slower. +# +# Exit 0 = measured; 1 = a timed call failed (non-zero exit), so its time is not a figure; 2 = usage (no binary). +set -u +HERE="$( cd "$( dirname "$0" )/.." && pwd )" +BIN="${RIPWIRE_BIN:-$HERE/build/ripwire}" +ROOT_ARG="${1:-$HERE}" +RUNS="${RUNS:-5}" +[ -x "$BIN" ] || { echo "warmcli: no ripwire binary at $BIN (set RIPWIRE_BIN)" >&2; exit 2; } +case "$RUNS" in ''|*[!0-9]*|0) echo "warmcli: RUNS must be a positive integer" >&2; exit 2 ;; esac +command -v python3 >/dev/null 2>&1 || { echo "warmcli: python3 is needed for the timer" >&2; exit 2; } + +CACHE="$( mktemp -d "${TMPDIR:-/tmp}/ripwire-warmcli.XXXXXX" )"; trap 'rm -rf "$CACHE"' EXIT +echo "# warmcli $( date -u +%Y-%m-%dT%H:%M:%SZ )" +echo "# bin: $BIN — $( "$BIN" --version 2>&1 | head -1 )" +echo "# git: $( git --version 2>/dev/null || echo absent )" +echo "# root: $ROOT_ARG ($( git -C "$ROOT_ARG" ls-files 2>/dev/null | wc -l | tr -d ' ' ) tracked files)" +echo "# machine: $( uname -sm ), $( sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null ) cores, load $( sysctl -n vm.loadavg 2>/dev/null || cut -d' ' -f1-3 /proc/loadavg 2>/dev/null )" +echo "# runs: $RUNS timed after 2 warm-up calls per argv; cache dir fresh for this invocation" + +python3 -I - "$BIN" "$ROOT_ARG" "$CACHE" "$RUNS" <<'PY' +import os, statistics, subprocess, sys, time +bin_, root, cache, runs = sys.argv[ 1 ], sys.argv[ 2 ], sys.argv[ 3 ], int( sys.argv[ 4 ] ) +ARGV = [ [ "--callers=rankGraphTeleport" ], + [ "--callees=rankGraphTeleport" ], + [ "--at=src/graph.h:100" ], + [ "--impact=rankGraphTeleport" ], + [ "--for=rankGraphTeleport" ] ] +env = dict( os.environ ); env[ "TMPDIR" ] = cache.rstrip( "/" ) + "/" +print( "argv\tmedian_ms\tmin_ms\tmax_ms\trc\tout_bytes" ) +medians, failed = [], False +for argv in ARGV: + cmd = [ bin_, root ] + argv + for _ in range( 2 ): + subprocess.run( cmd, stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL, env = env ) + ts, rc, nbytes = [], 0, 0 + for _ in range( runs ): + t = time.perf_counter() + p = subprocess.run( cmd, stdout = subprocess.PIPE, stderr = subprocess.DEVNULL, env = env ) + ts.append( ( time.perf_counter() - t ) * 1000.0 ) + rc, nbytes = p.returncode, len( p.stdout ) + failed = failed or p.returncode != 0 + medians.append( statistics.median( ts ) ) + print( f"{' '.join( argv )}\t{statistics.median( ts ):.0f}\t{min( ts ):.0f}\t{max( ts ):.0f}\t{rc}\t{nbytes}" ) +print( f"# median of the per-argv medians: {statistics.median( medians ):.0f} ms" ) +sys.exit( 1 if failed else 0 ) +PY diff --git a/docs/EVALS.md b/docs/EVALS.md index 3326be7ac..ad068adc0 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -290,7 +290,10 @@ bundles in bytes. The figures above use short relative roots. The whole sweep wa 118-character absolute root as a sensitivity check: the overall share moves 0.159× → 0.163× and the four class ratios to 1.44× / 1.24× / 0.40× / 0.06×, moving no verdict. -**Latency and index cost.** Median warm wall over the 48: ripwire **197 ms**, GitNexus **1,082 ms**. +**Latency and index cost.** Median warm wall over the 48: ripwire **197 ms**, GitNexus **1,082 ms**. These are +the 2026-08-22 figures (ripwire: a dev build at 7eb638e), kept as recorded. They are not today's per-call cost: on +2026-10-08 a warm CLI call on this repository took a median 342 ms (Release build f0845375, `bench/warmcli.sh`, +which prints its argv, runs and environment); GitNexus was not re-measured. Both warm with a pre-built index, so the multiple is an apples-to-apples cache state — but it excludes the index build, which is not comparable at all: From 0903ffef71ae150b1c0e84668d5fb2459e720642 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 08:35:23 -0400 Subject: [PATCH 030/170] refactor: the lane's quality-delta follow-ups (shared hex16, whereis helpers, per-doc bridge check) --quality-delta against the merge base flagged three things this lane made worse; each is fixed rather than acked: - quality::hex16 is the one spelling of a 64-bit key as 16 hex digits. ackMapKey now calls it (same bytes) and so does the ignore-probe sidecar name, which had cloned the formatter (and a fixed buffer; fixedbufsweep's table and counts are back to their base values). - computeWhereis' new tip listing is listDistinctTips (TipListings: the distinct tips, their rows, and whether each listing is whole) and its per-site row stamping is stampBlobRows, so the function's complexity returns to its base value. - docTextViaBridgeCache keeps its base signature: the markitdown reachability check runs per bridge doc, just before the popen it replaces (a PATH walk of stats, still never memoized). No output changes. Co-Authored-By: Claude Opus 5.5 --- src/crossref.h | 82 +++++++++++++++++++++++++++++-------------- src/docparse.h | 4 +-- src/ingest_crawl.h | 4 +-- src/ingest_docpass.h | 19 ++++------ src/quality.h | 10 ++++-- test/fixedbufsweep.sh | 3 +- 6 files changed, 75 insertions(+), 47 deletions(-) diff --git a/src/crossref.h b/src/crossref.h index e996162b4..750862901 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -2231,6 +2231,58 @@ inline bool whereHitBefore( const WhereHit& a, const WhereHit& b ) // such a row is PRINTED — a doc row claiming kind="def" is still a doc row claiming kind="def", now below the // code. Nothing is dropped and no row's attributes change; a reader who wants the doc evidence still gets // every row of it. +// Every ref's tree listing, each DISTINCT tip listed once (HEAD and its own branch share one; so do branches left at the +// same commit), the listings run across the crossref git pool: a listing is a pure function of an immutable commit, so a +// tip is never listed twice, and the order the listings finish in reaches nothing — each lands in its tip's own slot and +// computeWhereis assembles the sites in ref order, exactly as its serial loop did. A listing is WHOLE when its worker +// ran and it holds a row: a zero-row listing could be a failed `git ls-tree`, and either forfeits complete= (T1). +struct TipListings +{ + std::vector distinctTips; // sorted, unique + std::vector> rowsByTip; // parallel to distinctTips + std::vector whole; // parallel to distinctTips + + std::pair&, bool> of( const std::string& tip ) const + { + const auto at = std::lower_bound( distinctTips.begin(), distinctTips.end(), tip ); + ASSUME( at != distinctTips.end() && *at == tip, "whereis: every ref's tip was listed by listDistinctTips" ); + const std::size_t tipIndex = std::size_t( at - distinctTips.begin() ); + return { rowsByTip[ tipIndex ], whole[ tipIndex ] != 0 }; + } +}; + +inline TipListings listDistinctTips( const std::string& root, const std::vector& refs ) +{ + TipListings out; + out.distinctTips.reserve( refs.size() ); + for( const RefInfo& ref : refs ) + { + out.distinctTips.push_back( ref.tip ); + } + std::sort( out.distinctTips.begin(), out.distinctTips.end() ); + out.distinctTips.erase( std::unique( out.distinctTips.begin(), out.distinctTips.end() ), out.distinctTips.end() ); + out.rowsByTip.resize( out.distinctTips.size() ); + ParallelSweep sweep; + parallelIndexed( out.distinctTips.size(), [ & ]( std::size_t tipIndex ) { out.rowsByTip[ tipIndex ] = lsTree( root, out.distinctTips[ tipIndex ] ); }, sweep ); + out.whole.resize( out.distinctTips.size() ); + for( std::size_t tipIndex = 0; tipIndex < out.distinctTips.size(); ++tipIndex ) + { + out.whole[ tipIndex ] = sweep.isDone( tipIndex ) && !out.rowsByTip[ tipIndex ].empty() ? 1 : 0; + } + ENSURES( out.rowsByTip.size() == out.distinctTips.size() && out.whole.size() == out.distinctTips.size(), "one listing and one verdict per distinct tip" ); + return out; +} + +// One blob's rows (scanned once, with no site's label), stamped with one site's ref and path, in line order: the rows +// scanBlobForSymbol makes for that site, since a row's content is the blob's and its label is the site's. +inline void stampBlobRows( const std::vector& blobRows, const RefInfo& ref, const std::string& path, std::vector& out ) +{ + for( const WhereHit& row : blobRows ) + { + out.push_back( WhereHit{ ref.name, ref.tip, ref.date, path, row.line, row.isDef, row.text, false } ); + } +} + inline WhereResult computeWhereis( const std::string& root, std::string_view sym, std::string_view filter, WhereisEvidence evidence = {} ) { @@ -2264,29 +2316,11 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym struct Site { std::uint32_t refIndex; std::string path; bool replaced; }; bool anyEmptyTree = false; // T1: a zero-row ls-tree could be a FAILED listing — it forfeits complete= gtl::btree_map> sites; - // One `git ls-tree -r` per DISTINCT tip (HEAD and its own branch share one; so do branches left at the same - // commit), run across the crossref git pool: a listing is a pure function of an immutable commit, so a tip is - // never listed twice and the order the listings finish in reaches nothing — each lands in its tip's own slot and - // the sites are assembled below in ref order, exactly as the serial loop assembled them. A slot whose worker - // never ran reads as an empty listing, which forfeits complete= like any other suspect tree (T1). - std::vector distinctTips; - distinctTips.reserve( refs.size() ); - for( const RefInfo& ref : refs ) - { - distinctTips.push_back( ref.tip ); - } - std::sort( distinctTips.begin(), distinctTips.end() ); - distinctTips.erase( std::unique( distinctTips.begin(), distinctTips.end() ), distinctTips.end() ); - std::vector> rowsByTip( distinctTips.size() ); - ParallelSweep listingSweep; - parallelIndexed( distinctTips.size(), [ & ]( std::size_t tipIndex ) { rowsByTip[ tipIndex ] = lsTree( root, distinctTips[ tipIndex ] ); }, listingSweep ); + const TipListings listings = listDistinctTips( root, refs ); for( std::uint32_t i = 0; i < refs.size(); ++i ) { - const auto tipAt = std::lower_bound( distinctTips.begin(), distinctTips.end(), refs[ i ].tip ); - ASSUME( tipAt != distinctTips.end() && *tipAt == refs[ i ].tip, "whereis: every ref's tip was put in distinctTips above" ); - const std::size_t tipIndex = std::size_t( tipAt - distinctTips.begin() ); - const std::vector& rows = rowsByTip[ tipIndex ]; - if( rows.empty() || !listingSweep.isDone( tipIndex ) ) + const auto [ rows, whole ] = listings.of( refs[ i ].tip ); + if( !whole ) { anyEmptyTree = true; } @@ -2326,11 +2360,7 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym result.onHead = result.onHead || ( head && !s.replaced ); if( !s.replaced ) { - const RefInfo& ref = refs[ s.refIndex ]; - for( const WhereHit& row : blobRows ) - { - result.hits.push_back( WhereHit{ ref.name, ref.tip, ref.date, s.path, row.line, row.isDef, row.text, false } ); - } + stampBlobRows( blobRows, refs[ s.refIndex ], s.path, result.hits ); } } }, &blobStats ); // A stream that never started, or died before serving every blob, leaves hits= and on-head= unmeasured: a zero from diff --git a/src/docparse.h b/src/docparse.h index b7313e03a..8bd9f5e11 100644 --- a/src/docparse.h +++ b/src/docparse.h @@ -621,8 +621,8 @@ inline std::string shellQuote( const std::string& s ) // every warm run, for an answer known before the fork (cli-floor 2026-10-08: ~25 ms of a warm call on this repository, // two present/ decks). This asks the same question the shell would, with no process: os::which walks PATH exactly as // sh does (an empty entry is the current directory; a directory or a non-executable file named markitdown is not a -// program). It is asked once per doc post-pass, never memoized across ingests, so installing markitdown under a -// running MCP server is seen on its next ingest. It answers TRUE whenever it cannot rule the shell out — PATH unset +// program). It is asked per bridge doc, just before the popen it replaces, and never memoized, so installing +// markitdown under a running MCP server is seen on its next ingest. It answers TRUE whenever it cannot rule the shell out — PATH unset // (sh then searches its own default path), an executable `./markitdown` (a Windows shell searches the current // directory first), or an exported shell function of that name — because a false "absent" would drop a Section the // shell would have produced, while a false "present" only costs the popen it always cost. Gated by diff --git a/src/ingest_crawl.h b/src/ingest_crawl.h index 968ef3e28..669b50f63 100644 --- a/src/ingest_crawl.h +++ b/src/ingest_crawl.h @@ -1585,9 +1585,7 @@ class IgnoreProbeSpeculation static std::string sidecarName( const std::string& rootReal ) { - char sidecarFile[ 64 ]; - rw::formatTo( sidecarFile, sizeof( sidecarFile ), "ripwire-ignoreprobe-{:016x}.bin", static_cast( fnv1a64( rootReal ) ) ); - return sidecarFile; + return "ripwire-ignoreprobe-" + quality::hex16( fnv1a64( rootReal ) ) + ".bin"; } // The previous call's answer for this root, or nothing. The header names the root in full, so two roots whose names diff --git a/src/ingest_docpass.h b/src/ingest_docpass.h index 8ca45d50c..893c3cd39 100644 --- a/src/ingest_docpass.h +++ b/src/ingest_docpass.h @@ -64,11 +64,11 @@ inline void publishDocBridgeBlob( const std::string& bridgeBlobPath, std::uint32 // cacheEnabled=false (--no-cache) bypasses the sidecar entirely. tmpKey keeps concurrent workers' // unpublished temp files distinct; the publish itself is a whole-file rename, so a concurrent // reader sees every byte or none. -// `bridgeMayRun` is docparse::markitdownMayRun(), asked once by the post-pass: false means the shell could not start -// markitdown, so a bridge doc whose bytes have no cached extraction is "" without the popen — the answer the popen -// gave (rc 127), minus one shell start per doc per run. The cache is still consulted first either way, so a blob -// extracted while markitdown was installed keeps answering after it is removed, exactly as before. -inline std::string docTextViaBridgeCache( const std::string& path, const std::string& ext, bool cacheEnabled, std::uint32_t tmpKey, bool bridgeMayRun ) +// A bridge doc whose bytes have no cached extraction asks docparse::markitdownMayRun() before the popen: false means +// the shell could not start markitdown, so the answer is "" without the popen — what the popen answered (rc 127), +// minus one shell start per doc per run. The cache is still consulted first, so a blob extracted while markitdown +// was installed keeps answering after it is removed, exactly as before. +inline std::string docTextViaBridgeCache( const std::string& path, const std::string& ext, bool cacheEnabled, std::uint32_t tmpKey ) { EXPECTS( !path.empty(), "doc post-pass: every crawled doc path is a non-empty spelling" ); std::string text; @@ -85,7 +85,7 @@ inline std::string docTextViaBridgeCache( const std::string& path, const std::st text = docparse::detail::readWholeFile( bridgeBlobPath ).value_or( std::string() ); // miss ⇒ text stays empty } } - if( text.empty() && viaBridge && !bridgeMayRun ) + if( text.empty() && viaBridge && !docparse::markitdownMayRun() ) { return text; // no cached extraction, and no markitdown the shell could start: the popen's own answer, "" } @@ -130,11 +130,6 @@ inline void runDocPostPass( IngestResult& result, std::vector& rawDefs, // 2) extract in parallel, storing each result at its OWN slot (no cross-thread sharing of a slot → // order-independent). A per-doc `hasText` gate distinguishes "not extractable" (skip) from empty. const std::size_t ndocs = docIds.size(); - // Asked ONCE per post-pass, and only when a bridge doc exists (a PATH walk of stats, no process): every worker - // reads the same answer, and the next ingest asks again, so a markitdown installed meanwhile is seen. - const bool anyBridgeDoc = std::any_of( docIds.begin(), docIds.end(), [ & ]( std::uint32_t fid ) - { return docparse::docKindOf( lowerExtensionOf( result.files[ fid ] ) ) == docparse::DocKind::Markitdown; } ); - const bool bridgeMayRun = anyBridgeDoc && docparse::markitdownMayRun(); std::vector docTextOut( ndocs ); std::vector docHasText( ndocs, 0 ); std::vector docLex( ndocs ); // B0.2: per-doc Section stats (rich only), own slot per worker @@ -173,7 +168,7 @@ inline void runDocPostPass( IngestResult& result, std::vector& rawDefs, const std::uint32_t fid = docIds[ di ]; const std::string ext = lowerExtensionOf( result.files[ fid ] ); - std::string text = docTextViaBridgeCache( result.files[ fid ], ext, cacheEnabled, fid, bridgeMayRun ); + std::string text = docTextViaBridgeCache( result.files[ fid ], ext, cacheEnabled, fid ); if( !text.empty() ) { if( captureValueUses ) diff --git a/src/quality.h b/src/quality.h index 3de7c2fae..e45b5331c 100644 --- a/src/quality.h +++ b/src/quality.h @@ -6166,11 +6166,17 @@ inline std::optional rescoreAckRecord( const AckRecord& r, std::optional( key ) ); - return kind + " " + hex; + return hex; +} + +inline std::string ackMapKey( const std::string& kind, std::uint64_t key ) +{ + return kind + " " + hex16( key ); } // ─── THE LEGACY-ACK BACKFILL — what a row written before provenance existed CAN still get back ───────── diff --git a/test/fixedbufsweep.sh b/test/fixedbufsweep.sh index 1ecf118d7..ddf94a920 100755 --- a/test/fixedbufsweep.sh +++ b/test/fixedbufsweep.sh @@ -233,7 +233,6 @@ NUMERIC_ONLY = { ( "src/infra/profileScope.h", "buf" ): 5, ( "src/ingest_astquery.h", "suffix" ): 1, ( "src/ingest_docpass.h", "blobName" ): 1, - ( "src/ingest_crawl.h", "sidecarFile" ): 1, # cli-perf-068: IgnoreProbeSpeculation::sidecarName — "ripwire-ignoreprobe-" + 16 hex digits + ".bin" = 40 B against 63 usable + NUL. One uint64, nothing escaped. ( "src/lsp.h", "hdr" ): 1, # 2026-09-16 (--lsp Phase 1): lspWriteMessage's "Content-Length: {}\r\n\r\n" — ONE size_t, 20 digits worst case: 40 B against 63 usable + NUL. No %s, nothing escaped. ( "src/lsp.h", "buf" ): 1, # 2026-09-16 (--lsp Phase 1): lspRangeJson's range object — 61 fixed chars + FOUR uint32 line/character numbers, 10 digits worst case: 101 B against 127 usable + NUL. No %s, nothing escaped; the strings around it (URI, name) are composed on std::string by the callers, never through this buffer. ( "src/main.cpp", "hdr" ): 1, @@ -595,7 +594,7 @@ if not bad: # 238 -> 239 calls/sites, 104 -> 105 rows). noteCacheReject's new `char detail[192]` names both version # numbers on a refused cache blob; one formatTo of a literal and two std::uint32_t, rowed 'not-markup' # (a stderr notice). Re-derived on the train 20 merged tree; no other train lane moves the population. -EXPECTED = { "mentions": 352, "calls": 242, "sites": 242, "rows": 106, "widthforms": 0 } +EXPECTED = { "mentions": 351, "calls": 241, "sites": 241, "rows": 105, "widthforms": 0 } # 2026-09-30 (#325 ruby_bases_unscoped=): +2 calls/+2 mentions/+2 sites, rows unchanged — graphGaugeAttrXml/Json (graphlegend.h) each format the absent-at-zero Ruby gauge into the SAME local buf[160]: one size_t, no string argument (21 + 20 digits worst case). # 2026-09-04 (capture-audit L6, H9): +1 call/+1 mention, sites/rows UNCHANGED — re-read, not # re-counted. packConnect gained ONE snprintf into a new `char connectCeiling[32]` for the From f63b6bb1c6b991410b8508e2bc7e9b21b5995dc8 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 08:37:43 -0400 Subject: [PATCH 031/170] refactor: blobShardHex reuses hex16; the ignore-probe sidecar name is spelled inline quality-delta's two remaining duplication rows: blobShardHex's own "{:02x}" formatter (now the last two digits of hex16, the same low byte) and a three-term string sum shaped like os::path_prepend_hint. fixedbufsweep: one fewer quality.h hex buffer. No output changes. Co-Authored-By: Claude Opus 5.5 --- src/ingest_crawl.h | 7 +------ src/quality.h | 21 ++++++++++----------- test/fixedbufsweep.sh | 4 ++-- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/ingest_crawl.h b/src/ingest_crawl.h index 669b50f63..fc069d8ee 100644 --- a/src/ingest_crawl.h +++ b/src/ingest_crawl.h @@ -1550,7 +1550,7 @@ class IgnoreProbeSpeculation fresh_ = runGitIgnoreProbe( rootDir ); // no sidecar to speculate from, or no git child to overlap: as before return parseGitIgnoreProbe( *fresh_ ); } - sidecarPath_ = quality::resolveCacheBlobPath( quality::cacheDirLadder(), sidecarName( rootReal ) ); + sidecarPath_ = quality::resolveCacheBlobPath( quality::cacheDirLadder(), "ripwire-ignoreprobe-" + quality::hex16( fnv1a64( rootReal ) ) + ".bin" ); sidecarHeader_ = std::string( kSidecarMagic ) + rootReal + '\0'; pending_ = std::async( std::launch::async, [ root = std::string( rootDir ) ] { return runGitIgnoreProbe( root.c_str() ); } ); speculation_ = loadSidecar(); @@ -1583,11 +1583,6 @@ class IgnoreProbeSpeculation private: static constexpr std::string_view kSidecarMagic = "ripwire-ignore-speculation/1\n"; - static std::string sidecarName( const std::string& rootReal ) - { - return "ripwire-ignoreprobe-" + quality::hex16( fnv1a64( rootReal ) ) + ".bin"; - } - // The previous call's answer for this root, or nothing. The header names the root in full, so two roots whose names // hash alike never read each other's answer; anything unreadable or misshapen is simply no speculation. std::optional loadSidecar() const diff --git a/src/quality.h b/src/quality.h index e45b5331c..cadc13d57 100644 --- a/src/quality.h +++ b/src/quality.h @@ -2552,11 +2552,18 @@ inline std::string headSnapExclHex( const std::vector& excludes, st // only WRITTEN into its shard the next time it is (re)computed. A blob absent from both locations is a clean // miss, created fresh, in its shard. `evictOldCacheFamily` below sweeps both layouts, so an old flat blob // still ages out on schedule even if its key is never rewritten. +// A 64-bit key spelled as its 16 lowercase hex digits — the ack ledger's key (ackMapKey), the shard below, and the +// ingest's per-root sidecar names. +inline std::string hex16( std::uint64_t key ) +{ + char hex[ 20 ]; + rw::formatTo( hex, sizeof( hex ), "{:016x}", static_cast( key ) ); + return hex; +} + inline std::string blobShardHex( std::string_view filename ) { - char hex[ 3 ]; - rw::formatTo( hex, sizeof( hex ), "{:02x}", static_cast( fnv1a64( filename ) & 0xff ) ); - return std::string( hex ); + return hex16( fnv1a64( filename ) ).substr( 14 ); // the low byte, two hex digits: "{:02x}" of ( hash & 0xff ) } inline std::string resolveCacheBlobPath( const std::string& dir, const std::string& filename ) @@ -6166,14 +6173,6 @@ inline std::optional rescoreAckRecord( const AckRecord& r, std::optional( key ) ); - return hex; -} - inline std::string ackMapKey( const std::string& kind, std::uint64_t key ) { return kind + " " + hex16( key ); diff --git a/test/fixedbufsweep.sh b/test/fixedbufsweep.sh index ddf94a920..aa031079b 100755 --- a/test/fixedbufsweep.sh +++ b/test/fixedbufsweep.sh @@ -254,7 +254,7 @@ NUMERIC_ONLY = { ( "src/partition.h", "nb" ): 1, ( "src/quality.h", "b" ): 1, ( "src/quality.h", "cidHex" ): 1, - ( "src/quality.h", "hex" ): 7, + ( "src/quality.h", "hex" ): 6, # cli-perf-068: blobShardHex and ackMapKey now share hex16 (one buffer for two) ( "src/quality.h", "name" ): 1, ( "src/recall.h", "scoreText" ): 1, ( "src/serialize.h", "...)" ): 1, @@ -594,7 +594,7 @@ if not bad: # 238 -> 239 calls/sites, 104 -> 105 rows). noteCacheReject's new `char detail[192]` names both version # numbers on a refused cache blob; one formatTo of a literal and two std::uint32_t, rowed 'not-markup' # (a stderr notice). Re-derived on the train 20 merged tree; no other train lane moves the population. -EXPECTED = { "mentions": 351, "calls": 241, "sites": 241, "rows": 105, "widthforms": 0 } +EXPECTED = { "mentions": 350, "calls": 240, "sites": 240, "rows": 105, "widthforms": 0 } # 2026-09-30 (#325 ruby_bases_unscoped=): +2 calls/+2 mentions/+2 sites, rows unchanged — graphGaugeAttrXml/Json (graphlegend.h) each format the absent-at-zero Ruby gauge into the SAME local buf[160]: one size_t, no string argument (21 + 20 digits worst case). # 2026-09-04 (capture-audit L6, H9): +1 call/+1 mention, sites/rows UNCHANGED — re-read, not # re-counted. packConnect gained ONE snprintf into a new `char connectCeiling[32]` for the From b66de31faf67a08823e9b171ed367568f418a2d6 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 08:38:26 -0400 Subject: [PATCH 032/170] chore(quality): ack probeIgnoreSet's two new parameters (cli-perf-068) The one deliberate contract change of the lane: a section-local function of the ingest translation unit with one caller (collectSources) gains the ignore-probe speculation and the canonical root its sidecar is keyed by. Acked with --ack-only=contract-change; nothing else. Co-Authored-By: Claude Opus 5.5 --- .ripwire_quality_acks | 1 + 1 file changed, 1 insertion(+) diff --git a/.ripwire_quality_acks b/.ripwire_quality_acks index fff1468da..f32280325 100644 --- a/.ripwire_quality_acks +++ b/.ripwire_quality_acks @@ -7,6 +7,7 @@ ack api-surface 060a064b6ffa7775 7 cid=605cbb1f768828e0 P2.2 register-macro dead ack api-surface 085e3d408c2c4a35 2 cid=55e7936512b0563d A3: TestRunnerIndex takes the run's crawl root so run= is spelled relative to root= (one absolute root per document, test/rootrelemitcheck.sh ARM 9). The parameter is the fix; every caller passes the root it already holds. ack api-surface 0a20676f246f053a 5 cid=d4f1ceec6378a259 t14-cleanup: heals stale ack-ledger identities (kParserVer moved since these rows were written) and acks this lane's own minor footprint — printSkillScanArtifact's +1 defaulted fullLegend param, packTaskBundleText's bodyless-placeholder clause, kHelpHead's --readability validation note, kCompactAttributeReadings' merge-scout anchoring= entry — all additive/self-contained, none foreign ack api-surface 0a3d16d6f3139408 10 cid=e2b9873df9888866 by=src/* answer-safe --edit-check window: the three contract-change rows are ONE defaulted paging pair (pageLimit/pageOffset, 0/0 = the verb's own default cap) plumbed through the ONE assembler and its two front doors — editCheckBundleText, editpreview::run, editCheckText — rather than a second capped emitter, because two emitters would drift and a page that drifted could drop the flagged caller that IS the answer. The complexity/verbosity growth in the assembler is the partition-preserving row loop (the window advances on UNFLAGGED rows only, so a flagged row and its sites_l= ride every page uncut) plus the in-band legend that says what pages and what never does; the churn rows are this lane's own footprint across cli.h/mcp*/editcheck*, none foreign. | prior: card A1 pre-apply preview: preview= is a DEFAULTED flag on the ONE edit-check assembler rather than a second emitter — two emitters could drift, and a preview that disagrees with the post-hoc answer is worth nothing (test/editpreviewcheck.sh compares the two documents byte-for-byte). The +20 LOC and the churn are the legend sentence that tells a reader the numbers describe bytes that were never written. +ack api-surface 0d64cc4708da0ab4 5 cid=104918b8416000b6 now=5 was=3 facet=contract-change p=src/ingest_crawl.h:1656 cli-perf-068: probeIgnoreSet is a section-local function of the ingest translation unit (ingest_crawl.h) with one caller, collectSources. It gains the ingest's IgnoreProbeSpeculation (the git ignore probe running beside the walk, checked against the fresh answer before the parse) and the canonical root the speculation's sidecar is keyed by; both are passed by collectSources, whose own new parameter is defaulted. No other caller exists to break. ack api-surface 0d6e1106ea5949d5 4 WAVE-2 close (2026-08-19), finding 3 of 3: the 76 remaining gating rows, ONE change. All of them are W2-E's root-relative p= landing (9beaa2c/fccea68/a271e6c/b3fe074 plus the f9108b7 correction), measured for the first time at WAVE granularity. The per-lane acks written during W2-E covered only the correction round's own diff (working-tree-vs-HEAD at that moment), so the original ~30-verb landing was never QD-acked; this ack closes that gap rather than re-accepting anything. Verified by reading the whole 20cdc04..860291c src/main.cpp diff line by line: 282 of 448 added lines match the root-relative predicate directly and every one of the remaining 127 is an existing std::printf rewritten from ing.files[...] to the root-relative rp local, plus four extracted emit helpers (computeDirModules, printJsonSymbolRows, writeOversizeRows, writeDropRows). No unrelated logic rides in. By kind: api-surface 28 = the +1 rootArg/rootPrefix parameter on the emitters that must now be TOLD their root (writeAbiCheck/Ref/Struct, emitColumnar*, packBodies/Deps/Lego/Outline/Signatures[Json], serialize[Json], writeLayout*, packConnect, buildD1Row and the report writers) - defaulted wherever a caller could stay unchanged. complexity 31 and verbosity 16 = the single-root-condition ternary and its guarded root= clause applied per emitter, with no new nesting level and no new control flow beyond that one conditional; the large absolute numbers (runStructureText 207->231, runLint 313->323, runMaintenanceViews 174->190, runCallHierarchy 71->80) are pre-existing dispatcher size the wave adds to, not creates - decomposing them is its own round and is recorded as a wave-2 follow-up. params 1 = writeNonLocalStateReport 4->6, the same contract. The three sibling lanes are individually clean: --quality-delta at f6ec56d..1732fd8 (W2-J), 1732fd8..9a41c74 (W2-K) and 9a41c74..20cdc04 (W2-F) each report gating=0. Full suite green at this head: gates=429 pass=427 skip=2 fail=0, ASan+LSan clean, byte-deterministic, xmllint clean. churn= is unavailable in ref-pair mode by construction (both trees materialized out of the repo), so short-horizon-churn is silent here and that silence is not evidence. ack api-surface 1039e3c8e0fc3667 4 cid=4efcfe9cb7f739a2 M12 (capture-audit L9): path-spelling fixes — collectUseSites gained a root parameter (default-valued, back-compat) to root-relativize in_id=; runVerify grew from adding root=/verPathRel/the multi-root roots table it never had; short-horizon-churn rows are every function this finding's fix touched this session. ack api-surface 105c48e20c80c896 3 cid=720fab31ea99ebde A2: unmeasuredHintNote gained the AbsHintFrame parameter one commit after this lane introduced it (4db6fb3). It is a header-inline helper in namespace mcpedit with exactly one caller, resolveOneForEdit, in the same file; no consumer outside this lane ever saw the 2-arg form. The widening is what makes the never-parsed disclosure and the symbol scan agree about which files a hint names -- two copies of that rule is the defect this replaces. From 9fd14aa4f3c53c46bbcaef0fbd389f6ba0d6f337 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 08:56:23 -0400 Subject: [PATCH 033/170] fix(ingest): the ignore-probe sidecar carries the house root key; gate style fixes test/evictioncheck.sh (k)/(l): every cache family of one root must carry the ONE root key (quality::cacheRootKeyHex, realpath-normalized), or the eviction sweep cannot pin it with the rest. The sidecar was named by a plain fnv1a64 of the canonical root; it now uses cacheRootKeyHex (the header still names the root, so two roots never read each other's). test/gateexitcheck.sh (G2): gitignorecheck's arm 14 reported two verdicts through a one-line `&& ok || no`; now if/else. test/gitenvhermeticcheck.sh (D): crossrefdegradecheck builds a repo, so it now sources test/lib/clean-env.sh. Co-Authored-By: Claude Opus 5.5 --- src/ingest_crawl.h | 4 +++- test/crossrefdegradecheck.sh | 1 + test/gitignorecheck.sh | 7 ++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ingest_crawl.h b/src/ingest_crawl.h index fc069d8ee..2a7070f69 100644 --- a/src/ingest_crawl.h +++ b/src/ingest_crawl.h @@ -1550,7 +1550,9 @@ class IgnoreProbeSpeculation fresh_ = runGitIgnoreProbe( rootDir ); // no sidecar to speculate from, or no git child to overlap: as before return parseGitIgnoreProbe( *fresh_ ); } - sidecarPath_ = quality::resolveCacheBlobPath( quality::cacheDirLadder(), "ripwire-ignoreprobe-" + quality::hex16( fnv1a64( rootReal ) ) + ".bin" ); + // Named by the house root key (quality::cacheRootKeyHex, realpath-normalized), so the eviction sweep pins this + // file with every other family of the root (test/evictioncheck.sh (k)/(l)); the header still names the root. + sidecarPath_ = quality::resolveCacheBlobPath( quality::cacheDirLadder(), "ripwire-ignoreprobe-" + quality::cacheRootKeyHex( rootDir ) + ".bin" ); sidecarHeader_ = std::string( kSidecarMagic ) + rootReal + '\0'; pending_ = std::async( std::launch::async, [ root = std::string( rootDir ) ] { return runGitIgnoreProbe( root.c_str() ); } ); speculation_ = loadSidecar(); diff --git a/test/crossrefdegradecheck.sh b/test/crossrefdegradecheck.sh index a9a9d0a0c..32d2c2700 100755 --- a/test/crossrefdegradecheck.sh +++ b/test/crossrefdegradecheck.sh @@ -39,6 +39,7 @@ set -u ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +. "$ROOT/test/lib/clean-env.sh" BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" [ "${BIN#/}" = "$BIN" ] && BIN="$ROOT/$BIN" TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT diff --git a/test/gitignorecheck.sh b/test/gitignorecheck.sh index ffd85e6ce..4114b26dc 100755 --- a/test/gitignorecheck.sh +++ b/test/gitignorecheck.sh @@ -253,8 +253,9 @@ ref_run(){ spec_env "$BIN" "$SPEC" --top-k=400 --no-cache 2>/dev/null; } same_as_ref(){ spec_run >"$TMP/spec.out"; ref_run >"$TMP/ref.out" if ! cmp -s "$TMP/spec.out" "$TMP/ref.out"; then no "14$1: the cached (speculated) run differs from --no-cache"; return; fi - if [ "$3" = present ]; then grep -q "$2" "$TMP/spec.out" && ok "14$1: cached run == --no-cache, and $2 is indexed" || no "14$1: $2 should be indexed" - else grep -q "$2" "$TMP/spec.out" && no "14$1: $2 should be ignored now" || ok "14$1: cached run == --no-cache, and $2 is ignored"; fi + if grep -q "$2" "$TMP/spec.out"; then found=present; else found=absent; fi + if [ "$found" = "$3" ]; then ok "14$1: cached run == --no-cache, and $2 is $3" + else no "14$1: $2 should be $3, the cached run has it $found"; fi } spec_run >/dev/null if [ -n "$( find "$SC" -name 'ripwire-ignoreprobe-*.bin' 2>/dev/null | head -1 )" ]; then ok "14a: a cached run on a git root keeps the ignore answer for the next call's speculation" @@ -285,7 +286,7 @@ if ! grep -q 'stopped the crawl' "$TMP/spec.i.err"; then no "14i: the crawl:6 se elif [ "$rcSpecI" = "$rcRefI" ] && cmp -s "$TMP/spec.i" "$TMP/ref.i"; then ok "14i: a lost speculation under a crawl memory stop answers as --no-cache does (rc $rcSpecI)" else no "14i: a lost speculation under a crawl memory stop differs from --no-cache (rc $rcSpecI vs $rcRefI)"; fi spec_run >"$TMP/spec.a"; spec_run >"$TMP/spec.b" -cmp -s "$TMP/spec.a" "$TMP/spec.b" && ok "14h: two cached runs in a row are byte-identical" || no "14h: two cached runs in a row differ" +if cmp -s "$TMP/spec.a" "$TMP/spec.b"; then ok "14h: two cached runs in a row are byte-identical"; else no "14h: two cached runs in a row differ"; fi # ── 13. the flag is in --help (the deckcheck allowlist row for --no-ignore retires with it). "$BIN" --help=all 2>&1 | grep -q -- '--no-ignore' && ok "--no-ignore is documented in --help" \ From 945ff738542aea6d1122b1eb69edf9e89e004391 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 09:34:13 -0400 Subject: [PATCH 034/170] docs: re-pin the showcase --at seed after the resolve-loop views moved rankGraphTeleport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/graph.h's new dense-view block moved rankGraphTeleport from 7397 to 7430 (el 7458), so the published --at seed and the --callers=@ block follow it: rankGraph 7471, anchoredLexicalRank 8117, graph_ambiguous 10506, graph_unresolved 12928 — read from this tree, and the base binary reads the same gauges on it (the move adds references; the resolver answers them as before). Hand re-pin in the capture and docs/COMMANDS.md, as train 26a did. Co-Authored-By: Claude Opus 5.5 --- docs/COMMANDS.md | 6 +++--- docs/captures/COMMANDS_showcase_2026-09-14.md | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index a78cd60e9..3cb02ee8a 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -3350,11 +3350,11 @@ $ ./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:7397 +$ ./build/ripwire . --at=src/graph.h:7430 - - + + ``` diff --git a/docs/captures/COMMANDS_showcase_2026-09-14.md b/docs/captures/COMMANDS_showcase_2026-09-14.md index 65e566861..9b1fa3a4a 100644 --- a/docs/captures/COMMANDS_showcase_2026-09-14.md +++ b/docs/captures/COMMANDS_showcase_2026-09-14.md @@ -3921,29 +3921,29 @@ ripwire 0.6.1 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=f8d4 # navigate — seeds, claims, slices, shapes -## `./build/ripwire . --at=src/graph.h:7397` +## `./build/ripwire . --at=src/graph.h:7430` *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:7397` +## `./build/ripwire . --callers=@src/graph.h:7430` *The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* ````` - + - - + + From 620ef3226a6615fd0d61928c41b7042a7af7d2af Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 09:41:40 -0400 Subject: [PATCH 035/170] revert(docs): drop the README 197 ms edit and bench/warmcli.sh (owner: later) Owner direction: the stale README figure is answered by making the CLI faster, not by editing the README now. README.md and docs/EVALS.md are back to their base text, bench/warmcli.sh is removed, and the CHANGELOG table no longer names it. Every other change of the lane stands. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- README.md | 11 +++------ bench/warmcli.sh | 59 ------------------------------------------------ docs/EVALS.md | 5 +--- 4 files changed, 5 insertions(+), 72 deletions(-) delete mode 100755 bench/warmcli.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index bf464a702..36e4874ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Every answer is byte-identical to before except the one `--whereis` refusal belo | --- | ---: | ---: | | `--callers` on a 3,100-file snapshot of this repository, no `.git` | 319 ms | 269 ms | | the same snapshot as a one-commit git repository | 437 ms | 341 ms | -| five verbs on this repository's checkout (`bench/warmcli.sh`, median of medians) | 448 ms | 342 ms | +| five verbs on this repository's checkout (median of medians) | 448 ms | 342 ms | | `--whereis` on a checkout with 139 local branches | 12.3 s | 3.7 s | - **The git ignore probe runs beside the crawl.** Every call on a git checkout asked `git ls-files --others --ignored` diff --git a/README.md b/README.md index 152a73ba6..f7523c566 100644 --- a/README.md +++ b/README.md @@ -149,15 +149,11 @@ contributors named there; this release is largely theirs. alt="No API key. No embeddings. No index server. No daemon." width="470">

-One process, no server — indexes this repository in 0.25 s using 6.6 MB, against 46.8 s and 391 MB for the graph-database MCP server it was measured against; warm queries answered in a median 197 ms to its 1,082 ms (2026-08-22) +One process, no server — indexes this repository in 0.25 s using 6.6 MB, against 46.8 s and 391 MB for the graph-database MCP server it was measured against; warm queries answer in 197 ms to its 1,082 ms Measured on 48 matched questions across django, webpack and this repository. Across all three, ripwire indexes in **0.25–0.45 s** and **6.6–16.5 MB** against that server's **23–52 s** and -**391–623 MB**. The 197 ms is the median over all 48 questions and all three repositories, measured on -2026-08-22 with a dev build at `7eb638e`; it is not a per-call figure for this repository today. On -2026-10-08 a warm CLI call on this repository took a median **342 ms** (Release build `f0845375`, five -verbs, 10-core arm64 Mac; [`bench/warmcli.sh`](bench/warmcli.sh) reproduces it and prints its -environment). The other server's figures were not re-measured. The full method, the wins named one by one and the losses included, is in +**391–623 MB**. The full method, the wins named one by one and the losses included, is in [Against the leading graph-database code-context MCP server](#against-the-leading-graph-database-code-context-mcp-server) and [`docs/EVALS.md`](docs/EVALS.md).
@@ -438,8 +434,7 @@ spending **~77K tokens against its ~486K** for the whole sweep. The 48 questions span symbol lookup, conceptual search, blast radius, and one-call task orientation. ripwire indexes the same three repositories in 0.25–0.45 s and 6.6–16.5 MB, against that server's -23–52 s and 391–623 MB, and answered a warm query in a median 197 ms against its 1,082 ms (2026-08-22, dev -build `7eb638e`; today's warm CLI per-call cost is `bench/warmcli.sh`'s, above). Its seven +23–52 s and 391–623 MB, and answers a warm query in a median 197 ms against its 1,082 ms. Its seven wins are real and named one by one in the method. Both arms warm with a pre-built index, median of 3 timed calls, stdout to a file rather than a pipe. diff --git a/bench/warmcli.sh b/bench/warmcli.sh deleted file mode 100755 index f1f78d21b..000000000 --- a/bench/warmcli.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -# warmcli.sh — the warm CLI per-call figure, reproducibly: what one `ripwire ` costs once the cache is warm. -# -# bench/warmcli.sh [ROOT] # ROOT defaults to this repository's root -# RIPWIRE_BIN=build_rel/ripwire RUNS=9 bench/warmcli.sh -# -# A FIXED argv list (below; never derived from the corpus, so two runs time the same calls), each run against a cache -# directory made fresh for this invocation (TMPDIR, so no earlier binary's blob is read), two untimed warm-up calls per -# argv, then RUNS timed calls (default 5). Prints the environment first — binary --version (its built_from= names the -# build), git --version, the root, its file count, the machine and its load — then one row per argv (median / min / -# max wall ms, exit code, stdout bytes) and the median of the per-argv medians. Wall time includes process start and -# every git child the call makes, which is what an agent calling the CLI pays. Builds nothing and writes nothing -# outside its temporary cache dir. A Release build is the flavour users run; a plain dev build is slower. -# -# Exit 0 = measured; 1 = a timed call failed (non-zero exit), so its time is not a figure; 2 = usage (no binary). -set -u -HERE="$( cd "$( dirname "$0" )/.." && pwd )" -BIN="${RIPWIRE_BIN:-$HERE/build/ripwire}" -ROOT_ARG="${1:-$HERE}" -RUNS="${RUNS:-5}" -[ -x "$BIN" ] || { echo "warmcli: no ripwire binary at $BIN (set RIPWIRE_BIN)" >&2; exit 2; } -case "$RUNS" in ''|*[!0-9]*|0) echo "warmcli: RUNS must be a positive integer" >&2; exit 2 ;; esac -command -v python3 >/dev/null 2>&1 || { echo "warmcli: python3 is needed for the timer" >&2; exit 2; } - -CACHE="$( mktemp -d "${TMPDIR:-/tmp}/ripwire-warmcli.XXXXXX" )"; trap 'rm -rf "$CACHE"' EXIT -echo "# warmcli $( date -u +%Y-%m-%dT%H:%M:%SZ )" -echo "# bin: $BIN — $( "$BIN" --version 2>&1 | head -1 )" -echo "# git: $( git --version 2>/dev/null || echo absent )" -echo "# root: $ROOT_ARG ($( git -C "$ROOT_ARG" ls-files 2>/dev/null | wc -l | tr -d ' ' ) tracked files)" -echo "# machine: $( uname -sm ), $( sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null ) cores, load $( sysctl -n vm.loadavg 2>/dev/null || cut -d' ' -f1-3 /proc/loadavg 2>/dev/null )" -echo "# runs: $RUNS timed after 2 warm-up calls per argv; cache dir fresh for this invocation" - -python3 -I - "$BIN" "$ROOT_ARG" "$CACHE" "$RUNS" <<'PY' -import os, statistics, subprocess, sys, time -bin_, root, cache, runs = sys.argv[ 1 ], sys.argv[ 2 ], sys.argv[ 3 ], int( sys.argv[ 4 ] ) -ARGV = [ [ "--callers=rankGraphTeleport" ], - [ "--callees=rankGraphTeleport" ], - [ "--at=src/graph.h:100" ], - [ "--impact=rankGraphTeleport" ], - [ "--for=rankGraphTeleport" ] ] -env = dict( os.environ ); env[ "TMPDIR" ] = cache.rstrip( "/" ) + "/" -print( "argv\tmedian_ms\tmin_ms\tmax_ms\trc\tout_bytes" ) -medians, failed = [], False -for argv in ARGV: - cmd = [ bin_, root ] + argv - for _ in range( 2 ): - subprocess.run( cmd, stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL, env = env ) - ts, rc, nbytes = [], 0, 0 - for _ in range( runs ): - t = time.perf_counter() - p = subprocess.run( cmd, stdout = subprocess.PIPE, stderr = subprocess.DEVNULL, env = env ) - ts.append( ( time.perf_counter() - t ) * 1000.0 ) - rc, nbytes = p.returncode, len( p.stdout ) - failed = failed or p.returncode != 0 - medians.append( statistics.median( ts ) ) - print( f"{' '.join( argv )}\t{statistics.median( ts ):.0f}\t{min( ts ):.0f}\t{max( ts ):.0f}\t{rc}\t{nbytes}" ) -print( f"# median of the per-argv medians: {statistics.median( medians ):.0f} ms" ) -sys.exit( 1 if failed else 0 ) -PY diff --git a/docs/EVALS.md b/docs/EVALS.md index ad068adc0..3326be7ac 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -290,10 +290,7 @@ bundles in bytes. The figures above use short relative roots. The whole sweep wa 118-character absolute root as a sensitivity check: the overall share moves 0.159× → 0.163× and the four class ratios to 1.44× / 1.24× / 0.40× / 0.06×, moving no verdict. -**Latency and index cost.** Median warm wall over the 48: ripwire **197 ms**, GitNexus **1,082 ms**. These are -the 2026-08-22 figures (ripwire: a dev build at 7eb638e), kept as recorded. They are not today's per-call cost: on -2026-10-08 a warm CLI call on this repository took a median 342 ms (Release build f0845375, `bench/warmcli.sh`, -which prints its argv, runs and environment); GitNexus was not re-measured. +**Latency and index cost.** Median warm wall over the 48: ripwire **197 ms**, GitNexus **1,082 ms**. Both warm with a pre-built index, so the multiple is an apples-to-apples cache state — but it excludes the index build, which is not comparable at all: From 243d59c0225938a15859f576f5522b00e30af272 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 09:56:46 -0400 Subject: [PATCH 036/170] refactor(#358): Go import edges from one @import.path capture, not the shared walk branch Go's import_declaration (single, grouped, aliased, dot, blank, cgo "C") is matched by (import_declaration) @import.path in queries/go/tags.scm and read by a DepDialect::Go normaliser in src/ingest_importcap.h; captureIncludes no longer walks Go files. The clause reader is factored out as importClauseTarget so Swift's walk branch and the Go normaliser share one function. Dependency output is byte-identical; Include records are unchanged so no parser-version bump is owed. test/importcapcheck.sh gains the Go arms. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 18 ++++ queries/go/tags.scm | 14 +++ src/ingest_importcap.h | 49 ++++++++-- src/ingest_relations.h | 57 +++++++---- src/ingest_sidecap.h | 12 ++- test/importcapcheck.sh | 209 ++++++++++++++++++++++++++++++++++++++++- 6 files changed, 326 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d22e195d..2b6a6f958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1086,6 +1086,24 @@ false-confidence rate, next-clue usefulness) and the rule that answering comes b gains §9.2, which fixes the two roles a size limit may have (runaway guard, stair-step target) and the capped-versus-uncapped grading rule. Documentation only; no behaviour change. +### Changed — Go import edges come from the shared `@import.path` capture (#358, Go slice) + +Go is the second language onto the shared import-capture vocabulary that the C-family slice introduced. A Go +`import_declaration` — single or grouped, aliased (`import f "fmt"`), dot (`import . "x"`), blank (`import _ "x"`), and +cgo's `import "C"` — is now matched by `(import_declaration) @import.path` in `queries/go/tags.scm` and read by one +`DepDialect::Go` normaliser in `src/ingest_importcap.h`; `captureIncludes` no longer walks Go files. The captured node is +the whole declaration because that is the unit the Include record has always been built from: its target is the +declaration's clause (`importClauseTarget`, shared with Swift, which still reads it from the walk), so a grouped block +stays ONE record and `resolveGoImport` still takes the first quoted token. Splitting a group into one record per spec, +and moving the alias onto `@import.alias`, would add rows and are left for their own slice. + +**Output is byte-identical.** Dependency and resolve output over the Go fixtures and repos in the lane report is +unchanged, the go.mod `module` / `replace` resolution reads the same records, and a cache written by the previous binary +is accepted untouched, so no parser-version bump is owed. `test/importcapcheck.sh` gains six Go arms (embedded capture, +21 byte-exact rows covering every form above plus tab, semicolon, raw-string, UTF-8 cut and error-recovery spellings, no +widening for a function-body import, replace-based resolution with a bogus-target negative, the import-role use-site +ref, and the Go cache round-trip). + ### Changed — the C-family import edges come from one shared capture instead of two per-language extractors (#358, C-family slice) `src/ingest_relations.h` held about 21 per-language import extractors (`csharpUsingTarget`, `phpUseTarget`, diff --git a/queries/go/tags.scm b/queries/go/tags.scm index 3ecd51013..67003f12e 100644 --- a/queries/go/tags.scm +++ b/queries/go/tags.scm @@ -58,3 +58,17 @@ ; it parses as a plain call_expression and is captured by the pattern above. This widening was ; REJECTED on the evidence above rather than shipped, per this project's own rule for false-positive ; risk: reject with the evidence instead of shipping it. + +; ---- import capture (the shared vocabulary of issue #358) ---- +; ONE capture name: `@import.path`. For Go the captured node is the WHOLE `import_declaration`, because +; that is the unit the Include record has always been built from: `import "fmt"`, `import f "fmt"` +; (aliased), `import . "x"` (dot), `import _ "x"` (blank), cgo's `import "C"`, and a parenthesised group of +; any of those are each ONE declaration and ONE Include whose target is the declaration's clause +; (importClauseTarget in src/ingest_relations.h, applied by the DepDialect::Go normaliser in +; src/ingest_importcap.h). Capturing the inner `import_spec` / path string instead would split a group into +; one record per spec, which adds dependency edges — a behaviour change, not this slice. For the same +; reason there is no `@import.alias` / `@import.names` here: the alias, dot and blank spellings are part of +; the clause text, and a Go Include has no separate alias or names field today. +; The shape is read off a real parse (`--match`), not predicted: a declaration is `import` + exactly one +; `import_spec` or `import_spec_list`, and a broken file still yields an `import_declaration` node. +(import_declaration) @import.path diff --git a/src/ingest_importcap.h b/src/ingest_importcap.h index ffe957316..1a84c333a 100644 --- a/src/ingest_importcap.h +++ b/src/ingest_importcap.h @@ -25,9 +25,10 @@ // // THE VOCABULARY IS A FAMILY, AND ONLY ITS FIRST MEMBER IS SPENT. `@import.path` is the only capture name // in use, because C-family imports have no other part: `#include` names a path and nothing else — no -// alias to record, no list of imported names. The other two names the issue sketches are reserved BY NAME -// so the next language lands on them rather than inventing a spelling: `@import.alias` is the local name an -// import renames a target to (`import a.b as c`, `use Foo\Bar as Baz`, `using X = Foo.Bar`), and +// alias to record, no list of imported names — and because Go's alias/dot/blank spellings ride inside the +// declaration clause its Include has always carried (see DepDialect::Go below), not in a field of their own. +// The other two names the issue sketches are reserved BY NAME so the next language lands on them rather +// than inventing a spelling: `@import.alias` is the local name an import renames a target to (`import a.b as c`, `use Foo\Bar as Baz`, `using X = Foo.Bar`), and // `@import.names` is a group of targets ONE directive names (`use Foo\{A, B}`, `alias MyApp.{Bar, Baz}`), // which is why both of those are emitted by the C++ side today through a per-directive helper rather than // by one capture — a capture names one node, and a group is many. No enum value reserves them: an unused @@ -131,8 +132,29 @@ ImportSpec normaliseCFamilyImport( TSNode directive, std::string_view raw, std:: return out; } +// ── DepDialect::Go ─────────────────────────────────────────────────────────────────────────────────── +// A Go import is ONE `import_declaration` — `import "fmt"`, `import f "fmt"` (aliased), `import . "x"` (dot), +// `import _ "x"` (blank), cgo's `import "C"`, or a parenthesised group of any of those — and the Include +// target it has always carried is the declaration's own CLAUSE (importClauseTarget, shared with the one +// language that still reads it from the walk), not a per-spec specifier. So the captured node is the +// declaration itself (queries/go/tags.scm: `(import_declaration) @import.path`) and the normaliser is the +// clause reader, nothing more. A GROUPED import therefore stays ONE record whose text is the whole group cut +// at 96 bytes, exactly as before: resolve.h's resolveGoImport documents that ("grouped imports collapse to +// one node upstream and are left unresolved"), and splitting a group into one record per spec would add +// edges — a behaviour change, so its own slice. The alias / dot / blank spellings are the same: they ride +// INSIDE the clause text (`f "fmt"`), which is why `@import.alias` and `@import.names` are NOT used here — +// today's Go semantics have no separate alias or names field on an Include. (The alias a Go import binds is +// a different record altogether, the ModuleAlias RawBind captureGoImportFacts writes; it is not a dependency +// edge and is left where it is.) +ImportSpec normaliseGoImport( TSNode declaration, std::string_view src ) +{ + ImportSpec out; + out.text = importClauseTarget( declaration, src ); + return out; +} + // ── the dispatch: ONE normaliser per DepDialect ────────────────────────────────────────────────────── -// CFamily is the only dialect that has moved over so far; every other arm returns an empty ImportSpec, +// CFamily and Go are the dialects that have moved over so far; every other arm returns an empty ImportSpec, // which is what makes the capture INERT rather than wrong on a language whose tags.scm has not adopted // `@import.path` yet. Each `case` is deleted as its language moves, and the compiler then names every // other dialect still to do — the same "one language at a time" landing the issue asks for. @@ -141,10 +163,20 @@ ImportSpec normaliseImportSpecifier( DepDialect dialect, TSNode directive, std:: switch( dialect ) { case DepDialect::CFamily: return normaliseCFamilyImport( directive, raw, src ); - default: return {}; // no language but C-family captures @import.path yet + case DepDialect::Go: return normaliseGoImport( directive, src ); + default: return {}; // no other language captures @import.path yet } } +// The DIRECTIVE node a captured `@import.path` belongs to. For C-family the capture is the specifier token +// and the directive is its parent; for Go the capture IS the whole `import_declaration` (see above), so the +// directive is the captured node itself. One rule per dialect, in one place, so emitCapturedImport reads +// the same "directive" whichever grammar produced the match. +TSNode importDirectiveOf( DepDialect dialect, TSNode pathNode ) noexcept +{ + return dialect == DepDialect::Go ? pathNode : ts_node_parent( pathNode ); +} + // One captured `@import.path` → the Include record plus its ABS-3 import-role use-site ref, which is // exactly the pair captureIncludes' `emitDirective` lambda minted for a C-family directive. // @@ -178,8 +210,9 @@ ImportSpec normaliseImportSpecifier( DepDialect dialect, TSNode directive, std:: void emitCapturedImport( TSNode pathNode, std::uint32_t fileId, Lang lang, std::string_view src, std::vector& includes, std::vector& refs, ExtractShortfall& shortfall ) { - const TSNode directive = ts_node_parent( pathNode ); - bool reachable = false; + const DepDialect dialect = dependencyDialect( lang ); + const TSNode directive = importDirectiveOf( dialect, pathNode ); + bool reachable = false; if( importContainerReach( directive, lang, reachable ) > kMaxImportContainerDepth ) { DISCLOSE( shortfall, ExtractShortfall::DisclosureWhy::ImportNestingTooDeep, @@ -190,7 +223,7 @@ void emitCapturedImport( TSNode pathNode, std::uint32_t fileId, Lang lang, std:: { return; // a non-container ancestor the walk would never have entered — see importContainerReach } - const ImportSpec spec = normaliseImportSpecifier( dependencyDialect( lang ), directive, nodeTextOf( pathNode, src ), src ); + const ImportSpec spec = normaliseImportSpecifier( dialect, directive, nodeTextOf( pathNode, src ), src ); if( spec.text.empty() ) { return; diff --git a/src/ingest_relations.h b/src/ingest_relations.h index b3f1e9ea5..904585a31 100644 --- a/src/ingest_relations.h +++ b/src/ingest_relations.h @@ -2100,7 +2100,8 @@ constexpr std::uint16_t kMaxImportContainerDepth = 256; // CLEAN written specifier via grammar child fields (module path / quoted specifier / use argument), not a // sliced clause — the sound resolver input. // -// #358: the C-family rows are GONE from this list. C++ preproc_include (path field, "" local vs <> +// #358: the C-family rows are GONE from this list, and so is Go's (the Go `import_declaration` clause is read by +// the DepDialect::Go normaliser; only Swift still reaches the `import_declaration` branch below). C++ preproc_include (path field, "" local vs <> // external) and C++ preproc_call with directive `#import` are captured as `@import.path` by the grammar's // own query and read by the DepDialect::CFamily normaliser (src/ingest_importcap.h), so this function is // never asked about a C-family node. What is left is every language whose extraction needs the walk's own @@ -2122,6 +2123,35 @@ constexpr std::uint16_t kMaxImportContainerDepth = 256; // gone rather than documented as always-false. struct DirectiveTarget { std::string target; bool isLazy; bool isSymbolic; bool isReceiver; }; +// The written CLAUSE of an `import_declaration` — the declaration's own text with its leading keyword +// dropped (everything after the first space), at most 96 bytes, trailing `;` / space / newline / CR +// trimmed. This is the Include target Go has always carried (`"fmt"`, `f "fmt"`, `. "x"`, `_ "x"`, or a +// whole `( … )` group collapsed to ONE record), and Swift's `import Foundation`. Shared by the Swift walk +// branch below and by the DepDialect::Go normaliser (src/ingest_importcap.h) so the two cannot drift; the +// quirks are PRE-EXISTING and preserved byte-for-byte on purpose (#358 is a pure refactor): the keyword is +// dropped at a SPACE, so `import"os"` keeps the word `import`; a group is cut at 96 bytes, mid +// character if it lands there; a tab is not trimmed. Empty for an empty or out-of-range span. +std::string importClauseTarget( TSNode n, std::string_view src ) +{ + std::string target; + const std::uint32_t a = ts_node_start_byte( n ), b = ts_node_end_byte( n ); + if( a < b && b <= src.size() ) + { + std::string_view s = src.substr( a, b - a ); + const std::size_t sp = s.find( ' ' ); // drop the leading keyword + if( sp != std::string_view::npos ) + { + s = s.substr( sp + 1 ); + } + target.assign( s.data(), s.size() < 96 ? s.size() : 96 ); + while( !target.empty() && ( target.back() == ';' || target.back() == ' ' || target.back() == '\n' || target.back() == '\r' ) ) + { + target.pop_back(); + } + } + return target; +} + // `insideFn` exists for exactly the same one branch `lang` does: whether the call_expression being read // sits inside a TS/JS function body, per captureIncludes' walk — meaningless (and ignored) everywhere else. DirectiveTarget directiveTargetOf( TSNode n, const char* t, std::string_view src, Lang lang, bool insideFn ) @@ -2255,25 +2285,14 @@ DirectiveTarget directiveTargetOf( TSNode n, const char* t, std::string_view src } } } - else if( kindIs( t, "import_declaration" ) ) // Go / Swift — captured but NOT precise-resolved + else if( kindIs( t, "import_declaration" ) ) // Swift — captured but NOT precise-resolved { - // Go (needs go.mod module-root) and Swift (whole-module, no path) are DEFERRED — the precise - // resolver leaves them unresolved. Keep the best-effort target for --uses / --deps back-compat. - const uint32_t a = ts_node_start_byte( n ), b = ts_node_end_byte( n ); - if( a < b && b <= src.size() ) - { - std::string_view s = src.substr( a, b - a ); - const std::size_t sp = s.find( ' ' ); // drop the leading keyword - if( sp != std::string_view::npos ) - { - s = s.substr( sp + 1 ); - } - target.assign( s.data(), s.size() < 96 ? s.size() : 96 ); - while( !target.empty() && ( target.back() == ';' || target.back() == ' ' || target.back() == '\n' || target.back() == '\r' ) ) - { - target.pop_back(); - } - } + // #358: Go moved onto `@import.path` (queries/go/tags.scm + the DepDialect::Go normaliser in + // src/ingest_importcap.h), so this branch is reached by Swift ALONE now. The clause reader is the + // one both languages shared and is kept as ONE function (importClauseTarget) rather than copied: + // Swift (whole-module, no path) is DEFERRED in the precise resolver, and its slice has its own + // landing. Keep the best-effort target for --uses / --deps back-compat. + target = importClauseTarget( n, src ); } else if( kindIs( t, "using_directive" ) ) { // C# `using Foo.Bar;` / `using static Foo;` / `using X = Foo.Bar;` diff --git a/src/ingest_sidecap.h b/src/ingest_sidecap.h index 6ddd733dc..642e95596 100644 --- a/src/ingest_sidecap.h +++ b/src/ingest_sidecap.h @@ -1546,11 +1546,13 @@ void captureSideFacts( const LangEntry& le, std::uint32_t fileId, std::string_vi #endif // Elixir directives share the lexical tags context below (ElixirContext), and as of #358 so do the - // C-family ones: their `@import.path` captures are normalised and emitted by captureTagsFacts, and - // captureIncludes no longer reads a preproc_include / preproc_call at all. The walk is pure cost - // for those languages — it descends only allowlisted containers, and a C-family include lives at - // file scope or inside a preprocessor guard, which the tags query already reaches unanchored. - if( le.lang != Lang::Elixir && dependencyDialect( le.lang ) != DepDialect::CFamily ) + // C-family ones and Go's: their `@import.path` captures are normalised and emitted by captureTagsFacts, + // and captureIncludes no longer reads a preproc_include / preproc_call / Go import_declaration at all. + // The walk is pure cost for those languages — it descends only allowlisted containers, and a C-family + // include lives at file scope or inside a preprocessor guard, which the tags query already reaches + // unanchored (a Go import_declaration is a child of the file root by rule). + if( const DepDialect dialect = dependencyDialect( le.lang ); + le.lang != Lang::Elixir && dialect != DepDialect::CFamily && dialect != DepDialect::Go ) { captureIncludes( root, le.lang, fileId, src, incs, refs, binds, constOpens, shortfall ); } diff --git a/test/importcapcheck.sh b/test/importcapcheck.sh index b443f199a..2d2aaa6cd 100755 --- a/test/importcapcheck.sh +++ b/test/importcapcheck.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# importcapcheck.sh — the gate for the shared import-capture vocabulary (issue #358), C-family slice. +# importcapcheck.sh — the gate for the shared import-capture vocabulary (issue #358), C-family and Go slices. # # WHAT THIS PINS. `#include` / `#import` for c, cpp, objc (and the .h/.hpp/.cu/.cuh/.metal extensions # that ride those grammars) no longer come from a per-language extractor in src/ingest_relations.h. They @@ -40,6 +40,20 @@ # identically, which is the evidence the records did not change # (RIPWIRE_BASE_BIN, the house name; SKIPs when unset, as on CI) # +# +# THE GO SLICE (arms 14-19, below the C-family arms). Go's `import_declaration` — single and grouped, aliased, +# dot, blank, cgo `import "C"` — is captured as `@import.path` in queries/go/tags.scm and read by the ONE +# DepDialect::Go normaliser in src/ingest_importcap.h; the walk in captureIncludes no longer runs for Go. +# 14 the Go capture is COMPILED IN — `(import_declaration) @import.path` is in the binary's embedded tags.scm +# 15 every Go form, exact rows — single/aliased/dot/blank/group/cgo/tab/semicolon/raw-string/UTF-8 cut/error +# recovery, pinned as the byte-for-byte rows the extractor emitted +# 16 no widening — an import_declaration the grammar never makes a child of the file root +# (inside a function body) stays out, exactly as the walk left it +# 17 resolution through imports — a go.mod `replace` still maps an import onto a sibling root's package +# (single, aliased and grouped), and a bogus replace target removes the edge +# 18 the use-site half — `--uses=` reports role="import" for a Go import +# 19 Go cache + determinism — warm == cold == --no-cache on --deps over the Go fixture +# # Usage: test/importcapcheck.sh # RIPWIRE_BIN=asan/ripwire test/importcapcheck.sh # Exits non-zero on any failure. Does NOT edit test/regression.sh or test/golden.xml. @@ -443,6 +457,199 @@ else no "non-deterministic output" fi +# ═══════════════════════════════════════════════════════════════════════════════════════════════════ +# THE GO SLICE (arms 14-19). Go's import_declaration rides `(import_declaration) @import.path` and the +# DepDialect::Go normaliser; the Include target is the declaration CLAUSE, exactly as the removed walk branch +# built it, so every row below is the byte-for-byte row the extractor emitted (read off the pre-change binary, +# not predicted). A GROUPED import is ONE row whose text is the whole group cut at 96 bytes — resolve.h's +# resolveGoImport documents that — and splitting it per spec would ADD rows, which is exactly what arm 15 +# would show as a diff. +# ═══════════════════════════════════════════════════════════════════════════════════════════════════ +GO="$TMP/go"; mkdir -p "$GO/cmd" "$GO/pkg/util" "$GO/replaced" +printf 'module example.com/app\n\ngo 1.21\n\nrequire example.com/ext v0.0.0\n\nreplace example.com/ext => ./replaced\n' > "$GO/go.mod" +printf 'package ext\n\nfunc Ext() int { return 1 }\n' > "$GO/replaced/ext.go" +printf 'package util\n\nfunc Helper() int { return 2 }\n' > "$GO/pkg/util/util.go" +cat > "$GO/main.go" <<'GOSRC' +package main + +import "fmt" +import f2 "os" +import . "strings" +import _ "embed" +import ( + "example.com/app/pkg/util" + alias "example.com/ext" + . "math" + _ "net/http/pprof" + "strconv" // trailing comment +) + +import "C" + +func main() { + fmt.Println(util.Helper(), alias.Ext(), f2.Args, strconv.Itoa(1), ToUpper("x"), Abs(1)) +} +GOSRC +# a TAB after the keyword keeps the word `import` (the keyword is dropped at a SPACE); two spaces leave one +# leading space; a trailing `;` is trimmed; a raw-string path keeps its backticks. +printf 'package main\n\nimport\t"os"\nimport "io";\nimport `raw/path`\n\nfunc t() { _ = os.Args }\n' > "$GO/cmd/tab.go" +cat > "$GO/cmd/long.go" <<'GOSRC' +package main + +import ( + "github.com/very/long/module/path/that/goes/on/and/on/and/on/forever/and/ever/amen/pkg1" + "github.com/very/long/module/path/that/goes/on/and/on/and/on/forever/and/ever/amen/pkg2" +) + +func l() {} +GOSRC +# error recovery: a bare `import` swallowing the next lines is still ONE import_declaration, and a broken +# function header does not stop the next import being recovered. +printf 'package main\n\nimport\nfunc b( {\nimport "after/error"\n' > "$GO/cmd/broken.go" +printf 'package main\n\nfunc f( {\nimport "x"\n}\n' > "$GO/cmd/err.go" +# the 96-byte cut lands mid multibyte sequence on purpose: the pre-change bytes are kept, not "fixed". +printf 'package main\nimport "é世界/pkg"; import "z"\nimport ("é世界/pkg/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\n)\n' > "$GO/cmd/utf.go" +# a definition named like the last path element of utf.go's first import: --uses keys on an INDEXED name, so +# arm 18 needs one (`fmt`, `os` have no definition in the tree and answer found=0). +printf 'package main\n\nfunc pkg() {}\n' > "$GO/cmd/pkgdef.go" +# arm 16's negative: an import inside a function body is not a file-scope declaration and was never an edge. +printf 'package main\n\nfunc a() {\n\timport "inbody"\n}\n' > "$GO/cmd/nested.go" + +"$BIN" "$GO" --deps --no-cache > "$TMP/go.xml" 2>/dev/null +grep -o ' "$TMP/go.rows" +cat > "$TMP/go.want" <<'GOWANT' + "$WS/svc/pkg/handle.go" +printf 'module example.com/svc\n\ngo 1.21\n' > "$WS/svc/go.mod" +printf 'module example.com/cli\n\ngo 1.21\n\nreplace example.com/svc => ../svc\n' > "$WS/cli/go.mod" +printf 'package main\n\nimport svc "example.com/svc/pkg"\n\nfunc runGoMain() int { return svc.GoHandle() }\n' > "$WS/cli/main.go" +printf 'module example.com/cli2\n\ngo 1.21\n\nreplace example.com/svc => ../svc\n' > "$WS/cli2/go.mod" +printf 'package main\n\nimport (\n\tsvc "example.com/svc/pkg"\n\t"fmt"\n)\n\nfunc runGrouped() int { fmt.Println(); return svc.GoHandle() }\n' > "$WS/cli2/main.go" +printf 'module example.com/clibogus\n\ngo 1.21\n\nreplace example.com/svc => ../svc/NONEXISTENT\n' > "$WS/clibogus/go.mod" +printf 'package main\n\nimport svc "example.com/svc/pkg"\n\nfunc runBogus() int { return svc.GoHandle() }\n' > "$WS/clibogus/main.go" +"$BIN" "$WS/svc" "$WS/cli" "$WS/cli2" --deps --no-cache > "$TMP/gows.xml" 2>/dev/null +if grep -q ']*' "$TMP/gows.xml")" +fi +# the evidence-driven negative: the same import under a replace whose target does not exist resolves to nothing. +"$BIN" "$WS/svc" "$WS/clibogus" --deps --no-cache > "$TMP/gobogus.xml" 2>/dev/null +if grep -q ']*' "$TMP/gobogus.xml")" +fi + +# ── arm 18: the use-site half ───────────────────────────────────────────────────────────────────────── +GUSES="$("$BIN" "$GO" --uses=pkg --no-cache 2>/dev/null)" +if printf '%s' "$GUSES" | grep -q ']*' | head -3)" +fi + +# ── arm 19: Go cache round-trip + determinism ──────────────────────────────────────────────────────── +GCACHE="$TMP/go.cache" +"$BIN" "$GO" --cache="$GCACHE" --deps > "$TMP/gocold.xml" 2>/dev/null +"$BIN" "$GO" --cache="$GCACHE" --deps > "$TMP/gowarm.xml" 2>/dev/null +if cmp -s "$TMP/gocold.xml" "$TMP/gowarm.xml" && cmp -s "$TMP/gocold.xml" "$TMP/go.xml"; then + ok "Go: warm == cold == --no-cache on --deps (the Go Include round-trip is byte-identical)" +else + no "Go: --deps differs between cold, warm and --no-cache" +fi +if [ -n "${BASE_BIN:-}" ] && [ -x "${BASE_BIN:-}" ]; then + "$BASE_BIN" "$GO" --deps --no-cache > "$TMP/gobase.xml" 2>/dev/null + if cmp -s "$TMP/gobase.xml" "$TMP/go.xml"; then + ok "Go: --deps over the fixture is byte-identical to the pre-change binary's" + else + no "Go: --deps differs from the pre-change binary — dependency output changed" + fi + # and the Go half of arm 13: a cache written by the pre-change binary over the Go fixture is ACCEPTED + # (file untouched) and reads identically, so no kParserVer bump is owed for the Go records either. + GPRE="$TMP/gopre.bin"; rm -f "$GPRE" + "$BASE_BIN" "$GO" --cache="$GPRE" --deps >/dev/null 2>&1 + cp "$GPRE" "$TMP/gopre.bin.copy" + "$BIN" "$GO" --cache="$GPRE" --deps > "$TMP/gopre.xml" 2>/dev/null + if cmp -s "$TMP/gopre.xml" "$TMP/go.xml" && cmp -s "$GPRE" "$TMP/gopre.bin.copy"; then + ok "Go: a pre-change binary's cache over the Go fixture is accepted untouched and reads identically — no kParserVer bump owed" + else + no "Go: a pre-change cache reads differently or was rewritten — extraction output changed, kParserVer must be bumped" + fi +else + skip "Go: pre-change comparison (set RIPWIRE_BASE_BIN= to run it)" +fi + # ── well-formed XML ─────────────────────────────────────────────────────────────────────────────────── if command -v xmllint >/dev/null 2>&1; then if xmllint --noout "$TMP/cold.xml" 2>/dev/null; then ok "xml well-formed"; else no "xml malformed"; fi From 70cc6a067a468d6ef70145a164fd58fe64c8193c Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 10:01:30 -0400 Subject: [PATCH 037/170] perf(profile): PROFILE_SCOPE coverage for the warm call, --whereis and the cold ingest Self-profile scopes only (they compile to nothing without -DRIPWIRE_PROFILE=ON), so the owner's counter run (PMC, root) and the LLVM memory run can split time where nothing was scoped before: - warm call: the cache load's per-span pread, per-record digest (recordSum32) and per-record decode (readFileRecord); the resolve loop's dense candidate views; "main: query + emit" around the verb. The crawl walk, git ignore probe (process and join), model rebuild, buildGraph phases, CSR build and doc post-pass were already scoped. - --whereis: total, one scope per `git ls-tree -r` (process + read + parse, and the parse alone), the cat-file stream, the worktree overlay, the hit sort, and popenTrimmed (every git one-liner). - cold ingest: parse, side captures and tags captures per LANGUAGE FAMILY (eight families, through underExtractScope, which is a plain switch calling the same work when profiling is off), base-class and field relation capture per class, complexityOf per function. The cache save was already scoped. No scope sits in a per-byte or per-reference loop. Byte identity, dev builds before/after: 12 argv (map, --callers, --for, --at, --impact, --whereis, --stray-content, --skipped, --no-cache) identical. Co-Authored-By: Claude Opus 5.5 --- src/crossref.h | 6 ++++ src/gitmine.h | 1 + src/graph.h | 21 ++++++++------ src/ingest_cache.h | 21 ++++++++++++-- src/ingest_metrics.h | 3 +- src/ingest_parsepool.h | 19 +++++++++---- src/ingest_relations.h | 2 ++ src/ingest_sidecap.h | 63 ++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 3 ++ 9 files changed, 121 insertions(+), 18 deletions(-) diff --git a/src/crossref.h b/src/crossref.h index 750862901..db51f167a 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -524,6 +524,7 @@ inline void streamBlobs( const std::string& root, const std::vector return; } + PROFILE_SCOPE_DESCRIBE( "crossref: git cat-file --batch stream (every blob, callback included)" ); std::vector body; std::string header; for( std::size_t served = 0; served < shas.size(); ++served ) @@ -1685,7 +1686,9 @@ inline std::vector lsTree( const std::string& root, const std::string& r DISCLOSE( "crossref: refusing to list a tree whose revision argument is not a resolved object name" ); return {}; } + PROFILE_SCOPE_DESCRIBE( "crossref/whereis: git ls-tree -r of one tip (process + read + parse)" ); const std::string raw = gitCapture( root, "ls-tree -r " + shSingleQuote( rev ) + " -- 2>/dev/null" ); + PROFILE_SCOPE_DESCRIBE( "crossref/whereis: parse one ls-tree listing" ); std::vector out; for( std::string_view line : splitLines( raw ) ) { @@ -1940,6 +1943,7 @@ struct WorktreeScan // dropped by the caller, so a deleted or renamed definition stops answering from a commit the checkout left. inline WorktreeScan scanWorktree( const std::string& root, std::string_view sym, const RefInfo& head ) { + PROFILE_SCOPE_DESCRIBE( "crossref/whereis: worktree overlay (diff + others + read changed copies)" ); WorktreeScan scan; const GitListing changed = worktreeChangedPaths( root, head.tip ); if( !changed.ok ) @@ -2286,6 +2290,7 @@ inline void stampBlobRows( const std::vector& blobRows, const RefInfo& inline WhereResult computeWhereis( const std::string& root, std::string_view sym, std::string_view filter, WhereisEvidence evidence = {} ) { + PROFILE_SCOPE_DESCRIBE( "crossref/whereis: total (refs, listings, blob scan, overlay, sort)" ); WhereResult result; result.sym = std::string( sym ); result.filter = std::string( filter ); // H14/M6: echoed on the root beside refs_scanned=, which it bounds @@ -2380,6 +2385,7 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym // The checkout first, then refs by name; within a group, SOURCE before test before docs (§P11.5, see this // function's header), then definitions before references, then path/line — whereHitBefore states it. + PROFILE_SCOPE_DESCRIBE( "crossref/whereis: sort hit rows" ); std::sort( result.hits.begin(), result.hits.end(), []( const WhereHit& a, const WhereHit& b ) { return whereHitBefore( a, b ); } ); return result; } diff --git a/src/gitmine.h b/src/gitmine.h index da90fa025..454d7b526 100644 --- a/src/gitmine.h +++ b/src/gitmine.h @@ -248,6 +248,7 @@ inline std::string sinceUnresolvedRefusal( std::string_view value, std::string_v // 2026-10-08), and every git one-liner in the tool reads through here. inline std::string popenTrimmed( const std::string& cmd ) { + PROFILE_SCOPE_DESCRIBE( "gitmine: popenTrimmed (one shell + git child, block read)" ); std::FILE* pipe = os::popen( cmd.c_str(), "r" ); if( !pipe ) { diff --git a/src/graph.h b/src/graph.h index 51e083c5d..c30f72dc9 100644 --- a/src/graph.h +++ b/src/graph.h @@ -5743,19 +5743,22 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null // inserts into or erases from byName past its construction, so the address is stable for the loop. std::vector symLang( N ); std::vector symKind( N ); - for( std::size_t symIndex = 0; symIndex < N; ++symIndex ) - { - symLang[ symIndex ] = ing.symbols[ symIndex ].lang; - symKind[ symIndex ] = ing.symbols[ symIndex ].kind; - } ankerl::unordered_dense::set*> nameHasFnLocalDef; - if( !ing.fnLocalScopes.empty() ) { - for( const auto& [ name, ids ] : byName ) + PROFILE_SCOPE_DESCRIBE( "buildGraph/2l: dense candidate views + function-local name set" ); + for( std::size_t symIndex = 0; symIndex < N; ++symIndex ) + { + symLang[ symIndex ] = ing.symbols[ symIndex ].lang; + symKind[ symIndex ] = ing.symbols[ symIndex ].kind; + } + if( !ing.fnLocalScopes.empty() ) { - if( std::any_of( ids.begin(), ids.end(), [ & ]( NodeId c ) { return ing.symbols[ c ].fnLocal != 0; } ) ) + for( const auto& [ name, ids ] : byName ) { - nameHasFnLocalDef.insert( &ids ); + if( std::any_of( ids.begin(), ids.end(), [ & ]( NodeId c ) { return ing.symbols[ c ].fnLocal != 0; } ) ) + { + nameHasFnLocalDef.insert( &ids ); + } } } } diff --git a/src/ingest_cache.h b/src/ingest_cache.h index 521bceaad..2a7b030fc 100644 --- a/src/ingest_cache.h +++ b/src/ingest_cache.h @@ -2709,7 +2709,12 @@ inline HashMap loadCache( const std::string& path, std:: } fileBuf.resize( std::size_t( spanEnd - spanStart ) ); - if( !preadExact( frame.blob.fd, fileBuf.data(), fileBuf.size(), spanStart ) ) + bool spanRead = false; + { + PROFILE_SCOPE_DESCRIBE( "ingest/loadCache: pread one coalesced span" ); + spanRead = preadExact( frame.blob.fd, fileBuf.data(), fileBuf.size(), spanStart ); + } + if( !spanRead ) { DISCLOSE( Diagnostics::answerUnchanged, "the affected files are reparsed from source: the answer is byte-identical, only slower", "ingest: cache blob read failed mid-load — the unread records are reparsed" ); @@ -2720,7 +2725,12 @@ inline HashMap loadCache( const std::string& path, std:: { const CacheEntry& e = frame.entries[ wanted[i].entryIndex ]; const char* rec = fileBuf.data() + std::size_t( e.recOffset - spanStart ); - if( recordSum32( std::string_view( rec, e.recLength ) ) != e.recSum ) + bool recordSumOk = false; + { + PROFILE_SCOPE_DESCRIBE( "ingest/loadCache: record digest (recordSum32)" ); + recordSumOk = recordSum32( std::string_view( rec, e.recLength ) ) == e.recSum; + } + if( !recordSumOk ) { // A record torn on its own while the table survived: drop THIS file (it reparses) and // keep the rest of the blob. The table is what must be trusted whole, not each record. @@ -2731,7 +2741,12 @@ inline HashMap loadCache( const std::string& path, std:: ByteR r{ rec, rec + e.recLength }; std::string rel; FileFacts ff; - if( !readFileRecord( r, captureValueUses, dictScratch, rel, ff ) ) + bool decoded = false; + { + PROFILE_SCOPE_DESCRIBE( "ingest/loadCache: decode one record (readFileRecord)" ); + decoded = readFileRecord( r, captureValueUses, dictScratch, rel, ff ); + } + if( !decoded ) { continue; // corrupt record → that file reparses (readFileRecord already disclosed) } diff --git a/src/ingest_metrics.h b/src/ingest_metrics.h index ba093e357..23f1b685b 100644 --- a/src/ingest_metrics.h +++ b/src/ingest_metrics.h @@ -1213,7 +1213,8 @@ struct Complexity { std::uint32_t cx; std::uint32_t ccx; std::uint32_t maxNest; // per node and gets locals=0, which the caller (this file, RawDef→Symbol) leaves at 0 and serialize.h // never emits (absent, not a bare "0" — see localsCountedLang's own comment). inline Complexity complexityOf( TSNode root, std::string_view src, Lang lang ) // one fused DFS → cx, ccx, maxNest, locals, ppAlt, the nesting profile, AND ev -{ // A4-F25: NOT noexcept — cc_walk (and kids here) allocate +{ + PROFILE_SCOPE_DESCRIBE( "ingest/metrics: complexityOf one function (fused DFS)" ); // A4-F25: NOT noexcept — cc_walk (and kids here) allocate CcAccum acc; const bool countLocals = localsCountedLang( lang ); // essential complexity rides the SAME walk (zero new tree-sitter queries). ONE arena/label/pending set diff --git a/src/ingest_parsepool.h b/src/ingest_parsepool.h index 02ca53606..f583c78a0 100644 --- a/src/ingest_parsepool.h +++ b/src/ingest_parsepool.h @@ -407,7 +407,10 @@ inline void runParseWorker( ParsePoolShared& sh, unsigned t ) const TSNode root = ts_tree_root_node( pending.tree ); const std::size_t firstNewDefIndex = out.defs.size(); ExtractShortfall tagsShortfall; - captureTagsFacts( cursor, *pending.le, pending.fileId, pending.bytes, root, out.defs, out.refs, out.binds, out.incs, pending.ppDead, tagsShortfall ); + underExtractScope( pending.le->lang, [ & ] + { + captureTagsFacts( cursor, *pending.le, pending.fileId, pending.bytes, root, out.defs, out.refs, out.binds, out.incs, pending.ppDead, tagsShortfall ); + } ); notePartialExtract( scan, pending.fileId, tagsShortfall, pending.bytes.size() ); buildLexForNewDefs( out.defs, firstNewDefIndex, pending.bytes ); // B0.2: bytes still in memory ts_tree_delete( pending.tree ); @@ -565,7 +568,7 @@ inline void runParseWorker( ParsePoolShared& sh, unsigned t ) { continue; } - TreeGuard mdTree( parseTree( pg.p, bytes ) ); + TreeGuard mdTree( underExtractScope( le->lang, [ & ] { return parseTree( pg.p, bytes ); } ) ); if( mdTree.get() == nullptr ) { continue; @@ -602,7 +605,7 @@ inline void runParseWorker( ParsePoolShared& sh, unsigned t ) continue; } - TreeGuard tree( parseTree( pg.p, bytes ) ); + TreeGuard tree( underExtractScope( le->lang, [ & ] { return parseTree( pg.p, bytes ); } ) ); if( tree.get() == nullptr ) { continue; @@ -616,7 +619,10 @@ inline void runParseWorker( ParsePoolShared& sh, unsigned t ) // above), which is why it is computed here and not inside either driver. std::vector ppDead = preprocDeadRangesFor( *le, root, bytes ); ExtractShortfall shortfall; // every pass's disclosure for THIS file; noted on its slot once both have run - captureSideFacts( *le, static_cast( fileId ), bytes, root, out.refs, out.incs, out.binds, out.ffis, out.routeDefs, out.routeUses, out.constOpens, sh.captureValueUses, ppDead, shortfall ); + underExtractScope( le->lang, [ & ] + { + captureSideFacts( *le, static_cast( fileId ), bytes, root, out.refs, out.incs, out.binds, out.ffis, out.routeDefs, out.routeUses, out.constOpens, sh.captureValueUses, ppDead, shortfall ); + } ); notePartialExtract( scan, fileId, shortfall, bytes.size() ); // before a queued file's tags pass, which notes its own appendBlankedMacroUses( macroWork, le->lang, static_cast( fileId ), bytes, out.refs, ppDead ); @@ -636,7 +642,10 @@ inline void runParseWorker( ParsePoolShared& sh, unsigned t ) waitForQueryPrewarm( &sh.gate ); } const std::size_t firstNewDefIndex = out.defs.size(); - captureTagsFacts( cursor, *le, static_cast( fileId ), bytes, root, out.defs, out.refs, out.binds, out.incs, ppDead, shortfall ); + underExtractScope( le->lang, [ & ] + { + captureTagsFacts( cursor, *le, static_cast( fileId ), bytes, root, out.defs, out.refs, out.binds, out.incs, ppDead, shortfall ); + } ); notePartialExtract( scan, fileId, shortfall, bytes.size() ); buildLexForNewDefs( out.defs, firstNewDefIndex, bytes ); // B0.2: bytes still in memory } diff --git a/src/ingest_relations.h b/src/ingest_relations.h index b3f1e9ea5..1e7c9c2c0 100644 --- a/src/ingest_relations.h +++ b/src/ingest_relations.h @@ -477,6 +477,7 @@ void captureMacroBodyCalls( TSNode defineNode, std::uint32_t fileId, Lang lang, // child, collecting type nodes at both depths (Rust is a separate pass — impl Trait for T is a sibling). void captureBases( TSNode classNode, std::uint32_t fileId, Lang lang, std::string_view src, std::vector& refs ) { + PROFILE_SCOPE_DESCRIBE( "ingest/relations: base classes of one class" ); // O(children) at all three levels. These child lists LOOK grammar-bounded — a class node's clauses, a // clause's base types — and the earlier class-3 reasoning said so, but EXTRAS refute it: a comment run // between two base types is spliced straight into the clause's own child array (src/infra/tschildren.h), @@ -789,6 +790,7 @@ inline WrittenPointee stdSmartPointee( TSNode typeNode, std::string_view src ) n // `w_.m()` does not (kParserVer 113). Every other qualified template (`std::vector v_;`) still records nothing. void captureFields( TSNode classNode, std::uint32_t fileId, Lang lang, std::string_view src, std::vector& refs ) { + PROFILE_SCOPE_DESCRIBE( "ingest/relations: fields of one class" ); if( lang != Lang::Cpp ) { return; // C++ only for S5-E; extend for Python/TS later diff --git a/src/ingest_sidecap.h b/src/ingest_sidecap.h index 6ddd733dc..b79270b58 100644 --- a/src/ingest_sidecap.h +++ b/src/ingest_sidecap.h @@ -1150,6 +1150,69 @@ bool prepareParserFor( TSParser* parser, const LangEntry& le ) return true; } +// ── cold-ingest profile scopes by LANGUAGE FAMILY (cli-perf-068 STEP 0) ────────────────────────────────────────── +// A PROFILE_SCOPE names one code site, so the parse pool's per-file phases (parse, side captures, tags captures) are +// opened through underExtractScope, which picks one of eight family-named sites per phase: the self-profile then splits +// a cold index's parse and extraction cost by language family (the LLVM memory run reads these). In a build without +// RIPWIRE_PROFILE the macro is nothing and this is a switch that calls `work` — same work, same order, same answer. +enum class ExtractFamily : std::uint8_t { CFamily, Python, JsTs, JvmDotNet, GoRustSwiftDart, Scripting, Bash, DocsData, Count }; +enum class ExtractPhase : std::uint8_t { Parse, SideFacts, TagsFacts, Count }; + +constexpr ExtractFamily extractFamilyOf( Lang lang ) noexcept +{ + switch( lang ) + { + case Lang::Cpp: case Lang::C: case Lang::ObjC: return ExtractFamily::CFamily; + case Lang::Python: return ExtractFamily::Python; + case Lang::TypeScript: case Lang::JavaScript: return ExtractFamily::JsTs; + case Lang::Java: case Lang::Kotlin: case Lang::CSharp: return ExtractFamily::JvmDotNet; + case Lang::Go: case Lang::Rust: case Lang::Swift: case Lang::Dart: return ExtractFamily::GoRustSwiftDart; + case Lang::Ruby: case Lang::Php: case Lang::Lua: case Lang::Elixir: case Lang::GDScript: return ExtractFamily::Scripting; + case Lang::Bash: return ExtractFamily::Bash; + case Lang::Markdown: case Lang::Json: case Lang::Toml: case Lang::Yaml: case Lang::Unknown: return ExtractFamily::DocsData; + } + return ExtractFamily::DocsData; // a byte past the enum; a NEW Lang is a -Werror=switch error above +} + +inline constexpr const char* kExtractScopeNames[ std::size_t( ExtractPhase::Count ) ][ std::size_t( ExtractFamily::Count ) ] = { + { "ingest/extract[c-family]: parse one file", "ingest/extract[python]: parse one file", "ingest/extract[js-ts]: parse one file", + "ingest/extract[jvm-dotnet]: parse one file", "ingest/extract[go-rust-swift-dart]: parse one file", "ingest/extract[scripting]: parse one file", + "ingest/extract[bash]: parse one file", "ingest/extract[docs-data]: parse one file" }, + { "ingest/extract[c-family]: side captures (relations, binds, includes, values)", "ingest/extract[python]: side captures (relations, binds, includes, values)", + "ingest/extract[js-ts]: side captures (relations, binds, includes, values)", "ingest/extract[jvm-dotnet]: side captures (relations, binds, includes, values)", + "ingest/extract[go-rust-swift-dart]: side captures (relations, binds, includes, values)", "ingest/extract[scripting]: side captures (relations, binds, includes, values)", + "ingest/extract[bash]: side captures (relations, binds, includes, values)", "ingest/extract[docs-data]: side captures (relations, binds, includes, values)" }, + { "ingest/extract[c-family]: tags captures (defs, refs, metrics)", "ingest/extract[python]: tags captures (defs, refs, metrics)", + "ingest/extract[js-ts]: tags captures (defs, refs, metrics)", "ingest/extract[jvm-dotnet]: tags captures (defs, refs, metrics)", + "ingest/extract[go-rust-swift-dart]: tags captures (defs, refs, metrics)", "ingest/extract[scripting]: tags captures (defs, refs, metrics)", + "ingest/extract[bash]: tags captures (defs, refs, metrics)", "ingest/extract[docs-data]: tags captures (defs, refs, metrics)" }, +}; + +template +decltype( auto ) inExtractScope( Work&& work ) +{ + PROFILE_SCOPE_DESCRIBE( kExtractScopeNames[ std::size_t( Phase ) ][ std::size_t( Family ) ] ); + return work(); +} + +template +decltype( auto ) underExtractScope( Lang lang, Work&& work ) +{ + switch( extractFamilyOf( lang ) ) + { + case ExtractFamily::CFamily: return inExtractScope( work ); + case ExtractFamily::Python: return inExtractScope( work ); + case ExtractFamily::JsTs: return inExtractScope( work ); + case ExtractFamily::JvmDotNet: return inExtractScope( work ); + case ExtractFamily::GoRustSwiftDart: return inExtractScope( work ); + case ExtractFamily::Scripting: return inExtractScope( work ); + case ExtractFamily::Bash: return inExtractScope( work ); + case ExtractFamily::DocsData: break; + case ExtractFamily::Count: break; + } + return inExtractScope( work ); +} + TSTree* parseTree( TSParser* parser, std::string_view src ) { TSTree* tree = nullptr; diff --git a/src/main.cpp b/src/main.cpp index 003a9b14b..400a212a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5332,6 +5332,9 @@ static int dispatchMain( const rw::Config& cfg, char** argv ) ampPtr, cboPtr, testedPtr, lcom4Ptr, impurePtr, forChurn, redactCounts, redactPtr, notesPtr, notesDegraded, grepPhases.valid ? &grepPhases : nullptr, needsValueUses }; + // Every verb from here on answers from the built graph: its query and its emit are this scope (the self-profile's + // "after the graph" share; the verbs' own scopes nest inside it). + PROFILE_SCOPE_DESCRIBE( "main: query + emit (the verb, after the graph)" ); if( std::optional handled = runForLens( dsp ) ) { return *handled; From e9c35f0cf08debb710723e325699ad7fa8d9eb60 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 10:02:52 -0400 Subject: [PATCH 038/170] refactor(#358): TS/JS import edges from the shared @import.path capture, not captureIncludes' walk import/export-from statements and the CommonJS/dynamic require()/import() call are three patterns on the one capture name in queries/{typescript,tsx,javascript}/tags.scm, read by a DepDialect::Web arm of src/ingest_importcap.h. captureIncludes no longer walks TS/JS; its three directiveTargetOf branches are removed. The container reach and the lazy bit are re-derived from the captured node's ancestry. Dependency output is byte-identical; test/importcapcheck.sh gains a JS/TS section (J1-J8). Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 37 +++++ queries/javascript/tags.scm | 32 +++++ queries/tsx/tags.scm | 32 +++++ queries/typescript/tags.scm | 32 +++++ src/ingest_importcap.h | 111 ++++++++++++--- src/ingest_relations.h | 100 ++++++------- src/ingest_sidecap.h | 11 +- test/importcapcheck.sh | 275 ++++++++++++++++++++++++++++++++++++ 8 files changed, 545 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d22e195d..1671f1c56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1136,6 +1136,43 @@ on both `--no-cache` and `--deps`. The new `test/importcapcheck.sh` pins the sha `#import` spellings, a macro include, guarded arms, both dead arms, the `#pragma`/`#error` gate, the import-role use-site half, cache round-trip, determinism) over a 14-edge fixture it builds itself. +### Changed — the TypeScript / JavaScript import edges come from the shared capture too (#358, JS/TS slice) + +The second language family onto the `@import.path` vocabulary the C-family slice introduced. `import … from 'x'`, the +re-exports (`export * from`, `export * as n from`, `export { a as b } from`, `export type { T } from`) and the CommonJS / +dynamic `require('x')` / `import('x')` are now three patterns on the one capture name in `queries/{typescript,tsx, +javascript}/tags.scm`, normalised by a `DepDialect::Web` arm in `src/ingest_importcap.h`. `captureIncludes` no longer +walks a TypeScript or JavaScript file at all, and the three branches of `directiveTargetOf` that read it are gone. +`jsModuleLoadTarget` stays: it is the text gate for the call spelling, and `captureJsImportFacts` (the bindings a +destructured `require` makes) still reads it. + +A query cannot say "the callee's text is `require`" (tags-pass predicates never run), so the pattern matches any bare +call with a leading string and the three guards that keep an ordinary call out of the graph stay C++: the callee is +the bare name `require`/`import`, there is exactly one argument, and it is a string. Two more things the walk owned +are re-derived from the captured node's ancestry, not dropped: the **container reach** (the walk entered only an +allowlist, so `require('x').y`, `[require('x')]`, `a ? require('x') : b`, a class method body and a `declare module` +body were never edges, and still are not) and the **lazy bit** (a `require`/`import` call written inside a function +body is `lazy="1"` on `--impact`'s import tier, from the same ancestor loop that checks the reach). + +**Not changed, deliberately.** `import x = require('y')` (TypeScript) is not an edge, before or after: the grammar +gives it its own `import_require_clause`, which the walk never read and no pattern here matches. `@import.alias` and +`@import.names` are not used: the Include record of a JS/TS import names a module and nothing else; the bound names +are a different record (`captureJsImportFacts`, untouched). + +**The same disclosure difference as the C-family slice, in `--skipped` only.** `extract-partial` for import nesting now +fires only when a captured import is actually cut at the bound. A `require('x')` is itself a container to the old walk, +so it announced at 254 and 255 nested blocks although the edge was kept; this does not. In the other direction, an +import under 300 nested blocks inside a class method is announced (the ancestor loop reaches the bound before it can +learn the walk would never have entered the class). Both are pinned in `test/importcapcheck.sh`. + +**Dependency edges are byte-identical.** Verified with both binaries against unchanged trees: `--deps`, `--uses`, +`--impact`, `--callers`, `--zoom`, `--report` and `--for` over three TypeScript repositories, the CommonJS tree of a +global npm install, two npm cache trees, a `.astro` tree and this repository's `test/` fixtures. `test/importcapcheck.sh` +gains a JS/TS section (J1–J8): an exact edge list, in source order, for `.ts .js .tsx .mjs .cjs .jsx` and a `.astro` +frontmatter (64 edges, with the `neg_*` shapes that must stay out), the lazy bit, the use-site half, the depth bound at +254/255/256 blocks, the two disclosure differences, and cache round-trip with a pre-change cache accepted unchanged +(no `kParserVer` bump). + ### Changed — `--affected` and `--test-gate` mark which tests to run first (`run_first=`) A tests-to-run answer listed every reaching test in evidence order (changed, partner, then `hops=` ascending), 80 to diff --git a/queries/javascript/tags.scm b/queries/javascript/tags.scm index d5dc3cc88..b0ab13527 100644 --- a/queries/javascript/tags.scm +++ b/queries/javascript/tags.scm @@ -181,3 +181,35 @@ (jsx_opening_element name: (member_expression property: (property_identifier) @name)) @reference.call + +; ---- import capture (the shared vocabulary of issue #358) ---- +; ONE capture name for every way this grammar loads a module: the node is the specifier STRING as written, +; quotes included, and the DepDialect::Web normaliser (src/ingest_importcap.h) strips them. Read off a real +; parse with `--match`, never predicted. Three patterns, one name: +; +; import … from 'x' / import 'x' / import type … from 'x' — import_statement `source:` +; export * from 'x' / export { a as b } from 'x' / export type { T } from 'x' +; — export_statement `source:`; every other +; export_statement has none and never matches +; require('x') / import('x') — a call whose FIRST named argument is a string +; +; The call pattern is deliberately broader than a module load: it matches any bare-identifier (or `import`) call +; with a leading string, `foo('x')` included, because a tags-pass predicate (`#eq? @f "require"`) is NOT evaluated +; in this tree (pinned by test/matchgrammarcheck.sh). So the C++ normaliser owns the three guards that keep an +; ordinary call out of the dependency graph: the callee TEXT is `require`/`import`, there is exactly ONE argument, +; and that argument is the string. The leading `.` anchors the capture to the first named child, so a 16 000-argument +; call yields one match, not 16 000. +; +; The patterns are UNANCHORED, so they match at any depth; the walk this replaced entered only an allowlist of +; container nodes (kJsImportContainers), and `require('x').y`, `[require('x')]`, `a ? require('x') : b` were never +; edges. src/ingest_importcap.h restores that reach from the captured node's ancestry. `import x = require('y')` +; (TS) is an import_require_clause with its own `source:` and matches nothing here, as it matched nothing before. +(import_statement + source: (string) @import.path) + +(export_statement + source: (string) @import.path) + +(call_expression + function: [ (identifier) (import) ] + arguments: (arguments . (string) @import.path)) diff --git a/queries/tsx/tags.scm b/queries/tsx/tags.scm index 89dda42a9..e8a5dab66 100644 --- a/queries/tsx/tags.scm +++ b/queries/tsx/tags.scm @@ -204,6 +204,38 @@ constructor: (member_expression property: (property_identifier) @name)) @reference.call +; ---- import capture (the shared vocabulary of issue #358) ---- +; ONE capture name for every way this grammar loads a module: the node is the specifier STRING as written, +; quotes included, and the DepDialect::Web normaliser (src/ingest_importcap.h) strips them. Read off a real +; parse with `--match`, never predicted. Three patterns, one name: +; +; import … from 'x' / import 'x' / import type … from 'x' — import_statement `source:` +; export * from 'x' / export { a as b } from 'x' / export type { T } from 'x' +; — export_statement `source:`; every other +; export_statement has none and never matches +; require('x') / import('x') — a call whose FIRST named argument is a string +; +; The call pattern is deliberately broader than a module load: it matches any bare-identifier (or `import`) call +; with a leading string, `foo('x')` included, because a tags-pass predicate (`#eq? @f "require"`) is NOT evaluated +; in this tree (pinned by test/matchgrammarcheck.sh). So the C++ normaliser owns the three guards that keep an +; ordinary call out of the dependency graph: the callee TEXT is `require`/`import`, there is exactly ONE argument, +; and that argument is the string. The leading `.` anchors the capture to the first named child, so a 16 000-argument +; call yields one match, not 16 000. +; +; The patterns are UNANCHORED, so they match at any depth; the walk this replaced entered only an allowlist of +; container nodes (kJsImportContainers), and `require('x').y`, `[require('x')]`, `a ? require('x') : b` were never +; edges. src/ingest_importcap.h restores that reach from the captured node's ancestry. `import x = require('y')` +; (TS) is an import_require_clause with its own `source:` and matches nothing here, as it matched nothing before. +(import_statement + source: (string) @import.path) + +(export_statement + source: (string) @import.path) + +(call_expression + function: [ (identifier) (import) ] + arguments: (arguments . (string) @import.path)) + ; #285: `` / `…` invokes Foo exactly like `Foo()` — bind the OPENING tag's name only ; (self-closing has no separate closing tag; a paired element's jsx_closing_element repeats the same ; name and is deliberately NOT captured, so one JSX invocation mints exactly one edge, not two). diff --git a/queries/typescript/tags.scm b/queries/typescript/tags.scm index 2dc080710..36a3471e3 100644 --- a/queries/typescript/tags.scm +++ b/queries/typescript/tags.scm @@ -203,3 +203,35 @@ (new_expression constructor: (member_expression property: (property_identifier) @name)) @reference.call + +; ---- import capture (the shared vocabulary of issue #358) ---- +; ONE capture name for every way this grammar loads a module: the node is the specifier STRING as written, +; quotes included, and the DepDialect::Web normaliser (src/ingest_importcap.h) strips them. Read off a real +; parse with `--match`, never predicted. Three patterns, one name: +; +; import … from 'x' / import 'x' / import type … from 'x' — import_statement `source:` +; export * from 'x' / export { a as b } from 'x' / export type { T } from 'x' +; — export_statement `source:`; every other +; export_statement has none and never matches +; require('x') / import('x') — a call whose FIRST named argument is a string +; +; The call pattern is deliberately broader than a module load: it matches any bare-identifier (or `import`) call +; with a leading string, `foo('x')` included, because a tags-pass predicate (`#eq? @f "require"`) is NOT evaluated +; in this tree (pinned by test/matchgrammarcheck.sh). So the C++ normaliser owns the three guards that keep an +; ordinary call out of the dependency graph: the callee TEXT is `require`/`import`, there is exactly ONE argument, +; and that argument is the string. The leading `.` anchors the capture to the first named child, so a 16 000-argument +; call yields one match, not 16 000. +; +; The patterns are UNANCHORED, so they match at any depth; the walk this replaced entered only an allowlist of +; container nodes (kJsImportContainers), and `require('x').y`, `[require('x')]`, `a ? require('x') : b` were never +; edges. src/ingest_importcap.h restores that reach from the captured node's ancestry. `import x = require('y')` +; (TS) is an import_require_clause with its own `source:` and matches nothing here, as it matched nothing before. +(import_statement + source: (string) @import.path) + +(export_statement + source: (string) @import.path) + +(call_expression + function: [ (identifier) (import) ] + arguments: (arguments . (string) @import.path)) diff --git a/src/ingest_importcap.h b/src/ingest_importcap.h index ffe957316..2c0ecf5e7 100644 --- a/src/ingest_importcap.h +++ b/src/ingest_importcap.h @@ -33,18 +33,26 @@ // by one capture — a capture names one node, and a group is many. No enum value reserves them: an unused // arm that no query can reach is a lie about what the vocabulary supports. // +// SECOND MEMBER: DepDialect::Web (TypeScript, TSX, JavaScript, the .astro frontmatter that rides TypeScript). +// Its three spellings are three patterns on the SAME capture name — `import … from 'x'`, a re-export +// `export … from 'x'`, and the CommonJS / dynamic call `require('x')` / `import('x')` — and one normaliser. +// Neither `@import.alias` nor `@import.names` is used for it, for the same reason C-family has none: the +// Include record a TS/JS import produces names a MODULE and nothing else. The bound names (`import { a as b }`) +// are a different record — captureJsImportFacts' RawBinds, in src/ingest_jsimports.h — which this slice does +// not touch, so there is no current JS/TS semantics for those two names to carry yet. +// // ORDERING. ts_query_cursor_next_match yields matches by the match's start byte, so a capture arrives in // SOURCE order and the emitted Includes do too — the same order captureIncludes' walk produced, because // emitCapturedImport runs straight from the capture dispatch with nothing staging it in between. The // import-role use-site refs move into the tags pass's own window, where orderReferences re-sorts them by // (startByte, name, role, isInherit) — a total key, so the move cannot reorder a published row. // -// WHAT THE WALK STILL OWNS, AND DOES NOT TRAVEL WITH THE MOVE. Three things captureIncludes computed from +// WHAT THE WALK USED TO OWN, AND DOES NOT TRAVEL WITH THE MOVE. Three things captureIncludes computed from // its own frame are re-derived here rather than dropped: the import-container nesting bound -// (importContainerDepth + the same DISCLOSE, below), the lazy bit a TS/JS require needs from `insideFn`, -// and Ruby's innermost-open index for the constant-receiver dedupe. The first is recovered now because it -// is one parent hop per import; the other two are why a language still on the walk cannot move until the -// emission can re-derive them from ancestry too — the tags pass never sees a walk frame. +// (importContainerReach + the same DISCLOSE, below), the lazy bit a TS/JS require needs from `insideFn` +// (the same ancestor loop, one more bit), and Ruby's innermost-open index for the constant-receiver dedupe. +// The first two are recovered because each is one parent hop per import; the third is why Ruby cannot move +// until the emission can re-derive it from ancestry too — the tags pass never sees a walk frame. #include "depdialect.h" // DepDialect + dependencyDialect — the unit one normaliser is written per @@ -54,8 +62,16 @@ namespace rw namespace { -// The import-container REACH of a captured directive, and its DEPTH — the walk's own two rules, -// re-derived from the captured node because the walk no longer runs for this language. +// What importContainerReach reads off one directive's ancestry. +struct ImportReach +{ + std::uint16_t depth = 0; // containers above the directive, stopped at kMaxImportContainerDepth + 1 + bool reachable = true; // every node between the file root and the directive is an import container + bool insideFn = false; // some such container is a function body (kJsFunctionContainers): TS/JS lazy bit +}; + +// The import-container REACH of a captured directive, its DEPTH and its function-body bit — the walk's own +// rules, re-derived from the captured node because the walk no longer runs for this language. // // REACH. captureIncludes seeded the frame stack with root's DIRECT children and then entered only // `isImportContainer` nodes, so a directive was reachable exactly when every node between the file root @@ -71,10 +87,9 @@ namespace // The file root is deliberately NOT tested: its children are what the walk always started from, so // `isImportContainer(root)` being false must not reject a file-scope include. The loop stops when // `p`'s own parent is null, which is the root. -std::uint16_t importContainerReach( TSNode directive, Lang lang, bool& reachable ) noexcept +ImportReach importContainerReach( TSNode directive, Lang lang ) noexcept { - std::uint16_t depth = 0; - reachable = true; + ImportReach reach; for( TSNode p = ts_node_parent( directive ); !ts_node_is_null( p ); p = ts_node_parent( p ) ) { if( ts_node_is_null( ts_node_parent( p ) ) ) @@ -83,15 +98,18 @@ std::uint16_t importContainerReach( TSNode directive, Lang lang, bool& reachable } if( !isImportContainer( lang, ts_node_type( p ) ) ) { - reachable = false; // the walk would not have entered this node + reach.reachable = false; // the walk would not have entered this node break; } - if( ++depth > kMaxImportContainerDepth ) + // The walk's `childInsideFn = frame.insideFn || isFunctionLike( lang, t )` is sticky downward, so the + // directive is inside a function exactly when ANY container above it is a function-body kind. + reach.insideFn = reach.insideFn || isFunctionLike( lang, ts_node_type( p ) ); + if( ++reach.depth > kMaxImportContainerDepth ) { break; // STOPS AT THE BOUND: past it the answer cannot change, and } // a hostile file must not buy an unbounded parent walk } - return depth; + return reach; } // One captured `@import.path`, normalised. `text` empty is the "not an import after all" signal and the @@ -101,6 +119,7 @@ struct ImportSpec { std::string text; bool isAngle = false; // CFamily: is external (unresolvable without a build system), "x.h" is not + bool isLazy = false; // Web: a require()/import() CALL written inside a function body (Include::isLazy) }; // ── DepDialect::CFamily ─────────────────────────────────────────────────────────────────────────────── @@ -131,20 +150,66 @@ ImportSpec normaliseCFamilyImport( TSNode directive, std::string_view raw, std:: return out; } +// ── DepDialect::Web ────────────────────────────────────────────────────────────────────────────────── +// TypeScript, TSX, JavaScript and the .astro frontmatter. Two directive shapes reach this normaliser, told +// apart by the DIRECTIVE node (the captured string's statement, or its call): +// import_statement / export_statement — `import … from 'x'`, `import 'x'`, `import type …`, and the re-exports +// `export * from`, `export * as n from`, `export { a as b } from`, `export type { T } from`. The grammar +// gives all of them the same `source:` string, which is the whole capture; every other export_statement +// has no `source:` and is not matched by the query at all. +// call_expression — the CommonJS `require('x')` and the dynamic `import('x')`. A query cannot say "the +// callee's TEXT is require" (tags-pass predicates never run), so the three guards that keep an ordinary +// call out of the dependency graph stay C++: jsModuleLoadTarget (src/ingest_relations.h), UNCHANGED by +// this round and shared with captureJsImportFacts, reads the callee text, the argument count and the +// string. The query only says where a candidate call is; the guards say whether it is a module load. +// The lazy bit (a hit inside a function body) comes from the caller's ancestor walk, never from here. +// `import x = require('y')` (TS) is `import_require_clause` with its own `source:` and matches no pattern — +// the extractor never read it either, so it is still not an edge: a disclosed floor, unchanged by the round. +ImportSpec normaliseWebImport( TSNode directive, TSNode pathNode, bool insideFn, std::string_view src ) +{ + ImportSpec out; + if( kindIs( ts_node_type( directive ), "call_expression" ) ) + { + out.text = jsModuleLoadTarget( directive, src ); + out.isLazy = insideFn && !out.text.empty(); // kParserVer 72: a hit found inside a function body is LAZY + return out; + } + out.text = importSpecifierText( pathNode, src ); // strips the one quote pair + return out; +} + // ── the dispatch: ONE normaliser per DepDialect ────────────────────────────────────────────────────── -// CFamily is the only dialect that has moved over so far; every other arm returns an empty ImportSpec, +// CFamily and Web are the dialects that have moved over so far; every other arm returns an empty ImportSpec, // which is what makes the capture INERT rather than wrong on a language whose tags.scm has not adopted // `@import.path` yet. Each `case` is deleted as its language moves, and the compiler then names every // other dialect still to do — the same "one language at a time" landing the issue asks for. -ImportSpec normaliseImportSpecifier( DepDialect dialect, TSNode directive, std::string_view raw, std::string_view src ) +ImportSpec normaliseImportSpecifier( DepDialect dialect, TSNode directive, TSNode pathNode, bool insideFn, std::string_view src ) { switch( dialect ) { - case DepDialect::CFamily: return normaliseCFamilyImport( directive, raw, src ); - default: return {}; // no language but C-family captures @import.path yet + case DepDialect::CFamily: return normaliseCFamilyImport( directive, nodeTextOf( pathNode, src ), src ); + case DepDialect::Web: return normaliseWebImport( directive, pathNode, insideFn, src ); + default: return {}; // no other language captures @import.path yet } } +// True for a dialect whose directives come from `@import.path` and NOT from captureIncludes' walk. The one +// place the walk is switched off (captureSideFacts) asks this, so a dialect cannot be moved in the normaliser +// above and still be walked a second time, or walked-off without a normaliser. +inline bool importsFromCapture( DepDialect dialect ) noexcept +{ + return dialect == DepDialect::CFamily || dialect == DepDialect::Web; +} + +// The DIRECTIVE a captured specifier belongs to — the node both records are SITED at. For a C include or a +// JS import/re-export it is the specifier's parent; for a call it is the call, one hop further, because the +// specifier sits in the call's `arguments`. +TSNode importDirectiveOf( TSNode pathNode ) noexcept +{ + const TSNode parent = ts_node_parent( pathNode ); + return kindIs( ts_node_type( parent ), "arguments" ) ? ts_node_parent( parent ) : parent; +} + // One captured `@import.path` → the Include record plus its ABS-3 import-role use-site ref, which is // exactly the pair captureIncludes' `emitDirective` lambda minted for a C-family directive. // @@ -178,19 +243,19 @@ ImportSpec normaliseImportSpecifier( DepDialect dialect, TSNode directive, std:: void emitCapturedImport( TSNode pathNode, std::uint32_t fileId, Lang lang, std::string_view src, std::vector& includes, std::vector& refs, ExtractShortfall& shortfall ) { - const TSNode directive = ts_node_parent( pathNode ); - bool reachable = false; - if( importContainerReach( directive, lang, reachable ) > kMaxImportContainerDepth ) + const TSNode directive = importDirectiveOf( pathNode ); + const ImportReach reach = importContainerReach( directive, lang ); + if( reach.depth > kMaxImportContainerDepth ) { DISCLOSE( shortfall, ExtractShortfall::DisclosureWhy::ImportNestingTooDeep, "ingest: import-container nesting past the depth bound — deeper imports not captured" ); return; // the same degrade the walk performed: not captured, file still indexed } - if( !reachable ) + if( !reach.reachable ) { return; // a non-container ancestor the walk would never have entered — see importContainerReach } - const ImportSpec spec = normaliseImportSpecifier( dependencyDialect( lang ), directive, nodeTextOf( pathNode, src ), src ); + const ImportSpec spec = normaliseImportSpecifier( dependencyDialect( lang ), directive, pathNode, reach.insideFn, src ); if( spec.text.empty() ) { return; @@ -205,7 +270,7 @@ void emitCapturedImport( TSNode pathNode, std::uint32_t fileId, Lang lang, std:: r.name = std::move( name ); refs.push_back( std::move( r ) ); } - includes.push_back( { fileId, spec.isAngle, false, false, ts_node_start_byte( directive ), false, spec.text } ); + includes.push_back( { fileId, spec.isAngle, spec.isLazy, false, ts_node_start_byte( directive ), false, spec.text } ); } } // namespace diff --git a/src/ingest_relations.h b/src/ingest_relations.h index b3f1e9ea5..bb38d7885 100644 --- a/src/ingest_relations.h +++ b/src/ingest_relations.h @@ -1099,8 +1099,10 @@ inline std::string phpUseTarget( TSNode useNode, std::string_view src ) // TS/JS `require("./x")` and dynamic `import("./x")` → the written specifier, or empty when this call // expression is not a module load. Its own function for the same reason csharpUsingTarget and phpUseTarget -// are: one AST shape per language stays a one-line call inside directiveTargetOf, whose grain is one -// branch per grammar spelling rather than one branch plus one nested scan. +// are: one AST shape per language stays a one-line call at the use site rather than one branch plus one nested +// scan. #358: its readers are now the DepDialect::Web normaliser (src/ingest_importcap.h — the captured +// `require`/`import` call) and captureJsImportFacts (src/ingest_jsimports.h — the binding a destructured +// require makes); directiveTargetOf no longer reads it, because the walk no longer visits TS/JS. // // THREE conditions, all required, and each is what keeps an ordinary call out of the dependency graph: // * the callee is the BARE identifier `require` or `import` — a member expression @@ -1867,7 +1869,8 @@ inline bool isPreprocConditional( const char* type ) noexcept // --expand's sibling lift, the SameInclude call-resolution tier, and --impact's import tier) was reading // an empty table and reporting the emptiness as a horizontal, cycle-free architecture. The four entries // below are exactly the statement forms a top-level `require` is written in; the call node itself is read -// by directiveTargetOf, which is what keeps every OTHER call expression out. +// by jsModuleLoadTarget (read by the Web normaliser in src/ingest_importcap.h since #358), which is what keeps +// every OTHER call expression out. // // EVERY ENTRY HAS A FIXTURE ARM in test/nestedimportfix — an entry with no arm is an untested claim, and // every parent chain below was read off a real parse with `--match`, never predicted from the grammar. @@ -1994,7 +1997,10 @@ inline bool isFunctionLike( Lang lang, const char* type ) noexcept return false; } -// TS/JS: every container a `require("./x")` / `import("./x")` call can legitimately sit under. +// TS/JS: every container a `require("./x")` / `import("./x")` call can legitimately sit under. #358: no walk +// reads this table for TS/JS any more — it is now the REACH RULE src/ingest_importcap.h re-derives from a +// captured `@import.path` node's ancestry (a specifier under a node that is not listed here was never an edge, +// and still is not), and the `insideFn` source for the lazy bit (kJsFunctionContainers, below). // // The first five are the statement forms that wrap a TOP-LEVEL require, kParserVer 71's set — read off // real parses, not predicted: @@ -2028,7 +2034,7 @@ inline bool isFunctionLike( Lang lang, const char* type ) noexcept // function: (_) @f)'` returns a real `function:` field of text "import" here, so the earlier miss was the // missing container, never a grammar shape jsModuleLoadTarget could not read). // EVERY ENTRY still needs the SAME three jsModuleLoadTarget guards (bare require/import callee, one arg, a -// string literal) — this table only widens WHERE the walk looks, never what counts as a hit. +// string literal) — this table only widens WHERE a specifier may sit, never what counts as a hit. inline constexpr std::array kJsImportContainers = { "lexical_declaration", "variable_declaration", "variable_declarator", "expression_statement", "assignment_expression", "statement_block", "return_statement", "labeled_statement", "export_statement", @@ -2094,8 +2100,7 @@ constexpr std::uint16_t kMaxImportContainerDepth = 256; // READS them stay separately readable — the walk is one shape, this is one branch per grammar spelling. // // Node types confirmed per grammar: Python import_statement/import_from_statement; Go/Swift -// import_declaration; Rust use_declaration + mod_item; C# using_directive; TS/JS call_expression for the -// CommonJS `require("./x")` and dynamic `import("./x")` spellings; plus the kParserVer-81 four (bash +// import_declaration; Rust use_declaration + mod_item; C# using_directive; plus the kParserVer-81 four (bash // `command`, lua `function_call`, ruby `call`, elixir `call`). LEVER-B B0: non-C imports capture the // CLEAN written specifier via grammar child fields (module path / quoted specifier / use argument), not a // sliced clause — the sound resolver input. @@ -2103,16 +2108,20 @@ constexpr std::uint16_t kMaxImportContainerDepth = 256; // #358: the C-family rows are GONE from this list. C++ preproc_include (path field, "" local vs <> // external) and C++ preproc_call with directive `#import` are captured as `@import.path` by the grammar's // own query and read by the DepDialect::CFamily normaliser (src/ingest_importcap.h), so this function is -// never asked about a C-family node. What is left is every language whose extraction needs the walk's own -// frame state — which is also the list of languages that have NOT moved over yet. +// never asked about a C-family node. The TS/JS rows (import_statement `source:`, the export_statement +// re-export, the CommonJS / dynamic call_expression) went the same way: `@import.path` in +// queries/{typescript,tsx,javascript}/tags.scm, DepDialect::Web normaliser. What is left is every language +// whose extraction needs the walk's own frame state — which is also the list of languages that have NOT +// moved over yet. // -// `lang` exists for exactly one branch: `call_expression` is a node type in most of our grammars, and a -// C++ or Rust function that happens to be named `require` must never manufacture a dependency edge. The -// language gate makes that impossible by construction rather than by relying on where the walk goes. +// `lang` exists for the language-gated branches: `call_expression` / `call` / `command` are node types in +// most of our grammars, and a C++ or Rust function that happens to be named `require` must never +// manufacture a dependency edge. The language gate makes that impossible by construction rather than by +// relying on where the walk goes. // -// `isLazy` (kParserVer 72, TS/JS; Ruby since parser version 82/83): true when `insideFn` says this call sits -// inside a closure container — see kJsFunctionContainers, the Ruby closure kinds, and captureIncludes' -// `insideFn` propagation below — or when the directive is a Ruby `autoload`. Allocates a std::string → +// `isLazy` (Ruby since parser version 82/83; TS/JS since kParserVer 72, now computed in ingest_importcap.h): +// true when `insideFn` says this call sits inside a closure container — see the Ruby closure kinds and +// captureIncludes' `insideFn` propagation below — or when the directive is a Ruby `autoload`. Allocates a std::string → // not noexcept. `isSymbolic` (parser version 82, Ruby only): the target is a CONSTANT resolved through the // corpus's own class/module index, never a path — see model.h Include::isSymbolic. // @@ -2122,8 +2131,9 @@ constexpr std::uint16_t kMaxImportContainerDepth = 256; // gone rather than documented as always-false. struct DirectiveTarget { std::string target; bool isLazy; bool isSymbolic; bool isReceiver; }; -// `insideFn` exists for exactly the same one branch `lang` does: whether the call_expression being read -// sits inside a TS/JS function body, per captureIncludes' walk — meaningless (and ignored) everywhere else. +// `insideFn` is the walk's sticky "inside a closure" bit, now read by Ruby alone (a constant receiver written +// inside a method / lambda / block is lazy). TS/JS used to be the other reader: its require()/import() branch +// moved to `@import.path` (#358), whose lazy bit is re-derived from ancestry in src/ingest_importcap.h. DirectiveTarget directiveTargetOf( TSNode n, const char* t, std::string_view src, Lang lang, bool insideFn ) { std::string target; @@ -2131,36 +2141,19 @@ DirectiveTarget directiveTargetOf( TSNode n, const char* t, std::string_view src bool isSymbolic = false; bool isReceiver = false; - if( kindIs( t, "import_statement" ) ) // Python `import a` / TS `import … from 'x'` + if( kindIs( t, "import_statement" ) ) // Python `import a` { // Prefer the grammar's specifier field over slicing the whole statement (LEVER-B B0: the resolver - // needs the REAL written specifier, not the clause). Empirically confirmed node shapes: + // needs the REAL written specifier, not the clause). Empirically confirmed node shape: // Python: import_statement name:(dotted_name|aliased_import) → the dotted module `pkg.mod`. - // TS/JS: import_statement source:(string) → the quoted specifier `'./x'`. - // A grammar that lacks the field resolves it to id 0, and fieldChild returns null for it, so a single - // capture covers both grammars without a per-language branch. - if( const TSNode src_ = fieldChild( n, NodeField::Source ); !ts_node_is_null( src_ ) ) - { - target = importSpecifierText( src_, src ); // TS/JS: strip the surrounding quotes - } - else if( const TSNode nm = fieldChild( n, NodeField::Name ); !ts_node_is_null( nm ) ) + // #358: the TS/JS `import … from 'x'` spelling of this node (and the `export … from 'x'` re-export that + // used to follow it) is GONE from this function — it is `@import.path` in queries/{typescript,tsx,javascript} + // /tags.scm now, read by the DepDialect::Web normaliser (src/ingest_importcap.h). + if( const TSNode nm = fieldChild( n, NodeField::Name ); !ts_node_is_null( nm ) ) { target = importSpecifierText( nm, src ); // Python: the dotted module head } } - else if( kindIs( t, "export_statement" ) && ( lang == Lang::TypeScript || lang == Lang::JavaScript ) ) - { - // kParserVer 124 (#220 part 2): a RE-EXPORT — `export { x } from './y'`, `export * from './y'`, - // `export * as ns from './y'`, `export type { T } from './y'` — loads its module exactly as an import does, - // and a barrel file is made of nothing else, so an import graph without them misses every cycle through a - // barrel. The grammar gives the specifier the same `source:` field import_statement has; every other - // export_statement (`export function …`, `export { x }`, `export default …`) has none and reads empty, and - // the walk still descends into it (it is a kJsImportContainers entry) for the requires inside. - if( const TSNode src_ = fieldChild( n, NodeField::Source ); !ts_node_is_null( src_ ) ) - { - target = importSpecifierText( src_, src ); - } - } else if( kindIs( t, "import_from_statement" ) ) // Python `from pkg.mod import Z` { // module_name:(dotted_name) → `pkg.mod`; module_name:(relative_import) → `.rel` / `..up` (leading @@ -2170,12 +2163,6 @@ DirectiveTarget directiveTargetOf( TSNode n, const char* t, std::string_view src target = importSpecifierText( mn, src ); } } - else if( kindIs( t, "call_expression" ) - && ( lang == Lang::TypeScript || lang == Lang::JavaScript ) ) // TS/JS `require("./x")` / `import("./x")` - { - target = jsModuleLoadTarget( n, src ); - isLazy = insideFn && !target.empty(); // kParserVer 72: a hit found inside a function body is LAZY - } else if( kindIs( t, "command" ) && lang == Lang::Bash ) // Bash `source x.sh` / `. x.sh` { target = bashSourceTarget( n, src ); @@ -2293,14 +2280,12 @@ DirectiveTarget directiveTargetOf( TSNode n, const char* t, std::string_view src // Capture #include / import directives (physical dependencies) by walking the file's top-level nodes — // and the bodies of anything that WRAPS a directive, which tree-sitter does not flatten: preprocessor -// conditionals in the C family and C#, and ordinary language constructs in Python / Rust / C# / TS / JS -// (see isImportContainer). Each node is read by directiveTargetOf above. -// kParserVer 72 cost note: TS/JS's kJsImportContainers now includes call_expression/object/arguments — -// containers a require() sits under with no other purpose — so the walk over a TS/JS file approaches full -// AST size rather than "top-level statements only". Bounded per-node (each node is visited once) and still -// depth-capped by kMaxImportContainerDepth; no perf gate exists in this tree to budget against (see -// CLAUDE.md's "best tool first, then fast" note), and CommonJS's own weight — an entire module system's -// worth of edges was previously invisible (kParserVer 71's LB-H measurement) — is the justification. +// conditionals in C# (the C family and TS/JS read `@import.path` instead, #358), and ordinary language +// constructs in Python / Rust / C# (see isImportContainer). Each node is read by directiveTargetOf above. +// kParserVer 72 cost note (HISTORICAL for TS/JS since #358, which retired this walk for them — the cost is gone +// with it): TS/JS's kJsImportContainers included call_expression/object/arguments — containers a require() +// sits under with no other purpose — so the walk over a TS/JS file approached full AST size rather than +// "top-level statements only". // ABS-3: each directive ALSO emits an import-role RawRef (name = the importable final segment) so the // use-site index reports import sites. The ref is file-scope (fromSymbol=kNoNode) — that is correct for // a directive at any container depth, and it NEVER enters the call graph (role != Call → skipped in @@ -2441,10 +2426,11 @@ void captureIncludes( TSNode root, Lang lang, std::uint32_t fileId, std::string_ // order, which keeps `incs`/`refs` in SOURCE order: the determinism contract is byte-identity, and an // order that depended on the walk shape would break it. Only ALLOWLISTED containers are entered, so a // language whose imports are top-level by rule (Go, Java) still costs exactly the old scan. - // `insideFn` (kParserVer 72, TS/JS only): true once the walk has descended through a function-body - // container (kJsFunctionContainers) — sticky for every descendant, never cleared, exactly like `depth` + // `insideFn` (kParserVer 72, TS/JS; Ruby's closure kinds since 83): true once the walk has descended through a + // function-body container (isFunctionLike) — sticky for every descendant, never cleared, exactly like `depth` // is monotonic. It rides the frame rather than being recomputed from ancestry because the walk never - // keeps the ancestor chain around: this is the one bit of it a lazy-require call needs. + // keeps the ancestor chain around: this is the one bit of it a lazy-require call needs. #358: TS/JS no longer + // walks (its lazy bit is re-derived from ancestry in ingest_importcap.h), so Ruby is its only reader here. // `openIdx` (parser version 83, Ruby only): the index in `constOpens` of the innermost class/module open the // frame sits inside — kNoOpenIdx at file level. It rides the frame for the same reason `insideFn` does (the // walk keeps no ancestor chain) and exists for the RECEIVER DEDUPE: a constant receiver is recorded once per diff --git a/src/ingest_sidecap.h b/src/ingest_sidecap.h index 6ddd733dc..0de4e8865 100644 --- a/src/ingest_sidecap.h +++ b/src/ingest_sidecap.h @@ -1546,11 +1546,12 @@ void captureSideFacts( const LangEntry& le, std::uint32_t fileId, std::string_vi #endif // Elixir directives share the lexical tags context below (ElixirContext), and as of #358 so do the - // C-family ones: their `@import.path` captures are normalised and emitted by captureTagsFacts, and - // captureIncludes no longer reads a preproc_include / preproc_call at all. The walk is pure cost - // for those languages — it descends only allowlisted containers, and a C-family include lives at - // file scope or inside a preprocessor guard, which the tags query already reaches unanchored. - if( le.lang != Lang::Elixir && dependencyDialect( le.lang ) != DepDialect::CFamily ) + // C-family ones and TypeScript/JavaScript's: their `@import.path` captures are normalised and emitted + // by captureTagsFacts, and captureIncludes no longer reads a preproc_include / preproc_call / + // import_statement / export_statement / require() call at all. The walk is pure cost for those + // languages — it descends only allowlisted containers, and the tags query already reaches the same + // nodes unanchored (importsFromCapture names the dialects; ingest_importcap.h keeps the reach rule). + if( le.lang != Lang::Elixir && !importsFromCapture( dependencyDialect( le.lang ) ) ) { captureIncludes( root, le.lang, fileId, src, incs, refs, binds, constOpens, shortfall ); } diff --git a/test/importcapcheck.sh b/test/importcapcheck.sh index b443f199a..02a3fda70 100755 --- a/test/importcapcheck.sh +++ b/test/importcapcheck.sh @@ -443,6 +443,281 @@ else no "non-deterministic output" fi +# ═══════════════════════════════════════════════════════════════════════════════════════════════════ +# JS/TS SLICE (DepDialect::Web) — TypeScript, TSX, JavaScript and the .astro frontmatter. +# +# WHAT THIS PINS. `import … from 'x'`, the re-exports (`export * from`, `export { a as b } from`, +# `export type { T } from`), and the CommonJS / dynamic `require('x')` / `import('x')` no longer come from +# captureIncludes' walk (directiveTargetOf's import_statement-source / export_statement / call_expression +# branches). They come from the same `@import.path` name, three patterns per queries/{typescript,tsx, +# javascript}/tags.scm, normalised by the DepDialect::Web arm of src/ingest_importcap.h. The EMITTED edges +# are exactly the ones the walk emitted, for every spelling — and for every spelling the walk did NOT emit: +# +# J1 the Web patterns are COMPILED IN, and the walk's TS/JS branches are GONE from src/ +# J2 exact edge list, in source order, for .ts .js .tsx .mjs .cjs .jsx and the .astro frontmatter — +# ES default/named/namespace/side-effect/type imports, three re-export spellings, CommonJS in every +# container shape the walk entered (declaration, assignment, export, callback, await, if/try/for…) +# J3 THE NEGATIVES — the query matches ANY `x('s')` call at ANY depth; every `neg_*` specifier below is +# a shape the walk never turned into an edge (member callee, `.foo` chained on the call, ternary, +# array, two arguments, a comment before the string, template literal, computed, `require.resolve`, +# a `new` argument, TS `import x = require()`, a `declare module` body, a class method) and none may +# become one. The leading text gate (callee is `require`/`import`) and the reach rule are what hold. +# J4 the LAZY bit — a require()/import() written inside a function body is lazy="1" on --impact's import +# tier; a top-level one is lazy="0"; one file required both ways is lazy="0" (the pair rule) +# J5 the use-site half — --uses reports the import-role ref of a named import +# J6 the DEPTH BOUND on blocks — kept at 255 and 256 containers, cut AND announced at 257, as the walk did +# J7 the two DELIBERATE disclosure differences (same two as the C-family arm 12c, same reason) +# J8 cache round-trip, a pre-change binary's cache accepted (no kParserVer bump), determinism +# +# The expected lists are the SPECIFICATION and were read off the pre-change binary, never reasoned out: this +# slice is a refactor, so main's set is the contract. The `neg_*` / `lazy_*` / `cjs_*` names say what each +# edge (or non-edge) is, so a diff reads without opening the fixture. + +web_fixture() { # $1 = an empty directory + local W="$1" + mkdir -p "$W" + cat > "$W/main.ts" <<'TS' +import def from "./es_default"; +import { named1, named2 as alias2 } from "./es_named"; +import * as ns from "./es_namespace"; +import "./es_sideeffect"; +import type { T } from "./es_type_named"; +import type DefT from "./es_type_default"; +import { type U, V } from "./es_inline_type"; +export * from "./re_star"; +export * as hns from "./re_star_as"; +export { x as y } from "./re_named_as"; +export type { Z } from "./re_type"; +import eq = require("./neg_import_equals"); +import type eq2 = require("./neg_import_equals_type"); +export = ns; +const r1 = require("./cjs_const"); +const { q1, q2 } = require("./cjs_destructured"); +var r2 = require('./cjs_var_single'); +require("./cjs_bare"); +module.exports = require("./cjs_module_exports"); +const dyn = await import("./dyn_await"); +const opt = require?.("./cjs_optional_call"); +const memb = require("./neg_member").foo; +const tern = cond ? require("./neg_ternary") : null; +const arr = [require("./neg_array")]; +const two = require("./neg_two_args", "./neg_second"); +const cm = require(/*c*/ "./neg_comment"); +const tpl = require(`./neg_template`); +const nonlit = require(neg_variable); +const other = foo.require("./neg_foo_require"); +const cat = require("./neg_concat" + sfx); +const res = require.resolve("./neg_resolve"); +const nw = new Foo(require("./neg_new_arg")); +const nest = foo(bar(require("./fn_arg_nested"))); +function f() { return require("./lazy_function"); } +const g2 = () => import("./lazy_arrow"); +class K { m() { return require("./neg_class_method"); } } +const o = { get G() { return require("./lazy_getter"); } }; +if (x) { require("./if_then"); } else { require("./if_else"); } +try { require("./try_body"); } catch (e) { require("./catch_body"); } +for (;;) { require("./for_body"); } +switch (x) { case 1: require("./switch_case"); } +declare module "amb" { import q from "./neg_ambient"; } +namespace NS { import w = require("./neg_ns_equals"); const z2 = require("./neg_ns_const"); } +export default require("./export_default_require"); +export const ex = require("./export_const_require"); +export function ef() { return import("./export_fn_dynamic"); } +foo(function () { require("./callback_fn"); }); +foo(() => require("./callback_arrow"), require("./callback_second")); +const pe = (require("./paren_require")); +const aw = await require("./await_require"); +label: require("./labeled"); +TS + cat > "$W/main.js" <<'JS' +import def from "./es_default"; +import { named1, named2 as alias2 } from "./es_named"; +import * as ns from "./es_namespace"; +import "./es_sideeffect"; +export * from "./re_star"; +export * as hns from "./re_star_as"; +export { x as y } from "./re_named_as"; +const r1 = require("./cjs_const"); +const { q1, q2 } = require("./cjs_destructured"); +require("./cjs_bare"); +module.exports = require("./cjs_module_exports"); +const dyn = await import("./dyn_await"); +const memb = require("./neg_member").foo; +const arr = [require("./neg_array")]; +const two = require("./neg_two_args", "./neg_second"); +const cm = require(/*c*/ "./neg_comment"); +const nonlit = require(neg_variable); +function f() { return require("./lazy_function"); } +const g2 = () => import("./lazy_arrow"); +class K { m() { return require("./neg_class_method"); } } +exports.run = function () { return require("./lazy_exports_fn"); }; +JS + cat > "$W/comp.tsx" <<'TSX' +import React from "react"; +import { Button } from "./tsx_button"; +export { Card } from "./tsx_card"; +export const View = () => ; +const side = require("./tsx_cjs"); +TSX + printf 'import m from "./mjs_import";\nexport * from "./mjs_reexport";\nconst c = require("./mjs_cjs");\n' > "$W/mod.mjs" + printf 'const a = require("./cjs_file");\nmodule.exports = { get b() { return require("./cjs_lazy"); } };\n' > "$W/mod.cjs" + printf 'import J from "./jsx_import";\nconst V = () => ;\n' > "$W/view.jsx" + printf '%s\n' '---' 'import A from "./astro_import";' 'const m = require("./astro_require");' 'export * from "./astro_reexport";' '---' '
{A}
' > "$W/page.astro" + # every specifier above names a real file, so each edge RESOLVES and the lazy bit and --uses are observable + local spec + for spec in $(cat "$W"/main.ts "$W"/main.js "$W"/comp.tsx "$W"/mod.mjs "$W"/mod.cjs "$W"/view.jsx "$W"/page.astro \ + | grep -o '"\./[A-Za-z_0-9]*"\|'"'"'\./[A-Za-z_0-9]*'"'" | tr -d "\"'" | sort -u); do + printf 'export const %s = 1;\n' "${spec#./}" > "$W/${spec#./}.js" + done +} + +# The expected Include targets per file, in source order, as one space-separated line each — read off the +# PRE-CHANGE binary on exactly this fixture. 36 / 15 / 4 / 3 / 2 / 1 / 3 edges. +W_MAIN_TS='./es_default ./es_named ./es_namespace ./es_sideeffect ./es_type_named ./es_type_default ./es_inline_type ./re_star ./re_star_as ./re_named_as ./re_type ./cjs_const ./cjs_destructured ./cjs_var_single ./cjs_bare ./cjs_module_exports ./dyn_await ./cjs_optional_call ./fn_arg_nested ./lazy_function ./lazy_arrow ./lazy_getter ./if_then ./if_else ./try_body ./catch_body ./for_body ./export_default_require ./export_const_require ./export_fn_dynamic ./callback_fn ./callback_arrow ./callback_second ./paren_require ./await_require ./labeled' +W_MAIN_JS='./es_default ./es_named ./es_namespace ./es_sideeffect ./re_star ./re_star_as ./re_named_as ./cjs_const ./cjs_destructured ./cjs_bare ./cjs_module_exports ./dyn_await ./lazy_function ./lazy_arrow ./lazy_exports_fn' +W_COMP_TSX='react ./tsx_button ./tsx_card ./tsx_cjs' +W_MOD_MJS='./mjs_import ./mjs_reexport ./mjs_cjs' +W_MOD_CJS='./cjs_file ./cjs_lazy' +W_VIEW_JSX='./jsx_import' +W_PAGE_ASTRO='./astro_import ./astro_require ./astro_reexport' + +WEB="$TMP/web"; web_fixture "$WEB" +"$BIN" "$WEB" --deps --no-cache > "$TMP/web.deps.xml" 2> "$TMP/web.deps.err" +web_incs(){ sed 's/= 6 occurrences of the JS/TS @import.path patterns in the binary, got '${WEB_PATTERNS:-none}'" +fi +for gone in 'kindIs( t, "export_statement" )' 'target = jsModuleLoadTarget( n, src );'; do + if grep -rqF "$gone" "$ROOT/src"; then + no "J1: the walk's TS/JS branch \`$gone\` is still in src/ — it should have been removed with this language" + else + ok "J1: the walk's TS/JS branch is gone from src/ ($gone)" + fi +done + +# ── J2 + J3: exact edge list per file, which also pins every negative ────────────────────────────────── +WEB_BAD=0 +for pair in "main.ts:$W_MAIN_TS" "main.js:$W_MAIN_JS" "comp.tsx:$W_COMP_TSX" "mod.mjs:$W_MOD_MJS" \ + "mod.cjs:$W_MOD_CJS" "view.jsx:$W_VIEW_JSX" "page.astro:$W_PAGE_ASTRO"; do + f=${pair%%:*}; want=${pair#*:} + got=$(web_incs "$f") + if [ -z "$got" ] || [ "$got" != "$want" ]; then + WEB_BAD=1 + no "J2: $f Include targets differ from the pre-change set" + printf ' expected: %s\n got: %s\n' "$want" "${got:-}" + fi +done +[ "$WEB_BAD" -eq 0 ] && ok "J2: exact Include set and order for .ts .js .tsx .mjs .cjs .jsx and the .astro frontmatter (64 edges)" +NEG=$(grep -o ' "$LZ/entry.js" +for n in eager lazy_only both; do printf 'function %s_fn() { return 1; }\nmodule.exports = %s_fn;\n' "$n" "$n" > "$LZ/$n.js"; done +LZ_BAD=0 +for pair in eager_fn:0 lazy_only_fn:1 both_fn:0; do + s=${pair%%:*}; want=${pair#*:} + got=$("$BIN" "$LZ" --impact="$s" --no-cache 2>/dev/null | grep -o '/dev/null | grep -o ' "$WB/$1/deep.js" + local i=0 + { while [ "$i" -lt "$1" ]; do printf '{'; i=$((i + 1)); done + printf ' require("./deep"); ' + i=0; while [ "$i" -lt "$1" ]; do printf '}'; i=$((i + 1)); done; printf '\n'; } > "$WB/$1/a.js" +} +WB_BAD=0 +for n in 254 255 256; do + web_bound "$n" + e=$("$BIN" "$WB/$n" --deps --no-cache 2>/dev/null | grep -o '/dev/null | grep -c 'why="extract-partial"' | tr -d '[:space:]' || true) + if [ "$n" -le 255 ]; then want_e=1; want_f=0; else want_e=0; want_f=1; fi + if [ "$e" != "$want_e" ] || [ "$f" != "$want_f" ]; then + WB_BAD=1 + no "J6: $n nested blocks: edge=$e extract-partial=$f (want $want_e/$want_f)" + fi +done +[ "$WB_BAD" -eq 0 ] && ok "J6: depth bound — kept under 255 and 256 containers, cut AND announced under 257 (edges identical to the walk's)" + +# ── J7: the two DELIBERATE disclosure differences, in both directions (the C-family arm 12c, for JS) ──── +# (i) QUIETER: `require('./deep')` is ITSELF a container, so the walk refused to descend into it from depth +# 256 and announced, although the edge was kept and nothing was dropped. n=255 above is that shape: edge +# kept, and this binary says nothing because nothing was lost (the walk said extract-partial). +# (ii) LOUDER: 300 blocks inside a class METHOD. The walk never entered the class (class_declaration is not +# an import container) so it said nothing; the ancestor loop reaches the bound first and announces. The +# safe direction — a floor that says so — and the same trade the C-family slice accepted. +WL="$TMP/wloud"; mkdir -p "$WL"; printf 'export const deep = 1;\n' > "$WL/deep.js" +{ printf 'class K { m() { '; i=0; while [ "$i" -lt 300 ]; do printf '{'; i=$((i + 1)); done + printf ' require("./deep"); '; i=0; while [ "$i" -lt 300 ]; do printf '}'; i=$((i + 1)); done; printf ' } }\n'; } > "$WL/a.js" +LE=$("$BIN" "$WL" --deps --no-cache 2>/dev/null | grep -o '/dev/null | grep -c 'why="extract-partial"' | tr -d '[:space:]' || true) +QF=$("$BIN" "$WB/255" --skipped --no-cache 2>/dev/null | grep -c 'why="extract-partial"' | tr -d '[:space:]' || true) +if [ "$LE" = "0" ] && [ "$LF" = "1" ] && [ "$QF" = "0" ]; then + ok "J7: disclosure differences hold — silent when the edge was kept (255 blocks), loud when the bound cut an unreachable import (class method)" +else + no "J7: class-method-under-300: edges=$LE partial=$LF (want 0/1); 255-blocks partial=$QF (want 0)" +fi + +# ── J8: cache round-trip and a pre-change cache ───────────────────────────────────────────────────── +WC="$TMP/web.cache.bin" +"$BIN" "$WEB" --cache="$WC" --deps > "$TMP/web.cold.xml" 2>/dev/null +"$BIN" "$WEB" --cache="$WC" --deps > "$TMP/web.warm.xml" 2>/dev/null +if cmp -s "$TMP/web.cold.xml" "$TMP/web.warm.xml" && cmp -s "$TMP/web.cold.xml" "$TMP/web.deps.xml"; then + ok "J8: warm == cold == --no-cache on --deps for the JS/TS fixture (isLazy round-trips)" +else + no "J8: JS/TS --deps differs between cold, warm and --no-cache" +fi +if [ -n "$BASE_BIN" ] && [ -x "$BASE_BIN" ]; then + WP="$TMP/web.pre.bin"; rm -f "$WP" + "$BASE_BIN" "$WEB" --cache="$WP" --deps >/dev/null 2>&1 + cp "$WP" "$TMP/web.pre.bin.copy" + "$BIN" "$WEB" --cache="$WP" --deps > "$TMP/web.prewarm.xml" 2>/dev/null + if cmp -s "$TMP/web.prewarm.xml" "$TMP/web.deps.xml" && cmp -s "$WP" "$TMP/web.pre.bin.copy"; then + ok "J8: a pre-change binary's JS/TS cache is accepted unchanged and reads identically — no kParserVer bump is owed" + else + no "J8: a pre-change JS/TS cache reads differently or was rewritten — extraction output changed, kParserVer must be bumped" + fi +else + skip "J8: pre-change-cache arm for JS/TS (set RIPWIRE_BASE_BIN= to run it)" +fi +"$BIN" "$WEB" --no-cache --deps > "$TMP/web.d2.xml" 2>/dev/null +cmp -s "$TMP/web.deps.xml" "$TMP/web.d2.xml" && ok "J8: deterministic (two --no-cache JS/TS runs identical)" || no "J8: non-deterministic JS/TS output" + # ── well-formed XML ─────────────────────────────────────────────────────────────────────────────────── if command -v xmllint >/dev/null 2>&1; then if xmllint --noout "$TMP/cold.xml" 2>/dev/null; then ok "xml well-formed"; else no "xml malformed"; fi From 3a0a2ebf7f3716a07d362c6c2d41584b81c579a8 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 10:41:17 -0400 Subject: [PATCH 039/170] perf(graph): hoist the per-call candidate walks of a many-definition name into a per-(name, language) memo The owner's counter run (sudo, PMC) shows the resolve loop compute-bound (IPC 4.08, l1d 3.5 MPKI): the fix is less work, not another layout. A stage-timed scratch build at 70cc6a06 put the module-scope calls' loop time mostly in three walks that decide the same thing for every call to one name from one language: the language filter that builds the candidate list (the largest stage), the namespace gate over it, and the same-file tier. They are now computed once per (byName entry, caller language), on first use, for lists of 16 or more on the unnarrowed whole-name path of a single root with nothing yet in cand: the language filter's survivors, whether the namespace gate would remove any of them for each role, and whether the list is file-sorted. Every later call copies the survivors, skips a gate the memo proved removes nothing, and takes its same-file tier as one binary-searched run. The memo is dropped for that call when the Ruby side cut replaces the list or any gate or guard removes a candidate. Same survivors, same order, same tiers. The --pin-census (every decided call site) is byte-identical to the base binary's on snap, textual, hono and tmux. Resolve loop, profile flavour on a loaded machine (load 33-37, 9 interleaved runs, min): 135.8 -> 61.4 ms. test/localitycheck.sh arm 10: 36 definitions of one name across shell and Python, each file's module-scope call binds its own file's definition (a memo keyed without the language, or a misplaced same-file run, turns it red). Co-Authored-By: Claude Opus 5.5 --- src/graph.h | 87 +++++++++++++++++++++++++++++++++++++++++-- test/localitycheck.sh | 22 +++++++++++ 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/graph.h b/src/graph.h index c30f72dc9..1bacd28e8 100644 --- a/src/graph.h +++ b/src/graph.h @@ -5764,6 +5764,52 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null } ENSURES( symLang.size() == N && symKind.size() == N && symFileId.size() == N, "the dense views cover every symbol id the loop can hold" ); + // ---- the per-(name, language) candidate memo: work that is invariant across the calls to one name --------------- + // For a name with many definitions, three walks over its list decide the same thing for every call written in the + // same language: which candidates survive the language filter (the name-based fallback's admission loop), whether + // the namespace gate removes any of them for the call's role, and how the survivors fall by file. The resolve loop + // used to redo all three per call — 650 candidates for every one of ~28k module-scope `ok` / `no` calls in this + // repository's test scripts (the cand-build stage alone was the loop's largest). This memo computes them ONCE per + // (byName entry, caller language), on the first call that needs them, and every later call copies the survivors and + // finds its same-file tier by binary search over the file-sorted list. Only the unnarrowed whole-name path reads it + // (the list handed in IS the byName entry, single root, nothing in cand yet), only for lists of kLangCandMemoMin or + // more, and `candMemo` is dropped the moment anything but a removal could have touched cand (the Ruby side cut), or + // any removal did (its size no longer matches). Same survivors, same order, same tiers: the answer is unchanged. + struct LangCandidates + { + std::vector ids; // the name's candidates the language filter admits, in list order + std::uint32_t nsPassRoles = 0; // bit k: every one passes namespaceCompatible( RefRole( k ), … ) + bool sortedByFile = false; // ids is non-decreasing in symFileId, so one file's ids are one run + }; + struct NameLangCandidates + { + std::uint32_t filledLangs = 0; // bit k: byLang[ k ] is computed + std::array byLang; + }; + static_assert( kLangCount <= 32 && kRefRoleCount <= 32, "the memo's language and role sets are 32-bit masks" ); + constexpr std::size_t kLangCandMemoMin = 16; // below this the walks are cheaper than the memo lookup + HashMap*, NameLangCandidates> langCandMemo; + langCandMemo.reserve( 1024 ); + const auto fillLangCandidates = [ & ]( const rw::SmallVec& ids, Lang lang, LangCandidates& out ) + { + for( NodeId c : ids ) + { + if( langCompatible( symLang[ c ], lang ) ) + { + out.ids.push_back( c ); + } + } + for( std::size_t roleIndex = 0; roleIndex < kRefRoleCount; ++roleIndex ) + { + const RefRole role = static_cast( roleIndex ); + if( std::all_of( out.ids.begin(), out.ids.end(), [ & ]( NodeId c ) { return namespaceCompatible( role, symKind[ c ] ); } ) ) + { + out.nsPassRoles |= 1u << roleIndex; + } + } + out.sortedByFile = std::is_sorted( out.ids.begin(), out.ids.end(), [ & ]( NodeId a, NodeId b ) { return symFileId[ a ] < symFileId[ b ]; } ); + }; + // ---- census arming + the ORACLE side (eval-only; src/pincensus.h) ------------------------------ // The oracle rows are a straight transcription of the overlay's own (from, calleeName) → target table // into the census's id space — the same table graph.h consults below, so the two sides of the join @@ -5822,6 +5868,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null cand.clear(); tier.clear(); + const LangCandidates* candMemo = nullptr; // set when cand is exactly a memoized list (see langCandMemo) float tierConf = 1.0f; // tier 1: same file (default; overridden below) // census bookkeeping (inert unless armed): which narrowing stages actually FIRED on this site, and // how wide the tier was when it reached the locality tie-break. Read only at the emission point. @@ -6388,6 +6435,21 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null continue; } } + else if( !multiRoot && nameIds == baseIds && baseIds == &it->second && cand.empty() && nameIds->size() >= kLangCandMemoMin ) + { + // the whole-name path for a name with many definitions: the language filter's survivors, memoized + // per (name, language) — sameRoot is constant-true on a single root, so they are this loop's own + NameLangCandidates& memo = langCandMemo[ nameIds ]; + const std::size_t langIdx = static_cast( r.lang ); + ASSUME( langIdx < kLangCount, "a reference's language is a Lang enumerator" ); + if( ( memo.filledLangs >> langIdx & 1u ) == 0 ) + { + fillLangCandidates( *nameIds, r.lang, memo.byLang[ langIdx ] ); + memo.filledLangs |= 1u << langIdx; + } + candMemo = &memo.byLang[ langIdx ]; + cand.assign( candMemo->ids.begin(), candMemo->ids.end() ); + } else { for( NodeId c : *nameIds ) @@ -6438,6 +6500,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null : RubyCut::Unchanged; cut != RubyCut::Unchanged ) { + candMemo = nullptr; // cand is the side cut's list now, not the memoized one cand.swap( filtScratch ); if( cut == RubyCut::Declined ) { @@ -6461,7 +6524,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null // uniquely a macro by construction); see namespaceCompatible's comment for the derivation and // test/nsfiltercheck.sh for the gate that keeps it that way. It is here because it is the seam a // future round would edit, and a silent edit is the failure mode the gate exists to catch. - if( !cand.empty() ) + if( !cand.empty() && !( candMemo != nullptr && ( candMemo->nsPassRoles >> static_cast( r.role ) & 1u ) != 0 ) ) { std::size_t keepCount = 0; for( std::size_t ci = 0; ci < cand.size(); ++ci ) @@ -6494,6 +6557,10 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null disposition = vetoExternal( r ); // nothing inside std answers → external=, counted External continue; } + if( candMemo != nullptr && cand.size() != candMemo->ids.size() ) + { + candMemo = nullptr; // a gate or guard above removed a candidate: cand is no longer the memoized list + } // ---- tier ladder (the name-based fallback) — SKIPPED when SCIP pinned this site, and for Rule 2's class-identity CLAIM (a type fact, not a locality guess) const bool identityClaim = narrowed && narrower.identityClaimFor( r ); @@ -6544,11 +6611,23 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null } else { - for( NodeId c : cand ) + if( candMemo != nullptr && candMemo->sortedByFile ) + { + // the memoized list is file-sorted, so this file's candidates are one run, in list order + const auto fileLess = [ & ]( NodeId c, std::uint32_t fileId ) noexcept { return symFileId[ c ] < fileId; }; + const auto lessFile = [ & ]( std::uint32_t fileId, NodeId c ) noexcept { return fileId < symFileId[ c ]; }; + const auto runBegin = std::lower_bound( cand.begin(), cand.end(), r.fileId, fileLess ); + const auto runEnd = std::upper_bound( runBegin, cand.end(), r.fileId, lessFile ); + tier.insert( tier.end(), runBegin, runEnd ); + } + else { - if( symFileId[ c ] == r.fileId ) + for( NodeId c : cand ) { - tier.push_back( c ); // tier 1: same file + if( symFileId[ c ] == r.fileId ) + { + tier.push_back( c ); // tier 1: same file + } } } if( tier.empty() ) // tier 2: same directory diff --git a/test/localitycheck.sh b/test/localitycheck.sh index abf15765d..7526dde0b 100755 --- a/test/localitycheck.sh +++ b/test/localitycheck.sh @@ -169,5 +169,27 @@ expectSite "(8)" Decoy::peek peek "peek@1" locality # ── 9) control — Rule 2 narrows a typed local before the tie-break: one precise edge, untouched. expectSite "(9)" Decoy::typedLocal pick "pick@1" receiver-rule +# ── 10) (cli-perf-068) the per-(name, language) candidate memo answers every call as the plain walk did. One name, `ok`, +# is defined in 18 shell scripts AND 18 Python files (36 definitions, past the memo's 16-candidate floor), and each +# file calls its own `ok` at module scope. The same-file tier must pick the caller's OWN file in the caller's OWN +# language for every file, whichever language filled the memo first: a memo keyed without the language, or a +# same-file run found in the wrong place, binds some file's call elsewhere (or nowhere) and turns these red. +MF="$( mktemp -d )" +for i in $( seq 1 18 ); do + printf 'ok() { echo "$1"; }\nok "bash %s"\nok "again"\n' "$i" > "$MF/s$i.sh" + printf 'def ok(x):\n return x\nok("py %s")\n' "$i" > "$MF/p$i.py" +done +memoBad=0 +for i in 1 2 9 17 18; do + for f in "s$i.sh" "p$i.py"; do + out="$( "$BIN" "$MF" --no-cache --callers="$f:ok" 2>/dev/null )" + if printf '%s' "$out" | grep -q 'count="1"' && printf '%s' "$out" | grep -q "p=\"$f:1\""; then :; else + memoBad=1; no "(10) $f:ok should have exactly its own file's module scope as caller: $( printf '%s' "$out" | grep -o ']*>' | head -1 )" + fi + done +done +rm -rf "$MF" +if [ "$memoBad" -eq 0 ]; then ok "(10) 36 same-named defs over two languages: every file's module-scope call binds its own file's ok (10 files sampled)"; fi + [ "$fail" -eq 0 ] && echo "ALL PASS" || echo "SOME FAILED" exit "$fail" From f4e50ad2e5aa0bfb24550fa3c6faf599fe2aa545 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 10:54:35 -0400 Subject: [PATCH 040/170] refactor(#358): self-checks on the TS/JS import-capture arm Co-Authored-By: Claude Sonnet 5.5 --- src/ingest_importcap.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ingest_importcap.h b/src/ingest_importcap.h index 2c0ecf5e7..e5eb5b218 100644 --- a/src/ingest_importcap.h +++ b/src/ingest_importcap.h @@ -89,6 +89,8 @@ struct ImportReach // `p`'s own parent is null, which is the root. ImportReach importContainerReach( TSNode directive, Lang lang ) noexcept { + const bool directiveIsNode = !ts_node_is_null( directive ); // hoisted: a promise holds no call (selfcheckcheck C) + EXPECTS( directiveIsNode, "the directive is a node of the parsed tree: importDirectiveOf never returns null for a captured specifier" ); ImportReach reach; for( TSNode p = ts_node_parent( directive ); !ts_node_is_null( p ); p = ts_node_parent( p ) ) { @@ -109,6 +111,7 @@ ImportReach importContainerReach( TSNode directive, Lang lang ) noexcept break; // STOPS AT THE BOUND: past it the answer cannot change, and } // a hostile file must not buy an unbounded parent walk } + ENSURES( reach.depth <= kMaxImportContainerDepth + 1, "the ancestor loop stops one past the bound, so a hostile file buys a bounded parent walk" ); return reach; } @@ -167,11 +170,14 @@ ImportSpec normaliseCFamilyImport( TSNode directive, std::string_view raw, std:: // the extractor never read it either, so it is still not an edge: a disclosed floor, unchanged by the round. ImportSpec normaliseWebImport( TSNode directive, TSNode pathNode, bool insideFn, std::string_view src ) { + const bool bothNodes = !ts_node_is_null( directive ) && !ts_node_is_null( pathNode ); + EXPECTS( bothNodes, "both nodes come from one captured match" ); ImportSpec out; if( kindIs( ts_node_type( directive ), "call_expression" ) ) { out.text = jsModuleLoadTarget( directive, src ); out.isLazy = insideFn && !out.text.empty(); // kParserVer 72: a hit found inside a function body is LAZY + ENSURES( !out.isLazy || !out.text.empty(), "a lazy bit never rides on an empty (dropped) target" ); return out; } out.text = importSpecifierText( pathNode, src ); // strips the one quote pair @@ -207,7 +213,12 @@ inline bool importsFromCapture( DepDialect dialect ) noexcept TSNode importDirectiveOf( TSNode pathNode ) noexcept { const TSNode parent = ts_node_parent( pathNode ); - return kindIs( ts_node_type( parent ), "arguments" ) ? ts_node_parent( parent ) : parent; + const bool hasParent = !ts_node_is_null( parent ); + ASSUME( hasParent, "a captured specifier is a string or path token, never the file root" ); + const TSNode directive = kindIs( ts_node_type( parent ), "arguments" ) ? ts_node_parent( parent ) : parent; + const bool hasDirective = !ts_node_is_null( directive ); + ENSURES( hasDirective, "an `arguments` list always belongs to a call" ); + return directive; } // One captured `@import.path` → the Include record plus its ABS-3 import-role use-site ref, which is From 948c4511731fb4ee077442e8b559f7d3ef961748 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 11:13:48 -0400 Subject: [PATCH 041/170] refactor(#358): keep the normaliser dispatch at 4 params; lazy bit ANDed at emission normaliseImportSpecifier keeps its (dialect, directive, raw, src) shape: the Web arm now reports only that the directive is a call, and emitCapturedImport ANDs it with the ancestor walk's insideFn. The captured-dialect test is a table lookup. The gate's JS/TS fixture is split into small functions. Co-Authored-By: Claude Sonnet 5.5 --- src/ingest_importcap.h | 30 +++++++++++++++--------------- test/importcapcheck.sh | 14 +++++++++++--- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/ingest_importcap.h b/src/ingest_importcap.h index e5eb5b218..064b59a37 100644 --- a/src/ingest_importcap.h +++ b/src/ingest_importcap.h @@ -122,7 +122,7 @@ struct ImportSpec { std::string text; bool isAngle = false; // CFamily: is external (unresolvable without a build system), "x.h" is not - bool isLazy = false; // Web: a require()/import() CALL written inside a function body (Include::isLazy) + bool lazyInClosure = false; // Web: this is a require()/import() CALL, so it is Include::isLazy iff it sits in a function body }; // ── DepDialect::CFamily ─────────────────────────────────────────────────────────────────────────────── @@ -165,22 +165,20 @@ ImportSpec normaliseCFamilyImport( TSNode directive, std::string_view raw, std:: // call out of the dependency graph stay C++: jsModuleLoadTarget (src/ingest_relations.h), UNCHANGED by // this round and shared with captureJsImportFacts, reads the callee text, the argument count and the // string. The query only says where a candidate call is; the guards say whether it is a module load. -// The lazy bit (a hit inside a function body) comes from the caller's ancestor walk, never from here. +// Whether the hit is LAZY (written inside a function body) is the ancestry's answer, not the text's: this +// arm only says the directive is a call (`lazyInClosure`), and emitCapturedImport ANDs it with the walk up. // `import x = require('y')` (TS) is `import_require_clause` with its own `source:` and matches no pattern — // the extractor never read it either, so it is still not an edge: a disclosed floor, unchanged by the round. -ImportSpec normaliseWebImport( TSNode directive, TSNode pathNode, bool insideFn, std::string_view src ) +ImportSpec normaliseWebImport( TSNode directive, std::string_view raw, std::string_view src ) { - const bool bothNodes = !ts_node_is_null( directive ) && !ts_node_is_null( pathNode ); - EXPECTS( bothNodes, "both nodes come from one captured match" ); ImportSpec out; if( kindIs( ts_node_type( directive ), "call_expression" ) ) { - out.text = jsModuleLoadTarget( directive, src ); - out.isLazy = insideFn && !out.text.empty(); // kParserVer 72: a hit found inside a function body is LAZY - ENSURES( !out.isLazy || !out.text.empty(), "a lazy bit never rides on an empty (dropped) target" ); + out.text = jsModuleLoadTarget( directive, src ); + out.lazyInClosure = !out.text.empty(); // kParserVer 72: a hit found inside a function body is LAZY (the caller knows where it sits) return out; } - out.text = importSpecifierText( pathNode, src ); // strips the one quote pair + out.text = std::string( pattern::stripQuotePair( raw ) ); // the captured node is a `string`: strip the one quote pair return out; } @@ -189,12 +187,12 @@ ImportSpec normaliseWebImport( TSNode directive, TSNode pathNode, bool insideFn, // which is what makes the capture INERT rather than wrong on a language whose tags.scm has not adopted // `@import.path` yet. Each `case` is deleted as its language moves, and the compiler then names every // other dialect still to do — the same "one language at a time" landing the issue asks for. -ImportSpec normaliseImportSpecifier( DepDialect dialect, TSNode directive, TSNode pathNode, bool insideFn, std::string_view src ) +ImportSpec normaliseImportSpecifier( DepDialect dialect, TSNode directive, std::string_view raw, std::string_view src ) { switch( dialect ) { - case DepDialect::CFamily: return normaliseCFamilyImport( directive, nodeTextOf( pathNode, src ), src ); - case DepDialect::Web: return normaliseWebImport( directive, pathNode, insideFn, src ); + case DepDialect::CFamily: return normaliseCFamilyImport( directive, raw, src ); + case DepDialect::Web: return normaliseWebImport( directive, raw, src ); default: return {}; // no other language captures @import.path yet } } @@ -202,9 +200,11 @@ ImportSpec normaliseImportSpecifier( DepDialect dialect, TSNode directive, TSNod // True for a dialect whose directives come from `@import.path` and NOT from captureIncludes' walk. The one // place the walk is switched off (captureSideFacts) asks this, so a dialect cannot be moved in the normaliser // above and still be walked a second time, or walked-off without a normaliser. +inline constexpr std::array kCapturedImportDialects = { DepDialect::CFamily, DepDialect::Web }; + inline bool importsFromCapture( DepDialect dialect ) noexcept { - return dialect == DepDialect::CFamily || dialect == DepDialect::Web; + return std::find( kCapturedImportDialects.begin(), kCapturedImportDialects.end(), dialect ) != kCapturedImportDialects.end(); } // The DIRECTIVE a captured specifier belongs to — the node both records are SITED at. For a C include or a @@ -266,7 +266,7 @@ void emitCapturedImport( TSNode pathNode, std::uint32_t fileId, Lang lang, std:: { return; // a non-container ancestor the walk would never have entered — see importContainerReach } - const ImportSpec spec = normaliseImportSpecifier( dependencyDialect( lang ), directive, pathNode, reach.insideFn, src ); + const ImportSpec spec = normaliseImportSpecifier( dependencyDialect( lang ), directive, nodeTextOf( pathNode, src ), src ); if( spec.text.empty() ) { return; @@ -281,7 +281,7 @@ void emitCapturedImport( TSNode pathNode, std::uint32_t fileId, Lang lang, std:: r.name = std::move( name ); refs.push_back( std::move( r ) ); } - includes.push_back( { fileId, spec.isAngle, spec.isLazy, false, ts_node_start_byte( directive ), false, spec.text } ); + includes.push_back( { fileId, spec.isAngle, spec.lazyInClosure && reach.insideFn, false, ts_node_start_byte( directive ), false, spec.text } ); } } // namespace diff --git a/test/importcapcheck.sh b/test/importcapcheck.sh index 02a3fda70..e1657a5fd 100755 --- a/test/importcapcheck.sh +++ b/test/importcapcheck.sh @@ -473,9 +473,7 @@ fi # slice is a refactor, so main's set is the contract. The `neg_*` / `lazy_*` / `cjs_*` names say what each # edge (or non-edge) is, so a diff reads without opening the fixture. -web_fixture() { # $1 = an empty directory - local W="$1" - mkdir -p "$W" +web_fixture_ts() { local W="$1" cat > "$W/main.ts" <<'TS' import def from "./es_default"; import { named1, named2 as alias2 } from "./es_named"; @@ -529,6 +527,8 @@ const pe = (require("./paren_require")); const aw = await require("./await_require"); label: require("./labeled"); TS +} +web_fixture_js() { local W="$1" cat > "$W/main.js" <<'JS' import def from "./es_default"; import { named1, named2 as alias2 } from "./es_named"; @@ -552,6 +552,8 @@ const g2 = () => import("./lazy_arrow"); class K { m() { return require("./neg_class_method"); } } exports.run = function () { return require("./lazy_exports_fn"); }; JS +} +web_fixture_small() { local W="$1" # .tsx .mjs .cjs .jsx and the .astro frontmatter cat > "$W/comp.tsx" <<'TSX' import React from "react"; import { Button } from "./tsx_button"; @@ -563,6 +565,8 @@ TSX printf 'const a = require("./cjs_file");\nmodule.exports = { get b() { return require("./cjs_lazy"); } };\n' > "$W/mod.cjs" printf 'import J from "./jsx_import";\nconst V = () => ;\n' > "$W/view.jsx" printf '%s\n' '---' 'import A from "./astro_import";' 'const m = require("./astro_require");' 'export * from "./astro_reexport";' '---' '
{A}
' > "$W/page.astro" +} +web_fixture_stubs() { local W="$1" # every specifier above names a real file, so each edge RESOLVES and the lazy bit and --uses are observable local spec for spec in $(cat "$W"/main.ts "$W"/main.js "$W"/comp.tsx "$W"/mod.mjs "$W"/mod.cjs "$W"/view.jsx "$W"/page.astro \ @@ -570,6 +574,10 @@ TSX printf 'export const %s = 1;\n' "${spec#./}" > "$W/${spec#./}.js" done } +web_fixture() { # $1 = an empty directory + mkdir -p "$1" + web_fixture_ts "$1"; web_fixture_js "$1"; web_fixture_small "$1"; web_fixture_stubs "$1" +} # The expected Include targets per file, in source order, as one space-separated line each — read off the # PRE-CHANGE binary on exactly this fixture. 36 / 15 / 4 / 3 / 2 / 1 / 3 edges. From 78e685b5c53ea1782401463d3b1ed416f9bb989c Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 11:19:58 -0400 Subject: [PATCH 042/170] refactor(profile): the language-family scope map is a per-Lang table, not a switch quality-delta read extractFamilyOf's switch as a clone of depdialect.h's dependencyDialect (switch-name-table). The house form for an enum-to-enum map is a constexpr table: one row per Lang in enumerator order, size-asserted against kLangCount. Profiling-only; no output changes. Co-Authored-By: Claude Opus 5.5 --- src/ingest_sidecap.h | 45 ++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/ingest_sidecap.h b/src/ingest_sidecap.h index b79270b58..ae1c9660e 100644 --- a/src/ingest_sidecap.h +++ b/src/ingest_sidecap.h @@ -1158,20 +1158,41 @@ bool prepareParserFor( TSParser* parser, const LangEntry& le ) enum class ExtractFamily : std::uint8_t { CFamily, Python, JsTs, JvmDotNet, GoRustSwiftDart, Scripting, Bash, DocsData, Count }; enum class ExtractPhase : std::uint8_t { Parse, SideFacts, TagsFacts, Count }; +// One row per Lang, in enumerator order (model.h): the size assert below fails when a language is appended without a row. +inline constexpr ExtractFamily kExtractFamilyByLang[] = { + ExtractFamily::CFamily, // Cpp + ExtractFamily::Python, // Python + ExtractFamily::JsTs, // TypeScript + ExtractFamily::GoRustSwiftDart, // Go + ExtractFamily::GoRustSwiftDart, // Rust + ExtractFamily::GoRustSwiftDart, // Swift + ExtractFamily::CFamily, // ObjC + ExtractFamily::DocsData, // Markdown + ExtractFamily::JsTs, // JavaScript + ExtractFamily::Bash, // Bash + ExtractFamily::JvmDotNet, // Java + ExtractFamily::Scripting, // Ruby + ExtractFamily::DocsData, // Unknown + ExtractFamily::DocsData, // Json + ExtractFamily::JvmDotNet, // CSharp + ExtractFamily::CFamily, // C + ExtractFamily::DocsData, // Toml + ExtractFamily::DocsData, // Yaml + ExtractFamily::Scripting, // Php + ExtractFamily::Scripting, // Lua + ExtractFamily::Scripting, // Elixir + ExtractFamily::GoRustSwiftDart, // Dart + ExtractFamily::JvmDotNet, // Kotlin + ExtractFamily::Scripting, // GDScript +}; +static_assert( std::size( kExtractFamilyByLang ) == kLangCount, "one profile family row per Lang, in enumerator order" ); +static_assert( kExtractFamilyByLang[ std::size_t( Lang::Kotlin ) ] == ExtractFamily::JvmDotNet && kExtractFamilyByLang[ std::size_t( Lang::C ) ] == ExtractFamily::CFamily, + "the table follows Lang's enumerator order" ); + constexpr ExtractFamily extractFamilyOf( Lang lang ) noexcept { - switch( lang ) - { - case Lang::Cpp: case Lang::C: case Lang::ObjC: return ExtractFamily::CFamily; - case Lang::Python: return ExtractFamily::Python; - case Lang::TypeScript: case Lang::JavaScript: return ExtractFamily::JsTs; - case Lang::Java: case Lang::Kotlin: case Lang::CSharp: return ExtractFamily::JvmDotNet; - case Lang::Go: case Lang::Rust: case Lang::Swift: case Lang::Dart: return ExtractFamily::GoRustSwiftDart; - case Lang::Ruby: case Lang::Php: case Lang::Lua: case Lang::Elixir: case Lang::GDScript: return ExtractFamily::Scripting; - case Lang::Bash: return ExtractFamily::Bash; - case Lang::Markdown: case Lang::Json: case Lang::Toml: case Lang::Yaml: case Lang::Unknown: return ExtractFamily::DocsData; - } - return ExtractFamily::DocsData; // a byte past the enum; a NEW Lang is a -Werror=switch error above + const std::size_t langIdx = static_cast( lang ); + return langIdx < kLangCount ? kExtractFamilyByLang[ langIdx ] : ExtractFamily::DocsData; } inline constexpr const char* kExtractScopeNames[ std::size_t( ExtractPhase::Count ) ][ std::size_t( ExtractFamily::Count ) ] = { From 93068d82b10d487938fd3688760d1f3f46cb74ca Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 11:19:58 -0400 Subject: [PATCH 043/170] docs: re-pin the showcase --at seed after the candidate memo moved rankGraphTeleport 7430 -> 7512 (el 7540); rankGraph 7553, anchoredLexicalRank 8199, graph_ambiguous 10529, graph_unresolved 12925 on this tree (the base binary reads the same gauges on it). Co-Authored-By: Claude Opus 5.5 --- docs/COMMANDS.md | 6 +++--- docs/captures/COMMANDS_showcase_2026-09-14.md | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 3cb02ee8a..72200a9e4 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -3350,11 +3350,11 @@ $ ./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:7430 +$ ./build/ripwire . --at=src/graph.h:7512 - - + + ``` diff --git a/docs/captures/COMMANDS_showcase_2026-09-14.md b/docs/captures/COMMANDS_showcase_2026-09-14.md index 9b1fa3a4a..bd4e8b474 100644 --- a/docs/captures/COMMANDS_showcase_2026-09-14.md +++ b/docs/captures/COMMANDS_showcase_2026-09-14.md @@ -3921,29 +3921,29 @@ ripwire 0.6.1 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=f8d4 # navigate — seeds, claims, slices, shapes -## `./build/ripwire . --at=src/graph.h:7430` +## `./build/ripwire . --at=src/graph.h:7512` *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:7430` +## `./build/ripwire . --callers=@src/graph.h:7512` *The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.* ````` - + - - + + From d4cb304b8389a0c9998e8d6e7d69f4e6fd53b1e2 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 11:24:03 -0400 Subject: [PATCH 044/170] refactor(#358): importsFromCapture as a switch, one case per dialect with a normaliser arm Co-Authored-By: Claude Sonnet 5.5 --- src/ingest_importcap.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ingest_importcap.h b/src/ingest_importcap.h index 064b59a37..d21f61882 100644 --- a/src/ingest_importcap.h +++ b/src/ingest_importcap.h @@ -200,11 +200,16 @@ ImportSpec normaliseImportSpecifier( DepDialect dialect, TSNode directive, std:: // True for a dialect whose directives come from `@import.path` and NOT from captureIncludes' walk. The one // place the walk is switched off (captureSideFacts) asks this, so a dialect cannot be moved in the normaliser // above and still be walked a second time, or walked-off without a normaliser. -inline constexpr std::array kCapturedImportDialects = { DepDialect::CFamily, DepDialect::Web }; - inline bool importsFromCapture( DepDialect dialect ) noexcept { - return std::find( kCapturedImportDialects.begin(), kCapturedImportDialects.end(), dialect ) != kCapturedImportDialects.end(); + switch( dialect ) // one case per dialect normaliseImportSpecifier has an arm for + { + case DepDialect::CFamily: + case DepDialect::Web: + return true; + default: + return false; + } } // The DIRECTIVE a captured specifier belongs to — the node both records are SITED at. For a C include or a From bf5ddcc7ee7b0dc60b36ee3646a88fda5263716b Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 11:36:35 -0400 Subject: [PATCH 045/170] =?UTF-8?q?test:=20ownerhopcheck=20=E2=80=94=20the?= =?UTF-8?q?=20symbol=20a=20--for=20question=20is=20about=20gets=20a=20hop?= =?UTF-8?q?=20slot=20first=20(red)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gate is written before the code it measures. Fixture test/ownerhopfix has TS, Python and C roots, each indexed on its own. The gap: the compact --for answer hop-expands only the first six positive-rank rows, inside a ~1 KB hop budget. A question that names the function it is about ("How does the gateway check a session token?" -> gateway(), rank 13) never gets that function's callee names. Graded answers missed gold callee items that way (a middleware's cookie/exception callees, a validator's async helpers). Contract the arms pin: - A ranked-head callable DEFINITION, not in a test file, whose whole name (leading _ # $ @ removed, ASCII case folded) equals a question word is an owner. The word needs one such definition at rank <= 24. Every same-named definition in the pool is shown, each as its own row. - Owner rows come first in , beyond the six-candidate cap, with qword="". At most 3 rows; the rest are disclosed with qword_cut= and next=. - The hop-slot rule still applies. An owner row lists all callee names up to a runaway guard (past it: capped="1" + next=). Name-only callees keep FE-B's via="name". - In the default regime, owner rows ride on top of the hop budget, so every earlier row is still served. Under an explicit ceiling they are funded first. A question with no owner, --json, and the MCP for verb are byte-identical (A/B handle RIPWIRE_NO_OWNER_HOP=1). Red at ef25ea06: 13 FAIL, 18 PASS, 2 named SKIP (FE-B hedges, base identity). Registered: regression.sh, shard weights, gate count 677 (generator). Co-Authored-By: Claude Opus 5.5 --- .github/pargates-shard-weights.json | 1 + README.md | 4 +- docs/EVALS.md | 6 +- present/deck5_ripwire_build.js | 6 +- test/ownerhopcheck.sh | 329 +++++++++++ test/ownerhopfix/c/table.c | 22 + test/ownerhopfix/c/table.h | 13 + test/ownerhopfix/c/walkers.c | 52 ++ test/ownerhopfix/py/pkg/broadcast.py | 530 ++++++++++++++++++ test/ownerhopfix/py/pkg/events.py | 105 ++++ test/ownerhopfix/py/pkg/propagate.py | 91 +++ test/ownerhopfix/py/pkg/reactive.py | 43 ++ test/ownerhopfix/ts/package.json | 1 + test/ownerhopfix/ts/src/context.ts | 5 + test/ownerhopfix/ts/src/helper/cookie.ts | 8 + test/ownerhopfix/ts/src/middleware/gateway.ts | 11 + test/ownerhopfix/ts/src/session/check.ts | 13 + test/ownerhopfix/ts/src/session/claims.ts | 63 +++ test/ownerhopfix/ts/src/session/types.ts | 10 + test/ownerhopfix/ts/test/gateway.test.ts | 3 + test/regression.sh | 2 +- 21 files changed, 1309 insertions(+), 9 deletions(-) create mode 100755 test/ownerhopcheck.sh create mode 100644 test/ownerhopfix/c/table.c create mode 100644 test/ownerhopfix/c/table.h create mode 100644 test/ownerhopfix/c/walkers.c create mode 100644 test/ownerhopfix/py/pkg/broadcast.py create mode 100644 test/ownerhopfix/py/pkg/events.py create mode 100644 test/ownerhopfix/py/pkg/propagate.py create mode 100644 test/ownerhopfix/py/pkg/reactive.py create mode 100644 test/ownerhopfix/ts/package.json create mode 100644 test/ownerhopfix/ts/src/context.ts create mode 100644 test/ownerhopfix/ts/src/helper/cookie.ts create mode 100644 test/ownerhopfix/ts/src/middleware/gateway.ts create mode 100644 test/ownerhopfix/ts/src/session/check.ts create mode 100644 test/ownerhopfix/ts/src/session/claims.ts create mode 100644 test/ownerhopfix/ts/src/session/types.ts create mode 100644 test/ownerhopfix/ts/test/gateway.test.ts diff --git a/.github/pargates-shard-weights.json b/.github/pargates-shard-weights.json index 203900190..c33e0053d 100644 --- a/.github/pargates-shard-weights.json +++ b/.github/pargates-shard-weights.json @@ -377,6 +377,7 @@ "oswin32logiccheck.sh": 30.0, "outlinecheck.sh": 1.5, "overbudgetcommentcheck.sh": 0.5, +"ownerhopcheck.sh": 53.2, "ownerscheck.sh": 5.2, "packcallersharecheck.sh": 0.9, "packtaskcheck.sh": 20.8, diff --git a/README.md b/README.md index 8b11b155a..97e06806e 100644 --- a/README.md +++ b/README.md @@ -2082,9 +2082,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published ### In the tests
-676 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +677 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures -`test/regression.sh` names **676 gate scripts** and is the authoritative list; +`test/regression.sh` names **677 gate scripts** and is the authoritative list; `python3 test/pargates.py . ./build/ripwire -j 6` runs the same set in parallel. On top of them sit the contracts that do not fit a unit test: two runs byte-identical, warm output identical to cold, output that pipes clean through `xmllint --noout`, a sanitizer build with `-fno-sanitize-recover=all`, and a diff --git a/docs/EVALS.md b/docs/EVALS.md index 1dd31c36f..03c782613 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 676 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 677 gate scripts plus the determinism, cache-transparency and golden contracts. | | **`--quality-delta`** | `src/quality.h` | Ten measured code-quality failure modes, reported only where a change made them worse. | ### The labeling protocol (why the held-out eval is allowed to disagree with the ranker) @@ -5888,7 +5888,7 @@ copy here would be exactly the dialect divergence that gate exists to catch. Com tags, wrap, stable-order defaults), seven individually invoked standalone gates (`g1freshcheck`, `skillscan`, `htmlexport`, `compresscheck`, `handoffcheck`, `releaseinstallcheck`, `taskroutecheck`), and a single loop -naming **676 gate scripts**, all of which exist on disk. +naming **677 gate scripts**, all of which exist on disk. `python3 test/pargates.py . ./build/ripwire -j 6` runs the same scripts in parallel so a full verification fits in one sitting. It does not modify `regression.sh`. @@ -6991,7 +6991,7 @@ Listed because the reason is more useful than the silence. shipped**. See `bench/locbench/anchorhop_calib.json`. The mention anchor's reproducible numbers are the ablations in §4. - **A single round gate-count.** Two in-tree numbers disagree (`test/pargates.py`'s docstring says - ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 676. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 677. The loop is the authority; the stale docstrings are a known drift. Since 2026-09-10 the number is not written by hand anywhere: `docs/gatecount_build.py` derives it from the loop and rewrites every published site, `test/gatecountcheck.sh` fails if any of them drifts, and `test/manifestcheck.sh` diff --git a/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index b07b737e7..42ceb0870 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1214,7 +1214,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["676 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["677 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1238,7 +1238,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "676", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "677", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1540,7 +1540,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["185 long flags · 36 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["676 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["677 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 71 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/test/ownerhopcheck.sh b/test/ownerhopcheck.sh new file mode 100755 index 000000000..8f4ebb54b --- /dev/null +++ b/test/ownerhopcheck.sh @@ -0,0 +1,329 @@ +#!/usr/bin/env bash +# ownerhopcheck.sh — the symbol a --for question is ABOUT gets a hop slot, first, with its callee names. +# +# test/ownerhopcheck.sh # build/ripwire on test/ownerhopfix +# test/ownerhopcheck.sh build_base/ripwire # the RED run (a pre-change binary) +# RIPWIRE_BASE_BIN=old/ripwire test/ownerhopcheck.sh # adds the base-vs-head byte-identity arm (I) +# +# THE GAP (graded answers, round 1): the compact --for answer hop-expands only the first 6 positive-rank rows +# (packtask.h kPackTaskBodyCandidates, verbs_for.h buildForCompactHops) inside a ~1 KB hop budget. When the +# question names the function it is about ("How does the JWT middleware verify a token?" -> jwt(), rank 12; +# "How does Fastify validate a request ...?" -> validate(), rank 8), that function never gets a hop row, so its +# callees (getCookie, HTTPException, ctx.set, validateAsync*) — the gold items — are never named. +# +# THE CONTRACT (design note: $ORCH reports/owner-hop-068.md §Design). +# OWNER. A ranked-head symbol is an OWNER of the question when (all of): its kind is callable (fn/method); it is a +# DEFINITION (a C prototype and its body are ONE entity, the body's row); it is not a test symbol; its name, with +# leading _ # $ @ removed and ASCII case folded, EQUALS a whole word of the question (length >= 3, not a route +# stopword) — whole name only, never a substring ("log" never matches catalog/dialog); and that word has at +# least one such definition inside the head tier (rank <= serialize.h kForDocExcerptRankCount = 24). Every +# same-named definition of an owner word in the ranked pool is shown (never one picked silently), each as its own +# row (entity identity: two classes' _notify are two rows). +# SLOT. Owner rows come FIRST in , in rank order, regardless of the 6-candidate cap; each carries +# qword="". An owner that was already a hop candidate is moved, never duplicated. At most 3 +# owner rows; the rest are disclosed: (the first one cut). +# EDGES. The hop-slot rule still applies (>= 1 proven callee edge, else counted in noedge=). An owner row lists ALL +# its callee names (not the 16 a ranked hop shows) up to a runaway guard; past it: capped="1" + next=. +# A callee bound by name alone keeps FE-B's via="name" hedge (a builtin method, a test-file def, an untyped +# receiver) — never an unhedged claim. +# BUDGET. Default regime: owner rows ride ON TOP of the hop budget (completeness first; the existing hop rows keep +# their budget, so every pre-change row is still served). Explicit --token-budget: owner rows are funded +# first; anything cut is disclosed by the existing capped="1". +# OFF. A question that names no owner is byte-identical to before; RIPWIRE_NO_OWNER_HOP=1 restores the pre-change +# answer everywhere (the A/B handle the no-regression arms compare against). --json and the MCP `for` verb serve +# no hop block today and stay byte-identical. +# +# ARMS (fixture roots under test/ownerhopfix/, each indexed on its own, --no-cache): +# (P) positive: TS gateway (rank 7..24, beyond the 6-cap) is the FIRST row with qword="gateway" and names +# readCookie + checkSessionToken; Python two _notify owners (Reactive, Signal) -> two rows, distinct l=; +# C scan: ONE row, the table.c definition (the table.h prototype is not a second owner). +# (F) false-claim (needs FE-B; SKIP by name "expects FE-B" when the binary's own callee rows carry no via=): +# gateway's remember (untyped receiver) and encode (a test-file def of a builtin method name) carry +# via="name" inside the owner row; no unhedged row of an owner names a test-file definition. +# (Z) zero proven callees: C collect (only an indirect call) gets no row and is counted in noedge=. +# (N) near misses: "the log" never makes catalog/dialog owners; a class (Reactive) is never an owner; a word +# whose only definition is a test file (encode) is no owner; a non-code question carries no qword=. +# (T) tier: a word whose only definition ranks > 24 makes no owner (broadcast); a word with one head-tier +# definition shows EVERY same-named definition in the pool (the second _notify ranks > 24, still a row). +# (C) cap: five owners -> 3 rows + qword_cut="2" + next="--callees=..." naming the first cut owner. +# (G) guard: dispatch (20 callees) lists all 20 names; broadcast (105 callees) lists the guard's worth with +# capped="1" and next="--callees=pkg/broadcast.py:broadcast". +# (R) no-regression: every row of the RIPWIRE_NO_OWNER_HOP=1 answer is still served, same content, +# same relative order (owner rows removed); non-owner questions byte-identical to the A/B handle. +# (B) explicit --token-budget=4000 (hops fit): the owner row is first, est_tokens <= 4000 or over_ceiling="1"; +# --token-budget=1200 (ceiling spent, reason="budget"): byte-identical to the A/B handle. +# (J) --json --for and MCP `for` byte-identical to the A/B handle (no hop surface there). +# (L) legend: the qword clause is present iff a qword row is, and says "by name" (not a resolution claim). +# (I) optional: RIPWIRE_BASE_BIN set -> 12 out-of-scope argv byte-identical base vs head. +# +# Exit: 0 all PASS (named SKIPs allowed), 1 any FAIL, 2 missing prerequisite. + +set -u +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" +[ "${BIN#/}" = "$BIN" ] && BIN="$PWD/$BIN" +FIX="$ROOT/test/ownerhopfix" +fail=0 +ok(){ printf ' PASS %s\n' "$*" || { fail=1; printf ' FAIL could not write the PASS line for: %s\n' "$*"; }; return 0; } +no(){ printf ' FAIL %s\n' "$*"; fail=1; } +skip(){ printf ' SKIP %s\n' "$*"; } +[ -x "$BIN" ] || { echo "ownerhopcheck: no ripwire binary at $BIN — build first"; exit 2; } +command -v python3 >/dev/null 2>&1 || { echo "ownerhopcheck: python3 required"; exit 2; } +echo "ownerhopcheck: BIN=$BIN" +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT + +# run ROOTNAME OUTFILE ARGS... — in the fixture root, --no-cache; records the binary's own rc next to the output +run(){ local r="$1" f="$2"; shift 2; ( cd "$FIX/$r" && "$BIN" . --no-cache "$@" >"$f" 2>"$f.err" ); printf '%s' "$?" >"$f.rc"; } +runoff(){ local r="$1" f="$2"; shift 2; ( cd "$FIX/$r" && RIPWIRE_NO_OWNER_HOP=1 "$BIN" . --no-cache "$@" >"$f" 2>"$f.err" ); printf '%s' "$?" >"$f.rc"; } +ran_ok(){ local rc; rc="$( cat "$1.rc" 2>/dev/null )"; [ "$rc" = 0 ] && [ -s "$1" ] && return 0; no "$2: the binary exited rc=${rc:-missing} or printed nothing"; return 1; } + +cat >"$TMP/h.py" <<'PY' +import re, sys, html, json +def attrs(s): return {k: html.unescape(v) for k, v in re.findall(r'([\w:]+)="([^"]*)"', s)} +def hops(x): + m = re.search(r']*)>(.*?)', x, re.S) + if not m: return None, [] + rows = [] + for h in re.finditer(r']*)>(.*?)', m.group(2), re.S): + ca = re.search(r']*)>', h.group(2)) + rows.append(dict(a=attrs(h.group(1)), raw=h.group(0), calls=[attrs(c.group(1)) for c in re.finditer(r']*?)/?>', h.group(2))], + ca=attrs(ca.group(1)) if ca else {})) + return attrs(m.group(1)), rows +def cands(x): + return [attrs(m.group(1)) for m in re.finditer(r']*)>', x)] +cmd = sys.argv[1] +x = open(sys.argv[2]).read() +if cmd == 'hops': # one JSON line: {hops attrs, rows:[{n,l,p,qword,calls:[{n,via}], ca}]} + ha, rows = hops(x) + print(json.dumps({'hops': ha, 'rows': [dict(r['a'], calls=r['calls'], ca=r['ca'], raw=r['raw']) for r in rows]})) +elif cmd == 'rank': # rank of name N (first def, optional path substring P) in --format=candidates output + n = sys.argv[3]; p = sys.argv[4] if len(sys.argv) > 4 else '' + rs = [int(a['r']) for a in cands(x) if a.get('n') == n and p in a.get('p', '')] + print(rs[0] if rs else -1) +elif cmd == 'root': + m = re.search(r']*)>', x); print(json.dumps(attrs(m.group(1)) if m else {})) +PY +H(){ python3 "$TMP/h.py" "$@"; } +J(){ python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(eval(sys.argv[1]))" "$1"; } + +# ── premises: the fixture still reproduces the gap (rank beyond the 6-cap, inside the head tier) ────────────────────── +QTS="How does the gateway check a session token?" +QPY="How does notify propagate a reactive change?" +QC="How does scan walk the process tables?" +premise(){ # root question name pathsub lo hi + local f="$TMP/prem.$1.$3.xml"; run "$1" "$f" "--for=$2" --format=candidates + ran_ok "$f" "premise $1/$3" || return 1 + local r; r="$( H rank "$f" "$3" "$4" )" + if ! [[ "$r" =~ ^-?[0-9]+$ ]]; then no "premise $1/$3: rank not numeric ('$r')"; return 1; fi + if [ "$r" -ge "$5" ] && [ "$r" -le "$6" ]; then ok "premise $1: $3 ranks $r (in $5..$6)"; else no "premise $1: $3 ranks $r, outside $5..$6 — the fixture no longer reproduces the gap; re-tune it"; fi +} +premise ts "$QTS" gateway src/middleware 7 24 +premise py "$QPY" _notify pkg/reactive 7 24 +premise c "$QC" scan table.c 7 24 + +# ── (P) positive ───────────────────────────────────────────────────────────────────────────────────────────────────── +f="$TMP/ts.p.xml"; run ts "$f" "--for=$QTS" +if ran_ok "$f" "(P/TS)"; then + H hops "$f" >"$TMP/ts.p.json" + first="$( J "(d['rows'][0]['n'], d['rows'][0].get('qword',''), d['rows'][0]['p']) if d['rows'] else None" <"$TMP/ts.p.json" )" + if [ "$first" = "('gateway', 'gateway', 'src/middleware/gateway.ts')" ]; then ok "(P/TS) first row is gateway with qword=\"gateway\""; else no "(P/TS) first row is $first, want gateway / qword=gateway (rank-13 owner beyond the 6-cap)"; fi + names="$( J "sorted(c['n'] for r in d['rows'] if r['n']=='gateway' for c in r['calls'])" <"$TMP/ts.p.json" )" + case "$names" in *readCookie*checkSessionToken*|*checkSessionToken*readCookie*) ok "(P/TS) owner row names readCookie and checkSessionToken ($names)";; + *) no "(P/TS) owner row callee names $names lack readCookie/checkSessionToken";; esac + dup="$( J "sum(1 for r in d['rows'] if r['n']=='gateway')" <"$TMP/ts.p.json" )" + if [ "$dup" = 1 ]; then ok "(P/TS) gateway appears in exactly one row"; else no "(P/TS) gateway rows: $dup (want 1)"; fi +fi +f="$TMP/py.p.xml"; run py "$f" "--for=$QPY" +if ran_ok "$f" "(P/PY)"; then + H hops "$f" >"$TMP/py.p.json" + got="$( J "[(r['n'], r.get('qword',''), r['l']) for r in d['rows'][:2]]" <"$TMP/py.p.json" )" + want="[('_notify', 'notify', '11'), ('_notify', 'notify', '23')]" + if [ "$got" = "$want" ]; then ok "(P/PY) two same-named owners (Reactive._notify l=11, Signal._notify l=23) are the first two rows"; else no "(P/PY) first two rows $got, want $want (show each same-named owner, never one, never merged)"; fi +fi +f="$TMP/c.p.xml"; run c "$f" "--for=$QC" +if ran_ok "$f" "(P/C)"; then + H hops "$f" >"$TMP/c.p.json" + got="$( J "[(r['p'], r['l'], r.get('qword','')) for r in d['rows'] if r['n']=='scan']" <"$TMP/c.p.json" )" + if [ "$got" = "[('table.c', '8', 'scan')]" ]; then ok "(P/C) scan: one owner row, the table.c definition (prototype not a second owner)"; else no "(P/C) scan rows $got, want [('table.c', '8', 'scan')]"; fi + first="$( J "d['rows'][0]['n'] if d['rows'] else None" <"$TMP/c.p.json" )" + if [ "$first" = scan ]; then ok "(P/C) scan is the first row"; else no "(P/C) first row is $first, want scan"; fi +fi + +# ── (F) false-claim hedges (FE-B capability detected on the fixture's own name-only call) ───────────────────────────── +f="$TMP/ts.cl.xml"; run ts "$f" --callees=src/middleware/gateway.ts:gateway +if ran_ok "$f" "(F) capability probe"; then + if grep -q 'n="remember"[^>]*via="name"' "$f"; then + J "[(c['n'], c.get('via','')) for r in d['rows'] if r['n']=='gateway' for c in r['calls']]" <"$TMP/ts.p.json" >"$TMP/ts.f.txt" 2>/dev/null || echo "[]" >"$TMP/ts.f.txt" + if grep -q "('remember', 'name')" "$TMP/ts.f.txt"; then ok "(F) owner row: remember (untyped receiver) carries via=\"name\""; else no "(F) owner row callee rows $(cat "$TMP/ts.f.txt"): remember must be present AND via=\"name\""; fi + if grep -q "('encode', 'name')" "$TMP/ts.f.txt"; then ok "(F) owner row: encode (test-file def of a builtin method name) carries via=\"name\""; else no "(F) owner row callee rows $(cat "$TMP/ts.f.txt"): encode must be present AND via=\"name\""; fi + if grep -q "('encode', '')\|('remember', '')" "$TMP/ts.f.txt"; then no "(F) an owner callee row asserts a name-only edge unhedged" + else ok "(F) no unhedged name-only callee row under the owner"; fi + else + skip "(F) expects FE-B: this binary's --callees rows carry no via=\"name\" on the fixture's untyped receiver call" + fi +fi + +# ── (Z) zero proven callees ────────────────────────────────────────────────────────────────────────────────────────── +f="$TMP/c.z.xml"; run c "$f" "--for=How does collect gather the process tables?" +if ran_ok "$f" "(Z)"; then + H hops "$f" >"$TMP/c.z.json" + got="$( J "(sum(1 for r in d['rows'] if r['n']=='collect'), d['hops'].get('noedge','0') if d['hops'] is not None else 'nohops')" <"$TMP/c.z.json" )" + case "$got" in "(0, '"[1-9]*) ok "(Z) collect (no resolvable callee) gets no row and is counted in noedge ($got)";; + *) no "(Z) collect rows/noedge = $got, want no row and noedge >= 1";; esac +fi + +# ── (N) near misses ────────────────────────────────────────────────────────────────────────────────────────────────── +nq(){ # root question label — no qword= anywhere in the answer + local f="$TMP/n.$1.$3.xml"; run "$1" "$f" "--for=$2" + ran_ok "$f" "(N/$3)" || return + grep -q ' block at all — the arm measured nothing"; return; } + if grep -q 'qword=' "$f"; then no "(N/$3) carries qword= : $( grep -o ']*qword="[^"]*"' "$f" | head -3 )"; else ok "(N/$3) no owner row"; fi +} +nq c "How does the log walk the process tables?" substring-log +nq ts "How is a session token encoded with encode?" test-file-owner +nq ts "How does the team review pull requests?" non-code-question +f="$TMP/n.py.cls.xml"; run py "$f" "--for=How does Reactive notify a change?" +if ran_ok "$f" "(N/class)"; then + if grep -q ']*n="Reactive"[^>]*qword=' "$f"; then no "(N/class) the class Reactive became an owner row"; else ok "(N/class) a class is never an owner row"; fi +fi + +# ── (T) the head-tier gate is per WORD, never per definition ───────────────────────────────────────────────────────── +f="$TMP/py.t1.xml"; run py "$f" "--for=How does notify propagate a reactive change and broadcast?" +f2="$TMP/py.t1.cand"; run py "$f2" "--for=How does notify propagate a reactive change and broadcast?" --format=candidates +if ran_ok "$f" "(T/tail)" && ran_ok "$f2" "(T/tail) premise"; then + r="$( H rank "$f2" broadcast )" + if [[ "$r" =~ ^[0-9]+$ ]] && [ "$r" -gt 24 ]; then + if grep -q ']*n="broadcast"[^>]*qword=' "$f"; then no "(T/tail) broadcast (only definition ranks $r > 24) became an owner row" + else ok "(T/tail) a word whose only definition ranks $r (> 24, the tail tier) makes no owner"; fi + else no "(T/tail) premise: broadcast ranks '$r', want > 24 — re-tune the fixture"; fi +fi +q2="How does notify propagate a reactive change, then refresh and broadcast?" +f="$TMP/py.t2.xml"; run py "$f" "--for=$q2"; f2="$TMP/py.t2.cand"; run py "$f2" "--for=$q2" --format=candidates +if ran_ok "$f" "(T/each)" && ran_ok "$f2" "(T/each) premise"; then + ranks="$( python3 -c " +import re,sys +x=open(sys.argv[1]).read() +print(sorted(int(r) for r,n in re.findall(r'"$TMP/py.t2.json" + got="$( J "sorted(r['l'] for r in d['rows'] if r['n']=='_notify' and r.get('qword')=='notify')" <"$TMP/py.t2.json" )" + case "$ranks" in "["[0-9]*", 2"[5-9]"]"|"["[0-9]*", "[3-4][0-9]"]") + if [ "$got" = "['11', '23']" ]; then ok "(T/each) both _notify definitions are owner rows although one ranks in the tail ($ranks)" + else no "(T/each) _notify owner rows l=$got, want ['11', '23'] (ranks $ranks): a same-named owner was picked silently"; fi;; + *) no "(T/each) premise: _notify ranks $ranks, want one <= 24 and one > 24 — re-tune";; esac +fi + +# ── (C) cap ────────────────────────────────────────────────────────────────────────────────────────────────────────── +f="$TMP/py.cap.xml"; run py "$f" "--for=How do invoke_watcher, schedule_refresh, record_signal and notify work?" +if ran_ok "$f" "(C)"; then + H hops "$f" >"$TMP/py.cap.json" + got="$( J "(sum(1 for r in d['rows'] if r.get('qword')), (d['hops'] or {}).get('qword_cut'), (d['hops'] or {}).get('next',''))" <"$TMP/py.cap.json" )" + case "$got" in "(3, '2', '--callees="*) ok "(C) 3 owner rows, qword_cut=\"2\", next= names the first cut owner ($got)";; + *) no "(C) owner rows / qword_cut / next = $got, want (3, '2', '--callees=FILE:NAME')";; esac +fi + +# ── (G) all callee names up to the runaway guard ───────────────────────────────────────────────────────────────────── +f="$TMP/py.g1.xml"; run py "$f" "--for=How does dispatch route an event?" +if ran_ok "$f" "(G/20)"; then + H hops "$f" >"$TMP/py.g1.json" + got="$( J "[(len(r['calls']), r['ca'].get('capped','0')) for r in d['rows'] if r['n']=='dispatch']" <"$TMP/py.g1.json" )" + if [ "$got" = "[(20, '0')]" ]; then ok "(G/20) dispatch's owner row lists all 20 callee names"; else no "(G/20) dispatch rows (names, capped) = $got, want [(20, '0')]"; fi +fi +f="$TMP/py.g2.xml"; run py "$f" "--for=How does broadcast send an event?" +if ran_ok "$f" "(G/guard)"; then + H hops "$f" >"$TMP/py.g2.json" + got="$( J "[(r['ca'].get('total'), r['ca'].get('capped'), r['ca'].get('next', r.get('next',''))) for r in d['rows'] if r['n']=='broadcast']" <"$TMP/py.g2.json" )" + case "$got" in "[('105', '1', '--callees=pkg/broadcast.py:broadcast')]") ok "(G/guard) broadcast past the guard: capped=\"1\" + next= to the full list";; + *) no "(G/guard) broadcast (total, capped, next) = $got, want [('105', '1', '--callees=pkg/broadcast.py:broadcast')]";; esac +fi + +# ── (R) no-regression against the A/B handle ───────────────────────────────────────────────────────────────────────── +for spec in "ts|$QTS" "py|$QPY" "c|$QC"; do + r="${spec%%|*}"; q="${spec#*|}" + on="$TMP/r.$r.on.xml"; off="$TMP/r.$r.off.xml"; run "$r" "$on" "--for=$q"; runoff "$r" "$off" "--for=$q" + ran_ok "$on" "(R/$r) head" && ran_ok "$off" "(R/$r) A/B" || continue + H hops "$on" >"$on.json"; H hops "$off" >"$off.json" + res="$( python3 - "$on.json" "$off.json" <<'PY' +import json, sys +on = json.load(open(sys.argv[1])); off = json.load(open(sys.argv[2])) +owners = {(r['p'], r['n'], r['l']) for r in on['rows'] if r.get('qword')} +base = [r for r in off['rows'] if (r['p'], r['n'], r['l']) not in owners] +head = [r for r in on['rows'] if not r.get('qword')] +key = lambda r: (r['p'], r['n'], r['l'], tuple((c['n'], c.get('l'), c.get('via')) for c in r['calls'])) +missing = [k for k in map(key, base) if k not in set(map(key, head))] +order_ok = [key(r) for r in head if key(r) in set(map(key, base))] == [key(r) for r in base if key(r) in set(map(key, head))] +print('OK' if not missing and order_ok else 'BAD missing=%s order_ok=%s' % (missing[:3], order_ok)) +PY +)" + if [ "$res" = OK ]; then ok "(R/$r) every A/B row is still served, same calls, same order"; else no "(R/$r) $res"; fi +done +for spec in "ts|How does the team review pull requests?" "c|How does the log walk the process tables?"; do + r="${spec%%|*}"; q="${spec#*|}" + on="$TMP/r2.$r.on.xml"; off="$TMP/r2.$r.off.xml"; run "$r" "$on" "--for=$q"; runoff "$r" "$off" "--for=$q" + ran_ok "$on" "(R2/$r)" && ran_ok "$off" "(R2/$r) A/B" || continue + if cmp -s "$on" "$off"; then ok "(R2/$r) a question with no owner is byte-identical to the A/B handle"; else no "(R2/$r) no-owner answer differs from RIPWIRE_NO_OWNER_HOP=1"; fi +done + +# ── (B) explicit budget: owner funded first; an exhausted ceiling adds nothing ─────────────────────────────────────── +f="$TMP/ts.b.xml"; run ts "$f" "--for=$QTS" --token-budget=4000 +if ran_ok "$f" "(B1)"; then + H hops "$f" >"$TMP/ts.b.json"; H root "$f" >"$TMP/ts.b.root" + first="$( J "d['rows'][0]['n'] if d['rows'] else None" <"$TMP/ts.b.json" )" + est="$( J "d.get('est_tokens','')" <"$TMP/ts.b.root" )"; over="$( J "d.get('over_ceiling','0')" <"$TMP/ts.b.root" )" + if [ "$first" = gateway ]; then ok "(B1) under --token-budget=4000 the owner row is first"; else no "(B1) first row under --token-budget=4000 is $first, want gateway"; fi + if [[ "$est" =~ ^[0-9]+$ ]]; then + if { [ "$est" -le 4000 ] || [ "$over" = 1 ]; }; then ok "(B1) est_tokens=$est within 4000 (or over_ceiling disclosed)"; else no "(B1) est_tokens=$est > 4000 and no over_ceiling=\"1\""; fi + else no "(B1) est_tokens missing or not numeric ('$est')"; fi +fi +on="$TMP/b2.on.xml"; off="$TMP/b2.off.xml"; run ts "$on" "--for=$QTS" --token-budget=1200; runoff ts "$off" "--for=$QTS" --token-budget=1200 +if ran_ok "$on" "(B2)" && ran_ok "$off" "(B2) A/B"; then + grep -q 'reason="budget"' "$off" || no "(B2) premise: --token-budget=1200 no longer exhausts the ceiling (no reason=\"budget\") — re-tune" + if cmp -s "$on" "$off"; then ok "(B2) exhausted ceiling: byte-identical to the A/B handle (owner rows add nothing)"; else no "(B2) exhausted-ceiling answer changed"; fi +fi + +# ── (J) JSON and MCP twins: no hop surface, byte-identical ─────────────────────────────────────────────────────────── +on="$TMP/j.on.json"; off="$TMP/j.off.json"; run ts "$on" "--for=$QTS" --json; runoff ts "$off" "--for=$QTS" --json +if ran_ok "$on" "(J/json)" && ran_ok "$off" "(J/json) A/B"; then + if cmp -s "$on" "$off"; then ok "(J/json) --json --for byte-identical to the A/B handle"; else no "(J/json) --json --for changed"; fi +fi +mcp(){ printf '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"for","arguments":{"path":"%s","task":"%s"}}}\n' "$FIX/ts" "$QTS" \ + | env $1 "$BIN" --mcp --no-cache >"$2" 2>"$2.err"; printf '%s' "$?" >"$2.rc"; } +mcp "RIPWIRE_X=0" "$TMP/m.on"; mcp "RIPWIRE_NO_OWNER_HOP=1" "$TMP/m.off" +if ran_ok "$TMP/m.on" "(J/mcp)" && ran_ok "$TMP/m.off" "(J/mcp) A/B"; then + grep -q '"result"' "$TMP/m.on" || no "(J/mcp) no result object — the arm measured nothing" + if cmp -s "$TMP/m.on" "$TMP/m.off"; then ok "(J/mcp) MCP for byte-identical to the A/B handle (signatures-only surface)"; else no "(J/mcp) MCP for changed"; fi +fi + +# ── (L) legend ─────────────────────────────────────────────────────────────────────────────────────────────────────── +if ! grep -q 'qword=' "$TMP/ts.p.xml" 2>/dev/null; then no "(L) no qword row on the positive answer — the legend arm measured nothing" +elif grep -q 'qword= [^;]*by name' "$TMP/ts.p.xml"; then ok "(L) qword legend clause present and says 'by name'" +else no "(L) qword row emitted without a legend clause that says it is by name"; fi +if grep -q 'qword' "$TMP/n.ts.non-code-question.xml" 2>/dev/null; then no "(L) qword legend clause on an answer with no owner row"; else ok "(L) no qword clause when no owner row"; fi + +# ── (I) optional base-vs-head byte identity outside --for hop selection ────────────────────────────────────────────── +if [ -n "${RIPWIRE_BASE_BIN:-}" ] && [ -x "${RIPWIRE_BASE_BIN}" ]; then + n=0; d=0 + while IFS='|' read -r r a; do + [ -z "$r" ] && continue + ( cd "$FIX/$r" && "$BIN" . --no-cache $a >"$TMP/i.h" 2>/dev/null; cd "$FIX/$r" && "$RIPWIRE_BASE_BIN" . --no-cache $a >"$TMP/i.b" 2>/dev/null ) + n=$((n+1)); cmp -s "$TMP/i.h" "$TMP/i.b" || { d=$((d+1)); echo " differs: $r $a"; } + done <<'ARGV' +ts|--callers=checkSessionToken +ts|--callees=src/middleware/gateway.ts:gateway +ts|--expand=gateway +ts|--top-k=20 +py|--callers=invoke_watcher +py|--callees=dispatch +py|--expand=pkg/reactive.py:_notify +py|--top-k=20 +c|--callers=Table_rowCount +c|--callees=scan +c|--expand=scan +c|--top-k=20 +ARGV + if [ "$d" = 0 ] && [ "$n" -gt 0 ]; then ok "(I) $n out-of-scope argv byte-identical base vs head"; else no "(I) $d of $n out-of-scope argv differ"; fi +else + skip "(I) base-vs-head identity needs RIPWIRE_BASE_BIN" +fi + +[ "$fail" = 0 ] && { echo "ownerhopcheck: OK"; exit 0; } +echo "ownerhopcheck: FAILURES ABOVE"; exit 1 diff --git a/test/ownerhopfix/c/table.c b/test/ownerhopfix/c/table.c new file mode 100644 index 000000000..4a39cf294 --- /dev/null +++ b/test/ownerhopfix/c/table.c @@ -0,0 +1,22 @@ +#include "table.h" +int Table_rowCount(Table* t) { return t->rows; } +int Table_collectRows(Table* t) { return t->rows; } +int Table_prepare(Table* t) { return t->rows; } +int Table_cleanup(Table* t) { return t->rows; } + +/* scan: one refresh of the tables (the function the question is about) */ +int scan(Table* t) { + Table_prepare(t); + int n = Table_rowCount(t); + Table_cleanup(t); + return n; +} + +/* collect: only an indirect call through the table's own iterate pointer (no resolvable callee) */ +int collect(Table* t) { + return t->iterate(t); +} + +/* catalog and dialog: names that CONTAIN a question word ("log") without being it */ +int catalog(Table* t) { return Table_rowCount(t); } +int dialog(Table* t) { return Table_rowCount(t); } diff --git a/test/ownerhopfix/c/table.h b/test/ownerhopfix/c/table.h new file mode 100644 index 000000000..2253258a8 --- /dev/null +++ b/test/ownerhopfix/c/table.h @@ -0,0 +1,13 @@ +#ifndef TABLE_H +#define TABLE_H +typedef struct Table_ Table; +struct Table_ { int rows; int (*iterate)(Table*); }; +int Table_rowCount(Table* t); +int Table_collectRows(Table* t); +int Table_prepare(Table* t); +int Table_cleanup(Table* t); +int scan(Table* t); +int collect(Table* t); +int catalog(Table* t); +int dialog(Table* t); +#endif diff --git a/test/ownerhopfix/c/walkers.c b/test/ownerhopfix/c/walkers.c new file mode 100644 index 000000000..895d6771d --- /dev/null +++ b/test/ownerhopfix/c/walkers.c @@ -0,0 +1,52 @@ +/* process table walkers: decoys that repeat the question words (scan, walk, process, tables, collect, gather). */ +#include "table.h" + +/* scan and walk the process tables (Cpu): collect and gather process table rows */ +int ProcessTable_scanWalkGatherCpu(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} + +/* scan and walk the process tables (Mem): collect and gather process table rows */ +int ProcessTable_scanWalkGatherMem(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} + +/* scan and walk the process tables (Io): collect and gather process table rows */ +int ProcessTable_scanWalkGatherIo(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} + +/* scan and walk the process tables (Net): collect and gather process table rows */ +int ProcessTable_scanWalkGatherNet(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} + +/* scan and walk the process tables (Disk): collect and gather process table rows */ +int ProcessTable_scanWalkGatherDisk(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} + +/* scan and walk the process tables (Swap): collect and gather process table rows */ +int ProcessTable_scanWalkGatherSwap(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} + +/* scan and walk the process tables (Load): collect and gather process table rows */ +int ProcessTable_scanWalkGatherLoad(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} + +/* scan and walk the process tables (Uptime): collect and gather process table rows */ +int ProcessTable_scanWalkGatherUptime(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} + +/* scan and walk the process tables (Task): collect and gather process table rows */ +int ProcessTable_scanWalkGatherTask(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} + +/* scan and walk the process tables (Thread): collect and gather process table rows */ +int ProcessTable_scanWalkGatherThread(Table* processTables) { + return Table_rowCount(processTables) + Table_collectRows(processTables); +} diff --git a/test/ownerhopfix/py/pkg/broadcast.py b/test/ownerhopfix/py/pkg/broadcast.py new file mode 100644 index 000000000..715426c00 --- /dev/null +++ b/test/ownerhopfix/py/pkg/broadcast.py @@ -0,0 +1,530 @@ +"""A dispatcher with more callees than the owner-row runaway guard.""" + +def big_handler_001(event): + return event + + +def big_handler_002(event): + return event + + +def big_handler_003(event): + return event + + +def big_handler_004(event): + return event + + +def big_handler_005(event): + return event + + +def big_handler_006(event): + return event + + +def big_handler_007(event): + return event + + +def big_handler_008(event): + return event + + +def big_handler_009(event): + return event + + +def big_handler_010(event): + return event + + +def big_handler_011(event): + return event + + +def big_handler_012(event): + return event + + +def big_handler_013(event): + return event + + +def big_handler_014(event): + return event + + +def big_handler_015(event): + return event + + +def big_handler_016(event): + return event + + +def big_handler_017(event): + return event + + +def big_handler_018(event): + return event + + +def big_handler_019(event): + return event + + +def big_handler_020(event): + return event + + +def big_handler_021(event): + return event + + +def big_handler_022(event): + return event + + +def big_handler_023(event): + return event + + +def big_handler_024(event): + return event + + +def big_handler_025(event): + return event + + +def big_handler_026(event): + return event + + +def big_handler_027(event): + return event + + +def big_handler_028(event): + return event + + +def big_handler_029(event): + return event + + +def big_handler_030(event): + return event + + +def big_handler_031(event): + return event + + +def big_handler_032(event): + return event + + +def big_handler_033(event): + return event + + +def big_handler_034(event): + return event + + +def big_handler_035(event): + return event + + +def big_handler_036(event): + return event + + +def big_handler_037(event): + return event + + +def big_handler_038(event): + return event + + +def big_handler_039(event): + return event + + +def big_handler_040(event): + return event + + +def big_handler_041(event): + return event + + +def big_handler_042(event): + return event + + +def big_handler_043(event): + return event + + +def big_handler_044(event): + return event + + +def big_handler_045(event): + return event + + +def big_handler_046(event): + return event + + +def big_handler_047(event): + return event + + +def big_handler_048(event): + return event + + +def big_handler_049(event): + return event + + +def big_handler_050(event): + return event + + +def big_handler_051(event): + return event + + +def big_handler_052(event): + return event + + +def big_handler_053(event): + return event + + +def big_handler_054(event): + return event + + +def big_handler_055(event): + return event + + +def big_handler_056(event): + return event + + +def big_handler_057(event): + return event + + +def big_handler_058(event): + return event + + +def big_handler_059(event): + return event + + +def big_handler_060(event): + return event + + +def big_handler_061(event): + return event + + +def big_handler_062(event): + return event + + +def big_handler_063(event): + return event + + +def big_handler_064(event): + return event + + +def big_handler_065(event): + return event + + +def big_handler_066(event): + return event + + +def big_handler_067(event): + return event + + +def big_handler_068(event): + return event + + +def big_handler_069(event): + return event + + +def big_handler_070(event): + return event + + +def big_handler_071(event): + return event + + +def big_handler_072(event): + return event + + +def big_handler_073(event): + return event + + +def big_handler_074(event): + return event + + +def big_handler_075(event): + return event + + +def big_handler_076(event): + return event + + +def big_handler_077(event): + return event + + +def big_handler_078(event): + return event + + +def big_handler_079(event): + return event + + +def big_handler_080(event): + return event + + +def big_handler_081(event): + return event + + +def big_handler_082(event): + return event + + +def big_handler_083(event): + return event + + +def big_handler_084(event): + return event + + +def big_handler_085(event): + return event + + +def big_handler_086(event): + return event + + +def big_handler_087(event): + return event + + +def big_handler_088(event): + return event + + +def big_handler_089(event): + return event + + +def big_handler_090(event): + return event + + +def big_handler_091(event): + return event + + +def big_handler_092(event): + return event + + +def big_handler_093(event): + return event + + +def big_handler_094(event): + return event + + +def big_handler_095(event): + return event + + +def big_handler_096(event): + return event + + +def big_handler_097(event): + return event + + +def big_handler_098(event): + return event + + +def big_handler_099(event): + return event + + +def big_handler_100(event): + return event + + +def big_handler_101(event): + return event + + +def big_handler_102(event): + return event + + +def big_handler_103(event): + return event + + +def big_handler_104(event): + return event + + +def big_handler_105(event): + return event + + +def broadcast(event): + """send one event to every big handler.""" + big_handler_001(event) + big_handler_002(event) + big_handler_003(event) + big_handler_004(event) + big_handler_005(event) + big_handler_006(event) + big_handler_007(event) + big_handler_008(event) + big_handler_009(event) + big_handler_010(event) + big_handler_011(event) + big_handler_012(event) + big_handler_013(event) + big_handler_014(event) + big_handler_015(event) + big_handler_016(event) + big_handler_017(event) + big_handler_018(event) + big_handler_019(event) + big_handler_020(event) + big_handler_021(event) + big_handler_022(event) + big_handler_023(event) + big_handler_024(event) + big_handler_025(event) + big_handler_026(event) + big_handler_027(event) + big_handler_028(event) + big_handler_029(event) + big_handler_030(event) + big_handler_031(event) + big_handler_032(event) + big_handler_033(event) + big_handler_034(event) + big_handler_035(event) + big_handler_036(event) + big_handler_037(event) + big_handler_038(event) + big_handler_039(event) + big_handler_040(event) + big_handler_041(event) + big_handler_042(event) + big_handler_043(event) + big_handler_044(event) + big_handler_045(event) + big_handler_046(event) + big_handler_047(event) + big_handler_048(event) + big_handler_049(event) + big_handler_050(event) + big_handler_051(event) + big_handler_052(event) + big_handler_053(event) + big_handler_054(event) + big_handler_055(event) + big_handler_056(event) + big_handler_057(event) + big_handler_058(event) + big_handler_059(event) + big_handler_060(event) + big_handler_061(event) + big_handler_062(event) + big_handler_063(event) + big_handler_064(event) + big_handler_065(event) + big_handler_066(event) + big_handler_067(event) + big_handler_068(event) + big_handler_069(event) + big_handler_070(event) + big_handler_071(event) + big_handler_072(event) + big_handler_073(event) + big_handler_074(event) + big_handler_075(event) + big_handler_076(event) + big_handler_077(event) + big_handler_078(event) + big_handler_079(event) + big_handler_080(event) + big_handler_081(event) + big_handler_082(event) + big_handler_083(event) + big_handler_084(event) + big_handler_085(event) + big_handler_086(event) + big_handler_087(event) + big_handler_088(event) + big_handler_089(event) + big_handler_090(event) + big_handler_091(event) + big_handler_092(event) + big_handler_093(event) + big_handler_094(event) + big_handler_095(event) + big_handler_096(event) + big_handler_097(event) + big_handler_098(event) + big_handler_099(event) + big_handler_100(event) + big_handler_101(event) + big_handler_102(event) + big_handler_103(event) + big_handler_104(event) + big_handler_105(event) + return event diff --git a/test/ownerhopfix/py/pkg/events.py b/test/ownerhopfix/py/pkg/events.py new file mode 100644 index 000000000..45e183e53 --- /dev/null +++ b/test/ownerhopfix/py/pkg/events.py @@ -0,0 +1,105 @@ +"""Event dispatch: one dispatcher with many handlers (the owner whose callee list is longer than 16).""" + +def on_event_01(event): + return event + + +def on_event_02(event): + return event + + +def on_event_03(event): + return event + + +def on_event_04(event): + return event + + +def on_event_05(event): + return event + + +def on_event_06(event): + return event + + +def on_event_07(event): + return event + + +def on_event_08(event): + return event + + +def on_event_09(event): + return event + + +def on_event_10(event): + return event + + +def on_event_11(event): + return event + + +def on_event_12(event): + return event + + +def on_event_13(event): + return event + + +def on_event_14(event): + return event + + +def on_event_15(event): + return event + + +def on_event_16(event): + return event + + +def on_event_17(event): + return event + + +def on_event_18(event): + return event + + +def on_event_19(event): + return event + + +def on_event_20(event): + return event + + +def dispatch(event): + """route one event to every handler.""" + on_event_01(event) + on_event_02(event) + on_event_03(event) + on_event_04(event) + on_event_05(event) + on_event_06(event) + on_event_07(event) + on_event_08(event) + on_event_09(event) + on_event_10(event) + on_event_11(event) + on_event_12(event) + on_event_13(event) + on_event_14(event) + on_event_15(event) + on_event_16(event) + on_event_17(event) + on_event_18(event) + on_event_19(event) + on_event_20(event) + return event diff --git a/test/ownerhopfix/py/pkg/propagate.py b/test/ownerhopfix/py/pkg/propagate.py new file mode 100644 index 000000000..e6b9164d5 --- /dev/null +++ b/test/ownerhopfix/py/pkg/propagate.py @@ -0,0 +1,91 @@ +"""Reactive change propagation checks (decoys that repeat the question words).""" + +def notify_propagate_reactive_change_value(reactive_change): + """notify: propagate a reactive change (value): every reactive change propagates.""" + return reactive_change_value_step(reactive_change) + + +def reactive_change_value_step(reactive_change): + return reactive_change + + +def notify_propagate_reactive_change_default(reactive_change): + """notify: propagate a reactive change (default): every reactive change propagates.""" + return reactive_change_default_step(reactive_change) + + +def reactive_change_default_step(reactive_change): + return reactive_change + + +def notify_propagate_reactive_change_layout(reactive_change): + """notify: propagate a reactive change (layout): every reactive change propagates.""" + return reactive_change_layout_step(reactive_change) + + +def reactive_change_layout_step(reactive_change): + return reactive_change + + +def notify_propagate_reactive_change_repaint(reactive_change): + """notify: propagate a reactive change (repaint): every reactive change propagates.""" + return reactive_change_repaint_step(reactive_change) + + +def reactive_change_repaint_step(reactive_change): + return reactive_change + + +def notify_propagate_reactive_change_compute(reactive_change): + """notify: propagate a reactive change (compute): every reactive change propagates.""" + return reactive_change_compute_step(reactive_change) + + +def reactive_change_compute_step(reactive_change): + return reactive_change + + +def notify_propagate_reactive_change_validate(reactive_change): + """notify: propagate a reactive change (validate): every reactive change propagates.""" + return reactive_change_validate_step(reactive_change) + + +def reactive_change_validate_step(reactive_change): + return reactive_change + + +def notify_propagate_reactive_change_watch(reactive_change): + """notify: propagate a reactive change (watch): every reactive change propagates.""" + return reactive_change_watch_step(reactive_change) + + +def reactive_change_watch_step(reactive_change): + return reactive_change + + +def notify_propagate_reactive_change_bind(reactive_change): + """notify: propagate a reactive change (bind): every reactive change propagates.""" + return reactive_change_bind_step(reactive_change) + + +def reactive_change_bind_step(reactive_change): + return reactive_change + + +def notify_propagate_reactive_change_init(reactive_change): + """notify: propagate a reactive change (init): every reactive change propagates.""" + return reactive_change_init_step(reactive_change) + + +def reactive_change_init_step(reactive_change): + return reactive_change + + +def notify_propagate_reactive_change_always(reactive_change): + """notify: propagate a reactive change (always): every reactive change propagates.""" + return reactive_change_always_step(reactive_change) + + +def reactive_change_always_step(reactive_change): + return reactive_change + diff --git a/test/ownerhopfix/py/pkg/reactive.py b/test/ownerhopfix/py/pkg/reactive.py new file mode 100644 index 000000000..d299d1535 --- /dev/null +++ b/test/ownerhopfix/py/pkg/reactive.py @@ -0,0 +1,43 @@ +"""Reactive values: a change notifies watchers.""" + + +class Reactive: + """A reactive value.""" + + def __init__(self, value): + self.value = value + self.watchers = [] + + def _notify(self, old, new): + for w in self.watchers: + invoke_watcher(w, old, new) + schedule_refresh(self) + + +class Signal: + """A signal: a reactive value without a default.""" + + def __init__(self): + self.handlers = [] + + def _notify(self, old, new): + for h in self.handlers: + invoke_watcher(h, old, new) + record_signal(self) + + +def invoke_watcher(watcher, old, new): + return watcher(old, new) + + +def schedule_refresh(obj): + return obj + + +def record_signal(obj): + return obj + + +def refresh(obj): + """redraw.""" + return schedule_refresh(obj) diff --git a/test/ownerhopfix/ts/package.json b/test/ownerhopfix/ts/package.json new file mode 100644 index 000000000..8a78a602f --- /dev/null +++ b/test/ownerhopfix/ts/package.json @@ -0,0 +1 @@ +{ "name": "ownerhopfix-ts", "private": true } diff --git a/test/ownerhopfix/ts/src/context.ts b/test/ownerhopfix/ts/src/context.ts new file mode 100644 index 000000000..d469de50a --- /dev/null +++ b/test/ownerhopfix/ts/src/context.ts @@ -0,0 +1,5 @@ +// a request context; the untyped `store.remember` in gateway can bind here only by NAME +export class Context { + store: Record = {} + remember(key: string, value: unknown) { this.store[key] = value } +} diff --git a/test/ownerhopfix/ts/src/helper/cookie.ts b/test/ownerhopfix/ts/src/helper/cookie.ts new file mode 100644 index 000000000..9b3fb5d82 --- /dev/null +++ b/test/ownerhopfix/ts/src/helper/cookie.ts @@ -0,0 +1,8 @@ +// readCookie: one cookie value out of a raw Cookie header. +export const readCookie = (header: string, name: string): string => { + for (const part of header.split(';')) { + const [k, v] = part.trim().split('=') + if (k === name) return v + } + return '' +} diff --git a/test/ownerhopfix/ts/src/middleware/gateway.ts b/test/ownerhopfix/ts/src/middleware/gateway.ts new file mode 100644 index 000000000..cf2f5c84c --- /dev/null +++ b/test/ownerhopfix/ts/src/middleware/gateway.ts @@ -0,0 +1,11 @@ +import { readCookie } from '../helper/cookie' +import { checkSessionToken } from '../session/check' +// gateway: the middleware in front of every route. +export function gateway(ctx, cookie: string) { + const raw = readCookie(ctx.header, cookie) + const payload = checkSessionToken(raw) + const bytes = new TextEncoder().encode(raw) + const store: any = ctx + store.remember('payload', payload) + return bytes.length +} diff --git a/test/ownerhopfix/ts/src/session/check.ts b/test/ownerhopfix/ts/src/session/check.ts new file mode 100644 index 000000000..4a1def3a7 --- /dev/null +++ b/test/ownerhopfix/ts/src/session/check.ts @@ -0,0 +1,13 @@ +import { GatewaySessionTokenInvalid as SessionTokenInvalid, GatewaySessionTokenExpired as SessionTokenExpired, GatewaySessionTokenPayload as SessionTokenPayload } from './types' +// checkSessionToken: decodes a session token and checks its expiry. +export const checkSessionToken = (token: string): SessionTokenPayload => { + if (!token) throw new SessionTokenInvalid(token) + const payload = decodeSessionToken(token) + if (!payload.checked) throw new SessionTokenExpired(token) + return payload +} +// decodeSessionToken: splits the session token into its parts. +export const decodeSessionToken = (token: string): SessionTokenPayload => { + const parts = token.split('.') + return { session: parts[0], token: parts[1], checked: parts.length === 3 } +} diff --git a/test/ownerhopfix/ts/src/session/claims.ts b/test/ownerhopfix/ts/src/session/claims.ts new file mode 100644 index 000000000..dcf830601 --- /dev/null +++ b/test/ownerhopfix/ts/src/session/claims.ts @@ -0,0 +1,63 @@ +import { decodeSessionToken } from './check' +import { GatewaySessionTokenCheckFailed as SessionTokenCheckFailed } from './types' +// session token checks: one check per claim; each repeats session/token/check on purpose (the decoys that outrank the owner). +// checkGatewaySessionTokenExpiry: gateway check of the session token expiry claim of a session token. +export const checkGatewaySessionTokenExpiry = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} +// checkGatewaySessionTokenRevoked: gateway check of the session token revoked claim of a session token. +export const checkGatewaySessionTokenRevoked = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} +// checkGatewaySessionTokenIssuer: gateway check of the session token issuer claim of a session token. +export const checkGatewaySessionTokenIssuer = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} +// checkGatewaySessionTokenAudience: gateway check of the session token audience claim of a session token. +export const checkGatewaySessionTokenAudience = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} +// checkGatewaySessionTokenNotBefore: gateway check of the session token notbefore claim of a session token. +export const checkGatewaySessionTokenNotBefore = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} +// checkGatewaySessionTokenIssuedAt: gateway check of the session token issuedat claim of a session token. +export const checkGatewaySessionTokenIssuedAt = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} +// checkGatewaySessionTokenSignature: gateway check of the session token signature claim of a session token. +export const checkGatewaySessionTokenSignature = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} +// checkGatewaySessionTokenAlgorithm: gateway check of the session token algorithm claim of a session token. +export const checkGatewaySessionTokenAlgorithm = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} +// checkGatewaySessionTokenHeader: gateway check of the session token header claim of a session token. +export const checkGatewaySessionTokenHeader = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} +// checkGatewaySessionTokenScope: gateway check of the session token scope claim of a session token. +export const checkGatewaySessionTokenScope = (sessionToken: string): boolean => { + const session = decodeSessionToken(sessionToken) + if (!session.checked) throw new SessionTokenCheckFailed(sessionToken) + return session.token.length > 0 +} diff --git a/test/ownerhopfix/ts/src/session/types.ts b/test/ownerhopfix/ts/src/session/types.ts new file mode 100644 index 000000000..ccc648551 --- /dev/null +++ b/test/ownerhopfix/ts/src/session/types.ts @@ -0,0 +1,10 @@ +// session token types: every class below repeats the question's words (session, token, check) so the lexical +// ranker puts them ABOVE the function the question is about. +export class GatewaySessionTokenInvalid extends Error { constructor(token: string) { super(`session token invalid: ${token}`) } } +export class GatewaySessionTokenExpired extends Error { constructor(token: string) { super(`session token expired: ${token}`) } } +export class GatewaySessionTokenMissing extends Error { constructor(token: string) { super(`session token missing: ${token}`) } } +export class GatewaySessionTokenRevoked extends Error { constructor(token: string) { super(`session token revoked: ${token}`) } } +export class GatewaySessionTokenMalformed extends Error { constructor(token: string) { super(`session token malformed: ${token}`) } } +export class GatewaySessionTokenCheckFailed extends Error { constructor(token: string) { super(`session token check failed: ${token}`) } } +export type GatewaySessionTokenPayload = { session: string; token: string; checked: boolean } +export type GatewaySessionTokenHeader = { session: string; token: string; alg: string } diff --git a/test/ownerhopfix/ts/test/gateway.test.ts b/test/ownerhopfix/ts/test/gateway.test.ts new file mode 100644 index 000000000..4f8194104 --- /dev/null +++ b/test/ownerhopfix/ts/test/gateway.test.ts @@ -0,0 +1,3 @@ +// a test-only helper whose name equals a builtin method the gateway calls (TextEncoder#encode) +function encode(s: string): string { return Buffer.from(s).toString('base64') } +export const sample = encode('a.b.c') diff --git a/test/regression.sh b/test/regression.sh index 83d84c784..7df1c4547 100755 --- a/test/regression.sh +++ b/test/regression.sh @@ -278,7 +278,7 @@ else fi # retired: forspinecheck — the --for caller spine (Change A) was dropped by its own offline kill rule: a spine of up to 2 caller hops from the top 3 seeds named a missed rubric item on 1 of 7 in-sample rows, so the gate that pinned it went with it # retired: cacheexclkeycheck — the per-configuration auto-cache key it pinned is a registered NEGATIVE (docs/EVALS.md, "The auto-cache key ignores --exclude", RUN 2026-09-03: a 158K-file root with >= 12 gate configurations thrashed the 2 GiB sweep); the retry design keeps ONE superset blob per root and will bring its own gate -for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agentsurfacecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck astqueryregexcheck astrocheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck buildtypestampcheck builtinbindcheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachereservecheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck ceilingverdictcheck chacheck chaconecheck chainguardcheck chainidcheck childwalkscalecheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck commenttokencheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck countfloorcheck cppbenchcheck cppoperatorcheck cppqualcheck cpptmplscopecheck crashsweepcheck crawlescapecheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck declinecheck declinedlistcheck decltodefcheck deeptailcheck defaultceilingcheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck diagnoticecheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editcheckdeclcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck elixirnamearitycheck elixirsemanticcheck emitescapecheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck enumtablecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandbodyfirstcheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check extentcheck externalvetocheck falseedgecheck fficheck fieldaffinitycheck fieldidcheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnliteralcheck fnptrcheck forautobodycheck forblowupcheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forhdrshapecheck forlenscheck formatgatecheck formaxtokenscheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forsectioncollapsecheck forsigspancheck forwidencheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck gdscriptcheck genrecallcheck gitenvhermeticcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck gokindcheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck hazardpatterncheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactdepthcheck impactimportcheck impactpartitioncheck importcapcheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javamethodrefcheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck jsxcallcheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legendrefcheck legobundlecheck legocheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck listingpagingcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck macroreparsecheck manifestcheck mapdatasectioncheck mapdiffcheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadasancheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstdiolinecapcheck mcpstrictschemacheck mcptoolprunecheck mcptoolsubsetcheck mcptranchecheck mcptwinclaimscheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck memguardcheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck noaliascheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck notesdegradecheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck osswitchcheck oswin32logiccheck outlinecheck overbudgetcommentcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck pathgapcheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck ppdeadrolescheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck preprocdeadscalecheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pymodulealiascheck pyshapecheck qackconcurrencycheck qackorigincheck qbaselineproducercheck qchurncheck qchurnmemocheck qddialscheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qsnapproducercheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck recentscopecheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexguardcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck rootspellingcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyargcheck rubyattrscheck rubybarecallcheck rubyclassrecvcheck rubyconstcheck rubydeclrefcheck rubydescribedclasscheck rubyinheritcheck rubymetricscheck rubyrakejbuildercheck rubyreachcheck rubyrecvcheck rubyrecvnarrowcheck rubyrequirecheck rubyrspectargetcheck rubyscopecheck rubysettercheck rubytypedrecvcheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck scroundtripcheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcheckcheck selfcontainedcheck shadowcheck shallowhistorycheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sidecarsymlinkcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck situshapecheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skipclassifycheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck stdqualcheck strkerncheck structlayoutcheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck tempfilesymlinkcheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck traceasanlinearcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do +for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agentsurfacecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck astqueryregexcheck astrocheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck buildtypestampcheck builtinbindcheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachereservecheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck ceilingverdictcheck chacheck chaconecheck chainguardcheck chainidcheck childwalkscalecheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck commenttokencheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck countfloorcheck cppbenchcheck cppoperatorcheck cppqualcheck cpptmplscopecheck crashsweepcheck crawlescapecheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck declinecheck declinedlistcheck decltodefcheck deeptailcheck defaultceilingcheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck diagnoticecheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editcheckdeclcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck elixirnamearitycheck elixirsemanticcheck emitescapecheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck enumtablecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandbodyfirstcheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check extentcheck externalvetocheck falseedgecheck fficheck fieldaffinitycheck fieldidcheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnliteralcheck fnptrcheck forautobodycheck forblowupcheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forhdrshapecheck forlenscheck formatgatecheck formaxtokenscheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forsectioncollapsecheck forsigspancheck forwidencheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck gdscriptcheck genrecallcheck gitenvhermeticcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck gokindcheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck hazardpatterncheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactdepthcheck impactimportcheck impactpartitioncheck importcapcheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javamethodrefcheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck jsxcallcheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legendrefcheck legobundlecheck legocheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck listingpagingcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck macroreparsecheck manifestcheck mapdatasectioncheck mapdiffcheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadasancheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstdiolinecapcheck mcpstrictschemacheck mcptoolprunecheck mcptoolsubsetcheck mcptranchecheck mcptwinclaimscheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck memguardcheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck noaliascheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck notesdegradecheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck osswitchcheck oswin32logiccheck outlinecheck overbudgetcommentcheck ownerhopcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck pathgapcheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck ppdeadrolescheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck preprocdeadscalecheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pymodulealiascheck pyshapecheck qackconcurrencycheck qackorigincheck qbaselineproducercheck qchurncheck qchurnmemocheck qddialscheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qsnapproducercheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck recentscopecheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexguardcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck rootspellingcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyargcheck rubyattrscheck rubybarecallcheck rubyclassrecvcheck rubyconstcheck rubydeclrefcheck rubydescribedclasscheck rubyinheritcheck rubymetricscheck rubyrakejbuildercheck rubyreachcheck rubyrecvcheck rubyrecvnarrowcheck rubyrequirecheck rubyrspectargetcheck rubyscopecheck rubysettercheck rubytypedrecvcheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck scroundtripcheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcheckcheck selfcontainedcheck shadowcheck shallowhistorycheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sidecarsymlinkcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck situshapecheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skipclassifycheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck stdqualcheck strkerncheck structlayoutcheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck tempfilesymlinkcheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck traceasanlinearcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do [ -f "$ROOT/test/$_g.sh" ] || continue if RIPWIRE_BIN="$BIN" bash "$ROOT/test/$_g.sh" >/dev/null 2>&1; then ok "absorb gate ($_g.sh)" From d1eb69cfe7f38dd4d9bebade44fd9bf2cde3b6f8 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 11:37:53 -0400 Subject: [PATCH 046/170] test(#358): JS/TS arms for import attributes, default+named, export default re-export; gate verdict as if/else MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Siblings of the shape: `import x with { type: "json" }`, `import d, { n } from`, `export { default } from`, `export { default as D } from '…'` (single quote), plus two near-miss negatives (`import(…).then`, `!require(…)`). Expected lists were re-read off the pre-change binary. The deterministic-arm verdict is if/else so a failed PASS write cannot print FAIL (gateexitcheck G2). Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 2 +- test/importcapcheck.sh | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1671f1c56..e146bcf7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1169,7 +1169,7 @@ learn the walk would never have entered the class). Both are pinned in `test/imp `--impact`, `--callers`, `--zoom`, `--report` and `--for` over three TypeScript repositories, the CommonJS tree of a global npm install, two npm cache trees, a `.astro` tree and this repository's `test/` fixtures. `test/importcapcheck.sh` gains a JS/TS section (J1–J8): an exact edge list, in source order, for `.ts .js .tsx .mjs .cjs .jsx` and a `.astro` -frontmatter (64 edges, with the `neg_*` shapes that must stay out), the lazy bit, the use-site half, the depth bound at +frontmatter (68 edges, with the `neg_*` shapes that must stay out), the lazy bit, the use-site half, the depth bound at 254/255/256 blocks, the two disclosure differences, and cache round-trip with a pre-change cache accepted unchanged (no `kParserVer` bump). diff --git a/test/importcapcheck.sh b/test/importcapcheck.sh index e1657a5fd..06b572e8a 100755 --- a/test/importcapcheck.sh +++ b/test/importcapcheck.sh @@ -482,7 +482,11 @@ import "./es_sideeffect"; import type { T } from "./es_type_named"; import type DefT from "./es_type_default"; import { type U, V } from "./es_inline_type"; +import dflt, { n1 } from "./es_default_and_named"; +import json from "./es_import_attributes" with { type: "json" }; export * from "./re_star"; +export { default } from "./re_default"; +export { default as D } from './re_default_as_single_quote'; export * as hns from "./re_star_as"; export { x as y } from "./re_named_as"; export type { Z } from "./re_type"; @@ -508,6 +512,8 @@ const cat = require("./neg_concat" + sfx); const res = require.resolve("./neg_resolve"); const nw = new Foo(require("./neg_new_arg")); const nest = foo(bar(require("./fn_arg_nested"))); +const th = import("./neg_dynamic_then").then(( m ) => m); +const bang = !require("./neg_unary"); function f() { return require("./lazy_function"); } const g2 = () => import("./lazy_arrow"); class K { m() { return require("./neg_class_method"); } } @@ -580,8 +586,8 @@ web_fixture() { # $1 = an empty directory } # The expected Include targets per file, in source order, as one space-separated line each — read off the -# PRE-CHANGE binary on exactly this fixture. 36 / 15 / 4 / 3 / 2 / 1 / 3 edges. -W_MAIN_TS='./es_default ./es_named ./es_namespace ./es_sideeffect ./es_type_named ./es_type_default ./es_inline_type ./re_star ./re_star_as ./re_named_as ./re_type ./cjs_const ./cjs_destructured ./cjs_var_single ./cjs_bare ./cjs_module_exports ./dyn_await ./cjs_optional_call ./fn_arg_nested ./lazy_function ./lazy_arrow ./lazy_getter ./if_then ./if_else ./try_body ./catch_body ./for_body ./export_default_require ./export_const_require ./export_fn_dynamic ./callback_fn ./callback_arrow ./callback_second ./paren_require ./await_require ./labeled' +# PRE-CHANGE binary on exactly this fixture. 40 / 15 / 4 / 3 / 2 / 1 / 3 edges. +W_MAIN_TS='./es_default ./es_named ./es_namespace ./es_sideeffect ./es_type_named ./es_type_default ./es_inline_type ./es_default_and_named ./es_import_attributes ./re_star ./re_default ./re_default_as_single_quote ./re_star_as ./re_named_as ./re_type ./cjs_const ./cjs_destructured ./cjs_var_single ./cjs_bare ./cjs_module_exports ./dyn_await ./cjs_optional_call ./fn_arg_nested ./lazy_function ./lazy_arrow ./lazy_getter ./if_then ./if_else ./try_body ./catch_body ./for_body ./export_default_require ./export_const_require ./export_fn_dynamic ./callback_fn ./callback_arrow ./callback_second ./paren_require ./await_require ./labeled' W_MAIN_JS='./es_default ./es_named ./es_namespace ./es_sideeffect ./re_star ./re_star_as ./re_named_as ./cjs_const ./cjs_destructured ./cjs_bare ./cjs_module_exports ./dyn_await ./lazy_function ./lazy_arrow ./lazy_exports_fn' W_COMP_TSX='react ./tsx_button ./tsx_card ./tsx_cjs' W_MOD_MJS='./mjs_import ./mjs_reexport ./mjs_cjs' @@ -624,13 +630,13 @@ for pair in "main.ts:$W_MAIN_TS" "main.js:$W_MAIN_JS" "comp.tsx:$W_COMP_TSX" "mo printf ' expected: %s\n got: %s\n' "$want" "${got:-}" fi done -[ "$WEB_BAD" -eq 0 ] && ok "J2: exact Include set and order for .ts .js .tsx .mjs .cjs .jsx and the .astro frontmatter (64 edges)" +[ "$WEB_BAD" -eq 0 ] && ok "J2: exact Include set and order for .ts .js .tsx .mjs .cjs .jsx and the .astro frontmatter (68 edges)" NEG=$(grep -o ' to run it)" fi "$BIN" "$WEB" --no-cache --deps > "$TMP/web.d2.xml" 2>/dev/null -cmp -s "$TMP/web.deps.xml" "$TMP/web.d2.xml" && ok "J8: deterministic (two --no-cache JS/TS runs identical)" || no "J8: non-deterministic JS/TS output" +if cmp -s "$TMP/web.deps.xml" "$TMP/web.d2.xml"; then + ok "J8: deterministic (two --no-cache JS/TS runs identical)" +else + no "J8: non-deterministic JS/TS output" +fi # ── well-formed XML ─────────────────────────────────────────────────────────────────────────────────── if command -v xmllint >/dev/null 2>&1; then From eea3bf72bdbb68bc6388199619da9185f67038b6 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 12:04:26 -0400 Subject: [PATCH 047/170] fix(#358): import reach read in one descent from the root, not k upward ts_node_parent calls The first version walked up from the directive, bounded to 256 HOPS, but every ts_node_parent is itself a descent from the root, so the bound capped the hops and not the cost: a JS file of 2 000 nested foo( "x", foo( "x", ... ) ) calls (each matches the call pattern) took 152 s against 0.5 s for the walk it replaced. One descent (ts_node_child_with_descendant, the enclosingFunctionScope precedent) answers the same three questions - reachable, depth, inside a function - at O(depth) per directive. Language-neutral: the C-family arm shares the function. Outputs unchanged; the disclosure pins in test/importcapcheck.sh hold. Co-Authored-By: Claude Sonnet 5.5 --- src/ingest_importcap.h | 56 +++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/ingest_importcap.h b/src/ingest_importcap.h index d21f61882..09b4ff370 100644 --- a/src/ingest_importcap.h +++ b/src/ingest_importcap.h @@ -85,33 +85,55 @@ struct ImportReach // So reach is restored here and the widening is left as its own follow-up. // // The file root is deliberately NOT tested: its children are what the walk always started from, so -// `isImportContainer(root)` being false must not reject a file-scope include. The loop stops when -// `p`'s own parent is null, which is the root. +// `isImportContainer(root)` being false must not reject a file-scope include. The ancestors read are the +// nodes STRICTLY BETWEEN the root and the directive. +// +// ONE DESCENT FROM THE ROOT, not an upward walk. tree-sitter nodes hold no parent pointer, so every +// ts_node_parent is itself a descent from the root and walking k ancestors up costs k of them (the precedent +// and its measurement: ingest_names.h::enclosingFunctionScope — 600 nested blocks, 19.7 s upward against +// 9.4 s down). The first version of this function walked up, bounded to kMaxImportContainerDepth hops, and +// that bound capped the HOPS, not the cost: a TS/JS file of 2 000 nested `foo( "x", foo( "x", … ) )` calls +// (a hyperscript tree; every one matches the call pattern) took 152 s against 0.5 s for the walk it replaced. +// Descending once is O(depth) per directive, and what the upward loop answered is read off the same descent: +// `run` is how many container ancestors sit directly above the directive (the loop's reach before it met a +// non-container or the bound), `total` is how many ancestors there are. Reachable means the run is all of them, +// or the run already passed the bound (the loop stops at the bound before it can see a non-container above — +// that is the one LOUDER disclosure difference test/importcapcheck.sh pins, kept exactly). ImportReach importContainerReach( TSNode directive, Lang lang ) noexcept { const bool directiveIsNode = !ts_node_is_null( directive ); // hoisted: a promise holds no call (selfcheckcheck C) EXPECTS( directiveIsNode, "the directive is a node of the parsed tree: importDirectiveOf never returns null for a captured specifier" ); - ImportReach reach; - for( TSNode p = ts_node_parent( directive ); !ts_node_is_null( p ); p = ts_node_parent( p ) ) + std::uint32_t total = 0; + std::uint32_t run = 0; // container ancestors directly above the directive (reset by a non-container) + bool fnInRun = false; // some container in `run` is a function body + TSNode n = ts_node_child_with_descendant( ts_tree_root_node( directive.tree ), directive ); + for( ; !ts_node_is_null( n ) && !ts_node_eq( n, directive ); n = ts_node_child_with_descendant( n, directive ) ) { - if( ts_node_is_null( ts_node_parent( p ) ) ) + const char* t = ts_node_type( n ); + ++total; + if( isImportContainer( lang, t ) ) { - break; // p is the file root: reached unconditionally + ++run; + // The walk's `childInsideFn = frame.insideFn || isFunctionLike( lang, t )` is sticky downward, so the + // directive is inside a function exactly when ANY container above it is a function-body kind. + fnInRun = fnInRun || isFunctionLike( lang, t ); } - if( !isImportContainer( lang, ts_node_type( p ) ) ) + else { - reach.reachable = false; // the walk would not have entered this node - break; + run = 0; + fnInRun = false; } - // The walk's `childInsideFn = frame.insideFn || isFunctionLike( lang, t )` is sticky downward, so the - // directive is inside a function exactly when ANY container above it is a function-body kind. - reach.insideFn = reach.insideFn || isFunctionLike( lang, ts_node_type( p ) ); - if( ++reach.depth > kMaxImportContainerDepth ) - { - break; // STOPS AT THE BOUND: past it the answer cannot change, and - } // a hostile file must not buy an unbounded parent walk } - ENSURES( reach.depth <= kMaxImportContainerDepth + 1, "the ancestor loop stops one past the bound, so a hostile file buys a bounded parent walk" ); + ImportReach reach; + if( ts_node_is_null( n ) ) + { + reach.reachable = false; // the descent lost the directive: read as "the walk would not have entered", a floor + return reach; + } + reach.depth = static_cast( std::min( run, kMaxImportContainerDepth + 1u ) ); + reach.reachable = ( run == total ) || ( run > kMaxImportContainerDepth ); + reach.insideFn = fnInRun; + ENSURES( reach.depth <= kMaxImportContainerDepth + 1, "the depth is clamped one past the bound" ); return reach; } From c5486a49958aa9ff44da312dea1cb21e2c42161d Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 12:15:14 -0400 Subject: [PATCH 048/170] fix(#358): text gate before the ancestry read; a non-import under a too-deep nest is no longer announced Language-neutral (the C-family arm shares emitCapturedImport): most Web captures are ordinary f( "s" ) calls, and reading the ancestry just to drop them is the cost the previous commit measured. Dropping first also removes a false "an import was cut" on a #pragma once / foo( "x" ) under a too-deep nest. Co-Authored-By: Claude Sonnet 5.5 --- src/ingest_importcap.h | 19 ++++++++++++------- test/importcapcheck.sh | 22 ++++++++++++++++++++-- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/ingest_importcap.h b/src/ingest_importcap.h index 09b4ff370..7f61cb754 100644 --- a/src/ingest_importcap.h +++ b/src/ingest_importcap.h @@ -281,8 +281,18 @@ TSNode importDirectiveOf( TSNode pathNode ) noexcept void emitCapturedImport( TSNode pathNode, std::uint32_t fileId, Lang lang, std::string_view src, std::vector& includes, std::vector& refs, ExtractShortfall& shortfall ) { - const TSNode directive = importDirectiveOf( pathNode ); - const ImportReach reach = importContainerReach( directive, lang ); + const TSNode directive = importDirectiveOf( pathNode ); + // TEXT FIRST, ancestry second. The Web call pattern matches every bare `f( "s" … )`, so most captures here are + // not imports at all, and the reach read below is a descent from the root — paying it to learn that `t( "key" )` + // is not a dependency is the cost that made a 2 000-deep hyperscript tree 300x slower. Dropping a non-import + // BEFORE the bound test also means a `#pragma once` / `foo( "x" )` under a too-deep nest no longer announces + // "an import was cut" when none was (the walk announced on any too-deep container, import or not). + const ImportSpec spec = normaliseImportSpecifier( dependencyDialect( lang ), directive, nodeTextOf( pathNode, src ), src ); + if( spec.text.empty() ) + { + return; + } + const ImportReach reach = importContainerReach( directive, lang ); if( reach.depth > kMaxImportContainerDepth ) { DISCLOSE( shortfall, ExtractShortfall::DisclosureWhy::ImportNestingTooDeep, @@ -293,11 +303,6 @@ void emitCapturedImport( TSNode pathNode, std::uint32_t fileId, Lang lang, std:: { return; // a non-container ancestor the walk would never have entered — see importContainerReach } - const ImportSpec spec = normaliseImportSpecifier( dependencyDialect( lang ), directive, nodeTextOf( pathNode, src ), src ); - if( spec.text.empty() ) - { - return; - } if( std::string name = importName( spec.text ); !name.empty() ) { RawRef r; diff --git a/test/importcapcheck.sh b/test/importcapcheck.sh index 06b572e8a..a7efa1902 100755 --- a/test/importcapcheck.sh +++ b/test/importcapcheck.sh @@ -466,7 +466,7 @@ fi # tier; a top-level one is lazy="0"; one file required both ways is lazy="0" (the pair rule) # J5 the use-site half — --uses reports the import-role ref of a named import # J6 the DEPTH BOUND on blocks — kept at 255 and 256 containers, cut AND announced at 257, as the walk did -# J7 the two DELIBERATE disclosure differences (same two as the C-family arm 12c, same reason) +# J7 the DELIBERATE disclosure differences (the two of the C-family arm 12c, same reason, plus a non-import under a too-deep nest) # J8 cache round-trip, a pre-change binary's cache accepted (no kParserVer bump), determinism # # The expected lists are the SPECIFICATION and were read off the pre-change binary, never reasoned out: this @@ -688,7 +688,7 @@ for n in 254 255 256; do done [ "$WB_BAD" -eq 0 ] && ok "J6: depth bound — kept under 255 and 256 containers, cut AND announced under 257 (edges identical to the walk's)" -# ── J7: the two DELIBERATE disclosure differences, in both directions (the C-family arm 12c, for JS) ──── +# ── J7: the DELIBERATE disclosure differences, in both directions (the C-family arm 12c, for JS) ──── # (i) QUIETER: `require('./deep')` is ITSELF a container, so the walk refused to descend into it from depth # 256 and announced, although the edge was kept and nothing was dropped. n=255 above is that shape: edge # kept, and this binary says nothing because nothing was lost (the walk said extract-partial). @@ -707,6 +707,24 @@ else no "J7: class-method-under-300: edges=$LE partial=$LF (want 0/1); 255-blocks partial=$QF (want 0)" fi +# (iii) QUIETER, and no import in the file at all: 300 nested blocks around `foo( "x" )` (a call the Web pattern +# captures and the text gate drops), and 300 nested #ifdef around `#pragma once` (captured by the C/C++ +# preproc_call pattern, dropped by the #import gate). The walk announced on any too-deep CONTAINER, and the +# first version of this arm announced because the bound test ran before the text gate; both said "an import +# was cut" when nothing import-shaped existed. The text gate now runs first, so neither does. +WQ="$TMP/wquiet"; mkdir -p "$WQ/js" "$WQ/c" +{ i=0; while [ "$i" -lt 300 ]; do printf '{'; i=$((i + 1)); done + printf ' foo("x"); '; i=0; while [ "$i" -lt 300 ]; do printf '}'; i=$((i + 1)); done; printf '\n'; } > "$WQ/js/a.js" +{ i=0; while [ "$i" -lt 300 ]; do printf '#ifdef G%s\n' "$i"; i=$((i + 1)); done + printf '#pragma once\n'; i=0; while [ "$i" -lt 300 ]; do printf '#endif\n'; i=$((i + 1)); done; } > "$WQ/c/a.c" +QJ=$("$BIN" "$WQ/js" --skipped --no-cache 2>/dev/null | grep -c 'why="extract-partial"' | tr -d '[:space:]' || true) +QC=$("$BIN" "$WQ/c" --skipped --no-cache 2>/dev/null | grep -c 'why="extract-partial"' | tr -d '[:space:]' || true) +if [ "$QJ" = "0" ] && [ "$QC" = "0" ]; then + ok "J7: no announcement when nothing import-shaped was cut (foo( \"x\" ) under 300 blocks, #pragma once under 300 #ifdef)" +else + no "J7: non-import under a too-deep nest announced an import cut: js=$QJ c=$QC (want 0/0)" +fi + # ── J8: cache round-trip and a pre-change cache ───────────────────────────────────────────────────── WC="$TMP/web.cache.bin" "$BIN" "$WEB" --cache="$WC" --deps > "$TMP/web.cold.xml" 2>/dev/null From a0b82c41b1e000d32b62b242c229b9d408977400 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 12:27:10 -0400 Subject: [PATCH 049/170] refactor(#358): reachable is run == total; the past-the-bound case is carried by depth Found by mutation: the clause that also called a run past the bound reachable was dead, because the caller tests depth > bound first and returns. Behaviour unchanged. Co-Authored-By: Claude Sonnet 5.5 --- src/ingest_importcap.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ingest_importcap.h b/src/ingest_importcap.h index 7f61cb754..34342ba1b 100644 --- a/src/ingest_importcap.h +++ b/src/ingest_importcap.h @@ -96,9 +96,10 @@ struct ImportReach // (a hyperscript tree; every one matches the call pattern) took 152 s against 0.5 s for the walk it replaced. // Descending once is O(depth) per directive, and what the upward loop answered is read off the same descent: // `run` is how many container ancestors sit directly above the directive (the loop's reach before it met a -// non-container or the bound), `total` is how many ancestors there are. Reachable means the run is all of them, -// or the run already passed the bound (the loop stops at the bound before it can see a non-container above — -// that is the one LOUDER disclosure difference test/importcapcheck.sh pins, kept exactly). +// non-container or the bound), `total` is how many ancestors there are. Reachable means the run is all of them. +// A run PAST the bound is reported by `depth` (clamped one past it), which the caller tests BEFORE `reachable`: the +// upward loop stopped at the bound without ever seeing a non-container above it, so a too-deep import is announced +// whether or not the walk would have entered it — the one LOUDER disclosure difference test/importcapcheck.sh pins. ImportReach importContainerReach( TSNode directive, Lang lang ) noexcept { const bool directiveIsNode = !ts_node_is_null( directive ); // hoisted: a promise holds no call (selfcheckcheck C) @@ -131,7 +132,7 @@ ImportReach importContainerReach( TSNode directive, Lang lang ) noexcept return reach; } reach.depth = static_cast( std::min( run, kMaxImportContainerDepth + 1u ) ); - reach.reachable = ( run == total ) || ( run > kMaxImportContainerDepth ); + reach.reachable = ( run == total ); reach.insideFn = fnInRun; ENSURES( reach.depth <= kMaxImportContainerDepth + 1, "the depth is clamped one past the bound" ); return reach; From 09b4e2b33d5f67ba51fb5cf2dd9e489a2b65c341 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 12:44:27 -0400 Subject: [PATCH 050/170] test(#358): the two JS/TS no-edge ambient/namespace shapes move to their own fixture file Keeps each fixture function under the verbosity bar. Same expectations; no source change. Co-Authored-By: Claude Sonnet 5.5 --- test/importcapcheck.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/importcapcheck.sh b/test/importcapcheck.sh index a7efa1902..5a953ed0a 100755 --- a/test/importcapcheck.sh +++ b/test/importcapcheck.sh @@ -522,8 +522,6 @@ if (x) { require("./if_then"); } else { require("./if_else"); } try { require("./try_body"); } catch (e) { require("./catch_body"); } for (;;) { require("./for_body"); } switch (x) { case 1: require("./switch_case"); } -declare module "amb" { import q from "./neg_ambient"; } -namespace NS { import w = require("./neg_ns_equals"); const z2 = require("./neg_ns_const"); } export default require("./export_default_require"); export const ex = require("./export_const_require"); export function ef() { return import("./export_fn_dynamic"); } @@ -560,6 +558,10 @@ exports.run = function () { return require("./lazy_exports_fn"); }; JS } web_fixture_small() { local W="$1" # .tsx .mjs .cjs .jsx and the .astro frontmatter + cat > "$W/neg.ts" <<'NEG' +declare module "amb" { import q from "./neg_ambient"; } +namespace NS { import w = require("./neg_ns_equals"); const z2 = require("./neg_ns_const"); } +NEG cat > "$W/comp.tsx" <<'TSX' import React from "react"; import { Button } from "./tsx_button"; @@ -575,7 +577,7 @@ TSX web_fixture_stubs() { local W="$1" # every specifier above names a real file, so each edge RESOLVES and the lazy bit and --uses are observable local spec - for spec in $(cat "$W"/main.ts "$W"/main.js "$W"/comp.tsx "$W"/mod.mjs "$W"/mod.cjs "$W"/view.jsx "$W"/page.astro \ + for spec in $(cat "$W"/main.ts "$W"/neg.ts "$W"/main.js "$W"/comp.tsx "$W"/mod.mjs "$W"/mod.cjs "$W"/view.jsx "$W"/page.astro \ | grep -o '"\./[A-Za-z_0-9]*"\|'"'"'\./[A-Za-z_0-9]*'"'" | tr -d "\"'" | sort -u); do printf 'export const %s = 1;\n' "${spec#./}" > "$W/${spec#./}.js" done From 9a3e6c17d656baf944e70aa66fd20f74bc9fdfb9 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 12:54:18 -0400 Subject: [PATCH 051/170] docs(#358): CHANGELOG states the final disclosure differences and the one-descent ancestry read Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e146bcf7b..d965d4b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1152,25 +1152,35 @@ the bare name `require`/`import`, there is exactly one argument, and it is a str are re-derived from the captured node's ancestry, not dropped: the **container reach** (the walk entered only an allowlist, so `require('x').y`, `[require('x')]`, `a ? require('x') : b`, a class method body and a `declare module` body were never edges, and still are not) and the **lazy bit** (a `require`/`import` call written inside a function -body is `lazy="1"` on `--impact`'s import tier, from the same ancestor loop that checks the reach). +body is `lazy="1"` on `--impact`'s import tier, from the same ancestor read that checks the reach). **Not changed, deliberately.** `import x = require('y')` (TypeScript) is not an edge, before or after: the grammar gives it its own `import_require_clause`, which the walk never read and no pattern here matches. `@import.alias` and `@import.names` are not used: the Include record of a JS/TS import names a module and nothing else; the bound names are a different record (`captureJsImportFacts`, untouched). -**The same disclosure difference as the C-family slice, in `--skipped` only.** `extract-partial` for import nesting now -fires only when a captured import is actually cut at the bound. A `require('x')` is itself a container to the old walk, -so it announced at 254 and 255 nested blocks although the edge was kept; this does not. In the other direction, an -import under 300 nested blocks inside a class method is announced (the ancestor loop reaches the bound before it can -learn the walk would never have entered the class). Both are pinned in `test/importcapcheck.sh`. +**Disclosure differences from the walk, in `--skipped` only** (the C-family slice's two, plus a third in the function both languages share). +`extract-partial` for import nesting now fires only when a captured import is actually cut at the bound. A `require('x')` +is itself a container to the old walk, so it announced at 254 and 255 nested blocks although the edge was kept; this +does not. A call the pattern captures but the `require`/`import` text gate drops (`foo('x')`) under 300 nested blocks is +not announced either, and neither is a C-family `#pragma once` under 300 `#ifdef` — the text gate now runs before the +bound test, in the one function both languages share, so an import was never cut. In the other direction, an import +under 300 nested blocks inside a class method is announced (the ancestor read reaches the bound before it can learn the +walk would never have entered the class). All three are pinned in `test/importcapcheck.sh`. + +**The ancestry is read in one descent from the root.** `ts_node_parent` is itself a descent from the root, so walking up +k ancestors costs k of them. The first version of this slice did that, bounded to 256 hops, and a synthetic file of +2 000 nested `foo( "x", foo( "x", … ) )` calls took 152 s against 0.5 s for the walk it replaced (Apple M2 Pro, one run +each, loaded machine). The final version reads reach, depth and the lazy bit off one `ts_node_child_with_descendant` +descent, after the text gate, and the same file takes 0.9 s. On three TypeScript repositories and the CommonJS tree of a +global npm install the `--deps` wall time is unchanged to within run-to-run noise. **Dependency edges are byte-identical.** Verified with both binaries against unchanged trees: `--deps`, `--uses`, `--impact`, `--callers`, `--zoom`, `--report` and `--for` over three TypeScript repositories, the CommonJS tree of a global npm install, two npm cache trees, a `.astro` tree and this repository's `test/` fixtures. `test/importcapcheck.sh` gains a JS/TS section (J1–J8): an exact edge list, in source order, for `.ts .js .tsx .mjs .cjs .jsx` and a `.astro` frontmatter (68 edges, with the `neg_*` shapes that must stay out), the lazy bit, the use-site half, the depth bound at -254/255/256 blocks, the two disclosure differences, and cache round-trip with a pre-change cache accepted unchanged +254/255/256 blocks, the disclosure differences, and cache round-trip with a pre-change cache accepted unchanged (no `kParserVer` bump). ### Changed — `--affected` and `--test-gate` mark which tests to run first (`run_first=`) From 90e28e11ffffc71975c81a5dbf85e597cee75c11 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 13:20:30 -0400 Subject: [PATCH 052/170] test: ownerhopcheck gate changes from the early gates review (C1-C9), red on the 26b phase-B head The early gates review (GATES-OK-WITH-CHANGES) found wrong implementations that passed every arm. Each is now pinned: - (I) compares the head with RIPWIRE_NO_OWNER_HOP=1 against the BASE binary on the owner questions, --json --for, the MCP for verb and two explicit budgets, and the head as shipped against the base on the no-owner questions, beside the 12 out-of-scope argv; every binary's rc and output are checked. - (D) no (p,n,l) appears in two rows of any block the gate parses (moved, never duplicated). - (C) pins which three owners, in rank order, the exact next=, and that a cap-cut owner that was a hop candidate stays a plain row. The fixture's three helpers get a proven callee so they can be rows. - (P/spelling) "GATEWAY", `gateway()`, gateway's and gateway? name the same owner. - (G/guard) pins exactly 100 names. (Z) pins noedge = A/B + 1 (definition + prototype are one owner). (Z2) an owner whose only edge is name-only: no row, counted once, byte-identical. - (B1b, B3) an explicit budget never drops an owner's callee names silently. - (V) common-English-verb callables (get, then, write) ranking above the owner never take its slot. - (F) a missing via="name" hedge is a FAIL now that the hedge is in the base. - (N) a class-only question and a test-file word (now with a proven callee) are byte-identical to the A/B handle. (R) a base row's calls are a prefix of the head row's: a moved owner only frees budget. Gate count 677 -> 678 after merging the phase-B head (receiverevidencecheck + ownerhopcheck). Co-Authored-By: Claude Opus 5.5 --- README.md | 4 +- docs/EVALS.md | 6 +- present/deck5_ripwire_build.js | 6 +- test/ownerhopcheck.sh | 341 ++++++++++++++------ test/ownerhopfix/py/pkg/reactive.py | 8 + test/ownerhopfix/ts/src/middleware/relay.ts | 6 + test/ownerhopfix/ts/src/util/io.ts | 15 + test/ownerhopfix/ts/test/gateway.test.ts | 6 +- 8 files changed, 292 insertions(+), 100 deletions(-) create mode 100644 test/ownerhopfix/ts/src/middleware/relay.ts create mode 100644 test/ownerhopfix/ts/src/util/io.ts diff --git a/README.md b/README.md index 22953da7d..98db7b871 100644 --- a/README.md +++ b/README.md @@ -2082,9 +2082,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published ### In the tests
-677 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +678 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures -`test/regression.sh` names **677 gate scripts** and is the authoritative list; +`test/regression.sh` names **678 gate scripts** and is the authoritative list; `python3 test/pargates.py . ./build/ripwire -j 6` runs the same set in parallel. On top of them sit the contracts that do not fit a unit test: two runs byte-identical, warm output identical to cold, output that pipes clean through `xmllint --noout`, a sanitizer build with `-fno-sanitize-recover=all`, and a diff --git a/docs/EVALS.md b/docs/EVALS.md index 03c782613..005a31c08 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 677 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 678 gate scripts plus the determinism, cache-transparency and golden contracts. | | **`--quality-delta`** | `src/quality.h` | Ten measured code-quality failure modes, reported only where a change made them worse. | ### The labeling protocol (why the held-out eval is allowed to disagree with the ranker) @@ -5888,7 +5888,7 @@ copy here would be exactly the dialect divergence that gate exists to catch. Com tags, wrap, stable-order defaults), seven individually invoked standalone gates (`g1freshcheck`, `skillscan`, `htmlexport`, `compresscheck`, `handoffcheck`, `releaseinstallcheck`, `taskroutecheck`), and a single loop -naming **677 gate scripts**, all of which exist on disk. +naming **678 gate scripts**, all of which exist on disk. `python3 test/pargates.py . ./build/ripwire -j 6` runs the same scripts in parallel so a full verification fits in one sitting. It does not modify `regression.sh`. @@ -6991,7 +6991,7 @@ Listed because the reason is more useful than the silence. shipped**. See `bench/locbench/anchorhop_calib.json`. The mention anchor's reproducible numbers are the ablations in §4. - **A single round gate-count.** Two in-tree numbers disagree (`test/pargates.py`'s docstring says - ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 677. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 678. The loop is the authority; the stale docstrings are a known drift. Since 2026-09-10 the number is not written by hand anywhere: `docs/gatecount_build.py` derives it from the loop and rewrites every published site, `test/gatecountcheck.sh` fails if any of them drifts, and `test/manifestcheck.sh` diff --git a/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index 7da7b43f3..ddb51d145 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1214,7 +1214,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["677 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["678 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1238,7 +1238,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "677", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "678", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1540,7 +1540,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["187 long flags · 36 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["677 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["678 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 71 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/test/ownerhopcheck.sh b/test/ownerhopcheck.sh index 8f4ebb54b..577dbb00e 100755 --- a/test/ownerhopcheck.sh +++ b/test/ownerhopcheck.sh @@ -18,43 +18,61 @@ # stopword) — whole name only, never a substring ("log" never matches catalog/dialog); and that word has at # least one such definition inside the head tier (rank <= serialize.h kForDocExcerptRankCount = 24). Every # same-named definition of an owner word in the ranked pool is shown (never one picked silently), each as its own -# row (entity identity: two classes' _notify are two rows). -# SLOT. Owner rows come FIRST in , in rank order, regardless of the 6-candidate cap; each carries -# qword="". An owner that was already a hop candidate is moved, never duplicated. At most 3 -# owner rows; the rest are disclosed: (the first one cut). -# EDGES. The hop-slot rule still applies (>= 1 proven callee edge, else counted in noedge=). An owner row lists ALL -# its callee names (not the 16 a ranked hop shows) up to a runaway guard; past it: capped="1" + next=. -# A callee bound by name alone keeps FE-B's via="name" hedge (a builtin method, a test-file def, an untyped -# receiver) — never an unhedged claim. +# row (entity identity: two classes' _notify are two rows). Question words are split on every non-identifier +# byte, so "GATEWAY", `gateway()`, "gateway's" and "gateway?" all name the word gateway. +# SLOT. Owner rows come FIRST in , regardless of the 6-candidate cap; each carries qword="". +# An owner that was already a hop candidate is moved, never duplicated. At most 3 owner rows, in rank order — +# except that a word on the small common-English list (get, then, write, …) ranks AFTER every other owner word, +# so verbs the question merely USES never spend the slots before the function it is about. The rest are +# disclosed: (the first one cut). A cut owner that was already one of the +# six candidates stays where it was, a plain row (qword_cut counts owners not served as an OWNER row). +# EDGES. The hop-slot rule still applies (>= 1 proven callee edge, else counted in noedge= — exactly once, and an +# owner that is not an owner row never takes a slot). An owner row lists ALL its callee names (not the 16 a +# ranked hop shows) up to a runaway guard of 100; past it, and whenever an explicit budget cuts it: capped="1" + +# next= on its . A callee bound by name alone keeps FE-B's via="name" hedge — never an unhedged claim. # BUDGET. Default regime: owner rows ride ON TOP of the hop budget (completeness first; the existing hop rows keep # their budget, so every pre-change row is still served). Explicit --token-budget: owner rows are funded -# first; anything cut is disclosed by the existing capped="1". -# OFF. A question that names no owner is byte-identical to before; RIPWIRE_NO_OWNER_HOP=1 restores the pre-change -# answer everywhere (the A/B handle the no-regression arms compare against). --json and the MCP `for` verb serve -# no hop block today and stay byte-identical. +# first; a cut is disclosed (capped="1" + next= on the row, or qword_cut + next= when no row fits). +# OFF. A question that names no owner is byte-identical to the base binary; RIPWIRE_NO_OWNER_HOP=1 restores the +# base answer byte for byte everywhere (the A/B handle). --json and the MCP `for` verb serve no hop block today +# and stay byte-identical (MCP/JSON owner-hop parity is a named follow-up). # # ARMS (fixture roots under test/ownerhopfix/, each indexed on its own, --no-cache): # (P) positive: TS gateway (rank 7..24, beyond the 6-cap) is the FIRST row with qword="gateway" and names # readCookie + checkSessionToken; Python two _notify owners (Reactive, Signal) -> two rows, distinct l=; # C scan: ONE row, the table.c definition (the table.h prototype is not a second owner). -# (F) false-claim (needs FE-B; SKIP by name "expects FE-B" when the binary's own callee rows carry no via=): -# gateway's remember (untyped receiver) and encode (a test-file def of a builtin method name) carry -# via="name" inside the owner row; no unhedged row of an owner names a test-file definition. -# (Z) zero proven callees: C collect (only an indirect call) gets no row and is counted in noedge=. -# (N) near misses: "the log" never makes catalog/dialog owners; a class (Reactive) is never an owner; a word -# whose only definition is a test file (encode) is no owner; a non-code question carries no qword=. +# (P/case) "GATEWAY" and (P/punct) `gateway()`, gateway's, gateway? give the same first row. +# (D) every block the gate parses: no (p,n,l) appears in two rows (moved, never duplicated). +# (F) false-claim (FE-B is in the base): gateway's remember (untyped receiver) and encode (a test-file def of a +# builtin method name) carry via="name" inside the owner row; no unhedged row of an owner names a +# test-file definition. A binary without the FE-B hedge FAILs (it is a regression now, not a missing premise). +# (Z) zero proven callees: C collect (only an indirect call; definition r12 + prototype r13) gets no row and +# noedge(head) == noedge(A/B) + 1 exactly. (Z2) an owner whose only edge is name-only (relay -> remember): +# no row, never double-counted, byte-identical to the A/B handle. +# (N) near misses: "the log" never makes catalog/dialog owners; a class (Reactive) is never an owner and a +# class-only question is byte-identical to the A/B handle; a word whose only definition is a test file +# (encode, which has a proven callee) is no owner — byte-identical to the A/B handle; a non-code question carries +# no qword=. # (T) tier: a word whose only definition ranks > 24 makes no owner (broadcast); a word with one head-tier # definition shows EVERY same-named definition in the pool (the second _notify ranks > 24, still a row). -# (C) cap: five owners -> 3 rows + qword_cut="2" + next="--callees=..." naming the first cut owner. -# (G) guard: dispatch (20 callees) lists all 20 names; broadcast (105 callees) lists the guard's worth with +# (C) cap: five owners -> exactly Signal._notify, Reactive._notify, invoke_watcher (rank order) + qword_cut="2" + +# next="--callees=pkg/reactive.py:schedule_refresh"; the two cut owners stay plain rows; (R) holds. +# (V) common verbs: get/then/write methods rank above gateway — gateway is still the first owner row; the verbs +# fill the remaining two slots in rank order; qword_cut="1" next= names the third. +# (G) guard: dispatch (20 callees) lists all 20 names; broadcast (105 callees) lists exactly 100 (the guard) with # capped="1" and next="--callees=pkg/broadcast.py:broadcast". -# (R) no-regression: every row of the RIPWIRE_NO_OWNER_HOP=1 answer is still served, same content, -# same relative order (owner rows removed); non-owner questions byte-identical to the A/B handle. -# (B) explicit --token-budget=4000 (hops fit): the owner row is first, est_tokens <= 4000 or over_ceiling="1"; -# --token-budget=1200 (ceiling spent, reason="budget"): byte-identical to the A/B handle. +# (R) no-regression: every row of the RIPWIRE_NO_OWNER_HOP=1 answer is still served, in the same relative order +# (owner rows removed), its a prefix of the head row's (a moved owner only frees budget: more names of the +# same walk, never fewer, never others); non-owner questions byte-identical to the A/B handle. +# (B) explicit --token-budget=4500 (hops fit): the owner row is first, est_tokens <= 4500 or over_ceiling="1"; at 4000 +# (less room than the owner row needs) the owner is first or named by qword_cut + next=; +# --token-budget=1200 (ceiling spent, reason="budget"): byte-identical to the A/B handle. (B3) a budget sweep +# on broadcast: every owner row it serves is disclosed (capped="1" + next=), an absent one is disclosed +# (qword_cut + next= or reason="budget"), and at least one budget funds the row only partly. # (J) --json --for and MCP `for` byte-identical to the A/B handle (no hop surface there). # (L) legend: the qword clause is present iff a qword row is, and says "by name" (not a resolution claim). -# (I) optional: RIPWIRE_BASE_BIN set -> 12 out-of-scope argv byte-identical base vs head. +# (I) RIPWIRE_BASE_BIN set: head + RIPWIRE_NO_OWNER_HOP=1 vs base on the --for / --json / MCP / budget answers, +# head (no knob) vs base on the no-owner questions, plus 12 out-of-scope argv — every binary's rc checked. # # Exit: 0 all PASS (named SKIPs allowed), 1 any FAIL, 2 missing prerequisite. @@ -93,9 +111,11 @@ def cands(x): return [attrs(m.group(1)) for m in re.finditer(r']*)>', x)] cmd = sys.argv[1] x = open(sys.argv[2]).read() -if cmd == 'hops': # one JSON line: {hops attrs, rows:[{n,l,p,qword,calls:[{n,via}], ca}]} +if cmd == 'hops': # one JSON line: {hops attrs, rows:[{n,l,p,qword,calls:[{n,via}], ca}], dups:[(p,n,l) seen twice]} ha, rows = hops(x) - print(json.dumps({'hops': ha, 'rows': [dict(r['a'], calls=r['calls'], ca=r['ca'], raw=r['raw']) for r in rows]})) + keys = [(r['a'].get('p'), r['a'].get('n'), r['a'].get('l')) for r in rows] + dups = sorted({k for k in keys if keys.count(k) > 1}) + print(json.dumps({'hops': ha, 'rows': [dict(r['a'], calls=r['calls'], ca=r['ca'], raw=r['raw']) for r in rows], 'dups': dups})) elif cmd == 'rank': # rank of name N (first def, optional path substring P) in --format=candidates output n = sys.argv[3]; p = sys.argv[4] if len(sys.argv) > 4 else '' rs = [int(a['r']) for a in cands(x) if a.get('n') == n and p in a.get('p', '')] @@ -105,11 +125,17 @@ elif cmd == 'root': PY H(){ python3 "$TMP/h.py" "$@"; } J(){ python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(eval(sys.argv[1]))" "$1"; } +# HJ XML JSON LABEL — parse the block AND assert (D): no (p,n,l) in two rows (C2: moved, never duplicated) +ndup=0 +HJ(){ H hops "$1" >"$2"; local d; d="$( J "d['dups']" <"$2" )"; ndup=$((ndup+1)) + if [ "$d" != "[]" ]; then no "(D) $3: an row appears twice (owner moved AND left in place): $d"; fi; } # ── premises: the fixture still reproduces the gap (rank beyond the 6-cap, inside the head tier) ────────────────────── QTS="How does the gateway check a session token?" QPY="How does notify propagate a reactive change?" QC="How does scan walk the process tables?" +QCAP="How do invoke_watcher, schedule_refresh, record_signal and notify work?" +QV="How does the gateway get the session token, check it, then write it?" premise(){ # root question name pathsub lo hi local f="$TMP/prem.$1.$3.xml"; run "$1" "$f" "--for=$2" --format=candidates ran_ok "$f" "premise $1/$3" || return 1 @@ -120,36 +146,48 @@ premise(){ # root question name pathsub lo hi premise ts "$QTS" gateway src/middleware 7 24 premise py "$QPY" _notify pkg/reactive 7 24 premise c "$QC" scan table.c 7 24 +premise ts "$QV" gateway src/middleware 7 24 # ── (P) positive ───────────────────────────────────────────────────────────────────────────────────────────────────── f="$TMP/ts.p.xml"; run ts "$f" "--for=$QTS" if ran_ok "$f" "(P/TS)"; then - H hops "$f" >"$TMP/ts.p.json" + HJ "$f" "$TMP/ts.p.json" "(P/TS)" first="$( J "(d['rows'][0]['n'], d['rows'][0].get('qword',''), d['rows'][0]['p']) if d['rows'] else None" <"$TMP/ts.p.json" )" - if [ "$first" = "('gateway', 'gateway', 'src/middleware/gateway.ts')" ]; then ok "(P/TS) first row is gateway with qword=\"gateway\""; else no "(P/TS) first row is $first, want gateway / qword=gateway (rank-13 owner beyond the 6-cap)"; fi + if [ "$first" = "('gateway', 'gateway', 'src/middleware/gateway.ts')" ]; then ok "(P/TS) first row is gateway with qword=\"gateway\""; else no "(P/TS) first row is $first, want gateway / qword=gateway (rank-14 owner beyond the 6-cap)"; fi names="$( J "sorted(c['n'] for r in d['rows'] if r['n']=='gateway' for c in r['calls'])" <"$TMP/ts.p.json" )" case "$names" in *readCookie*checkSessionToken*|*checkSessionToken*readCookie*) ok "(P/TS) owner row names readCookie and checkSessionToken ($names)";; *) no "(P/TS) owner row callee names $names lack readCookie/checkSessionToken";; esac dup="$( J "sum(1 for r in d['rows'] if r['n']=='gateway')" <"$TMP/ts.p.json" )" if [ "$dup" = 1 ]; then ok "(P/TS) gateway appears in exactly one row"; else no "(P/TS) gateway rows: $dup (want 1)"; fi fi +# (P/case, P/punct) the question's spelling of the word: case folded, punctuation splits (C4 — hono-13 is "JWT") +pq=0 +for q in "How does the GATEWAY check a session token?" 'How does `gateway()` check a session token?' \ + "How does the gateway's check treat a session token?" "What does gateway? check in a session token"; do + pq=$((pq+1)); f="$TMP/ts.pq$pq.xml"; run ts "$f" "--for=$q" + ran_ok "$f" "(P/spelling $pq)" || continue + HJ "$f" "$TMP/ts.pq$pq.json" "(P/spelling $pq)" + first="$( J "(d['rows'][0]['n'], d['rows'][0].get('qword',''), d['rows'][0]['p']) if d['rows'] else None" <"$TMP/ts.pq$pq.json" )" + if [ "$first" = "('gateway', 'gateway', 'src/middleware/gateway.ts')" ]; then ok "(P/spelling) '$q': first row gateway, qword=\"gateway\"" + else no "(P/spelling) '$q': first row $first, want gateway / qword=gateway"; fi +done f="$TMP/py.p.xml"; run py "$f" "--for=$QPY" if ran_ok "$f" "(P/PY)"; then - H hops "$f" >"$TMP/py.p.json" + HJ "$f" "$TMP/py.p.json" "(P/PY)" got="$( J "[(r['n'], r.get('qword',''), r['l']) for r in d['rows'][:2]]" <"$TMP/py.p.json" )" want="[('_notify', 'notify', '11'), ('_notify', 'notify', '23')]" if [ "$got" = "$want" ]; then ok "(P/PY) two same-named owners (Reactive._notify l=11, Signal._notify l=23) are the first two rows"; else no "(P/PY) first two rows $got, want $want (show each same-named owner, never one, never merged)"; fi fi f="$TMP/c.p.xml"; run c "$f" "--for=$QC" if ran_ok "$f" "(P/C)"; then - H hops "$f" >"$TMP/c.p.json" + HJ "$f" "$TMP/c.p.json" "(P/C)" got="$( J "[(r['p'], r['l'], r.get('qword','')) for r in d['rows'] if r['n']=='scan']" <"$TMP/c.p.json" )" if [ "$got" = "[('table.c', '8', 'scan')]" ]; then ok "(P/C) scan: one owner row, the table.c definition (prototype not a second owner)"; else no "(P/C) scan rows $got, want [('table.c', '8', 'scan')]"; fi first="$( J "d['rows'][0]['n'] if d['rows'] else None" <"$TMP/c.p.json" )" if [ "$first" = scan ]; then ok "(P/C) scan is the first row"; else no "(P/C) first row is $first, want scan"; fi fi -# ── (F) false-claim hedges (FE-B capability detected on the fixture's own name-only call) ───────────────────────────── +# ── (F) false-claim hedges — FE-B is in the base now: a missing hedge is a FAIL, never a SKIP (C9) ──────────────────── f="$TMP/ts.cl.xml"; run ts "$f" --callees=src/middleware/gateway.ts:gateway if ran_ok "$f" "(F) capability probe"; then if grep -q 'n="remember"[^>]*via="name"' "$f"; then @@ -159,17 +197,31 @@ if ran_ok "$f" "(F) capability probe"; then if grep -q "('encode', '')\|('remember', '')" "$TMP/ts.f.txt"; then no "(F) an owner callee row asserts a name-only edge unhedged" else ok "(F) no unhedged name-only callee row under the owner"; fi else - skip "(F) expects FE-B: this binary's --callees rows carry no via=\"name\" on the fixture's untyped receiver call" + no "(F) FE-B hedge missing: --callees rows carry no via=\"name\" on the fixture's untyped receiver call (FE-B is in the base; this is a regression)" fi fi -# ── (Z) zero proven callees ────────────────────────────────────────────────────────────────────────────────────────── -f="$TMP/c.z.xml"; run c "$f" "--for=How does collect gather the process tables?" -if ran_ok "$f" "(Z)"; then - H hops "$f" >"$TMP/c.z.json" - got="$( J "(sum(1 for r in d['rows'] if r['n']=='collect'), d['hops'].get('noedge','0') if d['hops'] is not None else 'nohops')" <"$TMP/c.z.json" )" - case "$got" in "(0, '"[1-9]*) ok "(Z) collect (no resolvable callee) gets no row and is counted in noedge ($got)";; - *) no "(Z) collect rows/noedge = $got, want no row and noedge >= 1";; esac +# ── (Z) zero proven callees: no row, noedge exactly +1 (a C prototype must not count twice) ───────────────────────── +qz="How does collect gather the process tables?" +f="$TMP/c.z.xml"; run c "$f" "--for=$qz"; fo="$TMP/c.z.off.xml"; runoff c "$fo" "--for=$qz" +if ran_ok "$f" "(Z)" && ran_ok "$fo" "(Z) A/B"; then + HJ "$f" "$TMP/c.z.json" "(Z)"; HJ "$fo" "$TMP/c.z.off.json" "(Z) A/B" + rows="$( J "sum(1 for r in d['rows'] if r['n']=='collect')" <"$TMP/c.z.json" )" + ne="$( J "(d['hops'] or {}).get('noedge','0')" <"$TMP/c.z.json" )"; neo="$( J "(d['hops'] or {}).get('noedge','0')" <"$TMP/c.z.off.json" )" + if [[ "$ne" =~ ^[0-9]+$ ]] && [[ "$neo" =~ ^[0-9]+$ ]]; then + if [ "$rows" = 0 ] && [ "$ne" -eq $((neo+1)) ]; then ok "(Z) collect (no resolvable callee) gets no row; noedge $neo -> $ne (exactly +1: definition and prototype are one owner)" + else no "(Z) collect rows=$rows noedge head=$ne A/B=$neo, want rows=0 and noedge = A/B + 1"; fi + else no "(Z) noedge not numeric (head '$ne', A/B '$neo')"; fi +fi +# (Z2) an owner whose only out-edge is name-only (FE-B): the 26b hop-slot rule — no row, counted once, nothing else moves +qz2="How does relay forward a request?" +f="$TMP/ts.z2.xml"; run ts "$f" "--for=$qz2"; fo="$TMP/ts.z2.off.xml"; runoff ts "$fo" "--for=$qz2" +if ran_ok "$f" "(Z2)" && ran_ok "$fo" "(Z2) A/B"; then + HJ "$f" "$TMP/ts.z2.json" "(Z2)" + rows="$( J "sum(1 for r in d['rows'] if r['n']=='relay')" <"$TMP/ts.z2.json" )" + ne="$( J "(d['hops'] or {}).get('noedge','0')" <"$TMP/ts.z2.json" )" + if [ "$rows" = 0 ] && [[ "$ne" =~ ^[1-9][0-9]*$ ]] && cmp -s "$f" "$fo"; then ok "(Z2) relay (name-only edge only): no row, noedge=$ne, byte-identical to the A/B handle (counted once)" + else no "(Z2) relay rows=$rows noedge=$ne identical=$( cmp -s "$f" "$fo" && echo 1 || echo 0 ), want 0 / >=1 / 1"; fi fi # ── (N) near misses ────────────────────────────────────────────────────────────────────────────────────────────────── @@ -181,11 +233,23 @@ nq(){ # root question label — no qword= anywhere in the answer } nq c "How does the log walk the process tables?" substring-log nq ts "How is a session token encoded with encode?" test-file-owner +qtf="How is a session token encoded with encode?" +f="$TMP/n.ts.tf.on.xml"; run ts "$f" "--for=$qtf"; fo="$TMP/n.ts.tf.off.xml"; runoff ts "$fo" "--for=$qtf" +if ran_ok "$f" "(N/test-file A/B)" && ran_ok "$fo" "(N/test-file A/B) knob"; then + if cmp -s "$f" "$fo"; then ok "(N/test-file) the test-file encode (with a proven callee) is no owner: byte-identical to the A/B handle (no row, no noedge count)" + else no "(N/test-file) '$qtf' differs from RIPWIRE_NO_OWNER_HOP=1 — a test-file definition became an owner"; fi +fi nq ts "How does the team review pull requests?" non-code-question f="$TMP/n.py.cls.xml"; run py "$f" "--for=How does Reactive notify a change?" if ran_ok "$f" "(N/class)"; then if grep -q ']*n="Reactive"[^>]*qword=' "$f"; then no "(N/class) the class Reactive became an owner row"; else ok "(N/class) a class is never an owner row"; fi fi +qcls="How does Reactive work?" +f="$TMP/n.py.cls2.xml"; run py "$f" "--for=$qcls"; fo="$TMP/n.py.cls2.off.xml"; runoff py "$fo" "--for=$qcls" +if ran_ok "$f" "(N/class-only)" && ran_ok "$fo" "(N/class-only) A/B"; then + if cmp -s "$f" "$fo"; then ok "(N/class-only) a question whose only code word is a class is byte-identical to the A/B handle (no row, no noedge count)" + else no "(N/class-only) '$qcls' differs from RIPWIRE_NO_OWNER_HOP=1 (a class became an owner, or a noedge count)"; fi +fi # ── (T) the head-tier gate is per WORD, never per definition ───────────────────────────────────────────────────────── f="$TMP/py.t1.xml"; run py "$f" "--for=How does notify propagate a reactive change and broadcast?" @@ -204,57 +268,87 @@ if ran_ok "$f" "(T/each)" && ran_ok "$f2" "(T/each) premise"; then import re,sys x=open(sys.argv[1]).read() print(sorted(int(r) for r,n in re.findall(r'"$TMP/py.t2.json" + HJ "$f" "$TMP/py.t2.json" "(T/each)" got="$( J "sorted(r['l'] for r in d['rows'] if r['n']=='_notify' and r.get('qword')=='notify')" <"$TMP/py.t2.json" )" - case "$ranks" in "["[0-9]*", 2"[5-9]"]"|"["[0-9]*", "[3-4][0-9]"]") + case "$ranks" in "["[0-9]*", 2"[5-9]"]"|"["[0-9]*", "[3-4][0-9]"]") if [ "$got" = "['11', '23']" ]; then ok "(T/each) both _notify definitions are owner rows although one ranks in the tail ($ranks)" else no "(T/each) _notify owner rows l=$got, want ['11', '23'] (ranks $ranks): a same-named owner was picked silently"; fi;; *) no "(T/each) premise: _notify ranks $ranks, want one <= 24 and one > 24 — re-tune";; esac fi -# ── (C) cap ────────────────────────────────────────────────────────────────────────────────────────────────────────── -f="$TMP/py.cap.xml"; run py "$f" "--for=How do invoke_watcher, schedule_refresh, record_signal and notify work?" +# ── (C) cap: which three, in which order, the exact next=, and a cut owner stays a plain row (C3) ─────────────────── +f="$TMP/py.cap.xml"; run py "$f" "--for=$QCAP" if ran_ok "$f" "(C)"; then - H hops "$f" >"$TMP/py.cap.json" - got="$( J "(sum(1 for r in d['rows'] if r.get('qword')), (d['hops'] or {}).get('qword_cut'), (d['hops'] or {}).get('next',''))" <"$TMP/py.cap.json" )" - case "$got" in "(3, '2', '--callees="*) ok "(C) 3 owner rows, qword_cut=\"2\", next= names the first cut owner ($got)";; - *) no "(C) owner rows / qword_cut / next = $got, want (3, '2', '--callees=FILE:NAME')";; esac + HJ "$f" "$TMP/py.cap.json" "(C)" + got="$( J "[(r['n'], r['l'], r.get('qword')) for r in d['rows'] if r.get('qword')]" <"$TMP/py.cap.json" )" + want="[('_notify', '23', 'notify'), ('_notify', '11', 'notify'), ('invoke_watcher', '29', 'invoke_watcher')]" + if [ "$got" = "$want" ]; then ok "(C) the 3 owner rows are Signal._notify, Reactive._notify, invoke_watcher (rank order)"; else no "(C) owner rows $got, want $want"; fi + got="$( J "((d['hops'] or {}).get('qword_cut'), (d['hops'] or {}).get('next',''))" <"$TMP/py.cap.json" )" + if [ "$got" = "('2', '--callees=pkg/reactive.py:schedule_refresh')" ]; then ok "(C) qword_cut=\"2\" next= names the first cut owner by rank (schedule_refresh)" + else no "(C) (qword_cut, next) = $got, want ('2', '--callees=pkg/reactive.py:schedule_refresh')"; fi + got="$( J "sorted((r['n'], r.get('qword','')) for r in d['rows'] if r['n'] in ('schedule_refresh','record_signal'))" <"$TMP/py.cap.json" )" + if [ "$got" = "[('record_signal', ''), ('schedule_refresh', '')]" ]; then ok "(C) the two cut owners (already hop candidates) stay plain rows" + else no "(C) cut owners' rows $got, want each once as a plain row (no qword)"; fi +fi + +# ── (V) common English verbs never starve the owner the question is about (C8) ─────────────────────────────────────── +f="$TMP/ts.v.xml"; run ts "$f" "--for=$QV"; f2="$TMP/ts.v.cand"; run ts "$f2" "--for=$QV" --format=candidates +if ran_ok "$f" "(V)" && ran_ok "$f2" "(V) premise"; then + rg="$( H rank "$f2" get )"; rt="$( H rank "$f2" then )"; rw_="$( H rank "$f2" write )"; rgw="$( H rank "$f2" gateway src/middleware )" + if [[ "$rg$rt$rw_$rgw" =~ ^[0-9]+$ ]] && [ "$rg" -lt "$rgw" ] && [ "$rt" -lt "$rgw" ] && [ "$rw_" -lt "$rgw" ]; then + # the verbs in rank order: the first two fill the slots after gateway, the third is the one cut + order="$( printf '%s get\n%s then\n%s write\n' "$rg" "$rt" "$rw_" | sort -n | awk '{print $2}' | tr '\n' ' ' )" + set -- $order; v1="$1"; v2="$2"; v3="$3" + HJ "$f" "$TMP/ts.v.json" "(V)" + got="$( J "[(r['n'], r.get('qword')) for r in d['rows'] if r.get('qword')]" <"$TMP/ts.v.json" )" + want="[('gateway', 'gateway'), ('$v1', '$v1'), ('$v2', '$v2')]" + if [ "$got" = "$want" ]; then ok "(V) get/then/write rank $rg/$rt/$rw_ above gateway ($rgw); gateway is still the first owner row, the verbs follow in rank order" + else no "(V) owner rows $got, want $want (a common verb spent the slot of the function the question is about)"; fi + got="$( J "((d['hops'] or {}).get('qword_cut'), (d['hops'] or {}).get('next',''))" <"$TMP/ts.v.json" )" + if [ "$got" = "('1', '--callees=src/util/io.ts:$v3')" ]; then ok "(V) qword_cut=\"1\" next= names the cut verb ($v3)"; else no "(V) (qword_cut, next) = $got, want ('1', '--callees=src/util/io.ts:$v3')"; fi + else no "(V) premise: get/then/write ranks $rg/$rt/$rw_ must all be above gateway ($rgw) — re-tune the fixture"; fi fi -# ── (G) all callee names up to the runaway guard ───────────────────────────────────────────────────────────────────── +# ── (G) all callee names up to the runaway guard; the guard's count pinned (C5) ───────────────────────────────────── f="$TMP/py.g1.xml"; run py "$f" "--for=How does dispatch route an event?" if ran_ok "$f" "(G/20)"; then - H hops "$f" >"$TMP/py.g1.json" + HJ "$f" "$TMP/py.g1.json" "(G/20)" got="$( J "[(len(r['calls']), r['ca'].get('capped','0')) for r in d['rows'] if r['n']=='dispatch']" <"$TMP/py.g1.json" )" if [ "$got" = "[(20, '0')]" ]; then ok "(G/20) dispatch's owner row lists all 20 callee names"; else no "(G/20) dispatch rows (names, capped) = $got, want [(20, '0')]"; fi fi f="$TMP/py.g2.xml"; run py "$f" "--for=How does broadcast send an event?" if ran_ok "$f" "(G/guard)"; then - H hops "$f" >"$TMP/py.g2.json" - got="$( J "[(r['ca'].get('total'), r['ca'].get('capped'), r['ca'].get('next', r.get('next',''))) for r in d['rows'] if r['n']=='broadcast']" <"$TMP/py.g2.json" )" - case "$got" in "[('105', '1', '--callees=pkg/broadcast.py:broadcast')]") ok "(G/guard) broadcast past the guard: capped=\"1\" + next= to the full list";; - *) no "(G/guard) broadcast (total, capped, next) = $got, want [('105', '1', '--callees=pkg/broadcast.py:broadcast')]";; esac + HJ "$f" "$TMP/py.g2.json" "(G/guard)" + got="$( J "[(r['ca'].get('total'), r['ca'].get('capped'), r['ca'].get('next', r.get('next','')), len(r['calls']), r['ca'].get('shown')) for r in d['rows'] if r['n']=='broadcast']" <"$TMP/py.g2.json" )" + case "$got" in "[('105', '1', '--callees=pkg/broadcast.py:broadcast', 100, '100')]") ok "(G/guard) broadcast past the guard: exactly 100 names, capped=\"1\" + next= to the full list";; + *) no "(G/guard) broadcast (total, capped, next, names, shown) = $got, want [('105', '1', '--callees=pkg/broadcast.py:broadcast', 100, '100')]";; esac fi # ── (R) no-regression against the A/B handle ───────────────────────────────────────────────────────────────────────── -for spec in "ts|$QTS" "py|$QPY" "c|$QC"; do +for spec in "ts|$QTS" "py|$QPY" "c|$QC" "py|$QCAP" "ts|$QV"; do r="${spec%%|*}"; q="${spec#*|}" - on="$TMP/r.$r.on.xml"; off="$TMP/r.$r.off.xml"; run "$r" "$on" "--for=$q"; runoff "$r" "$off" "--for=$q" - ran_ok "$on" "(R/$r) head" && ran_ok "$off" "(R/$r) A/B" || continue - H hops "$on" >"$on.json"; H hops "$off" >"$off.json" + tag="$r.$( printf '%s' "$q" | cksum | cut -d' ' -f1 )" + on="$TMP/r.$tag.on.xml"; off="$TMP/r.$tag.off.xml"; run "$r" "$on" "--for=$q"; runoff "$r" "$off" "--for=$q" + ran_ok "$on" "(R/$r '$q') head" && ran_ok "$off" "(R/$r '$q') A/B" || continue + HJ "$on" "$on.json" "(R/$r) head"; HJ "$off" "$off.json" "(R/$r) A/B" res="$( python3 - "$on.json" "$off.json" <<'PY' import json, sys on = json.load(open(sys.argv[1])); off = json.load(open(sys.argv[2])) owners = {(r['p'], r['n'], r['l']) for r in on['rows'] if r.get('qword')} base = [r for r in off['rows'] if (r['p'], r['n'], r['l']) not in owners] head = [r for r in on['rows'] if not r.get('qword')] -key = lambda r: (r['p'], r['n'], r['l'], tuple((c['n'], c.get('l'), c.get('via')) for c in r['calls'])) -missing = [k for k in map(key, base) if k not in set(map(key, head))] -order_ok = [key(r) for r in head if key(r) in set(map(key, base))] == [key(r) for r in base if key(r) in set(map(key, head))] -print('OK' if not missing and order_ok else 'BAD missing=%s order_ok=%s' % (missing[:3], order_ok)) +# identity, and the calls as a PREFIX: a moved owner only frees budget, so a ranked row the A/B handle cut may name MORE of +# the same walk (measured: on fzf-12 replacePlaceholder went from 0 to 2 names), never fewer and never others (rows are walked in order +# against one cumulative budget, so every row the A/B handle printed starts at most as deep into it) +ident = lambda r: (r['p'], r['n'], r['l']) +calls = lambda r: [(c['n'], c.get('l'), c.get('via')) for c in r['calls']] +hd = {ident(r): r for r in head} +missing = [ident(r) for r in base if ident(r) not in hd or calls(hd[ident(r)])[:len(calls(r))] != calls(r)] +order_ok = [ident(r) for r in head if ident(r) in {ident(b) for b in base}] == [ident(r) for r in base if ident(r) in hd] +print('OK' if not missing and order_ok and off['rows'] else 'BAD missing=%s order_ok=%s base_rows=%d' % (missing[:3], order_ok, len(off['rows']))) PY )" - if [ "$res" = OK ]; then ok "(R/$r) every A/B row is still served, same calls, same order"; else no "(R/$r) $res"; fi + if [ "$res" = OK ]; then ok "(R/$r) '$q': every A/B row is still served, same calls, same order"; else no "(R/$r) '$q': $res"; fi done for spec in "ts|How does the team review pull requests?" "c|How does the log walk the process tables?"; do r="${spec%%|*}"; q="${spec#*|}" @@ -263,22 +357,58 @@ for spec in "ts|How does the team review pull requests?" "c|How does the log wal if cmp -s "$on" "$off"; then ok "(R2/$r) a question with no owner is byte-identical to the A/B handle"; else no "(R2/$r) no-owner answer differs from RIPWIRE_NO_OWNER_HOP=1"; fi done -# ── (B) explicit budget: owner funded first; an exhausted ceiling adds nothing ─────────────────────────────────────── -f="$TMP/ts.b.xml"; run ts "$f" "--for=$QTS" --token-budget=4000 +# ── (B) explicit budget: owner funded first; an exhausted ceiling adds nothing; a cut is disclosed (C7) ────────────── +f="$TMP/ts.b.xml"; run ts "$f" "--for=$QTS" --token-budget=4500 +fo="$TMP/ts.b.off.xml"; runoff ts "$fo" "--for=$QTS" --token-budget=4500 +if ran_ok "$fo" "(B1) A/B" && ! grep -q 'reason="compact-route"' "$fo"; then no "(B1) premise: --token-budget=4500 no longer leaves the hops a budget on the A/B handle — re-tune"; fi if ran_ok "$f" "(B1)"; then - H hops "$f" >"$TMP/ts.b.json"; H root "$f" >"$TMP/ts.b.root" + HJ "$f" "$TMP/ts.b.json" "(B1)"; H root "$f" >"$TMP/ts.b.root" first="$( J "d['rows'][0]['n'] if d['rows'] else None" <"$TMP/ts.b.json" )" est="$( J "d.get('est_tokens','')" <"$TMP/ts.b.root" )"; over="$( J "d.get('over_ceiling','0')" <"$TMP/ts.b.root" )" - if [ "$first" = gateway ]; then ok "(B1) under --token-budget=4000 the owner row is first"; else no "(B1) first row under --token-budget=4000 is $first, want gateway"; fi + if [ "$first" = gateway ]; then ok "(B1) under --token-budget=4500 the owner row is first"; else no "(B1) first row under --token-budget=4500 is $first, want gateway"; fi if [[ "$est" =~ ^[0-9]+$ ]]; then - if { [ "$est" -le 4000 ] || [ "$over" = 1 ]; }; then ok "(B1) est_tokens=$est within 4000 (or over_ceiling disclosed)"; else no "(B1) est_tokens=$est > 4000 and no over_ceiling=\"1\""; fi + if { [ "$est" -le 4500 ] || [ "$over" = 1 ]; }; then ok "(B1) est_tokens=$est within 4500 (or over_ceiling disclosed)"; else no "(B1) est_tokens=$est > 4500 and no over_ceiling=\"1\""; fi else no "(B1) est_tokens missing or not numeric ('$est')"; fi fi +# (B1b) a ceiling that leaves the owner less than its row: the owner is still first, or its cut is disclosed by name +f="$TMP/ts.b4.xml"; run ts "$f" "--for=$QTS" --token-budget=4000 +if ran_ok "$f" "(B1b)"; then + HJ "$f" "$TMP/ts.b4.json" "(B1b)" + got="$( J "('row' if d['rows'] and d['rows'][0]['n']=='gateway' and d['rows'][0].get('qword')=='gateway' else 'cut' if (d['hops'] or {}).get('next')=='--callees=src/middleware/gateway.ts:gateway' and (d['hops'] or {}).get('qword_cut','0')!='0' else '')" <"$TMP/ts.b4.json" )" + if [ "$got" = row ] || [ "$got" = cut ] || grep -q 'reason="budget"' "$f"; then ok "(B1b) --token-budget=4000: the owner is first or disclosed ($got)" + else no "(B1b) --token-budget=4000: gateway neither the first owner row nor named by qword_cut/next= ($got)"; fi +fi on="$TMP/b2.on.xml"; off="$TMP/b2.off.xml"; run ts "$on" "--for=$QTS" --token-budget=1200; runoff ts "$off" "--for=$QTS" --token-budget=1200 if ran_ok "$on" "(B2)" && ran_ok "$off" "(B2) A/B"; then grep -q 'reason="budget"' "$off" || no "(B2) premise: --token-budget=1200 no longer exhausts the ceiling (no reason=\"budget\") — re-tune" if cmp -s "$on" "$off"; then ok "(B2) exhausted ceiling: byte-identical to the A/B handle (owner rows add nothing)"; else no "(B2) exhausted-ceiling answer changed"; fi fi +# (B3) a budget sweep on the 105-callee owner: no budget may drop callee names silently +qb3="How does broadcast send an event?"; partial=0; seen=0; bad="" +for tb in 1400 1600 1800 2000 2200 2400 2600 2800 3000 3500 4000 5000 6000; do + f="$TMP/b3.$tb.xml"; run py "$f" "--for=$qb3" --token-budget=$tb + ran_ok "$f" "(B3) budget $tb" || continue + HJ "$f" "$f.json" "(B3) budget $tb" + v="$( python3 - "$f" "$f.json" <<'PY' +import json, re, sys +x = open(sys.argv[1]).read(); d = json.load(open(sys.argv[2])) +nx = '--callees=pkg/broadcast.py:broadcast' +rows = [r for r in d['rows'] if r['n'] == 'broadcast'] +if rows: + r = rows[0]; ca = r['ca']; n = len(r['calls']) + ok = ca.get('capped') == '1' and ca.get('next') == nx and ca.get('shown') == str(n) and ca.get('total') == '105' + print(('row %d' % n) if ok else ('BAD row names=%d calls=%s' % (n, ca))) +else: + h = d['hops'] or {} + if h.get('qword_cut', '0') not in ('', '0') and h.get('next') == nx: print('cut') + elif 'reason="budget"' in x: print('spent') + else: print('BAD absent with no disclosure hops=%s' % h) +PY +)" + case "$v" in "row "*) seen=$((seen+1)); n="${v#row }"; [ "$n" -gt 0 ] && [ "$n" -lt 100 ] && partial=$((partial+1));; cut|spent) ;; *) bad="$bad [$tb: $v]";; esac +done +if [ -z "$bad" ] && [ "$partial" -ge 1 ]; then ok "(B3) every explicit budget discloses the owner row's cut (capped=\"1\" + next=) or its absence; $partial budget(s) fund it only partly" +else no "(B3) bad=$bad partial=$partial seen=$seen (want no silent drop, and >= 1 partly funded row)"; fi # ── (J) JSON and MCP twins: no hop surface, byte-identical ─────────────────────────────────────────────────────────── on="$TMP/j.on.json"; off="$TMP/j.off.json"; run ts "$on" "--for=$QTS" --json; runoff ts "$off" "--for=$QTS" --json @@ -286,8 +416,8 @@ if ran_ok "$on" "(J/json)" && ran_ok "$off" "(J/json) A/B"; then if cmp -s "$on" "$off"; then ok "(J/json) --json --for byte-identical to the A/B handle"; else no "(J/json) --json --for changed"; fi fi mcp(){ printf '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"for","arguments":{"path":"%s","task":"%s"}}}\n' "$FIX/ts" "$QTS" \ - | env $1 "$BIN" --mcp --no-cache >"$2" 2>"$2.err"; printf '%s' "$?" >"$2.rc"; } -mcp "RIPWIRE_X=0" "$TMP/m.on"; mcp "RIPWIRE_NO_OWNER_HOP=1" "$TMP/m.off" + | env $1 "$2" --mcp --no-cache >"$3" 2>"$3.err"; printf '%s' "$?" >"$3.rc"; } +mcp "RIPWIRE_X=0" "$BIN" "$TMP/m.on"; mcp "RIPWIRE_NO_OWNER_HOP=1" "$BIN" "$TMP/m.off" if ran_ok "$TMP/m.on" "(J/mcp)" && ran_ok "$TMP/m.off" "(J/mcp) A/B"; then grep -q '"result"' "$TMP/m.on" || no "(J/mcp) no result object — the arm measured nothing" if cmp -s "$TMP/m.on" "$TMP/m.off"; then ok "(J/mcp) MCP for byte-identical to the A/B handle (signatures-only surface)"; else no "(J/mcp) MCP for changed"; fi @@ -299,28 +429,59 @@ elif grep -q 'qword= [^;]*by name' "$TMP/ts.p.xml"; then ok "(L) qword legend cl else no "(L) qword row emitted without a legend clause that says it is by name"; fi if grep -q 'qword' "$TMP/n.ts.non-code-question.xml" 2>/dev/null; then no "(L) qword legend clause on an answer with no owner row"; else ok "(L) no qword clause when no owner row"; fi -# ── (I) optional base-vs-head byte identity outside --for hop selection ────────────────────────────────────────────── +# (D) the duplicate check really ran on every parsed block +if [ "$ndup" -ge 20 ]; then ok "(D) $ndup blocks parsed, none with an row twice (failures above if any)"; else no "(D) only $ndup blocks parsed — the duplicate arm measured too little"; fi + +# ── (I) base vs head: the knob restores the BASE answer, and everything outside --for hop selection is unchanged (C1) ─ if [ -n "${RIPWIRE_BASE_BIN:-}" ] && [ -x "${RIPWIRE_BASE_BIN}" ]; then - n=0; d=0 - while IFS='|' read -r r a; do - [ -z "$r" ] && continue - ( cd "$FIX/$r" && "$BIN" . --no-cache $a >"$TMP/i.h" 2>/dev/null; cd "$FIX/$r" && "$RIPWIRE_BASE_BIN" . --no-cache $a >"$TMP/i.b" 2>/dev/null ) - n=$((n+1)); cmp -s "$TMP/i.h" "$TMP/i.b" || { d=$((d+1)); echo " differs: $r $a"; } - done <<'ARGV' -ts|--callers=checkSessionToken -ts|--callees=src/middleware/gateway.ts:gateway -ts|--expand=gateway -ts|--top-k=20 -py|--callers=invoke_watcher -py|--callees=dispatch -py|--expand=pkg/reactive.py:_notify -py|--top-k=20 -c|--callers=Table_rowCount -c|--callees=scan -c|--expand=scan -c|--top-k=20 + echo " (I) RIPWIRE_BASE_BIN=$RIPWIRE_BASE_BIN" + n=0; d=0; e=0 + # MODE|ROOT|ARG|ARG… — K: head with RIPWIRE_NO_OWNER_HOP=1 vs base; H: head as shipped vs base; M: MCP `for` (K form) + while IFS= read -r line; do + [ -z "$line" ] && continue + IFS='|' read -r -a parts <<<"$line" + mode="${parts[0]}"; r="${parts[1]}"; args=( ${parts[@]+"${parts[@]:2}"} ); shown="${args[*]-}" + n=$((n+1)); hf="$TMP/i.$n.h"; bf="$TMP/i.$n.b" + if [ "$mode" = M ]; then + mcp "RIPWIRE_NO_OWNER_HOP=1" "$BIN" "$hf"; mcp "RIPWIRE_X=0" "$RIPWIRE_BASE_BIN" "$bf" + else + if [ "$mode" = K ]; then knob="RIPWIRE_NO_OWNER_HOP=1"; else knob="RIPWIRE_X=0"; fi + ( cd "$FIX/$r" && env "$knob" "$BIN" . --no-cache ${args[@]+"${args[@]}"} >"$hf" 2>"$hf.err" ); printf '%s' "$?" >"$hf.rc" + ( cd "$FIX/$r" && "$RIPWIRE_BASE_BIN" . --no-cache ${args[@]+"${args[@]}"} >"$bf" 2>"$bf.err" ); printf '%s' "$?" >"$bf.rc" + fi + if ! ran_ok "$hf" "(I) head $mode $r $shown" || ! ran_ok "$bf" "(I) base $mode $r $shown"; then e=$((e+1)); continue; fi + cmp -s "$hf" "$bf" || { d=$((d+1)); echo " differs: $mode $r $shown"; } + done < Date: Thu, 8 Oct 2026 13:29:19 -0400 Subject: [PATCH 053/170] for: the function a question names gets the first hop row, with all its callee names The compact --for answer hop-expanded only the six best-ranked rows inside a ~1 KB allowance. A question that names the function it is about often ranks that function below a dozen symbols repeating the question's other words, so its callees were never served (graded round-1 misses: a JWT middleware's cookie/exception callees, a validator's async helpers). An OWNER is a ranked callable definition (function or method with a body, not a test symbol) whose whole name, leading _ # $ @ removed and ASCII case folded, equals a word of the question, when that word has such a definition in the head tier (rank <= 24). verbs_for.h forOwnerHopPlan seats up to three owners with a proven callee edge, in rank order, common English words (get, then, write, ...) after the rest; packHops writes them first with qword=, lists their callees up to a runaway guard of 100 names (capped="1" + next= past it or under a budget cut) and discloses the owners it did not seat as . Every same-named definition is its own row; a moved candidate is never duplicated; an owner with no proven edge is counted in noedge= once. Default regime: owner rows ride on top of the hop budget, so every ranked row keeps the budget it had. Explicit --token-budget: owners are funded first. The qword legend clause is present only with a qword row or cut, and exempt from the sigs trim. A question with no owner, --json and the MCP for verb are byte-identical; RIPWIRE_NO_OWNER_HOP=1 restores the base answer (gate (I) against the base binary). Gate: test/ownerhopcheck.sh. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 23 ++++ src/cli.h | 3 + src/serialize.h | 132 ++++++++++++++++++++--- src/verbs_for.h | 277 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 412 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51641eb01..edadd8430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,29 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Changed — `--for`: the function a question names gets the first hop row, with all its callee names + +The compact `--for` answer hop-expanded only the six best-ranked rows, inside a ~1 KB allowance. A question that names +the function it is about ("How does the JWT middleware verify a token?") often ranks that function below a dozen symbols +that repeat the question's other words, so its callees — the names an agent needed next — were never served. + +- A ranked function or method DEFINITION (not a test symbol, not a C prototype) whose whole name, leading `_ # $ @` + removed and ASCII case folded, equals a word of the question is that question's owner, when the word has such a + definition in the top 24. It gets a `` row first in ``, past the six-row cap; every same-named + definition in the ranked pool is its own row. At most three owner rows; the rest are disclosed as + ``. A small list of common English words (get, then, write, …) ranks after the + other owner words, so verbs a question merely uses do not take the owner's slot. +- An owner row lists its callees up to 100 names (a runaway guard); past it, or under an explicit budget that cuts it, + `` names the full list. The hop-slot rule still applies: an owner with no proven callee + edge has no row and is counted in `noedge=` once. Name-only callees keep `via="name"`. +- By name, not by resolution: `qword=` says the name equals the word, never that the symbol is the only or the true + implementation. Default regime: owner rows ride on top of the hop allowance, so every earlier `` row is still + served (a moved owner can only free room). Under `--token-budget` they are funded first. A question that names no + owner, `--json --for` and the MCP `for` verb are byte-identical; `RIPWIRE_NO_OWNER_HOP=1` restores the previous + answer. Measured on the 32 `--for` rows of the round-1 comparison set: the two in-scope rows now name 5 of 5 gold + callee items they missed (were 0 of 5); 23 of 32 answers are byte-identical, 2 differ only in `noedge=`, and 7 grow by 0.2–1.1 KB. Gate: + `test/ownerhopcheck.sh`. + ### Changed — a call bound by name alone keeps its rows, marked `via="name"`; typed receivers resolve A member call `x.m()` (or, where the receiver is implicit, a bare `m()`) bound to every in-repo definition spelled diff --git a/src/cli.h b/src/cli.h index ab80fe8f3..f8a63f876 100644 --- a/src/cli.h +++ b/src/cli.h @@ -933,6 +933,9 @@ inline constexpr char kHelpHead[] = " ranked map plus a section — the same candidate head's ONE-HOP callee signatures,\n" " the block a body carries — and NO body CDATA, disclosed as bundle=\"compact\"\n" " bodies=\"0\" reason=\"compact-route\". Read the map, then --expand=SYM the one you want.\n" + " A ranked function or method whose whole name is a word of the query (by name, not\n" + " resolved) gets the first rows, past that head: up to 3, each listing its\n" + " callees to 100 names (capped=1 next= past it); more are counted as qword_cut= with next=.\n" " --auto-bodies restores the body walk there. That shape discloses on every run too: a\n" " ceiling the signatures alone exhaust carries bundle=\"compact\" bodies=\"0\"\n" " reason=\"budget\" — three distinct reasons, never collapsed (compact-route = the route\n" diff --git a/src/serialize.h b/src/serialize.h index 5a5f8227c..662af9ed0 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -6072,24 +6072,38 @@ struct CalleeCallsSink // FE-B: set once the block's caller has been charged the via="name" legend comment it will write (packBodies/packHops // write it ahead of the rows, after the walk) — so the budget the walk honours already holds those bytes bool* viaLegendCharged = nullptr; + // owner-hop (verbs_for.h THE QUESTION'S OWNER): an owner row lists its callees up to a RUNAWAY GUARD instead of the + // sixteen a ranked row shows, and a cut listing carries the pasteable full list. Defaulted to the ranked shape, so + // every pre-existing caller is byte-identical. `cappedNext` is already XML-escaped and written only when cut. + std::uint32_t rowCap = 16; + std::string_view cappedNext; }; // The wrapper, written in front of the rows it describes (they have to be walked before `shown` // is known). pageview.h THE TRUNCATION VOCABULARY rule 3: shown=/capped= are added ONLY when the 16-row // cap or the byte budget actually cut the list, and they are added TOGETHER — never shown="T" capped="0" // on a complete listing. -inline void appendCallsBlock( std::string& out, std::uint32_t total, int shown, const std::string& rows ) +// `nextEscaped` (owner rows only): the continuation that lists the whole set, written beside capped="1" and only then. +inline void appendCallsBlock( std::string& out, std::uint32_t total, int shown, const std::string& rows, std::string_view nextEscaped = {} ) { char callsHdr[ 64 ]; - if( static_cast( shown ) < total ) + const bool cut = static_cast( shown ) < total; + if( cut ) { - rw::formatTo( callsHdr, sizeof( callsHdr ), "", total, shown ); + rw::formatTo( callsHdr, sizeof( callsHdr ), "", total ); + rw::formatTo( callsHdr, sizeof( callsHdr ), " nameRows; // names-only rendering: collected, merged by name, written after the walk int shown = 0; - for( std::uint32_t k = outOff[id]; k < outOff[id + 1] && shown < 16 && used < budgetBytes; ++k ) + 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() ) @@ -6342,7 +6356,7 @@ inline void emitCalleeCallsBlock( std::string& out, NodeId id, const std::vector } } appendMergedCalleeNameRows( callsBody, nameRows, esc ); // no-op on the signature rendering (nameRows stays empty) - appendCallsBlock( out, total, shown, callsBody ); + appendCallsBlock( out, total, shown, callsBody, sink.cappedNext ); } // THE DISCLOSURE (§B8.3). This was the one budgeted section element carrying NO attributes at all: @@ -7093,6 +7107,33 @@ inline void packBodies( std::FILE* out, const IngestResult& ing, const std::vect w.flush(); } +// ── THE QUESTION'S OWNER ROWS (lane owner-hop-068; the selection is verbs_for.h forOwnerHopPlan) ────────────────── +// The first `rows` nodes handed to packHops are OWNER rows: a ranked callable definition whose whole name equals a word +// of the question. Each prints `qword=""`, lists its callees up to kForOwnerHopNameGuard (a runaway guard, the +// kMaxExpandSibs precedent — never a target) and, when that listing is cut, says where the whole of it is +// (``). Owners the plan could not seat (the row cap) or the budget could not fund are counted +// in ``, the continuation naming the first of them. +inline constexpr std::uint32_t kForOwnerHopNameGuard = 100; +struct HopOwners +{ + std::size_t rows = 0; // the first `rows` nodes are owner rows, in slot order + std::vector words; // the folded question word of each owner row (one per row) + std::size_t budgetBytes = 0; // 0: ride ON TOP of the hop budget (default regime, guard only) + bool fundFirst = false; // explicit ceiling: owners are funded first and the ranked rows get + // what they leave of budgetBytes (never more than their own budget) + std::size_t capCut = 0; // owners with a proven edge past the row cap (never seated) + NodeId firstCapCut = kNoNode; +}; + +// The continuation that lists one owner's callees: the canonical id when the symbol has a scope (two same-named methods +// of one file are two entities, and FILE:NAME would merge them), else FILE:NAME. Escaped for an attribute value. +inline std::string ownerCalleesNext( const IngestResult& ing, const Symbol& s, std::string_view pathRel, std::string_view rootArg, + std::vector& esc ) +{ + std::string target = s.scope.empty() ? std::string( pathRel ) + ":" + std::string( s.name ) : canonicalIdForEmit( ing, s, rootArg ); + return "--callees=" + std::string( escapeXml( target, esc ) ); +} + // ── THE SECTION: one-hop edge context without the body text ──────────────────────────────────── // (pre-registered: docs/EVALS.md, the T3 route-narrowing round; the allowance is // kForCompactHopBudgetBytes above, with its derivation.) @@ -7112,8 +7153,9 @@ inline void packBodies( std::FILE* out, const IngestResult& ing, const std::vect // group for, and walking `nodes` in the order the caller ranked them spends the budget on the // best-ranked symbols first. // -// A candidate with no resolved out-edges gets NO row and is counted in `noedge=` instead — see the loop -// for why that is the honest form rather than the cheap one. Call edges are name-based, so dynamic +// A candidate with no resolved out-edges gets NO row and is counted in `noedge=` instead — and so does an OWNER of the +// question (THE QUESTION'S OWNER ROWS above) with none, exactly once whether or not it was also a ranked candidate — see +// the loop for why that is the honest form rather than the cheap one. Call edges are name-based, so dynamic // dispatch, callbacks and macros can be missing: `noedge=` means none FOUND, never none exists // (CONTRIBUTING #3), and it is deliberately a separate number from `capped=`, which says only that the // byte budget stopped before the remaining candidates. @@ -7125,13 +7167,16 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector std::size_t* outShown = nullptr, // rows actually emitted; nullptr ⇒ not recorded const std::vector* rank = nullptr, // query relevance, for ordering a CUT callee listing std::string_view rootArg = {}, // R-E: same single-root-only root= every verb takes - bool viaLegendInHead = false ) // FE-B: packBodies' parameter of the same name + bool viaLegendInHead = false, // FE-B: packBodies' parameter of the same name + const HopOwners* owners = nullptr ) // owner-hop: the leading owner rows; nullptr ⇒ none (byte-identical) { bool viaLegendCharged = viaLegendInHead; // FE-B: the via="name" legend has been charged against the budget (CalleeCallsSink) if( budgetBytes == 0 ) // 0 ⇒ UNLIMITED, packBodies' own convention { budgetBytes = SIZE_MAX; } + const std::size_t ownerRows = owners != nullptr ? std::min( owners->rows, nodes.size() ) : 0; + EXPECTS( owners == nullptr || owners->words.size() == owners->rows, "one question word per owner row" ); XmlWriter w( out ); std::vector esc; @@ -7173,8 +7218,58 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector std::size_t requestedCount = 0; std::size_t shownCount = 0; std::size_t noEdgeCount = 0; - for( NodeId id : nodes ) + // owner rows: their own byte room (default regime: unlimited, the name guard is the only stop), their own via-legend + // charge (so the ranked rows below are charged exactly what they were charged without owners), counted cuts + std::size_t ownerUsed = 0; + std::size_t ownerBudget = ( owners != nullptr && owners->budgetBytes > 0 ) ? owners->budgetBytes : SIZE_MAX; + bool ownerVia = viaLegendInHead; + std::size_t ownerCut = owners != nullptr ? owners->capCut : 0; + NodeId firstOwnerCut = kNoNode; // the first owner the BUDGET cut (it ranks before every cap cut) + for( std::size_t i = 0; i < ownerRows; ++i ) + { + const NodeId id = nodes[ i ]; + ASSUME( id < ing.symbols.size() && hopSlotHasProvenEdge( outOff, outNameOnly, id ), + "the owner plan seats only indexed symbols with a proven callee edge (forOwnerHopPlan)" ); + const Symbol& s = ing.symbols[ id ]; + const std::string next = ownerCalleesNext( ing, s, pathRel( s.fileId ), rootArg, esc ); + std::string row = "words[ i ], esc ); + row += "\">"; + // the row's identity AND the next= its may carry are charged before a single name — a row that cannot + // pay for both is not started, it is counted as cut (an owner row never drops its names without saying where) + const std::size_t rowFixed = row.size() + next.size() + 8 + 16; // + ' next=""' + '' + if( ownerUsed + rowFixed >= ownerBudget ) + { + if( firstOwnerCut == kNoNode ) + { + firstOwnerCut = id; + } + ++ownerCut; + continue; + } + ownerUsed += rowFixed; + emitCalleeCallsBlock( row, id, outOff, outTargets, ing, contentOf, esc, ownerUsed, ownerBudget, + CalleeCallsSink{ redact, /*recorded=*/nullptr, /*namesOnly=*/true, rank, &outNameOnly, &ownerVia, + kForOwnerHopNameGuard, next } ); + row += ""; + children += row; + ++requestedCount; + ++shownCount; + } + if( owners != nullptr && owners->fundFirst ) + { + // explicit ceiling: the owners were funded first; the ranked rows get the rest, never more than their own budget + const std::size_t left = owners->budgetBytes > ownerUsed ? owners->budgetBytes - ownerUsed : 0; + budgetBytes = std::min( budgetBytes, left ); + viaLegendCharged = viaLegendCharged || ownerVia; + } + for( std::size_t i = ownerRows; i < nodes.size(); ++i ) { + const NodeId id = nodes[ i ]; if( id >= ing.symbols.size() ) { continue; // never a request this function could answer @@ -7230,19 +7325,30 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector char open[ 128 ]; if( noEdgeCount > 0 ) { - rw::formatTo( open, sizeof( open ), "", + rw::formatTo( open, sizeof( open ), "", + rw::formatTo( open, sizeof( open ), " 0 ) + { + // DISCLOSE the owners not served as an owner row (the row cap, or the budget): how many, and where the first is + const NodeId first = firstOwnerCut != kNoNode ? firstOwnerCut : owners->firstCapCut; + ASSUME( first < ing.symbols.size(), "a counted owner cut names an indexed symbol (forOwnerHopPlan / the walk above)" ); + openTag += " qword_cut=\"" + std::to_string( ownerCut ) + "\" next=\""; + openTag += ownerCalleesNext( ing, ing.symbols[ first ], pathRel( ing.symbols[ first ].fileId ), rootArg, esc ); + openTag += "\""; + } + openTag += ">"; if( !viaLegendInHead && children.find( " via=\"name\"" ) != std::string::npos ) { w.write( viaNameLegendComment() ); // FE-B: exactly when a hop's row carries via="name" } - w.write( open ); + w.write( openTag ); w.write( children ); w.write( "" ); w.flush(); diff --git a/src/verbs_for.h b/src/verbs_for.h index 9ef341888..eb19c2eaf 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -551,6 +551,8 @@ struct ForLensHeaderParts bool viaPresent = false; // FE-B: may a row of this bundle carry via="name"? Some head row // (the same row head modScopePresent reads) has a name-only out-edge // (serialize.h namesOnlyOutAny). Over-approximates the 16-per-symbol cap. + bool ownerPresent = false; // owner-hop: the compact carries a qword= row or a qword_cut= disclosure + // (forOwnerHopPlan, decided before the header, cleared with the section) // ── THE DROPPABLE LEGEND, as ONE bit ────────────────────────────────────────────────────────────── // confidenceNote / tailLegend / idRouteLegend moved in lock step at every read and every write, and the @@ -780,6 +782,12 @@ inline constexpr std::string_view kForCompactLegendConfidence = "; confidence=/margin_pct= head score drop (low=flat)"; inline constexpr std::string_view kForCompactLegendHops = "; h l= p= n=, c n= l= (joined for same-named callees, shown= counts them), noedge= no callee resolved"; +// owner-hop: present-only, right after the hops clause, on the same plan the rows come from. Says it is BY NAME (a lookup in +// the ranked head, never a resolution), widens noedge= to the owners, and defines the two disclosures an owner cut carries. +// What qword= does NOT mean: that the row is the only, or the true, implementation of the question's subject. +inline constexpr std::string_view kForCompactLegendOwner = + "; h qword= its name equals that question word (by name, not resolved, not the only one), first, calls to 100 (capped=1 next= all); " + "qword_cut= owners not so served, next= the first"; inline constexpr std::string_view kForCompactLegendBodies = "; b t= n= p= l= full bodies, c n= l= callee signatures"; inline constexpr std::string_view kForCompactLegendTail = @@ -884,6 +892,10 @@ inline void appendCompactForLegend( std::string& h, const ForLensHeaderParts& p, if( p.compactBundle ) { h += kForCompactLegendHops; + if( p.ownerPresent ) + { + h += kForCompactLegendOwner; // owner-hop: present-only + } } else if( p.autoBundle ) { @@ -966,6 +978,13 @@ inline constexpr std::string_view kForCompactBundleLegend = "child names its callees (c n= l=; l= comma-joins the lines of same-named callees, shown= counts callees). hops and calls disclose total=requested shown=printed capped=1 " "when the BUDGET cut a listing; noedge=N counts ranked symbols with no RESOLVED callee found (never " "none exists). For a body: expand=p:n pasted off a row; the auto-bodies flag puts the bodies back"; +// owner-hop: the full dialect's reading of the same two attributes (present-only, after the clause above; no "--": a comment) +inline constexpr std::string_view kForOwnerHopLegend = + "; hops row qword= the question word that row's NAME equals (by name in the ranked head, a lookup, not a resolution; it " + "does not mean the symbol is the only or the true implementation): such an owner row comes first and its calls child " + "lists up to 100 callee names (capped=1 with next= naming the full list when the guard or the budget cut it); an owner " + "with no resolved callee is counted in noedge= like any ranked symbol. hops qword_cut=N: owners not served as a qword " + "row (the three-row cap or the budget; one of the six ranked candidates still keeps its plain row), next= the first"; // One spelling of --for's header, three shapes of it. `withTaskEcho=false` replaces the comment's echo with a // note pointing at the task= attribute that still holds the verbatim copy — the duplicate goes, nothing else. @@ -1032,6 +1051,10 @@ inline std::string forLensHeaderText( const ForLensHeaderParts& p, bool withRout if( p.compactBundle ) { h.append( kForCompactBundleLegend ); // COMPACT: replaces the auto legend on the conceptual route — never both + if( p.ownerPresent ) + { + h.append( kForOwnerHopLegend ); // owner-hop: present-only + } } else if( p.autoBundle ) { @@ -2122,6 +2145,190 @@ inline std::vector forCompactHopIds( const std::vector& } return hopIds; } +// ── THE QUESTION'S OWNER (lane owner-hop-068) ───────────────────────────────────────────────────────────────────── +// The hop candidates above are the six best-RANKED rows, and a question that names the function it is about ("How does +// the JWT middleware verify a token?" -> jwt(), rank 12 behind a dozen symbols that repeat the question's other words) +// never got that function's callees — the graded gold items of two round-1 rows (getCookie, HTTPException, set; +// validateAsync*). An OWNER is a symbol of the ranked pool (the first kForLensDefaultTopN rows with a positive score) that +// * is a callable DEFINITION — a function or method with a body (a C prototype and its body are one entity: the +// body's row is the owner, the prototype is no second one) — and not a test symbol (rw::isTestSymbol); +// * has a name that, with leading _ # $ @ removed and ASCII case folded, EQUALS a whole word of the question (words +// split on every byte that is not a letter, digit, '_' or a non-ASCII byte; length >= 3; not a route stopword) — +// whole name only: "log" never makes catalog or dialog owners; `__init__` folds to init__ and matches no word; +// * names a word with at least one such definition inside the head tier (rank <= kForDocExcerptRankCount). The gate +// is per WORD: once a word qualifies, EVERY same-named definition in the pool is an owner, each its own row (two +// classes' _notify are two entities, never one picked silently). +// This is a NAME lookup over the ranked head, not a resolution: qword= says the name equals the word, never that the +// symbol is the only or the true implementation of the subject. The case fold matches the question's word only; it never +// merges two entities. Language-neutral by construction: a name, a kind, a body span and the test partition are what +// every indexed language already sets. +// SLOTS: owners with a proven callee edge (the hop-slot rule, rw::hopSlotHasProvenEdge) take up to kForOwnerHopRows rows +// in rank order — except that a word on kOwnerCommonWords (verbs and function words a question USES: get, then, write …) +// ranks after every other word, so "how does the gateway get and then write the token" seats gateway first (gate (V)). +// The rest are disclosed (qword_cut= + next=). An owner with no proven edge never takes a slot; it is counted in noedge= +// once (it is appended to the hop nodes only when it is not already one of the six). RIPWIRE_NO_OWNER_HOP=1 turns the +// whole plan off — the A/B handle, the RIPWIRE_NO_DOCS_AFTER_CODE precedent. +inline constexpr std::size_t kForOwnerHopRows = 3; +inline constexpr std::string_view kOwnerCommonWords[] = { + "all", "call", "each", "from", "get", "has", "into", "new", "put", "read", "run", "set", "that", "then", "this", + "use", "when", "with", "write" }; + +struct ForOwnerPlan +{ + std::vector rowIds; // the owner rows, slot order (<= kForOwnerHopRows) + std::vector rowWords; // the folded question word of each row + std::vector noEdgeIds; // owners with no proven edge that are NOT among the six candidates + std::size_t capCut = 0; // owners with a proven edge past the row cap + rw::NodeId firstCapCut = rw::kNoNode; + bool any() const noexcept { return !rowIds.empty() || capCut > 0; } // a qword row or a qword_cut= disclosure (the legend's bit) + bool changesNodes() const noexcept { return any() || !noEdgeIds.empty(); } // …or an edgeless owner to count in noedge= +}; + +// ASCII fold of one name or word, leading _ # $ @ removed (the owner match key) +inline std::string ownerFoldName( std::string_view n ) +{ + std::size_t i = 0; + while( i < n.size() && ( n[i] == '_' || n[i] == '#' || n[i] == '$' || n[i] == '@' ) ) + { + ++i; + } + std::string out( n.substr( i ) ); + for( char& c : out ) + { + c = ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : c; + } + return out; +} + +// the question's distinct owner words, sorted (binary-searched below — any question length, no per-word scan) +inline std::vector ownerQuestionWords( std::string_view task ) +{ + std::vector words; + const auto isWordByte = []( unsigned char c ) noexcept + { return ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c >= '0' && c <= '9' ) || c == '_' || c >= 0x80; }; + std::size_t i = 0; + while( i < task.size() ) + { + if( !isWordByte( static_cast( task[i] ) ) ) + { + ++i; + continue; + } + const std::size_t start = i; + while( i < task.size() && isWordByte( static_cast( task[i] ) ) ) + { + ++i; + } + std::string w = ownerFoldName( task.substr( start, i - start ) ); + if( w.size() >= 3 && !rw::isRouteStopword( w ) ) + { + words.push_back( std::move( w ) ); + } + } + std::sort( words.begin(), words.end() ); + words.erase( std::unique( words.begin(), words.end() ), words.end() ); + return words; +} + +inline bool isOwnerCommonWord( std::string_view w ) noexcept +{ + return std::ranges::find( kOwnerCommonWords, w ) != std::end( kOwnerCommonWords ); +} + +inline ForOwnerPlan forOwnerHopPlan( const rw::IngestResult& ing, const rw::Graph& g, std::string_view task, + const std::vector& lensSurfaceIds, const std::vector& lensRank, + const std::vector& hopIds ) +{ + ForOwnerPlan plan; + if( std::getenv( "RIPWIRE_NO_OWNER_HOP" ) != nullptr ) + { + return plan; + } + const std::vector words = ownerQuestionWords( task ); + if( words.empty() ) + { + return plan; + } + struct Owner + { + rw::NodeId id; + std::size_t rank; // 1-based position in the ranked surface + std::string word; + }; + std::vector found; + const std::size_t pool = std::min( lensSurfaceIds.size(), std::size_t( rw::kForLensDefaultTopN ) ); + for( std::size_t i = 0; i < pool; ++i ) + { + const rw::NodeId id = lensSurfaceIds[i]; + if( id >= ing.symbols.size() || lensRank[id] <= 0.0f ) + { + continue; + } + const rw::Symbol& s = ing.symbols[id]; + if( ( s.kind != rw::SymKind::Function && s.kind != rw::SymKind::Method ) || !rw::isDefinitionNotDeclaration( s ) || rw::isTestSymbol( ing, id ) ) + { + continue; + } + std::string key = ownerFoldName( s.name ); + if( std::binary_search( words.begin(), words.end(), key ) ) + { + found.push_back( Owner{ id, i + 1, std::move( key ) } ); + } + } + // the head-tier gate, per WORD: a word qualifies when one of its definitions ranks inside the head tier + std::vector headWords; + for( const Owner& o : found ) + { + if( o.rank <= rw::kForDocExcerptRankCount ) + { + headWords.push_back( o.word ); + } + } + std::sort( headWords.begin(), headWords.end() ); + std::erase_if( found, [ & ]( const Owner& o ) { return !std::binary_search( headWords.begin(), headWords.end(), o.word ); } ); + // rank order, common words after the rest (stable: rank order inside each group) + std::stable_partition( found.begin(), found.end(), []( const Owner& o ) { return !isOwnerCommonWord( o.word ); } ); + for( Owner& o : found ) + { + if( !rw::hopSlotHasProvenEdge( g.outOff, g.outNameOnly, o.id ) ) + { + if( std::ranges::find( hopIds, o.id ) == hopIds.end() ) + { + plan.noEdgeIds.push_back( o.id ); // counted in noedge= once; a candidate already is + } + continue; + } + if( plan.rowIds.size() < kForOwnerHopRows ) + { + plan.rowIds.push_back( o.id ); + plan.rowWords.push_back( std::move( o.word ) ); + continue; + } + if( plan.capCut++ == 0 ) + { + plan.firstCapCut = o.id; + } + } + ENSURES( plan.rowIds.size() == plan.rowWords.size() && plan.rowIds.size() <= kForOwnerHopRows, "one word per seated owner, at most the cap" ); + return plan; +} + +// The node list with the owner plan applied: owner rows first, then the six candidates minus the owners MOVED out +// of them (moved, never duplicated), then the edgeless owners that are not candidates (each counted in noedge= once). +inline std::vector forOwnerHopNodes( const ForOwnerPlan& plan, const std::vector& hopIds ) +{ + std::vector nodes = plan.rowIds; + for( rw::NodeId id : hopIds ) + { + if( std::ranges::find( plan.rowIds, id ) == plan.rowIds.end() ) + { + nodes.push_back( id ); + } + } + nodes.insert( nodes.end(), plan.noEdgeIds.begin(), plan.noEdgeIds.end() ); + return nodes; +} + // --detail=N's bodies: the top min(N, forTopN) symbols by (score desc, id asc) — the order the sigs use. One spelling, read // by the --detail section and by the header's via="name" question (FE-B). inline std::vector forDetailIds( const rw::IngestResult& ing, const std::vector& lensRank, int detail, int forTopN ) @@ -2140,7 +2347,7 @@ inline std::vector forDetailIds( const rw::IngestResult& ing, const ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::IngestResult& ing, const rw::Graph& g, const std::vector& lensSurfaceIds, const std::vector& lensRank, std::size_t committedBytes, std::size_t bundleBudget, rw::RedactCounts* redactPtr, - bool viaLegendInHead ) + bool viaLegendInHead, const ForOwnerPlan* ownerPlan ) { ForAutoBodiesResult out; // R-E (2026-08-17 harvest): same single-root condition every other verb's root= uses (sarif.h). @@ -2180,9 +2387,46 @@ ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::Ingest ? rw::kForCompactSurfaceBudgetBytes - kCompactFixedBytes : std::size_t( 1 ) ); + // THE QUESTION'S OWNER (forOwnerHopPlan): owner rows first, the candidates they were not, the edgeless owners last. + // Default regime: the owner rows ride ON TOP of hopBudget (completeness first — every ranked row keeps the budget it + // had, and the name guard is the only stop). Explicit ceiling: the owners are funded first from what the ceiling + // left, minus the widest qword_cut/next= disclosure the walk could write. + const bool withOwners = ownerPlan != nullptr && ownerPlan->changesNodes(); + const std::vector hopNodes = withOwners ? forOwnerHopNodes( *ownerPlan, hopIds ) : hopIds; + rw::HopOwners owners; + if( withOwners ) + { + owners.rows = ownerPlan->rowIds.size(); + owners.words.assign( ownerPlan->rowWords.begin(), ownerPlan->rowWords.end() ); + owners.capCut = ownerPlan->capCut; + owners.firstCapCut = ownerPlan->firstCapCut; + if( cfg.tokenBudget > 0 ) + { + std::size_t cutReserve = 0; // ' qword_cut="NNN" next="--callees=' + the longest target + '"' + std::vector esc; + const std::string rootPrefix = fcRootArg.empty() ? std::string() : rw::sarif::rootPrefixOf( fcRootArg ); + const auto widest = [ & ]( rw::NodeId id ) + { + const rw::Symbol& s = ing.symbols[ id ]; + const std::string_view rel = fcRootArg.empty() ? std::string_view( ing.files[ s.fileId ] ) + : rw::sarif::rootRelativeUri( ing.files[ s.fileId ], rootPrefix ); // packHops' pathRel + cutReserve = std::max( cutReserve, rw::ownerCalleesNext( ing, s, rel, fcRootArg, esc ).size() + 28 ); + }; + std::for_each( ownerPlan->rowIds.begin(), ownerPlan->rowIds.end(), widest ); + if( ownerPlan->firstCapCut != rw::kNoNode ) + { + widest( ownerPlan->firstCapCut ); + } + // the envelope is the ranked rows' own reserve (kCompactFixedBytes) and is not charged twice; only the cut + // disclosure the owner walk may add is held back, so it always fits + owners.budgetBytes = leftBytes > cutReserve ? leftBytes - cutReserve : 1; // 1, never 0 (0 reads as "ride on top") + owners.fundFirst = true; + } + } + out.section = rw::chargeSection( [ & ]( std::FILE* f ) - { rw::packHops( f, ing, hopIds, hopBudget, g.outOff, g.outTargets, g.outNameOnly, redactPtr, /*outShown=*/nullptr, &lensRank, fcRootArg, - viaLegendInHead ); }, // g.outNameOnly also feeds the hop-slot rule (serialize.h noProvenEdge) + { rw::packHops( f, ing, hopNodes, hopBudget, g.outOff, g.outTargets, g.outNameOnly, redactPtr, /*outShown=*/nullptr, &lensRank, fcRootArg, + viaLegendInHead, withOwners ? &owners : nullptr ); }, // 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 // and line numbers, which tokenize like the rest of the bundle. Charging structured markup at the @@ -2197,7 +2441,7 @@ ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::Ingest out.section = rw::ChargedSection{}; return out; } - out.attr = hopIds.empty() ? " bundle=\"compact\" bodies=\"0\" reason=\"no_candidates\"" + out.attr = hopNodes.empty() ? " bundle=\"compact\" bodies=\"0\" reason=\"no_candidates\"" : " bundle=\"compact\" bodies=\"0\" reason=\"compact-route\""; return out; } @@ -2213,11 +2457,12 @@ ForAutoBodiesResult buildForEnrichment( const rw::Config& cfg, const rw::IngestR const std::vector& lensSurfaceIds, const std::vector& lensRank, const ForEnrichmentPlan& plan, const std::vector& anchorDefs, rw::RedactCounts* redactPtr, std::size_t committedBytes, std::size_t bundleBudget, - bool viaLegendInHead ) // FE-B: the header defines via="name" (ForLensHeaderParts::viaPresent) + bool viaLegendInHead, // FE-B: the header defines via="name" (ForLensHeaderParts::viaPresent) + const ForOwnerPlan* ownerPlan ) // owner-hop: the plan the header's qword clause read { const std::size_t committed = committedBytes + plan.attrReserve; ForAutoBodiesResult out = plan.compact - ? buildForCompactHops( cfg, ing, g, lensSurfaceIds, lensRank, committed, bundleBudget, redactPtr, viaLegendInHead ) + ? buildForCompactHops( cfg, ing, g, lensSurfaceIds, lensRank, committed, bundleBudget, redactPtr, viaLegendInHead, ownerPlan ) : buildForAutoBodies( cfg, ing, g, lensSurfaceIds, lensRank, committed, bundleBudget, redactPtr, anchorDefs, viaLegendInHead ); if( plan.compact ) { @@ -2704,6 +2949,11 @@ std::optional runForLens( const MainDispatch& d ) // node set is known before the header: the compact route's hop ids, T3's auto-body candidates, or --detail's head // (autoBundleMode excludes --detail). Their name-only out-edges decide it — over-approximating only the byte ladder // and the 16-per-symbol cap inside those sections, never the rows the header itself lists. + // owner-hop: THE QUESTION'S OWNER plan (forOwnerHopPlan), decided here, before the header, because the header's + // qword clause and its via="name" question both read it; the compact route's renders exactly this plan. + const ForOwnerPlan ownerPlan = ( autoBundleMode && plan.compact ) + ? forOwnerHopPlan( ing, g, cfg.forTask, lensSurfaceIds, lensRank, forCompactHopIds( lensSurfaceIds, lensRank ) ) + : ForOwnerPlan{}; bool forViaPresent = false; if( !g.outNameOnly.empty() ) { @@ -2711,6 +2961,7 @@ std::optional runForLens( const MainDispatch& d ) if( autoBundleMode && plan.compact ) { viaIds = forCompactHopIds( lensSurfaceIds, lensRank ); + viaIds.insert( viaIds.end(), ownerPlan.rowIds.begin(), ownerPlan.rowIds.end() ); // owner rows carry too // only the hops packHops gives a slot can carry a row (the hop-slot rule drops a hop whose every callee // edge is name-only, so its via="name" rows never print) std::erase_if( viaIds, [ & ]( rw::NodeId id ) { return !rw::hopSlotHasProvenEdge( g.outOff, g.outNameOnly, id ); } ); @@ -2731,7 +2982,8 @@ std::optional runForLens( const MainDispatch& d ) cfg.anchor, plan.autoBodies, plan.compact, cfg.legend == "compact", /*tailLegend=*/true, /*idRouteLegend=*/true, /*legendDropped=*/false, flRootArg, /*hdrLegend=*/!forHdrRows.empty(), forScPresent, forComposePresent, - forLegoPresent, forLayerPresent, forModScopePresent, forEndLinePresent, forViaPresent }; + forLegoPresent, forLayerPresent, forModScopePresent, forEndLinePresent, forViaPresent, + ownerPlan.any() }; const auto buildForHeader = [ & ]( bool withRouteAttr, bool withTaskEcho, std::string_view extraNotes ) { return forLensHeaderText( headerParts, withRouteAttr, withTaskEcho, extraNotes ); }; std::string headerStr = buildForHeader( /*withRouteAttr=*/true, /*withTaskEcho=*/true, {} ); @@ -3002,8 +3254,11 @@ std::optional runForLens( const MainDispatch& d ) // admits are the ones it admitted without it (owner ruling: e= is exempt from the row budget). const std::size_t endLineLegendEmitted = !headerParts.endLinePresent ? 0u : ( compactLegendOn ? rw::kForCompactEndLineLegend.size() : rw::kForEndLineLegend.size() ); + // owner-hop: the qword clause is a disclosure on the same contract — exempt, so is byte-identical with and + // without the owner rows (the rows ride on top of the hop budget; their reading never costs a signature row). + const std::size_t ownerLegendEmitted = !headerParts.ownerPresent ? 0u : ( compactLegendOn ? kForCompactLegendOwner.size() : kForOwnerHopLegend.size() ); const std::size_t exemptBytes = adaptiveNote.size() + autoLegendBytes + confidenceExemptBytes + tailLegendEmitted + idRouteLegendEmitted - + endLineLegendEmitted; + + endLineLegendEmitted + ownerLegendEmitted; if( exemptBytes > headerStr.size() ) { DISCLOSE( "runForLens: header exemptions exceed the emitted header — the sig ledger would underflow; charging the header whole" ); @@ -3026,7 +3281,8 @@ std::optional runForLens( const MainDispatch& d ) const std::size_t fullExempt = adaptiveNote.size() + enrichmentLegendBytesEmitted( plan, false ) + confidenceEarlyAttrsBytes + confidenceEarlyNoteBytes + forAtAttrStr.size() + rw::kForFileTailLegend.size() + idRouteParts.bytes() - + ( headerParts.endLinePresent ? rw::kForEndLineLegend.size() : 0u ); + + ( headerParts.endLinePresent ? rw::kForEndLineLegend.size() : 0u ) + + ( headerParts.ownerPresent ? kForOwnerHopLegend.size() : 0u ); std::size_t fullCharged = fullExempt > fullHeaderBytes ? fullHeaderBytes : fullHeaderBytes - fullExempt; fullCharged -= std::min( fullCharged, rw::forZeroNoteBytes( fullHeader ) ); // lean-answers: exempt, as above chargedHeaderBytes = std::min( chargedHeaderBytes, fullCharged ); @@ -3375,11 +3631,12 @@ std::optional runForLens( const MainDispatch& d ) // reserved ahead of the body walk (the kAutoAttrReserve pattern) so // the disclosure always fits; the DEFAULT regime reserves nothing — // the tail rides on top there and the bodies stay byte-identical. - bundleBudget, headerParts.viaPresent ); + bundleBudget, headerParts.viaPresent, &ownerPlan ); if( enrich.surfaceOff || enrich.legendOff ) { headerParts.autoBundle = headerParts.compactBundle = false; headerParts.viaPresent = false; // FE-B: the section that could carry it is gone (autoBundleMode excludes --detail) + headerParts.ownerPresent = false; // owner-hop: the same section headerStr = buildForHeader( /*withRouteAttr=*/true, /*withTaskEcho=*/true, {} ); } } From 50fc894a1231e4f8ea0f7b1dbb2bd7c817f5989a Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 14:13:53 -0400 Subject: [PATCH 054/170] for: the owner-row ASSUME states the index invariant only (selfcheckcheck C: no call inside a promise) Co-Authored-By: Claude Opus 5.5 --- src/serialize.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index 662af9ed0..3b1a6fa22 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -7228,8 +7228,8 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector for( std::size_t i = 0; i < ownerRows; ++i ) { const NodeId id = nodes[ i ]; - ASSUME( id < ing.symbols.size() && hopSlotHasProvenEdge( outOff, outNameOnly, id ), - "the owner plan seats only indexed symbols with a proven callee edge (forOwnerHopPlan)" ); + // forOwnerHopPlan seats only indexed symbols, each with a proven callee edge (hopSlotHasProvenEdge, read there) + ASSUME( id < ing.symbols.size(), "the owner plan seats only indexed symbols (forOwnerHopPlan)" ); const Symbol& s = ing.symbols[ id ]; const std::string next = ownerCalleesNext( ing, s, pathRel( s.fileId ), rootArg, esc ); std::string row = " Date: Thu, 8 Oct 2026 14:13:53 -0400 Subject: [PATCH 055/170] docs: regenerate LIMITS.md and TUNING.md (the owner-hop row cap, moved src lines, and the phase-B head's own stale rows); README cap count 241 Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- docs/LIMITS.md | 20 +++++++++++++++----- docs/TUNING.md | 24 ++++++++++++------------ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 98db7b871..5a1c203c3 100644 --- a/README.md +++ b/README.md @@ -2838,7 +2838,7 @@ python3 test/pargates.py . ./build/ripwire -j 6 A new gate script must be added to `test/regression.sh` in the same change. The gate `test/manifestcheck.sh` enforces this rule. -Another gate derives the cap inventory. The tool has 237 compile-time caps and 7 ranking parameters. +Another gate derives the cap inventory. The tool has 241 compile-time caps and 7 ranking parameters. `docs/LIMITS.md` lists each cap, its value, and whether the file discloses a truncation when the cap fires, and `python3 docs/limits_build.py --check` proves that list against `src/`. `docs/TUNING.md` lists the measured cost of each cap. diff --git a/docs/LIMITS.md b/docs/LIMITS.md index e10b7b3e7..bbddff55b 100644 --- a/docs/LIMITS.md +++ b/docs/LIMITS.md @@ -17,10 +17,10 @@ it once, marked `×N`. | total caps | files | caps whose file discloses | caps whose file discloses NOTHING | | --- | --- | --- | --- | -| 237 | 94 | 135 | **102** | +| 241 | 96 | 140 | **101** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 237 + 7 is the 244 constants this generator parses out of `src/`. +are not counted as caps, and 241 + 7 is the 248 constants this generator parses out of `src/`. ## INDEXING, OUTPUT or BOUNDARY — which half of the answer a cap bounds @@ -38,8 +38,8 @@ None of them truncates anything, so none can be judged by `shown=`/`total=` and a disclosure — labelling them OUTPUT would ask for a `capped="1"` that could never honestly fire. The distinction was named in review on #108 and the rows below now carry it. -The `class` column below carries that answer where it is known. **130 of 237 caps are classified -(45 INDEXING, 41 OUTPUT, 44 BOUNDARY); the remaining 107 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **132 of 241 caps are classified +(47 INDEXING, 41 OUTPUT, 44 BOUNDARY); the remaining 109 render `—`, which means NOT YET CLASSIFIED — never "neither".** Classifications live in `docs/limits_classes.tsv`, a sidecar with a known expiry: the tag belongs on the declaration itself, and this file exists only because the round that @@ -89,7 +89,7 @@ refuse to write, so the column cannot be satisfied by pointing at nothing. ## Caps, by file -One table for each of the 94 files that declare a cap — the 237 caps counted above, and no parameter. +One table for each of the 96 files that declare a cap — the 241 caps counted above, and no parameter. ### `src/abicheck.h` @@ -726,6 +726,14 @@ Discloses: **none** | --- | --- | --- | --- | | `kMaxPartitions` | `16` | BOUNDARY | — | +### `src/pathgaps.h` + +Discloses: `gap_syms_capped` + +| constant | value | class | note | +| --- | --- | --- | --- | +| `kPathGapRows` | `3` | — | rows printed; gap_syms= counts them all, gap_syms_capped="1" says so | + ### `src/pattern.h` Discloses: `hits_capped` @@ -903,6 +911,7 @@ Discloses: `capped` | constant | value | class | note | | --- | --- | --- | --- | +| `kSkillExcerptMaxBytes` | `120` | — | — | | `kSkillScanFindingCap` | `200` | INDEXING | generous for one file or a small dir; caps a pathological --scan-skills sweep | ### `src/slice.h` @@ -975,6 +984,7 @@ Discloses: `capped`, `coboost_commits_capped`, `terms_capped` | constant | value | class | note | | --- | --- | --- | --- | +| `kForOwnerHopRows` | `3` | — | — | | `kJsonEnvelopeDigitsMax` | `10` | — | — | ### `src/verbs_lint.h` diff --git a/docs/TUNING.md b/docs/TUNING.md index 8a09f448f..eb059740d 100644 --- a/docs/TUNING.md +++ b/docs/TUNING.md @@ -14,26 +14,26 @@ to production at defaults; that control is what makes these numbers mean anythin | cap declarations | distinct names | tunable | must stay `constexpr` | move >= 1 invocation | move nothing measurable | | --- | --- | --- | --- | --- | --- | -| 148 | 147 | 112 | 12 | **37** | 75 | +| 151 | 150 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **148 cap -declarations** under **147 distinct names** (`kRowCap` declared in more than one file). The sweep +The first two columns are not the same number, and the gap is not a rounding: `src/` holds **151 cap +declarations** under **150 distinct names** (`kRowCap` declared in more than one file). The sweep patches by NAME, so its own population is NAMES and not declarations — and the two columns beside those are frozen at the commit named under Provenance while this census is re-read from `src/` on -every run, which makes them a third population again. Enumerated over the 147 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **24 declared since the sweep was +every run, which makes them a third population again. Enumerated over the 150 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **27 declared since the sweep was prepared, which no measurement has touched** (`kChurnMergeBombMaxFiles`, -`kExtendedLengthThresholdUnits`, `kFieldIdCapacity`, `kForPageRowsDefault`, -`kForPageUnionSymbolCap`, `kMaxAstQueryNesting`, +18 more). 111 + 12 + 24 = 147, and `emit` refuses -to render a partition that does not add up. The `tunable` column above reads 112 rather than 111 -because one name the sweep classified is no longer declared in `src/` at all: `kSituTestRowsShown`. -Quoting "113 of 148" would be wrong in both halves at once, which is the shape of error a generated -table exists to prevent. +`kExtendedLengthThresholdUnits`, `kFieldIdCapacity`, `kForOwnerHopRows`, `kForPageRowsDefault`, +`kForPageUnionSymbolCap`, +21 more). 111 + 12 + 27 = 150, and `emit` refuses to render a partition +that does not add up. The `tunable` column above reads 112 rather than 111 because one name the +sweep classified is no longer declared in `src/` at all: `kSituTestRowsShown`. Quoting "113 of 151" +would be wrong in both halves at once, which is the shape of error a generated table exists to +prevent. ## Read this ratio before the tables **37 of 112 tunable caps move any invocation at all. 75 move nothing measurable.** That is the -finding, and it says what NOT to do: this is not a 148-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 151-cap audit. Most of these constants are inert on real invocations and should be left alone. The work worth doing is the small set below, plus the caps that fire SILENTLY — a cap that bites without disclosing is a defect independent of whether its value is right, and that fix is both cheaper and larger than any retuning. From 69ddbf9bcc4a48c5a9c46fad1080dfa7ff133f5b Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 14:29:37 -0400 Subject: [PATCH 056/170] refactor(for): the owner walk, the open tag, the owner budget and the owner legend read as their own helpers packHops, buildForCompactHops, forLensHeaderText and appendCompactForLegend were made materially worse by the owner rows (quality-delta vs the phase-B head: complexity 31->50, 9->29, 14->16, verbosity 107->164, 57->61). packOwnerHopRows, hopsOpenTag, forHopOwners and ownerHopLegend carry that logic; isOwnerCommonWord binary-searches a sorted table (static_assert). Output byte-identical (ownerhopcheck 52/52, (I) vs base). Co-Authored-By: Claude Opus 5.5 --- src/serialize.h | 194 +++++++++++++++++++++++++++++------------------- src/verbs_for.h | 121 ++++++++++++++++-------------- 2 files changed, 181 insertions(+), 134 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index 3b1a6fa22..8995a32d5 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -7134,6 +7134,110 @@ inline std::string ownerCalleesNext( const IngestResult& ing, const Symbol& s, s return "--callees=" + std::string( escapeXml( target, esc ) ); } +// What the owner walk hands back to packHops: the rows, how many nodes they consumed, and the budget and via-legend charge +// the RANKED rows then run on, plus the cut disclosure (`cutNext` already escaped; empty when nothing was cut). +struct OwnerHopRows +{ + std::string xml; + std::size_t rows = 0; // leading nodes that were owner rows (served or budget-cut) + std::size_t shown = 0; + std::size_t rankedBudget = SIZE_MAX; // the ranked rows' budget: unchanged, or (explicit ceiling) what the owners left + bool rankedVia = false; // the ranked rows' via-legend charge state + std::size_t cut = 0; // qword_cut=: cap cuts + budget cuts + std::string cutNext; +}; + +// The owner walk (packHops' first rows). Default regime: their own byte room, unlimited, the name guard the only stop, and +// their own via-legend charge — so the ranked rows are charged exactly what they were charged without owners. Explicit +// ceiling (fundFirst): funded first; the ranked rows get the rest. A row that cannot pay for its identity and its next= is +// not started: it is counted as cut, so an owner row never drops its names without saying where the rest are. +template +inline OwnerHopRows packOwnerHopRows( const IngestResult& ing, const std::vector& nodes, const HopOwners* owners, + const std::vector& outOff, const std::vector& outTargets, + const std::vector& outNameOnly, RedactCounts* redact, const std::vector* rank, + std::string_view rootArg, std::size_t rankedBudget, bool rankedVia, + PathRelFn&& pathRel, ContentOfFn&& contentOf, std::vector& esc ) +{ + OwnerHopRows out; + out.rankedBudget = rankedBudget; + out.rankedVia = rankedVia; + if( owners == nullptr ) + { + return out; + } + EXPECTS( owners->words.size() == owners->rows, "one question word per owner row" ); + out.rows = std::min( owners->rows, nodes.size() ); + out.cut = owners->capCut; + std::size_t used = 0; + const std::size_t budget = owners->budgetBytes > 0 ? owners->budgetBytes : SIZE_MAX; + bool via = rankedVia; + NodeId firstCut = kNoNode; // the first owner the BUDGET cut (it ranks before every cap cut) + for( std::size_t i = 0; i < out.rows; ++i ) + { + const NodeId id = nodes[ i ]; + // forOwnerHopPlan seats only indexed symbols, each with a proven callee edge (hopSlotHasProvenEdge, read there) + ASSUME( id < ing.symbols.size(), "the owner plan seats only indexed symbols (forOwnerHopPlan)" ); + const Symbol& s = ing.symbols[ id ]; + const std::string next = ownerCalleesNext( ing, s, pathRel( s.fileId ), rootArg, esc ); + std::string row = "words[ i ], esc ); + row += "\">"; + const std::size_t rowFixed = row.size() + next.size() + 8 + 16; // + ' next=""' + '' + if( used + rowFixed >= budget ) + { + firstCut = firstCut == kNoNode ? id : firstCut; + ++out.cut; + continue; + } + used += rowFixed; + emitCalleeCallsBlock( row, id, outOff, outTargets, ing, contentOf, esc, used, budget, + CalleeCallsSink{ redact, /*recorded=*/nullptr, /*namesOnly=*/true, rank, &outNameOnly, &via, + kForOwnerHopNameGuard, next } ); + out.xml += row; + out.xml += ""; + ++out.shown; + } + if( owners->fundFirst ) + { + out.rankedBudget = std::min( rankedBudget, owners->budgetBytes > used ? owners->budgetBytes - used : 0 ); + out.rankedVia = rankedVia || via; + } + const NodeId first = firstCut != kNoNode ? firstCut : owners->firstCapCut; + if( out.cut > 0 ) + { + ASSUME( first < ing.symbols.size(), "a counted owner cut names an indexed symbol (forOwnerHopPlan / the walk above)" ); + out.cutNext = ownerCalleesNext( ing, ing.symbols[ first ], pathRel( ing.symbols[ first ].fileId ), rootArg, esc ); + } + return out; +} + +// The open tag: the truncation triple, noedge= when non-zero, and the owner cut disclosure when there is one. +inline std::string hopsOpenTag( std::size_t shown, std::size_t requested, std::size_t noEdge, const OwnerHopRows& own ) +{ + char open[ 128 ]; + if( noEdge > 0 ) + { + rw::formatTo( open, sizeof( open ), " 0 ) + { + tag += " qword_cut=\"" + std::to_string( own.cut ) + "\" next=\"" + own.cutNext + "\""; // DISCLOSE the owners not served + } + tag += ">"; + return tag; +} + // ── THE SECTION: one-hop edge context without the body text ──────────────────────────────────── // (pre-registered: docs/EVALS.md, the T3 route-narrowing round; the allowance is // kForCompactHopBudgetBytes above, with its derivation.) @@ -7175,8 +7279,6 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector { budgetBytes = SIZE_MAX; } - const std::size_t ownerRows = owners != nullptr ? std::min( owners->rows, nodes.size() ) : 0; - EXPECTS( owners == nullptr || owners->words.size() == owners->rows, "one question word per owner row" ); XmlWriter w( out ); std::vector esc; @@ -7214,62 +7316,19 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector return contents.emplace( fid, std::move( s ) ).first->second; }; - std::string children; - std::size_t requestedCount = 0; - std::size_t shownCount = 0; + // 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, outNameOnly, redact, rank, rootArg, + budgetBytes, viaLegendCharged, pathRel, contentOf, esc ); + std::string children = own.xml; + std::size_t requestedCount = own.shown; + std::size_t shownCount = own.shown; std::size_t noEdgeCount = 0; - // owner rows: their own byte room (default regime: unlimited, the name guard is the only stop), their own via-legend - // charge (so the ranked rows below are charged exactly what they were charged without owners), counted cuts - std::size_t ownerUsed = 0; - std::size_t ownerBudget = ( owners != nullptr && owners->budgetBytes > 0 ) ? owners->budgetBytes : SIZE_MAX; - bool ownerVia = viaLegendInHead; - std::size_t ownerCut = owners != nullptr ? owners->capCut : 0; - NodeId firstOwnerCut = kNoNode; // the first owner the BUDGET cut (it ranks before every cap cut) - for( std::size_t i = 0; i < ownerRows; ++i ) - { - const NodeId id = nodes[ i ]; - // forOwnerHopPlan seats only indexed symbols, each with a proven callee edge (hopSlotHasProvenEdge, read there) - ASSUME( id < ing.symbols.size(), "the owner plan seats only indexed symbols (forOwnerHopPlan)" ); - const Symbol& s = ing.symbols[ id ]; - const std::string next = ownerCalleesNext( ing, s, pathRel( s.fileId ), rootArg, esc ); - std::string row = "words[ i ], esc ); - row += "\">"; - // the row's identity AND the next= its may carry are charged before a single name — a row that cannot - // pay for both is not started, it is counted as cut (an owner row never drops its names without saying where) - const std::size_t rowFixed = row.size() + next.size() + 8 + 16; // + ' next=""' + '' - if( ownerUsed + rowFixed >= ownerBudget ) - { - if( firstOwnerCut == kNoNode ) - { - firstOwnerCut = id; - } - ++ownerCut; - continue; - } - ownerUsed += rowFixed; - emitCalleeCallsBlock( row, id, outOff, outTargets, ing, contentOf, esc, ownerUsed, ownerBudget, - CalleeCallsSink{ redact, /*recorded=*/nullptr, /*namesOnly=*/true, rank, &outNameOnly, &ownerVia, - kForOwnerHopNameGuard, next } ); - row += ""; - children += row; - ++requestedCount; - ++shownCount; - } - if( owners != nullptr && owners->fundFirst ) - { - // explicit ceiling: the owners were funded first; the ranked rows get the rest, never more than their own budget - const std::size_t left = owners->budgetBytes > ownerUsed ? owners->budgetBytes - ownerUsed : 0; - budgetBytes = std::min( budgetBytes, left ); - viaLegendCharged = viaLegendCharged || ownerVia; - } - for( std::size_t i = ownerRows; i < nodes.size(); ++i ) + budgetBytes = own.rankedBudget; + viaLegendCharged = own.rankedVia; + for( auto it = nodes.begin() + std::ptrdiff_t( own.rows ); it != nodes.end(); ++it ) { - const NodeId id = nodes[ i ]; + const NodeId id = *it; if( id >= ing.symbols.size() ) { continue; // never a request this function could answer @@ -7322,28 +7381,7 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector // capped= is the BUDGET bit only: the candidates neither printed nor accounted by noedge=. Emitted // as the 0|1 boolean the vocabulary requires either way; noedge= follows the house silence rule and // appears only when it is non-zero. - char open[ 128 ]; - if( noEdgeCount > 0 ) - { - rw::formatTo( open, sizeof( open ), " 0 ) - { - // DISCLOSE the owners not served as an owner row (the row cap, or the budget): how many, and where the first is - const NodeId first = firstOwnerCut != kNoNode ? firstOwnerCut : owners->firstCapCut; - ASSUME( first < ing.symbols.size(), "a counted owner cut names an indexed symbol (forOwnerHopPlan / the walk above)" ); - openTag += " qword_cut=\"" + std::to_string( ownerCut ) + "\" next=\""; - openTag += ownerCalleesNext( ing, ing.symbols[ first ], pathRel( ing.symbols[ first ].fileId ), rootArg, esc ); - openTag += "\""; - } - openTag += ">"; + const std::string openTag = hopsOpenTag( shownCount, requestedCount, noEdgeCount, own ); if( !viaLegendInHead && children.find( " via=\"name\"" ) != std::string::npos ) { w.write( viaNameLegendComment() ); // FE-B: exactly when a hop's row carries via="name" diff --git a/src/verbs_for.h b/src/verbs_for.h index eb19c2eaf..a4b505814 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -788,6 +788,23 @@ inline constexpr std::string_view kForCompactLegendHops = inline constexpr std::string_view kForCompactLegendOwner = "; h qword= its name equals that question word (by name, not resolved, not the only one), first, calls to 100 (capped=1 next= all); " "qword_cut= owners not so served, next= the first"; +// owner-hop: the full dialect's reading of the same two attributes (present-only, after the clause above; no "--": a comment) +inline constexpr std::string_view kForOwnerHopLegend = + "; hops row qword= the question word that row's NAME equals (by name in the ranked head, a lookup, not a resolution; it " + "does not mean the symbol is the only or the true implementation): such an owner row comes first and its calls child " + "lists up to 100 callee names (capped=1 with next= naming the full list when the guard or the budget cut it); an owner " + "with no resolved callee is counted in noedge= like any ranked symbol. hops qword_cut=N: owners not served as a qword " + "row (the three-row cap or the budget; one of the six ranked candidates still keeps its plain row), next= the first"; +// …and the one question both dialects ask: the clause in this dialect, or nothing (ownerPresent is set only on the compact +// route and cleared with the section, so it never rides without the hops clause in front of it). +inline std::string_view ownerHopLegend( const ForLensHeaderParts& p, bool compact ) noexcept +{ + if( !p.ownerPresent ) + { + return {}; + } + return compact ? kForCompactLegendOwner : kForOwnerHopLegend; +} inline constexpr std::string_view kForCompactLegendBodies = "; b t= n= p= l= full bodies, c n= l= callee signatures"; inline constexpr std::string_view kForCompactLegendTail = @@ -892,15 +909,12 @@ inline void appendCompactForLegend( std::string& h, const ForLensHeaderParts& p, if( p.compactBundle ) { h += kForCompactLegendHops; - if( p.ownerPresent ) - { - h += kForCompactLegendOwner; // owner-hop: present-only - } } else if( p.autoBundle ) { h += kForCompactLegendBodies; } + h += ownerHopLegend( p, /*compact=*/true ); // owner-hop: present-only, right after the hops clause // The unconditionally-droppable-free tail of this dialect: five clauses that ride exactly when the bit beside // them is set, in THIS order, which is the contract a reader and every byte pin depend on. A table rather than // five identical ifs — the list has grown once per lane (hdr, compose, then train 9's lego and layer) and the @@ -978,13 +992,6 @@ inline constexpr std::string_view kForCompactBundleLegend = "child names its callees (c n= l=; l= comma-joins the lines of same-named callees, shown= counts callees). hops and calls disclose total=requested shown=printed capped=1 " "when the BUDGET cut a listing; noedge=N counts ranked symbols with no RESOLVED callee found (never " "none exists). For a body: expand=p:n pasted off a row; the auto-bodies flag puts the bodies back"; -// owner-hop: the full dialect's reading of the same two attributes (present-only, after the clause above; no "--": a comment) -inline constexpr std::string_view kForOwnerHopLegend = - "; hops row qword= the question word that row's NAME equals (by name in the ranked head, a lookup, not a resolution; it " - "does not mean the symbol is the only or the true implementation): such an owner row comes first and its calls child " - "lists up to 100 callee names (capped=1 with next= naming the full list when the guard or the budget cut it); an owner " - "with no resolved callee is counted in noedge= like any ranked symbol. hops qword_cut=N: owners not served as a qword " - "row (the three-row cap or the budget; one of the six ranked candidates still keeps its plain row), next= the first"; // One spelling of --for's header, three shapes of it. `withTaskEcho=false` replaces the comment's echo with a // note pointing at the task= attribute that still holds the verbatim copy — the duplicate goes, nothing else. @@ -1051,15 +1058,12 @@ inline std::string forLensHeaderText( const ForLensHeaderParts& p, bool withRout if( p.compactBundle ) { h.append( kForCompactBundleLegend ); // COMPACT: replaces the auto legend on the conceptual route — never both - if( p.ownerPresent ) - { - h.append( kForOwnerHopLegend ); // owner-hop: present-only - } } else if( p.autoBundle ) { h.append( kForAutoBundleLegend ); // T3: present whenever auto mode is on, whatever the fit outcome — it explains bodies="0" too } + h.append( ownerHopLegend( p, /*compact=*/false ) ); // owner-hop: present-only, right after the bundle clause if( p.tailLegend ) { h.append( rw::kForFileTailLegend ); // deep-tail: defines r= and the element (sigs-charge-exempt, serialize.h) @@ -2230,9 +2234,10 @@ inline std::vector ownerQuestionWords( std::string_view task ) return words; } +static_assert( std::ranges::is_sorted( kOwnerCommonWords ), "kOwnerCommonWords is binary-searched: keep it sorted" ); inline bool isOwnerCommonWord( std::string_view w ) noexcept { - return std::ranges::find( kOwnerCommonWords, w ) != std::end( kOwnerCommonWords ); + return std::binary_search( std::begin( kOwnerCommonWords ), std::end( kOwnerCommonWords ), w ); } inline ForOwnerPlan forOwnerHopPlan( const rw::IngestResult& ing, const rw::Graph& g, std::string_view task, @@ -2329,6 +2334,42 @@ inline std::vector forOwnerHopNodes( const ForOwnerPlan& plan, const return nodes; } +// packHops' owner description of a plan. Default regime: the owner rows ride ON TOP of the hop budget (completeness first — +// every ranked row keeps the budget it had, and the name guard is the only stop). Explicit ceiling: the owners are funded +// first from what the ceiling left, minus the widest qword_cut/next= disclosure the walk could write (the envelope is the +// ranked rows' own reserve, kCompactFixedBytes, and is not charged twice). +inline rw::HopOwners forHopOwners( const rw::Config& cfg, const rw::IngestResult& ing, const ForOwnerPlan& plan, + std::size_t leftBytes, std::string_view rootArg ) +{ + rw::HopOwners owners; + owners.rows = plan.rowIds.size(); + owners.words.assign( plan.rowWords.begin(), plan.rowWords.end() ); + owners.capCut = plan.capCut; + owners.firstCapCut = plan.firstCapCut; + if( cfg.tokenBudget == 0 || !plan.any() ) + { + return owners; + } + std::vector esc; + const std::string rootPrefix = rootArg.empty() ? std::string() : rw::sarif::rootPrefixOf( rootArg ); + std::size_t cutReserve = 0; // ' qword_cut="NNN" next="--callees=' + the longest target + '"' + std::vector cuttable = plan.rowIds; + if( plan.firstCapCut != rw::kNoNode ) + { + cuttable.push_back( plan.firstCapCut ); + } + for( rw::NodeId id : cuttable ) + { + const rw::Symbol& s = ing.symbols[ id ]; + const std::string_view rel = rootArg.empty() ? std::string_view( ing.files[ s.fileId ] ) + : rw::sarif::rootRelativeUri( ing.files[ s.fileId ], rootPrefix ); // packHops' pathRel + cutReserve = std::max( cutReserve, rw::ownerCalleesNext( ing, s, rel, rootArg, esc ).size() + 28 ); + } + owners.budgetBytes = leftBytes > cutReserve ? leftBytes - cutReserve : 1; // 1, never 0 (0 reads as "ride on top") + owners.fundFirst = true; + return owners; +} + // --detail=N's bodies: the top min(N, forTopN) symbols by (score desc, id asc) — the order the sigs use. One spelling, read // by the --detail section and by the header's via="name" question (FE-B). inline std::vector forDetailIds( const rw::IngestResult& ing, const std::vector& lensRank, int detail, int forTopN ) @@ -2347,7 +2388,7 @@ inline std::vector forDetailIds( const rw::IngestResult& ing, const ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::IngestResult& ing, const rw::Graph& g, const std::vector& lensSurfaceIds, const std::vector& lensRank, std::size_t committedBytes, std::size_t bundleBudget, rw::RedactCounts* redactPtr, - bool viaLegendInHead, const ForOwnerPlan* ownerPlan ) + bool viaLegendInHead, const ForOwnerPlan& ownerPlan ) { ForAutoBodiesResult out; // R-E (2026-08-17 harvest): same single-root condition every other verb's root= uses (sarif.h). @@ -2387,46 +2428,14 @@ ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::Ingest ? rw::kForCompactSurfaceBudgetBytes - kCompactFixedBytes : std::size_t( 1 ) ); - // THE QUESTION'S OWNER (forOwnerHopPlan): owner rows first, the candidates they were not, the edgeless owners last. - // Default regime: the owner rows ride ON TOP of hopBudget (completeness first — every ranked row keeps the budget it - // had, and the name guard is the only stop). Explicit ceiling: the owners are funded first from what the ceiling - // left, minus the widest qword_cut/next= disclosure the walk could write. - const bool withOwners = ownerPlan != nullptr && ownerPlan->changesNodes(); - const std::vector hopNodes = withOwners ? forOwnerHopNodes( *ownerPlan, hopIds ) : hopIds; - rw::HopOwners owners; - if( withOwners ) - { - owners.rows = ownerPlan->rowIds.size(); - owners.words.assign( ownerPlan->rowWords.begin(), ownerPlan->rowWords.end() ); - owners.capCut = ownerPlan->capCut; - owners.firstCapCut = ownerPlan->firstCapCut; - if( cfg.tokenBudget > 0 ) - { - std::size_t cutReserve = 0; // ' qword_cut="NNN" next="--callees=' + the longest target + '"' - std::vector esc; - const std::string rootPrefix = fcRootArg.empty() ? std::string() : rw::sarif::rootPrefixOf( fcRootArg ); - const auto widest = [ & ]( rw::NodeId id ) - { - const rw::Symbol& s = ing.symbols[ id ]; - const std::string_view rel = fcRootArg.empty() ? std::string_view( ing.files[ s.fileId ] ) - : rw::sarif::rootRelativeUri( ing.files[ s.fileId ], rootPrefix ); // packHops' pathRel - cutReserve = std::max( cutReserve, rw::ownerCalleesNext( ing, s, rel, fcRootArg, esc ).size() + 28 ); - }; - std::for_each( ownerPlan->rowIds.begin(), ownerPlan->rowIds.end(), widest ); - if( ownerPlan->firstCapCut != rw::kNoNode ) - { - widest( ownerPlan->firstCapCut ); - } - // the envelope is the ranked rows' own reserve (kCompactFixedBytes) and is not charged twice; only the cut - // disclosure the owner walk may add is held back, so it always fits - owners.budgetBytes = leftBytes > cutReserve ? leftBytes - cutReserve : 1; // 1, never 0 (0 reads as "ride on top") - owners.fundFirst = true; - } - } + // THE QUESTION'S OWNER (forOwnerHopPlan): owner rows first, the candidates they were not, the edgeless owners last; + // an empty plan leaves both exactly as they were (forOwnerHopNodes / forHopOwners). + const std::vector hopNodes = forOwnerHopNodes( ownerPlan, hopIds ); + 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, g.outNameOnly, redactPtr, /*outShown=*/nullptr, &lensRank, fcRootArg, - viaLegendInHead, withOwners ? &owners : nullptr ); }, // g.outNameOnly also feeds the hop-slot rule (serialize.h noProvenEdge) + viaLegendInHead, &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 // and line numbers, which tokenize like the rest of the bundle. Charging structured markup at the @@ -2458,7 +2467,7 @@ ForAutoBodiesResult buildForEnrichment( const rw::Config& cfg, const rw::IngestR const ForEnrichmentPlan& plan, const std::vector& anchorDefs, rw::RedactCounts* redactPtr, std::size_t committedBytes, std::size_t bundleBudget, bool viaLegendInHead, // FE-B: the header defines via="name" (ForLensHeaderParts::viaPresent) - const ForOwnerPlan* ownerPlan ) // owner-hop: the plan the header's qword clause read + const ForOwnerPlan& ownerPlan ) // owner-hop: the plan the header's qword clause read { const std::size_t committed = committedBytes + plan.attrReserve; ForAutoBodiesResult out = plan.compact @@ -3631,7 +3640,7 @@ std::optional runForLens( const MainDispatch& d ) // reserved ahead of the body walk (the kAutoAttrReserve pattern) so // the disclosure always fits; the DEFAULT regime reserves nothing — // the tail rides on top there and the bodies stay byte-identical. - bundleBudget, headerParts.viaPresent, &ownerPlan ); + bundleBudget, headerParts.viaPresent, ownerPlan ); if( enrich.surfaceOff || enrich.legendOff ) { headerParts.autoBundle = headerParts.compactBundle = false; From 8921f6f0ebd618c8a5b788f5526445dca21036b6 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 14:33:01 -0400 Subject: [PATCH 057/170] refactor(for): the common-word demotion reads the sorted table inline (no one-line lookup helper cloning others) Co-Authored-By: Claude Opus 5.5 --- src/verbs_for.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/verbs_for.h b/src/verbs_for.h index a4b505814..8b11857e0 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2235,10 +2235,6 @@ inline std::vector ownerQuestionWords( std::string_view task ) } static_assert( std::ranges::is_sorted( kOwnerCommonWords ), "kOwnerCommonWords is binary-searched: keep it sorted" ); -inline bool isOwnerCommonWord( std::string_view w ) noexcept -{ - return std::binary_search( std::begin( kOwnerCommonWords ), std::end( kOwnerCommonWords ), w ); -} inline ForOwnerPlan forOwnerHopPlan( const rw::IngestResult& ing, const rw::Graph& g, std::string_view task, const std::vector& lensSurfaceIds, const std::vector& lensRank, @@ -2292,7 +2288,8 @@ inline ForOwnerPlan forOwnerHopPlan( const rw::IngestResult& ing, const rw::Grap std::sort( headWords.begin(), headWords.end() ); std::erase_if( found, [ & ]( const Owner& o ) { return !std::binary_search( headWords.begin(), headWords.end(), o.word ); } ); // rank order, common words after the rest (stable: rank order inside each group) - std::stable_partition( found.begin(), found.end(), []( const Owner& o ) { return !isOwnerCommonWord( o.word ); } ); + std::stable_partition( found.begin(), found.end(), + []( const Owner& o ) { return !std::ranges::binary_search( kOwnerCommonWords, std::string_view( o.word ) ); } ); for( Owner& o : found ) { if( !rw::hopSlotHasProvenEdge( g.outOff, g.outNameOnly, o.id ) ) From 780cd12661e4ea980ddd69e9bbc2a07d0dcd6b9d Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 14:33:04 -0400 Subject: [PATCH 058/170] test(#358): ratio arms B37/B38 pin the one-descent ancestry read (JS and C-family) 2000 imports inside 2000 nested containers vs a control that keeps the nesting and drops the imports (JS) or keeps the lines at file scope (C). With the upward ts_node_parent walk restored these are 60-230x the control; at head under 3x. Cap 8x, user CPU, same shape as the gate's other isolation pairs. Co-Authored-By: Claude Sonnet 5.5 --- test/childwalkscalecheck.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/childwalkscalecheck.sh b/test/childwalkscalecheck.sh index 35233a02b..692225986 100755 --- a/test/childwalkscalecheck.sh +++ b/test/childwalkscalecheck.sh @@ -306,6 +306,21 @@ for n in ( 1000, 16000 ): # lists, so both sides execute nodesMatchExactly and only the flooded list differs (CodeRabbit, #130) w( "patmeta/n%d/big.c" % n, [ "int a( int x );", "int f( void )", "{", " return a(" ] + [ C ] * n + [ " 1 ) == a(" ] + [ C ] * n + [ " 1 );", "}" ] ) w( "patmeta_off/n%d/big.c" % n, [ "int a( int x );", "int f( void )", "{", " return a( 1 ) == a( 1 );" ] + [ C ] * n + [ C ] * n + [ "}" ] ) +# #358 import-capture reach: importContainerReach reads one directive's ancestry. These two fixtures are about +# DEPTH, not child-list width, so they sit outside the n1000/n16000 loop and the (C) corpus list on purpose. +# Each puts 2000 IMPORTS inside 2000 nested containers; the control keeps the nesting and swaps the imports +# for statements that are not imports, so both sides run the same tags pass over the same tree and only the +# import emission differs. A reach read that costs a descent PER HOP (an upward ts_node_parent walk) is +# 60-230x its control here; one descent from the root is under 3x. +D = 2000 +w( "reachjs/big.js", [ "{" * D ] + [ 'require( "./m" );' ] * D + [ "}" * D ] ) +w( "reachjs_off/big.js", [ "{" * D ] + [ "noop();" ] * D + [ "}" * D ] ) +w( "reachc/big.c", [ "#ifdef G%d" % i for i in range( D ) ] + [ '#include "m.h"' ] * D + [ "#endif" ] * D ) +w( "reachc_off/big.c", [ '#include "m.h"' ] * D ) +for d in ( "reachjs", "reachjs_off" ): + w( d + "/m.js", [ "int x;" ] ) +for d in ( "reachc", "reachc_off" ): + w( d + "/m.h", [ "int x;" ] ) PY # user-CPU seconds (user+sys) of one cold run of "$@" against corpus $1 @@ -528,6 +543,14 @@ b_pm_off="$( usercpu "$TMP/patmeta_off/n16000" "$BIN" --pattern='$X == $X' )" b_pm_walk="$( usercpu "$TMP/patmeta/n16000" "$BIN" --pattern='$X == $X' )" arm "(B36) nodesMatchExactly" "$b_pm_off" "$b_pm_walk" 8 0.30 "--pattern='\$X == \$X' over two 16000-comment argument lists vs the same pattern with the floods outside both lists" +# #358 — importContainerReach (src/ingest_importcap.h): ancestry read in ONE descent, not k ts_node_parent hops +b_rjs_off="$( usercpu "$TMP/reachjs_off" "$BIN" --top-k=100000 )" +b_rjs_on="$( usercpu "$TMP/reachjs" "$BIN" --top-k=100000 )" +arm "(B37) importContainerReach/js" "$b_rjs_off" "$b_rjs_on" 8 0.30 "2000 require() calls inside 2000 nested blocks vs the same nesting holding 2000 non-import calls" +b_rc_off="$( usercpu "$TMP/reachc_off" "$BIN" --top-k=100000 )" +b_rc_on="$( usercpu "$TMP/reachc" "$BIN" --top-k=100000 )" +arm "(B38) importContainerReach/c" "$b_rc_off" "$b_rc_on" 8 0.30 "2000 #include lines inside 2000 nested #ifdef vs the same 2000 lines at file scope" + # ── (C) byte-identical against a reference binary ──────────────────────────────────────────────────── echo echo "=== (C) byte-identical output vs RIPWIRE_REF_BIN ===" From a60040cabb6aecbf39cf88363caf7b69922c9c36 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 14:49:23 -0400 Subject: [PATCH 059/170] refactor(for): the enrichment builders take the header's promises as one aggregate; packHops drops its unused outShown quality-delta against the main merge-base gated three params rows (packHops 10->13, buildForCompactHops 8->10, buildForEnrichment 10->12): FE-B's viaLegendInHead and the owner plan now travel as ForSectionPromises, and packHops' outShown (its one caller passes nullptr) is gone. Output byte-identical (ownerhopcheck 52/52). Co-Authored-By: Claude Opus 5.5 --- src/serialize.h | 5 ----- src/verbs_for.h | 25 ++++++++++++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index 8995a32d5..75ecc0d2c 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -7268,7 +7268,6 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector const std::vector& outOff, const std::vector& outTargets, const std::vector& outNameOnly, // FE-B: Graph::outNameOnly — the rows' via="name" RedactCounts* redact = nullptr, - std::size_t* outShown = nullptr, // rows actually emitted; nullptr ⇒ not recorded const std::vector* rank = nullptr, // query relevance, for ordering a CUT callee listing std::string_view rootArg = {}, // R-E: same single-root-only root= every verb takes bool viaLegendInHead = false, // FE-B: packBodies' parameter of the same name @@ -7390,10 +7389,6 @@ inline void packHops( std::FILE* out, const IngestResult& ing, const std::vector w.write( children ); w.write( "" ); w.flush(); - if( outShown ) - { - *outShown = shownCount; - } } // ── M6 (density audit 2026-08-08): the WHOLE-FILE form a bare --expand can serve ───────────────────── diff --git a/src/verbs_for.h b/src/verbs_for.h index 8b11857e0..ff89eb5fe 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2188,6 +2188,15 @@ struct ForOwnerPlan bool changesNodes() const noexcept { return any() || !noEdgeIds.empty(); } // …or an edgeless owner to count in noedge= }; +// What the --for header already decided for the enrichment section it introduces: FE-B's "the header defines +// via=\"name\"" (ForLensHeaderParts::viaPresent) and the owner plan its qword clause read. One aggregate, so the section +// builders render exactly what the header promised without a parameter per promise. +struct ForSectionPromises +{ + bool viaLegendInHead = false; + const ForOwnerPlan* owners = nullptr; // nullptr ⇒ no owner rows (an empty plan reads the same) +}; + // ASCII fold of one name or word, leading _ # $ @ removed (the owner match key) inline std::string ownerFoldName( std::string_view n ) { @@ -2385,8 +2394,11 @@ inline std::vector forDetailIds( const rw::IngestResult& ing, const ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::IngestResult& ing, const rw::Graph& g, const std::vector& lensSurfaceIds, const std::vector& lensRank, std::size_t committedBytes, std::size_t bundleBudget, rw::RedactCounts* redactPtr, - bool viaLegendInHead, const ForOwnerPlan& ownerPlan ) + const ForSectionPromises& promised ) { + static const ForOwnerPlan kNoOwners{}; + const ForOwnerPlan& ownerPlan = promised.owners != nullptr ? *promised.owners : kNoOwners; + const bool viaLegendInHead = promised.viaLegendInHead; ForAutoBodiesResult out; // R-E (2026-08-17 harvest): same single-root condition every other verb's root= uses (sarif.h). const bool fcSingleRoot = ing.realPaths.empty() && cfg.roots.size() == 1; @@ -2431,7 +2443,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, g.outNameOnly, redactPtr, /*outShown=*/nullptr, &lensRank, fcRootArg, + { rw::packHops( f, ing, hopNodes, hopBudget, g.outOff, g.outTargets, g.outNameOnly, redactPtr, &lensRank, fcRootArg, viaLegendInHead, &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 @@ -2463,13 +2475,12 @@ ForAutoBodiesResult buildForEnrichment( const rw::Config& cfg, const rw::IngestR const std::vector& lensSurfaceIds, const std::vector& lensRank, const ForEnrichmentPlan& plan, const std::vector& anchorDefs, rw::RedactCounts* redactPtr, std::size_t committedBytes, std::size_t bundleBudget, - bool viaLegendInHead, // FE-B: the header defines via="name" (ForLensHeaderParts::viaPresent) - const ForOwnerPlan& ownerPlan ) // owner-hop: the plan the header's qword clause read + const ForSectionPromises& promised ) // FE-B via="name" in the head + the owner plan { const std::size_t committed = committedBytes + plan.attrReserve; ForAutoBodiesResult out = plan.compact - ? buildForCompactHops( cfg, ing, g, lensSurfaceIds, lensRank, committed, bundleBudget, redactPtr, viaLegendInHead, ownerPlan ) - : buildForAutoBodies( cfg, ing, g, lensSurfaceIds, lensRank, committed, bundleBudget, redactPtr, anchorDefs, viaLegendInHead ); + ? buildForCompactHops( cfg, ing, g, lensSurfaceIds, lensRank, committed, bundleBudget, redactPtr, promised ) + : buildForAutoBodies( cfg, ing, g, lensSurfaceIds, lensRank, committed, bundleBudget, redactPtr, anchorDefs, promised.viaLegendInHead ); if( plan.compact ) { out.markupBytes = out.section.xml.size(); @@ -3637,7 +3648,7 @@ std::optional runForLens( const MainDispatch& d ) // reserved ahead of the body walk (the kAutoAttrReserve pattern) so // the disclosure always fits; the DEFAULT regime reserves nothing — // the tail rides on top there and the bodies stay byte-identical. - bundleBudget, headerParts.viaPresent, ownerPlan ); + bundleBudget, ForSectionPromises{ headerParts.viaPresent, &ownerPlan } ); if( enrich.surfaceOff || enrich.legendOff ) { headerParts.autoBundle = headerParts.compactBundle = false; From f01bdd78dd962f9d8ce68f2afe0fd8bf926b54f0 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 15:15:32 -0400 Subject: [PATCH 060/170] docs(CHANGELOG): owner-hop measured row counts from the final head (1 noedge-only, 8 with owner rows) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edadd8430..69c8b3ac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ that repeat the question's other words, so its callees — the names an agent ne served (a moved owner can only free room). Under `--token-budget` they are funded first. A question that names no owner, `--json --for` and the MCP `for` verb are byte-identical; `RIPWIRE_NO_OWNER_HOP=1` restores the previous answer. Measured on the 32 `--for` rows of the round-1 comparison set: the two in-scope rows now name 5 of 5 gold - callee items they missed (were 0 of 5); 23 of 32 answers are byte-identical, 2 differ only in `noedge=`, and 7 grow by 0.2–1.1 KB. Gate: + callee items they missed (were 0 of 5); 23 of 32 answers are byte-identical, 1 differs only in `noedge=`, and 8 grow by 0.2–1.1 KB (+1.4% over all 32). Gate: `test/ownerhopcheck.sh`. ### Changed — a call bound by name alone keeps its rows, marked `via="name"`; typed receivers resolve From bdf3961b786aa689c39e0b88872893a62b89f88c Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 16:26:43 -0400 Subject: [PATCH 061/170] for: the common-word table is searched and checked with rw::sortutil::svLess (portablebuildcheck #6/#6b, the #345 Linux G1 abort) Co-Authored-By: Claude Opus 5.5 --- src/verbs_for.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/verbs_for.h b/src/verbs_for.h index ff89eb5fe..b8e25acb5 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2243,7 +2243,7 @@ inline std::vector ownerQuestionWords( std::string_view task ) return words; } -static_assert( std::ranges::is_sorted( kOwnerCommonWords ), "kOwnerCommonWords is binary-searched: keep it sorted" ); +static_assert( std::ranges::is_sorted( kOwnerCommonWords, rw::sortutil::svLess ), "kOwnerCommonWords is binary-searched: keep it sorted" ); inline ForOwnerPlan forOwnerHopPlan( const rw::IngestResult& ing, const rw::Graph& g, std::string_view task, const std::vector& lensSurfaceIds, const std::vector& lensRank, @@ -2298,7 +2298,7 @@ inline ForOwnerPlan forOwnerHopPlan( const rw::IngestResult& ing, const rw::Grap std::erase_if( found, [ & ]( const Owner& o ) { return !std::binary_search( headWords.begin(), headWords.end(), o.word ); } ); // rank order, common words after the rest (stable: rank order inside each group) std::stable_partition( found.begin(), found.end(), - []( const Owner& o ) { return !std::ranges::binary_search( kOwnerCommonWords, std::string_view( o.word ) ); } ); + []( const Owner& o ) { return !std::ranges::binary_search( kOwnerCommonWords, std::string_view( o.word ), rw::sortutil::svLess ); } ); // svLess: #345 for( Owner& o : found ) { if( !rw::hopSlotHasProvenEdge( g.outOff, g.outNameOnly, o.id ) ) From a12ab43f2b86521aa3906a20c57a615f24757f3c Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 19:12:58 -0400 Subject: [PATCH 062/170] feat(map): inventory tier names or counts every file the ranked rows leave out, plus program entries The default map prints the top-k ranked symbols, a few dozen files of a few hundred, and said nothing about the rest: subsystem files ranked just below the cut were silently absent, and a program's main (no in-repo caller, so PageRank's bottom) was never shown. The map scope now appends after the unchanged ranked rows: rows for program entries by name convention (a main nothing calls but its own module scope, or a __main__.py scope) with their callees, and rows naming the unshown code files per directory and counting tests, docs, config and test/doc/example trees. Present only when the ranked rows left out a file with code; never under --max-tokens, in=DIR or multi-root. JSON carries the same object; the compact legend reads it. Gate: test/mapinventorycheck.sh (fixture test/mapinvfix). Co-Authored-By: Claude Opus 5.5 --- src/compactlegend.h | 6 + src/main.cpp | 1 + src/mapinventory.h | 237 ++++++++++++++++++++++++++++++ src/mcpverbs.h | 4 +- src/serialize.h | 145 +++++++++++++++++- test/mapinventorycheck.sh | 126 ++++++++++++++++ test/mapinvfix/README.md | 3 + test/mapinvfix/data.json | 1 + test/mapinvfix/examples/demo.c | 2 + test/mapinvfix/go/cmd/main.go | 5 + test/mapinvfix/java/App.java | 4 + test/mapinvfix/lowfile.c | 1 + test/mapinvfix/prog.c | 3 + test/mapinvfix/py/lib.py | 6 + test/mapinvfix/py/pkg/__main__.py | 3 + test/mapinvfix/py/pkg/cli.py | 6 + test/mapinvfix/py/tool.py | 10 ++ test/mapinvfix/rs/src/main.rs | 3 + test/mapinvfix/tests/test_prog.c | 2 + test/mapinvfix/ts/index.ts | 1 + test/mapinvfix/util.c | 3 + test/mapinvfix/web/a/one.ts | 1 + test/mapinvfix/web/b/two.test.ts | 2 + test/mapinvfix/web/b/two.ts | 1 + 24 files changed, 573 insertions(+), 3 deletions(-) create mode 100644 src/mapinventory.h create mode 100755 test/mapinventorycheck.sh create mode 100644 test/mapinvfix/README.md create mode 100644 test/mapinvfix/data.json create mode 100644 test/mapinvfix/examples/demo.c create mode 100644 test/mapinvfix/go/cmd/main.go create mode 100644 test/mapinvfix/java/App.java create mode 100644 test/mapinvfix/lowfile.c create mode 100644 test/mapinvfix/prog.c create mode 100644 test/mapinvfix/py/lib.py create mode 100644 test/mapinvfix/py/pkg/__main__.py create mode 100644 test/mapinvfix/py/pkg/cli.py create mode 100644 test/mapinvfix/py/tool.py create mode 100644 test/mapinvfix/rs/src/main.rs create mode 100644 test/mapinvfix/tests/test_prog.c create mode 100644 test/mapinvfix/ts/index.ts create mode 100644 test/mapinvfix/util.c create mode 100644 test/mapinvfix/web/a/one.ts create mode 100644 test/mapinvfix/web/b/two.test.ts create mode 100644 test/mapinvfix/web/b/two.ts diff --git a/src/compactlegend.h b/src/compactlegend.h index 0f8a27cdb..07168470e 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -214,6 +214,8 @@ inline constexpr std::string_view kCompactProsePrefixes[] = ""; + +} // namespace rw diff --git a/src/mcpverbs.h b/src/mcpverbs.h index dfdfa611b..71bd12a5f 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -449,7 +449,7 @@ inline std::string analyzeToString( const std::string& root, int topK, bool stab // W2-F: the map's convergence disclosure. The CLI map carries pr_iters= and // this one must too — "the clause landed at 3 of its 5 echo sites" is the // §B4 family, and mcpclidiffcheck is the gate that keeps the two surfaces one. - /*ann=*/rw::MapAnnotations{ .prDisclosure = ix.prDisclosure, .codeFirstRows = ix.isCleanWorkingSet }, + /*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 ); } ); } @@ -517,7 +517,7 @@ inline std::string rankByText( const std::string& root, std::string_view mode, i ix.g.bindLabel.empty() ? nullptr : &ix.g.bindLabel, /*autoOrder=*/false, /*outEstTokens=*/nullptr, /*extraPayloadTokens=*/0, - /*ann=*/rw::MapAnnotations{ .rankByLabel = rankByLabel, .prDisclosure = disclosure, .codeFirstRows = rankByLabel == nullptr }, // pagerank: the map scope's code-first pick + /*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 ); } ); } diff --git a/src/serialize.h b/src/serialize.h index d1a650f1a..713b0ca9f 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -27,6 +27,7 @@ // strip --grep's emitGrepReport uses, reused here so the two verbs cannot diverge #include "prconverge.h" // W2-F: RankDisclosure + the pr_iters= / pr_converged= spellings (CLI and MCP share them) #include "gitmine.h" // F3 (H2H-Graft): RecentFile — the map's rows are the churn-decay miner's own product +#include "mapinventory.h" // D7: the map scope's tier — computeMapInventory, kMapInventoryLegend #include #include // std::iota — codeFirstKeep @@ -1953,6 +1954,11 @@ struct MapAnnotations // FE-B: the compact posture strips the map's via="name" comment (compactlegend.h, the via/x rows restate it), so the // map does not write it at all — and the --max-tokens fit, which measures this render, prices the delivered bytes. bool viaLegendStripped = false; + // D7 MAP-INVENTORY (mapinventory.h): the map scope's inventory tier — after the ranked rows, naming or counting + // every indexed file they did not show, plus the program entries. Set where codeFirstRows is (the plain CLI map, MCP + // analyze on a clean working set, MCP rank_by=pagerank) except under --max-tokens (mapInventoryWanted). Filled by + // assignment, like the trailing fields above. + bool inventoryTier = false; }; // ── the code-first row pick: data_sections_cut= / next= (docs/EVALS.md "Map data Sections never crowd code out of the @@ -2743,6 +2749,54 @@ inline bool hopSlotHasProvenEdge( const std::vector& outOff, cons []( std::uint8_t bit ) { return bit != 0; } ); } +// D7 MAP-INVENTORY: whether THIS map carries the inventory tier (mapinventory.h), decided once for both dialects. The +// default map scope only (MapAnnotations::inventoryTier); never under in=DIR's stub (no ranked rows to complement), +// multi-root (the roots table already partitions the paths, and a per-root inventory is a different answer), or +// --max-tokens (its fit binary-searches top-k over the rendered bytes; an inventory that GROWS as top-k shrinks would +// make that search non-monotone). +inline bool mapInventoryWanted( const IngestResult& ing, const MapAnnotations& ann ) noexcept +{ + return ann.inventoryTier && !ann.stubSymbols && ing.rootLabels.size() < 2 && ann.maxTokensFit == nullptr; +} + +// The XML element: `……`. Entry callees go +// through writeMapCalleeRows, the ranked rows' own writer, so prov=/via="name"/x= read exactly as they do above. +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 ) +{ + EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); + w.write( "" ); + for( NodeId id : inv.entries ) + { + const Symbol& s = ing.symbols[ id ]; + w.write( "" ); + writeMapCalleeRows( w, ing, outOff[ id ], outOff[ id + 1 ], outTargets, outProv, outNameOnly, esc, scratch ); + w.write( "" ); + } + for( const MapInventoryDir& d : inv.dirs ) + { + w.write( "" ); + } + w.write( "" ); +} + inline void serialize( std::FILE* out, const IngestResult& ing, const std::vector& rank, const std::vector& outOff, const std::vector& outTargets, int topK, bool mostImportantLast = false, @@ -2854,6 +2908,9 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto if( !seen[f] ) { seen[f] = 1; fileOrder.push_back( f ); } buckets[f].push_back( id ); } + // D7: the inventory over the files the kept rows did NOT reach (`seen` is exactly the printed groups) + const MapInventory inventory = ( mapInventoryWanted( ing, ann ) && !stubbed ) + ? computeMapInventory( ing, outOff, outTargets, seen, pathRel ) : MapInventory{}; // T1/§H7: the byte MODEL. It keeps two jobs and loses one. It still decides the T3 emit order below // (which has to be decided before any byte exists — see §H7 at estimateTokens) and it still supplies @@ -3029,6 +3086,10 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto { legend += kDataSectionsCutLegend; // charged to the map that carries data_sections_cut= } + if( inventory.active ) + { + legend += kMapInventoryLegend; // D7: charged to the map that carries + } // W2-F: the pr_iters= / pr_converged= definition, charged to the maps that carry the attributes — empty // for a lexical or HITS ordering, and the prose half only on the map whose iteration stopped short. legend += renderDisclosure( ann.prDisclosure, DiscloseAs::LegendComment ); @@ -3335,6 +3396,21 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto stub += "/>"; w.write( stub ); } + // D7: the inventory tier rides the LOW-importance end of the document — after the rows, or before them when the + // map is emitted important-last (an explicit flag or the fill-aware auto flip), so the top-ranked rows keep the + // end position that order promises. + const auto writeInventory = [ & ]() + { + if( inventory.active ) + { + 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 + if( inventoryFirst ) + { + writeInventory(); + } static const std::vector kNoFiles; for( std::uint32_t f : ann.stubSymbols ? kNoFiles : fileOrder ) { @@ -3553,6 +3629,10 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto } w.write( "" ); } + if( !inventoryFirst ) + { + writeInventory(); + } w.write( "" ); }; // DEGRADE, one path for both failures: the head goes out FIRST carrying the MODELLED estimate (the pre-§H7 number, so @@ -9438,6 +9518,61 @@ inline void writeJsonMapHeader( JsonWriter& w, std::string& esc, const JsonMapHe w.write( "]" ); } +// D7: the JSON twin of writeMapInventoryXml — `,"inv":{"listed":L,"unlisted":U,"entry":[{"p":…,"n":…,"c":[…]}], +// "ls":[{"p":…,"n":N,"f":[…]}]}` after the "r" array. The callee objects spell exactly what a "r" row's "c" array does. +template +inline void writeMapInventoryJson( JsonWriter& 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::string& esc ) +{ + EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); + w.write( ",\"inv\":{\"listed\":" ); w.write( std::to_string( inv.listed ) ); + w.write( ",\"unlisted\":" ); w.write( std::to_string( inv.unlisted ) ); + w.write( ",\"entry\":[" ); + for( std::size_t i = 0; i < inv.entries.size(); ++i ) + { + const NodeId id = inv.entries[ i ]; + const Symbol& s = ing.symbols[ id ]; + w.write( i ? ",{\"p\":" : "{\"p\":" ); writeJsonStr( w, pathRel( s.fileId ), esc ); + w.write( ",\"n\":" ); writeJsonStr( w, s.name, esc ); + w.write( ",\"c\":[" ); + for( std::uint32_t e = outOff[ id ]; e < outOff[ id + 1 ]; ++e ) + { + w.write( e > outOff[ id ] ? ",{\"n\":" : "{\"n\":" ); writeJsonStr( w, ing.symbols[ outTargets[ e ] ].name, esc ); + if( outProv && e < outProv->size() && ( *outProv )[ e ] ) + { + w.write( ",\"prov\":" ); writeJsonStr( w, provLabel( ( *outProv )[ e ] ), esc ); + } + if( outNameOnly && e < outNameOnly->size() && ( *outNameOnly )[ e ] != 0 ) + { + w.write( ",\"via\":\"name\"" ); + } + w.write( "}" ); + } + w.write( "]}" ); + } + w.write( "],\"ls\":[" ); + for( std::size_t i = 0; i < inv.dirs.size(); ++i ) + { + const MapInventoryDir& d = inv.dirs[ i ]; + w.write( i ? ",{\"p\":" : "{\"p\":" ); writeJsonStr( w, d.dir, esc ); + w.write( ",\"n\":" ); w.write( std::to_string( d.total ) ); + if( !d.named.empty() ) + { + w.write( ",\"f\":[" ); + for( std::size_t j = 0; j < d.named.size(); ++j ) + { + if( j ) { w.write( "," ); } + writeJsonStr( w, inventoryBaseOf( pathRel( d.named[ j ] ) ), esc ); + } + w.write( "]" ); + } + w.write( "}" ); + } + w.write( "]}" ); +} + // The default-map JSON sibling of serialize() — same rank/order/bucket logic (kept a deliberate, mechanical // duplication per the L2 seam decision: a sibling emitter, not a shared-emission refactor of the XML path, // so the G5 byte-identical-default contract carries zero risk from this addition). Scope: the common single- @@ -9510,6 +9645,9 @@ inline void serializeJson( std::FILE* out, const IngestResult& ing, const std::v if( !seen[f] ) { seen[f] = 1; fileOrder.push_back( f ); } buckets[f].push_back( id ); } + // D7: the XML twin's inventory, the same selection (mapinventory.h) over the same printed-file set + const MapInventory inventory = mapInventoryWanted( ing, ann ) ? computeMapInventory( ing, outOff, outTargets, seen, pathRel ) + : MapInventory{}; // T1/§H7: SAME byte-model the XML sibling uses, in the same two roles — the fill-order oracle and the // rate source. The REPORTED est_tokens is measured from the emitted bytes in PHASE 2 below, exactly as @@ -9670,7 +9808,12 @@ inline void serializeJson( std::FILE* out, const IngestResult& ing, const std::v } w.write( "]}" ); } - w.write( "]}" ); + w.write( "]" ); + if( inventory.active ) + { + writeMapInventoryJson( w, ing, inventory, pathRel, outOff, outTargets, outProv, outNameOnly, esc ); + } + w.write( "}" ); }; // DEGRADE, one path for both failures: the header FIRST with the MODELLED estimate, the array streamed behind it. const auto emitModelled = [ & ]() diff --git a/test/mapinventorycheck.sh b/test/mapinventorycheck.sh new file mode 100755 index 000000000..59cfac986 --- /dev/null +++ b/test/mapinventorycheck.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# mapinventorycheck.sh — the default map's inventory tier (): no indexed file is silently absent from an +# orient map, and the program entry points are named. +# +# WHY. The ranked map prints the top-k (200) symbols, which covers ~30-90 files of a few hundred. On the round-1 +# comparison table that cut was the whole orient miss class (K19): tmux input.c / key-bindings.c, hono's +# validator/, textual's drivers/ and command.py, htop's SignalsPanel.c — every one an indexed file ranked just below +# the cut, with NOTHING in the answer saying the file existed. Entry points are worse: `main` has no in-repo caller, +# so PageRank puts it at the very bottom (htop main #2548 of 3206, tmux main #3220 of 6361) and no re-rank of the +# map can surface it. The tier appends, after the ranked rows and without touching them: rows for program +# entries by name convention, and rows naming (code) or counting (tests/docs/examples/config) every file the +# ranked rows did not show, grouped by directory. +# +# Fixture test/mapinvfix: C, Python, Go, Rust, Java and TS files; a test-dir main, an examples main, a Python main +# with a real caller (NOT an entry), a cli main reached from __main__.py (NOT an entry: the __main__ scope is). +set -u + +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" +[ "${BIN#/}" = "$BIN" ] && BIN="$ROOT/$BIN" +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT +FAILED=0 +fail() { printf ' FAIL %s\n' "$*"; FAILED=$(( FAILED + 1 )); } +ok() { printf ' PASS %s\n' "$*" || { FAILED=$(( FAILED + 1 )); printf ' FAIL could not write the PASS line for: %s\n' "$*"; }; return 0; } +[ -x "$BIN" ] || { echo "mapinventorycheck: no binary at $BIN — build first"; exit 2; } +cd "$ROOT" || exit 2 +FIX=test/mapinvfix +[ -d "$FIX" ] || { echo "mapinventorycheck: fixture $FIX missing"; exit 2; } + +run() { "$BIN" "$FIX" --no-cache "$@" 2>"$TMP/err"; } +run --top-k=3 >"$TMP/map"; rc=$? +[ "$rc" -eq 0 ] && [ -s "$TMP/map" ] || { echo "mapinventorycheck: the map run failed (rc=$rc)"; cat "$TMP/err"; exit 1; } +run --top-k=3 --legend=full >"$TMP/full" +run --top-k=3 --json >"$TMP/json" +run >"$TMP/whole" # default top-k: every file shown +run --top-k=3 --max-tokens=400 >"$TMP/maxtok" +run --top-k=3 --expand=run >"$TMP/expand" +run --top-k=3 --for="where does main start" >"$TMP/for" +run --top-k=3 --stable >"$TMP/stable" +run --top-k=3 >"$TMP/map2" + +# The tier, isolated (one line of minified XML). +INV="$( grep -o '' "$TMP/map" )" + +# (A) the tier rides a map whose ranked rows dropped a file — RED on a binary without it +if [ -n "$INV" ]; then ok "(A) a map with a ranked cut carries "; else fail "(A) no on a map whose top-k=3 dropped 15 of 18 files"; fi + +# (B) it comes AFTER the ranked rows and the rows are untouched: … +if grep -q ']*>.*' "$TMP/map"; then ok "(B) follows the last ranked group and closes the root"; else fail "(B) is not appended after the ranked rows"; fi + +# (C) entries: exactly the program entries by convention, sorted by path +ENTRIES="$( printf '%s' "$INV" | grep -o ''; then ok "(D) prog.c main → run"; else fail "(D) prog.c main does not list its callee run"; fi +if printf '%s' "$INV" | grep -q ']*/>'; then ok "(D2) the __main__.py scope → main"; else fail "(D2) the __main__.py entry does not list main"; fi + +# (E) a low-ranked code file is NAMED in its directory's row +if printf '%s' "$INV" | grep -q '"; then ok "(F) $d/ rolls up count-only"; else fail "(F) $d/ is not a count-only row"; fi +done + +# (G) arithmetic: listed + Σ n = the header's files=, and listed = the groups the map printed +FILES="$( grep -o '"; +} + +} // namespace rw diff --git a/src/main.cpp b/src/main.cpp index ee4a09577..eed977798 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,6 +89,7 @@ static_assert( rw::kTestGateCcxBarMirror == rw::quality::kCcxBar, "situ.h kTestG #include "didyoumean.h" // §P12.1 / §B6 M8: the ONE near-miss suggester, now shared with the MCP refusal table #include "selectorrefuse.h" // §B4.2: the ONE file:name selector not-found refusal — all six SYM-taking verbs #include "gitmine.h" +#include "historyread.h" // K51: the disclosure of a git history walk that could not be read (churn=/amp=) #include "ownersview.h" // §P6.4: countUniformOwnership/ownershipRowsToPrint — shared with mcpverbs.h's `owners` verb #include "mention.h" // B8: query-mention anchoring — files/modules/symbols NAMED in the --for text #include "siblift.h" // r4 EXPERIMENT: env-gated same-directory sibling lift (inert by default) @@ -5252,6 +5253,18 @@ static int dispatchMain( const rw::Config& cfg, char** argv ) // sets concatenate (commits are disjoint across repos) and churn accumulates per file. ASSUME( historyWalks.size() == ( multiRoot ? ws.size() : 1u ), "this block runs under startHistoryWalks' condition (--help-task has already answered), so every root's walk was started" ); + // K51: a walk git could not finish arrives flagged (it is never cached either); the document says so (historyread.h). + HistoryReadReport& historyReport = historyRead(); + const auto joinWalk = [ & ]( std::size_t r ) + { + quality::HistoryWalk walk = historyWalks[ r ].get(); + if( walk.raw.unread ) + { + DISCLOSE( historyReport, HistoryReadReport::DisclosureWhy::WalkUnread, + "main: the git history walk could not be read — churn= is absent and amp= counts callers only, and the document says so" ); + } + return walk; + }; std::vector> commits; if( multiRoot ) { @@ -5261,7 +5274,7 @@ static int dispatchMain( const rw::Config& cfg, char** argv ) // Y2: the memoized form — skips the 431 ms `git log --name-only` walk on a // warm (repo, HEAD sha, window, boundary-sha) hit; see quality.h's qchurn family. std::vector> part = - quality::resolveHistoryWalk( historyWalks[ r ].get(), ing, + quality::resolveHistoryWalk( joinWalk( r ), ing, cfg.forTask.empty() ? 0u : 12u, cfg.forTask.empty() ? nullptr : &rootChurn, r ); for( std::vector& c : part ) @@ -5284,7 +5297,7 @@ static int dispatchMain( const rw::Config& cfg, char** argv ) else { // Y2: memoized — see the multi-root branch above. - commits = quality::resolveHistoryWalk( historyWalks[ 0 ].get(), ing, + commits = quality::resolveHistoryWalk( joinWalk( 0 ), ing, cfg.forTask.empty() ? 0u : 12u, cfg.forTask.empty() ? nullptr : &forChurn ); } diff --git a/src/quality.h b/src/quality.h index 3b8b98450..6423eb82a 100644 --- a/src/quality.h +++ b/src/quality.h @@ -4580,7 +4580,7 @@ inline bool deserializeRawCommitStream( const std::string& blob, const std::stri // corrupt/foreign blob — is a clean miss that falls through to a full recompute, never a wrong answer). No git repo // degrades to an empty stream; no resolvable HEAD to the uncached walk (which itself degrades to an empty stream — // gitLogNameOnlyRaw's contract). Thread-safe: every memo it reaches (the HEAD anchor, the toplevel) takes its own -// mutex, and the blob write is an atomic rename. +// mutex, and the blob write is an atomic rename. K51: a walk git could not complete comes back with `unread` set and is never stored. inline RawCommitStream gitRawCommitStreamCached( const std::string& root, const char* coSince ) { if( !hasEnclosingGitRepo( root ) ) @@ -4590,7 +4590,11 @@ inline RawCommitStream gitRawCommitStreamCached( const std::string& root, const const std::string headSha = gitHeadSha( root ); if( headSha.empty() ) { - return gitLogNameOnlyRaw( root, coSince ); + // No resolvable HEAD: an unborn branch has no history to read, and git log's failure there is that fact, not an I/O + // error — so this walk is "read, empty" (K51 floor: a HEAD that is corrupt rather than unborn reads the same here). + RawCommitStream noHead = gitLogNameOnlyRaw( root, coSince ); + noHead.unread = false; + return noHead; } const std::string repoHex = cacheRootKeyHex( root ); @@ -4609,6 +4613,13 @@ inline RawCommitStream gitRawCommitStreamCached( const std::string& root, const } raw = gitLogNameOnlyRaw( root, coSince ); // cold — the 431 ms walk + if( raw.unread ) + { + // K51: a walk that could not be read is NOT stored. Stored, its empty stream would answer "read, no commits" under this + // (repo, HEAD, window) key for as long as HEAD stayed put; unstored, the next call walks again and the caller discloses. + // The flag rides the returned stream to main.cpp's amp=/churn= block, which DISCLOSEs it into the document (historyread.h). + return raw; + } atomicWriteFile( cachePath, serializeRawCommitStream( raw, keyMat ) ); // best-effort; a failed // write just recomputes next time return raw; diff --git a/src/serialize.h b/src/serialize.h index d1a650f1a..d3c1505dd 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -26,6 +26,7 @@ #include "sarif.h" // R-E (2026-08-17): rootRelativeUri/rootPrefixOf — the same root= single-root-only // strip --grep's emitGrepReport uses, reused here so the two verbs cannot diverge #include "prconverge.h" // W2-F: RankDisclosure + the pr_iters= / pr_converged= spellings (CLI and MCP share them) +#include "historyread.h" // K51: history_unread= on every root that prints churn=/amp= #include "gitmine.h" // F3 (H2H-Graft): RecentFile — the map's rows are the churn-decay miner's own product #include @@ -349,7 +350,9 @@ inline std::string ctxRootOpen( std::string_view task, std::string_view routeNot { out += " route_scrubbed=\"1\""; } + out += renderHistoryUnread( DiscloseAs::XmlAttrs ); // K51: absent unless the amp=/churn= walk could not be read; shared by --for/--pack-task/--from-trace out += ">"; + out += renderHistoryUnread( DiscloseAs::LegendComment ); // its own definition (historyread.h) return out; } @@ -377,6 +380,7 @@ inline std::string ctxRootJsonScrubKeys( std::string_view task, std::string_view { keys += ",\"route_xml_scrubbed\":true"; } + keys += renderHistoryUnread( DiscloseAs::JsonKeys ); // K51: this is the one JSON root writer every ctx-family document shares (--for, --pack-task, --from-trace) return keys; } @@ -3247,6 +3251,7 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto // L3 follow-up (CodeRabbit 4053600616): TRULY last, past every pre-existing attribute — same placement // rule as lens= just above, so no attribute-adjacency assertion in test/ can break on it. if( ann.notesDegraded ) { h += notes::kNotesDegradedAttr; } + h += renderHistoryUnread( DiscloseAs::XmlAttrs ); // K51: absent unless the amp=/churn= walk could not be read // The code-first pick's swaps, last of all (the same placement rule as the two above): absent when nothing was swapped. if( dataSecCut.cut > 0 ) { @@ -3254,6 +3259,7 @@ inline void serialize( std::FILE* out, const IngestResult& ing, const std::vecto h += nextAttrXml( dataSectionsNext( dataSecCut ) ); } h += ">"; + h += renderHistoryUnread( DiscloseAs::LegendComment ); // K51: its own definition, right behind the root it marks return h; }; const auto buildTail = [ & ]( std::size_t estTokens ) -> std::string @@ -9406,6 +9412,7 @@ inline void writeJsonMapHeader( JsonWriter& w, std::string& esc, const JsonMapHe // (every MCP client) reading a truncated ranking with no key to tell it so is the exact defect the XML // side just closed. Same slot, same absent-means-converged rule. w.write( renderDisclosure( h.ann->prDisclosure, DiscloseAs::JsonKeys ) ); + w.write( renderHistoryUnread( DiscloseAs::JsonKeys ) ); // K51: the JSON twin of the root's history_unread= (absent unless the amp= walk was unread) // The JSON twin of the XML root's data_sections_cut= / next= (codeFirstKeep): same keys, same absent-at-zero rule. if( h.dataSectionsCut.cut > 0 ) diff --git a/src/verbs_grep.h b/src/verbs_grep.h index ee9285689..19bbed7f6 100644 --- a/src/verbs_grep.h +++ b/src/verbs_grep.h @@ -913,7 +913,7 @@ int emitGrepReport( const rw::Config& cfg, const rw::IngestResult& ing, const rw // R-H: the tier disclosure (helper above) — empty when nothing was held back. const std::string tierAttr = grepTierAttrs( tierReport, /*floorAlreadyEmitted=*/hitsCapped != 0 ); // N2: tier_budget= floors the root too // §R-J: unindexed_files_scanned=/unindexed_files_skipped=/unindexed_candidates_capped= (helper above). - const std::string auxAttr = grepUnindexedAttrs( aux ); + const std::string auxAttr = grepUnindexedAttrs( aux ) + renderHistoryUnread( DiscloseAs::XmlAttrs ); // K51: absent unless the amp= walk (--metrics) could not be read const std::string regexSkipAttr = grepRegexSkipAttrs( cfg, found, aux, /*floorAlreadyEmitted=*/hitsCapped != 0 || tierReport.budgetHit != nullptr ); std::string scanShortAttr; if( found.unreadableFiles != 0 ) @@ -954,6 +954,7 @@ int emitGrepReport( const rw::Config& cfg, const rw::IngestResult& ing, const rw /*collectionCapped=*/ hitsCapped != 0 ), // H8: the cap hits_capped= names floors the root hitsCapped, completeAttr, tierAttr.c_str(), corpusAttr.c_str(), auxAttr.c_str(), regexSkipAttr.c_str(), scanShortAttr.c_str(), rw::nextAttrXml( grepNext ).c_str() ); + rw::emitTo( stdout, "{}", renderHistoryUnread( DiscloseAs::LegendComment ) ); // K51: its own definition, behind the root it marks // G1 (2026-08-15 harvest): hits GROUP by file under , root-relative when this is a single-root // run (report-memgraph §F6: the absolute root prefix alone was 42.5% of a real --grep payload; the // repeated-per-hit path was report-octocode §F1's 31.4%). Byte-identical text within one file's group diff --git a/test/qchurncheck.sh b/test/qchurncheck.sh index 481ba8264..3ef573e50 100755 --- a/test/qchurncheck.sh +++ b/test/qchurncheck.sh @@ -121,6 +121,81 @@ diff -q "$TMP/new.out" "$TMP/new2.out" >/dev/null \ && ok "new-head cold vs warm output byte-identical" \ || no "new-head cold vs warm output differs" +# ── K51: a walk git COULD NOT FINISH is disclosed on every verb that prints churn=/amp=, never cached, and retried ────────── +# Before the fix a failed `git log --name-only` came back as an EMPTY stream, was stored under the (repo, HEAD, window) key, +# and every later call answered "no churn, no co-change partners" with no tell until HEAD moved. Two failure injections: a +# git shim that dies on the walk (reaches every build flavour), and a REAL one (a commit object removed from the store). +# The disclosure is history_unread="1" on the root (JSON: "history_unread":true); a read that found nothing stays silent. +KSHIM="$TMP/kshim"; mkdir -p "$KSHIM" +cat >"$KSHIM/git" <"$out" 2>"$TMP/k.err"; } +printf '// k51\n' >> "$REPO/src/lib.cpp" +git -C "$REPO" commit -qam "k51" # a fresh HEAD: no qchurn blob exists for it yet +kbefore="$( nqchurn )" +krun "$TMP/k_for.out" --for=helper; rcK=$? +[ "$rcK" -eq 0 ] && grep -q ' history_unread="1"' "$TMP/k_for.out" \ + && ok "K51: --for on a failed walk says history_unread=\"1\" on its root" \ + || no "K51: --for on a failed walk is silent (rc=$rcK): $( head -c 300 "$TMP/k_for.out" )" +grep -q ' churn="' "$TMP/k_for.out" \ + && no "K51: a failed walk still printed a churn= value" \ + || ok "K51: no churn= value is printed from a failed walk" +krun "$TMP/k_map.out" --metrics +grep -q ' history_unread="1"' "$TMP/k_map.out" && ok "K51: --metrics (the map) says history_unread=\"1\"" || no "K51: --metrics on a failed walk is silent" +krun "$TMP/k_json.out" --for=helper --json +grep -q '"history_unread":true' "$TMP/k_json.out" && ok "K51: --for --json says \"history_unread\":true" || no "K51: --for --json on a failed walk is silent" +krun "$TMP/k_grep.out" --grep=helper --metrics +grep -q ' history_unread="1"' "$TMP/k_grep.out" && ok "K51: --grep --metrics says history_unread=\"1\"" || no "K51: --grep --metrics on a failed walk is silent" +krun "$TMP/k_full.out" --for=helper --legend=full +grep -q 'history_unread=1: the git history walk' "$TMP/k_full.out" \ + && ok "K51: the attribute is defined beside its root (a comment that reads it)" || no "K51: history_unread= is not defined in the document" +xmllint --noout "$TMP/k_for.out" "$TMP/k_map.out" "$TMP/k_grep.out" "$TMP/k_full.out" 2>/dev/null \ + && ok "K51: every disclosing document is well-formed XML" || no "K51: a disclosing document is malformed XML" +[ "$( nqchurn )" -eq "$kbefore" ] \ + && ok "K51: the failed walk was NOT cached (qchurn blobs $kbefore -> $( nqchurn ))" \ + || no "K51: a failed walk was stored as a qchurn blob ($kbefore -> $( nqchurn ))" +run "$TMP/k_retry.log" --for=helper --no-cache >"$TMP/k_retry.out" 2>/dev/null +[ "$( name_only_count "$TMP/k_retry.log" )" -ge 1 ] && grep -q ' churn="[0-9]' "$TMP/k_retry.out" && ! grep -q 'history_unread' "$TMP/k_retry.out" \ + && ok "K51: the next call RETRIES the walk, prints churn=, and carries no disclosure" \ + || no "K51: the call after a failed walk did not retry cleanly (walks=$( name_only_count "$TMP/k_retry.log" )): $( head -c 200 "$TMP/k_retry.out" )" +[ "$( nqchurn )" -gt "$kbefore" ] && ok "K51: the successful retry IS cached" || no "K51: the successful retry wrote no blob" + +# the REAL failure: remove the parent commit's object — HEAD still resolves, the walk dies part-way +printf '// k51b\n' >> "$REPO/src/lib.cpp" +git -C "$REPO" commit -qam "k51b" +kparent="$( git -C "$REPO" rev-parse HEAD~1 )"; kobj="$REPO/.git/objects/${kparent:0:2}/${kparent:2}" +if [ -f "$kobj" ]; then + cp "$kobj" "$TMP/kobj.save"; rm -f "$kobj" + run "$TMP/k_real.log" --for=helper --no-cache >"$TMP/k_real.out" 2>/dev/null; rcR=$? + [ "$rcR" -eq 0 ] && grep -q ' history_unread="1"' "$TMP/k_real.out" \ + && ok "K51: a REAL unreadable object (git exits non-zero part-way) is disclosed" \ + || no "K51: a real git failure is silent (rc=$rcR): $( head -c 200 "$TMP/k_real.out" )" + kreal="$( nqchurn )" + run "$TMP/k_real2.log" --for=helper --no-cache >"$TMP/k_real2.out" 2>/dev/null + [ "$( name_only_count "$TMP/k_real2.log" )" -ge 1 ] && [ "$( nqchurn )" -eq "$kreal" ] \ + && ok "K51: the real failure is retried on the next call and still not cached" \ + || no "K51: the real failure was cached or not retried (walks=$( name_only_count "$TMP/k_real2.log" ), blobs $kreal -> $( nqchurn ))" + mkdir -p "$( dirname "$kobj" )"; cp "$TMP/kobj.save" "$kobj" + run "$TMP/k_heal.log" --for=helper --no-cache >"$TMP/k_heal.out" 2>/dev/null + grep -q ' churn="[0-9]' "$TMP/k_heal.out" && ! grep -q 'history_unread' "$TMP/k_heal.out" \ + && ok "K51: with the object restored the same call prints churn= and no disclosure" \ + || no "K51: the restored repo still answers degraded: $( head -c 200 "$TMP/k_heal.out" )" +else + no "K51: premise — the parent commit is not a loose object ($kobj), so the real-failure arm cannot run" +fi + +# negative: "read, and empty" is NOT "could not read" — a repository with no commit has no history to read +UNBORN="$( mktemp -d )"; mkdir -p "$UNBORN/src"; cp "$REPO/src/lib.cpp" "$UNBORN/src/lib.cpp"; git -C "$UNBORN" init -q +env TMPDIR="$QTMP" "$BIN" "$UNBORN" --for=helper --no-cache >"$TMP/k_unborn.out" 2>/dev/null; rcU=$? +[ "$rcU" -eq 0 ] && grep -q '/dev/null | xmllint --noout - 2>/dev/null \ && ok "xmllint clean" || no "xmllint reported malformed XML" From 084376c930b8786daa79aecce9f577de0e1aea76 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 19:53:18 -0400 Subject: [PATCH 070/170] feat(map): --no-inventory, runaway guards for the inventory tier, gate twins --no-inventory drops the default map's tier (the pre-tier shape; the ranked rows are identical either way). The tier gains two disclosed runaway guards far above every measured repo: at most 32 entries (entry_total=N past it) and at most 2000 file names (names_cut=N; later directories keep n= only). estchargecheck #2/#3 compared payload answers against the bare map as 'the same map'; the bare map now carries a tier the payload maps do not, so the comparison moves to the --no-inventory twin (same rows, premise asserted) and a new #2-tier arm keeps the old direction: the tiered map charges at least what its rows alone do. mapinventorycheck gains the row-identity, important- last and guard-boundary arms; the gate is registered. Co-Authored-By: Claude Opus 5.5 --- .github/pargates-shard-weights.json | 1 + src/cli.h | 9 +++++- src/compactlegend.h | 8 ++++-- src/main.cpp | 6 ++-- src/mapinventory.h | 35 +++++++++++++++++++++-- src/serialize.h | 7 ++++- test/estchargecheck.sh | 21 ++++++++++++-- test/mapinventorycheck.sh | 43 +++++++++++++++++++++++++---- test/regression.sh | 2 +- 9 files changed, 114 insertions(+), 18 deletions(-) diff --git a/.github/pargates-shard-weights.json b/.github/pargates-shard-weights.json index f16c8b459..5e0bcadbe 100644 --- a/.github/pargates-shard-weights.json +++ b/.github/pargates-shard-weights.json @@ -293,6 +293,7 @@ "manifestcheck.sh": 62.9, "mapdatasectioncheck.sh": 55.0, "mapdiffcheck.sh": 3.3, +"mapinventorycheck.sh": 2.5, "matchcapturecheck.sh": 11.8, "matchgrammarcheck.sh": 0.6, "maxfilesizecheck.sh": 6.8, diff --git a/src/cli.h b/src/cli.h index 3705dee7a..0b3f18683 100644 --- a/src/cli.h +++ b/src/cli.h @@ -176,6 +176,7 @@ struct Config bool mostImportantLast = false; // --order=important-last (was --most-important-last, now a hidden alias): emit highest-rank content last (explicit; always honoured) bool stable = false; // --order=stable (was --stable, now a hidden alias): emit in path/id order (stable prefix → provider KV-cache hits on re-runs) bool noStable = false; // --no-stable: opt OUT of the stable default that --mcp turns on (P2-C) — NOT an --order alias, unchanged + bool noInventory = false; // --no-inventory: drop the default map's tier (D7, serialize.h mapInventoryWanted) — the pre-D7 map shape bool noAutoOrder = false; // --order=important-first (was --no-auto-order, now a hidden alias): opt OUT of T3's fill-aware auto important-last (see serialize.h kFillOrderThreshold) int maxTokens = 0; // --max-tokens=N: budget the map to ~N tokens (binary-search top-K) — SHAPES the map to fit std::size_t tokenBudget = 0; // --token-budget=N[K]: CI gate — 0 = disabled. After the map is built, if its est_tokens @@ -830,6 +831,11 @@ inline constexpr char kHelpHead[] = " body and not the ~200-symbol map that otherwise rides along with it; the \n" " root then carries est_tokens= (the payload's price, the number --token-budget\n" " gates on), since no map header is there to carry it.\n" + " --no-inventory drop the default map's tier: the files its ranked rows leave out, and the entries\n" + " The default map appends after its ranked rows whenever they\n" + " left out a file with code: rows (program entries by name convention) and \n" + " rows naming or counting every unshown file by directory. This flag drops it (the rows\n" + " are unchanged either way). Never present under --max-tokens or on a payload verb's map.\n" " --max-tokens=N budget the map to ~N tokens (binary-search top-K) — SHAPES the map to fit.\n" " THE FIT IS A BYTE CEILING, and it is deliberately CONSERVATIVE: N is converted at\n" " 2.36 B/tok (the densest calibrated language, so N holds for any corpus) times a 0.90\n" @@ -3109,6 +3115,7 @@ inline constexpr BoolFlag kBoolFlags[] = // ordering (the --order= aliases stay hand-written: they warn) { "--no-stable", &Config::noStable }, + { "--no-inventory", &Config::noInventory }, // D7: the default map without its tier // the analysis lenses { "--metrics", &Config::metrics }, @@ -3531,7 +3538,7 @@ inline constexpr IntFlag kIntFlags[] = // nowhere to keep) // • a bare no-op / bare pair --route, --quality-ack (the =REASON form is a kViewFlags row) inline constexpr std::size_t kHandWrittenFlagArms = 26; // +2 lane/lean-answers-068 (2026-10-07): --whereis-listing= and --mcp-legend= (closed-value arms, same shape as --grep-in=); +1 #350 (2026-09-28): --max-memory= (byte-size arm, same shape as --max-file-size=); +1: --color-by= (enum-value arm); +3 G3 (2026-08-15 harvest): --and=/--not=/--grep-scope= (repeatable-value arms, same shape as --exclude=); +1 R-H: --grep-in= (closed-value arm, same shape as --grep-scope=); +1 lane/flag-biggest-first (2026-09-23): --readability (deprecation-warning alias, same shape as --stable/--most-important-last/--no-auto-order) — its kBoolFlags row is renamed to --biggest-first (same Config::readability member), so the old spelling moves OUT of the table and becomes a hand-written arm, per the house rule that deprecation-warning aliases stay hand-written -inline constexpr std::size_t kTotalFlagArms = 218; // +1 knob-honesty-068 (2026-10-07): --lint-max-per-rule= (kIntFlags row) — the per-rule lint budget a floored answer's findings_next= names; +2 lane/lean-answers-068 (2026-10-07): --whereis-listing= and --mcp-legend= (hand-written closed-value arms, see kHandWrittenFlagArms); +1 lane/mcp-tool-profile-065 (2026-09-26): --mcp-tools= (kViewFlags row) — list only a subset of the MCP tools (names and/or the core/full profiles); +1 #350 (2026-09-28): --max-memory= (hand-written byte-size arm, see kHandWrittenFlagArms); +1 lane/flag-biggest-first (2026-09-23): --readability renamed to --biggest-first, kept working as a hand-written deprecated alias (see kHandWrittenFlagArms); +1 lane/r1-for-sections-stub (2026-09-19, L2/B1): --sections= (kViewFlags row) — the closed-set opt-in that restores the / sections --for collapses to a counted stub by default; +1 --lsp (kBoolFlags row, 2026-09-15): the navigation LSP server stdio entry point — Phase 1 PoC, docs/LSP.md; +1 lane/recent-scope (2026-09-12, C1-b): --in= (kViewFlags row) — the directory-scoped block of --rank-by=churn-decay; +2 P4 (capture-audit 2026-09-04, lane L7): --zoom-levels= (kIntFlags row, the printed-levels ceiling) and --include-builtins (kBoolFlags row, the external-surface builtin opt-in); +1 P9 (capture-audit 2026-09-04, lane L8): --no-post-check (kBoolFlags row, the edit receipt's folded verification opt-out); +1 lane/ca-L2 (2026-09-04, H11): --allow-dirty (kBoolFlags row) — the explicit consent --quality-baseline needs before it pins a floor on a tree that differs from HEAD; +1 lane/n6-c (2026-09-03): --no-ignore (kBoolFlags row, the .gitignore-by-default escape hatch); +1 lane/af-scope (2026-08-29): --scope= (kViewFlags row, the quality-delta ownership partition); +1 --quality-delta= (kViewFlags, R-I ref-pair form); +1 --help-task= (kViewFlags); +2 VT-1: --run-trace= (kViewFlags) and --run-timeout= (kIntFlags); +1: --handoff (kBoolFlags row); +1 --readability (kBoolFlags row); +2 §CLIO: --cochange-groups (kBoolFlags), --cochange-recur= (kIntFlags); +1 --context-ratio (kBoolFlags row); +1 --nonlocal-state (kBoolFlags row); +2 --field-affinity (kBoolFlags) and --field-affinity= (kViewFlags); +1 --comment-coherence (kBoolFlags row); +2 --dmm (kBoolFlags) and --dmm= (kViewFlags); +2 --quality-panel (kBoolFlags) and --quality-panel= (kViewFlags); +1 --naming-consistency (kBoolFlags row); +1 --naming-locals (kBoolFlags row, local-variable-indexing plan Phase 2); +1 --skipped (kBoolFlags row, §P0.5d itemization); +1 --with-profile= (kViewFlags row, the --lint × #PROF_TSV heat join); +1 --color-by= (hand-written enum-value arm); +1 --sarif (kBoolFlags row, W1-SARIF: SARIF 2.1.0 export for --lint); +1 --signatures-only (kBoolFlags row, T3 terminal-by-default --for opt-out); +3 L7: --lint-catalog (kBoolFlags), --lint-select= and --lint-ignore= (kViewFlags); +3 G3 (2026-08-15 harvest): --and=/--not=/--grep-scope= (hand-written arms); +1 R-H: --grep-in= (hand-written arm); +1 R2: --pattern= (kViewFlags row, the code-shaped structural search); +1 lane/safe-delete (2026-08-21): --safe-delete= (kViewFlags row, the composed "can I delete this?" read); +1 lane/compact-conceptual (2026-08-22): --auto-bodies (kBoolFlags row, the compact-conceptual-serving opt-out); +5 CLI edit bridge (2026-08-27): --replace-symbol-body=/--insert-before-symbol=/--insert-after-symbol=/--edit-payload=/--edit-target-file= (kViewFlags rows); +1 --handles (kBoolFlags row, grep edit handles); +1 --legend= (kViewFlags row, compact schema dialect); +3 edit-plan: --edit-plan= (kViewFlags) and --dry-run/--apply (kBoolFlags rows); +1 --agent= (kViewFlags row, the --doctor Codex surface); +1 lane/paper-slice (2026-08-28): --slice= (kViewFlags row, the ARISE-motivated def-use slice); +1 lane/af-planlint (2026-08-29): --plan-lint= (kViewFlags row, the PLAN-format structure gate, P3.2); +2 lane/or-arise (2026-08-30): --slice-flow= (kViewFlags row) and --slice-depth= (kIntFlags row) — the ARISE rung-2 cross-statement data-flow slice; +1 lane/at-seed (2026-08-30): --at= (kViewFlags row) — the FILE:LINE enclosing-chain report, with the @FILE:LINE selector spelling resolved in graph.h (no flag arm of its own); +1 CARD-1 phase 2 (2026-08-31): --pin-census= (kViewFlags row) — the eval-only S6-C silent-pin census, written beside the map and never into it +inline constexpr std::size_t kTotalFlagArms = 219; // +1 lane/map-inventory-d7 (2026-10-08): --no-inventory (kBoolFlags row) — the default map without its tier; +1 knob-honesty-068 (2026-10-07): --lint-max-per-rule= (kIntFlags row) — the per-rule lint budget a floored answer's findings_next= names; +2 lane/lean-answers-068 (2026-10-07): --whereis-listing= and --mcp-legend= (hand-written closed-value arms, see kHandWrittenFlagArms); +1 lane/mcp-tool-profile-065 (2026-09-26): --mcp-tools= (kViewFlags row) — list only a subset of the MCP tools (names and/or the core/full profiles); +1 #350 (2026-09-28): --max-memory= (hand-written byte-size arm, see kHandWrittenFlagArms); +1 lane/flag-biggest-first (2026-09-23): --readability renamed to --biggest-first, kept working as a hand-written deprecated alias (see kHandWrittenFlagArms); +1 lane/r1-for-sections-stub (2026-09-19, L2/B1): --sections= (kViewFlags row) — the closed-set opt-in that restores the / sections --for collapses to a counted stub by default; +1 --lsp (kBoolFlags row, 2026-09-15): the navigation LSP server stdio entry point — Phase 1 PoC, docs/LSP.md; +1 lane/recent-scope (2026-09-12, C1-b): --in= (kViewFlags row) — the directory-scoped block of --rank-by=churn-decay; +2 P4 (capture-audit 2026-09-04, lane L7): --zoom-levels= (kIntFlags row, the printed-levels ceiling) and --include-builtins (kBoolFlags row, the external-surface builtin opt-in); +1 P9 (capture-audit 2026-09-04, lane L8): --no-post-check (kBoolFlags row, the edit receipt's folded verification opt-out); +1 lane/ca-L2 (2026-09-04, H11): --allow-dirty (kBoolFlags row) — the explicit consent --quality-baseline needs before it pins a floor on a tree that differs from HEAD; +1 lane/n6-c (2026-09-03): --no-ignore (kBoolFlags row, the .gitignore-by-default escape hatch); +1 lane/af-scope (2026-08-29): --scope= (kViewFlags row, the quality-delta ownership partition); +1 --quality-delta= (kViewFlags, R-I ref-pair form); +1 --help-task= (kViewFlags); +2 VT-1: --run-trace= (kViewFlags) and --run-timeout= (kIntFlags); +1: --handoff (kBoolFlags row); +1 --readability (kBoolFlags row); +2 §CLIO: --cochange-groups (kBoolFlags), --cochange-recur= (kIntFlags); +1 --context-ratio (kBoolFlags row); +1 --nonlocal-state (kBoolFlags row); +2 --field-affinity (kBoolFlags) and --field-affinity= (kViewFlags); +1 --comment-coherence (kBoolFlags row); +2 --dmm (kBoolFlags) and --dmm= (kViewFlags); +2 --quality-panel (kBoolFlags) and --quality-panel= (kViewFlags); +1 --naming-consistency (kBoolFlags row); +1 --naming-locals (kBoolFlags row, local-variable-indexing plan Phase 2); +1 --skipped (kBoolFlags row, §P0.5d itemization); +1 --with-profile= (kViewFlags row, the --lint × #PROF_TSV heat join); +1 --color-by= (hand-written enum-value arm); +1 --sarif (kBoolFlags row, W1-SARIF: SARIF 2.1.0 export for --lint); +1 --signatures-only (kBoolFlags row, T3 terminal-by-default --for opt-out); +3 L7: --lint-catalog (kBoolFlags), --lint-select= and --lint-ignore= (kViewFlags); +3 G3 (2026-08-15 harvest): --and=/--not=/--grep-scope= (hand-written arms); +1 R-H: --grep-in= (hand-written arm); +1 R2: --pattern= (kViewFlags row, the code-shaped structural search); +1 lane/safe-delete (2026-08-21): --safe-delete= (kViewFlags row, the composed "can I delete this?" read); +1 lane/compact-conceptual (2026-08-22): --auto-bodies (kBoolFlags row, the compact-conceptual-serving opt-out); +5 CLI edit bridge (2026-08-27): --replace-symbol-body=/--insert-before-symbol=/--insert-after-symbol=/--edit-payload=/--edit-target-file= (kViewFlags rows); +1 --handles (kBoolFlags row, grep edit handles); +1 --legend= (kViewFlags row, compact schema dialect); +3 edit-plan: --edit-plan= (kViewFlags) and --dry-run/--apply (kBoolFlags rows); +1 --agent= (kViewFlags row, the --doctor Codex surface); +1 lane/paper-slice (2026-08-28): --slice= (kViewFlags row, the ARISE-motivated def-use slice); +1 lane/af-planlint (2026-08-29): --plan-lint= (kViewFlags row, the PLAN-format structure gate, P3.2); +2 lane/or-arise (2026-08-30): --slice-flow= (kViewFlags row) and --slice-depth= (kIntFlags row) — the ARISE rung-2 cross-statement data-flow slice; +1 lane/at-seed (2026-08-30): --at= (kViewFlags row) — the FILE:LINE enclosing-chain report, with the @FILE:LINE selector spelling resolved in graph.h (no flag arm of its own); +1 CARD-1 phase 2 (2026-08-31): --pin-census= (kViewFlags row) — the eval-only S6-C silent-pin census, written beside the map and never into it static_assert( std::size( kBoolFlags ) + std::size( kViewFlags ) + std::size( kIntFlags ) + kHandWrittenFlagArms == kTotalFlagArms, "a --flag arm was added or removed without updating the ledger above — count the arms in parseArgs and fix the counter" ); diff --git a/src/compactlegend.h b/src/compactlegend.h index 07168470e..3c490e7d1 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -428,9 +428,11 @@ inline constexpr CompactCompletenessTerm kCompactCompletenessTerms[] = // The map scope's code-first pick (serialize.h codeFirstKeep): the Sections it swapped out. Present-only. { "data_sections_cut", "data_sections_cut=N: N data Sections (headings, data keys) swapped out of this top-K for lower-ranked code rows; next= pages them first" }, // D7 (mapinventory.h): the map scope's inventory tier, ELEMENT-qualified (p=/n=/f= mean other things on other rows). - { "listed", ": files the ranked rows show / do not (sum = files=); each unshown file is named or counted below, names only", true, "inv" }, - { "n", ": a program entry by name (main called only by its own module scope, or __main__.py), callees as c rows; manifest-only entries not detected", true, "entry" }, - { "n", ": n= unshown files under dir p=, f= its code files by name; test/doc/config files and test/doc/example dirs are counted, not named", true, "ls" }, + { "listed", ": files the ranked rows show / do not (sum = files=), each named or counted below", true, "inv" }, + { "n", ": a program entry by name (a main only its own module scope calls, or __main__.py), callees as c rows; manifest entries not read", true, "entry" }, + { "n", ": n= unshown files under dir p=; f= names its code files, the rest (tests, docs, config, test/doc/example dirs) counted", true, "ls" }, + { "entry_total", "inv entry_total=N: N entries, the first 32 by path shown", true, "inv" }, + { "names_cut", "inv names_cut=N: N code files left unnamed past the 2000-name ceiling (later dirs n= only)", true, "inv" }, { "pr_converged", "pr_converged=0: iteration cap hit before convergence" }, // Form-conditional map roots whose clauses (kRankByDisclosure, kChurnRankLegend, --around's seed block) are prose. // window= and defs= are ELEMENT-qualified: --hotspots carries window= and --callers defs=, each meaning something else. diff --git a/src/main.cpp b/src/main.cpp index 66b6df2d1..c1310b69f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1872,7 +1872,7 @@ int runDefaultMap( const MainDispatch& d ) mapAnn.viaLegendStripped = cfg.legend == "compact" && !cfg.json; // FE-B: the rewrite would strip the map's via comment mapAnn.notesDegraded = d.notesDegraded; // L3 follow-up (CodeRabbit 4053600616): onto every this run emits mapAnn.codeFirstRows = isDefaultMapScope; // the code-first row pick + its data_sections_cut= / next= (serialize.h) - mapAnn.inventoryTier = isDefaultMapScope; // D7: the tier (serialize.h mapInventoryWanted drops it under --max-tokens) + mapAnn.inventoryTier = isDefaultMapScope && !cfg.noInventory; // D7: the tier (mapInventoryWanted also drops it under --max-tokens) // C1-b (2026-09-12): --in=DIR — the scoped block and the map stub, filled by assignment like seed. The two next= strings // outlive every serialize() call below (mapAnn holds views into them). The scoped next= is the SAME run at the next // offset, page size carried when the caller set one; the stub's next= is the same run without in= (the map it stubbed). @@ -3079,7 +3079,7 @@ inline constexpr std::string_view kJsonVerbs[] = // that itself refuses --json is deliberately NOT here — its verb is named first, which is the answer. inline constexpr std::string_view kJsonRideAlongFlags[] = { - "--json", "--ignore-tests", "--no-cache", "--no-ignore", "--no-stable", "--refetch", "--compress", "--no-redact", + "--json", "--ignore-tests", "--no-cache", "--no-ignore", "--no-stable", "--no-inventory", "--refetch", "--compress", "--no-redact", "--cache", "--since", "--pin-census", "--scip", "--legend", "--anchor", "--no-route", "--adaptive", "--no-mention-boost", "--cochange-boost", "--no-doc-mention", "--signatures-only", "--auto-bodies", "--with-graph", "--scope", "--task", "--brief", @@ -3119,7 +3119,7 @@ std::string_view firstFlagOutside( const rw::Config& c, std::span groups) std::size_t unlisted = 0; // every other indexed file; listed + unlisted = files= - std::vector entries; // path order, then symbol id + std::vector entries; // path order, then symbol id; at most kInventoryEntryCap + std::size_t entryTotal = 0; // every entry found (entries.size() < entryTotal ⇒ cut, disclosed) std::vector dirs; // path order + std::size_t namesCut = 0; // code files counted but not named: the name ceiling was reached }; +// RUNAWAY GUARDS (PROCESS rule 5a), far above any measured answer: the eleven orient repos of the round-1 table name at +// most 431 files (textual) and list at most 6 entries (gotestsum), so neither guard fires on them. A monorepo of +// thousands of unshown code files would otherwise print every name (~13 B each). Past the ceiling, directories in path +// order keep n= and drop f= (names_cut= on says how many files that left unnamed; ripwire on p= ranks them); +// past the entry cap, entry_total= says how many entries exist. +inline constexpr std::size_t kInventoryNameCeiling = 2000; +inline constexpr std::size_t kInventoryEntryCap = 32; + // Directory components whose subtree rolls up count-only. pathTierOf's test/bench/fixture conventions plus the // presentational ones (examples, docs, regress, samples) — local to the inventory on purpose: widening pathTierOf // itself would re-tier --grep/--callers ordering on every verb. @@ -179,6 +189,11 @@ inline MapInventory computeMapInventory( const IngestResult& ing, const std::vec } std::stable_sort( inv.entries.begin(), inv.entries.end(), [ & ]( NodeId a, NodeId b ) { return pathRel( ing.symbols[ a ].fileId ) < pathRel( ing.symbols[ b ].fileId ); } ); + inv.entryTotal = inv.entries.size(); + if( inv.entries.size() > kInventoryEntryCap ) + { + inv.entries.resize( kInventoryEntryCap ); + } // ── ls rows ── std::vector unshown; @@ -220,6 +235,20 @@ inline MapInventory computeMapInventory( const IngestResult& ing, const std::vec d.named.push_back( k.f ); } } + // the name ceiling: whole directories, path order — a directory is named completely or not at all, so f= is never a + // silent part-list + std::size_t namedSoFar = 0; + for( MapInventoryDir& d : inv.dirs ) + { + if( namedSoFar + d.named.size() > kInventoryNameCeiling ) + { + inv.namesCut += d.named.size(); + d.named.clear(); + namedSoFar = kInventoryNameCeiling; // every later directory is count-only too: one contiguous cut + continue; + } + namedSoFar += d.named.size(); + } ENSURES( inv.listed + inv.unlisted == F, "every indexed file is listed or inventoried" ); return inv; } @@ -232,6 +261,8 @@ inline constexpr std::string_view kMapInventoryLegend = "or a __main__.py module scope, with its resolved callees as c rows; entries declared only in a manifest (package.json " "main/bin, console_scripts) are not detected. ls p= n= f=: n= unshown files under directory p=, f= the code files " "among them by name; test, doc and config files are counted, not named, and a test/bench/fixture/example/doc " - "directory rolls up whole. ripwire on p= ranks that directory -->"; + "directory rolls up whole. ripwire on p= ranks that directory. Runaway guards: entry_total=N when more than 32 " + "entries exist (the first 32 by path shown); names_cut=N code files left unnamed past a 2000 name ceiling " + "(directories after it, path order, keep n= only) -->"; } // namespace rw diff --git a/src/serialize.h b/src/serialize.h index 713b0ca9f..e2d08ca5d 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -2769,7 +2769,10 @@ inline void writeMapInventoryXml( XmlWriter& w, const IngestResult& ing, const M { EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( "" ); + w.write( "\" unlisted=\"" ); w.write( std::to_string( inv.unlisted ) ); w.write( "\"" ); + if( inv.entryTotal > inv.entries.size() ) { w.write( " entry_total=\"" ); w.write( std::to_string( inv.entryTotal ) ); w.write( "\"" ); } + if( inv.namesCut > 0 ) { w.write( " names_cut=\"" ); w.write( std::to_string( inv.namesCut ) ); w.write( "\"" ); } + w.write( ">" ); for( NodeId id : inv.entries ) { const Symbol& s = ing.symbols[ id ]; @@ -9529,6 +9532,8 @@ inline void writeMapInventoryJson( JsonWriter& w, const IngestResult& ing, const EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( ",\"inv\":{\"listed\":" ); w.write( std::to_string( inv.listed ) ); w.write( ",\"unlisted\":" ); w.write( std::to_string( inv.unlisted ) ); + if( inv.entryTotal > inv.entries.size() ) { w.write( ",\"entry_total\":" ); w.write( std::to_string( inv.entryTotal ) ); } + if( inv.namesCut > 0 ) { w.write( ",\"names_cut\":" ); w.write( std::to_string( inv.namesCut ) ); } w.write( ",\"entry\":[" ); for( std::size_t i = 0; i < inv.entries.size(); ++i ) { diff --git a/test/estchargecheck.sh b/test/estchargecheck.sh index 05117459d..a26d611c1 100755 --- a/test/estchargecheck.sh +++ b/test/estchargecheck.sh @@ -127,7 +127,23 @@ for entry in "${SHAPES[@]}"; do done # ── #2: MONOTONE charging — adding a payload to the same map can never LOWER the reported estimate. ──── -E_MAP="$( est_of "$TMP/map.out" )" +# D7 (2026-10-08, by-design break, PROCESS rule 4): the bare map now carries the inventory tier (, the files its +# ranked rows leave out), and a payload verb's ride-along map does not — so "the same map" is the bare map WITHOUT that +# tier. --no-inventory is that map (the same rows, no tier; #2-rows proves both premises). The old comparison is kept +# as #2-tier: the bare map with its tier must charge at least what the rows alone do. +"$BIN" src --top-k=10 --no-inventory --no-cache >"$TMP/map_rows.out" 2>/dev/null +E_MAP="$( est_of "$TMP/map_rows.out" )" +E_TIER="$( est_of "$TMP/map.out" )" +rows_of(){ sed 's/ (est $E_MAP)" +else + no "#2-rows premise broken: the rows-only twin is not the bare map minus its tier (est ${E_MAP:-?}) — #2/#3 compare against the wrong map" +fi +[ -n "$E_TIER" ] && [ -n "$E_MAP" ] && [ "$E_TIER" -ge "$E_MAP" ] 2>/dev/null \ + && ok "#2-tier: the bare map with its inventory reports est_tokens=$E_TIER >= the rows alone ($E_MAP)" \ + || no "#2-tier: the bare map with its inventory reports ${E_TIER:-?} < the rows alone (${E_MAP:-?}) — the tier is uncharged" mono=1 for label in metrics sigs src bodies outline "bodies+sigs"; do E="$( est_of "$TMP/$label.out" )" @@ -141,7 +157,8 @@ done # streamed 12 KB / 67 KB. Budget is set from the bare map's own estimate (so the map alone always # fits) and each payload arm must fail it. ──────────────────────────────────────────────────────────── TB=$(( E_MAP + 20 )) -"$BIN" src --top-k=10 --token-budget=$TB --no-cache >"$TMP/tb_map.out" 2>"$TMP/tb_map.err" +# D7: the control is the rows-only twin above (the bare map's tier is charged and need not fit this budget) +"$BIN" src --top-k=10 --no-inventory --token-budget=$TB --no-cache >"$TMP/tb_map.out" 2>"$TMP/tb_map.err" rc_map=$? [ "$rc_map" -eq 0 ] \ && ok "#3 control: the bare map fits --token-budget=$TB (exit 0)" \ diff --git a/test/mapinventorycheck.sh b/test/mapinventorycheck.sh index 59cfac986..4e44b05a9 100755 --- a/test/mapinventorycheck.sh +++ b/test/mapinventorycheck.sh @@ -23,6 +23,7 @@ FAILED=0 fail() { printf ' FAIL %s\n' "$*"; FAILED=$(( FAILED + 1 )); } ok() { printf ' PASS %s\n' "$*" || { FAILED=$(( FAILED + 1 )); printf ' FAIL could not write the PASS line for: %s\n' "$*"; }; return 0; } [ -x "$BIN" ] || { echo "mapinventorycheck: no binary at $BIN — build first"; exit 2; } +echo "mapinventorycheck: BIN=$BIN" cd "$ROOT" || exit 2 FIX=test/mapinvfix [ -d "$FIX" ] || { echo "mapinventorycheck: fixture $FIX missing"; exit 2; } @@ -37,10 +38,11 @@ run --top-k=3 --max-tokens=400 >"$TMP/maxtok" run --top-k=3 --expand=run >"$TMP/expand" run --top-k=3 --for="where does main start" >"$TMP/for" run --top-k=3 --stable >"$TMP/stable" +run --top-k=3 --no-inventory --json >"$TMP/noinvjson" run --top-k=3 >"$TMP/map2" # The tier, isolated (one line of minified XML). -INV="$( grep -o '' "$TMP/map" )" +INV="$( grep -o '"; else fail "(A) no on a map whose top-k=3 dropped 15 of 18 files"; fi @@ -91,12 +93,12 @@ done if [ "$DUP" = 0 ]; then ok "(G2) no shown file is named again"; else fail "(G2) a file is both a ranked group and an name"; fi # (H) present-only: a map that shows every file carries no tier (small maps stay byte-identical) -if grep -q ' on a map that shows every file"; else ok "(H) no when the ranked rows show every file"; fi +if grep -q ' on a map that shows every file"; else ok "(H) no when the ranked rows show every file"; fi # (I) scope: never under --max-tokens (its fit is a binary search on top-k; the tier would make it non-monotone), # never on a payload verb's ride-along map -for f in maxtok expand for; do - if grep -q ' rides the $f answer"; else ok "(I) no on the $f answer"; fi +for f in maxtok expand for noinvjson; do + if grep -q ' rides the $f answer"; else ok "(I) no on the $f answer"; fi done # (J) the JSON twin carries the same decision @@ -120,7 +122,38 @@ for f in map full stable; do if wf "$TMP/$f"; then ok "(L) $f is well-formed"; e # (M) determinism, and --stable keeps the tier (path order is already stable) if cmp -s "$TMP/map" "$TMP/map2"; then ok "(M) two runs byte-identical"; else fail "(M) non-deterministic"; fi -if grep -q '// +# sequence the tiered map does, and nothing of the tier (element or legend clause) +rows_of(){ sed 's/]*>(]*/>)*' | tr '\n' ' '; } +run --top-k=3 --no-inventory >"$TMP/rowsonly" +run --top-k=3 --no-inventory --legend=full >"$TMP/rowsonly_full" +if ! grep -q '"$TMP/last" +if grep -q '">.*' "$TMP/last"; then ok "(O) --most-important-last: precedes the rows"; else fail "(O) --most-important-last: is not before the ranked rows"; fi + +# (P) runaway guards, each at the boundary and one past it (generated corpora; top-k=1 shows one file) +gen_c(){ d="$1"; n="$2"; mkdir -p "$d/src"; i=0; while [ "$i" -lt "$n" ]; do printf 'int fn%d( void ) { return %d; }\n' "$i" "$i" >"$d/src/f$i.c"; i=$(( i + 1 )); done; } +gen_go(){ d="$1"; n="$2"; i=0; while [ "$i" -lt "$n" ]; do mkdir -p "$d/cmd/p$i"; printf 'package main\n\nfunc main() {}\n' >"$d/cmd/p$i/main.go"; i=$(( i + 1 )); done; } +gen_c "$TMP/c2001" 2001; gen_c "$TMP/c2002" 2002 +gen_go "$TMP/g32" 32; gen_c "$TMP/g32" 2; gen_go "$TMP/g33" 33; gen_c "$TMP/g33" 2 +"$BIN" "$TMP/c2001" --no-cache --top-k=1 >"$TMP/c2001.out" 2>/dev/null +"$BIN" "$TMP/c2002" --no-cache --top-k=1 >"$TMP/c2002.out" 2>/dev/null +"$BIN" "$TMP/g32" --no-cache --top-k=1 >"$TMP/g32.out" 2>/dev/null +"$BIN" "$TMP/g33" --no-cache --top-k=1 >"$TMP/g33.out" 2>/dev/null +NC1="$( grep -o '' "$TMP/c2002.out" && grep -q '' "$TMP/c2002.out"; then ok "(P) 2001 past the ceiling: the dir keeps n= only, names_cut=2001"; else fail "(P) past the ceiling: $( grep -o ']*>' "$TMP/c2002.out" ) $( grep -o '= 12 gate configurations thrashed the 2 GiB sweep); the retry design keeps ONE superset blob per root and will bring its own gate -for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agentsurfacecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck astqueryregexcheck astrocheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck buildtypestampcheck builtinbindcheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachereservecheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck ceilingverdictcheck chacheck chaconecheck chainguardcheck chainidcheck childwalkscalecheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck commenttokencheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck countfloorcheck cppbenchcheck cppoperatorcheck cppqualcheck cpptmplscopecheck crashsweepcheck crawlescapecheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck declinecheck declinedlistcheck decltodefcheck deeptailcheck defaultceilingcheck defectshapecheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck diagnoticecheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editcheckdeclcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck elixirnamearitycheck elixirsemanticcheck emitescapecheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck enumtablecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandbodyfirstcheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check extentcheck externalvetocheck falseedgecheck fficheck fieldaffinitycheck fieldidcheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnliteralcheck fnptrcheck forautobodycheck forblowupcheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forhdrshapecheck forlenscheck formatgatecheck formaxtokenscheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forsectioncollapsecheck forsigspancheck forwidencheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck gdscriptcheck genrecallcheck gitenvhermeticcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck gokindcheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck hazardpatterncheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactdepthcheck impactimportcheck impactpartitioncheck importcapcheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javamethodrefcheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck jsxcallcheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legendrefcheck legobundlecheck legocheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck listingpagingcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck macroreparsecheck manifestcheck mapdatasectioncheck mapdiffcheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadasancheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstdiolinecapcheck mcpstrictschemacheck mcptoolprunecheck mcptoolsubsetcheck mcptranchecheck mcptwinclaimscheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck memguardcheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck noaliascheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck notesdegradecheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck osswitchcheck oswin32logiccheck outlinecheck overbudgetcommentcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck pathgapcheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck ppdeadrolescheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck preprocdeadscalecheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pymodulealiascheck pyshapecheck qackconcurrencycheck qackorigincheck qbaselineproducercheck qchurncheck qchurnmemocheck qddialscheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qsnapproducercheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck recentscopecheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexguardcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck rootspellingcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyargcheck rubyattrscheck rubybarecallcheck rubyclassrecvcheck rubyconstcheck rubydeclrefcheck rubydescribedclasscheck rubyinheritcheck rubymetricscheck rubyrakejbuildercheck rubyreachcheck rubyrecvcheck rubyrecvnarrowcheck rubyrequirecheck rubyrspectargetcheck rubyscopecheck rubysettercheck rubytypedrecvcheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck scroundtripcheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcheckcheck selfcontainedcheck shadowcheck shallowhistorycheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sidecarsymlinkcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck situshapecheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skipclassifycheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck stdqualcheck strkerncheck structlayoutcheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck tempfilesymlinkcheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck traceasanlinearcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do +for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agentsurfacecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck astqueryregexcheck astrocheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck buildtypestampcheck builtinbindcheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachereservecheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck ceilingverdictcheck chacheck chaconecheck chainguardcheck chainidcheck childwalkscalecheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck commenttokencheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck countfloorcheck cppbenchcheck cppoperatorcheck cppqualcheck cpptmplscopecheck crashsweepcheck crawlescapecheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck declinecheck declinedlistcheck decltodefcheck deeptailcheck defaultceilingcheck defectshapecheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck diagnoticecheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editcheckdeclcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck elixirnamearitycheck elixirsemanticcheck emitescapecheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck enumtablecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandbodyfirstcheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check extentcheck externalvetocheck falseedgecheck fficheck fieldaffinitycheck fieldidcheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnliteralcheck fnptrcheck forautobodycheck forblowupcheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forhdrshapecheck forlenscheck formatgatecheck formaxtokenscheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck forsectioncollapsecheck forsigspancheck forwidencheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck gdscriptcheck genrecallcheck gitenvhermeticcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck gokindcheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck hazardpatterncheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactdepthcheck impactimportcheck impactpartitioncheck importcapcheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javamethodrefcheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck jsxcallcheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legendrefcheck legobundlecheck legocheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck listingpagingcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck macroreparsecheck manifestcheck mapdatasectioncheck mapdiffcheck mapinventorycheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadasancheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstdiolinecapcheck mcpstrictschemacheck mcptoolprunecheck mcptoolsubsetcheck mcptranchecheck mcptwinclaimscheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck memguardcheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck noaliascheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck notesdegradecheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck osswitchcheck oswin32logiccheck outlinecheck overbudgetcommentcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck pathgapcheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck ppdeadrolescheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck preprocdeadscalecheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pymodulealiascheck pyshapecheck qackconcurrencycheck qackorigincheck qbaselineproducercheck qchurncheck qchurnmemocheck qddialscheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qsnapproducercheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recallshapecheck recalltablecheck recalltotalcheck receiptpostcheck receiverevidencecheck recentscopecheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexguardcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck rootspellingcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyargcheck rubyattrscheck rubybarecallcheck rubyclassrecvcheck rubyconstcheck rubydeclrefcheck rubydescribedclasscheck rubyinheritcheck rubymetricscheck rubyrakejbuildercheck rubyreachcheck rubyrecvcheck rubyrecvnarrowcheck rubyrequirecheck rubyrspectargetcheck rubyscopecheck rubysettercheck rubytypedrecvcheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck scroundtripcheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcheckcheck selfcontainedcheck shadowcheck shallowhistorycheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sidecarsymlinkcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck situshapecheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skipclassifycheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck stdqualcheck strkerncheck structlayoutcheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck tempfilesymlinkcheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck traceasanlinearcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do [ -f "$ROOT/test/$_g.sh" ] || continue if RIPWIRE_BIN="$BIN" bash "$ROOT/test/$_g.sh" >/dev/null 2>&1; then ok "absorb gate ($_g.sh)" From 57330483dcc9a84572e0c863fd10b3811909382d Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:00:13 -0400 Subject: [PATCH 071/170] fix(crossref): the cross-branch verbs refuse when the blob batch cannot run (K75) --stray-content, --abi, --plan, --eval-stray and the MCP stray_content tool read every branch's content through one `git cat-file --batch` whose sha list is written under the cache directory. With TMPDIR naming a missing directory the list could not be written, every blob read as absent, and the answer was merged="1" for a branch holding unmerged work (--abi: exit 0, no drift). The failure was recorded in StreamBlobStats, which BlobStore and the abi sweep never read. BlobStore now keeps those stats and says unread(); the abi sweep reads its own. computeStrayContent and computeAbiCheck return ok=false with blobsUnread, and each host refuses by name: exit 1 and the cause (naming TMPDIR) on stderr, an MCP -32603, no document. The mechanism is the --whereis refusal's. Sweep of every other cache/temp writer behind an answer (TMPDIR missing vs good, --legend=full, 38 verbs): --merge-scout (arm ok="0" reason=tree_unavailable), --quality-delta REF (exit 1 naming the tree), --edit-check (status="no-baseline"), --edit-check --dry-run (refuses by name), --doctor (the cache row) and the compact-legend capture (stderr note, same answer) already disclose; slice-diff's temp root has a sink. --whereis has the same defect and its fix is on lane/cli-perf-068, not in this base. Gate: crossrefdegradecheck.sh K75 arms (red on the base binary: 5 failures, green here), with a control that the same repo answers on a good TMPDIR. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 8 ++++++ src/abicheck.h | 10 ++++++- src/crossref.h | 29 +++++++++++++++++-- src/landingplan.h | 2 ++ src/mcp.h | 8 +++++- src/mcpverbs.h | 3 +- src/verbs_change.h | 24 ++++++++++++++++ test/crossrefdegradecheck.sh | 56 ++++++++++++++++++++++++++++++++++++ 8 files changed, 135 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4441c7a12..8508c013f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Fixed — the cross-branch verbs refuse when the blob batch cannot run + +`--stray-content`, `--stray-content --abi`, `--stray-content --plan`, `--eval-stray` and the MCP `stray_content` tool read +every branch's content through one `git cat-file --batch` whose sha list is written under the cache directory. With `TMPDIR` +naming a directory that does not exist the list could not be written, every blob read as absent, and the answer was +`merged="1"` for a branch holding unmerged work (`--abi`: exit 0, no drift). Each now exits 1 (MCP: `-32603`) naming the +cause and `TMPDIR`, with nothing on stdout, the way `--whereis` refuses on the same condition. + ### Fixed — a git history walk that could not be read says so, and is retried A `git log --name-only` that failed (an unreadable object, a bad ref, git not starting) came back as an empty stream and was diff --git a/src/abicheck.h b/src/abicheck.h index 716e9ca9d..ddb4f541a 100644 --- a/src/abicheck.h +++ b/src/abicheck.h @@ -241,6 +241,7 @@ struct AbiResult bool nonGitRoot = false; bool tooManyRefs = false; bool filterMatchedNothing = false; // H7: the --stray-content=SUBSTR filter selected no ref NAME + bool blobsUnread = false; // K75: the blob batch both sides are modelled from never started or died part-way — refused std::string headSha; std::string headRef; std::string atStamp; // M10: gitstamp::stampAt(root) — head= above stays a bare 9-hex sha @@ -543,6 +544,7 @@ inline void modelRefBlobs( const std::string& root, layout::ModelCtx& ctx, const for( const auto& [ sha, paths ] : sw.shaPaths ) { (void)paths; shas.push_back( sha ); } result.distinctBlobs = shas.size(); + crossref::StreamBlobStats blobStats; // K75: a batch that never started leaves every slot present=false, which reads as "no break" crossref::streamBlobs( root, shas, [ & ]( const std::string& sha, std::string_view bytes, bool isText ) { const auto sit = sw.shaPaths.find( sha ); @@ -587,7 +589,8 @@ inline void modelRefBlobs( const std::string& root, layout::ModelCtx& ctx, const mit->second[ ci ].present = true; } } - } ); + }, &blobStats ); + result.blobsUnread = blobStats.startFailed || blobStats.endedEarly; } // ── pass 3: classify one (ref, path) site's candidates ─────────────────────────────────────────────────── @@ -739,6 +742,11 @@ inline AbiResult computeAbiCheck( const std::string& root, const IngestResult& i SweepState sw; collectAuthoredSites( root, refs, byPath, sw, Tally{ rows, result } ); // WHERE each ref's own work reaches modelRefBlobs( root, ctx, byPath, sw, result ); // one cat-file batch for both sides + if( result.blobsUnread ) + { + result.ok = false; // K75: refused, not answered — every slot would read present=false, i.e. "no break on any branch" + return result; + } const ClassifyCtx cc{ ctx, headCache, result }; for( const PathSite& ps : sw.pathSites ) diff --git a/src/crossref.h b/src/crossref.h index 30619b140..66498e387 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -677,9 +677,15 @@ class BlobStore } facts_.emplace( sha, std::move( f ) ); ++fetched_; - } ); + }, &stats_ ); } + // K75: a batch that never started (the sha list could not be written under the cache dir, git did not spawn) or died + // before serving every blob leaves the shas it never reached ABSENT from facts_, and get() answers an absent sha with a + // shared empty record — "this side contributed no lines". A caller that then reports verdicts from it prints a measured- + // looking answer made of nothing (a branch with unmerged work reading merged), so it asks this first and refuses. + bool unread() const noexcept { return stats_.startFailed || stats_.endedEarly; } + // The facts for `sha`, or a shared empty (non-text) record for an absent side — so a caller can treat // "this path did not exist on that side" as "it contributed no lines" without a null check. const BlobFacts& get( const std::string& sha ) const @@ -697,8 +703,17 @@ class BlobStore gtl::btree_map facts_; std::vector pending_; std::size_t fetched_ = 0; + StreamBlobStats stats_; }; +// K75: the words every host of a blob-reading verb refuses in when the one `git cat-file --batch` behind it did not +// serve every blob. The cause is named WHERE the reader looks (stderr + exit 1, or an MCP error), never as a zero. +inline std::string blobsUnreadCause() +{ + return "could not read the branches' blobs (the one `git cat-file --batch` did not start or stopped early; its sha list is written under " + + quality::cacheDirLadder() + " - is TMPDIR a writable directory?)"; +} + // ── git plumbing (read-only) ───────────────────────────────────────────────────────────────────────────── // Capture a git command's FULL multi-line stdout ("" on failure). This is exactly quality::gitOneLine's @@ -1068,6 +1083,9 @@ struct StrayResult // unknown="0" at exit 0 reads as "no branch carries stray work" — the most reassuring possible answer, // from a sweep that never happened. ok=false, so the caller refuses in the --doc-drift/--dead-code words. bool filterMatchedNothing = false; + // K75: the blob batch behind every verdict never started or died part-way (an unwritable cache dir, TMPDIR naming a missing + // directory, git failing): each ref's lines would read as absent and its verdict as merged. ok=false; the caller refuses. + bool blobsUnread = false; std::string filter; // H14/M6: the --stray-content=SUBSTR this sweep was narrowed by ("" = none) std::string headSha; std::string headRef; @@ -1359,6 +1377,12 @@ inline StrayResult computeStrayContent( const std::string& root, std::string_vie BlobStore blobs( root ); registerSweepBlobs( diffs, plumbing, blobs ); + if( blobs.unread() ) + { + result.ok = false; // refused, not answered: the caller names blobsUnreadCause() on stderr / as an MCP error + result.blobsUnread = true; + return result; + } // ── phase 5: the analysis proper — pure, serial, deterministic ────────────────────────────────────── // A ref whose work is entirely on the live line is the boring, common case — in a 30-branch sweep it is @@ -2359,6 +2383,7 @@ struct EvalReport std::vector badRefs; // labels naming a ref this git repo does not have at all — // refused, never scored (see evalStray below) bool ok = true; + bool blobsUnread = false; // K75: the sweep's blob batch was unread — refused, like a non-git root }; inline bool parseVerdict( std::string_view s, Verdict& out ) @@ -2388,7 +2413,7 @@ inline EvalReport evalStray( const std::string& root, const std::string& labelsP } const StrayResult res = computeStrayContent( root, {} ); - if( !res.ok ) { rep.ok = false; return rep; } + if( !res.ok ) { rep.ok = false; rep.blobsUnread = res.blobsUnread; return rep; } // Reported refs carry their verdict; every ref NOT reported was scanned and found merged (writeStrayContent // omits those), so an absent ref scores as `merged` rather than as a miss. diff --git a/src/landingplan.h b/src/landingplan.h index a69fbbda3..0069333de 100644 --- a/src/landingplan.h +++ b/src/landingplan.h @@ -73,6 +73,7 @@ struct PlanResult bool nonGitRoot = false; // --stray-content's own refusal reasons, passed through bool tooManyRefs = false; // verbatim — this verb adds no refusal of its own bool filterMatchedNothing = false; // H7: the filter selected no ref NAME — a refusal, never refs="0" + bool blobsUnread = false; // K75: the sweep's blob batch was unread — passed through, a refusal crossref::StrayResult stray; // the full sweep (headSha/refsScanned/mergedRefs/refs) std::vector scouted; // indices into stray.refs: the landing set fed to merge-scout std::vector bounded; // indices into stray.refs: unmerged, but cut by the size bound @@ -126,6 +127,7 @@ inline PlanResult computePlan( const std::string& root, std::string_view filter, result.nonGitRoot = result.stray.nonGitRoot; result.tooManyRefs = result.stray.tooManyRefs; result.filterMatchedNothing = result.stray.filterMatchedNothing; + result.blobsUnread = result.stray.blobsUnread; return result; } diff --git a/src/mcp.h b/src/mcp.h index 2407a0c72..cdd688323 100644 --- a/src/mcp.h +++ b/src/mcp.h @@ -2332,7 +2332,13 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo // `kind` doubles as the optional ref-name substring filter (no dedicated arg needed). resp = pagedResult( [ & ]( McpPageArgs pg ) // M13 { - const std::string t = strayContentText( path, kind, crossref::kStrayFilesPerRef, pg ); + bool blobFault = false; + const std::string t = strayContentText( path, kind, crossref::kStrayFilesPerRef, pg, &blobFault ); + if( blobFault ) + { + DISCLOSE( Diagnostics::answerRefused, "stray_content (MCP): the blob batch did not serve every blob - a -32603 error names it, no verdicts" ); + return errResultMsg( -32603, crossref::blobsUnreadCause() ); + } return t.empty() ? errResult( -32602, "not a git repository (or no HEAD commit) — no refs to compare" ) : textResult( t ); } ); } diff --git a/src/mcpverbs.h b/src/mcpverbs.h index dfdfa611b..77b825ff4 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -602,11 +602,12 @@ inline std::string whereisText( const std::string& root, const std::string& symb // M13: --stray-content is in cli.h's honorsPaging set — writeStrayContentPage is the entry point the // CLI already calls with cfg.pageLimit/cfg.pageOffset, so the twin passes the same pair rather than 0,0. inline std::string strayContentText( const std::string& root, const std::string& filter, std::size_t maxFiles, - McpPageArgs page = {} ) + McpPageArgs page = {}, bool* blobFault = nullptr ) { const crossref::StrayResult res = crossref::computeStrayContent( root, filter ); if( !res.ok ) { + if( res.blobsUnread && blobFault != nullptr ) { *blobFault = true; } // K75: the host answers an error naming the cause, not "not a git repository" return {}; } return captureXml( [ & ]( std::FILE* f ) { crossref::writeStrayContentPage( f, res, maxFiles, page.limit, page.offset ); } ); diff --git a/src/verbs_change.h b/src/verbs_change.h index b8d3afe04..fe8f6d689 100644 --- a/src/verbs_change.h +++ b/src/verbs_change.h @@ -688,6 +688,13 @@ inline constexpr int kRunTraceExitCommandFailed = 4; // H7 (capture-audit 2026-09-04; hosts added at the wave-3 close): the ONE sentence for a --stray-content=SUBSTR // filter that selected no ref NAME — spoken by the bare verb ("" host) and by its two hosts ("--plan: ", "--abi: "). // refs="0" at exit 0 would read as "no branch carries stray work", the most reassuring answer these verbs give. +// K75: the blob batch behind a cross-branch verb never started or died part-way. The refusal names the cause where the reader +// looks (stderr, exit 1) — the other answer was merged="1" / "no break" for every branch, a measured-looking zero made of nothing. +inline void printBlobsUnreadRefusal( const char* hostPrefix ) +{ + DISCLOSE( Diagnostics::answerRefused, "the blob batch behind the cross-branch sweep did not serve every blob, so no verdict is printed - stderr names the cause, exit 1" ); + rw::emitTo( stderr, "ripwire: {}{} - refusing rather than print verdicts nothing measured\n", hostPrefix, rw::crossref::blobsUnreadCause() ); +} inline void printStrayFilterNoMatch( const char* hostPrefix, std::string_view filter ) { rw::emitTo( stderr, "ripwire: {}--stray-content={} matches no local ref — a zero here would be a failure, not a " @@ -1416,6 +1423,10 @@ int runAbiCheck( const MainDispatch& d ) { printStrayFilterNoMatch( "--abi: ", d.cfg.strayFilter ); // H7, the host's spelling (wave-3 close) } + else if( result.blobsUnread ) + { + printBlobsUnreadRefusal( "--abi: " ); + } else { rw::emitTo( stderr, "ripwire: --abi: more than {} refs match — narrow it with --stray-content=SUBSTR\n", crossref::kMaxRefs ); @@ -1465,6 +1476,10 @@ std::optional runCrossRef( const MainDispatch& d ) { printStrayFilterNoMatch( "--plan: ", cfg.strayFilter ); // H7, the host's spelling (wave-3 close) } + else if( result.blobsUnread ) + { + printBlobsUnreadRefusal( "--plan: " ); + } else { rw::emitTo( stderr, "ripwire: --plan: more than {} refs match — narrow it with --stray-content=SUBSTR\n", crossref::kMaxRefs ); @@ -1499,6 +1514,10 @@ std::optional runCrossRef( const MainDispatch& d ) // branch carries stray work" — from a sweep that matched no branch NAME at all. printStrayFilterNoMatch( "", cfg.strayFilter ); } + else if( result.blobsUnread ) + { + printBlobsUnreadRefusal( "--stray-content: " ); // K75 + } else { rw::emitTo( stderr, "ripwire: --stray-content: more than {} refs match — narrow it with --stray-content=SUBSTR\n", crossref::kMaxRefs ); @@ -1517,6 +1536,11 @@ std::optional runCrossRef( const MainDispatch& d ) const crossref::EvalReport rep = crossref::evalStray( root, std::string( cfg.evalStray ) ); if( !rep.ok ) { + if( rep.blobsUnread ) + { + printBlobsUnreadRefusal( "--eval-stray: " ); // K75 + return 1; + } if( !rep.badRefs.empty() ) { // H13: the file read fine and this IS a git repo — the refusal is that one or more diff --git a/test/crossrefdegradecheck.sh b/test/crossrefdegradecheck.sh index f22196105..2923a569f 100755 --- a/test/crossrefdegradecheck.sh +++ b/test/crossrefdegradecheck.sh @@ -60,6 +60,62 @@ count_of(){ tr '<' '\n' | grep -v '^!--' | grep -c "^$1 "; } echo "crossrefdegradecheck: BIN=$BIN" +# ══ K75: --stray-content, --abi, --plan and --eval-stray REFUSE when the blob batch cannot run ═══════════════ +# +# Every cross-branch verdict is built from ONE `git cat-file --batch` whose sha list is written under the cache directory. With +# TMPDIR naming a directory that does not exist, that list cannot be written, the batch never starts, every blob reads as absent, +# and the answer was merged="1" refs="1" (a branch holding unmerged work), --abi exit 0 with no drift: a measured-looking zero made +# of nothing (the whereis hits="0" bug's siblings). The refusal is the whereis mechanism: exit 1, the cause named on stderr, no +# document on stdout. Arms: a control (the same repo answers when the directory exists, which proves the refusal is about the +# missing directory and not about the fixture), then the refusal on each verb and on the MCP twin. +K75="$TMP/k75repo"; mkdir -p "$K75" +k(){ git -C "$K75" "$@" >/dev/null 2>&1; } +k init -q -b main +k config commit.gpgsign false +printf 'int base( void )\n{\n return 1;\n}\n' > "$K75/a.c" +printf '#pragma once\nstruct Uniforms\n{\n float x;\n float y;\n};\nstatic_assert( sizeof( Uniforms ) == 8, "uniform GPU contract" );\n' > "$K75/shader.h" +k add -A; k commit -qm base +k checkout -qb side +printf 'int base( void )\n{\n return 1;\n}\n\nint sideOnlyThing( void )\n{\n return 42;\n}\n' > "$K75/a.c" +printf '#pragma once\nstruct Uniforms\n{\n float x;\n float y;\n float z;\n};\nstatic_assert( sizeof( Uniforms ) == 8, "uniform GPU contract" );\n' > "$K75/shader.h" +k commit -qam "side work" +k checkout -q main +printf 'int base( void )\n{\n return 1;\n}\n\nint mainWork( void )\n{\n return 7;\n}\n' > "$K75/a.c" +k commit -qam "live-line work" +K75GOOD="$TMP/k75tmp"; mkdir -p "$K75GOOD" +K75BAD="$TMP/k75-no-such-dir" # never created: TMPDIR names a directory that does not exist +printf 'side\tunmerged\n' > "$TMP/k75labels.tsv" +k75(){ local out="$1" tmpd="$2"; shift 2; env TMPDIR="$tmpd" "$BIN" "$K75" "$@" --no-cache --legend=full >"$out" 2>"$out.err"; } + +k75 "$TMP/k75_s_good.out" "$K75GOOD" --stray-content; rcS=$? +[ "$rcS" = 0 ] && grep -q 'v="unmerged"' "$TMP/k75_s_good.out" \ + && ok "K75 control: --stray-content answers the branch with work as unmerged when the cache dir works" \ + || no "K75 control: --stray-content (rc=$rcS) did not report v=\"unmerged\" - the refusal arms below would prove nothing" +k75 "$TMP/k75_a_good.out" "$K75GOOD" --stray-content --abi; rcA=$? +[ "$rcA" = 2 ] && ok "K75 control: --abi reports the break (exit 2) when the cache dir works" || no "K75 control: --abi exited $rcA, not 2" + +for v in "--stray-content" "--stray-content --abi" "--stray-content --plan" "--eval-stray=$TMP/k75labels.tsv"; do + # shellcheck disable=SC2086 + k75 "$TMP/k75_bad.out" "$K75BAD" $v; rcB=$? + if [ "$rcB" = 1 ] && [ ! -s "$TMP/k75_bad.out" ] && grep -q "could not read the branches' blobs" "$TMP/k75_bad.out.err" && grep -q 'TMPDIR' "$TMP/k75_bad.out.err"; then + ok "K75: $v with TMPDIR missing refuses (exit 1, no document, the cause and TMPDIR named on stderr)" + else + no "K75: $v with TMPDIR missing did not refuse (rc=$rcB, stdout $( wc -c <"$TMP/k75_bad.out" | tr -d ' ' ) bytes): $( grep -v 'math degraded\|compact legend' "$TMP/k75_bad.out.err" | head -c 200 )" + fi +done +printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \ + '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"stray_content","arguments":{"path":"'"$K75"'"}}}' \ + | env TMPDIR="$K75BAD" "$BIN" --mcp 2>/dev/null | tail -1 >"$TMP/k75_mcp.json" +grep -q '"code":-32603' "$TMP/k75_mcp.json" && grep -q "could not read the branches" "$TMP/k75_mcp.json" \ + && ok "K75: the MCP stray_content twin answers -32603 naming the cause, not a verdict or 'not a git repository'" \ + || no "K75: the MCP twin did not refuse with -32603: $( head -c 240 "$TMP/k75_mcp.json" )" +printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \ + '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"stray_content","arguments":{"path":"'"$K75"'"}}}' \ + | env TMPDIR="$K75GOOD" "$BIN" --mcp 2>/dev/null | tail -1 >"$TMP/k75_mcp_good.json" +grep -q 'unmerged' "$TMP/k75_mcp_good.json" && ! grep -q '"error"' "$TMP/k75_mcp_good.json" \ + && ok "K75 control: the MCP twin answers when the cache dir works" || no "K75 control: the MCP twin did not answer on a good TMPDIR" + + # ══ fixture A: a SHALLOW clone — the CI default, and the case that reported every branch as merged ═══════ # # `side` carries two lines of genuinely unmerged work. The shallow clone keeps one commit per ref, so the From e064e04647c90f38ea9bb154c5f42114009cf597 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:04:51 -0400 Subject: [PATCH 072/170] for: the qword legend says the name is case-folded with leading _ # $ @ dropped; the full dialect names the common-verb order The compact and full owner clauses said the row's name "equals" the question word; the match is on the folded name (_notify gets qword="notify", gateway matches "GATEWAY"). The full dialect now also states that a word on the short common-verb list (get, set, run, write, ...) ranks after the other owner words, the rule that decides which owners take the three rows. Co-Authored-By: Claude Opus 5.5 --- src/verbs_for.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/verbs_for.h b/src/verbs_for.h index 9941625ea..3ba40bdd5 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -789,12 +789,14 @@ inline constexpr std::string_view kForCompactLegendHops = // the ranked head, never a resolution), widens noedge= to the owners, and defines the two disclosures an owner cut carries. // What qword= does NOT mean: that the row is the only, or the true, implementation of the question's subject. inline constexpr std::string_view kForCompactLegendOwner = - "; h qword= its name equals that question word (by name, not resolved, not the only one), first, calls to 100 (capped=1 next= all); " + "; h qword= its name (case-folded, leading _#$@ dropped) equals that question word (by name, not resolved, not the only one), first, calls to 100 (capped=1 next= all); " "qword_cut= owners not so served, next= the first"; // owner-hop: the full dialect's reading of the same two attributes (present-only, after the clause above; no "--": a comment) inline constexpr std::string_view kForOwnerHopLegend = - "; hops row qword= the question word that row's NAME equals (by name in the ranked head, a lookup, not a resolution; it " - "does not mean the symbol is the only or the true implementation): such an owner row comes first and its calls child " + "; hops row qword= the question word that row's NAME equals (ASCII case-folded, leading _ # $ @ dropped; by name in the " + "ranked head, a lookup, not a resolution; it " + "does not mean the symbol is the only or the true implementation): such an owner row comes first (a word on a short " + "common-verb list such as get, set, run or write ranks after the other owner words) and its calls child " "lists up to 100 callee names (capped=1 with next= naming the full list when the guard or the budget cut it); an owner " "with no resolved callee is counted in noedge= like any ranked symbol. hops qword_cut=N: owners not served as a qword " "row (the three-row cap or the budget; one of the six ranked candidates still keeps its plain row), next= the first"; From c67a2648d1d1aaa9928c6de26c977653f33ba351 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:15:52 -0400 Subject: [PATCH 073/170] test(compactlegend): re-pin the map legend for the inventory readings, keep the tier-less twin at the old pin Co-Authored-By: Claude Opus 5.5 --- test/compactlegendcheck.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/compactlegendcheck.sh b/test/compactlegendcheck.sh index 7e575a652..8c857db2d 100755 --- a/test/compactlegendcheck.sh +++ b/test/compactlegendcheck.sh @@ -551,9 +551,14 @@ probeFor() # the defs page whenever it lists MORE definitions than the all page under the row cap, whatever its bytes. Rides only a # page that carries listing=; nothing else moved. The OLD path keeps its pin: the whereis-listing=all twin holds the # whole-list page's legend at 700 B (measured 692, unchanged). +# RE-PINNED 2026-10-08 (lane/map-inventory-d7, D7), measured on this gate's fixture: ripwire.map/v1 892->1129, pin +# 910->1140 (measured + 10 rounded up to 10). The --top-k=3 probe leaves out three of the fixture's six files, so the map +# now carries the inventory tier (, ; no on this fixture) and its two +# present-only readings (+237 B with separators). Nothing else moved. The OLD path keeps its pin: the --no-inventory twin +# after the universe loop holds the same probe without the tier at 910 B. PIN_TABLE=' ripwire.edit-check/v1 610 595 -ripwire.map/v1 910 892 +ripwire.map/v1 1140 1129 ripwire.map-diff/v1 900 885 ripwire.pack-signatures/v1 770 759 ripwire.metrics/v1 1040 1021 @@ -796,6 +801,18 @@ if [ "$rcWall" -eq 0 ] && [ "$( leg schema "$TMP/wall.c" )" = "ripwire.whereis/v else no "(U-TWIN) --whereis-listing=all compact PROSE legend is '${wallLb:-none}' B (pin 700; exit $rcWall): $( leg legend "$TMP/wall.c" | head -c 200 )" fi +# (U-TWIN) the map without its inventory tier keeps the pre-D7 pin: the --top-k=3 probe's pin rose to 1140 B for the +# readings, and without this twin the rest of the map legend could grow inside that headroom unseen. --no-inventory serves +# the same ranked rows with no tier (mapinventorycheck (N)). 910 = the pre-D7 pin (measured 892). +( cd "$REPO" && "$BIN" . --top-k=3 --no-inventory >"$TMP/mnoinv.c" 2>/dev/null "$TMP/mnoinv.f" 2>/dev/null /dev/null && [ "$mniLb" -le 910 ]; then + ok "(U-TWIN) --top-k=3 --no-inventory compact PROSE legend is $mniLb B (<= the pre-D7 910 B pin; the tiered probe's pin is 1140)" +else + no "(U-TWIN) --top-k=3 --no-inventory compact PROSE legend is '${mniLb:-none}' B (pin 910; exit $rcMni): $( leg legend "$TMP/mnoinv.c" | head -c 200 )" +fi echo echo "=== (F) the DEFAULT is the default posture; --legend=full restores MORE legend over the SAME payload (L1) ===" From e0b0f83cd46a6dd5044ae24846c58f4e394fb153 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:34:41 -0400 Subject: [PATCH 074/170] test: the K51/K75 arms spell their verdicts on wrapped lines (gateexitcheck G2) Co-Authored-By: Claude Sonnet 5.5 --- test/crossrefdegradecheck.sh | 4 +++- test/qchurncheck.sh | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/test/crossrefdegradecheck.sh b/test/crossrefdegradecheck.sh index 2923a569f..27ee4ca30 100755 --- a/test/crossrefdegradecheck.sh +++ b/test/crossrefdegradecheck.sh @@ -92,7 +92,9 @@ k75 "$TMP/k75_s_good.out" "$K75GOOD" --stray-content; rcS=$? && ok "K75 control: --stray-content answers the branch with work as unmerged when the cache dir works" \ || no "K75 control: --stray-content (rc=$rcS) did not report v=\"unmerged\" - the refusal arms below would prove nothing" k75 "$TMP/k75_a_good.out" "$K75GOOD" --stray-content --abi; rcA=$? -[ "$rcA" = 2 ] && ok "K75 control: --abi reports the break (exit 2) when the cache dir works" || no "K75 control: --abi exited $rcA, not 2" +[ "$rcA" = 2 ] \ + && ok "K75 control: --abi reports the break (exit 2) when the cache dir works" \ + || no "K75 control: --abi exited $rcA, not 2" for v in "--stray-content" "--stray-content --abi" "--stray-content --plan" "--eval-stray=$TMP/k75labels.tsv"; do # shellcheck disable=SC2086 diff --git a/test/qchurncheck.sh b/test/qchurncheck.sh index 3ef573e50..e7b9cc619 100755 --- a/test/qchurncheck.sh +++ b/test/qchurncheck.sh @@ -145,11 +145,17 @@ grep -q ' churn="' "$TMP/k_for.out" \ && no "K51: a failed walk still printed a churn= value" \ || ok "K51: no churn= value is printed from a failed walk" krun "$TMP/k_map.out" --metrics -grep -q ' history_unread="1"' "$TMP/k_map.out" && ok "K51: --metrics (the map) says history_unread=\"1\"" || no "K51: --metrics on a failed walk is silent" +grep -q ' history_unread="1"' "$TMP/k_map.out" \ + && ok "K51: --metrics (the map) says history_unread=\"1\"" \ + || no "K51: --metrics on a failed walk is silent" krun "$TMP/k_json.out" --for=helper --json -grep -q '"history_unread":true' "$TMP/k_json.out" && ok "K51: --for --json says \"history_unread\":true" || no "K51: --for --json on a failed walk is silent" +grep -q '"history_unread":true' "$TMP/k_json.out" \ + && ok "K51: --for --json says \"history_unread\":true" \ + || no "K51: --for --json on a failed walk is silent" krun "$TMP/k_grep.out" --grep=helper --metrics -grep -q ' history_unread="1"' "$TMP/k_grep.out" && ok "K51: --grep --metrics says history_unread=\"1\"" || no "K51: --grep --metrics on a failed walk is silent" +grep -q ' history_unread="1"' "$TMP/k_grep.out" \ + && ok "K51: --grep --metrics says history_unread=\"1\"" \ + || no "K51: --grep --metrics on a failed walk is silent" krun "$TMP/k_full.out" --for=helper --legend=full grep -q 'history_unread=1: the git history walk' "$TMP/k_full.out" \ && ok "K51: the attribute is defined beside its root (a comment that reads it)" || no "K51: history_unread= is not defined in the document" @@ -162,7 +168,9 @@ run "$TMP/k_retry.log" --for=helper --no-cache >"$TMP/k_retry.out" 2>/dev/null [ "$( name_only_count "$TMP/k_retry.log" )" -ge 1 ] && grep -q ' churn="[0-9]' "$TMP/k_retry.out" && ! grep -q 'history_unread' "$TMP/k_retry.out" \ && ok "K51: the next call RETRIES the walk, prints churn=, and carries no disclosure" \ || no "K51: the call after a failed walk did not retry cleanly (walks=$( name_only_count "$TMP/k_retry.log" )): $( head -c 200 "$TMP/k_retry.out" )" -[ "$( nqchurn )" -gt "$kbefore" ] && ok "K51: the successful retry IS cached" || no "K51: the successful retry wrote no blob" +[ "$( nqchurn )" -gt "$kbefore" ] \ + && ok "K51: the successful retry IS cached" \ + || no "K51: the successful retry wrote no blob" # the REAL failure: remove the parent commit's object — HEAD still resolves, the walk dies part-way printf '// k51b\n' >> "$REPO/src/lib.cpp" From 6a7ac06b9300fe0d12e79f2bf64208d2bfaa4610 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:50:21 -0400 Subject: [PATCH 075/170] feat(expand): --expand/--outline ship no ride-along map when every definition is served A multi-definition name (and a single-definition --outline) carried the generic top-200 map beside bodies that already answered the request, and the inflated bundle flipped the whole-file comparison. When every requested definition is served the map now defaults to top-k=0: the root carries topk_default and a map_next pointer; when a byte budget cut some definitions the root names exactly those (unserved_total, unserved_next) instead. An explicit --top-k keeps the classic shape. The whole-file comparison prices the payload only. Gates: expandtopk0check gains the multi-def, bodies-cut, whole-file pricing, outline and near-miss arms (its explicit-top-k and ordinary-default twins kept); expandbodyfirstcheck and expandmodecheck (4d) reach the ride-along map through a composed payload now that the plain multi-def shape is lean. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 12 +++ src/cli.h | 10 +- src/compactlegend.h | 8 ++ src/main.cpp | 136 +++++++++++++++++++++++++-- src/serialize.h | 8 +- test/expandbodyfirstcheck.sh | 16 ++-- test/expandmodecheck.sh | 25 +++-- test/expandtopk0check.sh | 173 ++++++++++++++++++++++++++++++++--- 8 files changed, 345 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aae4dbd93..c3a4dd7bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,18 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Changed — `--expand` / `--outline` ship no ride-along map when every definition they name is served + +`--expand=NAME` on a name with several definitions, and `--outline=NAME`, used to carry the generic top-200 ranked map +beside bodies that already answered the request (on this repository `--expand=subtokens`: 26,752 B, of which 19,444 B +was map rows that never mention the symbol; `--expand=emitTo` served the whole 24,788 B file because the whole-file +comparison priced the bundle with that map). When every requested definition is served the map now defaults to +`top-k=0`: the root carries `topk_default="0"` and `map_next="ripwire ROOT"` (the call that prints the map, for a session +that skipped orienting). When a byte budget cut some definitions the root names exactly those in `unserved_total=N` and +`unserved_next="--expand=FILE:LINE:NAME,..."` instead of shipping the map. The whole-file comparison therefore prices the +payload only. An explicit `--top-k=N` keeps the previous shape. Language-neutral: the decision reads the record of which +requested definitions were emitted. + ### Changed — a call bound by name alone keeps its rows, marked `via="name"`; typed receivers resolve A member call `x.m()` (or, where the receiver is implicit, a bare `m()`) bound to every in-repo definition spelled diff --git a/src/cli.h b/src/cli.h index 3705dee7a..02cf5059b 100644 --- a/src/cli.h +++ b/src/cli.h @@ -1349,9 +1349,13 @@ inline constexpr char kHelpHead[] = " EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map\n" " defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is\n" " pure overhead in front of the one body it exists to summarize. Disclosed on the root as\n" - " topk_default=\"0\" (self-describing: the change is visible without reading source). A MULTI-match\n" - " name (an ambiguous bare name) or a multi-token --expand keeps the map — there IS something to\n" - " disambiguate. An EXPLICIT --top-k=N (0 included) always overrides this default.\n" + " topk_default=\"0\" (self-describing: the change is visible without reading source). The same\n" + " default applies when --expand names SEVERAL definitions (an ambiguous bare name, or several\n" + " tokens) and EVERY one is served: the bodies carry p= l= t= for each, the root adds map_next=\n" + " (the shell call that prints the map you did not get — orient once per session, then look up\n" + " lean). If a byte budget cut some, the root adds unserved_total=N and unserved_next=\"--expand=\n" + " FILE:LINE:NAME,…\" (the call serving them) — never the generic map. --outline follows the same\n" + " rule for its one token. An EXPLICIT --top-k=N (0 included) always overrides this default.\n" " Each body also carries sibs=\"a,b,...\" sibs_total=\"N\" [sibs_capped=\"1\"] (the file's OTHER\n" " symbols, names only, capped at 40) and inc=\"x.h,...\" inc_total=\"N\" [inc_capped=\"1\"] (the\n" " file's own #include/import targets, capped at 24) — both absent when the count is 0 (a\n" diff --git a/src/compactlegend.h b/src/compactlegend.h index 0f8a27cdb..c25db294d 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -1188,10 +1188,14 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = // expand (bundle serving): src/main.cpp (chooseExpandServe, the exact-name top-k default insert) + src/serialize.h (inc= emit) { "mode", "mode=/reason=: bundle served, not the whole file; reason= gives both byte counts or the cause; explicit top-k opts out", false, "ctx", MapHeaderRead::No, {}, "expand" }, // also defines reason= { "topk_default", "topk_default=0: an exact name was expanded, so the ranked map was dropped; pass top-k=N to get it back", false, "ctx", MapHeaderRead::No, {}, "expand" }, + // expand-lean-k64: a multi-definition name whose definitions were ALL served drops the map too (map_next=), and a cut one names what it did not serve + { "map_next", "map_next=: the shell call that prints the ranked map this answer did not ship (orient once per session, not per lookup)", false, "ctx", MapHeaderRead::No, {}, "expand" }, + { "unserved_total", "unserved_total=N: N requested definitions have no body here (budget or unreadable); unserved_next= is the call serving them", false, "ctx", MapHeaderRead::No, {}, "expand" }, // also defines unserved_next= { "inc_total", "inc_total=N: the file's true include/import count; inc= lists the first 24, inc_capped=1 when cut", true, "b", MapHeaderRead::No, {}, "expand" }, // expand (whole-file serving): src/main.cpp chooseExpandServe (fileOpen) + the topk_default insert { "mode", "mode=/reason=: whole-file won, the file text cost fewer bytes than the bundle; reason= gives both sizes", false, "ctx", MapHeaderRead::No, {}, "expand-file" }, // also defines reason= { "topk_default", "topk_default=0: an exact name was expanded, so the ranked map was dropped; pass top-k=N to get it back", false, "ctx", MapHeaderRead::No, {}, "expand-file" }, + { "map_next", "map_next=: the shell call that prints the ranked map this answer did not ship (orient once per session, not per lookup)", false, "ctx", MapHeaderRead::No, {}, "expand-file" }, // slice: src/slice.h (the root emit, kSliceCountsAttrXml) { "sym", "sym=/lang=: the sliced definition's name and language; p= is its file:line", false, "slice", MapHeaderRead::No, {}, "slice" }, // also defines lang= { "vars", "vars=N: sliceable local bindings in the definition, one v row each; name one to slice it", false, "slice", MapHeaderRead::No, {}, "slice" }, @@ -1235,6 +1239,10 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = // batch: src/mcpverbs.h (the / emit) { "verb", "i=/verb=/ok=: sub-query index, its verb text, 1 answered (payload in CDATA) or 0 failed", true, "q", MapHeaderRead::No, {}, "batch" }, // also defines i= ok= { "err", "err=: why an ok=0 sub-query failed; no payload follows", true, "q", MapHeaderRead::No, {}, "batch" }, + // pack-signatures (--outline whose definitions were all served): src/main.cpp (ctxLeanAttr / ctxUnservedAttr) + { "topk_default", "topk_default=0: the outlines asked for were all served, so the ranked map was dropped; pass top-k=N to get it back", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, + { "map_next", "map_next=: the shell call that prints the ranked map this answer did not ship (orient once per session, not per lookup)", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, + { "unserved_total", "unserved_total=N: N requested definitions have no outline here (budget or unreadable); unserved_next= is the call serving them", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, // also defines unserved_next= // pack-signatures: src/serialize.h ( from src/arch.h builtinLayer) { "layer", "layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none", true, "f", MapHeaderRead::No, {}, "pack-signatures" }, // pack-top-n: src/serialize.h ( from src/arch.h builtinLayer) diff --git a/src/main.cpp b/src/main.cpp index ee4a09577..021215012 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1525,6 +1525,78 @@ inline constexpr const char* kMapRidesAlongFmt = " note=\"the ranked top-{} map below rides along with the requested payload; --top-k=0 for the payload " "alone (--top-k=1 for a minimal map)\""; +// ── expand-lean-k64: --expand/--outline whose requested definitions were ALL served ride no map ───────────────────── +// A multi-definition name used to ship the generic top-200 map beside bodies that already answer it (0 of 185 rows named +// the symbol). When every requested definition is served there is nothing left to disambiguate: top-k defaults to 0, the +// root says so (topk_default="0", the exact-name default's own disclosure) and carries ONE recoverable pointer to the map +// it did not ship (map_next=, a shell line: the map is the default verb of `ripwire ROOT`). The bodies' headers already +// carry p= l= t= for every definition. When some were NOT served (cut by the byte budget, unreadable), the root names +// exactly those — unserved_total= and unserved_next= (the --expand/--outline call serving them) — never the generic map. +// Language-neutral: the decision reads the served-id record, not any language's syntax. An EXPLICIT --top-k keeps today's +// shape. kMaxUnservedSelectors is a blow-up guard (the kMaxExpandSibs precedent), not a target: unserved_total= is the true count. +inline constexpr std::size_t kMaxUnservedSelectors = 16; + +inline std::string leanMapPointer( std::string_view rootArg ) +{ + std::string root( rootArg.empty() ? std::string_view( "." ) : rootArg ); + const bool plain = std::all_of( root.begin(), root.end(), []( char c ) + { return std::isalnum( static_cast( c ) ) != 0 || c == '.' || c == '/' || c == '_' || c == '-' || c == '+' || c == '@' || c == '~' || c == ':' || c == ','; } ); + if( !plain ) + { + std::string q = "'"; + for( char c : root ) + { + q += c == '\'' ? std::string( "'\\''" ) : std::string( 1, c ); + } + root = q + "'"; + } + return "ripwire " + root; +} + +struct UnservedDefs +{ + std::size_t requested = 0; // valid, non-module-scope requests + std::size_t unserved = 0; // of those, the ones with no body/outline in the answer + std::string attrs; // ` unserved_total="N" unserved_next="--expand=P:L:N,…"` — empty when unserved == 0 +}; + +inline UnservedDefs unservedDefsOf( const rw::IngestResult& ing, const std::vector& requested, std::vector served, + std::string_view verbFlag, std::string_view rootArg ) +{ + UnservedDefs u; + std::sort( served.begin(), served.end() ); + const std::string rootPrefix = rootArg.empty() ? std::string() : rw::sarif::rootPrefixOf( rootArg ); + std::string selectors; + std::size_t listed = 0; + for( rw::NodeId id : requested ) + { + if( id >= ing.symbols.size() || ing.symbols[ id ].kind == rw::SymKind::ModuleScope ) + { + continue; + } + ++u.requested; + if( std::binary_search( served.begin(), served.end(), id ) ) + { + continue; + } + ++u.unserved; + if( listed >= kMaxUnservedSelectors ) + { + continue; + } + const rw::Symbol& sym = ing.symbols[ id ]; + const std::string_view path = rootArg.empty() ? std::string_view( ing.files[ sym.fileId ] ) : rw::sarif::rootRelativeUri( ing.files[ sym.fileId ], rootPrefix ); + selectors += ( listed == 0 ? "" : "," ); + selectors += std::string( path ) + ":" + std::to_string( sym.line ) + ":" + sym.name; + ++listed; + } + if( u.unserved > 0 ) + { + u.attrs = " unserved_total=\"" + std::to_string( u.unserved ) + "\"" + rw::nextAttrXml( std::string( verbFlag ) + "=" + selectors, "unserved_next" ); + } + return u; +} + // ── ONE PRICE FOR BOTH SERVING CANDIDATES (CodeRabbit, PR #215, second round on this comparison) ───── // THE DEFECT was not a missing addend, it was two counters. The bundle candidate was priced to the byte // (envelope, root attributes, the unproven residue, the map, the rendered , the closing tag) by the @@ -2319,21 +2391,64 @@ int runDefaultMap( const MainDispatch& d ) // measurement). Built only when a body is expanded; the same vector rides the two emissions below, so the // charged render and the emitted one cannot differ. const std::vector calleeOrder = expandNodes.empty() ? std::vector{} : rw::calleeNameSpecificity( ing ); + EmittedBodies expandRecord; // expand-lean-k64: which requested bodies the render above actually emitted if( !expandNodes.empty() ) { bodiesSection = rw::chargeSection( [ & ]( std::FILE* f ) - { packBodies( f, ing, expandNodes, cfg.packBudgetBytes, g.outOff, g.outTargets, g.outNameOnly, cfg.compress, redactPtr, - expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/nullptr, + { 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, + expandRanges.empty() ? nullptr : &expandRanges, d.notesPtr, /*outEmitted=*/&expandRecord, /*truncateOversizedFirst=*/true, /*withFileContext=*/true, mapRootArg, &calleeOrder ); }, // V1: octocode F2 sibs=/inc= rw::kBytesPerTokenBody ); } + std::vector outlineShown; // expand-lean-k64: the outlines the render below emitted if( !outlineNodes.empty() ) { outlineSection = rw::chargeSection( [ & ]( std::FILE* f ) - { packOutline( f, ing, outlineNodes, cfg.packBudgetBytes, cfg.compress, redactPtr, mapRootArg ); }, + { outlineShown.clear(); + packOutline( f, ing, outlineNodes, cfg.packBudgetBytes, cfg.compress, redactPtr, mapRootArg, &outlineShown ); }, rw::kBytesPerTokenBody ); } + // expand-lean-k64 (see kMaxUnservedSelectors above): a payload verb whose requested definitions were ALL served rides no + // map. Decided HERE, after the payload rendered, because "all served" is a property of the render; nothing between the + // exact-name default above and this point reads mapTopK. Same exclusions as the exact-name default (composed verbs claim + // the map for their own purpose; --max-tokens sizes the map; --json has its own twin), plus an explicit --top-k. + // Scope: every --expand the exact-name default above did not take (one ambiguous name, several tokens, several tokens + // naming one definition) and ONE --outline token (any count — a single-definition --outline rode the same map). + // --outline with several tokens keeps the ordinary default. Consequence: wherever the M6 mode choice runs, the map is + // already gone, so its bundleDoc.mapBytes term below is 0 from the CLI (kept: the price must stay honest if a future + // posture rides a map there). + std::string ctxLeanAttr = exactNameExpandDefault ? std::string( " topk_default=\"0\"" ) : std::string(); + std::string ctxUnservedAttr; + const bool leanEligible = !exactNameExpandDefault && !cfg.topKExplicit && !cfg.packSignatures && cfg.packTopN == 0 && cfg.query.empty() + && !cfg.adaptive && cfg.maxTokens == 0 && !cfg.json + && ( ( !cfg.expand.empty() && cfg.outline.empty() && !expandNodes.empty() && bodiesSection.isRendered ) + || ( cfg.outline.size() == 1 && cfg.expand.empty() && !outlineNodes.empty() && outlineSection.isRendered ) ); + if( leanEligible ) + { + std::vector served; + if( cfg.expand.empty() ) + { + served = outlineShown; + } + else + { + for( const EmittedBody& b : expandRecord.kept ) + { + served.push_back( b.id ); + } + } + const UnservedDefs un = unservedDefsOf( ing, cfg.expand.empty() ? outlineNodes : expandNodes, std::move( served ), + cfg.expand.empty() ? "--outline" : "--expand", mapRootArg ); + if( un.requested > 0 ) + { + mapTopK = 0; + ctxLeanAttr = " topk_default=\"0\"" + nextAttrXml( leanMapPointer( mapRootArg ), "map_next" ); + ctxUnservedAttr = un.attrs; + } + } + // The --expand fallback: estimateExpandBodyTokens is no longer the primary estimate (measured bytes are), // but it remains the honest answer on the degrade path — better than dropping the block out of the // charge entirely, which is the defect this whole item is about. @@ -2378,7 +2493,7 @@ int runDefaultMap( const MainDispatch& d ) // the INITIAL value so it survives even when expandAutoServeScope below does not run (a range slice, or // a pre-render degrade) — every path exactNameExpandDefault can reach ends up with SOME to // decorate, since hasExtension is provably true whenever --expand is non-empty. - std::string ctxOpenStr = exactNameExpandDefault ? "" : ""; + std::string ctxOpenStr = ""; // R-E fix (2026-08-19): the payload document root DISCLOSES the root its p= are relative to, on the two // shapes where the ride-along map's is NOT there to do it — no map at all (mapTopK==0, which // the exact-name --expand default always picks) and whole-file mode. The first R-E landing made @@ -2448,11 +2563,11 @@ int runDefaultMap( const MainDispatch& d ) // because every byte on that path is raw code text — the same rate and the same pricedRootAttr fixpoint // the whole-file emission below applies. topk_default="0" and the unproven residue ride BOTH openers, so // the first is charged to both documents and the second is passed as the shared root bytes. - const std::size_t topkDefaultBytes = exactNameExpandDefault ? ( sizeof( " topk_default=\"0\"" ) - 1 ) : 0; + const std::size_t topkDefaultBytes = ctxLeanAttr.size(); ExpandServeDocument bundleDoc; bundleDoc.payloadBytes = bodiesSection.xml.size(); bundleDoc.mapBytes = mapTopK > 0 ? measureEmittedMapBytes( mapTopK, payloadTokens ) : 0; - bundleDoc.rootAttrBytes = ctxRootBytesWhenNoMap + topkDefaultBytes + noteBytes; // root=/est_tokens= only where no map carries them; note= only where a map does + bundleDoc.rootAttrBytes = ctxRootBytesWhenNoMap + topkDefaultBytes + ctxUnservedAttr.size() + noteBytes; // root=/est_tokens= only where no map carries them; note= only where a map does ExpandServeDocument fileDoc; fileDoc.payloadBytes = wholeFile.xml.size(); // as EMITTED, not the raw file bytes fileDoc.rootAttrBytes = ctxRootAttr.size() + topkDefaultBytes + ctxNotesDegradedBytes; // whole-file mode always carries root= (no rides with it) and, when degraded, the marker too @@ -2488,9 +2603,9 @@ int runDefaultMap( const MainDispatch& d ) // the ROOT ATTRIBUTES ride too: the compact legend reads root=/est_tokens=/topk_default= etc. present-only, so a // candidate without them would price a legend shorter than the one delivered. The est_tokens= values are the // candidates' own prices (the layer reprices them, and a placeholder of another digit count would move the delta). - const std::string topk = exactNameExpandDefault ? " topk_default=\"0\"" : ""; + const std::string topk = ctxLeanAttr; const std::string fileEst = std::to_string( static_cast( double( wholeFile.xml.size() + kExpandWholeFileLegend.size() + ctxRootAttr.size() ) / rw::kBytesPerTokenBody ) + 1 ); - const std::string bundleRoot = ""; + const std::string bundleRoot = ""; const std::string fileRoot = ""; // in the order the bundle is EMITTED — the bodies first, the ride-along map after them: the layer reads its // head terms off the root's first child, so a map-first candidate priced a different legend (rv-r1-L1-2: 107 B) @@ -2505,13 +2620,14 @@ int runDefaultMap( const MainDispatch& d ) ASSUME( ctxOpenStr.rfind( "" — insert // right after ", byte-identical). total= counts the valid requests with a body to outline (a module scope has // none, the packBodies bodyless rule); a skeleton whose span is unreadable is not shown and so also cuts. inline void packOutline( std::FILE* out, const IngestResult& ing, const std::vector& nodes, std::size_t budgetBytes, bool compress, RedactCounts* redact, - std::string_view rootArg = {} ) // R-E (2026-08-17): same single-root-only root + std::string_view rootArg = {}, // R-E (2026-08-17): same single-root-only root // argument serialize() takes — see its comment. + std::vector* shownIdsOut = nullptr ) // expand-lean-k64: the ids this call EMITTED an for + // (nullptr ⇒ not recorded; output unchanged either way) { XmlWriter w( out ); std::vector esc; @@ -7934,6 +7936,10 @@ inline void packOutline( std::FILE* out, const IngestResult& ing, const std::vec piece += safe; piece += "]]>"; used += safe.size(); ++shownCount; + if( shownIdsOut != nullptr ) + { + shownIdsOut->push_back( id ); + } pieces.push_back( PackedBodyPiece{ slot, id, SIZE_MAX, false, std::move( piece ) } ); } std::stable_sort( pieces.begin(), pieces.end(), []( const PackedBodyPiece& x, const PackedBodyPiece& y ) { return x.fileSlot < y.fileSlot; } ); diff --git a/test/expandbodyfirstcheck.sh b/test/expandbodyfirstcheck.sh index 8c39d11fc..c382ed4a4 100755 --- a/test/expandbodyfirstcheck.sh +++ b/test/expandbodyfirstcheck.sh @@ -36,13 +36,17 @@ TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT # L1 (2026-09-19): the CLI default legend is compact and spells / inside its comment; these arms measure the # byte offsets and counts of the REAL elements, so the three runs ask for the full legend. -"$BIN" "$FIX" --expand=dupTarget --pack-budget-bytes=10 --no-cache --legend=full >"$TMP/dup.xml" 2>"$TMP/dup.err" +# expand-lean-k64 (2026-10-08): an --expand whose definitions are all served no longer carries a map by itself (see +# expandtopk0check (H)); the map still rides — body-first reorder, note=, stderr note — wherever a composed payload owns +# it. --outline beside --expand is that shape, so this gate's probe composes the two. The ride-along mechanics under test +# are unchanged; only the way to reach them moved. +"$BIN" "$FIX" --expand=dupTarget --outline=uniqueTarget --pack-budget-bytes=10 --no-cache --legend=full >"$TMP/dup.xml" 2>"$TMP/dup.err" -# ── sanity: the fixture really landed in bundle mode (map+bodies both ride) — otherwise every arm below -# proves nothing (identical to expandtopk0check.sh (B)'s own sanity arm, same fixture, same forcing flag). -grep -q 'mode="bundle"' "$TMP/dup.xml" \ - && ok "(A) sanity: the ambiguous probe landed in bundle mode (map+bodies both ride)" \ - || no "(A) sanity failed: --pack-budget-bytes=10 did not force bundle mode — arm proves nothing" +# ── sanity: the map really rides beside the bodies (a composed --outline owns it) — otherwise every arm below +# proves nothing. +grep -q ' starts before the ranked map's , or no map rides at all. ─────────── bodiesOff="$( grep -bo '"$TMP/ambig.xml" 2>/dev/null ambTotal="$( wc -c <"$TMP/ambig.xml" | tr -d ' ' )" amb_num="$( reason_num "$TMP/ambig.xml" bundle )" -if ! grep -q '"$TMP/ambig5.xml" 2>/dev/null +if grep -q ']*>' "$TMP/ambig5.xml" )" fi [ "$fail" = 0 ] && echo "ALL PASS" || echo "FAILURES ABOVE" diff --git a/test/expandtopk0check.sh b/test/expandtopk0check.sh index 6cb00e2b1..6e00af01a 100755 --- a/test/expandtopk0check.sh +++ b/test/expandtopk0check.sh @@ -58,27 +58,33 @@ else ok "(A) no ride-along stderr note (nothing is riding along to warn about)" fi -# ── (B) ambiguous name (2 defs), no --top-k: the ORDINARY default applies — map rides, note fires ──────── -# --pack-budget-bytes=10 forces M6's whole-file candidate over budget (dupA.c+dupB.c together are well -# over 10 B), so chooseExpandServe picks mode="bundle" deterministically regardless of this tiny fixture's -# absolute byte counts — the note only ever fires in bundle mode ("never in whole-file mode" is the -# pre-existing, correct rule; this arm needs bundle mode to observe it at all). +# ── (B) ambiguous name (2 defs), the ORDINARY default where the lean default does not apply ──────────────── +# expand-lean-k64 (2026-10-08): a multi-definition name whose definitions are ALL served now drops the map too — see (H) +# below. This TWIN pins the path that stays: wherever the lean default is excluded the caller's ordinary default applies, the +# ranked map rides along and the pre-existing "ranked top-N map rides along" stderr note still fires, with NO topk_default=. +# --max-tokens=3000 is such a shape (it sizes the map, so the exact-name/lean defaults both stand aside); the explicit +# --top-k=5 twin right below pins the other half (the caller asked for the map). "$BIN" "$FIX" --expand=dupTarget --top-k=5 --no-cache >"$TMP/dup5.xml" 2>/dev/null grep -q '"$TMP/dup.xml" 2>"$TMP/dup.err" -grep -q 'mode="bundle"' "$TMP/dup.xml" \ - && ok "(B) sanity: the ambiguous probe landed in bundle mode (the note's only firing ground)" \ - || no "(B) sanity failed: --pack-budget-bytes=10 did not force bundle mode — arm proves nothing" -if grep -q 'topk_default="0"' "$TMP/dup.xml"; then - no "(B) ambiguous --expand (2 matches) wrongly got the exact-name default" +if grep -qE 'topk_default=|map_next=' "$TMP/dup5.xml"; then + no "(B) explicit --top-k=5 on a multi-def name carries lean-default decoration — the caller chose, not the tool" else - ok "(B) ambiguous --expand carries NO topk_default= — the multi-match case is untouched" + ok "(B) explicit --top-k=5 on a multi-def name keeps the classic shape (no topk_default=, no map_next=)" +fi +"$BIN" "$FIX" --expand=dupTarget --max-tokens=3000 --pack-budget-bytes=10 --no-cache >"$TMP/dup.xml" 2>"$TMP/dup.err" +grep -q '"$TMP/tk5.xml" 2>/dev/null @@ -180,9 +186,146 @@ else no "(G-b) no reason=\"bundle NNNB ...\" clause found on the real-repo default root — unexpected mode, see (G) above: $( grep -oE ']*>' "$TMP/real_default.xml" )" fi +# ── (H) expand-lean-k64: a multi-definition name, EVERY definition served — no ride-along map ───────────────────────── +# `subtokens` has three definitions (one C++ header, two Python fixtures) and the default budget serves all of them in +# bundle mode (the header is far over the pack budget as a whole file). The root discloses the default (topk_default="0"), +# carries the recoverable pointer to the map it did not ship (map_next=), the bodies keep p= l= for each def, and no / +# rows ride. (The tiny dupTarget fixture serves whole-file instead — (J2) pins that.) +"$BIN" "$ROOT" --expand=subtokens --no-cache --legend=full >"$TMP/mdef.xml" 2>"$TMP/mdef.err" +grep -q 'topk_default="0"' "$TMP/mdef.xml" && grep -q 'map_next="ripwire ' "$TMP/mdef.xml" \ + && ok "(H) multi-def, all served: root carries topk_default=\"0\" and map_next=" \ + || no "(H) multi-def default lacks topk_default=/map_next=: $( grep -oE ']*>' "$TMP/mdef.xml" )" +if grep -qE '"$TMP/mapnext.xml" 2>/dev/null + grep -q '"$TMP/mdef_tk.xml" 2>/dev/null +bodiesOf(){ perl -0777 -ne 'print $1 if /()/s' "$1"; } +[ -n "$( bodiesOf "$TMP/mdef.xml" )" ] && [ "$( bodiesOf "$TMP/mdef.xml" )" = "$( bodiesOf "$TMP/mdef_tk.xml" )" ] \ + && ok "(H2) the lean answer's are byte-identical to the explicit --top-k=3 twin's" \ + || no "(H2) the lean answer's differ from the explicit twin's (every body must still be served)" +nb=$( grep -oE ', shown==total)" \ + || no "(H2) the lean answer serves $nb located , expected >= 3 with shown==total" + +# ── (I) bodies CUT: a symbol-scoped aid (the unserved defs), never the generic map ───────────────────────────────── +"$BIN" "$FIX" --expand=dupTarget --pack-budget-bytes=10 --no-cache >"$TMP/cut.xml" 2>/dev/null +if grep -qE ']*>' "$TMP/cut.xml" )" +unNext=$( grep -oE 'unserved_next="[^"]*"' "$TMP/cut.xml" | head -1 | sed 's/^unserved_next="//;s/"$//' ) +case "$unNext" in + --expand=*dupB.c:*:dupTarget) + if grep -q 'dupB.c:2:dupTarget' <<<"$unNext"; then + "$BIN" "$FIX" "$unNext" --no-cache >"$TMP/cut2.xml" 2>/dev/null + grep -q 'return 2' "$TMP/cut2.xml" \ + && ok "(I) unserved_next serves exactly the cut definition (dupB.c's body)" \ + || no "(I) unserved_next did not serve dupB.c's body" + grep -q 'return 1' "$TMP/cut2.xml" \ + && no "(I) unserved_next re-served the definition that was already served" \ + || ok "(I) unserved_next names only the unserved def (dupA.c's body is not re-served)" + else + no "(I) unserved_next names the wrong line: $unNext" + fi ;; + *) no "(I) unserved_next missing or names the wrong def: '$unNext'" ;; +esac +grep -q ']*p="dupA.c"' "$TMP/cut.xml" \ + && ok "(I) the served def (dupA.c) is still in the cut answer" || no "(I) the first def is missing from the cut answer" + +# ── (J) the whole-file comparison prices the PAYLOAD, not a map ───────────────────────────────────────────────────── +# (J1) real repo: emitTo (2 overloads, one file) used to serve the whole 24 KB file because the bundle was priced with the +# ~19 KB map. Now bundle mode, the priced bundle is the served document, and the document is far under the file. +"$BIN" "$ROOT" --expand=emitTo --no-cache --legend=full >"$TMP/emit.xml" 2>/dev/null +if grep -q 'mode="bundle"' "$TMP/emit.xml"; then + pb=$( grep -oE 'reason="bundle [0-9]+B' "$TMP/emit.xml" | grep -oE '[0-9]+' ) + sz=$( wc -c < "$TMP/emit.xml" | tr -d ' ' ) + [ -n "$pb" ] && [ "$pb" = "$sz" ] && [ "$sz" -lt 12000 ] \ + && ok "(J1) emitTo: bundle mode, priced bundle ${pb}B == served ${sz}B (< 12000B; was the whole 24788B file)" \ + || no "(J1) emitTo bundle priced '${pb}' vs served ${sz}B" +else + no "(J1) emitTo is not in bundle mode: $( grep -oE ']*>' "$TMP/emit.xml" )" +fi +# (J2) near-miss: where the file really is cheaper (tiny fixture) whole-file STILL wins, with the lean disclosure intact +"$BIN" "$FIX" --expand=dupTarget --no-cache --legend=full >"$TMP/small.xml" 2>/dev/null +grep -q 'mode="whole-file"' "$TMP/small.xml" && grep -q 'topk_default="0"' "$TMP/small.xml" && ! grep -q ']*>' "$TMP/small.xml" )" +if grep -q 'unserved_' "$TMP/small.xml"; then + no "(J2) whole-file serving names unserved defs though the file carries every one" +else + ok "(J2) whole-file serving carries no unserved_* (the file has every definition)" +fi + +# ── (K) --outline follows the same rule ────────────────────────────────────────────────────────────────────────────── +"$BIN" "$FIX" --outline=dupTarget --no-cache >"$TMP/ol.xml" 2>/dev/null +if grep -qE '"$TMP/ol5.xml" 2>/dev/null +grep -q '"$TMP/olcut.xml" 2>/dev/null +grep -q 'unserved_total=' "$TMP/olcut.xml" && grep -q 'unserved_next="--outline=' "$TMP/olcut.xml" && ! grep -q ']*>' "$TMP/olcut.xml" )" + +# ── (L) near-miss negatives: the lean default must NOT apply where the caller or a composed verb owns the map ───────── +"$BIN" "$FIX" --expand=dupTarget --top-k=0 --no-cache >"$TMP/mtk0.xml" 2>/dev/null +if grep -qE 'topk_default=|map_next=|mode="' "$TMP/mtk0.xml"; then + no "(L) explicit --top-k=0 on a multi-def name picked up lean decoration — must stay the undecorated lean form" +else + ok "(L) explicit --top-k=0 on a multi-def name stays undecorated" +fi +"$BIN" "$FIX" --expand=dupTarget --pack-top-n=1 --no-cache >"$TMP/mpack.xml" 2>/dev/null +if grep -qE 'topk_default=|map_next=' "$TMP/mpack.xml"; then + no "(L) --expand composed with --pack-top-n wrongly got the lean default (the composed verb owns the map)" +else + ok "(L) --expand composed with --pack-top-n keeps its own shape (no lean decoration)" +fi +"$BIN" "$FIX" --expand=uniqueTarget,dupTarget --pack-budget-bytes=1000000 --no-cache >"$TMP/mtok.xml" 2>/dev/null +if grep -q ']*>' "$TMP/mtok.xml" )" +else + ok "(L) a multi-token --expand with every def served is lean too" +fi +"$BIN" "$FIX" --expand=uniqueTarget --no-cache >"$TMP/u1.xml" 2>/dev/null +if grep -q 'map_next=' "$TMP/u1.xml"; then + no "(L) the single-definition exact-name answer changed shape (map_next= appeared)" +else + ok "(L) the single-definition exact-name answer is unchanged (topk_default only, no map_next=)" +fi + # ── (F) well-formedness + determinism ───────────────────────────────────────────────────────────────── if command -v xmllint >/dev/null 2>&1; then - for f in uniq dup5 dup tk5 tk0 big real_default real_tk0; do + for f in uniq dup5 dup tk5 tk0 big real_default real_tk0 mdef cut ol olcut emit; do if xmllint --noout "$TMP/$f.xml" 2>/dev/null; then ok "(F) $f.xml well-formed"; else no "(F) $f.xml fails xmllint"; fi done else From 455b2c7e8da7386411c27668b63e7c332a494b71 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:50:29 -0400 Subject: [PATCH 076/170] docs(skills): orient once per session, then look up lean --expand/--outline no longer carry a ride-along map when every definition is served, so the navigate, orient, router and handoff skills and the README drop the advice to add --top-k=0 to --expand and the claim that an ambiguous bare name costs the whole map; they now say to orient once and follow map_next= only when that step was skipped. Co-Authored-By: Claude Sonnet 5.5 --- README.md | 6 +++--- skills/ripwire-handoff/SKILL.md | 2 +- skills/ripwire-navigate/SKILL.md | 13 ++++++++----- skills/ripwire-orient/SKILL.md | 6 +++++- skills/ripwire-router/SKILL.md | 2 +- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0f9f2afd6..b2af00db9 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ Ordered understand → navigate → review-the-change: | "Where is X handled?" | `ripwire . --for="…"` | **~2.1K tok** | ~4.9K–20K tok — `grep -rn src/`, then read the file it points at | 2.3×–9.3× | | "What do I already know?" | `ripwire . --recall="…"` | **~15K tok** | ~445K tok — read all 119 markdown docs this repo carries | 29.2× | | "Set me up for this task" | `ripwire . --pack-task="…"` | **~2.1K tok** | ~16K–80K tok — read every relevant file, whole | 7.7×–37.7× | -| "Show me this one function" | `ripwire . --expand=SYM --top-k=0` | **~260–16.5K tok** body (+~5.7K for the ranked-neighborhood bundle) | ~43K–174K tok — read the whole file it lives in | 2.6×–670× | +| "Show me this one function" | `ripwire . --expand=SYM` | **~260–16.5K tok** body (+~5.7K for the ranked-neighborhood bundle) | ~43K–174K tok — read the whole file it lives in | 2.6×–670× | | "Who calls this function?" | `ripwire . --callers=SYM` | **~580 tok** | ~40K–52K tok — `grep -rn SYM src/` (mostly noise), then open 2–3 files to sort real calls from mentions | 69.2×–89.1× | | "Is it safe to change this?" | `ripwire . --impact=SYM` + `--uses=SYM` | **~1.3K tok** | ~18K tok — open every direct-use file, whole | 14.4× | | "I have a stack trace" | `ripwire . --from-trace=FILE` | **~1.4K tok** | ~124K–298K tok — grep all 7 frame names, then open the innermost file(s) | 86.9×–208.6× | @@ -260,7 +260,7 @@ three `docs/ARCHITECTURE.md` names as central; the `--for` row lands `mcpStale` (`src/mcpindex.h:633`), the actual staleness check, 5th-ranked; `--recall` lands the container-rule doc (`AGENTS.md`) that states, verbatim, the same "no `std::map`" rule `CONTRIBUTING.md` explains in full; `--pack-task` names the same three touch points a human would — `cachelint.h`, `mergeCachePack` -(`src/main.cpp:1787`), the `lintrules.h` helpers it reuses; `--expand --top-k=0` hands back the +(`src/main.cpp:1787`), the `lintrules.h` helpers it reuses; `--expand` hands back the requested function's complete, unmodified body — the ranked-neighborhood addition costs the same ~22.6 KB regardless of which function you ask for, confirmed on two (a fixed floor, not per-function variance); `--callers` on `langOfPath` names its 2 real callers, the same ones a `grep` hit-list @@ -799,7 +799,7 @@ to do with it.** Two shapes get the most out of it, and one gets nothing. |---|---|---| | `ripwire . --for="…" --top-k=5` | `ripwire . --for="…" --signatures-only` (or `--token-budget=N`, `--detail=N`) | `--top-k` is inert on `--for`: the run warns on stderr and emits the full bundle anyway, so the agent *believes* it narrowed the output and did not. | | `ripwire . --query="…"` as the default lens | `ripwire . --for="…"` | `--query` is the raw BM25 ranking — the binary's own help calls it debug and says "use --for". It is the right tool for hunting a vocabulary, the wrong default for a task. | -| `ripwire . --expand=SYM` where SYM is an **ambiguous** bare name | `ripwire . --expand=SYM --top-k=0` (or name it exactly: `--expand=FILE:NAME`) | A multi-match name keeps the ranked map — there IS something to disambiguate — so ~9K est_tokens of map ride along with the bodies. An **unambiguous** single match already defaults to `--top-k=0` on its own (disclosed as `topk_default="0"`); no flag needed there. | +| `ripwire . --expand=SYM --top-k=0` (a habit from before the lean default) | `ripwire . --expand=SYM` | `--expand` and `--outline` ship no ride-along map whenever every definition asked for is served — one exact match or an ambiguous bare name alike (disclosed as `topk_default="0"`, with `map_next=` naming the call that prints the map if you never oriented). Definitions a byte budget cut are named in `unserved_total=` / `unserved_next=`. An explicit `--top-k=N` still brings the map back. | | `--callers=` expecting routes | find the URL in the project's own docs (e.g. a feature map), then `--expand` the handler | Framework route handlers have no callers in the graph — the decorator reaches them, not project code. Empty `--callers` on a handler is the design, not a bug. | | `ripwire …` (ONE root that happens to contain checkouts) | `cd` into ONE checkout first | Nothing refuses this: the crawl silently walks the nested repos and merges them into one corpus, so you pay for a map of everything and rank across unrelated codebases. Distinct from the real multi-root feature, which is N **explicit** positional roots (`ripwire dir1 dir2 … `, 2–16 checkouts merged on purpose). | diff --git a/skills/ripwire-handoff/SKILL.md b/skills/ripwire-handoff/SKILL.md index 1f4427c43..40e15351e 100644 --- a/skills/ripwire-handoff/SKILL.md +++ b/skills/ripwire-handoff/SKILL.md @@ -28,7 +28,7 @@ fast, accurate brief — not a wall of source code. 2. **Expand the key symbols** — `ripwire --expand=SYM1,SYM2,SYM3 --legend=compact` (Pick the top 3 by rank from step 1.) - Output: full bodies + callee signatures. This is the actual contract — paste it into the + Output: full bodies + callee signatures (no ranked map rides along; step 1 was your orient). This is the actual contract — paste it into the handoff verbatim rather than paraphrasing. **Bodies are redacted by default** — high-confidence credentials (API keys, tokens, connection strings) are masked before you see them, so pasting this straight into a handoff doc is safe as-is; pass `--no-redact` only if you deliberately need the diff --git a/skills/ripwire-navigate/SKILL.md b/skills/ripwire-navigate/SKILL.md index b17ce3f0b..db5938e42 100644 --- a/skills/ripwire-navigate/SKILL.md +++ b/skills/ripwire-navigate/SKILL.md @@ -171,8 +171,11 @@ passing `--scip`) — check header `precise=N` to confirm the overlay actually m When you need to understand a specific function/class/concept in full (its body, contract, and rationale): 1. **Full body + callee signatures** — `ripwire --expand=SYM --legend=compact` - The ranked map, then `` with SYM's full source in CDATA and a `` block of inline one-line - signatures for everything it calls — read the body with the callee signatures beside it. (No `` block + `` with SYM's full source in CDATA and a `` block of inline one-line signatures for + everything it calls — read the body with the callee signatures beside it. No ranked map rides along (orient + ONCE per session with `ripwire `, then look symbols up lean); a bare name with several definitions + serves them all, still map-free — `map_next=` on the root is the call that prints the map if you skipped + orienting, and `unserved_next=` names the definitions a byte budget cut. (No `` block here; SYM's own doc-comment is in the CDATA body if it sits inside the definition — otherwise read the source lines just above `l=`.) About to Edit what you just expanded? If your edit tool needs a fresh native Read of the file first @@ -231,9 +234,9 @@ field un-places every field after it. A `modeled="1"` number that agrees with th emits the full bundle anyway. (`--for --format=candidates --top-k=N` is different: the export consumes the flag and caps the rows, no warning. `--for --top-k=0` is refused by the payload-only guard.) Narrow plain `--for` with its own arguments: `--signatures-only` (no auto-bodies), `--token-budget=N` (shapes -the bundle to fit), `--detail=N` (full bodies for just the top N). `--top-k` does shape `--query` — and pair -it with `--expand` as `--top-k=0` when SYM is an *ambiguous* bare name; an unambiguous single match already -drops the map by default (`topk_default="0"`). +the bundle to fit), `--detail=N` (full bodies for just the top N). `--top-k` does shape `--query`. `--expand`/`--outline` need no +`--top-k=0`: they drop the map by default whenever every definition asked for is served (`topk_default="0"`), +an ambiguous bare name included; pass `--top-k=N` only if you want the map back in the same call. ## When the fixed verbs can't phrase the question diff --git a/skills/ripwire-orient/SKILL.md b/skills/ripwire-orient/SKILL.md index 53edf33d6..257aea323 100644 --- a/skills/ripwire-orient/SKILL.md +++ b/skills/ripwire-orient/SKILL.md @@ -25,6 +25,10 @@ subsystem you're working in — also accepts a remote `ripwire ` (shall you can orient in a dependency before ever cloning it) — or several roots for a split checkout, `ripwire dir1 dir2 --report`: ONE merged, root-labeled map instead of two separate mental models. +**Orient ONCE per session, then look up lean.** Run the map (`ripwire `, or `--for`) once to learn the +subsystems and entry points; after that `--expand` / `--outline` / `--whereis` / `--callers` answer with no +ride-along map. If you skipped the orient step, the `map_next=` on an `--expand` root is the call that prints it. + ## The escalation ladder — climb only until you feel oriented **0. Recall what you already KNOW** — `ripwire --recall=""` @@ -58,7 +62,7 @@ this default deletes. `bodies="0" reason="budget"` means it did not fit whole. *Conceptual phrase* (`bundle="compact" bodies="0" reason="compact-route"`): no bodies — you get the ranked map plus a `` section, one row per top-ranked symbol with its one-hop callee names. **Read the map, pick ONE symbol, then `--expand=path:name`** (paste the row's own `p=` and `n=`; a bare name that is not -unique costs you the whole map). That is the flow, and it is cheaper than the bodies were: half the bytes +unique serves every definition, still without the map). That is the flow, and it is cheaper than the bodies were: half the bytes of the old bundle on conceptual queries, and the edges tell you which symbol is worth the second call. `--auto-bodies` restores inline bodies on that route if you want them; `--signatures-only` drops both shapes; `--detail=N` picks the body count explicitly. diff --git a/skills/ripwire-router/SKILL.md b/skills/ripwire-router/SKILL.md index 98be98db4..6fdc7df64 100644 --- a/skills/ripwire-router/SKILL.md +++ b/skills/ripwire-router/SKILL.md @@ -74,7 +74,7 @@ Match the **default you were about to use**, not a moment: | About to… | Reach for instead | |---|---| -| `Read` a whole file to understand one function | `--expand=SYM` — that symbol's body + its callees' signatures. The file is not the unit of an answer. | +| `Read` a whole file to understand one function | `--expand=SYM` — that symbol's body + its callees' signatures, no ride-along map (orient ONCE per session with `ripwire `, then look up lean). The file is not the unit of an answer. | | `Read` several files to learn how something works | `--pack-task=""` — ranking + bodies + callers + tests in ONE budgeted call | | `Grep`/`rg` a symbol name across the tree | `--for="theExactName"` (name-exact routing, recall@1 ~99%) · `--uses=SYM` for every read/write/import site | | `Grep` a concept ("where do we retry") | `--for=""` — matches doc-comments and bodies, not just identifiers | From fddd4b3cbaa7d39c1101e1a5b99507bb0e4c8f36 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:52:03 -0400 Subject: [PATCH 077/170] refactor(taskroute): lowerAscii is queryshape's one lowercaser, not a second copy of it (K50 follow-up) The explicit-narrowing fix made taskroute.h's loop byte-identical to queryshape.h's, which --quality-delta gates as a clone of a reused helper. A using-declaration replaces the copy. Co-Authored-By: Claude Sonnet 5.5 --- src/taskroute.h | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/taskroute.h b/src/taskroute.h index 242fb607a..820df9003 100644 --- a/src/taskroute.h +++ b/src/taskroute.h @@ -15,6 +15,7 @@ #include "infra/jsonesc.h" // shSingleQuote — the repository's canonical POSIX argv quoting #include "model.h" // IngestResult +#include "queryshape.h" // detail::lowerAscii — the one ASCII lowercaser (K50) #include "query.h" // isKnownLayerWord — the layer vocabulary --verify enforces at evaluation #include "sarif.h" // rootRelativeUri / rootPrefixOf — the ONE root-relative path rule the map emits with #include "verify.h" // parseClaim — the SHIPPED claim grammar; the router never re-implements it @@ -62,17 +63,10 @@ struct TaskRouteResult int margin = 0; }; -inline std::string lowerAscii( std::string_view text ) -{ - std::string out; - out.reserve( text.size() ); - for( const char ch : text ) // EXPLICIT unsigned narrowing: `for( const unsigned char c : text )` is G1's implicit-integer-sign-change on any byte >= 0x80 (K50) - { - const unsigned char c = static_cast( ch ); - out.push_back( ( c >= 'A' && c <= 'Z' ) ? char( c - 'A' + 'a' ) : char( c ) ); // A-Z only: a byte >= 0x80 passes through untouched - } - return out; -} +// K50: ONE ASCII lowercaser for the tree's task-text readers. This file used to spell its own loop (`for( const unsigned char c : +// text )`, an implicit sign change on every byte >= 0x80 that aborted the sanitizer build on a non-ASCII prompt), and the fix for it +// made that loop byte-identical to queryshape.h's, which --quality-delta flagged as a clone of the reused helper. It is that one now. +using queryshape::detail::lowerAscii; // "is this word one of these?" — the shape four tables' predicates were each writing out by hand, which is // how a five-line any_of becomes a duplication finding against its own neighbours. From ab2c5cd50e8ea43d360403c6e3516d9b2ec5b1f8 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:53:24 -0400 Subject: [PATCH 078/170] docs: regenerate COMMANDS.md and LIMITS.md for the lean --expand/--outline default Co-Authored-By: Claude Sonnet 5.5 --- docs/LIMITS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/LIMITS.md b/docs/LIMITS.md index 40041f310..21d20c728 100644 --- a/docs/LIMITS.md +++ b/docs/LIMITS.md @@ -17,10 +17,10 @@ it once, marked `×N`. | total caps | files | caps whose file discloses | caps whose file discloses NOTHING | | --- | --- | --- | --- | -| 240 | 96 | 140 | **100** | +| 241 | 96 | 140 | **101** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 240 + 7 is the 247 constants this generator parses out of `src/`. +are not counted as caps, and 241 + 7 is the 248 constants this generator parses out of `src/`. ## INDEXING, OUTPUT or BOUNDARY — which half of the answer a cap bounds @@ -38,8 +38,8 @@ None of them truncates anything, so none can be judged by `shown=`/`total=` and a disclosure — labelling them OUTPUT would ask for a `capped="1"` that could never honestly fire. The distinction was named in review on #108 and the rows below now carry it. -The `class` column below carries that answer where it is known. **132 of 240 caps are classified -(47 INDEXING, 41 OUTPUT, 44 BOUNDARY); the remaining 108 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **132 of 241 caps are classified +(47 INDEXING, 41 OUTPUT, 44 BOUNDARY); the remaining 109 render `—`, which means NOT YET CLASSIFIED — never "neither".** Classifications live in `docs/limits_classes.tsv`, a sidecar with a known expiry: the tag belongs on the declaration itself, and this file exists only because the round that @@ -89,7 +89,7 @@ refuse to write, so the column cannot be satisfied by pointing at nothing. ## Caps, by file -One table for each of the 96 files that declare a cap — the 240 caps counted above, and no parameter. +One table for each of the 96 files that declare a cap — the 241 caps counted above, and no parameter. ### `src/abicheck.h` @@ -583,6 +583,7 @@ Discloses: **none** | constant | value | class | note | | --- | --- | --- | --- | +| `kMaxUnservedSelectors` | `16` | — | — | | `kRecentRows` | `40` | — | F3: ~45 B a row; the file-level answer, not the file list | ### `src/mcp.h` From 8fb178143b44bef52920d6cb7a9a56bbbdd51a3c Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:53:29 -0400 Subject: [PATCH 079/170] docs: regenerate COMMANDS.md for the lean --expand/--outline default Co-Authored-By: Claude Sonnet 5.5 --- docs/COMMANDS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index b4d03761b..600044fa4 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -1572,7 +1572,7 @@ $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 **Answers:** print these symbols' full bodies, plus the signatures of what they call full bodies of A,B,... -Selector grammar per item (the tail after the LAST ':' decides; a tail STARTING WITH A DIGIT is a range, anything else is a name): NAME every def of that name | FILE:NAME that file's def NAME:START-END body slice | FILE:NAME:START-END selector + slice FILE:LINE:NAME paste a row's p="path:line" straight from --callers/--lint/--grep (NOT --hotspots: its p= is a BARE path — build FILE:LINE:NAME from its own p=/top_l=/top= instead, since top= is just the worst function's name) path::scope::name the id composed from a row's p= sc= n= (the map, --for, --pack-task) Scope::name the sym= spelling edit-check and grep's in= rows print — matches the name under any scope whose ::-boundary SUFFIX is Scope (Box::lid, deep::Box::lid); a wrong scope refuses, it never falls back to the bare-name union Scope.name|Scope#name the same match, dotted (Python/JS/Java) or Ruby-style; tried only when no other spelling matched (START-END is 1-based within the def's OWN body — lines="lo-hi/total" marks the slice partial; out-of-range clamps. FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. Disclosed on the root as topk_default="0" (self-describing: the change is visible without reading source). A MULTI-match name (an ambiguous bare name) or a multi-token --expand keeps the map — there IS something to disambiguate. An EXPLICIT --top-k=N (0 included) always overrides this default. Each body also carries sibs="a,b,..." sibs_total="N" [sibs_capped="1"] (the file's OTHER symbols, names only, capped at 40) and inc="x.h,..." inc_total="N" [inc_capped="1"] (the file's own #include/import targets, capped at 24) — both absent when the count is 0 (a documented zero, not a degrade), so a body no longer needs a second --outline call just to learn what else lives in its file. CHEAPEST-COMPLETE-ANSWER SERVING (no explicit --top-k, no range slice): the verb ALSO measures the (possibly map-less) bundle against the requested symbols' whole FILE(s) and emits the SMALLER, disclosed on the root as mode="bundle|whole-file" reason="the two byte counts" — on a small file the old bundle was 5.65x the file itself; on a big file the bundle saves ~26x. The whole-file form is with the file CDATA-wrapped (redacted as usual) and every requested symbol's line anchor kept. An EXPLICIT --top-k=N (including 0) opts out of BOTH the exact-name default and mode= auto-selection and keeps the classic undecorated shape; a SYM:START-END slice opts out of mode= auto-selection only (serving the whole file would invert an explicit narrowing) but still gets the exact-name top-k=0 default when it applies. When the bundle wins AND no explicit --top-k was given, the requested bodies are served BEFORE the ranked map (not buried after it), and the map's escape hatch also rides the root as note="...--top-k=0 for the payload alone..." (not stderr-only): a caller reading only stdout sees both the answer and how to drop the map on the next call. Absent whenever the bundle carries no map at all (top-k=0, exact-name default) or loses to whole-file, and whenever --top-k was explicit — the classic shape above carries no note= either. +Selector grammar per item (the tail after the LAST ':' decides; a tail STARTING WITH A DIGIT is a range, anything else is a name): NAME every def of that name | FILE:NAME that file's def NAME:START-END body slice | FILE:NAME:START-END selector + slice FILE:LINE:NAME paste a row's p="path:line" straight from --callers/--lint/--grep (NOT --hotspots: its p= is a BARE path — build FILE:LINE:NAME from its own p=/top_l=/top= instead, since top= is just the worst function's name) path::scope::name the id composed from a row's p= sc= n= (the map, --for, --pack-task) Scope::name the sym= spelling edit-check and grep's in= rows print — matches the name under any scope whose ::-boundary SUFFIX is Scope (Box::lid, deep::Box::lid); a wrong scope refuses, it never falls back to the bare-name union Scope.name|Scope#name the same match, dotted (Python/JS/Java) or Ruby-style; tried only when no other spelling matched (START-END is 1-based within the def's OWN body — lines="lo-hi/total" marks the slice partial; out-of-range clamps. FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. Disclosed on the root as topk_default="0" (self-describing: the change is visible without reading source). The same default applies when --expand names SEVERAL definitions (an ambiguous bare name, or several tokens) and EVERY one is served: the bodies carry p= l= t= for each, the root adds map_next= (the shell call that prints the map you did not get — orient once per session, then look up lean). If a byte budget cut some, the root adds unserved_total=N and unserved_next="--expand= FILE:LINE:NAME,…" (the call serving them) — never the generic map. --outline follows the same rule for its one token. An EXPLICIT --top-k=N (0 included) always overrides this default. Each body also carries sibs="a,b,..." sibs_total="N" [sibs_capped="1"] (the file's OTHER symbols, names only, capped at 40) and inc="x.h,..." inc_total="N" [inc_capped="1"] (the file's own #include/import targets, capped at 24) — both absent when the count is 0 (a documented zero, not a degrade), so a body no longer needs a second --outline call just to learn what else lives in its file. CHEAPEST-COMPLETE-ANSWER SERVING (no explicit --top-k, no range slice): the verb ALSO measures the (possibly map-less) bundle against the requested symbols' whole FILE(s) and emits the SMALLER, disclosed on the root as mode="bundle|whole-file" reason="the two byte counts" — on a small file the old bundle was 5.65x the file itself; on a big file the bundle saves ~26x. The whole-file form is with the file CDATA-wrapped (redacted as usual) and every requested symbol's line anchor kept. An EXPLICIT --top-k=N (including 0) opts out of BOTH the exact-name default and mode= auto-selection and keeps the classic undecorated shape; a SYM:START-END slice opts out of mode= auto-selection only (serving the whole file would invert an explicit narrowing) but still gets the exact-name top-k=0 default when it applies. When the bundle wins AND no explicit --top-k was given, the requested bodies are served BEFORE the ranked map (not buried after it), and the map's escape hatch also rides the root as note="...--top-k=0 for the payload alone..." (not stderr-only): a caller reading only stdout sees both the answer and how to drop the map on the next call. Absent whenever the bundle carries no map at all (top-k=0, exact-name default) or loses to whole-file, and whenever --top-k was explicit — the classic shape above carries no note= either. **Try it** @@ -1603,7 +1603,7 @@ $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport - a wrong scope refuses, it never falls back to the bare-name union Scope.name|Scope#name the same match, dotted (Python/JS/Java) or Ruby-style; - FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. -- A MULTI-match name (an ambiguous bare name) or a multi-token --expand keeps the map — there IS something to disambiguate. +- The same default applies when --expand names SEVERAL definitions (an ambiguous bare name, or several tokens) and EVERY one is served: the bodies carry p= l= t= for each, the root adds map_next= (the shell call that prints the map you did not get — orient once per session, then look up lean). ### `--compress` From 5db01c477ea21591ec60b9a61c4435ed92dfa2fa Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:55:07 -0400 Subject: [PATCH 080/170] test(scroundtrip): the whole-file arms read a file that is cheaper than its payload bundle Co-Authored-By: Claude Sonnet 5.5 --- test/scroundtripcheck.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/scroundtripcheck.sh b/test/scroundtripcheck.sh index c60402b98..727c7c22f 100755 --- a/test/scroundtripcheck.sh +++ b/test/scroundtripcheck.sh @@ -155,7 +155,13 @@ if grep -q '"sc":"Widget"' "$TMP/map.json" && ! grep -q '"id":"qual.cpp::' "$TMP # that had just printed the path, on a document carrying no legend at all — a repetition AND an undefined # first-screen attribute on the one --expand shape with nothing else to read. # L1 (2026-09-19): (E2) reads the FULL legend's composition prose, so this run asks for the full legend. -"$BIN" test/nestedqualfix --expand=Outer --no-cache --legend=full >"$TMP/wf.xml" 2>/dev/null "$WFQ/outer.hpp" +"$BIN" "$WFQ" --expand=Outer --no-cache --legend=full >"$TMP/wf.xml" 2>/dev/null ]*id="outer.hpp::' "$TMP/wf.xml"; then ok "(E2) whole-file --expand anchor rows carry sc= and no path-repeating id=" @@ -167,12 +173,12 @@ if grep -q 'mode="whole-file"' "$TMP/wf.xml"; then && ok "(E2) the whole-file root defines the composition (sc= is not an undefined first-screen attribute)" \ || no "(E2) the whole-file serving carries no legend defining sc=/the composition" # the composed id still RESOLVES, which is the whole contract - "$BIN" test/nestedqualfix --expand='outer.hpp::Outer::Outer' --no-cache >"$TMP/wf2.xml" 2>/dev/null "$TMP/wf2.xml" 2>/dev/null wrapper, no p= repetition) — pinned so the design # stays a decision rather than an omission nobody re-checked. @@ -205,7 +211,7 @@ else no "(E3) the probe did not serve the bundle shape (root says $E3_MODE) — this arm proves nothing" fi # …and the positive control: a REAL whole-file serving still selects expand-file. -"$BIN" test/nestedqualfix --expand=Outer --legend=compact --no-cache >"$TMP/e3b.xml" 2>/dev/null "$TMP/e3b.xml" 2>/dev/null Date: Thu, 8 Oct 2026 20:56:49 -0400 Subject: [PATCH 081/170] docs: regenerate TUNING.md and the README cap count for the lean expand lane Co-Authored-By: Claude Sonnet 5.5 --- README.md | 2 +- docs/TUNING.md | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b2af00db9..9b4684e74 100644 --- a/README.md +++ b/README.md @@ -2841,7 +2841,7 @@ python3 test/pargates.py . ./build/ripwire -j 6 A new gate script must be added to `test/regression.sh` in the same change. The gate `test/manifestcheck.sh` enforces this rule. -Another gate derives the cap inventory. The tool has 240 compile-time caps and 7 ranking parameters. +Another gate derives the cap inventory. The tool has 241 compile-time caps and 7 ranking parameters. `docs/LIMITS.md` lists each cap, its value, and whether the file discloses a truncation when the cap fires, and `python3 docs/limits_build.py --check` proves that list against `src/`. `docs/TUNING.md` lists the measured cost of each cap. diff --git a/docs/TUNING.md b/docs/TUNING.md index 0dc297ff9..58d0684a9 100644 --- a/docs/TUNING.md +++ b/docs/TUNING.md @@ -14,26 +14,26 @@ to production at defaults; that control is what makes these numbers mean anythin | cap declarations | distinct names | tunable | must stay `constexpr` | move >= 1 invocation | move nothing measurable | | --- | --- | --- | --- | --- | --- | -| 150 | 149 | 112 | 12 | **37** | 75 | +| 151 | 150 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **150 cap -declarations** under **149 distinct names** (`kRowCap` declared in more than one file). The sweep +The first two columns are not the same number, and the gap is not a rounding: `src/` holds **151 cap +declarations** under **150 distinct names** (`kRowCap` declared in more than one file). The sweep patches by NAME, so its own population is NAMES and not declarations — and the two columns beside those are frozen at the commit named under Provenance while this census is re-read from `src/` on -every run, which makes them a third population again. Enumerated over the 149 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **26 declared since the sweep was +every run, which makes them a third population again. Enumerated over the 150 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **27 declared since the sweep was prepared, which no measurement has touched** (`kChurnMergeBombMaxFiles`, `kExtendedLengthThresholdUnits`, `kFieldIdCapacity`, `kForPageRowsDefault`, -`kForPageUnionSymbolCap`, `kMaxAstQueryNesting`, +20 more). 111 + 12 + 26 = 149, and `emit` refuses +`kForPageUnionSymbolCap`, `kMaxAstQueryNesting`, +21 more). 111 + 12 + 27 = 150, and `emit` refuses to render a partition that does not add up. The `tunable` column above reads 112 rather than 111 because one name the sweep classified is no longer declared in `src/` at all: `kSituTestRowsShown`. -Quoting "113 of 150" would be wrong in both halves at once, which is the shape of error a generated +Quoting "113 of 151" would be wrong in both halves at once, which is the shape of error a generated table exists to prevent. ## Read this ratio before the tables **37 of 112 tunable caps move any invocation at all. 75 move nothing measurable.** That is the -finding, and it says what NOT to do: this is not a 150-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 151-cap audit. Most of these constants are inert on real invocations and should be left alone. The work worth doing is the small set below, plus the caps that fire SILENTLY — a cap that bites without disclosing is a defect independent of whether its value is right, and that fix is both cheaper and larger than any retuning. From 3bd3bee1e57ffa3db632b58d27616b4f6b035d36 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:59:26 -0400 Subject: [PATCH 082/170] map inventory: house _capped/_total disclosure, TS and C# entry siblings, generated docs The two runaway guards now disclose in the shared vocabulary (entries_capped/entries_total, names_capped/names_total) and are classified OUTPUT caps. The fixture gains a TS main called by its own module scope and a C# static Main; both are entries by the same rule. The orient skill names the tier and --no-inventory. Regenerated: LIMITS.md, COMMANDS.md, gate counts, README flag and cap counts. Co-Authored-By: Claude Opus 5.5 --- README.md | 12 ++++++------ docs/COMMANDS.md | 15 +++++++++++++-- docs/EVALS.md | 6 +++--- docs/LIMITS.md | 21 +++++++++++++++------ docs/limits_classes.tsv | 2 ++ present/deck5_ripwire_build.js | 6 +++--- skills/ripwire-orient/SKILL.md | 3 +++ src/compactlegend.h | 4 ++-- src/mapinventory.h | 21 +++++++++++++-------- src/serialize.h | 8 ++++---- test/mapinventorycheck.sh | 12 ++++++------ test/mapinvfix/cs/Program.cs | 5 +++++ test/mapinvfix/ts/cli.ts | 3 +++ 13 files changed, 78 insertions(+), 40 deletions(-) create mode 100644 test/mapinvfix/cs/Program.cs create mode 100644 test/mapinvfix/ts/cli.ts diff --git a/README.md b/README.md index 0f9f2afd6..5a22c147a 100644 --- a/README.md +++ b/README.md @@ -948,9 +948,9 @@ Full retrieval tables — including the MRR figures behind the router numbers ab ## What it answers
-188 long flags across seven families, plus the MCP server — and --help-task names the ONE command a task wants, or abstains honestly when the evidence is too thin +189 long flags across seven families, plus the MCP server — and --help-task names the ONE command a task wants, or abstains honestly when the evidence is too thin -Around the core sit 188 long flags advertised in `--help`, across seven families — plus an MCP +Around the core sit 189 long flags advertised in `--help`, across seven families — plus an MCP server, so a coding agent can call any of them mid-task instead of grepping and reading whole files. `--help` prints one line per flag (~4.5K tokens); `--help=--FLAG` prints that flag's full entry with every caveat, `--help=SECTION` one family, and `--help=all` the whole catalog. @@ -2085,9 +2085,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published ### In the tests
-678 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +679 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures -`test/regression.sh` names **678 gate scripts** and is the authoritative list; +`test/regression.sh` names **679 gate scripts** and is the authoritative list; `python3 test/pargates.py . ./build/ripwire -j 6` runs the same set in parallel. On top of them sit the contracts that do not fit a unit test: two runs byte-identical, warm output identical to cold, output that pipes clean through `xmllint --noout`, a sanitizer build with `-fno-sanitize-recover=all`, and a @@ -2647,7 +2647,7 @@ identity of the index, and says which one is at fault. ### 5. Command families -The `--help` output groups 188 long flags advertised in `--help` into seven families. The `--help=` +The `--help` output groups 189 long flags advertised in `--help` into seven families. The `--help=` column below is the argument that prints one family: `ripwire --help=navigate`. `ripwire --help=--FLAG` prints one flag's full text — the caveats, the units, what it refuses and why. `ripwire --help=all` is the whole catalog, about 46,000 tokens. @@ -2841,7 +2841,7 @@ python3 test/pargates.py . ./build/ripwire -j 6 A new gate script must be added to `test/regression.sh` in the same change. The gate `test/manifestcheck.sh` enforces this rule. -Another gate derives the cap inventory. The tool has 240 compile-time caps and 7 ranking parameters. +Another gate derives the cap inventory. The tool has 242 compile-time caps and 7 ranking parameters. `docs/LIMITS.md` lists each cap, its value, and whether the file discloses a truncation when the cap fires, and `python3 docs/limits_build.py --check` proves that list against `src/`. `docs/TUNING.md` lists the measured cost of each cap. diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index b4d03761b..4f2991528 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -38,7 +38,7 @@ Two limits apply to nearly everything here and are not repeated in every section ## Contents -**understand a codebase cold** — [`--top-k`](#--top-kn) · [`--max-tokens`](#--max-tokensn) · [`--token-budget`](#--token-budgetnkmg) · [`--help-task`](#--help-tasktask) · [`--for`](#--fortask) · [`--sections`](#--sectionslegocompose) · [`--signatures-only`](#--signatures-only) · [`--auto-bodies`](#--auto-bodies) · [`--no-route`](#--no-route) · [`--adaptive`](#--adaptive) · [`--no-mention-boost`](#--no-mention-boost) · [`--no-doc-mention`](#--no-doc-mention) · [`--lego`](#--legotype) · [`--exemplar`](#--exemplartaskkind) · [`--recall`](#--recalltask) · [`--tree`](#--tree) · [`--html`](#--htmlfile) · [`--color-by`](#--color-bymode) · [`--order`](#--ordermode) · [`--no-stable`](#--no-stable) +**understand a codebase cold** — [`--top-k`](#--top-kn) · [`--no-inventory`](#--no-inventory) · [`--max-tokens`](#--max-tokensn) · [`--token-budget`](#--token-budgetnkmg) · [`--help-task`](#--help-tasktask) · [`--for`](#--fortask) · [`--sections`](#--sectionslegocompose) · [`--signatures-only`](#--signatures-only) · [`--auto-bodies`](#--auto-bodies) · [`--no-route`](#--no-route) · [`--adaptive`](#--adaptive) · [`--no-mention-boost`](#--no-mention-boost) · [`--no-doc-mention`](#--no-doc-mention) · [`--lego`](#--legotype) · [`--exemplar`](#--exemplartaskkind) · [`--recall`](#--recalltask) · [`--tree`](#--tree) · [`--html`](#--htmlfile) · [`--color-by`](#--color-bymode) · [`--order`](#--ordermode) · [`--no-stable`](#--no-stable) **navigate / answer a question** — [`--around`](#--aroundsym) · [`--callers`](#--callerssym) · [`--callees`](#--calleessym) · [`--uses`](#--usessym) · [`--graph-query`](#--graph-queryexpr) · [`--external-surface`](#--external-surface) · [`--path`](#--pathsrcdst) · [`--connect`](#--connectabc) · [`--impact`](#--impactsym) · [`--verify`](#--verifyclaim) · [`--mentions`](#--mentionssym) · [`--affected`](#--affectedf1f2sym) · [`--exercises`](#--exercisestestfile) · [`--situ`](#--situf1f2) · [`--handoff`](#--handoff) · [`--test-gate`](#--test-gatef1f2) · [`--grep`](#--grepstr----regexpat) · [`--grep-context`](#--grep-contextn----grep-beforen----grep-aftern) · [`--and`](#--andstr) · [`--not`](#--notstr) · [`--grep-scope`](#--grep-scopelinefile) · [`--grep-in`](#--grep-incodeany) · [`--handles`](#--handles) · [`--match`](#--matchquery) · [`--pattern`](#--patternpat) · [`--query`](#--queryterms) @@ -92,6 +92,17 @@ $ ./build/ripwire . --top-k=5 - --for's OWN signature/lego/compose bundle self-limits via --pack-top-n instead — --top-k is INERT there (documented, not fixed — a real fix is a behavior change); - to WIDEN a --for answer use --limit=N, the file-grain page (one row per file), not --top-k. +### `--no-inventory` + +**Answers:** drop the default map's tier: the files its ranked rows leave out, and the entries The default map appends after its ranked rows whenever they left out a file with code: rows (program entries by name convention) and rows naming or counting every unshown file by directory. + +This flag drops it (the rows are unchanged either way). Never present under --max-tokens or on a payload verb's map. + +**Caveats (stated by the binary):** + +- drop the default map's tier: the files its ranked rows leave out, and the entries The default map appends after its ranked rows whenever they left out a file with code: rows (program entries by name convention) and rows naming or counting every unshown file by directory. +- Never present under --max-tokens or on a payload verb's map. + ### `--max-tokens=N` **Answers:** budget the map to ~N tokens (binary-search top-K) — SHAPES the map to fit. @@ -115,7 +126,7 @@ $ ./build/ripwire . --max-tokens=1500 ``` -**Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace`, `--limit` +**Shaped by:** `--no-inventory`, `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace`, `--limit` **Caveats (stated by the binary):** diff --git a/docs/EVALS.md b/docs/EVALS.md index fa753d237..803fa3014 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 678 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 679 gate scripts plus the determinism, cache-transparency and golden contracts. | | **`--quality-delta`** | `src/quality.h` | Ten measured code-quality failure modes, reported only where a change made them worse. | ### The labeling protocol (why the held-out eval is allowed to disagree with the ranker) @@ -5888,7 +5888,7 @@ copy here would be exactly the dialect divergence that gate exists to catch. Com tags, wrap, stable-order defaults), seven individually invoked standalone gates (`g1freshcheck`, `skillscan`, `htmlexport`, `compresscheck`, `handoffcheck`, `releaseinstallcheck`, `taskroutecheck`), and a single loop -naming **678 gate scripts**, all of which exist on disk. +naming **679 gate scripts**, all of which exist on disk. `python3 test/pargates.py . ./build/ripwire -j 6` runs the same scripts in parallel so a full verification fits in one sitting. It does not modify `regression.sh`. @@ -7005,7 +7005,7 @@ Listed because the reason is more useful than the silence. shipped**. See `bench/locbench/anchorhop_calib.json`. The mention anchor's reproducible numbers are the ablations in §4. - **A single round gate-count.** Two in-tree numbers disagree (`test/pargates.py`'s docstring says - ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 678. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 679. The loop is the authority; the stale docstrings are a known drift. Since 2026-09-10 the number is not written by hand anywhere: `docs/gatecount_build.py` derives it from the loop and rewrites every published site, `test/gatecountcheck.sh` fails if any of them drifts, and `test/manifestcheck.sh` diff --git a/docs/LIMITS.md b/docs/LIMITS.md index 40041f310..ec3ad4c94 100644 --- a/docs/LIMITS.md +++ b/docs/LIMITS.md @@ -17,10 +17,10 @@ it once, marked `×N`. | total caps | files | caps whose file discloses | caps whose file discloses NOTHING | | --- | --- | --- | --- | -| 240 | 96 | 140 | **100** | +| 242 | 97 | 142 | **100** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 240 + 7 is the 247 constants this generator parses out of `src/`. +are not counted as caps, and 242 + 7 is the 249 constants this generator parses out of `src/`. ## INDEXING, OUTPUT or BOUNDARY — which half of the answer a cap bounds @@ -38,8 +38,8 @@ None of them truncates anything, so none can be judged by `shown=`/`total=` and a disclosure — labelling them OUTPUT would ask for a `capped="1"` that could never honestly fire. The distinction was named in review on #108 and the rows below now carry it. -The `class` column below carries that answer where it is known. **132 of 240 caps are classified -(47 INDEXING, 41 OUTPUT, 44 BOUNDARY); the remaining 108 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **134 of 242 caps are classified +(47 INDEXING, 43 OUTPUT, 44 BOUNDARY); the remaining 108 render `—`, which means NOT YET CLASSIFIED — never "neither".** Classifications live in `docs/limits_classes.tsv`, a sidecar with a known expiry: the tag belongs on the declaration itself, and this file exists only because the round that @@ -89,7 +89,7 @@ refuse to write, so the column cannot be satisfied by pointing at nothing. ## Caps, by file -One table for each of the 96 files that declare a cap — the 240 caps counted above, and no parameter. +One table for each of the 97 files that declare a cap — the 242 caps counted above, and no parameter. ### `src/abicheck.h` @@ -585,6 +585,15 @@ Discloses: **none** | --- | --- | --- | --- | | `kRecentRows` | `40` | — | F3: ~45 B a row; the file-level answer, not the file list | +### `src/mapinventory.h` + +Discloses: `entries_capped`, `names_capped` + +| constant | value | class | note | +| --- | --- | --- | --- | +| `kInventoryEntryCap` | `32` | OUTPUT | — | +| `kInventoryNameCeiling` | `2000` | OUTPUT | — | + ### `src/mcp.h` Discloses: `hits_capped` @@ -867,7 +876,7 @@ Discloses: **none** ### `src/serialize.h` -Discloses: `capped`, `calls_capped`, `inc_capped`, `sibs_capped` +Discloses: `capped`, `calls_capped`, `entries_capped`, `inc_capped`, `names_capped`, `sibs_capped` | constant | value | class | note | | --- | --- | --- | --- | diff --git a/docs/limits_classes.tsv b/docs/limits_classes.tsv index b93f9336e..96731b219 100644 --- a/docs/limits_classes.tsv +++ b/docs/limits_classes.tsv @@ -47,6 +47,8 @@ kHandoffSymbolsPerDocFile OUTPUT kIdiomMaxCondTokens BOUNDARY kIdiomMaxLabelTokens BOUNDARY kIdiomMaxReturnTokens BOUNDARY +kInventoryEntryCap OUTPUT +kInventoryNameCeiling OUTPUT kMatchMaxHits INDEXING kMaxAggsModeled INDEXING kMaxAliasDepth INDEXING diff --git a/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index cff63a28c..9baf2feee 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1214,7 +1214,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["678 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["679 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1238,7 +1238,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "678", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "679", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1540,7 +1540,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["188 long flags · 36 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["678 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["679 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 71 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/skills/ripwire-orient/SKILL.md b/skills/ripwire-orient/SKILL.md index 53edf33d6..e3641ead2 100644 --- a/skills/ripwire-orient/SKILL.md +++ b/skills/ripwire-orient/SKILL.md @@ -102,6 +102,9 @@ the gnarliest function. Plan edits around this list. **6. Budget it** if the map is large — `--max-tokens=8000` or `--top-k=50`. Both shape the default map; a positive, explicit `--top-k` is inert on plain `--for` (it warns on stderr and emits the full bundle) — bound a `--for` call with `--signatures-only`, `--token-budget=N` or `--detail=N` instead. +When the ranked rows leave files out, the map ends with ``: the program entries (``, by name convention) +and every unshown file, named or counted per directory (``). Read it before concluding a file or entry point is +not there; `--no-inventory` drops it (the rows are unchanged). ## Orienting N agents at once, not yourself — `--partition=N` diff --git a/src/compactlegend.h b/src/compactlegend.h index 3c490e7d1..3229af1e5 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -431,8 +431,8 @@ inline constexpr CompactCompletenessTerm kCompactCompletenessTerms[] = { "listed", ": files the ranked rows show / do not (sum = files=), each named or counted below", true, "inv" }, { "n", ": a program entry by name (a main only its own module scope calls, or __main__.py), callees as c rows; manifest entries not read", true, "entry" }, { "n", ": n= unshown files under dir p=; f= names its code files, the rest (tests, docs, config, test/doc/example dirs) counted", true, "ls" }, - { "entry_total", "inv entry_total=N: N entries, the first 32 by path shown", true, "inv" }, - { "names_cut", "inv names_cut=N: N code files left unnamed past the 2000-name ceiling (later dirs n= only)", true, "inv" }, + { "entries_capped", "inv entries_capped=1 entries_total=N: N entries, the first 32 by path shown", true, "inv" }, + { "names_capped", "inv names_capped=1 names_total=N: N unshown code files passed the 2000-name ceiling; later dirs n= only", true, "inv" }, { "pr_converged", "pr_converged=0: iteration cap hit before convergence" }, // Form-conditional map roots whose clauses (kRankByDisclosure, kChurnRankLegend, --around's seed block) are prose. // window= and defs= are ELEMENT-qualified: --hotspots carries window= and --callers defs=, each meaning something else. diff --git a/src/mapinventory.h b/src/mapinventory.h index c27873d66..718ee082e 100644 --- a/src/mapinventory.h +++ b/src/mapinventory.h @@ -48,14 +48,15 @@ struct MapInventory std::vector entries; // path order, then symbol id; at most kInventoryEntryCap std::size_t entryTotal = 0; // every entry found (entries.size() < entryTotal ⇒ cut, disclosed) std::vector dirs; // path order - std::size_t namesCut = 0; // code files counted but not named: the name ceiling was reached + std::size_t namesTotal = 0; // every unshown code file a directory row could name + bool namesCapped = false; // the name ceiling left some of them counted, not named (disclosed) }; // RUNAWAY GUARDS (PROCESS rule 5a), far above any measured answer: the eleven orient repos of the round-1 table name at -// most 431 files (textual) and list at most 6 entries (gotestsum), so neither guard fires on them. A monorepo of +// most 199 files (htop) and list at most 5 entries (gotestsum), so neither guard fires on them. A monorepo of // thousands of unshown code files would otherwise print every name (~13 B each). Past the ceiling, directories in path -// order keep n= and drop f= (names_cut= on says how many files that left unnamed; ripwire on p= ranks them); -// past the entry cap, entry_total= says how many entries exist. +// order keep n= and drop f= (names_capped="1" names_total=N on ; ripwire on p= ranks them); past the entry cap, +// entries_capped="1" entries_total=N. inline constexpr std::size_t kInventoryNameCeiling = 2000; inline constexpr std::size_t kInventoryEntryCap = 32; @@ -238,11 +239,15 @@ inline MapInventory computeMapInventory( const IngestResult& ing, const std::vec // the name ceiling: whole directories, path order — a directory is named completely or not at all, so f= is never a // silent part-list std::size_t namedSoFar = 0; + for( const MapInventoryDir& d : inv.dirs ) + { + inv.namesTotal += d.named.size(); + } for( MapInventoryDir& d : inv.dirs ) { if( namedSoFar + d.named.size() > kInventoryNameCeiling ) { - inv.namesCut += d.named.size(); + inv.namesCapped = inv.namesCapped || !d.named.empty(); d.named.clear(); namedSoFar = kInventoryNameCeiling; // every later directory is count-only too: one contiguous cut continue; @@ -261,8 +266,8 @@ inline constexpr std::string_view kMapInventoryLegend = "or a __main__.py module scope, with its resolved callees as c rows; entries declared only in a manifest (package.json " "main/bin, console_scripts) are not detected. ls p= n= f=: n= unshown files under directory p=, f= the code files " "among them by name; test, doc and config files are counted, not named, and a test/bench/fixture/example/doc " - "directory rolls up whole. ripwire on p= ranks that directory. Runaway guards: entry_total=N when more than 32 " - "entries exist (the first 32 by path shown); names_cut=N code files left unnamed past a 2000 name ceiling " - "(directories after it, path order, keep n= only) -->"; + "directory rolls up whole. ripwire on p= ranks that directory. Runaway guards: entries_capped=1 entries_total=N " + "when more than 32 entries exist (the first 32 by path shown); names_capped=1 names_total=N when the N unshown " + "code files pass a 2000 name ceiling (directories past it, path order, keep n= only) -->"; } // namespace rw diff --git a/src/serialize.h b/src/serialize.h index e2d08ca5d..c6fcb1b43 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -2770,8 +2770,8 @@ inline void writeMapInventoryXml( XmlWriter& w, const IngestResult& ing, const M EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( " inv.entries.size() ) { w.write( " entry_total=\"" ); w.write( std::to_string( inv.entryTotal ) ); w.write( "\"" ); } - if( inv.namesCut > 0 ) { w.write( " names_cut=\"" ); w.write( std::to_string( inv.namesCut ) ); w.write( "\"" ); } + if( inv.entryTotal > inv.entries.size() ) { w.write( " entries_capped=\"1\" entries_total=\"" ); w.write( std::to_string( inv.entryTotal ) ); w.write( "\"" ); } + if( inv.namesCapped ) { w.write( " names_capped=\"1\" names_total=\"" ); w.write( std::to_string( inv.namesTotal ) ); w.write( "\"" ); } w.write( ">" ); for( NodeId id : inv.entries ) { @@ -9532,8 +9532,8 @@ inline void writeMapInventoryJson( JsonWriter& w, const IngestResult& ing, const EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( ",\"inv\":{\"listed\":" ); w.write( std::to_string( inv.listed ) ); w.write( ",\"unlisted\":" ); w.write( std::to_string( inv.unlisted ) ); - if( inv.entryTotal > inv.entries.size() ) { w.write( ",\"entry_total\":" ); w.write( std::to_string( inv.entryTotal ) ); } - if( inv.namesCut > 0 ) { w.write( ",\"names_cut\":" ); w.write( std::to_string( inv.namesCut ) ); } + if( inv.entryTotal > inv.entries.size() ) { w.write( ",\"entries_capped\":true,\"entries_total\":" ); w.write( std::to_string( inv.entryTotal ) ); } + if( inv.namesCapped ) { w.write( ",\"names_capped\":true,\"names_total\":" ); w.write( std::to_string( inv.namesTotal ) ); } w.write( ",\"entry\":[" ); for( std::size_t i = 0; i < inv.entries.size(); ++i ) { diff --git a/test/mapinventorycheck.sh b/test/mapinventorycheck.sh index 4e44b05a9..488bcab5a 100755 --- a/test/mapinventorycheck.sh +++ b/test/mapinventorycheck.sh @@ -52,8 +52,8 @@ if grep -q ']*>.*' "$TMP/map"; then ok "(B) follows # (C) entries: exactly the program entries by convention, sorted by path ENTRIES="$( printf '%s' "$INV" | grep -o '"$TMP/g32.out" 2>/dev/null "$BIN" "$TMP/g33" --no-cache --top-k=1 >"$TMP/g33.out" 2>/dev/null NC1="$( grep -o '' "$TMP/c2002.out" && grep -q '' "$TMP/c2002.out"; then ok "(P) 2001 past the ceiling: the dir keeps n= only, names_cut=2001"; else fail "(P) past the ceiling: $( grep -o ']*>' "$TMP/c2002.out" ) $( grep -o '' "$TMP/c2002.out" && grep -q '' "$TMP/c2002.out"; then ok "(P) 2001 past the ceiling: the dir keeps n= only, names_capped=1 names_total=2001"; else fail "(P) past the ceiling: $( grep -o ']*>' "$TMP/c2002.out" ) $( grep -o ' Date: Thu, 8 Oct 2026 21:00:02 -0400 Subject: [PATCH 083/170] test(mapinventory): near-miss fixtures for the trigger (an empty module) and a test-named guarded main Co-Authored-By: Claude Opus 5.5 --- test/mapinventorycheck.sh | 4 ++-- test/mapinvfix/py/pkg/__init__.py | 0 test/mapinvfix/py/test_runner.py | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 test/mapinvfix/py/pkg/__init__.py create mode 100644 test/mapinvfix/py/test_runner.py diff --git a/test/mapinventorycheck.sh b/test/mapinventorycheck.sh index 488bcab5a..de91421a6 100755 --- a/test/mapinventorycheck.sh +++ b/test/mapinventorycheck.sh @@ -55,8 +55,8 @@ ENTRIES="$( printf '%s' "$INV" | grep -o ' Date: Thu, 8 Oct 2026 21:05:28 -0400 Subject: [PATCH 084/170] test(shapingflag): re-pin the --max-tokens / --top-k read sites for the lean-default predicate Co-Authored-By: Claude Sonnet 5.5 --- test/shapingflagcheck.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/shapingflagcheck.sh b/test/shapingflagcheck.sh index 5ac0b39c8..b8b8d01ff 100755 --- a/test/shapingflagcheck.sh +++ b/test/shapingflagcheck.sh @@ -111,12 +111,16 @@ git status --porcelain 2>/dev/null | grep -vE '^\?\? (build|asan|tsan)' | LC_ALL # decide whether the ride-along note='s bytes should be charged to the search/verdict, the SAME predicate # `noteAppliesToBundle` already evaluates further down for the SAME two verbs (--expand/--outline). It does # not change which verbs honour --top-k, only fixes an existing verb's ceiling math, so this is a re-pin. +# expand-lean-k64 (2026-10-08, 21->22 / 14->15): ONE new read of each, the same kind as V1's. `leanEligible` (src/main.cpp) +# is the multi-definition twin of V1's exact-name predicate — it reads cfg.topKExplicit and cfg.maxTokens to decide whether +# the lean default applies (an explicit --top-k and a --max-tokens-sized map both stand it aside). --expand/--outline +# already honour both flags in kShapingVerbs, so this is a re-pin, not a column change. MAXSITES="$( grep -c 'cfg\.maxTokens\|c\.maxTokens' src/main.cpp src/verbs_*.h src/mcpserver.h 2>/dev/null | awk -F: '{s+=$2} END{print s+0}' )" TOPSITES="$( grep -c 'cfg\.topK\|c\.topK' src/main.cpp src/verbs_*.h src/mcpserver.h 2>/dev/null | awk -F: '{s+=$2} END{print s+0}' )" -[ "$MAXSITES" = 21 ] && ok "(A) --max-tokens has 21 read sites outside cli.h (grep 'cfg\\.maxTokens' src/main.cpp src/verbs_*.h src/mcpserver.h)" \ - || no "(A) --max-tokens read sites moved 21 -> $MAXSITES: a verb gained or lost the budget, so kShapingVerbs' honorsMaxTokens column must be re-decided (and this number re-pinned)" -[ "$TOPSITES" = 14 ] && ok "(A) --top-k has 14 read sites outside cli.h" \ - || no "(A) --top-k read sites moved 14 -> $TOPSITES: re-decide kShapingVerbs' honorsTopK column and re-pin this number" +[ "$MAXSITES" = 22 ] && ok "(A) --max-tokens has 22 read sites outside cli.h (grep 'cfg\\.maxTokens' src/main.cpp src/verbs_*.h src/mcpserver.h)" \ + || no "(A) --max-tokens read sites moved 22 -> $MAXSITES: a verb gained or lost the budget, so kShapingVerbs' honorsMaxTokens column must be re-decided (and this number re-pinned)" +[ "$TOPSITES" = 15 ] && ok "(A) --top-k has 15 read sites outside cli.h" \ + || no "(A) --top-k read sites moved 15 -> $TOPSITES: re-decide kShapingVerbs' honorsTopK column and re-pin this number" # no OTHER file may read them: a third file would be a verb family this table has never heard of. # 2026-08-29 main.cpp split: src/verbs_*.h are SECTIONS of main.cpp's own TU (RIPWIRE_MAIN_TU-guarded), # so they count as main.cpp in this derivation — the counts above sweep them, the exclusion below too. From 1347f532e8ca03a44740d2b6106e635d62ac2ac2 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 21:24:02 -0400 Subject: [PATCH 085/170] map inventory: drop the redundant test-path clause from entry eligibility (an equivalent mutant) isTestSymbol already refuses a candidate def on a test path, and every test directory isTestPath recognizes is a rolled-up directory, so the clause could be deleted with no gate going red. It is gone rather than kept unguarded. Co-Authored-By: Claude Opus 5.5 --- src/mapinventory.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mapinventory.h b/src/mapinventory.h index 718ee082e..ce3c5f8b0 100644 --- a/src/mapinventory.h +++ b/src/mapinventory.h @@ -156,8 +156,9 @@ inline MapInventory computeMapInventory( const IngestResult& ing, const std::vec }; const auto entryEligibleFile = [ & ]( std::uint32_t f ) noexcept { - const std::string_view rel = rootRelPath( ing, f ); - return !isTestPath( rel ) && inventoryRollupDirOf( rel ).empty(); + // test PATHS need no clause here: isTestSymbol (below) covers a candidate def, and every test directory isTestPath + // knows (test/, tests/, __tests__/) is a rolled-up directory, which covers a __main__.py scope + return inventoryRollupDirOf( rootRelPath( ing, f ) ).empty(); }; std::vector candidate( S, 0 ); for( NodeId id = 0; id < S; ++id ) From e09410c38826624d963a6b126faaf4d901cda4de Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 21:33:00 -0400 Subject: [PATCH 086/170] map inventory: reuse the shared path splitters, split the selection into entries / directory rows / ceiling quality-delta flagged the two new dir/basename helpers as clones of resolve.h includerDir and namesplit::afterLast; both now call those. The selection is three named steps instead of one 54-branch function. Co-Authored-By: Claude Opus 5.5 --- src/mapinventory.h | 138 +++++++++++++++++++++++++-------------------- src/serialize.h | 4 +- 2 files changed, 78 insertions(+), 64 deletions(-) diff --git a/src/mapinventory.h b/src/mapinventory.h index ce3c5f8b0..89a475529 100644 --- a/src/mapinventory.h +++ b/src/mapinventory.h @@ -28,7 +28,9 @@ #include #include "model.h" -#include "filter.h" // isTestPath / isTestSymbol / pathTierOf / hasDirSegment — the shared test-path conventions +#include "filter.h" // isTestSymbol — the shared test-path convention +#include "resolve.h" // includerDir — the shared directory-of-a-path splitter +#include "infra/namesplit.h" // namesplit::afterLast — the shared basename splitter namespace rw { @@ -93,16 +95,12 @@ inline std::string_view inventoryRollupDirOf( std::string_view rel ) noexcept return rel.substr( 0, best + bestLen - 1 ); // without the trailing '/' } +// directory and basename of a display path: the shared splitters (resolve.h includerDir, namesplit afterLast), with +// the root's files grouped under "." inline std::string_view inventoryDirOf( std::string_view rel ) noexcept { - const std::size_t slash = rel.rfind( '/' ); - return slash == std::string_view::npos ? std::string_view( "." ) : rel.substr( 0, slash ); -} - -inline std::string_view inventoryBaseOf( std::string_view rel ) noexcept -{ - const std::size_t slash = rel.rfind( '/' ); - return slash == std::string_view::npos ? rel : rel.substr( slash + 1 ); + const std::string_view dir = includerDir( rel ); + return dir.empty() ? std::string_view( "." ) : dir; } // A code symbol: anything a reader can open as a definition — not a data Section (doc heading, JSON/YAML key) and @@ -112,54 +110,23 @@ inline bool isInventoryCodeSymbol( const Symbol& s ) noexcept return s.kind != SymKind::Section && s.kind != SymKind::ModuleScope; } -// `shownFile[f]` != 0 ⇔ the ranked rows printed a group for file f. `pathRel` maps a file id to its display path -// (the same root-relative form every uses). +// A program entry by name convention: a def named main (Main for a method) outside the rolled-up directories and test +// code, whose only in-repo callers are its own file's module scope; or a __main__.py module scope. Path order, at most +// kInventoryEntryCap kept (entryTotal counts them all). template -inline MapInventory computeMapInventory( const IngestResult& ing, const std::vector& outOff, - const std::vector& outTargets, const std::vector& shownFile, - const PathRel& pathRel ) +inline void collectInventoryEntries( MapInventory& inv, const IngestResult& ing, const std::vector& outOff, + const std::vector& outTargets, const PathRel& pathRel ) { const std::size_t F = ing.files.size(); const std::size_t S = ing.symbols.size(); - EXPECTS( shownFile.size() == F, "one shown bit per indexed file" ); - EXPECTS( outOff.size() >= S + 1 || S == 0, "outOff is the CSR offset array over the symbols" ); - MapInventory inv; - - // code symbols per file: a file with none is counted, never named - std::vector codeSyms( F, 0 ); - for( const Symbol& s : ing.symbols ) - { - if( s.fileId < F && isInventoryCodeSymbol( s ) ) - { - ++codeSyms[ s.fileId ]; - } - } - // Present-only: the tier rides a map whose ranked rows LEFT OUT a file with code in it. A map that shows every - // such file has nothing to inventory (its unshown files are empty or data-only) and stays byte-identical. - bool anyCut = false; - for( std::size_t f = 0; f < F; ++f ) - { - if( !shownFile[ f ] ) { ++inv.unlisted; } else { ++inv.listed; } - anyCut = anyCut || ( !shownFile[ f ] && codeSyms[ f ] > 0 ); - } - if( !anyCut ) - { - return MapInventory{}; - } - inv.active = true; - - // ── entries ── const auto isEntryName = [ & ]( const Symbol& s ) noexcept { return ( s.kind == SymKind::Function && s.name == "main" ) || ( s.kind == SymKind::Method && ( s.name == "main" || s.name == "Main" ) ); }; - const auto entryEligibleFile = [ & ]( std::uint32_t f ) noexcept - { - // test PATHS need no clause here: isTestSymbol (below) covers a candidate def, and every test directory isTestPath - // knows (test/, tests/, __tests__/) is a rolled-up directory, which covers a __main__.py scope - return inventoryRollupDirOf( rootRelPath( ing, f ) ).empty(); - }; + // test PATHS need no clause here: isTestSymbol (below) covers a candidate def, and every test directory isTestPath + // knows (test/, tests/, __tests__/) is a rolled-up directory, which covers a __main__.py scope + const auto entryEligibleFile = [ & ]( std::uint32_t f ) noexcept { return inventoryRollupDirOf( rootRelPath( ing, f ) ).empty(); }; std::vector candidate( S, 0 ); for( NodeId id = 0; id < S; ++id ) { @@ -183,7 +150,7 @@ inline MapInventory computeMapInventory( const IngestResult& ing, const std::vec { const Symbol& s = ing.symbols[ id ]; const bool mainModule = s.kind == SymKind::ModuleScope && s.fileId < F - && inventoryBaseOf( rootRelPath( ing, s.fileId ) ) == "__main__.py" && entryEligibleFile( s.fileId ); + && namesplit::afterLast( rootRelPath( ing, s.fileId ), "/" ) == "__main__.py" && entryEligibleFile( s.fileId ); if( candidate[ id ] || mainModule ) { inv.entries.push_back( id ); @@ -196,19 +163,22 @@ inline MapInventory computeMapInventory( const IngestResult& ing, const std::vec { inv.entries.resize( kInventoryEntryCap ); } +} - // ── ls rows ── - std::vector unshown; - for( std::uint32_t f = 0; f < F; ++f ) - { - if( !shownFile[ f ] ) { unshown.push_back( f ); } - } - // group key: the rolled-up dir when the file sits under one, else its own dir +// One row per directory over the unshown files, path order. A file under a rolled-up directory groups under that +// directory and is counted; elsewhere a code file (codeSyms > 0, not test-named) is named, every other file counted. +template +inline void collectInventoryDirs( MapInventory& inv, const IngestResult& ing, const std::vector& shownFile, + const std::vector& codeSyms, const PathRel& pathRel ) +{ struct Keyed { std::string key; bool rolled; std::uint32_t f; }; std::vector keyed; - keyed.reserve( unshown.size() ); - for( std::uint32_t f : unshown ) + for( std::uint32_t f = 0; f < ing.files.size(); ++f ) { + if( shownFile[ f ] ) + { + continue; + } // classify on the ROOT-RELATIVE path (a checkout living under a tests/ dir is not a test tree — #228), group // and print in the display form every uses const std::string_view rel = rootRelPath( ing, f ); @@ -237,24 +207,68 @@ inline MapInventory computeMapInventory( const IngestResult& ing, const std::vec d.named.push_back( k.f ); } } - // the name ceiling: whole directories, path order — a directory is named completely or not at all, so f= is never a - // silent part-list - std::size_t namedSoFar = 0; +} + +// The name ceiling: whole directories, path order — a directory is named completely or not at all, so f= is never a +// silent part-list; past the ceiling every later directory is count-only too (one contiguous cut). +inline void applyInventoryNameCeiling( MapInventory& inv ) noexcept +{ for( const MapInventoryDir& d : inv.dirs ) { inv.namesTotal += d.named.size(); } + std::size_t namedSoFar = 0; for( MapInventoryDir& d : inv.dirs ) { if( namedSoFar + d.named.size() > kInventoryNameCeiling ) { inv.namesCapped = inv.namesCapped || !d.named.empty(); d.named.clear(); - namedSoFar = kInventoryNameCeiling; // every later directory is count-only too: one contiguous cut + namedSoFar = kInventoryNameCeiling; continue; } namedSoFar += d.named.size(); } + ENSURES( namedSoFar <= kInventoryNameCeiling, "the printed names stay within the ceiling" ); +} + +// `shownFile[f]` != 0 ⇔ the ranked rows printed a group for file f. `pathRel` maps a file id to its display path +// (the same root-relative form every uses). +template +inline MapInventory computeMapInventory( const IngestResult& ing, const std::vector& outOff, + const std::vector& outTargets, const std::vector& shownFile, + const PathRel& pathRel ) +{ + const std::size_t F = ing.files.size(); + EXPECTS( shownFile.size() == F, "one shown bit per indexed file" ); + EXPECTS( outOff.size() >= ing.symbols.size() + 1 || ing.symbols.empty(), "outOff is the CSR offset array over the symbols" ); + MapInventory inv; + + // code symbols per file: a file with none is counted, never named + std::vector codeSyms( F, 0 ); + for( const Symbol& s : ing.symbols ) + { + if( s.fileId < F && isInventoryCodeSymbol( s ) ) + { + ++codeSyms[ s.fileId ]; + } + } + // Present-only: the tier rides a map whose ranked rows LEFT OUT a file with code in it. A map that shows every + // such file has nothing to inventory (its unshown files are empty or data-only) and stays byte-identical. + bool anyCut = false; + for( std::size_t f = 0; f < F; ++f ) + { + if( !shownFile[ f ] ) { ++inv.unlisted; } else { ++inv.listed; } + anyCut = anyCut || ( !shownFile[ f ] && codeSyms[ f ] > 0 ); + } + if( !anyCut ) + { + return MapInventory{}; + } + inv.active = true; + collectInventoryEntries( inv, ing, outOff, outTargets, pathRel ); + collectInventoryDirs( inv, ing, shownFile, codeSyms, pathRel ); + applyInventoryNameCeiling( inv ); ENSURES( inv.listed + inv.unlisted == F, "every indexed file is listed or inventoried" ); return inv; } diff --git a/src/serialize.h b/src/serialize.h index c6fcb1b43..4c30aa27a 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -2791,7 +2791,7 @@ inline void writeMapInventoryXml( XmlWriter& w, const IngestResult& ing, const M for( std::uint32_t f : d.named ) { if( !names.empty() ) { names += ','; } - names += inventoryBaseOf( pathRel( f ) ); + names += namesplit::afterLast( pathRel( f ), "/" ); } w.write( " f=\"" ); w.write( escapeXml( names, esc ) ); w.write( "\"" ); } @@ -9569,7 +9569,7 @@ inline void writeMapInventoryJson( JsonWriter& w, const IngestResult& ing, const for( std::size_t j = 0; j < d.named.size(); ++j ) { if( j ) { w.write( "," ); } - writeJsonStr( w, inventoryBaseOf( pathRel( d.named[ j ] ) ), esc ); + writeJsonStr( w, namesplit::afterLast( pathRel( d.named[ j ] ), "/" ), esc ); } w.write( "]" ); } From 164b3548b431c1d71922b9c798f69a98a691c1de Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 21:36:10 -0400 Subject: [PATCH 087/170] map inventory: inline the root-dir spelling (a three-line helper read as a clone) Co-Authored-By: Claude Opus 5.5 --- src/mapinventory.h | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/mapinventory.h b/src/mapinventory.h index 89a475529..8796a91f4 100644 --- a/src/mapinventory.h +++ b/src/mapinventory.h @@ -95,14 +95,6 @@ inline std::string_view inventoryRollupDirOf( std::string_view rel ) noexcept return rel.substr( 0, best + bestLen - 1 ); // without the trailing '/' } -// directory and basename of a display path: the shared splitters (resolve.h includerDir, namesplit afterLast), with -// the root's files grouped under "." -inline std::string_view inventoryDirOf( std::string_view rel ) noexcept -{ - const std::string_view dir = includerDir( rel ); - return dir.empty() ? std::string_view( "." ) : dir; -} - // A code symbol: anything a reader can open as a definition — not a data Section (doc heading, JSON/YAML key) and // not the synthetic module-scope row every script file carries. inline bool isInventoryCodeSymbol( const Symbol& s ) noexcept @@ -186,7 +178,8 @@ inline void collectInventoryDirs( MapInventory& inv, const IngestResult& ing, co const std::string_view rolled = inventoryRollupDirOf( rel ); if( rolled.empty() ) { - keyed.push_back( Keyed{ std::string( inventoryDirOf( disp ) ), false, f } ); + const std::string_view dir = includerDir( disp ); // the shared directory splitter; the root's files group under "." + keyed.push_back( Keyed{ std::string( dir.empty() ? std::string_view( "." ) : dir ), false, f } ); continue; } const std::size_t prefix = disp.ends_with( rel ) ? disp.size() - rel.size() : 0; From eec9bdcbddb05f14fed65cd82731682ddfc6e0ec Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 21:44:50 -0400 Subject: [PATCH 088/170] test: re-pin the expand-file legend (with its old-pin twin); outline id lookup asks for the map The lean multi-definition answer reaches the whole-file serving with topk_default and map_next on its root, so ripwire.expand-file/v1's compact prose pin moves 360 -> 510 and a twin keeps the old 360 B pin for the rest of that legend. The usesselector canonical-id lookup passes --top-k=200 to --outline (the map it reads is no longer the default). map_next's legend sentence is shortened. Co-Authored-By: Claude Sonnet 5.5 --- src/compactlegend.h | 6 +++--- test/compactlegendcheck.sh | 39 +++++++++++++++++++++++++++++++++++++- test/usesselectorcheck.sh | 4 +++- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/compactlegend.h b/src/compactlegend.h index c25db294d..277b196d7 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -1189,13 +1189,13 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "mode", "mode=/reason=: bundle served, not the whole file; reason= gives both byte counts or the cause; explicit top-k opts out", false, "ctx", MapHeaderRead::No, {}, "expand" }, // also defines reason= { "topk_default", "topk_default=0: an exact name was expanded, so the ranked map was dropped; pass top-k=N to get it back", false, "ctx", MapHeaderRead::No, {}, "expand" }, // expand-lean-k64: a multi-definition name whose definitions were ALL served drops the map too (map_next=), and a cut one names what it did not serve - { "map_next", "map_next=: the shell call that prints the ranked map this answer did not ship (orient once per session, not per lookup)", false, "ctx", MapHeaderRead::No, {}, "expand" }, + { "map_next", "map_next=: the shell call printing the map this answer omits", false, "ctx", MapHeaderRead::No, {}, "expand" }, { "unserved_total", "unserved_total=N: N requested definitions have no body here (budget or unreadable); unserved_next= is the call serving them", false, "ctx", MapHeaderRead::No, {}, "expand" }, // also defines unserved_next= { "inc_total", "inc_total=N: the file's true include/import count; inc= lists the first 24, inc_capped=1 when cut", true, "b", MapHeaderRead::No, {}, "expand" }, // expand (whole-file serving): src/main.cpp chooseExpandServe (fileOpen) + the topk_default insert { "mode", "mode=/reason=: whole-file won, the file text cost fewer bytes than the bundle; reason= gives both sizes", false, "ctx", MapHeaderRead::No, {}, "expand-file" }, // also defines reason= { "topk_default", "topk_default=0: an exact name was expanded, so the ranked map was dropped; pass top-k=N to get it back", false, "ctx", MapHeaderRead::No, {}, "expand-file" }, - { "map_next", "map_next=: the shell call that prints the ranked map this answer did not ship (orient once per session, not per lookup)", false, "ctx", MapHeaderRead::No, {}, "expand-file" }, + { "map_next", "map_next=: the shell call printing the map this answer omits", false, "ctx", MapHeaderRead::No, {}, "expand-file" }, // slice: src/slice.h (the root emit, kSliceCountsAttrXml) { "sym", "sym=/lang=: the sliced definition's name and language; p= is its file:line", false, "slice", MapHeaderRead::No, {}, "slice" }, // also defines lang= { "vars", "vars=N: sliceable local bindings in the definition, one v row each; name one to slice it", false, "slice", MapHeaderRead::No, {}, "slice" }, @@ -1241,7 +1241,7 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "err", "err=: why an ok=0 sub-query failed; no payload follows", true, "q", MapHeaderRead::No, {}, "batch" }, // pack-signatures (--outline whose definitions were all served): src/main.cpp (ctxLeanAttr / ctxUnservedAttr) { "topk_default", "topk_default=0: the outlines asked for were all served, so the ranked map was dropped; pass top-k=N to get it back", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, - { "map_next", "map_next=: the shell call that prints the ranked map this answer did not ship (orient once per session, not per lookup)", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, + { "map_next", "map_next=: the shell call printing the map this answer omits", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, { "unserved_total", "unserved_total=N: N requested definitions have no outline here (budget or unreadable); unserved_next= is the call serving them", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, // also defines unserved_next= // pack-signatures: src/serialize.h ( from src/arch.h builtinLayer) { "layer", "layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none", true, "f", MapHeaderRead::No, {}, "pack-signatures" }, diff --git a/test/compactlegendcheck.sh b/test/compactlegendcheck.sh index 7e575a652..00afbbc2e 100755 --- a/test/compactlegendcheck.sh +++ b/test/compactlegendcheck.sh @@ -551,6 +551,10 @@ probeFor() # the defs page whenever it lists MORE definitions than the all page under the row cap, whatever its bytes. Rides only a # page that carries listing=; nothing else moved. The OLD path keeps its pin: the whereis-listing=all twin holds the # whole-list page's legend at 700 B (measured 692, unchanged). +# RE-PINNED 2026-10-08 (lane/expand-lean-k64): ripwire.expand-file/v1 360 -> 510 (measured 508, the --expand=distance probe). +# A name with several definitions now serves lean (no ride-along map), so the whole-file answer this probe reaches carries +# topk_default="0" (+112 B reading) and map_next= (+56 B reading) on its root, both present-only (compactlegend.h). The +# (U-twin-expand) arm below keeps the OLD 360 B pin for everything else in that legend. PIN_TABLE=' ripwire.edit-check/v1 610 595 ripwire.map/v1 910 892 @@ -618,7 +622,7 @@ ripwire.layout/v1 1220 1203 ripwire.pack-task/v1 990 974 ripwire.pack-top-n/v1 760 745 ripwire.expand/v1 280 265 -ripwire.expand-file/v1 360 342 +ripwire.expand-file/v1 510 508 ripwire.for/v1 775 761 ' pinFor() @@ -658,6 +662,39 @@ else fi ;; esac fi +# (U-twin-expand) the pre-2026-10-08 ripwire.expand-file/v1 pin (360 B), kept (PROCESS rule 4): the same --expand=distance probe +# with exactly the two lean-default readings cut out of a copy of the compact answer must still fit the OLD pin. The cut is +# taken only when BOTH readings occur once; any other premise FAILs (a cut that removes nothing proves nothing). +( cd "$REPO" && "$BIN" . --expand=distance --legend=compact >"$TMP/xft.c" 2>/dev/null "$TMP/xft.full" 2>/dev/null /dev/null && [ "$xftRest" -le 360 ]; then + ok "(U-twin-expand) expand-file compact prose minus the two lean-default readings ($xftCut B) is $xftRest B <= 360 B (the pre-lean pin)" + else + no "(U-twin-expand) expand-file compact prose minus the two lean-default readings ($xftCut B) is ${xftRest:-?} B > 360 B — the rest re-inflated" + fi ;; + esac +fi nXml=0; nXmlBad=0; nRefuse=0; nSkip=0; loopBytes=0; xmlVerbs=""; nDefBad=0 # (UG) rv-r1-L1-2: EVERY XML verb the flag universe reaches, EVERY instance of every attribute its DEFAULT answer carries, # defined `name=` in that answer's own legend (legendcoveragecheck (G)'s predicate, on this gate's fixture). No floor. diff --git a/test/usesselectorcheck.sh b/test/usesselectorcheck.sh index 2117c262c..cde67bc80 100755 --- a/test/usesselectorcheck.sh +++ b/test/usesselectorcheck.sh @@ -142,7 +142,9 @@ grep -q 'srcmut_sigchange' "$TMP/err" && no "the constant nonsense suggestion (s # fetch a canonical id; --outline always rides the classic 200-row map (no V1 lean default applies to # it) and its rows carry id= unconditionally, so it is the stable lookup path here. # row 6 (2026-09-12): the row prints the short id sc=; the canonical id composes as ::sc::n -CANON_ID="$( "$BIN" "$ROOT" --outline='src/notes.h:empty' --no-cache 2>/dev/null | python3 -c ' +# expand-lean-k64 (2026-10-08): --outline of definitions it fully serves no longer rides the map by default; the lookup asks +# for the classic ranked map explicitly (--top-k=200, the old default), which is what carries the id= rows read here. +CANON_ID="$( "$BIN" "$ROOT" --outline='src/notes.h:empty' --top-k=200 --no-cache 2>/dev/null | python3 -c ' import re, sys doc, name, scope = sys.stdin.read(), sys.argv[1], ( sys.argv[2] if len( sys.argv ) > 2 else None ) for f in re.finditer( r"]*>(.*?)", doc, re.S ): From 4940593742259984073f5efbbb6a49537b856555 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 22:00:49 -0400 Subject: [PATCH 089/170] docs(tuning): regenerate for the two inventory caps Co-Authored-By: Claude Opus 5.5 --- docs/TUNING.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/TUNING.md b/docs/TUNING.md index 0dc297ff9..7440a569c 100644 --- a/docs/TUNING.md +++ b/docs/TUNING.md @@ -14,26 +14,26 @@ to production at defaults; that control is what makes these numbers mean anythin | cap declarations | distinct names | tunable | must stay `constexpr` | move >= 1 invocation | move nothing measurable | | --- | --- | --- | --- | --- | --- | -| 150 | 149 | 112 | 12 | **37** | 75 | +| 152 | 151 | 112 | 12 | **37** | 75 | -The first two columns are not the same number, and the gap is not a rounding: `src/` holds **150 cap -declarations** under **149 distinct names** (`kRowCap` declared in more than one file). The sweep +The first two columns are not the same number, and the gap is not a rounding: `src/` holds **152 cap +declarations** under **151 distinct names** (`kRowCap` declared in more than one file). The sweep patches by NAME, so its own population is NAMES and not declarations — and the two columns beside those are frozen at the commit named under Provenance while this census is re-read from `src/` on -every run, which makes them a third population again. Enumerated over the 149 names `src/` declares -today: **111 tunable**, **12 must stay `constexpr`**, and **26 declared since the sweep was +every run, which makes them a third population again. Enumerated over the 151 names `src/` declares +today: **111 tunable**, **12 must stay `constexpr`**, and **28 declared since the sweep was prepared, which no measurement has touched** (`kChurnMergeBombMaxFiles`, `kExtendedLengthThresholdUnits`, `kFieldIdCapacity`, `kForPageRowsDefault`, -`kForPageUnionSymbolCap`, `kMaxAstQueryNesting`, +20 more). 111 + 12 + 26 = 149, and `emit` refuses +`kForPageUnionSymbolCap`, `kInventoryEntryCap`, +22 more). 111 + 12 + 28 = 151, and `emit` refuses to render a partition that does not add up. The `tunable` column above reads 112 rather than 111 because one name the sweep classified is no longer declared in `src/` at all: `kSituTestRowsShown`. -Quoting "113 of 150" would be wrong in both halves at once, which is the shape of error a generated +Quoting "113 of 152" would be wrong in both halves at once, which is the shape of error a generated table exists to prevent. ## Read this ratio before the tables **37 of 112 tunable caps move any invocation at all. 75 move nothing measurable.** That is the -finding, and it says what NOT to do: this is not a 150-cap audit. Most of these constants are +finding, and it says what NOT to do: this is not a 152-cap audit. Most of these constants are inert on real invocations and should be left alone. The work worth doing is the small set below, plus the caps that fire SILENTLY — a cap that bites without disclosing is a defect independent of whether its value is right, and that fix is both cheaper and larger than any retuning. @@ -61,7 +61,7 @@ moving. Re-run `prepare|screen|sweep` to refresh them. ### `kForLensDefaultTopN` = `40` -`src/serialize.h` — discloses: `calls_capped`, `inc_capped`, `sibs_capped` — probe value `320` — **12 verb(s) respond** +`src/serialize.h` — discloses: `calls_capped`, `entries_capped`, `inc_capped`, `names_capped`, `sibs_capped` — probe value `320` — **12 verb(s) respond** | invocation | default | at probe | delta | | --- | --- | --- | --- | @@ -117,7 +117,7 @@ moving. Re-run `prepare|screen|sweep` to refresh them. ### `kForFileTailShownCap` = `24` -`src/serialize.h` — discloses: `calls_capped`, `inc_capped`, `sibs_capped` — probe value `192` — **10 verb(s) respond** +`src/serialize.h` — discloses: `calls_capped`, `entries_capped`, `inc_capped`, `names_capped`, `sibs_capped` — probe value `192` — **10 verb(s) respond** | invocation | default | at probe | delta | | --- | --- | --- | --- | @@ -134,7 +134,7 @@ moving. Re-run `prepare|screen|sweep` to refresh them. ### `kForPayloadBudgetBytes` = `7500` -`src/serialize.h` — discloses: `calls_capped`, `inc_capped`, `sibs_capped` — probe value `60000` — **10 verb(s) respond** +`src/serialize.h` — discloses: `calls_capped`, `entries_capped`, `inc_capped`, `names_capped`, `sibs_capped` — probe value `60000` — **10 verb(s) respond** | invocation | default | at probe | delta | | --- | --- | --- | --- | @@ -151,7 +151,7 @@ moving. Re-run `prepare|screen|sweep` to refresh them. ### `kForCapTailSigBytes` = `96` -`src/serialize.h` — discloses: `calls_capped`, `inc_capped`, `sibs_capped` — probe value `768` — **8 verb(s) respond** +`src/serialize.h` — discloses: `calls_capped`, `entries_capped`, `inc_capped`, `names_capped`, `sibs_capped` — probe value `768` — **8 verb(s) respond** | invocation | default | at probe | delta | | --- | --- | --- | --- | @@ -166,7 +166,7 @@ moving. Re-run `prepare|screen|sweep` to refresh them. ### `kMaxExpandSibs` = `100` -`src/serialize.h` — discloses: `calls_capped`, `inc_capped`, `sibs_capped` — probe value `800` — **5 verb(s) respond** +`src/serialize.h` — discloses: `calls_capped`, `entries_capped`, `inc_capped`, `names_capped`, `sibs_capped` — probe value `800` — **5 verb(s) respond** | invocation | default | at probe | delta | | --- | --- | --- | --- | @@ -219,7 +219,7 @@ moving. Re-run `prepare|screen|sweep` to refresh them. ### `kForAutoBodyBudgetBytes` = `6000` -`src/serialize.h` — discloses: `calls_capped`, `inc_capped`, `sibs_capped` — probe value `48000` — **2 verb(s) respond** +`src/serialize.h` — discloses: `calls_capped`, `entries_capped`, `inc_capped`, `names_capped`, `sibs_capped` — probe value `48000` — **2 verb(s) respond** | invocation | default | at probe | delta | | --- | --- | --- | --- | @@ -373,7 +373,7 @@ moving. Re-run `prepare|screen|sweep` to refresh them. ### `kMaxExpandIncludes` = `24` -`src/serialize.h` — discloses: `calls_capped`, `inc_capped`, `sibs_capped` — probe value `192` — **1 verb(s) respond** +`src/serialize.h` — discloses: `calls_capped`, `entries_capped`, `inc_capped`, `names_capped`, `sibs_capped` — probe value `192` — **1 verb(s) respond** | invocation | default | at probe | delta | | --- | --- | --- | --- | @@ -429,7 +429,7 @@ moving. Re-run `prepare|screen|sweep` to refresh them. ### `kWithGraphNodeCap` = `8` -`src/serialize.h` — discloses: `calls_capped`, `inc_capped`, `sibs_capped` — probe value `64` — **1 verb(s) respond** +`src/serialize.h` — discloses: `calls_capped`, `entries_capped`, `inc_capped`, `names_capped`, `sibs_capped` — probe value `64` — **1 verb(s) respond** | invocation | default | at probe | delta | | --- | --- | --- | --- | From 2857f22c6bf31fc193d547a196d51f0f4efdb0c7 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 22:15:24 -0400 Subject: [PATCH 090/170] test(printfparity): re-pin the expand and help_all labels for the lean multi-definition default Exactly two labels move: --expand=distance (a name with several definitions now serves lean) and --help (the --expand paragraph). Co-Authored-By: Claude Sonnet 5.5 --- test/printf_parity.manifest | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index c44bce03f..310dec377 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -10,7 +10,7 @@ callers 0 31971e419a6df4d59c4464fcdd1afa56c6125e4fc76d096f0203c80cedd9a4a8 e3b0c impact 0 c48ba09589d13cad6011bd6fadbb2898d68e2f5bd97a10bf44324901cfa08d49 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 clones 0 a082f92ce84cd952ced8341e3faff33a23685729bfa4ab6f0eda0f7867e41d7c e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help 0 62646b47d6faa9f090934dc19830bfae16593386c7c361c3cedcf1a0084e1b84 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -expand 0 f3e60d37ab964c4772d794326420441e0b027d1970ea5fb0c60efe6c89f808b8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +expand 0 0cca7164a48d6543694bf07b4252209558d189771d50e913e46783bb98ae4bed e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 callees 0 cbd133fa53917bad05926d46615d4d8b4457ba9285513dc4b193fa57a1ef8b85 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 around 0 ff0b962658da396e7bf3360584de21ab2f0e8bebcd8c0e4e997a77994b004a9f e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 uses 0 5442767b638c8cac9eb0327cbfd5fde73f92fde5b85e2a995670a3982d903711 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 @@ -38,5 +38,5 @@ safe_delete 0 d010f9ca7e45d5bedcde9be49875bbc705632b3011cb9c8c4185f47957d51504 e verify_layer 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5288c345d7d6e335f88b9c1daa8935db22e1dcf89c0c8bc1f6140d4cb5af0b48 graph_query 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 bfa4319feb9dee09cfbd3991cf6fbd752297e99d14de9e75768820d2a9c8832f callers_limit 0 e40ee1ddba0fbf548c58c98b91fe052af8c34f8913edb3edd85245b12a6967b9 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help_all 0 d6c3f9a001f3eb341b5bc7cec50a6647b7f2814cb28726ae310640ed977a7293 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help_all 0 9d9f6074f8c59b4c69306d9bb7ddb34ad690d1d6d6f40dd8ff5c233bdda0e7d2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help_one 0 938ff4f8802a8f5d9cb10f9a1f3804ac755be44c8ab00e24e45bfe8255224a21 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 From 60a28936089ce012a8dd34eeab5ada2101c31f26 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:07:21 +0000 Subject: [PATCH 091/170] =?UTF-8?q?test(paging):=20the=20#362=20CI=20round?= =?UTF-8?q?=20=E2=80=94=20the=20new=20sweep=20arms=20fit=20the=20slowest?= =?UTF-8?q?=20shard's=20budget=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The B1/B2 arms this review round added walked 1232 heavy rows on FIVE surfaces over src/ and pushed the macos Release shard past the sweep's declared 1200s budget (rc=124 at 1201s). Re-anchored onto test/ (152 candidates on the same task — past the 40 top-K the pruning is exact for, so the B1 class is still exercised), the MCP walks capped at 4 pages with a loud FAIL if the corpus ever outgrows the cap, and the pack_task twin's thinner pages (its shell reserves differ) handled by the walk loop rather than a hardcoded two-page assumption. Same six arms, same claims: five surfaces one list (r=1..152, identical order, has_more=0, pruning default), every ranking flag's next= pastes onto the same list. --- test/pagingsweepcheck.sh | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/test/pagingsweepcheck.sh b/test/pagingsweepcheck.sh index 13fdccba0..fe2f24e8d 100755 --- a/test/pagingsweepcheck.sh +++ b/test/pagingsweepcheck.sh @@ -877,15 +877,19 @@ PYM # B1: CLI --for, CLI --pack-task and MCP for/explore/pack_task must page ONE candidate list, with # pruning in its DEFAULT state (no RIPWIRE_NO_PRUNE): a pruned ranking is exact only for its # top-K, so a page that reads past it made total= depend on a performance optimisation -# (measured on src/: 931 vs 1232). Every walk runs to has_more="0" and the five row -# sequences must be identical, r= 1..total, each exactly once. +# (measured on src/: 931 vs 1232; the reproduction corpus there yields 1232 candidates). +# The arm anchors on $ROOT/test (152 candidates on the same task — past the 40 top-K the +# pruning is exact for, so the class is exercised) because walking 1232 heavy rows on +# FIVE surfaces pushed the slowest macos shard past this gate's declared 1200s budget +# (#362 round 4, CI). Every walk runs to has_more="0" and the five row sequences must be +# identical, r= 1..total, each exactly once. # B2: every ACCEPTED flag that changes the ranking rides next= (or the paste re-ranks a different # list — measured: --no-route page 1 total=563, its flagless next= pasted gave total=1). # For each flag: page 1 with it, paste the returned next= verbatim (shlex), and the rows must # equal page 2 of the SAME ranking taken directly. --cochange-boost needs RIPWIRE_DEV=1 on # both runs (its gate), so its env rides the paste too. MCP `for` no_route: the page's next= # (a CLI argv now, B1) must carry --no-route and paste onto the same list. -PGCORPUS="$ROOT/src" +PGCORPUS="$ROOT/test" PGCACHE="$TMP/pg_b1b2.cache" [ -s "$PGCACHE" ] || "$BIN" "$PGCORPUS" --cache="$PGCACHE" >/dev/null 2>&1 PGBIN="$BIN" PGROOT="$PGCORPUS" PGCACHE="$PGCACHE" python3 <<'PGB' @@ -922,15 +926,24 @@ def mcp( verb, args ): return msg["result"]["content"][0]["text"] return "" def walk_mcp( verb, budget, limit ): - seq, off, pages = [], 0, 0 - while pages < 80: + # page 1 alone (its next_offset names page 2's window), page 2 in its own call: two pages + # per surface on this corpus (152 candidates, limit 80), and the arm FAILS loudly if the + # corpus ever grows past two pages — raise the budget here, not a silent partial walk. + def page( off ): doc = mcp( verb, { "path": CORPUS, "task": "how does pagerank converge", "budget_tokens": budget, "limit": limit, "offset": off } ) - if not doc.startswith( " Date: Thu, 8 Oct 2026 23:07:52 -0400 Subject: [PATCH 092/170] fix(token-budget): the gate reads the compacted body's printed price instead of recounting its size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compactPostureMapPrice compacted the map body and then repriced from compacted.size() — the size AFTER the compact layer had rewritten the est_tokens digits themselves. Where the full price has five digits and the compact one four, the root and header are two bytes shorter than the layer priced, and the gate said one token less than the header it asserts (src --top-k=233 on 7608d1f6: header 9965, gate 9964). It now reads the root's est_tokens= (or the map header's field under --stable); the recount is only the fallback for a body that prints no price. tokenbudgetcheck #9b locates the digit boundary on today's src/ and asserts gate == header across it (red on 7608d1f6 at k=232/233/234). Also for the inventory tier: printf parity manifest re-pinned (only help and help_all moved: the --no-inventory line), deck flag count 189, and churnjsonstampcheck compares header keys with the JSON 'inv' payload excluded (asserting it rides the pagerank map and not the churn one). Co-Authored-By: Claude Opus 5.5 --- present/deck5_ripwire_build.js | 6 +++--- src/main.cpp | 29 +++++++++++++++++++++++++++++ test/churnjsonstampcheck.sh | 11 +++++++++++ test/printf_parity.manifest | 4 ++-- test/tokenbudgetcheck.sh | 28 ++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index 9baf2feee..9e5ceadb4 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -195,7 +195,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["byte-identical", "two runs, same bytes — a gate on every push, not a tendency; warm equals cold"], ["zero runtime deps", "CMake + a C++23 compiler; builds with the network off — vendored everything"], ["the languages", "Rust · C++ · ObjC/C++ · C · Metal · CUDA · Python · Go · Swift · TypeScript · JavaScript · Java · Kotlin · Ruby · PHP · Lua · Elixir · Dart · GDScript · Bash · C# · JSON · TOML · YAML · Markdown — 25 vendored grammars, Astro frontmatter on the TypeScript one; markdown headings are real symbols"], // GDScript: CHANGELOG [0.6.2] (#233); Astro: CHANGELOG [0.6.4] (#320) - ["agent-native", "an MCP server and 188 long flags behind one `--help` that is always the authority"], + ["agent-native", "an MCP server and 189 long flags behind one `--help` that is always the authority"], ]; // Row height carries the LONGEST body (the language line, which wraps to three at this width), // not the shortest — a fixed 0.68 clipped its last line off the bottom of the card. @@ -230,7 +230,7 @@ function storyCards(s, { kick, head, stories, footText }){ s.addText(flags, { x: MX+6.3, y: y+0.04, w: 5.65, h: 0.58, fontFace: MONO, fontSize: 9, color: CYAN, valign: "middle", margin: 0 }); y += 0.74; } - foot(s, "--help is generated from the binary's own flag table — 188 long flags; docs/COMMANDS.md carries an entry for every one of them, 168 with a recorded invocation and its output"); + foot(s, "--help is generated from the binary's own flag table — 189 long flags; docs/COMMANDS.md carries an entry for every one of them, 168 with a recorded invocation and its output"); } /* ── S5 · the moments ───────────────────────────────────────────────────── */ @@ -1537,7 +1537,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// do not take any of it on trust", AMBER); title(s, "Every claim, and the command that re-derives it"); const claims = [ - ["188 long flags · 36 slides", "bash test/deckclaimcheck.sh"], + ["189 long flags · 36 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], ["679 gate scripts", "bash test/manifestcheck.sh"], // gatecount diff --git a/src/main.cpp b/src/main.cpp index c1310b69f..fa564b71a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1080,6 +1080,12 @@ inline std::FILE* openTokenBudgetBuffer( rw::MemoryStream& stream, TokenBudgetSt // here the same way the layer will compact it and repriced by the same rule (compactlegend.h compactRepricedTokens), // so the number decided on is the number the root prints. A body the dialect cannot shape keeps its own price — an // empty one (the map streamed unbuffered) and a --json one (no posture) included. +// map-inventory-d7: the number decided on is READ from the compacted body — the root's est_tokens=, or the map header's +// field when --stable keeps the root free of it — never recomputed from compacted.size(). That recount priced the bytes +// AFTER the layer had rewritten the est digits themselves (a 5-digit full price repriced to 4 digits is 2 bytes shorter +// in the root and the header), so on that boundary the gate said one token less than the header it was asserting +// (tokenbudgetcheck #9: src --top-k=233 on 7608d1f6, header 9965, gate 9964). The recount stays only as the fallback for +// a compacted body that prints no price at all. static std::size_t compactPostureMapPrice( const rw::Config& cfg, std::string_view body, std::size_t fullEstTokens ) { if( cfg.legend != "compact" || fullEstTokens == 0 ) @@ -1091,6 +1097,29 @@ static std::size_t compactPostureMapPrice( const rw::Config& cfg, std::string_vi { return fullEstTokens; } + const std::string_view doc = compacted; + const rw::CompactRootInfo root = rw::findCompactRoot( doc ); + if( const std::size_t printed = root.tag.empty() ? 0 : rw::rootUnsignedAttr( doc.substr( root.openBegin, root.openEnd - root.openBegin ), "est_tokens" ); + printed > 0 ) + { + return printed; + } + const std::string_view header = rw::compactMapHeader( doc ); + if( const std::size_t at = header.find( " est_tokens=" ); at != std::string_view::npos ) + { + std::size_t v = 0; + bool any = false; + for( std::size_t i = at + 12; i < header.size() && header[ i ] >= '0' && header[ i ] <= '9'; ++i ) + { + v = v * 10 + std::size_t( header[ i ] - '0' ); + any = true; + } + if( any && v > 0 ) + { + return v; + } + } + // no printed price to read (a body whose root and header carry none): the recount is the only number there is return static_cast( rw::compactRepricedTokens( static_cast( fullEstTokens ), body.size(), compacted.size() ) ); } diff --git a/test/churnjsonstampcheck.sh b/test/churnjsonstampcheck.sh index e3ec111ba..ef7eda89c 100755 --- a/test/churnjsonstampcheck.sh +++ b/test/churnjsonstampcheck.sh @@ -40,6 +40,14 @@ python3 - "$TMP/churn.json" "$TMP/pagerank.json" > "$TMP/verdict.txt" <<'PY' import json, sys churn = json.load( open( sys.argv[1] ) ) plain = json.load( open( sys.argv[2] ) ) +# D7 (2026-10-08): "inv" is PAYLOAD, not header — the JSON twin of the map scope's element, which rides the +# pagerank map whose ranked rows left files out and never a churn-ranked map (not the map scope). It sits beside "r" +# and is compared as payload: the header keysets below exclude it, and INV_* says where it rode. +PAYLOAD = { "inv" } +print( "INV_PLAIN=" + str( int( "inv" in plain ) ) ) +print( "INV_CHURN=" + str( int( "inv" in churn ) ) ) +churn = { k: v for k, v in churn.items() if k not in PAYLOAD } +plain = { k: v for k, v in plain.items() if k not in PAYLOAD } extra = sorted( set( churn ) - set( plain ) ) missing = sorted( set( plain ) - set( churn ) ) print( "EXTRA=" + ",".join( extra ) ) @@ -62,6 +70,9 @@ else fi [ -z "$MISSING" ] && ok "churn JSON header drops no key the pagerank header carries" \ || no "churn JSON header is MISSING keys the pagerank header has: $MISSING" +# D7: the inventory payload rides the map scope (pagerank, top-k=3 over src/ leaves files out) and never churn +[ "$INV_PLAIN" = 1 ] && [ "$INV_CHURN" = 0 ] && ok "\"inv\" (payload) rides the pagerank map and not the churn map" \ + || no "\"inv\" placement: pagerank=$INV_PLAIN churn=$INV_CHURN (want 1/0) — the payload exclusion above would hide a header key" # (2) the values carry MEANING (not pinned to a sha/window that moves with the tree) if [ "$RANK_BY" = "churn" ]; then ok "rank_by == \"churn\""; else no "rank_by is '$RANK_BY', want \"churn\""; fi diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index c44bce03f..320d68843 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -9,7 +9,7 @@ pattern 0 b7a2467faab2b69e4fe8e3d8204f158329f6b349d644e5681af860ce6f8ba6ca e3b0c callers 0 31971e419a6df4d59c4464fcdd1afa56c6125e4fc76d096f0203c80cedd9a4a8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 impact 0 c48ba09589d13cad6011bd6fadbb2898d68e2f5bd97a10bf44324901cfa08d49 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 clones 0 a082f92ce84cd952ced8341e3faff33a23685729bfa4ab6f0eda0f7867e41d7c e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help 0 62646b47d6faa9f090934dc19830bfae16593386c7c361c3cedcf1a0084e1b84 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help 0 7791b39af306921bb00de0dd499a489f05def5bf0c3414a1d360bfc71461f41e e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 expand 0 f3e60d37ab964c4772d794326420441e0b027d1970ea5fb0c60efe6c89f808b8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 callees 0 cbd133fa53917bad05926d46615d4d8b4457ba9285513dc4b193fa57a1ef8b85 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 around 0 ff0b962658da396e7bf3360584de21ab2f0e8bebcd8c0e4e997a77994b004a9f e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 @@ -38,5 +38,5 @@ safe_delete 0 d010f9ca7e45d5bedcde9be49875bbc705632b3011cb9c8c4185f47957d51504 e verify_layer 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5288c345d7d6e335f88b9c1daa8935db22e1dcf89c0c8bc1f6140d4cb5af0b48 graph_query 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 bfa4319feb9dee09cfbd3991cf6fbd752297e99d14de9e75768820d2a9c8832f callers_limit 0 e40ee1ddba0fbf548c58c98b91fe052af8c34f8913edb3edd85245b12a6967b9 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help_all 0 d6c3f9a001f3eb341b5bc7cec50a6647b7f2814cb28726ae310640ed977a7293 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help_all 0 6ae09bb876f98394fdd0f5c7b881bf8931e97017e601e5df654d49feb54242e8 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help_one 0 938ff4f8802a8f5d9cb10f9a1f3804ac755be44c8ab00e24e45bfe8255224a21 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 diff --git a/test/tokenbudgetcheck.sh b/test/tokenbudgetcheck.sh index ef91a893c..8a97a432b 100755 --- a/test/tokenbudgetcheck.sh +++ b/test/tokenbudgetcheck.sh @@ -236,6 +236,34 @@ GATE_EST="$( grep -oE 'est_tokens=[0-9]+' "$TMP_TB_B_ERR" | grep -oE '[0-9]+' )" && ok "gate value ($GATE_EST) == map header's est_tokens ($HDR_EST) — one counter, not two" \ || no "gate/header est_tokens MISMATCH (header=$HDR_EST gate=$GATE_EST) — a second counter crept in" +# ── #9b (map-inventory-d7, 2026-10-08): the SAME equality on the boundary where the compact reprice drops a digit. The gate +# used to recount the compacted body's size AFTER the layer had rewritten the est digits (a 5-digit full price repriced to 4 +# digits is 2 bytes shorter in the root and the header), so there it said one token less than the header (7608d1f6: +# src --top-k=233, header 9965, gate 9964). The arm LOCATES that boundary on today's src/ (it moves with the tree): the +# smallest top-k whose FULL price reaches 10000, then every k up to 8 past it whose compact header is still 4 digits. +full_est(){ "$BIN" src --top-k="$1" --legend=full --token-budget=1 --no-cache 2>&1 >/dev/null | grep -oE 'est_tokens=[0-9]+' | grep -oE '[0-9]+'; } +lo=1; hi=4000 +if [ "$( full_est $hi )" -ge 10000 ] 2>/dev/null; then + while [ $(( hi - lo )) -gt 1 ]; do mid=$(( ( lo + hi ) / 2 )); if [ "$( full_est $mid )" -ge 10000 ] 2>/dev/null; then hi=$mid; else lo=$mid; fi; done + crossed=0; bad="" + for k in $( seq "$hi" $(( hi + 8 )) ); do + H="$( "$BIN" src --top-k="$k" --token-budget=999999 --no-cache 2>/dev/null | grep -oE 'est_tokens=[0-9]+' | head -1 | grep -oE '[0-9]+' )" + [ -n "$H" ] && [ "$H" -lt 10000 ] 2>/dev/null || continue + crossed=$(( crossed + 1 )) + G="$( "$BIN" src --top-k="$k" --token-budget=1 --no-cache 2>&1 >/dev/null | grep -oE 'est_tokens=[0-9]+' | grep -oE '[0-9]+' )" + [ "$G" = "$H" ] || bad="$bad k=$k(header=$H,gate=${G:-?})" + done + if [ "$crossed" -eq 0 ]; then + no "#9b premise: no top-k in $hi..$(( hi + 8 )) has a 5-digit full price and a 4-digit compact one — the arm tested nothing" + elif [ -z "$bad" ]; then + ok "#9b gate == header on all $crossed digit-crossing top-k (from $hi): the gate reads the printed price" + else + no "#9b gate/header MISMATCH where the compact reprice drops a digit:$bad" + fi +else + no "#9b premise: src --top-k=4000 prices under 10000 tokens — no digit boundary to probe" +fi + # ── #10: determinism — the same command re-run twice gives the same exit code + the same est_tokens ─── "$BIN" src --token-budget=1 --no-cache >/dev/null 2>"$TMP_TB_B2_ERR"; rc_tiny2=$? GATE_EST2="$( grep -oE 'est_tokens=[0-9]+' "$TMP_TB_B2_ERR" | grep -oE '[0-9]+' )" From 997d3d9cccb82e515ace23701263e3a3b034e522 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 19:29:40 -0400 Subject: [PATCH 093/170] for: charge the budget for the / stub it serves, not the full render The default --for answer collapses / to a counted stub, but runForLens and the MCP for twin charged the full pre-rendered section against the budget. A question naming many interfaces left almost nothing for signatures (4 of 40 rows on one Python corpus, a ~4.6 KB answer under a 7.5 KB ceiling). - rw::planForSectionStubs (serialize.h): the one stub decision both surfaces share, now also used for the charge (rw::forSectionsSigChargePlan) before the sigs render. - Without an explicit ceiling, --sections= keeps the stubbed answer's charge, so the stub's next= restores both sections with the same (byte-identical, as kForSectionStubLegend says). Under --token-budget/--max-tokens or MCP budget_tokens an opted-in section is charged in full. - ENSURES: the served lego stub is never longer than the stub the budget was charged for. - forsectioncollapsecheck (13) on the new test/legochargefix fixture; --sections help updated. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 13 + src/cli.h | 4 +- src/mcpverbs.h | 70 ++- src/serialize.h | 75 +++ src/verbs_for.h | 62 ++- test/forsectioncollapsecheck.sh | 57 ++- test/legochargefix/pipeline.cpp | 59 +++ test/legochargefix/stages.h | 787 ++++++++++++++++++++++++++++++++ 8 files changed, 1057 insertions(+), 70 deletions(-) create mode 100644 test/legochargefix/pipeline.cpp create mode 100644 test/legochargefix/stages.h diff --git a/CHANGELOG.md b/CHANGELOG.md index e9358a5cf..97b95fb45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,19 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Fixed — `--for`: the `` budget is charged for the ``/`` stub it serves, not the full render + +The default answer collapses ``/`` to a ~120 B counted stub, but the `` budget was charged the full +render before the collapse. On a corpus whose question names many interfaces, the full `` filled the budget: one +measured Python corpus served 4 of 40 signature rows in a ~4.6 KB answer under the 7.5 KB ceiling. + +- A section served as a stub is charged at the stub's size (the CLI lens and the MCP `for` tool share one rule). +- Without an explicit ceiling, `--sections=lego,compose` keeps the stubbed answer's `` budget, so the stub's `next=` + restores both sections with the same `` rows, byte-identically. Under `--token-budget`/`--max-tokens` (MCP + `budget_tokens`) the restored sections are charged in full, so the hard bound holds. +- Measured on 32 `--for` questions over 8 corpora: 19 answers byte-identical, 13 carry more signature rows + (4 to 29-33 on the corpus above), and every row the old answer showed is still shown. Gate: `forsectioncollapsecheck` (13). + ### Changed — `--for`: the function a question names gets the first hop row, with all its callee names The compact `--for` answer hop-expanded only the six best-ranked rows, inside a ~1 KB allowance. A question that names diff --git a/src/cli.h b/src/cli.h index f8a63f876..5e5a66893 100644 --- a/src/cli.h +++ b/src/cli.h @@ -987,7 +987,9 @@ inline constexpr char kHelpHead[] = " (with --for=TASK) restore the / sections the ranked bundle collapses to\n" " a counted stub by default — this IS the stub's own next= spelling, so pasting it back\n" " unchanged is enough. A comma-separated, order-insensitive, closed set (lego and/or\n" - " compose, each named at most once); every other section of the bundle is unaffected.\n" + " compose, each named at most once); every other section of the bundle is unaffected —\n" + " the budget is the stubbed answer's. Under an explicit --token-budget/--max-tokens\n" + " the restored sections are charged in full, so can carry fewer rows there.\n" " --signatures-only (with --for) signatures only: no automatic bodies in the bundle\n" " (with --for) opt out of the terminal-by-default bundle: no auto bodies, no bundle=\"auto\"\n" " attribute — the signatures-only lens exactly as before. Contradicts --detail=N (refused\n" diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 9e2ab26e4..efe042ecb 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -2248,10 +2248,17 @@ inline std::optional forTaskText( const std::string& root, const st const std::size_t mcpAtLegendExemptBytes = !flRootArg.empty() && !mcpForAtAttrStr.empty() ? rw::kForAtStampProse.size() : 0; // r2-LO, above // e=: a disclosure on the same contract (the CLI twin exempts the same clause) const std::size_t mcpEndLineExemptBytes = mcpEndLineLegend.size(); + // lego-charge: / cost what they are SERVED as (the CLI twin's rule, rw::forSectionsSigChargePlan): + // a collapsing section its stub; under an explicit budget_tokens, a section `sections` opts into its full render. + const ForStubbableSection mcpLegoChargeIn{ .hasContent = !legoStr.empty(), .preCapTotal = legoPreCapCount, .rendered = true, + .renderedBytes = legoStr.size() }; + const ForStubbableSection mcpComposeChargeIn{ .hasContent = !composeStr.empty(), .preCapTotal = composePreCapCount, .rendered = true, + .renderedBytes = composeStr.size() }; + const ForSectionStubPlan mcpSigChargePlan = forSectionsSigChargePlan( sections, budgetTokens > 0, task, mcpLegoChargeIn, mcpComposeChargeIn ); const std::size_t fixedBytes = headerStr.size() - rw::kForFileTailLegend.size() - mcpConfidenceExemptBytes - mcpIdRouteExemptBytes - mcpAtLegendExemptBytes - mcpEndLineExemptBytes - rw::forZeroNoteBytes( headerStr ) // lean-answers: the zero reading never costs a row - + legoStr.size() + composeStr.size() + routeStr.size() + 6; // + "" + + mcpSigChargePlan.servedBytes( mcpLegoChargeIn, mcpComposeChargeIn ) + routeStr.size() + 6; // + "" const std::size_t sigsBudget = forBudgetBytes > fixedBytes ? forBudgetBytes - fixedBytes : 1; // ≥1: 0 = "no budget" // L3: field-notes surfacing — parity with the CLI --for lens. loadNoteIndex reads root/.ripwire_notes (a @@ -2402,41 +2409,32 @@ inline std::optional forTaskText( const std::string& root, const st // late-insertion technique for kOverCeilingLegend) knows whether to add it. bool mcpSectionsWillStub = false; { - const bool mcpWantLego = sectionsWant( sections, "lego" ); - const bool mcpWantCompose = sectionsWant( sections, "compose" ); - const bool mcpLegoCandidate = !legoStr.empty() && !mcpWantLego; - const bool mcpComposeCandidate = !composeStr.empty() && !mcpWantCompose; - if( mcpLegoCandidate || mcpComposeCandidate ) - { - std::string sectionsNextMcp = nextFlag( "--for=", task ); - sectionsNextMcp += ' '; - sectionsNextMcp += nextFlag( "--sections=", "lego,compose" ); - const SectionStubPricing legoPricing = mcpLegoCandidate - ? priceSectionStub( "lego", legoPreCapCount, sectionsNextMcp, /*hasRenderedBytes=*/true, legoStr.size() ) - : SectionStubPricing{}; - const SectionStubPricing composePricing = mcpComposeCandidate - ? priceSectionStub( "compose", composePreCapCount, sectionsNextMcp, /*hasRenderedBytes=*/true, composeStr.size() ) - : SectionStubPricing{}; - const bool mcpLegoWillStub = mcpLegoCandidate && legoPricing.collapse; - const bool mcpComposeWillStub = mcpComposeCandidate && composePricing.collapse; - // postcondition of the rule itself, checked BEFORE the swap below discards the original size: - // whichever section actually collapses is, by construction, smaller than what it replaced. - ENSURES( !mcpLegoWillStub || legoPricing.stubXml.size() < legoStr.size(), - "R2-L2' MCP twin: a lego stub collapsed without being smaller than the section it replaced" ); - ENSURES( !mcpComposeWillStub || composePricing.stubXml.size() < composeStr.size(), - "R2-L2' MCP twin: a compose stub collapsed without being smaller than the section it replaced" ); - if( mcpLegoWillStub ) - { - legoStr = legoPricing.stubXml; - } - if( mcpComposeWillStub ) - { - composeStr = composePricing.stubXml; - } - // R2-L2p: same truth table as the CLI's `if( legoWillStub || composeWillStub ) sectionsStubNote = …` - // (verbs_for.h ~2858): the legend rides iff at least one section actually collapsed. - mcpSectionsWillStub = mcpLegoWillStub || mcpComposeWillStub; - } + // the CLI twin's one plan (rw::planForSectionStubs): this surface always renders whole, so rendered=true + const ForSectionStubPlan plan = planForSectionStubs( + sections, task, + ForStubbableSection{ .hasContent = !legoStr.empty(), .preCapTotal = legoPreCapCount, .rendered = true, .renderedBytes = legoStr.size() }, + ForStubbableSection{ .hasContent = !composeStr.empty(), .preCapTotal = composePreCapCount, .rendered = true, .renderedBytes = composeStr.size() } ); + const bool mcpLegoWillStub = plan.legoWillStub; + const bool mcpComposeWillStub = plan.composeWillStub; + // postcondition of the rule itself, checked BEFORE the swap below discards the original size: + // whichever section actually collapses is, by construction, smaller than what it replaced. + ENSURES( !mcpLegoWillStub || plan.lego.stubXml.size() < legoStr.size(), + "R2-L2' MCP twin: a lego stub collapsed without being smaller than the section it replaced" ); + ENSURES( !mcpComposeWillStub || plan.compose.stubXml.size() < composeStr.size(), + "R2-L2' MCP twin: a compose stub collapsed without being smaller than the section it replaced" ); + ENSURES( !( mcpLegoWillStub && mcpSigChargePlan.legoWillStub ) || plan.lego.stubXml.size() <= mcpSigChargePlan.lego.stubXml.size(), + "lego-charge MCP twin: the served lego stub is longer than the stub the budget was charged for" ); + if( mcpLegoWillStub ) + { + legoStr = plan.lego.stubXml; + } + if( mcpComposeWillStub ) + { + composeStr = plan.compose.stubXml; + } + // R2-L2p: same truth table as the CLI's `if( legoWillStub || composeWillStub ) sectionsStubNote = …` + // (verbs_for.h ~2858): the legend rides iff at least one section actually collapsed. + mcpSectionsWillStub = mcpLegoWillStub || mcpComposeWillStub; } const bool mcpLegoCountAttrs = legoStr.find( rw::kLegoCountAttrPrefix ) != std::string::npos; // false on a stub (its own attributes only) std::fwrite( sigsStr.data(), 1, sigsStr.size(), mem ); diff --git a/src/serialize.h b/src/serialize.h index 75ecc0d2c..8150d6f35 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -8053,6 +8053,81 @@ inline SectionStubPricing priceSectionStub( const char* tag, std::size_t preCapT return p; } +// One stubbable --for section ( or ) as the stub plan reads it. +struct ForStubbableSection +{ + bool hasContent; // the section would render non-empty + std::size_t preCapTotal; // its own pre-cap row count: the stub's total= + bool rendered; // its bytes were measured (false = the open_memstream degrade path, never collapsed) + std::size_t renderedBytes; // the measured full render (0 when !rendered) +}; + +// The / stub decision of a ranked --for answer: which section collapses (L2 / R2-L2', priceSectionStub) and +// the ONE restoring spelling. One function for the CLI lens (verbs_for.h runForLens) and its MCP twin (mcpverbs.h +// forTaskText), for both the final decision and the charge (forSectionsSigCharge below). +struct ForSectionStubPlan +{ + std::string nextInvocation; // empty when neither section is a candidate + SectionStubPricing lego{}; + SectionStubPricing compose{}; + bool legoWillStub = false; + bool composeWillStub = false; + + // the bytes the two sections occupy as served: a collapsing section at its stub, any other at its measured render. + // kForSectionStubLegend is not in it: a disclosure clause, exempt from the charge like the e=/owner/tail clauses. + [[nodiscard]] std::size_t servedBytes( const ForStubbableSection& legoIn, const ForStubbableSection& composeIn ) const noexcept + { + return ( legoWillStub ? lego.stubXml.size() : legoIn.renderedBytes ) + ( composeWillStub ? compose.stubXml.size() : composeIn.renderedBytes ); + } +}; + +inline ForSectionStubPlan planForSectionStubs( std::string_view sections, std::string_view forTask, const ForStubbableSection& legoIn, + const ForStubbableSection& composeIn ) +{ + ForSectionStubPlan p; + // a CANDIDATE has content and was not opted back in by --sections=; it collapses only when its stub is cheaper + const bool legoCandidate = legoIn.hasContent && !sectionsWant( sections, "lego" ); + const bool composeCandidate = composeIn.hasContent && !sectionsWant( sections, "compose" ); + if( legoCandidate || composeCandidate ) + { + p.nextInvocation = nextFlag( "--for=", forTask ); + p.nextInvocation += ' '; + p.nextInvocation += nextFlag( "--sections=", "lego,compose" ); + } + if( legoCandidate ) + { + p.lego = priceSectionStub( "lego", legoIn.preCapTotal, p.nextInvocation, legoIn.rendered, legoIn.renderedBytes ); + } + if( composeCandidate ) + { + p.compose = priceSectionStub( "compose", composeIn.preCapTotal, p.nextInvocation, composeIn.rendered, composeIn.renderedBytes ); + } + p.legoWillStub = legoCandidate && p.lego.collapse; + p.composeWillStub = composeCandidate && p.compose.collapse; + ENSURES( p.servedBytes( legoIn, composeIn ) <= legoIn.renderedBytes + composeIn.renderedBytes, + "planForSectionStubs: a collapse made the two sections bigger than their measured renders" ); + return p; +} + +// lego-charge (K18 root cause): what / cost the budget, computed BEFORE the sigs render. It used to be +// the full pre-rendered bytes, while the default answer serves a collapsing section as a ~120 B stub: textual's 6.5 KB lego +// left 71 B for (4 rows shipped in a 4.6 KB answer under a 7.5 KB ceiling). Now a section is charged as it is SERVED. +// Two rules decide what "served" means here: +// * Without an explicit ceiling, the charge is the DEFAULT answer's, also under --sections=: the stub's next= promises the +// sections back "byte-identically in one call" (kForSectionStubLegend), and only a call whose match the stubbed +// answer's renders them so (the lego is narrowed to the rendered sigs' files, §P3×§P4). The restored sections then ride +// on top of that answer, priced in est_tokens=; charging them in full would serve FEWER rows than the stub promised. +// * Under an explicit ceiling (--token-budget / --max-tokens / MCP budget_tokens), a section --sections= opts into is +// charged at its full render: the ceiling is a hard bound, and the stub's next= never carries one. +// The charge is taken before the §P3×§P4 narrow, on the pre-narrow render and count. The narrow only empties implementor +// lists, so a served stub's total= can only shrink (runForLens ENSURES it). A section served whole was charged at its +// pre-narrow render, as it was before this change. +inline ForSectionStubPlan forSectionsSigChargePlan( std::string_view sections, bool explicitCeiling, std::string_view forTask, + const ForStubbableSection& legoIn, const ForStubbableSection& composeIn ) +{ + return planForSectionStubs( explicitCeiling ? sections : std::string_view(), forTask, legoIn, composeIn ); +} + // B6.3 HTTP-route cross-service view: for a set of relevant symbols, emit the synthesized route USE→DEF // edges (client call → server handler) as a block. ONLY called from --for and --around (NOT the // default map) — see model.h RouteEdge / graph.h buildGraph's B6.3 section for how these are matched. diff --git a/src/verbs_for.h b/src/verbs_for.h index 3ba40bdd5..4cdabffa8 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -3326,7 +3326,16 @@ std::optional runForLens( const MainDispatch& d ) fullCharged -= std::min( fullCharged, rw::forZeroNoteBytes( fullHeader ) ); // lean-answers: exempt, as above chargedHeaderBytes = std::min( chargedHeaderBytes, fullCharged ); } - const std::size_t fixedBytes = chargedHeaderBytes + legoStr.size() + composeStr.size() + routeStr.size() + 6; // + "" + // lego-charge: / cost what they are SERVED as — a collapsing section its stub, not its full render + // (rw::forSectionsSigChargePlan states both rules and why --sections= keeps the default's charge without a ceiling). + // A section the degrade path will stream unmeasured has no bytes here (legoStr/composeStr empty): blockCharge discloses it. + const bool explicitForCeiling = cfg.tokenBudget > 0 || cfg.maxTokens > 0; + const rw::ForStubbableSection legoChargeIn{ .hasContent = !legoStr.empty(), .preCapTotal = legoPreCapCount, .rendered = legoPreRendered, + .renderedBytes = legoStr.size() }; + const rw::ForStubbableSection composeChargeIn{ .hasContent = !composeStr.empty(), .preCapTotal = composePreCapCount, + .rendered = composePreRendered, .renderedBytes = composeStr.size() }; + const rw::ForSectionStubPlan sigChargePlan = rw::forSectionsSigChargePlan( cfg.sections, explicitForCeiling, cfg.forTask, legoChargeIn, composeChargeIn ); + const std::size_t fixedBytes = chargedHeaderBytes + sigChargePlan.servedBytes( legoChargeIn, composeChargeIn ) + routeStr.size() + 6; // + "" // the auto bundle's SECTION SPLIT — the sig side's claim is capped so an explicit ceiling wider // than the default cannot re-inflate the trimmed sig tail at the bodies' expense (the rule, its // measured defect and the invariant: forSigSideCeiling above; gate: forbudgetmonotoncheck). @@ -3523,45 +3532,34 @@ std::optional runForLens( const MainDispatch& d ) // whichever state they are already in (post-narrow on the happy path, since narrowing already ran // above; un-narrowed on the degrade path, since narrowing never runs there either) — the same state // each path's own render (buffered or direct-to-stdout) would use. - const bool sectionsWantLego = rw::sectionsWant( cfg.sections, "lego" ); - const bool sectionsWantCompose = rw::sectionsWant( cfg.sections, "compose" ); const std::size_t legoStubTotal = legoPreRendered ? legoPreCapCount : rw::legoPreCapRowCount( ing, legoScoped ); const std::size_t composeStubTotal = composePreRendered ? composePreCapCount : rw::composePreCapRowCount( ing, g.composeEdges, lensSurfaceIds ); - const bool legoHasContent = legoPreRendered ? !legoStr.empty() : legoStubTotal > 0; - const bool composeHasContent = composePreRendered ? !composeStr.empty() : composeStubTotal > 0; - // CANDIDATE for collapse — has content, and --sections did not already opt it back in. Round 1 - // stubbed every candidate unconditionally; round 2 (below) additionally PRICES it. - const bool legoCandidate = legoHasContent && !sectionsWantLego; - const bool composeCandidate = composeHasContent && !sectionsWantCompose; - // the ONE restoring invocation, built once and used at every site (the size-gate probe below, the - // buffered substitution, and the degrade-path fallback further down) that needs it — never a second, - // differently-spelled build. - std::string sectionsNextInvocation; - if( legoCandidate || composeCandidate ) - { - sectionsNextInvocation = rw::nextFlag( "--for=", cfg.forTask ); - sectionsNextInvocation += ' '; - sectionsNextInvocation += rw::nextFlag( "--sections=", "lego,compose" ); - } - // R2-L2' (round-2, priced re-registration of L2/B1, rv-prereg2 Amendment 1 R4): a candidate collapses - // ONLY WHEN CHEAPER — see rw::priceSectionStub (serialize.h) for the shared rule (posture-independent - // by construction) and the DEGRADE path it documents: no rendered bytes to gate on there, so the - // section never collapses and is streamed whole below. Pulled into one small function so this - // already-long lens carries none of the pricing branching itself. - const rw::SectionStubPricing legoPricing = legoCandidate - ? rw::priceSectionStub( "lego", legoStubTotal, sectionsNextInvocation, legoPreRendered, legoStr.size() ) - : rw::SectionStubPricing{}; - const rw::SectionStubPricing composePricing = composeCandidate - ? rw::priceSectionStub( "compose", composeStubTotal, sectionsNextInvocation, composePreRendered, composeStr.size() ) - : rw::SectionStubPricing{}; - const bool legoWillStub = legoCandidate && legoPricing.collapse; - const bool composeWillStub = composeCandidate && composePricing.collapse; + // R2-L2' (round-2, priced re-registration of L2/B1, rv-prereg2 Amendment 1 R4): a CANDIDATE (has content, not opted + // back in by --sections=) collapses ONLY WHEN CHEAPER — rw::planForSectionStubs / priceSectionStub (serialize.h) hold + // the one rule both surfaces share, its ONE restoring invocation (used at every site below: the buffered substitution + // and the degrade-path fallback), and the DEGRADE path it documents: no rendered bytes to gate on there, so the + // section never collapses and is streamed whole below. + const rw::ForSectionStubPlan stubPlan = rw::planForSectionStubs( + cfg.sections, cfg.forTask, + rw::ForStubbableSection{ .hasContent = legoPreRendered ? !legoStr.empty() : legoStubTotal > 0, .preCapTotal = legoStubTotal, + .rendered = legoPreRendered, .renderedBytes = legoStr.size() }, + rw::ForStubbableSection{ .hasContent = composePreRendered ? !composeStr.empty() : composeStubTotal > 0, .preCapTotal = composeStubTotal, + .rendered = composePreRendered, .renderedBytes = composeStr.size() } ); + const std::string& sectionsNextInvocation = stubPlan.nextInvocation; + const rw::SectionStubPricing& legoPricing = stubPlan.lego; + const rw::SectionStubPricing& composePricing = stubPlan.compose; + const bool legoWillStub = stubPlan.legoWillStub; + const bool composeWillStub = stubPlan.composeWillStub; // postcondition of the rule itself: whichever section actually collapses is, by construction, smaller // than what it replaced (the WHOLE point of pricing it) — never a stub that grew the answer. ENSURES( !( legoPreRendered && legoWillStub ) || legoPricing.stubXml.size() < legoStr.size(), "R2-L2': a lego stub collapsed without being smaller than the section it replaced" ); ENSURES( !( composePreRendered && composeWillStub ) || composePricing.stubXml.size() < composeStr.size(), "R2-L2': a compose stub collapsed without being smaller than the section it replaced" ); + // lego-charge: the budget priced a collapsing lego at its PRE-narrow stub; the narrow only empties implementor + // lists, so the stub served now is never longer than the one charged (total= can only shrink). + ENSURES( !( legoWillStub && sigChargePlan.legoWillStub ) || legoPricing.stubXml.size() <= sigChargePlan.lego.stubXml.size(), + "lego-charge: the served lego stub is longer than the stub the budget was charged for" ); std::string sectionsStubNote; // present-only legend clause (kForSectionStubLegend), spliced below if( legoWillStub || composeWillStub ) { diff --git a/test/forsectioncollapsecheck.sh b/test/forsectioncollapsecheck.sh index 53de643b0..27eb844cd 100644 --- a/test/forsectioncollapsecheck.sh +++ b/test/forsectioncollapsecheck.sh @@ -25,7 +25,8 @@ # Fixtures: test/legofix (lego collapses: three small interfaces still clear the threshold) and # test/sectionpricefix (both lego and compose collapse: a purpose-built interface + HAS-A owners sized # comfortably above threshold) prove the ABOVE-threshold side; test/hasafix (compose stays whole: two -# short rows, priced BELOW threshold) proves the round-2 delta itself, arm (10). +# short rows, priced BELOW threshold) proves the round-2 delta itself, arm (10). test/legochargefix (a lego +# block bigger than the budget) proves the stub is what the budget is charged for, arm (13). # # Usage: bash test/forsectioncollapsecheck.sh [path-to-ripwire-binary] # RIPWIRE_BIN=build/ripwire bash test/forsectioncollapsecheck.sh @@ -393,5 +394,59 @@ else ok "(12) MCP legend disclosure arm skipped (python3 absent)" fi +# ── (13) lego-charge (K18 root cause): the budget charges a collapsing section at its STUB, not its full render. +# test/legochargefix is textual-shaped: its query names 14 interfaces with 4 implementors each, so the full render +# (~10 KB) is bigger than the default budget, while the default answer serves it as a ~150 B stub. Charging the +# full render left 1 B for : 4 rows in a ~3 KB answer under a 7.5 KB ceiling (textual's round-1 rows: 4 of 40). +# Rules (rw::forSectionsSigChargePlan, serialize.h): no explicit ceiling → the default answer's charge, also under +# --sections= (so the stub's next= restores both sections with the SAME , byte-identically); an explicit ceiling +# (--token-budget, MCP budget_tokens) → a section --sections= opts into is charged in full (hard bound). +LCQ="how do render stage classes arrange and reflow widgets" +lc_shown(){ grep -o '' "$1"; } +"$BIN" test/legochargefix --no-cache --for="$LCQ" >"$TMP/lc_def.xml" 2>/dev/null; rc_def=$? +"$BIN" test/legochargefix --no-cache --for="$LCQ" --sections=lego,compose >"$TMP/lc_rest.xml" 2>/dev/null; rc_rest=$? +"$BIN" test/legochargefix --no-cache --for="$LCQ" --token-budget=3000 >"$TMP/lc_tb.xml" 2>/dev/null; rc_tb=$? +"$BIN" test/legochargefix --no-cache --for="$LCQ" --token-budget=3000 --sections=lego,compose >"$TMP/lc_tbrest.xml" 2>/dev/null; rc_tbrest=$? +LC_DEF="$( lc_shown "$TMP/lc_def.xml" )"; LC_REST="$( lc_shown "$TMP/lc_rest.xml" )" +LC_TB="$( lc_shown "$TMP/lc_tb.xml" )"; LC_TBREST="$( lc_shown "$TMP/lc_tbrest.xml" )"; LC_TBEST="$( lc_est "$TMP/lc_tbrest.xml" )" +if [ "$rc_def$rc_rest$rc_tb$rc_tbrest" != "0000" ] || ! [[ "$LC_DEF" =~ ^[0-9]+$ && "$LC_REST" =~ ^[0-9]+$ && "$LC_TB" =~ ^[0-9]+$ && "$LC_TBREST" =~ ^[0-9]+$ && "$LC_TBEST" =~ ^[0-9]+$ ]]; then + no "(13) legochargefix: a run failed or printed no /est_tokens= (rc=$rc_def/$rc_rest/$rc_tb/$rc_tbrest shown=$LC_DEF/$LC_REST/$LC_TB/$LC_TBREST est=$LC_TBEST)" +else + # (13a) RED on the base: the stub is served, so the budget pays the stub — the answer carries a budget's worth of rows + grep -Eq '' "$TMP/lc_def.xml" && [ "$LC_DEF" -ge 24 ] \ + && ok "(13a) default: is served as a stub and shows $LC_DEF rows (the stub's price was charged, not the full render's)" \ + || no "(13a) default: (want >= 24 with a stub) — the budget paid for a lego render it never served" + # (13b) the stub's next= restores both sections byte-identically: same block, the full on top + [ "$( lc_sigs "$TMP/lc_def.xml" )" = "$( lc_sigs "$TMP/lc_rest.xml" )" ] && grep -q ' as the stubbed answer ($LC_REST rows) plus the full " \ + || no "(13b) --sections=lego,compose: differs from the stubbed answer's ($LC_DEF) or no full — next= does not restore the cut it disclosed" + LC_STUBTOTAL="$( grep -o ' ($LC_IFACES rows, display cap 12)" \ + || no "(13c) the stub's total=\"$LC_STUBTOTAL\" vs $LC_IFACES restored rows (display cap 12)" + # (13d) NEAR-MISS: under an explicit ceiling, a section --sections= opts into is charged in full — the hard bound holds + # (its is then narrowed to the few rendered rows' files — here none of stages.h — so the bound is read off ) + [ "$LC_TBEST" -le 3000 ] && [ "$LC_TBREST" -lt "$LC_TB" ] \ + && ok "(13d) --token-budget=3000 --sections=lego,compose: est_tokens=$LC_TBEST <= 3000, $LC_TBREST < $LC_TB (the full was charged)" \ + || no "(13d) --token-budget=3000 --sections=lego,compose: est_tokens=$LC_TBEST, $LC_TBREST vs $LC_TB stubbed — the opted-in section was not charged in full under an explicit ceiling" +fi +if command -v python3 >/dev/null 2>&1; then + lc_mcp(){ printf '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"for","arguments":{"path":"test/legochargefix","task":"%s"%s}}}\n' "$LCQ" "$1" \ + | "$BIN" --mcp 2>/dev/null | python3 -c " +import json,sys,re +t = json.loads(sys.stdin.readline())['result']['content'][0]['text'] +m = re.search(r' $LCM_DEF rows by default; under budget_tokens + sections $LCM_TBREST < $LCM_TB" \ + || no "(13e) MCP for: default=$LCM_DEF budget=$LCM_TB budget+sections=$LCM_TBREST (want >= 24, and the last below the middle)" +else + printf ' SKIP %s\n' "(13e) python3 absent: the MCP twin of the lego charge is not checked" +fi + [ "$fail" -eq 0 ] && echo "ALL PASS" || echo "SOME FAILED" exit "$fail" diff --git a/test/legochargefix/pipeline.cpp b/test/legochargefix/pipeline.cpp new file mode 100644 index 000000000..c8c79f6bc --- /dev/null +++ b/test/legochargefix/pipeline.cpp @@ -0,0 +1,59 @@ +// legochargefix/pipeline.cpp — the ranked rows: free functions the render-stage query matches by name. +#include "stages.h" + +int arrangeRenderStageWidgetsLayout( LayoutRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeLayoutWidgets( regionWidth, regionHeight, "Layout" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsLayout( LayoutRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowLayoutWidgets( regionWidth, regionHeight, "Layout" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsLayout( LayoutRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshLayoutWidgets( regionWidth, regionHeight, "Layout" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsLayout( LayoutRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintLayoutWidgets( regionWidth, regionHeight, "Layout" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsCompositor( CompositorRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeCompositorWidgets( regionWidth, regionHeight, "Compositor" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsCompositor( CompositorRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowCompositorWidgets( regionWidth, regionHeight, "Compositor" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsCompositor( CompositorRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshCompositorWidgets( regionWidth, regionHeight, "Compositor" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsCompositor( CompositorRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintCompositorWidgets( regionWidth, regionHeight, "Compositor" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsStyles( StylesRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeStylesWidgets( regionWidth, regionHeight, "Styles" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsStyles( StylesRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowStylesWidgets( regionWidth, regionHeight, "Styles" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsStyles( StylesRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshStylesWidgets( regionWidth, regionHeight, "Styles" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsStyles( StylesRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintStylesWidgets( regionWidth, regionHeight, "Styles" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsScreen( ScreenRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeScreenWidgets( regionWidth, regionHeight, "Screen" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsScreen( ScreenRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowScreenWidgets( regionWidth, regionHeight, "Screen" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsScreen( ScreenRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshScreenWidgets( regionWidth, regionHeight, "Screen" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsScreen( ScreenRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintScreenWidgets( regionWidth, regionHeight, "Screen" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsMessage( MessageRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeMessageWidgets( regionWidth, regionHeight, "Message" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsMessage( MessageRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowMessageWidgets( regionWidth, regionHeight, "Message" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsMessage( MessageRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshMessageWidgets( regionWidth, regionHeight, "Message" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsMessage( MessageRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintMessageWidgets( regionWidth, regionHeight, "Message" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsBinding( BindingRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeBindingWidgets( regionWidth, regionHeight, "Binding" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsBinding( BindingRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowBindingWidgets( regionWidth, regionHeight, "Binding" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsBinding( BindingRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshBindingWidgets( regionWidth, regionHeight, "Binding" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsBinding( BindingRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintBindingWidgets( regionWidth, regionHeight, "Binding" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsTimer( TimerRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeTimerWidgets( regionWidth, regionHeight, "Timer" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsTimer( TimerRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowTimerWidgets( regionWidth, regionHeight, "Timer" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsTimer( TimerRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshTimerWidgets( regionWidth, regionHeight, "Timer" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsTimer( TimerRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintTimerWidgets( regionWidth, regionHeight, "Timer" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsReactive( ReactiveRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeReactiveWidgets( regionWidth, regionHeight, "Reactive" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsReactive( ReactiveRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowReactiveWidgets( regionWidth, regionHeight, "Reactive" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsReactive( ReactiveRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshReactiveWidgets( regionWidth, regionHeight, "Reactive" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsReactive( ReactiveRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintReactiveWidgets( regionWidth, regionHeight, "Reactive" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsScroll( ScrollRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeScrollWidgets( regionWidth, regionHeight, "Scroll" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsScroll( ScrollRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowScrollWidgets( regionWidth, regionHeight, "Scroll" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsScroll( ScrollRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshScrollWidgets( regionWidth, regionHeight, "Scroll" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsScroll( ScrollRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintScrollWidgets( regionWidth, regionHeight, "Scroll" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsFocus( FocusRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeFocusWidgets( regionWidth, regionHeight, "Focus" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsFocus( FocusRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowFocusWidgets( regionWidth, regionHeight, "Focus" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsFocus( FocusRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshFocusWidgets( regionWidth, regionHeight, "Focus" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsFocus( FocusRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintFocusWidgets( regionWidth, regionHeight, "Focus" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsMount( MountRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeMountWidgets( regionWidth, regionHeight, "Mount" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsMount( MountRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowMountWidgets( regionWidth, regionHeight, "Mount" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsMount( MountRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshMountWidgets( regionWidth, regionHeight, "Mount" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsMount( MountRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintMountWidgets( regionWidth, regionHeight, "Mount" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsTheme( ThemeRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeThemeWidgets( regionWidth, regionHeight, "Theme" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsTheme( ThemeRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowThemeWidgets( regionWidth, regionHeight, "Theme" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsTheme( ThemeRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshThemeWidgets( regionWidth, regionHeight, "Theme" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsTheme( ThemeRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintThemeWidgets( regionWidth, regionHeight, "Theme" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsCursor( CursorRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeCursorWidgets( regionWidth, regionHeight, "Cursor" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsCursor( CursorRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowCursorWidgets( regionWidth, regionHeight, "Cursor" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsCursor( CursorRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshCursorWidgets( regionWidth, regionHeight, "Cursor" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsCursor( CursorRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintCursorWidgets( regionWidth, regionHeight, "Cursor" ); return regionWidth + regionHeight; } +int arrangeRenderStageWidgetsRegion( RegionRenderStage& stage, int regionWidth, int regionHeight ) { stage.arrangeRegionWidgets( regionWidth, regionHeight, "Region" ); return regionWidth + regionHeight; } +int reflowRenderStageWidgetsRegion( RegionRenderStage& stage, int regionWidth, int regionHeight ) { stage.reflowRegionWidgets( regionWidth, regionHeight, "Region" ); return regionWidth + regionHeight; } +int refreshRenderStageWidgetsRegion( RegionRenderStage& stage, int regionWidth, int regionHeight ) { stage.refreshRegionWidgets( regionWidth, regionHeight, "Region" ); return regionWidth + regionHeight; } +int repaintRenderStageWidgetsRegion( RegionRenderStage& stage, int regionWidth, int regionHeight ) { stage.repaintRegionWidgets( regionWidth, regionHeight, "Region" ); return regionWidth + regionHeight; } diff --git a/test/legochargefix/stages.h b/test/legochargefix/stages.h new file mode 100644 index 000000000..fd782a5ad --- /dev/null +++ b/test/legochargefix/stages.h @@ -0,0 +1,787 @@ +// legochargefix/stages.h — lego-charge gate fixture (forsectioncollapsecheck.sh arm (13)): a textual-like corpus whose +// --for answer names many interfaces with implementors, so the FULL render (~8 KB) is bigger than the default +// budget while the default answer serves it as a ~150 B counted stub. Generated once; plain C++ on purpose. + +class LayoutRenderStage +{ +public: + virtual ~LayoutRenderStage() = default; + virtual void arrangeLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class LayoutRenderStageImpl1 : public LayoutRenderStage +{ +public: + void arrangeLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class LayoutRenderStageImpl2 : public LayoutRenderStage +{ +public: + void arrangeLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class LayoutRenderStageImpl3 : public LayoutRenderStage +{ +public: + void arrangeLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class LayoutRenderStageImpl4 : public LayoutRenderStage +{ +public: + void arrangeLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveLayoutWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class CompositorRenderStage +{ +public: + virtual ~CompositorRenderStage() = default; + virtual void arrangeCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class CompositorRenderStageImpl1 : public CompositorRenderStage +{ +public: + void arrangeCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class CompositorRenderStageImpl2 : public CompositorRenderStage +{ +public: + void arrangeCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class CompositorRenderStageImpl3 : public CompositorRenderStage +{ +public: + void arrangeCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class CompositorRenderStageImpl4 : public CompositorRenderStage +{ +public: + void arrangeCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveCompositorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class StylesRenderStage +{ +public: + virtual ~StylesRenderStage() = default; + virtual void arrangeStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class StylesRenderStageImpl1 : public StylesRenderStage +{ +public: + void arrangeStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class StylesRenderStageImpl2 : public StylesRenderStage +{ +public: + void arrangeStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class StylesRenderStageImpl3 : public StylesRenderStage +{ +public: + void arrangeStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class StylesRenderStageImpl4 : public StylesRenderStage +{ +public: + void arrangeStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveStylesWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ScreenRenderStage +{ +public: + virtual ~ScreenRenderStage() = default; + virtual void arrangeScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class ScreenRenderStageImpl1 : public ScreenRenderStage +{ +public: + void arrangeScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ScreenRenderStageImpl2 : public ScreenRenderStage +{ +public: + void arrangeScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ScreenRenderStageImpl3 : public ScreenRenderStage +{ +public: + void arrangeScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ScreenRenderStageImpl4 : public ScreenRenderStage +{ +public: + void arrangeScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveScreenWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class MessageRenderStage +{ +public: + virtual ~MessageRenderStage() = default; + virtual void arrangeMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class MessageRenderStageImpl1 : public MessageRenderStage +{ +public: + void arrangeMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class MessageRenderStageImpl2 : public MessageRenderStage +{ +public: + void arrangeMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class MessageRenderStageImpl3 : public MessageRenderStage +{ +public: + void arrangeMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class MessageRenderStageImpl4 : public MessageRenderStage +{ +public: + void arrangeMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveMessageWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class BindingRenderStage +{ +public: + virtual ~BindingRenderStage() = default; + virtual void arrangeBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class BindingRenderStageImpl1 : public BindingRenderStage +{ +public: + void arrangeBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class BindingRenderStageImpl2 : public BindingRenderStage +{ +public: + void arrangeBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class BindingRenderStageImpl3 : public BindingRenderStage +{ +public: + void arrangeBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class BindingRenderStageImpl4 : public BindingRenderStage +{ +public: + void arrangeBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveBindingWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class TimerRenderStage +{ +public: + virtual ~TimerRenderStage() = default; + virtual void arrangeTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class TimerRenderStageImpl1 : public TimerRenderStage +{ +public: + void arrangeTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class TimerRenderStageImpl2 : public TimerRenderStage +{ +public: + void arrangeTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class TimerRenderStageImpl3 : public TimerRenderStage +{ +public: + void arrangeTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class TimerRenderStageImpl4 : public TimerRenderStage +{ +public: + void arrangeTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveTimerWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ReactiveRenderStage +{ +public: + virtual ~ReactiveRenderStage() = default; + virtual void arrangeReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class ReactiveRenderStageImpl1 : public ReactiveRenderStage +{ +public: + void arrangeReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ReactiveRenderStageImpl2 : public ReactiveRenderStage +{ +public: + void arrangeReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ReactiveRenderStageImpl3 : public ReactiveRenderStage +{ +public: + void arrangeReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ReactiveRenderStageImpl4 : public ReactiveRenderStage +{ +public: + void arrangeReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveReactiveWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ScrollRenderStage +{ +public: + virtual ~ScrollRenderStage() = default; + virtual void arrangeScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class ScrollRenderStageImpl1 : public ScrollRenderStage +{ +public: + void arrangeScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ScrollRenderStageImpl2 : public ScrollRenderStage +{ +public: + void arrangeScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ScrollRenderStageImpl3 : public ScrollRenderStage +{ +public: + void arrangeScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ScrollRenderStageImpl4 : public ScrollRenderStage +{ +public: + void arrangeScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveScrollWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class FocusRenderStage +{ +public: + virtual ~FocusRenderStage() = default; + virtual void arrangeFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class FocusRenderStageImpl1 : public FocusRenderStage +{ +public: + void arrangeFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class FocusRenderStageImpl2 : public FocusRenderStage +{ +public: + void arrangeFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class FocusRenderStageImpl3 : public FocusRenderStage +{ +public: + void arrangeFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class FocusRenderStageImpl4 : public FocusRenderStage +{ +public: + void arrangeFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveFocusWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class MountRenderStage +{ +public: + virtual ~MountRenderStage() = default; + virtual void arrangeMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class MountRenderStageImpl1 : public MountRenderStage +{ +public: + void arrangeMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class MountRenderStageImpl2 : public MountRenderStage +{ +public: + void arrangeMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class MountRenderStageImpl3 : public MountRenderStage +{ +public: + void arrangeMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class MountRenderStageImpl4 : public MountRenderStage +{ +public: + void arrangeMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveMountWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ThemeRenderStage +{ +public: + virtual ~ThemeRenderStage() = default; + virtual void arrangeThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class ThemeRenderStageImpl1 : public ThemeRenderStage +{ +public: + void arrangeThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ThemeRenderStageImpl2 : public ThemeRenderStage +{ +public: + void arrangeThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ThemeRenderStageImpl3 : public ThemeRenderStage +{ +public: + void arrangeThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class ThemeRenderStageImpl4 : public ThemeRenderStage +{ +public: + void arrangeThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveThemeWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class CursorRenderStage +{ +public: + virtual ~CursorRenderStage() = default; + virtual void arrangeCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class CursorRenderStageImpl1 : public CursorRenderStage +{ +public: + void arrangeCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class CursorRenderStageImpl2 : public CursorRenderStage +{ +public: + void arrangeCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class CursorRenderStageImpl3 : public CursorRenderStage +{ +public: + void arrangeCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class CursorRenderStageImpl4 : public CursorRenderStage +{ +public: + void arrangeCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveCursorWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class RegionRenderStage +{ +public: + virtual ~RegionRenderStage() = default; + virtual void arrangeRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void reflowRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void refreshRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void repaintRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void measureRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; + virtual void resolveRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) = 0; +}; + +class RegionRenderStageImpl1 : public RegionRenderStage +{ +public: + void arrangeRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class RegionRenderStageImpl2 : public RegionRenderStage +{ +public: + void arrangeRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class RegionRenderStageImpl3 : public RegionRenderStage +{ +public: + void arrangeRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; + +class RegionRenderStageImpl4 : public RegionRenderStage +{ +public: + void arrangeRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void reflowRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void refreshRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void repaintRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void measureRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} + void resolveRegionWidgets( int regionWidth, int regionHeight, const char* stageLabel ) override {} +}; From 770a8484382b2a404d0a388b7566225765cb3bdf Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 20:18:35 -0400 Subject: [PATCH 094/170] =?UTF-8?q?for:=20lego-charge=20=E2=80=94=20read?= =?UTF-8?q?=20servedBytes=20outside=20the=20ENSURES=20(selfcheckcheck=20C:?= =?UTF-8?q?=20promises=20call=20accessors=20only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- src/serialize.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/serialize.h b/src/serialize.h index 8150d6f35..430e4799d 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -8104,7 +8104,8 @@ inline ForSectionStubPlan planForSectionStubs( std::string_view sections, std::s } p.legoWillStub = legoCandidate && p.lego.collapse; p.composeWillStub = composeCandidate && p.compose.collapse; - ENSURES( p.servedBytes( legoIn, composeIn ) <= legoIn.renderedBytes + composeIn.renderedBytes, + const std::size_t served = p.servedBytes( legoIn, composeIn ); + ENSURES( served <= legoIn.renderedBytes + composeIn.renderedBytes, "planForSectionStubs: a collapse made the two sections bigger than their measured renders" ); return p; } From e748ab010998e92127df5d4a744d6bbb2c2f84cc Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 8 Oct 2026 21:54:58 -0400 Subject: [PATCH 095/170] =?UTF-8?q?for:=20lego-charge=20=E2=80=94=20only?= =?UTF-8?q?=20--token-budget=20is=20the=20lens's=20explicit=20=20cei?= =?UTF-8?q?ling=20(--max-tokens=20bounds=20--detail=20bodies;=20shapingfla?= =?UTF-8?q?gcheck=20A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 ++-- src/cli.h | 2 +- src/serialize.h | 2 +- src/verbs_for.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97b95fb45..d0618ef10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,8 +23,8 @@ measured Python corpus served 4 of 40 signature rows in a ~4.6 KB answer under t - A section served as a stub is charged at the stub's size (the CLI lens and the MCP `for` tool share one rule). - Without an explicit ceiling, `--sections=lego,compose` keeps the stubbed answer's `` budget, so the stub's `next=` - restores both sections with the same `` rows, byte-identically. Under `--token-budget`/`--max-tokens` (MCP - `budget_tokens`) the restored sections are charged in full, so the hard bound holds. + restores both sections with the same `` rows, byte-identically. Under `--token-budget` (MCP `budget_tokens`) + the restored sections are charged in full, so the hard bound holds. - Measured on 32 `--for` questions over 8 corpora: 19 answers byte-identical, 13 carry more signature rows (4 to 29-33 on the corpus above), and every row the old answer showed is still shown. Gate: `forsectioncollapsecheck` (13). diff --git a/src/cli.h b/src/cli.h index 5e5a66893..852b8b1c9 100644 --- a/src/cli.h +++ b/src/cli.h @@ -988,7 +988,7 @@ inline constexpr char kHelpHead[] = " a counted stub by default — this IS the stub's own next= spelling, so pasting it back\n" " unchanged is enough. A comma-separated, order-insensitive, closed set (lego and/or\n" " compose, each named at most once); every other section of the bundle is unaffected —\n" - " the budget is the stubbed answer's. Under an explicit --token-budget/--max-tokens\n" + " the budget is the stubbed answer's. Under an explicit --token-budget\n" " the restored sections are charged in full, so can carry fewer rows there.\n" " --signatures-only (with --for) signatures only: no automatic bodies in the bundle\n" " (with --for) opt out of the terminal-by-default bundle: no auto bodies, no bundle=\"auto\"\n" diff --git a/src/serialize.h b/src/serialize.h index 430e4799d..7826f35f6 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -8118,7 +8118,7 @@ inline ForSectionStubPlan planForSectionStubs( std::string_view sections, std::s // sections back "byte-identically in one call" (kForSectionStubLegend), and only a call whose match the stubbed // answer's renders them so (the lego is narrowed to the rendered sigs' files, §P3×§P4). The restored sections then ride // on top of that answer, priced in est_tokens=; charging them in full would serve FEWER rows than the stub promised. -// * Under an explicit ceiling (--token-budget / --max-tokens / MCP budget_tokens), a section --sections= opts into is +// * Under an explicit ceiling (--token-budget / MCP budget_tokens), a section --sections= opts into is // charged at its full render: the ceiling is a hard bound, and the stub's next= never carries one. // The charge is taken before the §P3×§P4 narrow, on the pre-narrow render and count. The narrow only empties implementor // lists, so a served stub's total= can only shrink (runForLens ENSURES it). A section served whole was charged at its diff --git a/src/verbs_for.h b/src/verbs_for.h index 4cdabffa8..ca9e9a96f 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -3329,7 +3329,7 @@ std::optional runForLens( const MainDispatch& d ) // lego-charge: / cost what they are SERVED as — a collapsing section its stub, not its full render // (rw::forSectionsSigChargePlan states both rules and why --sections= keeps the default's charge without a ceiling). // A section the degrade path will stream unmeasured has no bytes here (legoStr/composeStr empty): blockCharge discloses it. - const bool explicitForCeiling = cfg.tokenBudget > 0 || cfg.maxTokens > 0; + const bool explicitForCeiling = cfg.tokenBudget > 0; // the lens's only ceiling (--max-tokens bounds --detail bodies) const rw::ForStubbableSection legoChargeIn{ .hasContent = !legoStr.empty(), .preCapTotal = legoPreCapCount, .rendered = legoPreRendered, .renderedBytes = legoStr.size() }; const rw::ForStubbableSection composeChargeIn{ .hasContent = !composeStr.empty(), .preCapTotal = composePreCapCount, From 43d21e56880ef5509d6f232c1adb59e75538fd0e Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 05:28:39 -0400 Subject: [PATCH 096/170] for: under --token-budget, leaves room for the --with-graph block and a first --detail body floor (estchargecheck #11 A7) The graph block and the first --detail body were charged only after had claimed the whole sig-side ceiling, so both rode past an explicit budget (src --for --token-budget=2000 --detail=20 --with-graph: ~6.1 KB vs a 5428 B allowance, over_ceiling=1). forSigClaimWithTrailingReserve caps the sig claim so the graph bytes and the measured first-body floor fit; the --detail budget never drops below that floor. No --token-budget, or neither flag: unchanged. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 5 +++ src/verbs_for.h | 101 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0618ef10..17dddc1e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,11 @@ measured Python corpus served 4 of 40 signature rows in a ~4.6 KB answer under t the restored sections are charged in full, so the hard bound holds. - Measured on 32 `--for` questions over 8 corpora: 19 answers byte-identical, 13 carry more signature rows (4 to 29-33 on the corpus above), and every row the old answer showed is still shown. Gate: `forsectioncollapsecheck` (13). +- Under `--token-budget` with `--with-graph` and/or `--detail`, the `` budget now leaves room for the graph block and + the first `--detail` body (whole up to a quarter of the budget, else cut with a `next=` for the rest). Before, both rode + past the budget: on this repo's own source, `--token-budget=2000 --detail=20 --with-graph` delivered ~6.1 KB against a + 5.4 KB allowance, with `over_ceiling="1"` and a note blaming the header. Without `--token-budget` nothing changes. + Gate: `estchargecheck` #11 A7R. ### Changed — `--for`: the function a question names gets the first hop row, with all its callee names diff --git a/src/verbs_for.h b/src/verbs_for.h index ca9e9a96f..81214cb1f 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -1889,6 +1889,49 @@ std::size_t forSigSideCeiling( bool autoBundleMode, int packTopN, std::size_t bu return bundleBudget; } +// ── THE TRAILING-SECTION RESERVE under an explicit ceiling (lego-charge fix round 4, estchargecheck #11 A7) ── +// §F1 charged --with-graph's block and the --detail bodies, but only AFTER had already claimed the whole +// sig-side ceiling: the graph block (a FIXED cost, no budget knob) and the first --detail body (packBodies serves +// it whole or head-cut even at a 1-byte budget, with its section wrapper and the omitted-bodies comment) then rode +// past the stated budget. MEASURED on base with `src --for --token-budget=2000 --detail=20 --with-graph`: 6177 B +// ("parse the command line flags") and 6167 B ("emit the legend") against a 5428 B allowance, both over_ceiling="1" +// with the ladder's last-rung note claiming the HEADER floor left no payload to trim — the trimmable payload was +// , sized as if the two trailing sections cost nothing. The fix is the forSigSideCeiling idea applied to these +// two sections: under an explicit --token-budget the sig side's claim is capped so the graph block and a first-body +// floor fit inside the bundle budget. The default regime (no --token-budget) and a budgeted run with neither flag +// pass reserveBytes=0, so the claim is the unchanged sigSideCeiling (byte-identical by construction). The cap only +// binds when the bundle cannot hold sigSideCeiling AND the reserve, so a wide explicit ceiling in auto-bundle mode +// (sig side already frozen at the default share) is untouched too — forbudgetmonotoncheck's invariant holds. +std::size_t forSigClaimWithTrailingReserve( std::size_t sigSideCeiling, std::size_t bundleBudget, std::size_t reserveBytes ) noexcept +{ + if( reserveBytes == 0 ) + { + return sigSideCeiling; + } + const std::size_t room = bundleBudget > reserveBytes ? bundleBudget - reserveBytes : 0; + return std::min( sigSideCeiling, room ); +} + +// The FIRST-BODY FLOOR's byte budget: the first --detail body served whole when it is at most a quarter of the +// bundle budget, else head-cut at that quarter (packBodies' truncateOversizedFirst path, which carries lines= and +// the next= that serves the rest — a disclosed, recoverable cut). Only the FIRST body is floored: the rest of +// --detail still takes whatever the ceiling leaves, exactly as before. 0 when there is no servable first body. +inline constexpr std::size_t kForDetailFloorShareDivisor = 4; // the floor's cap = bundleBudget / this +std::size_t forDetailFirstBodyFloor( const rw::IngestResult& ing, const std::vector& detailIds, std::size_t bundleBudget ) +{ + for( const rw::NodeId id : detailIds ) // packBodies' own skip rule: an invalid id or a module scope is no body + { + if( id >= ing.symbols.size() || ing.symbols[id].kind == rw::SymKind::ModuleScope ) + { + continue; + } + const rw::Symbol& s = ing.symbols[id]; + const std::size_t raw = s.endByte > s.sigStartByte ? s.endByte - s.sigStartByte : 0; + return std::min( raw, bundleBudget / kForDetailFloorShareDivisor ); + } + return 0; +} + // The auto-body candidate head: the top-kPackTaskBodyCandidates positive-score rows of the ranked // surface — the same "top heads with a positive score" rule packTaskBundleText applies, on the same // (score desc, id asc) order sigs selected with. @@ -3340,7 +3383,41 @@ std::optional runForLens( const MainDispatch& d ) // than the default cannot re-inflate the trimmed sig tail at the bodies' expense (the rule, its // measured defect and the invariant: forSigSideCeiling above; gate: forbudgetmonotoncheck). const std::size_t sigSideCeiling = forSigSideCeiling( autoBundleMode, cfg.packTopN, bundleBudget ); - const std::size_t sigsBudget = sigSideCeiling > fixedBytes ? sigSideCeiling - fixedBytes : 1; // ≥1: 0 would mean "no budget" + + // §F1's graph block, rendered HERE (before ) rather than beside the bodies: it reads only lensRank, so the + // bytes are the same wherever it renders, and an explicit ceiling must know them before the sig side claims its + // share (fix round 4: forSigClaimWithTrailingReserve). Emission order is unchanged (detail, then graph). + rw::ChargedSection graphSection, detailSection; + if( cfg.withGraph ) + { + graphSection = rw::chargeSection( [ & ]( std::FILE* f ) { packGraphBlock( f, ing, lensRank, g.outOff, g.outTargets ); }, + rw::kBytesPerTokenDefault ); + if( !graphSection.isRendered ) + { + // the block streams uncharged below: the lens's own contract omits an est_tokens that left it out + DISCLOSE( blockCharge, ForLensBlockCharge::DisclosureWhy::SiblingBlockUnmeasured, "runForLens: the graph block streams uncharged — est_tokens omitted" ); + } + } + // fix round 4 (estchargecheck #11 A7): under an EXPLICIT ceiling the graph block and a first --detail body floor are + // reserved out of the sig side's claim, so cannot spend the bytes the two trailing sections will ship. The + // floor is MEASURED — the --detail section rendered at the floor budget (wrapper, the first body, the omitted-bodies + // comment) — not estimated. No explicit ceiling, or neither flag: reserve 0, claim unchanged (byte-identical). + const std::vector detailIds = cfg.detail > 0 ? forDetailIds( ing, lensRank, cfg.detail, forTopN ) : std::vector{}; // (score desc, id asc) — same order as the sigs + const std::size_t detailFloorBudget = explicitForCeiling ? forDetailFirstBodyFloor( ing, detailIds, bundleBudget ) : 0u; + std::size_t trailingReserve = explicitForCeiling ? graphSection.xml.size() : 0u; + if( detailFloorBudget > 0 ) + { + // a MEASURE, not a rendering the reader gets: it redacts the same text into a scratch tally, so a secret the + // real render redacts is still counted once + std::optional floorRedact = redactPtr != nullptr ? std::optional( *redactPtr ) : std::nullopt; + trailingReserve += rw::chargeSection( [ & ]( std::FILE* f ) + { packBodies( f, ing, detailIds, detailFloorBudget, g.outOff, g.outTargets, g.outNameOnly, cfg.compress, floorRedact ? &*floorRedact : nullptr, + /*ranges=*/nullptr, notesPtr, /*outEmitted=*/nullptr, /*truncateOversizedFirst=*/true, + /*withFileContext=*/false, flRootArg, &lensRank, headerParts.viaPresent ); }, + rw::kBytesPerTokenBody ).xml.size(); // a degraded measure is 0 bytes: the reserve falls back to the graph alone + } + const std::size_t sigClaim = forSigClaimWithTrailingReserve( sigSideCeiling, bundleBudget, trailingReserve ); + const std::size_t sigsBudget = sigClaim > fixedBytes ? sigClaim - fixedBytes : 1; // ≥1: 0 would mean "no budget" // The two attributes SPLICED into the header AFTER the ceiling ladder has chosen a rung — est_tokens // (" est_tokens=\"NNNNNNNN\"", bounded well under 24 B: 8 digits covers ~100M tokens) and, when the @@ -3595,22 +3672,11 @@ std::optional runForLens( const MainDispatch& d ) // budget knob, so its size is a FIXED cost; the bodies are the one section with a byte budget, so they // are the section that absorbs whatever the ceiling has left. Pricing the fixed cost first is what lets // the bodies' budget be exact. - rw::ChargedSection graphSection, detailSection; - if( cfg.withGraph ) - { - graphSection = rw::chargeSection( [ & ]( std::FILE* f ) { packGraphBlock( f, ing, lensRank, g.outOff, g.outTargets ); }, - rw::kBytesPerTokenDefault ); - if( !graphSection.isRendered ) - { - // the block streams uncharged below: the lens's own contract omits an est_tokens that left it out - DISCLOSE( blockCharge, ForLensBlockCharge::DisclosureWhy::SiblingBlockUnmeasured, "runForLens: the graph block streams uncharged — est_tokens omitted" ); - } - } + // (graphSection is rendered above, before — fix round 4 reserves its bytes out of the sig side.) // both kept alive past the render so the isRendered=false degrade path below re-emits the SAME set at - // the SAME budget (the map path's emitSection lambda has the identical contract) - std::vector detailIds; - std::size_t detailBodyBudget = 0; + // the SAME budget (the map path's emitSection lambda has the identical contract); detailIds is computed above + std::size_t detailBodyBudget = 0; // --detail=N (lever 3): importance-weighted detail — spend FULL bodies on only // the top-N ranked symbols (the head the rank identifies), leaving the rest as the signatures emitted @@ -3619,7 +3685,6 @@ std::optional runForLens( const MainDispatch& d ) // the lens. N=0 emits nothing → byte-identical to a run without --detail. if( cfg.detail > 0 ) { - detailIds = forDetailIds( ing, lensRank, cfg.detail, forTopN ); // (score desc, id asc) — same order as the sigs // Composes with --max-tokens: when set, it bounds the body byte budget (same conservative rate the // map path uses). §F1: --token-budget SHAPES this lens (D10 — trims to fit, always exit 0), so it // has to bound the bodies as well; before this it bounded ONLY and the bodies rode along on @@ -3636,7 +3701,9 @@ std::optional runForLens( const MainDispatch& d ) + routeStr.size() + graphSection.xml.size() + 6 + headerSpliceReserve + droppedPositiveSpliceReserve + sectionsStubSpliceReserve + rw::kForFileTailShellReserve; // deep-tail: the shell's reserved bytes (explicit regime only — this branch) const std::size_t leftBytes = bundleBudget > spentBytes ? bundleBudget - spentBytes : 1; - detailBodyBudget = std::min( detailBodyBudget, leftBytes ); + // fix round 4: never below the first-body floor the sig side reserved (forDetailFirstBodyFloor) — the header's + // exempt disclosures are paid by this side, and they must not cut the one body the reserve was taken for + detailBodyBudget = std::min( detailBodyBudget, std::max( leftBytes, detailFloorBudget ) ); } detailSection = rw::chargeSection( [ & ]( std::FILE* f ) { packBodies( f, ing, detailIds, detailBodyBudget, g.outOff, g.outTargets, g.outNameOnly, cfg.compress, redactPtr, From 479bb19ac3abf9c2d40274f4ee6fca74a6861329 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 05:39:48 -0400 Subject: [PATCH 097/170] K29 fix round 1: true CHANGELOG disclosure wording, wrapverbscheck covers all 5 CLI-first agents The CHANGELOG claimed every cut at --token-budget=2000 was disclosed with capped="1" and a next=. Only the cut is; the file tail has capped="1" alone, and the whole block drops with no marker on 24 of 40 questions. State that, and name all five CLI-first agents. wrapverbscheck section 8: the budget arm now loops claude, opencode, codex, openclaw, hermes, aider (red on the 7608d1f6 base for all six), and the --max-tokens arm gains openclaw and hermes. The test comment no longer cites the provisional round-2 score as settled. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 14 +++++++------- test/wrapverbscheck.sh | 12 +++++++----- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64aede7bb..7b6966155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,13 +17,13 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ### Changed — `ripwire wrap` recommends `--for` at its default budget -The CLI-first paste block (claude, opencode, codex-style agents) and the aider repo-map line told the agent -to run `--for="" --token-budget=2000`. That explicit budget is a hard ceiling, so the answer kept -about half the bytes of the default and dropped signature, file and call-hop rows the default shows (40 `--for` -questions over eight open-source repositories, one release binary, `--no-cache`: median 4.6 KB against 9.2 KB; -35 questions lost signature rows and 24 lost call-hop rows; every cut was disclosed with `capped="1"` and a `next=`). -Both lines now read `--for=""`, which serves the default budget; the default budget itself and every other knob are unchanged, and a caller can still add -`--token-budget=N` by hand. Only the printed `wrap` text changes. +The CLI-first paste block (claude, codex, opencode, openclaw, hermes) and the aider repo-map line now read +`--for=""` instead of adding `--token-budget=2000`. That budget is a ceiling. Over 40 `--for` questions in +eight open-source repositories (one release binary, `--no-cache`) it halved the median answer (4.6 KB against 9.2 KB). +It dropped signature rows on 35 questions and call-hop rows on 24. Only the signature cut carried `capped="1"` and a +`next=`. The file tail carried `capped="1"` alone, and on 22 questions the whole call-hop block was left out with no +marker. The default budget and every other knob are unchanged, and `--token-budget=N` still works when passed by hand. +Only the printed `wrap` text changes. ### Changed — a call bound by name alone keeps its rows, marked `via="name"`; typed receivers resolve diff --git a/test/wrapverbscheck.sh b/test/wrapverbscheck.sh index 0f3a99e7d..270ceccf7 100755 --- a/test/wrapverbscheck.sh +++ b/test/wrapverbscheck.sh @@ -295,7 +295,7 @@ echo "=== 8. one-shot --for recipes name no budget (never --max-tokens, never a # --token-budget is the flag that actually shapes --for's output. The wrap recipes are the tool's # own advice, so they must not teach the inert pairing (found live 2026-08-20: wrap claude # recommended `--for="" --max-tokens=2000`, which produced an unbudgeted map + a warning). -for _agent in claude codex cursor windsurf gemini opencode aider; do +for _agent in claude codex cursor windsurf gemini opencode openclaw hermes aider; do _out="$( "$BIN" wrap "$_agent" 2>/dev/null )" if ! echo "$_out" | grep -q -- '--for='; then no "wrap $_agent printed no --for= line — the --max-tokens check below would pass on nothing" @@ -305,10 +305,12 @@ for _agent in claude codex cursor windsurf gemini opencode aider; do ok "wrap $_agent never pairs --for with --max-tokens" fi done -# K29 (2026-10-08): the three one-shot --for recipes name NO budget. A budget handed to an agent is a ceiling it -# pastes for every task: --token-budget=2000 answered 5 of 9 measured questions where the default answered 8, and -# the owner rule is lean AND answered. The default budget shapes the answer; the recipe must not narrow it. -for _agent in claude opencode aider; do +# K29 (2026-10-08): the one-shot --for recipes name NO budget: all five CLI-first agents (claude, opencode, codex, +# openclaw, hermes) print the same line, plus aider's repo-map line. A budget handed to an agent is a ceiling it +# pastes for every task: in round-2 mechanical (provisional) scoring --token-budget=2000 answered fewer of the +# measured questions than the default, and the owner rule is lean AND answered. The default budget shapes the +# answer; the recipe must not narrow it. +for _agent in claude opencode codex openclaw hermes aider; do _out="$( "$BIN" wrap "$_agent" 2>/dev/null )" if ! echo "$_out" | grep -q -- ' --for=""'; then no "wrap $_agent lost its one-shot --for=\"\" recipe line" From acb00e3a113e1cb25b276ad49ae5a538b5b610aa Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 05:49:59 -0400 Subject: [PATCH 098/170] changelog(wrap): the call-hop block vanished on 24 questions, 22 of which had hop rows Review delta 1 (rv-wrap-recipe-k29): the round-1 text said 22 where the measured count of questions losing the whole block is 24; two partial hop cuts carried capped="1" alone like the file tail. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b6966155..8c6386f73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,9 +21,9 @@ The CLI-first paste block (claude, codex, opencode, openclaw, hermes) and the ai `--for=""` instead of adding `--token-budget=2000`. That budget is a ceiling. Over 40 `--for` questions in eight open-source repositories (one release binary, `--no-cache`) it halved the median answer (4.6 KB against 9.2 KB). It dropped signature rows on 35 questions and call-hop rows on 24. Only the signature cut carried `capped="1"` and a -`next=`. The file tail carried `capped="1"` alone, and on 22 questions the whole call-hop block was left out with no -marker. The default budget and every other knob are unchanged, and `--token-budget=N` still works when passed by hand. -Only the printed `wrap` text changes. +`next=`. The file tail and two partial hop cuts carried `capped="1"` alone, and on 24 questions the whole call-hop block +was left out with no marker (22 of them had hop rows). The default budget and every other knob are unchanged, and +`--token-budget=N` still works when passed by hand. Only the printed `wrap` text changes. ### Changed — a call bound by name alone keeps its rows, marked `via="name"`; typed receivers resolve From c199dd3877f1a84fdabbf2a81955629a2a2e8ef2 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 05:56:36 -0400 Subject: [PATCH 099/170] for: under --token-budget a cut and a cut name the call that serves the rest (rule 5) An exhausted explicit ceiling dropped the compact section with no element (24 of 40 round-1 questions at 2000 tokens), and the cut carried capped=1 with no next=. Now the exhausted case emits with a present-only legend clause, a partially cut gets the same next=, and a cut carries the --for file page (--limit=N). The bytes are reserved out of the sig claim like the trailing sections, so disclosing never pushes the answer over its budget. Without --token-budget nothing changes. Co-Authored-By: Claude Opus 5.5 --- src/serialize.h | 16 +++++-- src/verbs_for.h | 121 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 124 insertions(+), 13 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index 7826f35f6..12643b312 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -1063,12 +1063,20 @@ inline FileTail computeFileTail( const IngestResult& ing, const std::vector& esc ) +// `cutNextAttr` (a ready ` next="…"` from nextAttrXml, or empty) rides the open tag ONLY when the tail is cut +// (shown < total): the call that serves the files left out. Empty (every caller but the --for explicit-budget +// regime) ⇒ byte-identical to before. +inline std::string renderFileTailXml( const FileTail& t, std::size_t shownCap, std::vector& esc, std::string_view cutNextAttr = {} ) { const std::size_t shown = std::min( shownCap, t.paths.size() ); std::string x = ""; + + "\" capped=\"" + ( shown < t.total ? "1" : "0" ) + "\""; + if( shown < t.total ) + { + x += cutNextAttr; + } + x += ">"; for( std::size_t i = 0; i < shown; ++i ) { x += ""; @@ -1117,11 +1125,11 @@ inline constexpr std::string_view kForHdrLegend = // Explicit-budget row fit: the largest shown count whose rendered XML fits `budgetBytes` (0 rows always // "fits" — the shell is reserved by the caller). Walks down from the collected count; deterministic. -inline std::size_t fileTailShownForBudget( const FileTail& t, std::size_t budgetBytes, std::vector& esc ) +inline std::size_t fileTailShownForBudget( const FileTail& t, std::size_t budgetBytes, std::vector& esc, std::string_view cutNextAttr = {} ) { for( std::size_t shown = t.paths.size(); shown > 0; --shown ) { - if( renderFileTailXml( t, shown, esc ).size() <= budgetBytes ) + if( renderFileTailXml( t, shown, esc, cutNextAttr ).size() <= budgetBytes ) { return shown; } diff --git a/src/verbs_for.h b/src/verbs_for.h index 81214cb1f..47476db00 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2449,6 +2449,71 @@ inline std::vector forDetailIds( const rw::IngestResult& ing, const return ids; } +// fix round 4 (rule 5, review rv-wrap-recipe-k29 F1): the compact section an exhausted explicit ceiling +// leaves out, as a counted marker instead of nothing — ``. +// total= counts the rows the section would have been asked to serve (the owner rows + the candidate head, ids the +// ingest knows) — the requested count packHops itself would print; how many of them have proven edges (noedge=) is +// not known without the render, so the marker does not claim it. next= is the same question without --token-budget, +// the answer whose own compact allowance serves the hop rows. No rows ⇒ no marker (nothing was cut). Markup bytes, +// charged through buildForEnrichment's markupBytes like the rendered section. +inline constexpr std::string_view kForHopsBudgetCutLegend = + "; hops shown=0 capped=1: the call-hop rows (one hop of callees per top row) this budget left out, total= of them; next= serves them"; +// the hops cut's continuation (` next="--for=TASK"`) — one spelling for the marker, the partial-cut splice and the reserve +inline std::string forHopsCutNextAttr( const rw::Config& cfg ) +{ + return rw::nextAttrXml( rw::nextFlag( "--for=", cfg.forTask ) ); +} +// the bytes an explicit ceiling reserves out of the sig claim so the compact hops' cut disclosure always fits: the widest +// marker (``, total <= kPackTaskBodyCandidates + owners, two digits) +// plus its present-only legend clause — the kCompactAttrReserve / kForFileTailShellReserve pattern. +inline std::size_t forHopsCutReserve( const rw::Config& cfg ) +{ + constexpr std::size_t kMarkerShell = std::string_view( "" ).size(); + return kMarkerShell + forHopsCutNextAttr( cfg ).size() + kForHopsBudgetCutLegend.size(); +} + +rw::ChargedSection forHopsBudgetCutMarker( const rw::Config& cfg, const rw::IngestResult& ing, const ForOwnerPlan& ownerPlan, + const std::vector& hopIds ) +{ + rw::ChargedSection marker; + std::size_t total = 0; + for( const rw::NodeId id : forOwnerHopNodes( ownerPlan, hopIds ) ) + { + total += id < ing.symbols.size() ? 1u : 0u; + } + if( total == 0 ) + { + return marker; + } + marker.xml = ""; + marker.tokens = rw::tokensForEmittedBytes( marker.xml.size(), rw::kBytesPerTokenDefault ); + marker.isRendered = true; + return marker; +} + +// fix round 4 (rule 5): an explicit-budget the budget CUT names the call that serves the rest. A cut with no +// next= of its own (the qword_cut= disclosure already carries one) gets ` next=` spliced into the open tag; the bytes +// were held back from the rows' budget (buildForCompactHops' rowRoom), so the section stays inside what it was given. +// A rendered section with shown="0" capped="1" is spliced the same way and then reads as the exhausted-ceiling marker +// does (counted, continued). An uncut section (capped="0") is untouched. +void forHopsCutDisclose( rw::ChargedSection& section, std::string_view cutNext ) +{ + std::string& x = section.xml; + const std::size_t end = x.find( '>' ); + if( !section.isRendered || end == std::string::npos || x.compare( 0, 5, " carrying the count and the call that serves them (the same question without the + // budget, whose compact allowance is its own). The bytes are this section's and the ladder prices them. + out.section = forHopsBudgetCutMarker( cfg, ing, ownerPlan, hopIds ); return out; } // the surface allowance MINUS this surface's own fixed disclosure — see kForCompactSurfaceBudgetBytes. constexpr std::size_t kCompactFixedBytes = kForCompactBundleLegend.size() + kCompactAttrReserve + kCompactWrapReserve; - const std::size_t hopBudget = std::min( leftBytes, rw::kForCompactSurfaceBudgetBytes > kCompactFixedBytes - ? rw::kForCompactSurfaceBudgetBytes - kCompactFixedBytes - : std::size_t( 1 ) ); + // fix round 4: under an explicit ceiling a cut carries next= (forHopsCutNextAttr), so its bytes come out of the + // rows' budget first; the default regime is unchanged (cutNext empty) + const std::string cutNext = cfg.tokenBudget > 0 ? forHopsCutNextAttr( cfg ) : std::string(); + const std::size_t rowRoom = leftBytes > cutNext.size() ? leftBytes - cutNext.size() : 1u; + const std::size_t hopBudget = std::min( rowRoom, rw::kForCompactSurfaceBudgetBytes > kCompactFixedBytes + ? rw::kForCompactSurfaceBudgetBytes - kCompactFixedBytes + : std::size_t( 1 ) ); // THE QUESTION'S OWNER (forOwnerHopPlan): owner rows first, the candidates they were not, the edgeless owners last; // an empty plan leaves both exactly as they were (forOwnerHopNodes / forHopOwners). @@ -2519,6 +2592,10 @@ ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::Ingest } out.attr = hopNodes.empty() ? " bundle=\"compact\" bodies=\"0\" reason=\"no_candidates\"" : " bundle=\"compact\" bodies=\"0\" reason=\"compact-route\""; + if( !cutNext.empty() ) + { + forHopsCutDisclose( out.section, cutNext ); + } return out; } @@ -2555,8 +2632,11 @@ ForAutoBodiesResult buildForEnrichment( const rw::Config& cfg, const rw::IngestR // regime: the full row cap, bytes riding on top (est_tokens measures them). Explicit regime: rows fit the // residual the rendered bundle actually left; the shell's bytes were reserved ahead of the body walk // (kForFileTailShellReserve inside the enrichment's committed sum), so the disclosure always fits. +// fix round 4 (rule 5, review rv-wrap-recipe-k29 F1): under an explicit ceiling the cut tail carries `cutNextAttr` — +// the --for file page (forTailCutNextAttr) that lists every positive-score file — and the shell reserve the caller +// took (forTailShellReserve) includes it, so the disclosure still always fits. inline std::string renderForFileTailXml( const rw::FileTail& tail, std::size_t tokenBudget, - std::size_t bundleBudget, std::size_t spentBytes ) + std::size_t bundleBudget, std::size_t spentBytes, std::string_view cutNextAttr = {} ) { std::vector esc; if( tokenBudget == 0 ) @@ -2564,8 +2644,16 @@ inline std::string renderForFileTailXml( const rw::FileTail& tail, std::size_t t return rw::renderFileTailXml( tail, rw::kForFileTailShownCap, esc ); } const std::size_t tailAllowed = std::max( bundleBudget > spentBytes ? bundleBudget - spentBytes : 0u, - rw::kForFileTailShellReserve ); - return rw::renderFileTailXml( tail, rw::fileTailShownForBudget( tail, tailAllowed, esc ), esc ); + rw::kForFileTailShellReserve + cutNextAttr.size() ); + return rw::renderFileTailXml( tail, rw::fileTailShownForBudget( tail, tailAllowed, esc, cutNextAttr ), esc, cutNextAttr ); +} + +// The explicit-budget tail's cut continuation: the --for file page (one row per positive-score file, pageable), the +// one call that lists the files a budget left out of . Empty without --token-budget: the default regime's tail is +// unchanged (a named deferral — adding it there moves every default --for answer). +inline std::string forTailCutNextAttr( const rw::Config& cfg ) +{ + return cfg.tokenBudget > 0 ? rw::nextAttrXml( rw::forWidenNext( cfg.forTask ) ) : std::string(); } // R2-AF (round 2, S4): render the task's named-file/decl-impl-partner rows (rw::forNamedHeaderRows, @@ -3416,6 +3504,14 @@ std::optional runForLens( const MainDispatch& d ) /*withFileContext=*/false, flRootArg, &lensRank, headerParts.viaPresent ); }, rw::kBytesPerTokenBody ).xml.size(); // a degraded measure is 0 bytes: the reserve falls back to the graph alone } + const std::string forTailNext = forTailCutNextAttr( cfg ); // fix round 4: the explicit-budget tail's cut next= (empty otherwise) + if( explicitForCeiling ) + { + // fix round 4 (rule 5): the two cut disclosures an explicit ceiling owes — the tail's next= and, on the compact + // route, the hops cut (marker or spliced next= + its clause) — are reserved like the trailing sections, so + // disclosing a cut never pushes the answer over the budget it was cut for + trailingReserve += forTailNext.size() + ( plan.compact ? forHopsCutReserve( cfg ) : 0u ); + } const std::size_t sigClaim = forSigClaimWithTrailingReserve( sigSideCeiling, bundleBudget, trailingReserve ); const std::size_t sigsBudget = sigClaim > fixedBytes ? sigClaim - fixedBytes : 1; // ≥1: 0 would mean "no budget" @@ -3699,7 +3795,7 @@ std::optional runForLens( const MainDispatch& d ) { const std::size_t spentBytes = headerStr.size() + forHdrXml.size() + sigsStr.size() + legoStr.size() + composeStr.size() + routeStr.size() + graphSection.xml.size() + 6 + headerSpliceReserve + droppedPositiveSpliceReserve + sectionsStubSpliceReserve - + rw::kForFileTailShellReserve; // deep-tail: the shell's reserved bytes (explicit regime only — this branch) + + rw::kForFileTailShellReserve + forTailNext.size(); // deep-tail: the shell's reserved bytes + its cut next= (explicit regime only — this branch) const std::size_t leftBytes = bundleBudget > spentBytes ? bundleBudget - spentBytes : 1; // fix round 4: never below the first-body floor the sig side reserved (forDetailFirstBodyFloor) — the header's // exempt disclosures are paid by this side, and they must not cut the one body the reserve was taken for @@ -3730,7 +3826,7 @@ std::optional runForLens( const MainDispatch& d ) enrich = buildForEnrichment( cfg, ing, g, lensSurfaceIds, lensRank, plan, routeAnchorDefs, redactPtr, headerStr.size() + forHdrXml.size() + sigsStr.size() + legoStr.size() + composeStr.size() + routeStr.size() + graphSection.xml.size() + 6 + headerSpliceReserve + droppedPositiveSpliceReserve + sectionsStubSpliceReserve - + ( cfg.tokenBudget > 0 ? rw::kForFileTailShellReserve : 0u ), + + ( cfg.tokenBudget > 0 ? rw::kForFileTailShellReserve + forTailNext.size() : 0u ), // deep-tail: under an explicit ceiling the tail SHELL's bytes are // reserved ahead of the body walk (the kAutoAttrReserve pattern) so // the disclosure always fits; the DEFAULT regime reserves nothing — @@ -3755,6 +3851,12 @@ std::optional runForLens( const MainDispatch& d ) } const rw::ChargedSection& autoSection = enrich.section; const std::string& autoAttr = enrich.attr; + if( enrich.legendOff && !autoSection.xml.empty() ) + { + // fix round 4: the hops budget-cut marker rides a header whose compact legend was just dropped — its one + // present-only clause says what the empty element is (priced by the ladder through rootFinish) + sectionsStubNote += kForHopsBudgetCutLegend; + } // ── DEEP-TAIL d2: render the file-grain tail, funded LAST (see serialize.h kForFileTailShownCap) ── // Rendered after the bodies decision so the explicit regime spends only the RESIDUAL the rendered @@ -3766,7 +3868,8 @@ std::optional runForLens( const MainDispatch& d ) const std::string tailStr = renderForFileTailXml( forFileTailShown, cfg.tokenBudget, bundleBudget, headerStr.size() + forHdrXml.size() + sigsStr.size() + legoStr.size() + composeStr.size() + routeStr.size() + graphSection.xml.size() + detailSection.xml.size() - + autoSection.xml.size() + autoAttr.size() + 6 + headerSpliceReserve + droppedPositiveSpliceReserve + sectionsStubSpliceReserve ); + + autoSection.xml.size() + autoAttr.size() + 6 + headerSpliceReserve + droppedPositiveSpliceReserve + sectionsStubSpliceReserve, + forTailNext ); // N1: the last rung's note DEFINES the root attribute it accompanies (over_ceiling= …), so the attribute is // never on a document whose legend does not explain it; the bracket spelling stays. It is PROSE ONLY now — From a8ac1d58b58e82f6e96a00ba4dd8faf53de02e17 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 06:17:14 -0400 Subject: [PATCH 100/170] fix(K51): a prefix walk is labelled a floor, and git that does not run is disclosed Review of fddd4b3c, CHANGES 1-4: 1. A walk that dies part-way keeps the commits it read (a floor is still information) and the history_unread comment now says churn= and amp= count only the commits that were read, both floors; it no longer says churn= is absent beside a printed churn=. The comment names the cause per run (git did not run / stopped part-way); HistoryReadReport counts both. 2. git missing from PATH in a repository: gitHeadSha's "" took the unborn floor. The no-HEAD branch now clears `unread` only on git's own exit 128; the shell's 127/126 or a failed popen stays unread (cause: did not run). 3. historyread.h names the real gate (qchurncheck.sh, K51 block). 4. --cochange / MCP cochange, --rank-by=churn and --owners are named as not covered (CHANGELOG, historyread.h); deferred. qchurncheck: prefix arm (HEAD~2 removed) and git-not-on-PATH arm, both red on fddd4b3c. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 19 +++++++++------ src/gitmine.h | 19 +++++++++++++-- src/historyread.h | 53 ++++++++++++++++++++++++++++++----------- src/main.cpp | 11 ++++++--- src/quality.h | 15 +++++++++--- test/qchurncheck.sh | 58 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 146 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8508c013f..b45e02c63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,13 +25,18 @@ cause and `TMPDIR`, with nothing on stdout, the way `--whereis` refuses on the s ### Fixed — a git history walk that could not be read says so, and is retried -A `git log --name-only` that failed (an unreadable object, a bad ref, git not starting) came back as an empty stream and was -stored under the (repo, HEAD, window) key, so `churn=` and `amp=` read as "no change history" until HEAD moved, with no -tell. A walk git could not finish is now never cached, so the next call walks again, and every document that prints -`churn=` or `amp=` (the map under `--metrics`, `--for`, `--pack-task` and `--grep` with `--metrics`, `--around`) carries -`history_unread="1"` on its root (`"history_unread":true` in JSON) beside a comment that defines it. It means `churn=` is -absent and `amp=` counts callers only. It does not mean the other numbers are affected, and a walk that read an empty -window (a repository with no commit) stays silent. +A `git log --name-only` that failed (an unreadable object, a bad ref) came back as an empty stream or as the newest part of +the window and was stored under the (repo, HEAD, window) key, so `churn=` and `amp=` read as "no change history", or as the +whole window's count, until HEAD moved, with no tell. A git that would not run at all (not on `PATH`) in a repository read +the same way, as an unborn branch. A walk git could not finish is now never cached, so the next call walks again, and every +document that prints `churn=` or `amp=` (the map under `--metrics`, `--for`, `--pack-task` and `--grep` with `--metrics`, +`--around`) carries `history_unread="1"` on its root (`"history_unread":true` in JSON) beside a comment that defines it and +names the cause. It means `churn=` and the co-change half of `amp=` count only the commits that were read, so both are +floors: when git did not run, `churn=` is absent and `amp=` counts callers only; when git stopped part-way, they count the +newest part of the window it read. It does not mean the other numbers are affected, and a walk that read an empty window (a +repository with no commit) stays silent; a HEAD that is corrupt rather than unborn still reads as that empty window. Not yet +covered: `--cochange` (and the MCP `cochange` tool), `--rank-by=churn` and `--owners` run their own git walks and still read a +failed one as empty (`commits="0"`, "found no commits", no ``). ### Fixed — a non-ASCII `--help-task` prompt no longer trips the sanitizer build diff --git a/src/gitmine.h b/src/gitmine.h index ca8cf4bc6..1e08164a4 100644 --- a/src/gitmine.h +++ b/src/gitmine.h @@ -1564,6 +1564,11 @@ struct RawCommitStream // window holds no commit". `commits` is then empty or a prefix, never a measurement, and the memoizing caller // must not store it: an empty stream cached under the (repo, HEAD, window) key silenced churn=/amp= until HEAD moved. bool unread = false; + // Why, when `unread`: git never ran (no popen, or the shell could not find/execute it: exit 127/126) — `commits` is + // empty — as against git ran and stopped part-way, `commits` the newest-first prefix it printed before it died. + bool notRun = false; + // git's decoded exit code (WEXITSTATUS), -1 when it did not exit normally or never started; 0 on a clean read. + int exitCode = 0; }; inline RawCommitStream gitLogNameOnlyRaw( const std::string& root, const std::string& coSince ) @@ -1576,7 +1581,9 @@ inline RawCommitStream gitLogNameOnlyRaw( const std::string& root, const std::st std::FILE* pipe = os::popen( cmd.c_str(), "r" ); if( !pipe ) { - out.unread = true; + out.unread = true; + out.notRun = true; + out.exitCode = -1; return out; } @@ -1603,7 +1610,15 @@ inline RawCommitStream gitLogNameOnlyRaw( const std::string& root, const std::st } out.commits.back().paths.push_back( std::move( s ) ); // readByteSafeLine clear()s its buffer first, so moving out of it is safe } - out.unread = os::pclose( pipe ) != 0; // K51: git ran and died (a missing or corrupt object, a bad ref) — the lines above are a prefix, not the window + const int status = os::pclose( pipe ); + if( status != 0 ) + { + // K51: git died (a missing or corrupt object, a bad ref) — the lines above are a prefix, not the window — or the + // shell never ran it (127: not on PATH, 126: not executable), and there are no lines at all. + out.unread = true; + out.exitCode = WIFEXITED( status ) ? WEXITSTATUS( status ) : -1; + out.notRun = out.exitCode == 127 || out.exitCode == 126; + } return out; } diff --git a/src/historyread.h b/src/historyread.h index 2b1bb7993..b58225086 100644 --- a/src/historyread.h +++ b/src/historyread.h @@ -4,15 +4,23 @@ // // The history walk (`git log` over the 18-month window) feeds two numbers every rich verb prints: churn= (--for, the // commit count of a file) and amp= (a symbol's callers plus the files that share a commit with it). When git cannot -// finish that walk — an unreadable object, a bad ref, git failing to start — the stream is EMPTY OR A PREFIX, and both -// numbers read as "this file never changed" and "no co-change partners": a zero that measured nothing. This header is -// the disclosure for it, in the dialect of each host document, so the failure is a field of the answer, not a trace. +// finish that walk the stream is EMPTY (git never ran: not on PATH, not executable, no popen) or a newest-first PREFIX +// (git ran and stopped part-way: an unreadable object, a bad ref). Undisclosed, both numbers read as "this file never +// changed" / "no co-change partners", or as the whole window when they are only its newest part. This header is the +// disclosure for it, in the dialect of each host document, so the failure is a field of the answer, not a trace. // // THE ATTRIBUTE. history_unread="1" on the root of a document that prints churn= or amp=; absent when the walk was read -// (including a read that found no commit: that is a measurement, and it stays silent). It means: churn= is not -// available and amp= counts callers only — a FLOOR. It does NOT mean the repository is damaged beyond that walk, that the -// other numbers in the document are affected, or that a retry will fail: the failed walk is never cached, so the next call -// walks again. A verb that runs no history walk (a plain map, --callers) never carries it. +// (including a read that found no commit — an unborn branch, git's own exit 128 with no HEAD: that is a measurement, and +// it stays silent). It means: churn= and the co-change half of amp= count only the commits that WERE read, so both are +// FLOORS — none read when git did not run (churn= absent, amp= callers only), the newest part of the window when it +// stopped part-way. A prefix is kept rather than dropped because a floor is still information (a file with churn="3" from +// a prefix changed at least 3 times); the attribute is what stops it reading as the full count. An absent churn= under +// the attribute is not zero churn. It does NOT mean the repository is damaged beyond that walk, that the other numbers +// in the document are affected, or that a retry will fail: the failed walk is never cached, so the next call walks again. +// The comment names which cause this run hit. A verb that runs no history walk (a plain map, --callers) never carries it. +// +// NOT COVERED (pre-existing, deferred): the other git walks — --cochange and the MCP cochange tool, --rank-by=churn and +// --owners — still read a failed walk as an empty one (CHANGELOG, Known issues). // // ONE RENDERER (the prconverge.h rule): the forms differ only in the syntax of their host, never in what they say, so a // new host adds a DiscloseAs case here, never a sibling function. XmlAttrs and the comment form (LegendComment) go on every @@ -21,7 +29,7 @@ // // STATE. One process-wide report, written once by main.cpp's amp=/churn= block on the main thread, after the walks were // joined and before any emitter runs; emitters only read it. The MCP server never runs that block, so it never sets it. -// Gate: test/historyreadcheck.sh. +// Gate: test/qchurncheck.sh (the K51 block). #include #include @@ -31,15 +39,22 @@ namespace rw { -// The DISCLOSE sink (CONTRIBUTING §3): `unreadRoots` counts the roots whose walk could not be read. +// The DISCLOSE sink (CONTRIBUTING §3): the roots whose walk could not be read, by cause. struct HistoryReadReport { enum class DisclosureWhy : std::uint8_t { - WalkUnread, // git log did not start or did not finish: the root's stream is empty or a prefix + WalkNotRun, // git did not run (not on PATH, not executable, no popen): the root's stream is empty + WalkStopped, // git ran and exited non-zero part-way: the root's stream is a newest-first prefix (possibly empty) }; - std::uint32_t unreadRoots = 0; - void disclose( DisclosureWhy ) noexcept { ++unreadRoots; } + std::uint32_t unreadRoots = 0; + std::uint32_t notRunRoots = 0; + std::uint32_t stoppedRoots = 0; + void disclose( DisclosureWhy why ) noexcept + { + ++unreadRoots; + ++( why == DisclosureWhy::WalkNotRun ? notRunRoots : stoppedRoots ); + } }; inline HistoryReadReport& historyRead() noexcept @@ -63,8 +78,18 @@ inline std::string renderHistoryUnread( DiscloseAs as ) { return ",\"history_unread\":true"; } - return ""; + std::string comment = ""; + return comment; } } // namespace rw diff --git a/src/main.cpp b/src/main.cpp index eed977798..f3ea17d11 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5258,10 +5258,15 @@ static int dispatchMain( const rw::Config& cfg, char** argv ) const auto joinWalk = [ & ]( std::size_t r ) { quality::HistoryWalk walk = historyWalks[ r ].get(); - if( walk.raw.unread ) + if( walk.raw.unread && walk.raw.notRun ) { - DISCLOSE( historyReport, HistoryReadReport::DisclosureWhy::WalkUnread, - "main: the git history walk could not be read — churn= is absent and amp= counts callers only, and the document says so" ); + DISCLOSE( historyReport, HistoryReadReport::DisclosureWhy::WalkNotRun, + "main: git did not run for the history walk — churn= is absent and amp= counts callers only, and the document says so" ); + } + else if( walk.raw.unread ) + { + DISCLOSE( historyReport, HistoryReadReport::DisclosureWhy::WalkStopped, + "main: the git history walk stopped part-way — churn= and amp= count only the commits read (floors), and the document says so" ); } return walk; }; diff --git a/src/quality.h b/src/quality.h index 6423eb82a..c2937a3e3 100644 --- a/src/quality.h +++ b/src/quality.h @@ -4590,10 +4590,19 @@ inline RawCommitStream gitRawCommitStreamCached( const std::string& root, const const std::string headSha = gitHeadSha( root ); if( headSha.empty() ) { - // No resolvable HEAD: an unborn branch has no history to read, and git log's failure there is that fact, not an I/O - // error — so this walk is "read, empty" (K51 floor: a HEAD that is corrupt rather than unborn reads the same here). + // No resolvable HEAD: an unborn branch has no history to read, and git log's failure there (git's own fatal exit, + // 128) is that fact, not an I/O error — so that walk is "read, empty" (K51 floor: a HEAD that is corrupt rather than + // unborn exits 128 too and reads the same here). Any OTHER failure means git did not answer at all — not on PATH + // (the shell's 127), not executable, no popen — and gitHeadSha's "" was that, not an unborn branch: it stays unread. RawCommitStream noHead = gitLogNameOnlyRaw( root, coSince ); - noHead.unread = false; + if( noHead.unread && noHead.exitCode == 128 ) + { + noHead.unread = false; + } + else if( noHead.unread ) + { + noHead.notRun = true; + } return noHead; } diff --git a/test/qchurncheck.sh b/test/qchurncheck.sh index e7b9cc619..c2ed160d0 100755 --- a/test/qchurncheck.sh +++ b/test/qchurncheck.sh @@ -196,6 +196,64 @@ else no "K51: premise — the parent commit is not a loose object ($kobj), so the real-failure arm cannot run" fi +# the PREFIX: git dies two commits below HEAD, after printing the newest part of the window. The commits it printed are KEPT +# (a floor is still information) and the document must say they are a floor, never "churn= is absent" beside a churn= value. +# Red on fddd4b3c: it printed churn="1" next to a comment saying churn= is absent. +printf '// k51c\n' >> "$REPO/src/lib.cpp" +git -C "$REPO" commit -qam "k51c" +kgp="$( git -C "$REPO" rev-parse HEAD~2 )"; kgpobj="$REPO/.git/objects/${kgp:0:2}/${kgp:2}" +KPRETMP="$TMP/kpretmp"; mkdir -p "$KPRETMP" # its own cache dir: a healthy blob under $QTMP would serve the broken run warm +env TMPDIR="$KPRETMP" "$BIN" "$REPO" --for=helper --no-cache --legend=full >"$TMP/k_pre1.out" 2>/dev/null +khealthy="$( grep -o ' p="src/lib.cpp"[^>]* churn="[0-9]*"' "$TMP/k_pre1.out" | head -1 | sed 's/.*churn="\([0-9]*\)"/\1/' )" +if [ -f "$kgpobj" ] && [ -n "$khealthy" ]; then + cp "$kgpobj" "$TMP/kgpobj.save"; rm -f "$kgpobj" + run "$TMP/k_pre.log" --for=helper --no-cache --legend=full >"$TMP/k_pre.out" 2>/dev/null; rcP=$? + kpre="$( grep -o ' p="src/lib.cpp"[^>]* churn="[0-9]*"' "$TMP/k_pre.out" | head -1 | sed 's/.*churn="\([0-9]*\)"/\1/' )" + if [ "$rcP" -eq 0 ] && grep -q ' history_unread="1"' "$TMP/k_pre.out"; then + ok "K51: a walk that dies two commits below HEAD is disclosed" + else + no "K51: a prefix walk is silent (rc=$rcP): $( head -c 200 "$TMP/k_pre.out" )" + fi + if [ -n "$kpre" ] && [ "$kpre" -ge 1 ] && [ "$kpre" -lt "$khealthy" ]; then + ok "K51: the prefix keeps its commits as a floor (churn=$kpre of $khealthy)" + else + no "K51: the prefix churn= is not a floor below the healthy count (prefix='$kpre', healthy=$khealthy)" + fi + if grep -q 'history_unread=1: the git history walk.*(git stopped part-way), so churn= and amp= count only the commits that were read: both are floors' "$TMP/k_pre.out"; then + ok "K51: the prefix comment names the cause and says churn=/amp= are floors" + else + no "K51: the prefix comment does not call churn=/amp= floors: $( grep -o '"; + "program entry by its language's convention: a main nothing in the repo calls in C, C++, ObjC, Go, Rust, Kotlin or " + "Dart; a static Main/main method nothing calls in Java or C#; in a script language (Python, JS, TS, Ruby, Bash, PHP, " + "Lua) a main that its own file's module scope calls and nothing else does; or a __main__.py module scope; its " + "resolved callees as c rows. A method named main elsewhere, or a script main nothing runs, is not an entry; entries " + "declared only in a manifest (package.json main/bin, console_scripts) or by an attribute (Swift @main) are not " + "detected. ls p= n= f=: n= unshown files under directory p=, f= the code files among them by name; test, doc and " + "config files are counted, not named, and a test/e2e/bench/benchmark/fixture/example/_examples/doc directory rolls " + "up whole. ripwire on p= ranks that directory. Runaway guards: entries_capped=1 entries_total=N when more than 256 " + "entries exist (the first 256 by path shown), entries_next= the argv listing every def that could be a cut entry (a " + "superset); names_capped=1 names_total=N when the N unshown code files pass a 2000 name ceiling (directories past " + "it, path order, keep n= only), names_next= the argv listing every indexed file (a superset) -->"; } // namespace rw diff --git a/src/serialize.h b/src/serialize.h index 4c30aa27a..0fd942b9e 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -2770,8 +2770,16 @@ inline void writeMapInventoryXml( XmlWriter& w, const IngestResult& ing, const M EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( " inv.entries.size() ) { w.write( " entries_capped=\"1\" entries_total=\"" ); w.write( std::to_string( inv.entryTotal ) ); w.write( "\"" ); } - if( inv.namesCapped ) { w.write( " names_capped=\"1\" names_total=\"" ); w.write( std::to_string( inv.namesTotal ) ); w.write( "\"" ); } + if( inv.entryTotal > inv.entries.size() ) + { + w.write( " entries_capped=\"1\" entries_total=\"" ); w.write( std::to_string( inv.entryTotal ) ); w.write( "\"" ); + w.write( nextAttrXml( inv.entriesNext, "entries_next" ) ); // rule 5: the cut is recoverable + } + if( inv.namesCapped ) + { + w.write( " names_capped=\"1\" names_total=\"" ); w.write( std::to_string( inv.namesTotal ) ); w.write( "\"" ); + w.write( nextAttrXml( inv.namesNext, "names_next" ) ); + } w.write( ">" ); for( NodeId id : inv.entries ) { @@ -9532,8 +9540,16 @@ inline void writeMapInventoryJson( JsonWriter& w, const IngestResult& ing, const EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( ",\"inv\":{\"listed\":" ); w.write( std::to_string( inv.listed ) ); w.write( ",\"unlisted\":" ); w.write( std::to_string( inv.unlisted ) ); - if( inv.entryTotal > inv.entries.size() ) { w.write( ",\"entries_capped\":true,\"entries_total\":" ); w.write( std::to_string( inv.entryTotal ) ); } - if( inv.namesCapped ) { w.write( ",\"names_capped\":true,\"names_total\":" ); w.write( std::to_string( inv.namesTotal ) ); } + if( inv.entryTotal > inv.entries.size() ) + { + w.write( ",\"entries_capped\":true,\"entries_total\":" ); w.write( std::to_string( inv.entryTotal ) ); + w.write( ",\"entries_next\":" ); writeJsonStr( w, inv.entriesNext, esc ); + } + if( inv.namesCapped ) + { + w.write( ",\"names_capped\":true,\"names_total\":" ); w.write( std::to_string( inv.namesTotal ) ); + w.write( ",\"names_next\":" ); writeJsonStr( w, inv.namesNext, esc ); + } w.write( ",\"entry\":[" ); for( std::size_t i = 0; i < inv.entries.size(); ++i ) { diff --git a/test/mapinventorycheck.sh b/test/mapinventorycheck.sh index de91421a6..d7639ad04 100755 --- a/test/mapinventorycheck.sh +++ b/test/mapinventorycheck.sh @@ -139,21 +139,85 @@ fi run --top-k=3 --most-important-last >"$TMP/last" if grep -q '">.*' "$TMP/last"; then ok "(O) --most-important-last: precedes the rows"; else fail "(O) --most-important-last: is not before the ranked rows"; fi -# (P) runaway guards, each at the boundary and one past it (generated corpora; top-k=1 shows one file) +# (P) runaway guards, each at the boundary and one past it (generated corpora; top-k=1 shows one file). Past either +# guard the cut is RECOVERABLE (PROCESS rule 5): entries_next= / names_next= are argv on the same root that run and list +# what was cut — asserted by running them and finding a cut item in the answer. gen_c(){ d="$1"; n="$2"; mkdir -p "$d/src"; i=0; while [ "$i" -lt "$n" ]; do printf 'int fn%d( void ) { return %d; }\n' "$i" "$i" >"$d/src/f$i.c"; i=$(( i + 1 )); done; } gen_go(){ d="$1"; n="$2"; i=0; while [ "$i" -lt "$n" ]; do mkdir -p "$d/cmd/p$i"; printf 'package main\n\nfunc main() {}\n' >"$d/cmd/p$i/main.go"; i=$(( i + 1 )); done; } gen_c "$TMP/c2001" 2001; gen_c "$TMP/c2002" 2002 -gen_go "$TMP/g32" 32; gen_c "$TMP/g32" 2; gen_go "$TMP/g33" 33; gen_c "$TMP/g33" 2 +gen_go "$TMP/g256" 256; gen_c "$TMP/g256" 2; gen_go "$TMP/g257" 257; gen_c "$TMP/g257" 2 "$BIN" "$TMP/c2001" --no-cache --top-k=1 >"$TMP/c2001.out" 2>/dev/null "$BIN" "$TMP/c2002" --no-cache --top-k=1 >"$TMP/c2002.out" 2>/dev/null -"$BIN" "$TMP/g32" --no-cache --top-k=1 >"$TMP/g32.out" 2>/dev/null -"$BIN" "$TMP/g33" --no-cache --top-k=1 >"$TMP/g33.out" 2>/dev/null +"$BIN" "$TMP/c2002" --no-cache --top-k=1 --json >"$TMP/c2002.json" 2>/dev/null +"$BIN" "$TMP/g256" --no-cache --top-k=1 >"$TMP/g256.out" 2>/dev/null +"$BIN" "$TMP/g257" --no-cache --top-k=1 >"$TMP/g257.out" 2>/dev/null +"$BIN" "$TMP/g257" --no-cache --top-k=1 --json >"$TMP/g257.json" 2>/dev/null NC1="$( grep -o '' "$TMP/c2002.out" && grep -q '' "$TMP/c2002.out"; then ok "(P) 2001 past the ceiling: the dir keeps n= only, names_capped=1 names_total=2001"; else fail "(P) past the ceiling: $( grep -o ']*>' "$TMP/c2002.out" ) $( grep -o '' "$TMP/c2002.out" && grep -q '' "$TMP/c2002.out"; then ok "(P) 2001 past the ceiling: the dir keeps n= only, names_capped=1 names_total=2001 names_next="; else fail "(P) past the ceiling: $( grep -o ']*>' "$TMP/c2002.out" ) $( grep -o '/g;s/&/\&/g'; } +CUTE="$( grep -o '"$TMP/enext.out" 2>"$TMP/enext.err"; rcE=$? + if [ "$rcE" -eq 0 ] && [ -s "$TMP/enext.out" ] && grep -q "p=\"$LASTE:" "$TMP/enext.out"; then ok "(P2) entries_next [$ENEXT] runs and lists the cut entry $LASTE"; else fail "(P2) entries_next [$ENEXT]: rc=$rcE, cut entry $LASTE listed: $( grep -c "p=\"$LASTE:" "$TMP/enext.out" )"; fi +else + fail "(P2) no entries_next= (or the cut entry $LASTE was shown)" +fi +NNEXT="$( grep -o 'names_next="[^"]*"' "$TMP/c2002.out" | sed 's/^names_next="//;s/"$//' | xmlunesc )" +if [ -n "$NNEXT" ]; then + eval "\"\$BIN\" \"\$TMP/c2002\" --no-cache $NNEXT" >"$TMP/nnext.out" 2>"$TMP/nnext.err"; rcN=$? + NLIST="$( grep -o '/dev/null )" +JNN="$( python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["inv"].get("names_next",""))' "$TMP/c2002.json" 2>/dev/null )" +if [ -n "$JEN" ] && [ "$JEN" = "$ENEXT" ] && [ -n "$JNN" ] && [ "$JNN" = "$NNEXT" ]; then ok "(P3) JSON entries_next / names_next = XML"; else fail "(P3) JSON next: entries [$JEN] vs [$ENEXT], names [$JNN] vs [$NNEXT]"; fi + +# (Q) an entry is claimed only by its language's OWN convention (fix round 1: a method or an uncalled script function +# named main was labelled a program entry). Six near-misses, each must NOT be an entry: a Python / JS class method +# main, a Ruby singleton method main, a Python main with no __main__ guard and no caller, an uncalled exported TS +# main, a Bash main defined and never invoked. Positive controls in the same run: a Bash script that runs +# `main "$@"`, a Python main under the __main__ guard, a C main. +Q="$TMP/langs"; mkdir -p "$Q/lib" "$Q/web" "$Q/bin" "$Q/pkg" +printf 'class Runner:\n def main(self):\n return 2\n' >"$Q/lib/runner.py" +printf 'class Job { main() { return 3 } }\nmodule.exports = Job\n' >"$Q/web/job.js" +printf 'module Foo\n def self.main\n 1\n end\nend\n' >"$Q/lib/foo.rb" +printf 'def helper():\n return 1\n\ndef main():\n return helper()\n' >"$Q/lib/unused_main.py" +printf 'export function main() { return 1 }\n' >"$Q/web/lib.ts" +printf 'main() { echo hi; }\n' >"$Q/lib/lib.sh" +printf '#!/bin/sh\nwork() { echo w; }\nmain() { work; }\nmain "$@"\n' >"$Q/bin/run.sh" +printf 'def go():\n return 1\n\ndef main():\n return go()\n\nif __name__ == "__main__":\n main()\n' >"$Q/bin/tool.py" +printf 'int step( void ) { return 0; }\nint main( void ) { return step(); }\n' >"$Q/prog.c" +i=0; while [ "$i" -lt 30 ]; do printf 'def g%d():\n return %d\n' "$i" "$i" >"$Q/pkg/m$i.py"; i=$(( i + 1 )); done +"$BIN" "$Q" --no-cache --top-k=5 >"$TMP/langs.out" 2>/dev/null +QE="$( grep -o ' row. integration/ and testing/ stay named (real package names, +# stated in mapinventory.h): their main is judged by the entry rule. Control: cmd/app/main.go stays an entry. +R="$TMP/sibs" +for d in benchmark _examples/hello _example/hi e2e integration cmd/app; do mkdir -p "$R/$d"; printf 'package main\n\nfunc helperX() {}\n\nfunc main() { helperX() }\n' >"$R/$d/main.go"; done +i=0; mkdir -p "$R/pkg"; while [ "$i" -lt 30 ]; do printf 'package pkg\n\nfunc G%d() int { return %d }\n' "$i" "$i" >"$R/pkg/m$i.go"; i=$(( i + 1 )); done +"$BIN" "$R" --no-cache --top-k=5 >"$TMP/sibs.out" 2>/dev/null +for d in benchmark _examples _example e2e; do + if grep -q "" "$TMP/sibs.out"; then ok "(R) $d/ rolls up count-only"; else fail "(R) $d/ is not a count-only row: $( grep -o "]*>" "$TMP/sibs.out" )"; fi +done +if grep -q '' "$TMP/sibs.out" && grep -q '' "$TMP/sibs.out"; then ok "(R) controls: cmd/app and integration/ mains stay entries"; else fail "(R) controls: $( grep -o ' Date: Fri, 9 Oct 2026 07:42:43 -0400 Subject: [PATCH 107/170] fix(map inventory): a class member main is never a script entry, even when called Python indexes `def main(self)` as a Function scoped to its class, so the module-scope-call rule alone claimed `Runner().main()`. A script language's main must be top-level (no enclosing scope). New gate arms: a Python, JS and Ruby member main, each called from its file's module scope. Co-Authored-By: Claude Opus 5.5 --- src/mapinventory.h | 6 ++++-- test/mapinventorycheck.sh | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/mapinventory.h b/src/mapinventory.h index e71223363..8b40eca54 100644 --- a/src/mapinventory.h +++ b/src/mapinventory.h @@ -146,8 +146,10 @@ inline bool isInventoryEntryShape( const Symbol& s ) noexcept { switch( inventoryEntryStyle( s.lang ) ) { - case InventoryEntryStyle::EntryFn: - case InventoryEntryStyle::ModuleScopeCall: return s.kind == SymKind::Function && s.name == "main"; + case InventoryEntryStyle::EntryFn: return s.kind == SymKind::Function && s.name == "main"; + // a script language indexes a class member as a Function with its class as scope (Python `def main(self)`): + // only a TOP-LEVEL main is the script's entry + case InventoryEntryStyle::ModuleScopeCall: return s.kind == SymKind::Function && s.name == "main" && s.scope.empty(); case InventoryEntryStyle::EntryStaticMethod: return s.kind == SymKind::Method && ( s.name == "main" || s.name == "Main" ); case InventoryEntryStyle::None: break; } diff --git a/test/mapinventorycheck.sh b/test/mapinventorycheck.sh index d7639ad04..930d54733 100755 --- a/test/mapinventorycheck.sh +++ b/test/mapinventorycheck.sh @@ -184,9 +184,9 @@ JNN="$( python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["inv"].g if [ -n "$JEN" ] && [ "$JEN" = "$ENEXT" ] && [ -n "$JNN" ] && [ "$JNN" = "$NNEXT" ]; then ok "(P3) JSON entries_next / names_next = XML"; else fail "(P3) JSON next: entries [$JEN] vs [$ENEXT], names [$JNN] vs [$NNEXT]"; fi # (Q) an entry is claimed only by its language's OWN convention (fix round 1: a method or an uncalled script function -# named main was labelled a program entry). Six near-misses, each must NOT be an entry: a Python / JS class method -# main, a Ruby singleton method main, a Python main with no __main__ guard and no caller, an uncalled exported TS -# main, a Bash main defined and never invoked. Positive controls in the same run: a Bash script that runs +# named main was labelled a program entry). Nine near-misses, each must NOT be an entry: a Python / JS class method +# main, a Ruby singleton method main (each uncalled, then each called from module scope), a Python main with no +# __main__ guard and no caller, an uncalled exported TS main, a Bash main defined and never invoked. Positive controls in the same run: a Bash script that runs # `main "$@"`, a Python main under the __main__ guard, a C main. Q="$TMP/langs"; mkdir -p "$Q/lib" "$Q/web" "$Q/bin" "$Q/pkg" printf 'class Runner:\n def main(self):\n return 2\n' >"$Q/lib/runner.py" @@ -195,13 +195,18 @@ printf 'module Foo\n def self.main\n 1\n end\nend\n' >"$Q/lib/foo.rb" printf 'def helper():\n return 1\n\ndef main():\n return helper()\n' >"$Q/lib/unused_main.py" printf 'export function main() { return 1 }\n' >"$Q/web/lib.ts" printf 'main() { echo hi; }\n' >"$Q/lib/lib.sh" +# the same class members, now CALLED by their file's module scope: still methods, never the script's entry (Python +# indexes `def main(self)` as a Function scoped to its class, so the scope-call rule alone would claim it) +printf 'class Runner:\n def main(self):\n return 2\n\nRunner().main()\n' >"$Q/lib/runner_called.py" +printf 'class Job { main() { return 3 } }\nnew Job().main()\n' >"$Q/web/job_called.js" +printf 'class Tool\n def self.main\n 1\n end\nend\nTool.main\n' >"$Q/lib/tool_called.rb" printf '#!/bin/sh\nwork() { echo w; }\nmain() { work; }\nmain "$@"\n' >"$Q/bin/run.sh" printf 'def go():\n return 1\n\ndef main():\n return go()\n\nif __name__ == "__main__":\n main()\n' >"$Q/bin/tool.py" printf 'int step( void ) { return 0; }\nint main( void ) { return step(); }\n' >"$Q/prog.c" i=0; while [ "$i" -lt 30 ]; do printf 'def g%d():\n return %d\n' "$i" "$i" >"$Q/pkg/m$i.py"; i=$(( i + 1 )); done "$BIN" "$Q" --no-cache --top-k=5 >"$TMP/langs.out" 2>/dev/null QE="$( grep -o ' Date: Fri, 9 Oct 2026 08:09:04 -0400 Subject: [PATCH 108/170] fix(expand-lean): pasteable, honest continuations; quality-bar gating stated once Review fix round 1 for lane/expand-lean-k64: - map_next= replays every root of the call (a multi-root call used to fall back to "ripwire .") plus the crawl-scope flags scopedMapNextInvocation replays (--exclude, --max-file-size, --no-ignore, --ignore-tests), each quoted with rw::nextFlag; the shared crawlScopeFlags helper keeps the --in next= byte-identical. - unserved_next= is built with rw::nextFlag, so a path with a space pastes. - past the 16-selector guard the root adds unserved_listed="16"; legend, --help, CHANGELOG and the navigate skill say unserved_next serves the first unserved_listed of them and a larger --pack-budget-bytes serves the rest. - expandtopk0check arms (M) multi-root and crawl-scope pointers, pasted; (N) space path, >16 cut, paste and recovery; (O) multi-token --outline keeps the map. - quality-bar skill: the gating rule is stated once ("What exits 2"); the repeats and the eleventh/twelfth clauses are rewritten. printf_parity: help_all re-pinned. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 8 +- docs/COMMANDS.md | 8 +- skills/ripwire-navigate/SKILL.md | 3 +- skills/ripwire-quality-bar/SKILL.md | 24 ++--- skills/ripwire-quality-bar/quality-metrics.md | 8 +- src/cli.h | 10 +- src/compactlegend.h | 4 +- src/main.cpp | 77 +++++++++------ test/expandtopk0check.sh | 96 ++++++++++++++++++- test/printf_parity.manifest | 2 +- 10 files changed, 179 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3a4dd7bf..1457a6dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,9 +21,11 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli beside bodies that already answered the request (on this repository `--expand=subtokens`: 26,752 B, of which 19,444 B was map rows that never mention the symbol; `--expand=emitTo` served the whole 24,788 B file because the whole-file comparison priced the bundle with that map). When every requested definition is served the map now defaults to -`top-k=0`: the root carries `topk_default="0"` and `map_next="ripwire ROOT"` (the call that prints the map, for a session -that skipped orienting). When a byte budget cut some definitions the root names exactly those in `unserved_total=N` and -`unserved_next="--expand=FILE:LINE:NAME,..."` instead of shipping the map. The whole-file comparison therefore prices the +`top-k=0`: the root carries `topk_default="0"` and `map_next="ripwire ROOT..."` (the call that prints the map, for a session +that skipped orienting: every root of the call plus its crawl-scope flags, shell-quoted). When a byte budget cut some +definitions the root names exactly those in `unserved_total=N` (the true count) and a shell-quoted +`unserved_next="--expand=FILE:LINE:NAME,..."` instead of shipping the map; past 16 cut definitions it serves the first 16, +`unserved_listed="16"` says so, and a larger `--pack-budget-bytes` serves the rest. The whole-file comparison therefore prices the payload only. An explicit `--top-k=N` keeps the previous shape. Language-neutral: the decision reads the record of which requested definitions were emitted. diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 600044fa4..a8efcf3f6 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -1572,7 +1572,7 @@ $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 **Answers:** print these symbols' full bodies, plus the signatures of what they call full bodies of A,B,... -Selector grammar per item (the tail after the LAST ':' decides; a tail STARTING WITH A DIGIT is a range, anything else is a name): NAME every def of that name | FILE:NAME that file's def NAME:START-END body slice | FILE:NAME:START-END selector + slice FILE:LINE:NAME paste a row's p="path:line" straight from --callers/--lint/--grep (NOT --hotspots: its p= is a BARE path — build FILE:LINE:NAME from its own p=/top_l=/top= instead, since top= is just the worst function's name) path::scope::name the id composed from a row's p= sc= n= (the map, --for, --pack-task) Scope::name the sym= spelling edit-check and grep's in= rows print — matches the name under any scope whose ::-boundary SUFFIX is Scope (Box::lid, deep::Box::lid); a wrong scope refuses, it never falls back to the bare-name union Scope.name|Scope#name the same match, dotted (Python/JS/Java) or Ruby-style; tried only when no other spelling matched (START-END is 1-based within the def's OWN body — lines="lo-hi/total" marks the slice partial; out-of-range clamps. FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. Disclosed on the root as topk_default="0" (self-describing: the change is visible without reading source). The same default applies when --expand names SEVERAL definitions (an ambiguous bare name, or several tokens) and EVERY one is served: the bodies carry p= l= t= for each, the root adds map_next= (the shell call that prints the map you did not get — orient once per session, then look up lean). If a byte budget cut some, the root adds unserved_total=N and unserved_next="--expand= FILE:LINE:NAME,…" (the call serving them) — never the generic map. --outline follows the same rule for its one token. An EXPLICIT --top-k=N (0 included) always overrides this default. Each body also carries sibs="a,b,..." sibs_total="N" [sibs_capped="1"] (the file's OTHER symbols, names only, capped at 40) and inc="x.h,..." inc_total="N" [inc_capped="1"] (the file's own #include/import targets, capped at 24) — both absent when the count is 0 (a documented zero, not a degrade), so a body no longer needs a second --outline call just to learn what else lives in its file. CHEAPEST-COMPLETE-ANSWER SERVING (no explicit --top-k, no range slice): the verb ALSO measures the (possibly map-less) bundle against the requested symbols' whole FILE(s) and emits the SMALLER, disclosed on the root as mode="bundle|whole-file" reason="the two byte counts" — on a small file the old bundle was 5.65x the file itself; on a big file the bundle saves ~26x. The whole-file form is with the file CDATA-wrapped (redacted as usual) and every requested symbol's line anchor kept. An EXPLICIT --top-k=N (including 0) opts out of BOTH the exact-name default and mode= auto-selection and keeps the classic undecorated shape; a SYM:START-END slice opts out of mode= auto-selection only (serving the whole file would invert an explicit narrowing) but still gets the exact-name top-k=0 default when it applies. When the bundle wins AND no explicit --top-k was given, the requested bodies are served BEFORE the ranked map (not buried after it), and the map's escape hatch also rides the root as note="...--top-k=0 for the payload alone..." (not stderr-only): a caller reading only stdout sees both the answer and how to drop the map on the next call. Absent whenever the bundle carries no map at all (top-k=0, exact-name default) or loses to whole-file, and whenever --top-k was explicit — the classic shape above carries no note= either. +Selector grammar per item (the tail after the LAST ':' decides; a tail STARTING WITH A DIGIT is a range, anything else is a name): NAME every def of that name | FILE:NAME that file's def NAME:START-END body slice | FILE:NAME:START-END selector + slice FILE:LINE:NAME paste a row's p="path:line" straight from --callers/--lint/--grep (NOT --hotspots: its p= is a BARE path — build FILE:LINE:NAME from its own p=/top_l=/top= instead, since top= is just the worst function's name) path::scope::name the id composed from a row's p= sc= n= (the map, --for, --pack-task) Scope::name the sym= spelling edit-check and grep's in= rows print — matches the name under any scope whose ::-boundary SUFFIX is Scope (Box::lid, deep::Box::lid); a wrong scope refuses, it never falls back to the bare-name union Scope.name|Scope#name the same match, dotted (Python/JS/Java) or Ruby-style; tried only when no other spelling matched (START-END is 1-based within the def's OWN body — lines="lo-hi/total" marks the slice partial; out-of-range clamps. FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. Disclosed on the root as topk_default="0" (self-describing: the change is visible without reading source). The same default applies when --expand names SEVERAL definitions (an ambiguous bare name, or several tokens) and EVERY one is served: the bodies carry p= l= t= for each, the root adds map_next= (the shell call that prints the map you did not get — orient once per session, then look up lean). If a byte budget cut some, the root adds unserved_total=N (the true count) and unserved_next="--expand=FILE:LINE:NAME,…" (the call serving them; past 16 it serves the first 16 and unserved_listed="16" says so — a larger --pack-budget-bytes serves the rest) — never the generic map. map_next= and unserved_next= are quoted for the shell; map_next= names every root plus the crawl-scope flags (--exclude, --max-file-size, --no-ignore, --ignore-tests). --outline follows the same rule for its one token (several --outline tokens keep the ordinary map). An EXPLICIT --top-k=N (0 included) always overrides this default. Each body also carries sibs="a,b,..." sibs_total="N" [sibs_capped="1"] (the file's OTHER symbols, names only, capped at 40) and inc="x.h,..." inc_total="N" [inc_capped="1"] (the file's own #include/import targets, capped at 24) — both absent when the count is 0 (a documented zero, not a degrade), so a body no longer needs a second --outline call just to learn what else lives in its file. CHEAPEST-COMPLETE-ANSWER SERVING (no explicit --top-k, no range slice): the verb ALSO measures the (possibly map-less) bundle against the requested symbols' whole FILE(s) and emits the SMALLER, disclosed on the root as mode="bundle|whole-file" reason="the two byte counts" — on a small file the old bundle was 5.65x the file itself; on a big file the bundle saves ~26x. The whole-file form is with the file CDATA-wrapped (redacted as usual) and every requested symbol's line anchor kept. An EXPLICIT --top-k=N (including 0) opts out of BOTH the exact-name default and mode= auto-selection and keeps the classic undecorated shape; a SYM:START-END slice opts out of mode= auto-selection only (serving the whole file would invert an explicit narrowing) but still gets the exact-name top-k=0 default when it applies. When the bundle wins AND no explicit --top-k was given, the requested bodies are served BEFORE the ranked map (not buried after it), and the map's escape hatch also rides the root as note="...--top-k=0 for the payload alone..." (not stderr-only): a caller reading only stdout sees both the answer and how to drop the map on the next call. Absent whenever the bundle carries no map at all (top-k=0, exact-name default) or loses to whole-file, and whenever --top-k was explicit — the classic shape above carries no note= either. **Try it** @@ -4586,7 +4586,7 @@ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 ... [11 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--skipped`, `--in`, `--index-out` +**Shaped by:** `--expand`, `--skipped`, `--in`, `--index-out` ### `--map-diff` @@ -4729,6 +4729,8 @@ $ ./build/ripwire . --no-ignore --top-k=3 ``` +**Shaped by:** `--expand` + **Caveats (stated by the binary):** - by default they are skipped crawl paths the repository's own .gitignore covers. @@ -4763,7 +4765,7 @@ $ ./build/ripwire . --max-file-size=8K --top-k=3 ... [3 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--skipped` +**Shaped by:** `--expand`, `--skipped` **Caveats (stated by the binary):** diff --git a/skills/ripwire-navigate/SKILL.md b/skills/ripwire-navigate/SKILL.md index db5938e42..1bca667cb 100644 --- a/skills/ripwire-navigate/SKILL.md +++ b/skills/ripwire-navigate/SKILL.md @@ -175,7 +175,8 @@ When you need to understand a specific function/class/concept in full (its body, everything it calls — read the body with the callee signatures beside it. No ranked map rides along (orient ONCE per session with `ripwire `, then look symbols up lean); a bare name with several definitions serves them all, still map-free — `map_next=` on the root is the call that prints the map if you skipped - orienting, and `unserved_next=` names the definitions a byte budget cut. (No `` block + orienting, and `unserved_next=` is the call serving the definitions a byte budget cut (the first `unserved_listed=` of + them when that attribute is present; `unserved_total=` is the true count). (No `` block here; SYM's own doc-comment is in the CDATA body if it sits inside the definition — otherwise read the source lines just above `l=`.) About to Edit what you just expanded? If your edit tool needs a fresh native Read of the file first diff --git a/skills/ripwire-quality-bar/SKILL.md b/skills/ripwire-quality-bar/SKILL.md index 86aded233..f548e7ea6 100644 --- a/skills/ripwire-quality-bar/SKILL.md +++ b/skills/ripwire-quality-bar/SKILL.md @@ -162,9 +162,11 @@ something always fires.) preexisting-worse="0" new-symbol="0" gating="0" at="f0a45e43d"> ``` - **`origin="new-symbol"` rows are PRINTED; new-symbol rows never gate, except defect-shape format-arity** - (a format/argument mismatch is a defect, not debt, so it gates on new code too). They are the debt you are adding — read - them; nothing else will make you. And `--help` is explicit that **exit 0 means "nothing that already + **What exits 2** (the `gating="N"` rows, and nothing else): a finding that is major, unacked AND on code + that already existed (`preexisting-worse`), plus any `defect-shape` `format-arity` row on any origin — a + format/argument mismatch is a defect, not debt, so it gates on new code too. Minor-tier findings, acked + findings and every other `origin="new-symbol"` row are PRINTED but do not gate. The new-symbol rows are the + debt you are adding — read them; nothing else will make you. And `--help` is explicit that **exit 0 means "nothing that already existed got worse", NOT "clean"**: a change that is entirely new code can add unbounded new-symbol debt and still exit 0. Never report "quality-delta passed" as "no new debt" — open the rows. @@ -261,10 +263,9 @@ Thresholds/definitions are the catalog in [`quality-metrics.md`](quality-metrics | `placeholder` (an added stub or TODO: `todo!()`, `NotImplementedException`, a "not implemented" throw/panic, a TODO/FIXME naming no issue) | `--expand=SYM` | finish it, or name the issue that tracks it — never gates, but do not call the work done over it | | `defect-shape` (`defect=`: `format-arity` — a literal format whose `{}` fields do not match its arguments; `utf8-cut` — a byte cap plus an ellipsis with no UTF-8 back-off; `dedup-first` — `std::unique` keeping the first of a run over a type with a severity field; `vacuous-assert` — a test script's absence check read off a command whose failure is unchecked) | `--expand=SYM` | `format-arity` gates on any origin: match the fields to the arguments. The other three are `sev="minor"`: back off continuation bytes (or call the utf8 helper), sort worst-first before the unique, check the run's exit status before the absence | -These 12 kinds aren't a generic lint list — ten target a large-N-validated agent-code degradation mode (the -eleventh, `placeholder`, is an honesty check on your own "done"; the twelfth, `defect-shape`, is four defect -shapes code review kept finding by hand) -(verbosity, structural erosion, smell rate, contract drift; passing tests ≠ clean design). Numbers + why the +These 12 kinds aren't a generic lint list. Ten target an agent-code degradation mode validated at large N +(verbosity, structural erosion, smell rate, contract drift; passing tests ≠ clean design); `placeholder` is an +honesty check on your own "done"; `defect-shape` is four defect shapes code review kept finding by hand. Numbers + why the loop must be continuous, not a one-time prompt → [`quality-metrics.md`](quality-metrics.md). **Read the Fix column as DIRECTION, not a computed answer.** None of these 12 kinds has a corpus-derivable @@ -313,7 +314,7 @@ ripwire --affected=F1,F2 --legend=compact # 4. which tests PROVE it? one: the row you were chasing should be gone, **and** nothing new should have appeared. A "split the function" fix that drops `complexity` while adding `api-surface` + `duplication` is a lateral move. Read `gating=`, but also read the `origin="new-symbol"` rows — extraction *always* creates new symbols, and - new-symbol rows never gate, except defect-shape format-arity, so exit 0 is not the same as "the fix was free." + those rows do not gate (see **What exits 2** above), so exit 0 is not the same as "the fix was free." 3. **`--edit-check=SYM`** — `unchanged` / `new-symbol` / `contract-change` for the symbol you just edited: param count and publicness NOW vs `git HEAD`, plus its 1-hop callers with any call site provably incompatible with the new arity flagged. A refactor is *supposed* to be `unchanged` here; a @@ -372,10 +373,9 @@ module", "fix the worst of it"), run this instead — bounded, one finding at a ## Wire it into CI / pre-commit -`--quality-delta` exits 2 **only when a finding is major AND unacked AND preexisting-worse** (or a defect-shape -format-arity row, on any origin) — the `gating="N"` header count. Minor-tier and acked findings report but never -gate, and new-symbol rows never gate, except defect-shape format-arity, -so **a green hook does not mean the diff added no debt** — it means nothing that already existed got worse. +`--quality-delta` exits 2 on exactly the **What exits 2** rows above — the `gating="N"` header count — so +**a green hook does not mean the diff added no debt**: it means nothing that already existed got worse (and no +format/argument mismatch was added). Non-zero is the hook contract, no wrapper needed: `ripwire --quality-delta --legend=compact || exit 1`. If you want CI to also block on the debt a change ADDS, exit 2 will not do it for you — parse `new-symbol="N"` from the header (`--json` is supported for this verb) and apply your own policy. Chain the diff --git a/skills/ripwire-quality-bar/quality-metrics.md b/skills/ripwire-quality-bar/quality-metrics.md index 58d7795b3..3b39579ef 100644 --- a/skills/ripwire-quality-bar/quality-metrics.md +++ b/skills/ripwire-quality-bar/quality-metrics.md @@ -68,11 +68,11 @@ degrades on (large-N studies): - **Contract drift**: unplanned API-surface growth is a documented failure mode distinct from complexity — classic complexity metrics lost predictive power for real agent-maintainability failures once controlled for size; contract drift and code growth are what remained predictive (arXiv:2606.21804). -- **Placeholders** (the eleventh kind, not a literature-measured mode): a stub or TODO the change added. +- **Placeholders** (`placeholder`; not a literature-measured mode): a stub or TODO the change added. It never gates; it exists so a change is not called done over a `todo!()` or a `NotImplementedException`. -- **Defect shapes** (the twelfth kind, `defect-shape`, measured on this project's own review findings, not - the literature): a literal format whose fields do not match its arguments (`format-arity` — gates on any - origin: new-symbol rows never gate, except defect-shape format-arity), a byte cap plus an ellipsis with no +- **Defect shapes** (`defect-shape`; measured on this project's own review findings, not the literature): a + literal format whose fields do not match its arguments (`format-arity` — the one row that gates on new code + too), a byte cap plus an ellipsis with no UTF-8 back-off (`utf8-cut`), a keep-first dedup over a severity field (`dedup-first`), and a test script's absence assertion that passes on a crash (`vacuous-assert`). The last three are `sev="minor"`. - **Pass-rate ≠ design quality**: fewer than half of test-passing agent patches satisfy design constraints diff --git a/src/cli.h b/src/cli.h index 02cf5059b..38705d688 100644 --- a/src/cli.h +++ b/src/cli.h @@ -1353,9 +1353,13 @@ inline constexpr char kHelpHead[] = " default applies when --expand names SEVERAL definitions (an ambiguous bare name, or several\n" " tokens) and EVERY one is served: the bodies carry p= l= t= for each, the root adds map_next=\n" " (the shell call that prints the map you did not get — orient once per session, then look up\n" - " lean). If a byte budget cut some, the root adds unserved_total=N and unserved_next=\"--expand=\n" - " FILE:LINE:NAME,…\" (the call serving them) — never the generic map. --outline follows the same\n" - " rule for its one token. An EXPLICIT --top-k=N (0 included) always overrides this default.\n" + " lean). If a byte budget cut some, the root adds unserved_total=N (the true count) and\n" + " unserved_next=\"--expand=FILE:LINE:NAME,…\" (the call serving them; past 16 it serves the first\n" + " 16 and unserved_listed=\"16\" says so — a larger --pack-budget-bytes serves the rest) — never the\n" + " generic map. map_next= and unserved_next= are quoted for the shell; map_next= names every root\n" + " plus the crawl-scope flags (--exclude, --max-file-size, --no-ignore, --ignore-tests). --outline\n" + " follows the same rule for its one token (several --outline tokens keep the ordinary map). An\n" + " EXPLICIT --top-k=N (0 included) always overrides this default.\n" " Each body also carries sibs=\"a,b,...\" sibs_total=\"N\" [sibs_capped=\"1\"] (the file's OTHER\n" " symbols, names only, capped at 40) and inc=\"x.h,...\" inc_total=\"N\" [inc_capped=\"1\"] (the\n" " file's own #include/import targets, capped at 24) — both absent when the count is 0 (a\n" diff --git a/src/compactlegend.h b/src/compactlegend.h index 277b196d7..2c5af1e85 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -1190,7 +1190,7 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "topk_default", "topk_default=0: an exact name was expanded, so the ranked map was dropped; pass top-k=N to get it back", false, "ctx", MapHeaderRead::No, {}, "expand" }, // expand-lean-k64: a multi-definition name whose definitions were ALL served drops the map too (map_next=), and a cut one names what it did not serve { "map_next", "map_next=: the shell call printing the map this answer omits", false, "ctx", MapHeaderRead::No, {}, "expand" }, - { "unserved_total", "unserved_total=N: N requested definitions have no body here (budget or unreadable); unserved_next= is the call serving them", false, "ctx", MapHeaderRead::No, {}, "expand" }, // also defines unserved_next= + { "unserved_total", "unserved_total=N: N requested definitions have no body here (budget or unreadable); unserved_next= serves the first unserved_listed= of them (all when absent); a larger pack-budget-bytes serves the rest", false, "ctx", MapHeaderRead::No, {}, "expand" }, // also defines unserved_next= unserved_listed= { "inc_total", "inc_total=N: the file's true include/import count; inc= lists the first 24, inc_capped=1 when cut", true, "b", MapHeaderRead::No, {}, "expand" }, // expand (whole-file serving): src/main.cpp chooseExpandServe (fileOpen) + the topk_default insert { "mode", "mode=/reason=: whole-file won, the file text cost fewer bytes than the bundle; reason= gives both sizes", false, "ctx", MapHeaderRead::No, {}, "expand-file" }, // also defines reason= @@ -1242,7 +1242,7 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = // pack-signatures (--outline whose definitions were all served): src/main.cpp (ctxLeanAttr / ctxUnservedAttr) { "topk_default", "topk_default=0: the outlines asked for were all served, so the ranked map was dropped; pass top-k=N to get it back", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, { "map_next", "map_next=: the shell call printing the map this answer omits", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, - { "unserved_total", "unserved_total=N: N requested definitions have no outline here (budget or unreadable); unserved_next= is the call serving them", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, // also defines unserved_next= + { "unserved_total", "unserved_total=N: N requested definitions have no outline here (budget or unreadable); unserved_next= serves the first unserved_listed= of them (all when absent); a larger pack-budget-bytes serves the rest", false, "ctx", MapHeaderRead::No, {}, "pack-signatures" }, // also defines unserved_next= unserved_listed= // pack-signatures: src/serialize.h ( from src/arch.h builtinLayer) { "layer", "layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none", true, "f", MapHeaderRead::No, {}, "pack-signatures" }, // pack-top-n: src/serialize.h ( from src/arch.h builtinLayer) diff --git a/src/main.cpp b/src/main.cpp index 021215012..d70d11702 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1205,6 +1205,28 @@ inline std::string churnDecayWindowLabel( std::string_view minedSpan ) return label; } +// The crawl-scope flags a pasted continuation must replay so it indexes the SAME files this run did: --exclude, +// --max-file-size, --no-ignore, --ignore-tests (the enumeration below says why these four and no others). Each is +// returned with its leading space; empty at the defaults. Shared by scopedMapNextInvocation and leanMapPointer. +inline std::string crawlScopeFlags( const rw::Config& cfg ) +{ + std::string inv; + for( const std::string& x : cfg.excludes ) + { + inv += " " + rw::nextFlag( "--exclude=", x ); + } + // The ceiling is replayed as the BYTE COUNT it resolved to, not as the caller's "2K": parseByteSize accepts + // plain digits, and the number is what shaped the crawl. Omitted at the default, so the common hint is + // unchanged to the byte. + if( cfg.maxFileBytes != rw::kDefaultMaxFileBytes ) + { + inv += " --max-file-size=" + std::to_string( cfg.maxFileBytes ); + } + if( cfg.noIgnore ) { inv += " --no-ignore"; } + if( cfg.ignoreTests ) { inv += " --ignore-tests"; } + return inv; +} + // C1-b (2026-09-12): --in=DIR's page of rows, from the SAME mining pass as the global block. The prefix is // matched on the ROOT-RELATIVE spelling the map prints (serialize.h pathRel: rootRelativeUri against the crawl root as // typed), so a scoped row is byte-identical to its global twin — a sub-root-relative spelling missed every held-out gold @@ -1268,19 +1290,7 @@ inline std::string scopedMapNextInvocation( const rw::Config& cfg, std::string_v { inv += " " + rw::nextFlag( "--since=", cfg.since ); } - for( const std::string& x : cfg.excludes ) - { - inv += " " + rw::nextFlag( "--exclude=", x ); - } - // The ceiling is replayed as the BYTE COUNT it resolved to, not as the caller's "2K": parseByteSize accepts - // plain digits, and the number is what shaped the crawl. Omitted at the default, so the common hint is - // unchanged to the byte. - if( cfg.maxFileBytes != rw::kDefaultMaxFileBytes ) - { - inv += " --max-file-size=" + std::to_string( cfg.maxFileBytes ); - } - if( cfg.noIgnore ) { inv += " --no-ignore"; } - if( cfg.ignoreTests ) { inv += " --ignore-tests"; } + inv += crawlScopeFlags( cfg ); if( withIn ) { inv += " " + rw::nextFlag( "--in=", scopeDir ); @@ -1531,33 +1541,35 @@ inline constexpr const char* kMapRidesAlongFmt = // root says so (topk_default="0", the exact-name default's own disclosure) and carries ONE recoverable pointer to the map // it did not ship (map_next=, a shell line: the map is the default verb of `ripwire ROOT`). The bodies' headers already // carry p= l= t= for every definition. When some were NOT served (cut by the byte budget, unreadable), the root names -// exactly those — unserved_total= and unserved_next= (the --expand/--outline call serving them) — never the generic map. -// Language-neutral: the decision reads the served-id record, not any language's syntax. An EXPLICIT --top-k keeps today's -// shape. kMaxUnservedSelectors is a blow-up guard (the kMaxExpandSibs precedent), not a target: unserved_total= is the true count. +// exactly those — unserved_total= (the true count) and unserved_next= (the --expand/--outline call serving them, quoted by +// the next= house rule) — never the generic map. kMaxUnservedSelectors is a blow-up guard (the kMaxExpandSibs precedent), +// not a target: past it unserved_next= serves the FIRST kMaxUnservedSelectors and unserved_listed= says how many it lists; +// the rest are recovered by the same call with a larger --pack-budget-bytes (the legend says so). Language-neutral: the +// decision reads the served-id record, not any language's syntax. An EXPLICIT --top-k keeps today's shape. inline constexpr std::size_t kMaxUnservedSelectors = 16; -inline std::string leanMapPointer( std::string_view rootArg ) +// `ripwire ROOT…`: EVERY root this call crawled (a multi-root call's map is the map of all of them — an empty mapRootArg +// there used to fall back to ".", the parent tree), each quoted by the next= house rule, plus the crawl-scope flags that +// decided which files were indexed, so the pasted call maps the same corpus. Presentation and payload flags stay out. +inline std::string leanMapPointer( const rw::Config& cfg ) { - std::string root( rootArg.empty() ? std::string_view( "." ) : rootArg ); - const bool plain = std::all_of( root.begin(), root.end(), []( char c ) - { return std::isalnum( static_cast( c ) ) != 0 || c == '.' || c == '/' || c == '_' || c == '-' || c == '+' || c == '@' || c == '~' || c == ':' || c == ','; } ); - if( !plain ) + std::string inv = "ripwire"; + for( const std::string_view root : cfg.roots ) { - std::string q = "'"; - for( char c : root ) - { - q += c == '\'' ? std::string( "'\\''" ) : std::string( 1, c ); - } - root = q + "'"; + inv += " " + rw::nextFlag( "", root ); + } + if( cfg.roots.empty() ) + { + inv += " ."; } - return "ripwire " + root; + return inv + crawlScopeFlags( cfg ); } struct UnservedDefs { std::size_t requested = 0; // valid, non-module-scope requests std::size_t unserved = 0; // of those, the ones with no body/outline in the answer - std::string attrs; // ` unserved_total="N" unserved_next="--expand=P:L:N,…"` — empty when unserved == 0 + std::string attrs; // ` unserved_total="N" [unserved_listed="K"] unserved_next="--expand=P:L:N,…"` — empty when unserved == 0 }; inline UnservedDefs unservedDefsOf( const rw::IngestResult& ing, const std::vector& requested, std::vector served, @@ -1592,7 +1604,10 @@ inline UnservedDefs unservedDefsOf( const rw::IngestResult& ing, const std::vect } if( u.unserved > 0 ) { - u.attrs = " unserved_total=\"" + std::to_string( u.unserved ) + "\"" + rw::nextAttrXml( std::string( verbFlag ) + "=" + selectors, "unserved_next" ); + // unserved_listed= only when the guard cut the list: unserved_next= then serves the first `listed`, not all of them. + u.attrs = " unserved_total=\"" + std::to_string( u.unserved ) + "\"" + + ( listed < u.unserved ? " unserved_listed=\"" + std::to_string( listed ) + "\"" : std::string() ) + + rw::nextAttrXml( rw::nextFlag( std::string( verbFlag ) + "=", selectors ), "unserved_next" ); } return u; } @@ -2444,7 +2459,7 @@ int runDefaultMap( const MainDispatch& d ) if( un.requested > 0 ) { mapTopK = 0; - ctxLeanAttr = " topk_default=\"0\"" + nextAttrXml( leanMapPointer( mapRootArg ), "map_next" ); + ctxLeanAttr = " topk_default=\"0\"" + nextAttrXml( leanMapPointer( cfg ), "map_next" ); ctxUnservedAttr = un.attrs; } } diff --git a/test/expandtopk0check.sh b/test/expandtopk0check.sh index 6e00af01a..70216185e 100755 --- a/test/expandtopk0check.sh +++ b/test/expandtopk0check.sh @@ -323,9 +323,103 @@ else ok "(L) the single-definition exact-name answer is unchanged (topk_default only, no map_next=)" fi +# ── (M) fix round 1: map_next= replays EVERY root and the crawl-scope flags, shell-quoted (nextFlag) ───────────────── +# A multi-root call's map is the map of all its roots; the pointer used to fall back to "ripwire ." (the parent tree), and +# it dropped --ignore-tests/--exclude, so pasting it printed a map of a different corpus. Exact pointers, then pasted. +xmlUnesc(){ sed -e 's/'/'"'"'/g' -e 's/"/"/g' -e 's/<//g' -e 's/&/\&/g'; } +attrOf(){ grep -oE "$1=\"[^\"]*\"" "$2" | head -1 | sed "s/^$1=\"//;s/\"\$//"; } +MR="$TMP/mr"; mkdir -p "$MR/r1" "$MR/r2" +printf 'int multiWork(void) { return 1; }\n' >"$MR/r1/w.c" +printf 'int multiWork(void) { return 2; }\n' >"$MR/r2/w.c" +( cd "$MR" && "$BIN" r1 r2 --expand=multiWork --no-cache ) >"$TMP/mroot.xml" 2>/dev/null +mrNext="$( attrOf map_next "$TMP/mroot.xml" )" +[ "$mrNext" = "ripwire r1 r2" ] \ + && ok "(M) multi-root: map_next names BOTH roots (\"$mrNext\")" \ + || no "(M) multi-root: map_next='$mrNext', want 'ripwire r1 r2': $( grep -oE ']*>' "$TMP/mroot.xml" | head -1 )" +grep -q 'return 1' "$TMP/mroot.xml" && grep -q 'return 2' "$TMP/mroot.xml" \ + && ok "(M) multi-root: both definitions are served (premise of the lean answer)" \ + || no "(M) multi-root: the two definitions are not both served" +( cd "$MR" && "$BIN" r1 r2 --no-cache --top-k=3 ) >"$TMP/mroot_map.xml" 2>/dev/null +( cd "$MR" && eval "\"\$BIN\" ${mrNext#ripwire } --no-cache --top-k=3" ) >"$TMP/mroot_paste.xml" 2>/dev/null +grep -q '"$SC/fix/tests/test_dup.c" +( cd "$SC" && "$BIN" fix --expand=dupTarget --ignore-tests '--exclude=nomatch*' --no-cache ) >"$TMP/scope.xml" 2>/dev/null +scNext="$( attrOf map_next "$TMP/scope.xml" )" +[ "$scNext" = "ripwire fix --exclude='nomatch*' --ignore-tests" ] \ + && ok "(M) crawl scope: map_next replays --exclude (quoted) and --ignore-tests" \ + || no "(M) crawl scope: map_next='$scNext', want the root plus --exclude='nomatch*' --ignore-tests" +scShell="$( printf '%s' "$scNext" | xmlUnesc )" +( cd "$SC" && eval "\"\$BIN\" ${scShell#ripwire } --no-cache --top-k=50" ) >"$TMP/scope_paste.xml" 2>/dev/null +( cd "$SC" && "$BIN" fix --ignore-tests '--exclude=nomatch*' --no-cache --top-k=50 ) >"$TMP/scope_map.xml" 2>/dev/null +( cd "$SC" && "$BIN" fix --no-cache --top-k=50 ) >"$TMP/scope_wide.xml" 2>/dev/null +cmp -s "$TMP/scope_map.xml" "$TMP/scope_wide.xml" \ + && no "(M) crawl scope premise: --ignore-tests does not change this tree's map — the paste arm would prove nothing" \ + || ok "(M) crawl scope premise: --ignore-tests changes this tree's map" +grep -q '"$TMP/plainptr.xml" 2>/dev/null +[ "$( attrOf map_next "$TMP/plainptr.xml" )" = "ripwire test/expandtopk0fix" ] \ + && ok "(M) negative: with no crawl-scope flag map_next is just the root" \ + || no "(M) negative: plain map_next='$( attrOf map_next "$TMP/plainptr.xml" )', want 'ripwire test/expandtopk0fix'" + +# ── (N) fix round 1: unserved_next= is shell-quoted, and past 16 cut definitions it says how many it lists ────────── +# Each file under "sub dir/" holds a 62-line (~2.8 KB) `def work`; a 3500 B pack budget serves one. With 4 files the 3 cut ones are +# all listed (no unserved_listed=); with 20 files the 19 cut ones exceed the 16-selector guard: unserved_listed="16", the +# paste (default budget: 16 bodies fit) serves exactly those 16, and the same call with a larger budget serves all 20 (the +# legend's "the rest"). Served = rows in bundle mode or blocks in whole-file mode, whichever the call picks. +rootOf(){ grep -oE ']*>' "$1" | head -1; } +mkwork(){ local d="$1" n="$2" i j; mkdir -p "$d/sub dir" + for i in $( seq -w 1 "$n" ); do + { echo "def work():"; for j in $( seq 1 61 ); do echo " value_$j = 'padding to about forty-five bytes $j'"; done; } >"$d/sub dir/m$i.py" + done; } +selectorsOf(){ printf '%s' "$1" | xmlUnesc | sed -e "s/^--expand=//" -e "s/^'//" -e "s/'\$//" | tr ',' '\n' | sort; } +servedOf(){ grep -oE '"$TMP/sp4.xml" 2>/dev/null +un4="$( attrOf unserved_next "$TMP/sp4.xml" )" +grep -q 'unserved_total="3"' "$TMP/sp4.xml" && case "$un4" in "--expand='sub dir/"*) true ;; *) false ;; esac \ + && ok "(N) space path: unserved_total=\"3\" and unserved_next is single-quoted for the shell" \ + || no "(N) space path: $( grep -oE ']*>' "$TMP/sp4.xml" | head -1 )" +rootOf "$TMP/sp4.xml" | grep -q 'unserved_listed=' \ + && no "(N) negative: unserved_listed= appeared though all 3 cut definitions are listed" \ + || ok "(N) negative: no unserved_listed= when every cut definition is listed" +( cd "$SP4" && eval "\"\$BIN\" . --no-cache $( printf '%s' "$un4" | xmlUnesc )" ) >"$TMP/sp4_paste.xml" 2>/dev/null +[ "$( selectorsOf "$un4" | wc -l | tr -d ' ' )" = 3 ] && [ "$( servedAnyOf "$TMP/sp4_paste.xml" )" = "$( selectorsOf "$un4" )" ] \ + && ok "(N) space path: the pasted unserved_next serves exactly the 3 cut definitions" \ + || no "(N) space path: pasted unserved_next served [$( servedAnyOf "$TMP/sp4_paste.xml" | tr '\n' ' ' )], listed [$( selectorsOf "$un4" | tr '\n' ' ' )]" +SP20="$TMP/sp20"; mkwork "$SP20" 20 +( cd "$SP20" && "$BIN" . --expand=work --pack-budget-bytes=3500 --no-cache ) >"$TMP/sp20.xml" 2>/dev/null +tot20="$( attrOf unserved_total "$TMP/sp20.xml" )"; un20="$( attrOf unserved_next "$TMP/sp20.xml" )" +served20="$( servedOf "$TMP/sp20.xml" | wc -l | tr -d ' ' )" +[ -n "$tot20" ] && [ "$tot20" -gt 16 ] && [ "$(( tot20 + served20 ))" = 20 ] && rootOf "$TMP/sp20.xml" | grep -q 'unserved_listed="16"' \ + && ok "(N) >16 cut: unserved_total=\"$tot20\" (+$served20 served = 20) and unserved_listed=\"16\"" \ + || no "(N) >16 cut: total='$tot20' served=$served20: $( grep -oE ']*>' "$TMP/sp20.xml" | head -1 | cut -c1-300 )" +( cd "$SP20" && eval "\"\$BIN\" . --no-cache $( printf '%s' "$un20" | xmlUnesc )" ) >"$TMP/sp20_paste.xml" 2>/dev/null +[ "$( selectorsOf "$un20" | wc -l | tr -d ' ' )" = 16 ] && [ "$( servedAnyOf "$TMP/sp20_paste.xml" )" = "$( selectorsOf "$un20" )" ] \ + && rootOf "$TMP/sp20_paste.xml" | grep -q 'schema=' && ! rootOf "$TMP/sp20_paste.xml" | grep -q 'unserved_' \ + && ok "(N) >16 cut: the pasted unserved_next serves exactly the 16 it lists, and nothing is cut in that answer" \ + || no "(N) >16 cut: pasted unserved_next served $( servedAnyOf "$TMP/sp20_paste.xml" | wc -l | tr -d ' ' ): $( rootOf "$TMP/sp20_paste.xml" | cut -c1-200 )" +( cd "$SP20" && "$BIN" . --expand=work --pack-budget-bytes=1000000 --no-cache ) >"$TMP/sp20_all.xml" 2>/dev/null # (deprecated knob, still the one that cut) +[ "$( servedAnyOf "$TMP/sp20_all.xml" | wc -l | tr -d ' ' )" = 20 ] && rootOf "$TMP/sp20_all.xml" | grep -q 'schema=' && ! rootOf "$TMP/sp20_all.xml" | grep -q 'unserved_' \ + && [ -z "$( comm -23 <( servedOf "$TMP/sp20_paste.xml" ) <( servedAnyOf "$TMP/sp20_all.xml" ) )" ] \ + && ok "(N) >16 cut: the rest are recoverable — a larger --pack-budget-bytes serves all 20, no unserved_*" \ + || no "(N) >16 cut: the larger-budget call did not serve all 20: $( grep -oE ']*>' "$TMP/sp20_all.xml" | head -1 | cut -c1-200 )" + +# ── (O) fix round 1 (review N1): several --outline tokens keep the ordinary map (the lean rule is for ONE token) ───── +"$BIN" "$FIX" --outline=uniqueTarget,dupTarget --no-cache >"$TMP/ol2.xml" 2>/dev/null +grep -q ']*>' "$TMP/ol2.xml" | head -1 )" + # ── (F) well-formedness + determinism ───────────────────────────────────────────────────────────────── if command -v xmllint >/dev/null 2>&1; then - for f in uniq dup5 dup tk5 tk0 big real_default real_tk0 mdef cut ol olcut emit; do + for f in uniq dup5 dup tk5 tk0 big real_default real_tk0 mdef cut ol olcut emit mroot scope sp4 sp20 sp20_paste ol2; do if xmllint --noout "$TMP/$f.xml" 2>/dev/null; then ok "(F) $f.xml well-formed"; else no "(F) $f.xml fails xmllint"; fi done else diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index 310dec377..ce21fbdaf 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -38,5 +38,5 @@ safe_delete 0 d010f9ca7e45d5bedcde9be49875bbc705632b3011cb9c8c4185f47957d51504 e verify_layer 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5288c345d7d6e335f88b9c1daa8935db22e1dcf89c0c8bc1f6140d4cb5af0b48 graph_query 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 bfa4319feb9dee09cfbd3991cf6fbd752297e99d14de9e75768820d2a9c8832f callers_limit 0 e40ee1ddba0fbf548c58c98b91fe052af8c34f8913edb3edd85245b12a6967b9 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help_all 0 9d9f6074f8c59b4c69306d9bb7ddb34ad690d1d6d6f40dd8ff5c233bdda0e7d2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help_all 0 2a1699d5c2b63a3c8010b2a723cecbbf7fcdf5167edcf63aab29e05921c7ec1f e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help_one 0 938ff4f8802a8f5d9cb10f9a1f3804ac755be44c8ab00e24e45bfe8255224a21 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 From 76da4bde953d45971cee204dd934d7dd5933fa29 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 08:21:12 -0400 Subject: [PATCH 109/170] test(mapinventorycheck): a Swift method main is no entry claim Swift has no main convention the index reads (its entry is @main or main.swift). This arm proves the no-claim default for such a language: the mutant that drops it goes red here. Co-Authored-By: Claude Opus 5.5 --- test/mapinventorycheck.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/mapinventorycheck.sh b/test/mapinventorycheck.sh index 930d54733..0f627ed44 100755 --- a/test/mapinventorycheck.sh +++ b/test/mapinventorycheck.sh @@ -184,9 +184,9 @@ JNN="$( python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["inv"].g if [ -n "$JEN" ] && [ "$JEN" = "$ENEXT" ] && [ -n "$JNN" ] && [ "$JNN" = "$NNEXT" ]; then ok "(P3) JSON entries_next / names_next = XML"; else fail "(P3) JSON next: entries [$JEN] vs [$ENEXT], names [$JNN] vs [$NNEXT]"; fi # (Q) an entry is claimed only by its language's OWN convention (fix round 1: a method or an uncalled script function -# named main was labelled a program entry). Nine near-misses, each must NOT be an entry: a Python / JS class method +# named main was labelled a program entry). Ten near-misses, each must NOT be an entry: a Python / JS class method # main, a Ruby singleton method main (each uncalled, then each called from module scope), a Python main with no -# __main__ guard and no caller, an uncalled exported TS main, a Bash main defined and never invoked. Positive controls in the same run: a Bash script that runs +# __main__ guard and no caller, an uncalled exported TS main, a Bash main defined and never invoked, a Swift method main. Positive controls in the same run: a Bash script that runs # `main "$@"`, a Python main under the __main__ guard, a C main. Q="$TMP/langs"; mkdir -p "$Q/lib" "$Q/web" "$Q/bin" "$Q/pkg" printf 'class Runner:\n def main(self):\n return 2\n' >"$Q/lib/runner.py" @@ -200,13 +200,15 @@ printf 'main() { echo hi; }\n' >"$Q/lib/lib.sh" printf 'class Runner:\n def main(self):\n return 2\n\nRunner().main()\n' >"$Q/lib/runner_called.py" printf 'class Job { main() { return 3 } }\nnew Job().main()\n' >"$Q/web/job_called.js" printf 'class Tool\n def self.main\n 1\n end\nend\nTool.main\n' >"$Q/lib/tool_called.rb" +# a language with no main convention the index can read (Swift's entry is @main / main.swift): a method main is no claim +printf 'class App {\n func main() {}\n}\n' >"$Q/lib/App.swift" printf '#!/bin/sh\nwork() { echo w; }\nmain() { work; }\nmain "$@"\n' >"$Q/bin/run.sh" printf 'def go():\n return 1\n\ndef main():\n return go()\n\nif __name__ == "__main__":\n main()\n' >"$Q/bin/tool.py" printf 'int step( void ) { return 0; }\nint main( void ) { return step(); }\n' >"$Q/prog.c" i=0; while [ "$i" -lt 30 ]; do printf 'def g%d():\n return %d\n' "$i" "$i" >"$Q/pkg/m$i.py"; i=$(( i + 1 )); done "$BIN" "$Q" --no-cache --top-k=5 >"$TMP/langs.out" 2>/dev/null QE="$( grep -o ' Date: Fri, 9 Oct 2026 08:27:50 -0400 Subject: [PATCH 110/170] for: the spent-ceiling marker counts the candidate head, not owner rows (ownerhopcheck B2); re-anchor overbudgetcommentcheck B6 CI on 7aef0eed: ownerhopcheck (B2) requires a spent-ceiling answer to be byte-identical with and without the owner-hop plan; the marker's total= counted owner rows. It now counts the candidate head (the legend says so). overbudgetcommentcheck (B6) lost its premise: the first --detail body at 800 tokens now ships whole under its floor. The whole-lines property is asserted at 300 tokens, where the floor cuts it, and the 800 run is kept as a twin asserting the body is served whole. ENSURES on the sig-claim helper. Co-Authored-By: Claude Opus 5.5 --- src/verbs_for.h | 31 +++++++++++++++++-------------- test/estchargecheck.sh | 2 +- test/overbudgetcommentcheck.sh | 28 ++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/verbs_for.h b/src/verbs_for.h index 45ed05613..45d944bed 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -1908,8 +1908,11 @@ std::size_t forSigClaimWithTrailingReserve( std::size_t sigSideCeiling, std::siz { return sigSideCeiling; } - const std::size_t room = bundleBudget > reserveBytes ? bundleBudget - reserveBytes : 0; - return std::min( sigSideCeiling, room ); + const std::size_t room = bundleBudget > reserveBytes ? bundleBudget - reserveBytes : 0; + const std::size_t claim = std::min( sigSideCeiling, room ); + ENSURES( claim <= sigSideCeiling && claim + reserveBytes <= std::max( bundleBudget, reserveBytes ), + "forSigClaimWithTrailingReserve: the sig claim grew past its ceiling or left the reserve no room" ); + return claim; } // The FIRST-BODY FLOOR's byte budget: the first --detail body served whole when it is at most a quarter of the @@ -2451,33 +2454,33 @@ inline std::vector forDetailIds( const rw::IngestResult& ing, const // fix round 4 (rule 5, review rv-wrap-recipe-k29 F1): the compact section an exhausted explicit ceiling // leaves out, as a counted marker instead of nothing — ``. -// total= counts the rows the section would have been asked to serve (the owner rows + the candidate head, ids the -// ingest knows) — the requested count packHops itself would print; how many of them have proven edges (noedge=) is -// not known without the render, so the marker does not claim it. next= is the same question without --token-budget, -// the answer whose own compact allowance serves the hop rows. No rows ⇒ no marker (nothing was cut). Markup bytes, -// charged through buildForEnrichment's markupBytes like the rendered section. +// total= counts the CANDIDATE head the section covers (ids the ingest knows) — not the owner rows: at a spent ceiling the +// answer must stay byte-identical with and without the owner-hop plan (ownerhopcheck (B2), owner-hop's signed contract: +// "owner rows add nothing"), and the next= answer serves those owner rows too. How many candidates have proven edges +// (noedge=) is not known without the render, so the marker does not claim it. next= is the same question without +// --token-budget, whose own compact allowance serves the hop rows. No candidates => no marker (nothing was cut). Markup +// bytes, charged through buildForEnrichment's markupBytes like the rendered section. inline constexpr std::string_view kForHopsBudgetCutLegend = - "; hops shown=0 capped=1: the call-hop rows (one hop of callees per top row) this budget left out, total= of them; next= serves them"; + "; hops shown=0 capped=1: this budget left out the call-hop rows (one hop of callees for each of total= top rows); next= serves them"; // the hops cut's continuation (` next="--for=TASK"`) — one spelling for the marker, the partial-cut splice and the reserve inline std::string forHopsCutNextAttr( const rw::Config& cfg ) { return rw::nextAttrXml( rw::nextFlag( "--for=", cfg.forTask ) ); } // the bytes an explicit ceiling reserves out of the sig claim so the compact hops' cut disclosure always fits: the widest -// marker (``, total <= kPackTaskBodyCandidates + owners, two digits) -// plus its present-only legend clause — the kCompactAttrReserve / kForFileTailShellReserve pattern. +// marker (``, total <= kPackTaskBodyCandidates, two digits) plus its +// present-only legend clause — the kCompactAttrReserve / kForFileTailShellReserve pattern. inline std::size_t forHopsCutReserve( const rw::Config& cfg ) { constexpr std::size_t kMarkerShell = std::string_view( "" ).size(); return kMarkerShell + forHopsCutNextAttr( cfg ).size() + kForHopsBudgetCutLegend.size(); } -rw::ChargedSection forHopsBudgetCutMarker( const rw::Config& cfg, const rw::IngestResult& ing, const ForOwnerPlan& ownerPlan, - const std::vector& hopIds ) +rw::ChargedSection forHopsBudgetCutMarker( const rw::Config& cfg, const rw::IngestResult& ing, const std::vector& hopIds ) { rw::ChargedSection marker; std::size_t total = 0; - for( const rw::NodeId id : forOwnerHopNodes( ownerPlan, hopIds ) ) + for( const rw::NodeId id : hopIds ) { total += id < ing.symbols.size() ? 1u : 0u; } @@ -2554,7 +2557,7 @@ ForAutoBodiesResult buildForCompactHops( const rw::Config& cfg, const rw::Ingest // fix round 4 (rule 5, review rv-wrap-recipe-k29 F1): the hop rows this budget left out are NAMED, not dropped // silently — an empty carrying the count and the call that serves them (the same question without the // budget, whose compact allowance is its own). The bytes are this section's and the ladder prices them. - out.section = forHopsBudgetCutMarker( cfg, ing, ownerPlan, hopIds ); + out.section = forHopsBudgetCutMarker( cfg, ing, hopIds ); return out; } diff --git a/test/estchargecheck.sh b/test/estchargecheck.sh index 252ef7da6..54f31f89d 100755 --- a/test/estchargecheck.sh +++ b/test/estchargecheck.sh @@ -780,7 +780,7 @@ a7r_out >"$A7R/hd.xml" hd_tag="$( grep -aoE ']*>' "$A7R/hd.xml" | head -1 )" hd_shown="$( printf '%s' "$hd_tag" | sed -nE 's/" ] && [ "${h_tot:-0}" -ge 1 ] 2>/dev/null \ - && grep -aqF 'hops shown=0 capped=1: the call-hop rows' "$A7R/h.xml" && [ "${h_root#* over_ceiling=}" = "$h_root" ] \ + && grep -aqF 'hops shown=0 capped=1: this budget left out the call-hop rows' "$A7R/h.xml" && [ "${h_root#* over_ceiling=}" = "$h_root" ] \ && [ "${hd_shown:-0}" -ge 1 ] 2>/dev/null && [ "$hd_tot" = "$h_tot" ]; } \ && ok "#11 A7D hops: the spent-ceiling is the counted marker ($h_tag), defined, inside budget; its next= serves $hd_shown of $hd_tot rows" \ || no "#11 A7D hops: spent-ceiling not the counted, continued marker (got '${h_tag:-none}', next= answer '${hd_tag:-none}', root '${h_root:0:80}…')" diff --git a/test/overbudgetcommentcheck.sh b/test/overbudgetcommentcheck.sh index d4f71b660..4cf0ed849 100755 --- a/test/overbudgetcommentcheck.sh +++ b/test/overbudgetcommentcheck.sh @@ -244,7 +244,13 @@ FMX="$TMP/fmx"; mkdir -p "$FMX/src" for i in $( seq -w 1 30 ); do printf '// Process one entry: trim the raw input and normalise it for lane %s.\nexport function processEntry%s( input: string ): string {\n const trimmed = input.trim();\n if( trimmed.length === 0 ) {\n return "";\n }\n return trimmed.toLowerCase();\n}\n' "$i" "$i" > "$FMX/src/entry$i.ts" done -"$BIN" "$FMX" --no-cache --for="processEntry input trim" --detail=30 --token-budget=800 --legend=full > "$TMP/fmx.xml" 2>/dev/null +# RE-ANCHORED (lane/lego-charge fix round 4, reported under PROCESS rule 4): at 800 tokens the first --detail body is now +# reserved a floor out of the sig claim and ships WHOLE (a 7-line body under a quarter of the budget), so the 800 run no +# longer cuts anything and this arm measured nothing there. The property is unchanged — every truncated body is exactly +# its whole lines — and is asserted at 300 tokens, where the floor (a quarter of the budget) is below the body and cuts +# it; the 800 run is kept as a twin below asserting its first body is served whole, byte for byte. +"$BIN" "$FMX" --no-cache --for="processEntry input trim" --detail=30 --token-budget=300 --legend=full > "$TMP/fmx.xml" 2>/dev/null +"$BIN" "$FMX" --no-cache --for="processEntry input trim" --detail=30 --token-budget=800 --legend=full > "$TMP/fmx800.xml" 2>/dev/null B6="$( python3 - "$TMP/fmx.xml" "$BIN" "$FMX" <<'PY' import re, subprocess, sys, html doc = open( sys.argv[1], "rb" ).read() @@ -263,7 +269,25 @@ for at, text in cut: print( "OK %d truncated body(ies), each exactly its whole lines%s" % ( len( cut ), " (over_ceiling)" if any( a.get( "over_ceiling" ) == "1" for a, _ in cut ) else "" ) ) PY )" -case "$B6" in OK*) ok "(B6) formaxtokens fixture: $B6" ;; *) no "(B6) formaxtokens fixture: $B6" ;; esac +case "$B6" in OK*) ok "(B6) formaxtokens fixture @300: $B6" ;; *) no "(B6) formaxtokens fixture @300: $B6" ;; esac +# (B6 twin @800) the old operating point: the first body is served whole (no truncated=, no over_ceiling=), byte-identical +# to an uncut --expand of the same definition. +B6W="$( python3 - "$TMP/fmx800.xml" "$BIN" "$FMX" <<'PY' +import re, subprocess, sys, html +doc = open( sys.argv[1], "rb" ).read() +m = re.search( rb']*)>', doc, re.S ) +if not m: + print( "FAIL no body at all" ); sys.exit( 0 ) +at = dict( ( k.decode(), html.unescape( v.decode() ) ) for k, v in re.findall( rb'(\w+)="([^"]*)"', m.group( 1 ) ) ) +if "truncated" in at or "over_ceiling" in at: + print( "FAIL first body cut: %s" % m.group( 1 ).decode() ); sys.exit( 0 ) +out = subprocess.run( [ sys.argv[2], sys.argv[3], "--no-cache", "--top-k=0", "--legend=full", "--expand=%s:%s:%s" % ( at["p"], at["l"], at["n"] ) ], + capture_output=True, timeout=300 ).stdout +whole = re.search( rb']*>(?:)', out, re.S ).group( 1 ) +print( "OK %s served whole (%d B)" % ( at["n"], len( whole ) ) if m.group( 2 ) == whole else "FAIL %s differs from its --expand" % at["n"] ) +PY +)" +case "$B6W" in OK*) ok "(B6 twin @800) $B6W" ;; *) no "(B6 twin @800) $B6W" ;; esac if xmllint --noout "$TMP/fmx.xml" 2>/dev/null; then ok "(B6) the document is well-formed" else From 54c0c7dc8e0ca6bde6b6178395d3f4908279660b Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 08:31:39 -0400 Subject: [PATCH 111/170] docs(CHANGELOG): lego-charge fix round 4 hop-row count from the final head (192, the candidate head) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 251f97190..1f57395fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ measured Python corpus served 4 of 40 signature rows in a ~4.6 KB answer under t - Under `--token-budget`, a spent ceiling no longer drops the compact `` section without a trace: it ships as ``, where `next=` is the same question without the budget. A partly cut `` carries the same `next=`, and a cut `` carries the `--for … --limit=N` file page. These bytes are reserved - from the signature rows, so measured at 2000 tokens over 32 questions: 202 hop rows named on 32/32 answers (none before), + from the signature rows, so measured at 2000 tokens over 32 questions: 192 hop rows named on 32/32 answers (none before), 65 signature rows fewer (still disclosed by ``'s own `next=`). Gates: `estchargecheck` #11 A7D, `fordisclosurecheck` #1b/#1c. From 86ad98f43838b2a7a40447242e4fa11d5c19d120 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 09:00:36 -0400 Subject: [PATCH 112/170] map inventory: entry styles as language lists; shorter recovery composer quality-delta read the per-language switch as a clone of slice.h's language switch, and writeMapInventoryJson grew past the verbosity bar. The styles are now three Lang lists with a lookup. The entries_next builder joins a fixed table of parts. The JSON guard lines fold to one line each. Output is unchanged. Co-Authored-By: Claude Opus 5.5 --- src/mapinventory.h | 55 ++++++++++++++++++++++------------------------ src/serialize.h | 13 +++-------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/mapinventory.h b/src/mapinventory.h index 8b40eca54..b80eee816 100644 --- a/src/mapinventory.h +++ b/src/mapinventory.h @@ -125,20 +125,19 @@ inline bool isInventoryCodeSymbol( const Symbol& s ) noexcept // `main` is an entry only when its own file's module scope calls it. None: no claim. enum class InventoryEntryStyle : std::uint8_t { None, EntryFn, EntryStaticMethod, ModuleScopeCall }; +// The languages of each style, as data (a reader checks one list, not a switch). +inline constexpr Lang kInventoryEntryFnLangs[] = { Lang::C, Lang::Cpp, Lang::ObjC, Lang::Go, Lang::Rust, Lang::Kotlin, Lang::Dart }; +inline constexpr Lang kInventoryStaticMethodLangs[] = { Lang::Java, Lang::CSharp }; +inline constexpr Lang kInventoryModuleScopeLangs[] = { Lang::Python, Lang::JavaScript, Lang::TypeScript, Lang::Ruby, Lang::Bash, + Lang::Php, Lang::Lua }; + inline constexpr InventoryEntryStyle inventoryEntryStyle( Lang l ) noexcept { - switch( l ) - { - case Lang::C: case Lang::Cpp: case Lang::ObjC: case Lang::Go: case Lang::Rust: case Lang::Kotlin: case Lang::Dart: - return InventoryEntryStyle::EntryFn; - case Lang::Java: case Lang::CSharp: - return InventoryEntryStyle::EntryStaticMethod; - case Lang::Python: case Lang::JavaScript: case Lang::TypeScript: case Lang::Ruby: case Lang::Bash: case Lang::Php: - case Lang::Lua: - return InventoryEntryStyle::ModuleScopeCall; - default: - return InventoryEntryStyle::None; - } + const auto in = [ l ]( const auto& langs ) noexcept { return std::find( std::begin( langs ), std::end( langs ), l ) != std::end( langs ); }; + return in( kInventoryEntryFnLangs ) ? InventoryEntryStyle::EntryFn + : in( kInventoryStaticMethodLangs ) ? InventoryEntryStyle::EntryStaticMethod + : in( kInventoryModuleScopeLangs ) ? InventoryEntryStyle::ModuleScopeCall + : InventoryEntryStyle::None; } // A def whose name and kind fit its language's entry convention (callers are judged separately). @@ -167,31 +166,29 @@ inline bool isInventoryMainModule( const IngestResult& ing, const Symbol& s ) no // so its own window never cuts. A superset — it also lists non-entries of those names (tests, methods, callees). inline std::string inventoryEntriesNext( const IngestResult& ing, const MapInventory& inv, std::size_t from ) { - bool lower = false, upper = false, mainModule = false; + bool on[ 3 ] = { false, false, false }; // a cut def named main, one named Main, a cut __main__.py scope for( std::size_t i = from; i < inv.entries.size(); ++i ) { const Symbol& s = ing.symbols[ inv.entries[ i ] ]; - mainModule = mainModule || s.kind == SymKind::ModuleScope; - lower = lower || ( s.kind != SymKind::ModuleScope && s.name == "main" ); - upper = upper || ( s.kind != SymKind::ModuleScope && s.name == "Main" ); + const bool scope = s.kind == SymKind::ModuleScope; + on[ 0 ] = on[ 0 ] || ( !scope && s.name == "main" ); + on[ 1 ] = on[ 1 ] || ( !scope && s.name == "Main" ); + on[ 2 ] = on[ 2 ] || scope; } - std::vector parts; - if( lower ) { parts.emplace_back( "name(\"main\")" ); } - if( upper ) { parts.emplace_back( "name(\"Main\")" ); } - if( mainModule ) { parts.emplace_back( "and(file(all,\"(^|/)__main__\\.py$\"),kind(all,modscope))" ); } - std::size_t limit = 0; - for( const Symbol& s : ing.symbols ) + static constexpr std::string_view kParts[ 3 ] = { "name(\"main\")", "name(\"Main\")", + "and(file(all,\"(^|/)__main__\\.py$\"),kind(all,modscope))" }; + std::string joined; + int n = 0; + for( int i = 0; i < 3; ++i ) { - limit += ( lower && s.name == "main" ) || ( upper && s.name == "Main" ) || ( mainModule && isInventoryMainModule( ing, s ) ); + if( on[ i ] ) { joined += n++ ? "," : ""; joined += kParts[ i ]; } } - std::string expr = parts.size() == 1 ? parts[ 0 ] : std::string(); - for( std::size_t i = 0; parts.size() > 1 && i < parts.size(); ++i ) + std::size_t limit = 0; // every symbol the expression can match, so the query's own window never cuts + for( const Symbol& s : ing.symbols ) { - expr += i == 0 ? "or(" : ","; - expr += parts[ i ]; + limit += ( on[ 0 ] && s.name == "main" ) || ( on[ 1 ] && s.name == "Main" ) || ( on[ 2 ] && isInventoryMainModule( ing, s ) ); } - if( parts.size() > 1 ) { expr += ")"; } - return nextFlag( "--graph-query=", expr ) + " --limit=" + std::to_string( limit ); + return nextFlag( "--graph-query=", n > 1 ? "or(" + joined + ")" : joined ) + " --limit=" + std::to_string( limit ); } // The program entries by each language's convention (the header's rule), outside the rolled-up directories and test diff --git a/src/serialize.h b/src/serialize.h index 0fd942b9e..b4139eb2c 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -9540,16 +9540,9 @@ inline void writeMapInventoryJson( JsonWriter& w, const IngestResult& ing, const EXPECTS( inv.active, "the caller emits the tier only when it has a cut to inventory" ); w.write( ",\"inv\":{\"listed\":" ); w.write( std::to_string( inv.listed ) ); w.write( ",\"unlisted\":" ); w.write( std::to_string( inv.unlisted ) ); - if( inv.entryTotal > inv.entries.size() ) - { - w.write( ",\"entries_capped\":true,\"entries_total\":" ); w.write( std::to_string( inv.entryTotal ) ); - w.write( ",\"entries_next\":" ); writeJsonStr( w, inv.entriesNext, esc ); - } - if( inv.namesCapped ) - { - w.write( ",\"names_capped\":true,\"names_total\":" ); w.write( std::to_string( inv.namesTotal ) ); - w.write( ",\"names_next\":" ); writeJsonStr( w, inv.namesNext, esc ); - } + // each guard with its recovery argv (rule 5), as the XML twin prints them + if( inv.entryTotal > inv.entries.size() ) { w.write( ",\"entries_capped\":true,\"entries_total\":" + std::to_string( inv.entryTotal ) + ",\"entries_next\":" ); writeJsonStr( w, inv.entriesNext, esc ); } + if( inv.namesCapped ) { w.write( ",\"names_capped\":true,\"names_total\":" + std::to_string( inv.namesTotal ) + ",\"names_next\":" ); writeJsonStr( w, inv.namesNext, esc ); } w.write( ",\"entry\":[" ); for( std::size_t i = 0; i < inv.entries.size(); ++i ) { From f88781bc832937c2b4873e006d9444d51ef5a004 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 11:23:12 -0400 Subject: [PATCH 113/170] =?UTF-8?q?for:=20owner=20ruling=20(option=20B)=20?= =?UTF-8?q?on=20estchargecheck=20#11=20A7=20=E2=80=94=20both=20trailing=20?= =?UTF-8?q?sections=20keep=20the=20rows,=20over=5Fceiling=20names=20the=20?= =?UTF-8?q?true=20reason?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With --detail and --with-graph under an explicit --token-budget the answer keeps its ranked rows and goes over the budget, labelled over_ceiling="1". The last rung blamed the header floor there, which was false (a ~1.8 KB header under a 5.4 KB allowance); it now names the kept rows, the graph block and the first body's floor, and only when the header alone fits. The header-floor note stays where it is true. estchargecheck: BY-DESIGN BREAK (rule 4). A7 keeps its old assertion as a twin (each flag alone at 2000, both flags at 5000 where rows fit); A7B asserts the new contract (rows kept vs the flagless budget answer and the uncapped prefix, over_ceiling, a true reason); A7R kill+next proves the cut first body's next= recovers exactly the cut lines. The sweep and A7R reserve accept either reason, and the kept-rows one must be true. Co-Authored-By: Claude Opus 5.5 --- src/verbs_for.h | 15 +++++- test/estchargecheck.sh | 114 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 6 deletions(-) diff --git a/src/verbs_for.h b/src/verbs_for.h index 45d944bed..1167142bf 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -3890,6 +3890,16 @@ std::optional runForLens( const MainDispatch& d ) " [task_echo: dropped (ceiling)]", " [task_echo + route_attr: dropped (ceiling)]", " [over_ceiling= is 1 on the root: the header floor (verbatim task echo + fixed legend) exceeds this budget" " - no payload left to trim]" }; + // fix round 5 (owner ruling 2026-10-09, option B; reports/lego-charge.md): the both-trailing-sections shape under an + // explicit ceiling KEEPS its ranked rows (reserveTrailing above is off) and goes over the budget on purpose — answers + // first, a budget never silently removes an answer. There the header floor is NOT what overshoots (measured: a ~1.8 KB + // header under a 5.4 KB allowance), so blaming it was false. This note names what really rides past the budget. It + // is chosen only when the header as built fits the allowance by itself; a header floor that alone exceeds it keeps + // the note above, which is then true. + static constexpr rw::CeilingLadderNotes kNotesKeptRows{ + kNotes.echoDropped, kNotes.echoAndRouteDropped, + " [over_ceiling= is 1 on the root: the ranked rows are kept, not cut for the trailing sections, and with the" + " --with-graph block and the first --detail body at its floor they exceed this budget - kept, not trimmed]" }; // PR #135: the priced root this header will carry — every late splice and the est_tokens fixpoint, in // finishForLensHeader (above runForLens, with each splice's rationale). Assembled BEFORE the ladder so the @@ -4041,8 +4051,11 @@ std::optional runForLens( const MainDispatch& d ) } // M3: the ladder hands back the rung it took. That value — never a search of the emitted text — is // what puts over_ceiling="1" on the root below. + // fix round 5: the last rung's reason must be TRUE — the header floor only when the header alone overruns + const bool keptTrailingOverrun = explicitForCeiling && !reserveTrailing && headerStr.size() + 6 <= ladderCeiling; rw::CeilingLadderChoice chosen = rw::climbCeilingLadderBy( buildForHeader, headerStr, fitsExactCeiling, fitsCeiling, - /*hasRouteAttr=*/!routeNoteRaw.empty(), kNotes ); + /*hasRouteAttr=*/!routeNoteRaw.empty(), + keptTrailingOverrun ? kNotesKeptRows : kNotes ); headerStr = std::move( chosen.header ); rootFinish.lastRungFired = ( chosen.rung == rw::CeilingRung::OverCeiling ); } diff --git a/test/estchargecheck.sh b/test/estchargecheck.sh index 54f31f89d..880a70380 100755 --- a/test/estchargecheck.sh +++ b/test/estchargecheck.sh @@ -23,6 +23,14 @@ # gate — that band is the recurrence guard, and it is the reason this file asserts a band and never a # bit-exact token count (house rule: float/estimate assertions get a tolerance, never bit-equality). # +# BY-DESIGN BREAK (rule 4; owner ruling 2026-10-09, option B; reports/lego-charge.md fix round 5): #11 A7's old bar +# "--token-budget=2000 --detail=20 --with-graph on src stays inside N x 2.36 x 1.15" no longer holds for that ONE shape. +# With BOTH trailing sections under an explicit ceiling the answer KEEPS its ranked rows (a gold owner row, textual-12 +# Reactive._set, was cut when the two sections were reserved out of ) and goes over the budget, labelled +# over_ceiling="1" with a TRUE reason and a next= on every cut: answers first, a budget never silently removes an +# answer. The old assertion is kept as A7 twin wherever it must still hold (each flag alone at 2000, and both flags at +# a budget the rows fit); A7B asserts the new contract for both-flags-over-budget. +# # Usage: bash test/estchargecheck.sh [BIN] | RIPWIRE_BIN=asan/ripwire bash test/estchargecheck.sh # Exits non-zero on any failure; prints PASS/FAIL per check, ALL PASS on success. @@ -580,14 +588,61 @@ done # A7 — and --token-budget must SHAPE the bodies, not just the signatures. D10: --for trims to fit, exit 0. # The pre-fix binary streamed 68 035 B here against a 4 248 B ceiling; the bar is the stated ceiling plus the # documented single-entry overshoot tolerance (serialize.h kCeilingFirstEntryTolerance = 1.15). +# BY-DESIGN BREAK (owner ruling 2026-10-09, option B — see this file's header): the old arm ran ONLY the both-flags shape +# at 2000, which now keeps its rows and goes over on purpose. A7 TWIN keeps the old assertion, unchanged in substance +# (exit 0, delivered <= the allowance), on every shape where it must still hold: each trailing flag alone at 2000, and +# both flags at 5000 where the ranked rows fit (measured at fix round 5: 4727 / 4769 / 12337 B vs 5428 / 5428 / 13570 B). TBF=2000 ALLOW="$( awk "BEGIN{printf \"%d\", $TBF*2.36*1.15}" )" +for tw in "2000:--detail=20" "2000:--with-graph" "5000:--detail=20 --with-graph"; do + tw_n="${tw%%:*}"; tw_args="${tw#*:}" + tw_allow="$( awk "BEGIN{printf \"%d\", $tw_n*2.36*1.15}" )" + # shellcheck disable=SC2086 + "$BIN" src --for="$FOR_TASK" --token-budget=$tw_n $tw_args --no-cache >"$TMP/f_tw.out" 2>/dev/null + rc_tw=$? + tw_b="$( bytes_of "$TMP/f_tw.out" )" + { [ "$rc_tw" -eq 0 ] && [ -n "$tw_b" ] && [ "$tw_b" -le "$tw_allow" ]; } 2>/dev/null \ + && ok "#11 A7 twin --for --token-budget=$tw_n $tw_args: $tw_b B within the $tw_allow B allowance (exit 0, SHAPED)" \ + || no "#11 A7 twin --for --token-budget=$tw_n $tw_args: ${tw_b:-?} B vs the $tw_allow B allowance, exit $rc_tw — the budget does not bound the appended sections" +done +# A7B — THE NEW CONTRACT for both flags OVER the budget (owner ruling, option B). Same query, same 2000 tokens. It must: +# (premise) deliver past the allowance at exit 0 — else the shape now fits and this arm must be re-anchored, not passed; +# (rows) keep every row the same budget shows WITHOUT the trailing flags, and those rows are the uncapped +# answer's top rows in its order (no higher-ranked row of the uncapped answer is missing) — the trailing +# sections never remove an answer row; +# (label) carry over_ceiling="1" on the root; +# (reason) name the reason TRUTHFULLY: the last-rung note is the kept-rows one, not "the header floor ... exceeds", +# and what it names is there — the header alone fits the allowance, and rows, the graph block and a +# first --detail body are all present; +# (next) a cut first body carries a next= that recovers exactly the lines it cut (checked on A7R's corpus below, +# whose first body is longer than the floor; here a first body served whole carries no truncated=). +KEPT_NOTE='[over_ceiling= is 1 on the root: the ranked rows are kept, not cut for the trailing sections' +FLOOR_NOTE='[over_ceiling= is 1 on the root: the header floor' +rows_of(){ grep -aoE ']*>' "$1" | sed -nE 's/.* n="([^"]*)".*/\1/p'; } "$BIN" src --for="$FOR_TASK" --token-budget=$TBF --detail=20 --with-graph --no-cache >"$TMP/f_tb.out" 2>"$TMP/f_tb.err" rc_ftb=$? +"$BIN" src --for="$FOR_TASK" --token-budget=$TBF --no-cache >"$TMP/f_tbp.out" 2>/dev/null FTB="$( bytes_of "$TMP/f_tb.out" )" -{ [ "$rc_ftb" -eq 0 ] && [ "$FTB" -le "$ALLOW" ]; } 2>/dev/null \ - && ok "#11 A7 --for --token-budget=$TBF --detail=20 --with-graph: $FTB B within the $ALLOW B allowance (exit 0, SHAPED)" \ - || no "#11 A7 --for --token-budget=$TBF --detail=20 --with-graph: $FTB B vs the $ALLOW B allowance, exit $rc_ftb — the budget does not bound the appended sections" +a7b_root="$( grep -aoE '^]*>' "$TMP/f_tb.out" | head -1 )" +a7b_hdr="$( python3 -c 'import sys; d=open(sys.argv[1],"rb").read(); print(d.find(b""$TMP/a7b.rows"; rows_of "$TMP/f_tbp.out" >"$TMP/a7b.plain"; rows_of "$TMP/f_for.out" >"$TMP/a7b.unc" +a7b_n="$( wc -l < "$TMP/a7b.rows" | tr -d ' ' )"; a7b_pn="$( wc -l < "$TMP/a7b.plain" | tr -d ' ' )" +a7b_why="" +{ [ "$rc_ftb" -eq 0 ] && [ -n "$a7b_root" ] && [ -n "$FTB" ] && [ "$FTB" -gt "$ALLOW" ]; } 2>/dev/null \ + || a7b_why="$a7b_why premise:${FTB:-?}B<=${ALLOW}B-or-exit-$rc_ftb(re-anchor)" +{ [ "$a7b_pn" -ge 1 ] && [ "$a7b_n" -ge "$a7b_pn" ] && [ -z "$( grep -vxF -f "$TMP/a7b.rows" "$TMP/a7b.plain" )" ] \ + && [ "$( head -n "$a7b_n" "$TMP/a7b.unc" )" = "$( cat "$TMP/a7b.rows" )" ]; } 2>/dev/null \ + || a7b_why="$a7b_why rows:$a7b_n-vs-plain-$a7b_pn-or-not-the-uncapped-prefix" +[ "${a7b_root#* over_ceiling=\"1\"}" != "$a7b_root" ] || a7b_why="$a7b_why no-over_ceiling" +{ grep -aqF "$KEPT_NOTE" "$TMP/f_tb.out" && ! grep -aqF "$FLOOR_NOTE" "$TMP/f_tb.out" && [ "${a7b_hdr:--1}" -gt 0 ] && [ "$a7b_hdr" -le "$ALLOW" ] \ + && grep -aq ']*>/dev/null \ + || a7b_why="$a7b_why reason:not-the-kept-rows-note-or-untrue(header ${a7b_hdr:-?}B)" +a7b_b1="$( grep -aoE ']*>' "$TMP/f_tb.out" | head -1 )" +{ [ -n "$a7b_b1" ] && { [ "${a7b_b1#* truncated=\"1\"}" = "$a7b_b1" ] || [ "${a7b_b1#* next=\"--expand=}" != "$a7b_b1" ]; }; } \ + || a7b_why="$a7b_why first-body-cut-without-next" +[ -z "$a7b_why" ] \ + && ok "#11 A7B (by-design, owner option B) --token-budget=$TBF --detail=20 --with-graph: $FTB B past the $ALLOW B allowance, over_ceiling=\"1\", keeps all $a7b_pn rows of the flagless budget answer ($a7b_n rows, the uncapped answer's top), reason names the kept rows (header ${a7b_hdr} B fits alone)" \ + || no "#11 A7B (by-design, owner option B) --token-budget=$TBF --detail=20 --with-graph —$a7b_why" # and without an explicit --token-budget the bodies keep their own budget: the bundle must NOT have shrunk { [ "$( bytes_of "$TMP/f_for_detail.out" )" -gt "$ALLOW" ]; } 2>/dev/null \ && ok "#11 A7 no --token-budget: --detail keeps its --pack-budget-bytes budget (unbudgeted bundle unshrunk)" \ @@ -640,7 +695,15 @@ for spec in "default:760:1500:" "detail_graph:2880:3080:--detail=20 --with-graph s_b="$( bytes_of "$A7S/o.xml" )" s_a="$( awk "BEGIN{printf \"%d\", $N*2.36*1.15}" )" s_root="$( grep -aoE '^]*>' "$A7S/o.xml" | head -1 )" - if [ "$s_rc" -ne 0 ] || { [ "$s_b" -gt "$s_a" ] && ! grep -aqF '[over_ceiling= is 1 on the root: the header floor' "$A7S/o.xml"; }; then + # fix round 5: the last rung now names one of two reasons, each only where it is true — the header floor, or (both + # trailing sections under the ceiling) the kept rows. Either is the disclosure; the kept-rows one must also be TRUE + # here: only on the both-flags shape, and with the header alone inside the allowance. + s_hdr="$( python3 -c 'import sys; d=open(sys.argv[1],"rb").read(); print(d.find(b"/dev/null || r_why="$r_why ${r_b:-?}B>${r_a}B" [ "${r_root#* over_ceiling=\"1\"}" = "$r_root" ] || r_why="$r_why over_ceiling" - ! grep -aqF '[over_ceiling= is 1 on the root: the header floor' "$A7R/o.xml" || r_why="$r_why last-rung" + ! grep -aqF '[over_ceiling= is 1 on the root: ' "$A7R/o.xml" || r_why="$r_why last-rung" # either reason (fix round 5) if [ "${r_args#*--detail}" != "$r_args" ]; then r_b1="$( grep -aoE ']*>' "$A7R/o.xml" | head -1 )" [ -n "$r_b1" ] || r_why="$r_why no-first-body" @@ -742,6 +805,47 @@ done [ -z "$a7k_bad" ] \ && ok "#11 A7R kill: --detail=20 --with-graph at 2000..3000 keeps at least the --with-graph run's rows, labels any overshoot over_ceiling, serves the first body at its floor" \ || no "#11 A7R kill: the both-sections shape —$a7k_bad" +# A7R-K2 (fix round 5, owner ruling option B — the A7B contract on a corpus whose first body is LONGER than the floor): +# at 2000 and 2500 tokens --detail=20 --with-graph delivers past the allowance with over_ceiling="1", the last rung names +# the kept rows (never the header floor: the header alone fits), and the head-cut first body's next= RECOVERS EXACTLY the +# lines it cut — the cut CDATA plus the next= answer's CDATA is the function's source, line for line (rule 5). +a7k2_bad="" +for N in 2000 2500; do + a7r_out --token-budget=$N --detail=20 --with-graph >"$A7R/k2.xml"; k2_rc=$? + k2_b="$( bytes_of "$A7R/k2.xml" )"; k2_a="$( awk "BEGIN{printf \"%d\", $N*2.36*1.15}" )" + k2_root="$( grep -aoE '^]*>' "$A7R/k2.xml" | head -1 )" + k2_hdr="$( python3 -c 'import sys; d=open(sys.argv[1],"rb").read(); print(d.find(b"/dev/null \ + || k2_why="$k2_why premise/label:${k2_b:-?}B-vs-${k2_a}B" + { grep -aqF "$KEPT_NOTE" "$A7R/k2.xml" && ! grep -aqF "$FLOOR_NOTE" "$A7R/k2.xml" && [ "${k2_hdr:--1}" -gt 0 ] && [ "$k2_hdr" -le "$k2_a" ]; } 2>/dev/null \ + || k2_why="$k2_why reason" + k2_next="$( grep -aoE ']*>' "$A7R/k2.xml" | head -1 | sed -nE 's/.* truncated="1".* next="--expand=([^"]*)".*/\1/p' )" + if [ -z "$k2_next" ]; then + k2_why="$k2_why first-body-not-head-cut-with-next" + else + ( cd "$A7R" && "$BIN" corpus --expand="$k2_next" --no-cache ) >"$A7R/k2n.xml" 2>/dev/null + python3 - "$A7R/k2.xml" "$A7R/k2n.xml" "$A7R/corpus" <<'PYR' || k2_why="$k2_why next=-does-not-recover-the-cut" +import re, sys, os +cut = open( sys.argv[1] ).read(); nxt = open( sys.argv[2] ).read() +m1 = re.search( r']* p="([^"]+)" n="[^"]+" lines="1-(\d+)/(\d+)" truncated="1"[^>]*>', cut, re.S ) +l1 = re.search( r']* l="(\d+)" p="', cut ) +m2 = re.search( r']* lines="(\d+)-(\d+)/(\d+)"[^>]*>', nxt, re.S ) +if not ( m1 and l1 and m2 ): sys.exit( 1 ) +k, t = int( m1.group( 2 ) ), int( m1.group( 3 ) ) +if ( int( m2.group( 1 ) ), int( m2.group( 2 ) ), int( m2.group( 3 ) ) ) != ( k + 1, t, t ): sys.exit( 1 ) +src = open( os.path.join( sys.argv[3], m1.group( 1 ) ) ).read().split( "\n" ) +start = int( l1.group( 1 ) ) - 1 +want = "\n".join( src[ start : start + t ] ) +got = m1.group( 4 ).rstrip( "\n" ) + "\n" + m2.group( 4 ) +sys.exit( 0 if got.rstrip( "\n" ) == want.rstrip( "\n" ) else 1 ) +PYR + fi + [ -z "$k2_why" ] || a7k2_bad="$a7k2_bad @$N:[${k2_why# }]" +done +[ -z "$a7k2_bad" ] \ + && ok "#11 A7R kill+next (by-design, owner option B): --detail=20 --with-graph at 2000/2500 is over_ceiling with the kept-rows reason, and the cut first body's next= returns exactly the lines it cut" \ + || no "#11 A7R kill+next (by-design, owner option B): —$a7k2_bad" # A7R-2 (the floor's cut is the one the floor priced): at 1500 tokens the first body is longer than the floor, so it ships # head-cut — truncated="1" with lines= and next= — never dropped and never the bare one-line stub. a7r_out --token-budget=1500 --detail=3 >"$A7R/f.xml" From 879a54b48dbb0003b7f88a49bcc890d10f6af38d Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:42:28 +0000 Subject: [PATCH 114/170] =?UTF-8?q?fix(paging):=20the=20#362=20review=20ro?= =?UTF-8?q?und=205=20=E2=80=94=20the=20gate=20keeps=20proving=20B1;=20the?= =?UTF-8?q?=20quality=20bars=20back=20under?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B1b: the test/ walk proves order and completeness but on test/ the pruning drops NOTHING (0a8432a1 answers the same total with and without RIPWIRE_NO_PRUNE), so the walk alone cannot catch B1 - one page per surface on src/ (where the pruning does drop candidates: 931 pruned vs 1232 exhaustive on 0a8432a1) asserts all five totals equal. ~15s locally, the maintainer's own recipe. Quality bars: candidatePageWindowed (verbs_for.h) and mcpPageRankFlags (mcpverbs.h) name the two inline conditionals round 4 duplicated; the duplicates took packTaskText 33->42 and runPackTask 19->24 over their gating thresholds against the merge base. Both helpers are one-liners, the maintainer verified gating=0 with exactly these on a scratch commit. Also: the B2 MCP no_route arm asks 'packSignatures budget' (routed: total=0, unrouted: 87) so the paste comparison can fail on its own, not only the echo check; the walk_mcp comment matches its 4-page cap; the CHANGELOG's flag sentence says 'ranking flag' (the list-changing-but-unechoed flags belong to a separate carried item). --- CHANGELOG.md | 6 ++-- src/mcpverbs.h | 13 +++++-- src/verbs_for.h | 10 +++++- test/pagingsweepcheck.sh | 74 +++++++++++++++++++++++++++++++++------- 4 files changed, 85 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f637187..24de58630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1111,9 +1111,9 @@ page ONE candidate list: the CLI pages force the ranking's full distribution und rule `--for`'s bundle path already followed), exactly the exhaustive scoring the MCP twins always used, so the `ranking=` attribute the first round carried is gone — there is nothing left to distinguish. Every page, CLI or MCP, carries the same pasteable `next=`/`next_tier=`: an MCP page's -handle is a CLI argv now, and it walks the same list. Every accepted flag that changes the ranking -(`--no-route`, `--no-mention-boost`, `--no-doc-mention`, `--cochange-boost` on the CLI; `no_route` -on the MCP twins) rides `next=`, so a pasted continuation re-ranks identically — the +handle is a CLI argv now, and it walks the same list. Every accepted ranking flag +(the CLI's `--no-route`, `--no-mention-boost`, `--no-doc-mention`, `--cochange-boost`; the twins' +`no_route`) rides `next=`, so a pasted continuation re-ranks identically — the `test/pagingsweepcheck.sh` walk arms pin all five surfaces to `has_more="0"` with identical row sequences and pin each flag's echo. The `tools/list` manifest grows 46,732 → 46,916 B (measured against train 25's `main`; descriptions identical, and the +184 B of schema is `explore`'s diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 8269fe539..e5253793a 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -1802,6 +1802,14 @@ inline void priceForTaskRoot( std::string& doc, std::size_t budgetTokens ) rw::spliceRootAttrs( doc, rootAttrs ); } +// #362 review round 4, follow-up (quality bar): the twins' B2 rank-flags fragment, named once - +// the inline conditional appeared in both twins' page calls and the second read pushed +// packTaskText past its complexity bar. +inline std::string_view mcpPageRankFlags( bool noRoute ) +{ + return noRoute ? std::string_view( " --no-route" ) : std::string_view(); +} + inline std::optional forTaskText( const std::string& root, const std::string& task, RedactCounts* redact = nullptr, std::size_t budgetTokens = 0, bool noRoute = false, McpPageArgs page = {}, // L-W: limit/offset select the FILE PAGE (forpage.h), the CLI --for --limit twin @@ -1970,7 +1978,7 @@ inline std::optional forTaskText( const std::string& root, const st // emitted in-doc (schema= present) so the ref posture reduces it like any other spec-keyed // answer — the outer layer sees AlreadyCompact and passes it through. return forCandidatePageDoc( ing, lensRank, cut, ForCandidatePageReq{ task, "--for=", - noRoute ? std::string_view( " --no-route" ) : std::string_view(), + mcpPageRankFlags( noRoute ), /*pasteHandle=*/true, /*compactLegend=*/true, routeNoteOf( rc, shape, noRoute ), mcpRootArg, redact, gitstamp::stampAt( root ), budgetTokens, page.limit, page.offset } ); @@ -4160,6 +4168,7 @@ struct PackTaskForm bool noRoute = false; // the CLI --no-route twin McpPageArgs page; // limit/offset: the candidate page under a budget, the file page without }; + inline std::string packTaskText( const std::string& root, const std::string& task, std::size_t budgetTokens, RedactCounts* redact = nullptr, const PackTaskForm& form = {} ) { @@ -4275,7 +4284,7 @@ inline std::string packTaskText( const std::string& root, const std::string& tas // #362 review round 4 (B1/B2): one list across all five page paths — the same pasteable // next= as the CLI pages, no_route echoed so the pasted continuation re-ranks identically. return forCandidatePageDoc( ing, lr.rank, cut, ForCandidatePageReq{ task, "--pack-task=", - noRoute ? std::string_view( " --no-route" ) : std::string_view(), + mcpPageRankFlags( noRoute ), /*pasteHandle=*/true, /*compactLegend=*/true, lr.routeNote, mcpRootArg, redact, gitstamp::stampAt( root ), budgetTokens, pageLimit, pageOffset } ); diff --git a/src/verbs_for.h b/src/verbs_for.h index 4e5bbd649..ffe953481 100644 --- a/src/verbs_for.h +++ b/src/verbs_for.h @@ -2241,6 +2241,14 @@ inline std::string forPageRankFlagArgv( const rw::Config& cfg ) return s; } +// #362 review round 4, follow-up (quality bar): the windowed-page predicate, named once — the +// inline spelling appeared twice (runForLens's bundle path, runPackTask's B1 gate) and the second +// read pushed runPackTask past its complexity bar. +inline bool candidatePageWindowed( const rw::Config& cfg ) +{ + return cfg.tokenBudget != 0 && ( cfg.pageLimit > 0 || cfg.pageOffset > 0 ); +} + std::optional runForLens( const MainDispatch& d ) { using namespace rw; @@ -3828,7 +3836,7 @@ std::optional runPackTask( const MainDispatch& d ) // exact only for its top-K, so total= and the far pages would depend on a performance optimisation // (measured: 931 vs 1232 candidates on src/). The rule --for's bundle path already follows. The un-paged // bundle keeps the H2 pruning: its consumers read only the head. - const bool packWindowPages = cfg.tokenBudget != 0 && ( cfg.pageLimit > 0 || cfg.pageOffset > 0 ); + const bool packWindowPages = candidatePageWindowed( cfg ); LensRanking lr = computeLensRanking( d, task, /*compactCandidate=*/false, /*fullDistribution=*/packWindowPages ); // Q3 per-file churn (mirror --for): only mined on the --for git pass, so here it stays empty/zero when diff --git a/test/pagingsweepcheck.sh b/test/pagingsweepcheck.sh index fe2f24e8d..eee88752d 100755 --- a/test/pagingsweepcheck.sh +++ b/test/pagingsweepcheck.sh @@ -878,11 +878,13 @@ PYM # pruning in its DEFAULT state (no RIPWIRE_NO_PRUNE): a pruned ranking is exact only for its # top-K, so a page that reads past it made total= depend on a performance optimisation # (measured on src/: 931 vs 1232; the reproduction corpus there yields 1232 candidates). -# The arm anchors on $ROOT/test (152 candidates on the same task — past the 40 top-K the -# pruning is exact for, so the class is exercised) because walking 1232 heavy rows on -# FIVE surfaces pushed the slowest macos shard past this gate's declared 1200s budget -# (#362 round 4, CI). Every walk runs to has_more="0" and the five row sequences must be -# identical, r= 1..total, each exactly once. +# The walk anchors on $ROOT/test (152 candidates on the same task) because walking 1232 +# heavy rows on FIVE surfaces pushed the slowest macos shard past this gate's declared +# 1200s budget (#362 round 4, CI). The walk proves order and completeness; on test/ the +# pruning drops NOTHING (0a8432a1 answers the same total with and without +# RIPWIRE_NO_PRUNE), so B1b below adds one page per surface on src/ — a corpus where the +# pruning does drop candidates — and asserts all five totals are equal. Every walk runs to +# has_more="0" and the five row sequences must be identical, r= 1..total, each exactly once. # B2: every ACCEPTED flag that changes the ranking rides next= (or the paste re-ranks a different # list — measured: --no-route page 1 total=563, its flagless next= pasted gave total=1). # For each flag: page 1 with it, paste the returned next= verbatim (shlex), and the rows must @@ -913,6 +915,10 @@ def cli( extra, env=None ): t = subprocess.run( [ BIN, CORPUS, "--cache=" + CACHE, "--legend=full" ] + extra, capture_output = True, text = True, env = e ).stdout return t +def cli_on( corpus, cache, extra, env=None ): + e = dict( os.environ ); e.update( env or {} ) + return subprocess.run( [ BIN, corpus, "--cache=" + cache, "--legend=full" ] + extra, + capture_output = True, text = True, env = e ).stdout def mcp( verb, args ): reqs = [ '{"jsonrpc":"2.0","id":1,"method":"initialize"}', json.dumps( { "jsonrpc": "2.0", "id": 2, "method": "tools/call", @@ -925,10 +931,23 @@ def mcp( verb, args ): if msg.get( "id" ) == 2: return msg["result"]["content"][0]["text"] return "" +def mcp_on( corpus, verb, args ): + reqs = [ '{"jsonrpc":"2.0","id":1,"method":"initialize"}', + json.dumps( { "jsonrpc": "2.0", "id": 2, "method": "tools/call", + "params": { "name": verb, "arguments": args } } ) ] + p = subprocess.run( [ BIN, "--mcp" ], input = "\n".join( reqs ) + "\n", + capture_output = True, text = True ) + for line in p.stdout.splitlines(): + try: msg = json.loads( line ) + except ValueError: continue + if msg.get( "id" ) == 2: + return msg["result"]["content"][0]["text"] + return "" def walk_mcp( verb, budget, limit ): - # page 1 alone (its next_offset names page 2's window), page 2 in its own call: two pages - # per surface on this corpus (152 candidates, limit 80), and the arm FAILS loudly if the - # corpus ever grows past two pages — raise the budget here, not a silent partial walk. + # walked page by page (each page's next_offset names the next window), capped at 4 pages: + # the pack_task twin can serve fewer rows per page than its window (its shell reserves + # differ), so 152 candidates are not always 2 pages; the cap FAILs loudly rather than + # walking silently short. def page( off ): doc = mcp( verb, { "path": CORPUS, "task": "how does pagerank converge", "budget_tokens": budget, "limit": limit, "offset": off } ) @@ -964,7 +983,7 @@ seq_mfor, a_mfor = walk_mcp( "for", 999999, 80 ) seq_mexp, a_mexp = walk_mcp( "explore", 999999, 80 ) seq_mpk, a_mpk = walk_mcp( "pack_task", 999999, 80 ) if None in ( seq_for, seq_pack, seq_mfor, seq_mexp, seq_mpk ): - no( "B1: a walk did not terminate — see the failures above" ) + no( "B1: a walk did not terminate - see the failures above" ) else: walks = { "cli --for": seq_for, "cli --pack-task": seq_pack, "mcp for": seq_mfor, "mcp explore": seq_mexp, "mcp pack_task": seq_mpk } @@ -980,6 +999,34 @@ else: no( "B1: walks differ (" + ",".join( diff ) + ") or the sequence is not r=1..total=" + str( total ) + " (got " + str( len( base ) ) + " rows)" ) +# B1b - the test/ walk above proves order and completeness, but on test/ the pruning drops +# NOTHING (a 0a8432a1 build answers the same total with and without RIPWIRE_NO_PRUNE), so it +# cannot catch B1 by itself: one page per surface on src/ (a corpus where the pruning does drop +# candidates - 931 pruned vs 1232 exhaustive on 0a8432a1) asserts all five totals are equal. +SRCCACHE = os.environ.get( "PGSRCCACHE", os.path.join( os.path.dirname( CACHE ), "src.cache" ) ) +if not os.path.isfile( SRCCACHE ) or os.path.getsize( SRCCACHE ) == 0: + subprocess.run( [ BIN, os.path.join( os.path.dirname( CORPUS ), "src" ), "--cache=" + SRCCACHE ], + capture_output = True ) +SRCROOT = os.path.join( os.path.dirname( CORPUS ), "src" ) +src_totals = {} +for verb_flag in ( "--for=", "--pack-task=" ): + doc = cli_on( SRCROOT, SRCCACHE, [ verb_flag + "how does pagerank converge", + "--token-budget=1000", "--limit=1" ] ) + a = attrs_of( doc ) + src_totals[ "cli " + verb_flag ] = int( a["total"] ) if a else -1 +for verb in ( "for", "explore", "pack_task" ): + doc = mcp_on( SRCROOT, verb, { "path": SRCROOT, "task": "how does pagerank converge", + "budget_tokens": 1000, "limit": 1, "offset": 0 } ) + a = attrs_of( doc ) if doc.startswith( " 0: + ok( "B1b: all five surfaces report the same total= (" + str( vals[ 0 ] ) + ") on the pruning-sensitive corpus - " + + ", ".join( k + "=" + str( v ) for k, v in src_totals.items() ) ) +else: + no( "B1b: totals differ across the five surfaces on the pruning-sensitive corpus: " + + ", ".join( k + "=" + str( v ) for k, v in src_totals.items() ) ) + # B2 — each accepted ranking flag rides next=; the paste continues the same list. # (a BROAD-route task: the name-exact route can answer total=1 has_more="0", where no next= # exists to echo — the echo is a property of the walkable lists, so the arm asks for one.) @@ -999,8 +1046,11 @@ for flag, env in ( ( "--no-route", {} ), ( "--no-mention-boost", {} ), ( "--no-d else: no( "B2: pasting " + flag + "'s next= walked a different list — pasted total=" + str( a2p.get( "total" ) ) + " vs page-1 total=" + str( a1.get( "total" ) ) ) -# MCP no_route: the page's next= is a CLI argv now — it must carry --no-route and paste onto the same list. -p1 = mcp( "for", { "path": CORPUS, "task": "how does pagerank converge", "budget_tokens": 3000, +# MCP no_route: the page's next= is a CLI argv now — it must carry --no-route and paste onto the +# same list. "packSignatures budget" is the divergence-catching task: routed it answers +# total=0 (name-exact, nothing found), unrouted total=87 — so a pasted handle that lost +# --no-route walks a DIFFERENT list and the row comparison fails, not just the echo check. +p1 = mcp( "for", { "path": CORPUS, "task": "packSignatures budget", "budget_tokens": 3000, "limit": 40, "no_route": True } ) a1 = attrs_of( p1 ) nxt = a1.get( "next", "" ) @@ -1008,7 +1058,7 @@ if "--no-route" not in nxt: no( "B2: MCP for no_route page does not echo --no-route in next=: " + nxt[:80] ) else: pasted = cli( shlex.split( html.unescape( nxt ) ) ) - direct = cli( [ "--no-route", "--for=how does pagerank converge", "--token-budget=3000", "--limit=40", + direct = cli( [ "--no-route", "--for=packSignatures budget", "--token-budget=3000", "--limit=40", "--offset=" + a1["next_offset"] ] ) if rows_of( pasted ) == rows_of( direct ) and attrs_of( pasted ).get( "total" ) == a1.get( "total" ): ok( "B2: MCP for no_route echoes --no-route; the pasted CLI handle continues the MCP page's list" ) From 058f963dc11ee2d023f8771dc0d138eef211a28a Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 11:57:59 -0400 Subject: [PATCH 115/170] test(ceilingverdictcheck): the kept-rows reason cannot be forged either; CHANGELOG: the A7 by-design break (owner option B) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++++++ test/ceilingverdictcheck.sh | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f57395fd..3baa94081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,12 @@ measured Python corpus served 4 of 40 signature rows in a ~4.6 KB answer under t past the budget and the first body shrank to its first line. With BOTH flags nothing is reserved: on the measured questions that cut a needed signature row, so those answers keep their rows, serve the first body at its floor, and say `over_ceiling="1"` when they overshoot. Without `--token-budget` nothing changes. Gate: `estchargecheck` #11 A7R. +- By-design break, owner ruling 2026-10-09: `--token-budget=2000 --detail=20 --with-graph` on this repo's `src` no longer + fits its allowance (6029 B vs 5428 B). It keeps every signature row the same budget shows without the two flags, says + `over_ceiling="1"`, and the cut first body carries a `next=` that returns exactly the lines it cut. The over-budget note + now names the kept rows, the graph block and the first body's floor. It used to blame the header floor, which was false + here: the header alone is ~1.8 KB. The old bar still holds for each flag alone and for both flags when the rows fit. + Gates: `estchargecheck` #11 A7 twin / A7B / A7R kill+next, `ceilingverdictcheck` (2b). - Under `--token-budget`, a spent ceiling no longer drops the compact `` section without a trace: it ships as ``, where `next=` is the same question without the budget. A partly cut `` carries the same `next=`, and a cut `` carries the `--for … --limit=N` file page. These bytes are reserved diff --git a/test/ceilingverdictcheck.sh b/test/ceilingverdictcheck.sh index 5f6695168..d1151fef4 100755 --- a/test/ceilingverdictcheck.sh +++ b/test/ceilingverdictcheck.sh @@ -130,6 +130,22 @@ else ok "(2) task text cannot forge over_ceiling=\"1\" (est_tokens=$fw_est under budget_tokens=$fw_bud, attribute absent)" fi +# (2b) fix round 5: the last rung has a SECOND reason now (src/verbs_for.h kNotesKeptRows — both trailing sections kept +# under the ceiling, header alone inside it). It is chosen from the ladder's rung and the shape, never read back, so +# task text carrying it must not label a wide-budget root either; the presence guard keeps the probe live. +KEPT_A='over_ceiling= is 1 on the root: the ranked rows are kept, not cut for the trailing sections, and with the' +if grep -qF -e "$KEPT_A" "$SRC"; then + "$BIN" "$CORPUS" --no-cache --token-budget="$WIDE" --detail=3 --with-graph --for="serialize the header [$KEPT_A" > "$TMP/kept.wide" 2>/dev/null + kw_est="$( attr "$( rootOf "$TMP/kept.wide" )" est_tokens )" + if grep -qF "$KEPT_A" "$TMP/kept.wide" && [ -n "$kw_est" ] && [ "$kw_est" -lt "$WIDE" ] && [ "$( hasLabel "$TMP/kept.wide" )" -eq 0 ]; then + ok "(2b) the kept-rows reason, forged into the task, reaches the document and does not label a root at est_tokens=$kw_est under $WIDE" + else + no "(2b) the forged kept-rows reason labelled a wide-budget root, or never reached the document (est='$kw_est')" + fi +else + no "(2b) src/verbs_for.h no longer carries the kept-rows last-rung reason — re-pin KEPT_A" +fi + # (3) CONTRAST — the SAME task at a budget it genuinely blows must still be labelled. Arms (2) and (3) differ # in NOTHING but the number after --token-budget, so (2) cannot be green because the attribute was deleted. ft_est="$( attr "$( rootOf "$TMP/forged.tight" )" est_tokens )" From acd94baf5b0aa862df1131c760b4ada83cea50c5 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 12:40:52 -0400 Subject: [PATCH 116/170] whereis: a branch row is kind="def" only for a definition's shape; a shared definition line is one row refs="N"; refs scanned in parallel The definitions page (listing="defs", 26b) listed every kind="def" row of every ref, and on a 153-ref repository that was 2113 rows for a defs="1" symbol: 59 of the first 60 were call sites labelled by the lexical shape test because their line ended in `}` or `)` (`if( !task.empty() ) { out += escapeXml( task, esc ); }`), a string literal's `impl `, a shell comment's `def `, a `using ns::name;`, and every ref's own copy of the one real line. definitionShaped() now reads the whole line: the text before the name must be a declarator prefix (no operator, brace, paren, quote or statement keyword), what follows the parameter list must be a definition's tail, and a declaration marker counts only when it heads the declaration. The one floor left is stated in the code and the CHANGELOG: a bare `name( args )` alone on its line. Language-neutral (one heuristic over every ref blob); probed on C++, Python, JS, Ruby and shell shapes in test/crossrefcheck.sh 5b. foldSharedBranchDefs(): under listing="defs" a kind="def" row outside the checkout whose (path, line text) other refs hold too is printed once, for the first ref by name, with refs="N"; folded= on the root counts the rows it stands for (shown + more + folded + refs count = hits). listing=all prints every row as before. Both legends define refs= and folded= where they ride. Speed, with no ref cut: `git ls-tree -r` per ref through parallelIndexed (an unlisted slot forfeits complete=), and an occurrence-driven blob walk. Measured on this repo, interleaved with the base: 20.9-21.1 s -> 10.1-10.8 s. test/pagingsweepcheck.sh: the paging fixture's branches each define the symbol differently, so the definitions page keeps enough rows to page. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 26 +++ src/cli.h | 4 +- src/compactlegend.h | 2 + src/crossref.h | 369 +++++++++++++++++++++++++++++++++++---- test/crossrefcheck.sh | 186 ++++++++++++++++++-- test/pagingsweepcheck.sh | 6 +- 6 files changed, 549 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4e190411..a00b0936e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,32 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Changed — `--whereis` labels a branch row `kind="def"` only for a definition's shape, lists a line several refs share once, and scans refs in parallel + +Measured on this repository (153 local refs), `--whereis=escapeXml`, warm, base and this build run turn about on the same +loaded machine: the definitions page went from 60 rows (`total="2113"`, 59 of them call sites such as +`if( !task.empty() ) { out += escapeXml( task, esc ); }` labelled `kind="def"`) to 2 rows, both the declaration; wall +20.9–21.1 s → 10.1–10.8 s. Two repositories with no local refs answer byte for byte as before. + +- **The lexical shape test reads the whole line.** On a non-HEAD ref (and on HEAD when `head_labels="lexical"`) a line is + `kind="def"` only when the text before the name is a declarator prefix — no operator, brace, paren, quote or statement + keyword (`return`, `if`, `await`, `match`…) — and what follows the parameter list is a definition's tail (`{`, an init + list, `->`, a qualifier, the end of the line), not a call's (`;`, `,`, `)`, `.`, `do`…). A declaration marker (`def `, + `fn `, `impl `…) counts only when it heads the declaration, never from inside a string literal or a comment, and + `using ns::name;` is a reference. What stays a floor: a bare `name( args )` alone on its line is a wrapped signature + in C and a semicolon-free call in JS or Python, and one line cannot tell them apart. Language-neutral (one heuristic + serves every ref blob); probed on C++, Python, JS, Ruby and shell shapes. +- **A definition line several refs hold is one row.** Under `listing="defs"` a `kind="def"` row outside the checkout whose + path and line text other refs hold too is printed once, for the first of those refs by name, with `refs="N"`; the root's + `folded=` counts the rows it stands for (`shown + more + folded + refs count = hits`). `hits=` still counts every copy and + `--whereis-listing=all` prints each ref's own row. A ref holding the same text at two lines of one path is two rows. +- **Faster, with no ref cut.** The per-ref `git ls-tree -r` runs through the stray-content sweep's pool (7.0 s → 2.1 s + serial vs parallel on 153 refs), and the blob walk jumps from occurrence to occurrence instead of reading every line. + `complete=` keeps its meaning: a tree a worker never listed forfeits it like an empty one. + +Gates: `test/crossrefcheck.sh` (5b: the shapes and the fold, red on the previous build), `test/pagingsweepcheck.sh` (its +fixture's branches now define the symbol differently each, so the definitions page still pages). + ### Changed — a call bound by name alone keeps its rows, marked `via="name"`; typed receivers resolve A member call `x.m()` (or, where the receiver is implicit, a bare `m()`) bound to every in-repo definition spelled diff --git a/src/cli.h b/src/cli.h index 93c3bdf92..2d905d3ca 100644 --- a/src/cli.h +++ b/src/cli.h @@ -2118,7 +2118,9 @@ inline constexpr char kHelpTail[] = " LISTING: by default only the kind=\"def\" rows are listed and the kind=\"ref\" rows\n" " counted in one element, when that page lists MORE definitions\n" " than the --whereis-listing=all page under the row cap, or the same ones in strictly\n" - " fewer bytes; otherwise every hit is listed.\n" + " fewer bytes; otherwise every hit is listed. On that page a definition line several refs\n" + " hold (same path, same text) is ONE row with refs=\"N\", and folded= on the root counts\n" + " the rows it stands for (shown + more + folded + refs count = hits); listing=all prints each.\n" " --whereis-listing=WHICH with --whereis: which rows to list, defs, refs or all (default: more defs, else shorter).\n" " The default lists defs when that page lists MORE definitions than all under the same\n" " row cap (references can fill a capped all page before the branch definitions arrive),\n" diff --git a/src/compactlegend.h b/src/compactlegend.h index 0f8a27cdb..40842e6c8 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -958,6 +958,8 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "missing", "missing=from|to|both: the endpoint(s) that matched nothing; near= retries the first", false, "path", MapHeaderRead::No, {}, "path" }, // 2026-10-01 (crossref.h demoteTestLocalDefs): present only when an answer holds production AND test-local defs. { "test_local", "hit test_local=1: a definition in a test scope or under a test/bench/fixture path, ordered after the production definitions (only when both exist; nothing dropped)", true, "hit", MapHeaderRead::No, {}, "whereis" }, + { "refs", "hit refs=N: N scanned refs hold this same definition line (path + text); printed once for the first by name, whose ref= tip= date= l= it carries; defs page only, absent = one ref; hits= counts every copy, listing=all prints each", true, "hit", MapHeaderRead::No, {}, "whereis" }, + { "folded", "folded=N: def rows of other refs a listed row's refs= stands for; shown + more + folded + refs count = hits", false, "whereis", MapHeaderRead::No, {}, "whereis" }, // 2026-10-01 freshness fix (crossref.h scanWorktree): present only on a checkout that differs from HEAD. { "worktree", "worktree=read|partial|unlisted: the checkout differs from HEAD (at= +dirty); each changed path is read from disk as ref=\"worktree\" rows replacing HEAD's, and on-head=/hits=/head_labels= count them; on-head= then reads the checkout, not HEAD's commit; partial = some changed path unreadable or a directory (nested repo, submodule), its HEAD rows may be stale; unlisted = git could not list the changes; either withholds complete=", false, "whereis", MapHeaderRead::No, {}, "whereis" }, { "hits", "more hits=N: rows after this page; page on with offset=next_offset", true, "more", MapHeaderRead::No, {}, "whereis" }, diff --git a/src/crossref.h b/src/crossref.h index ad0d29b46..e3106b924 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -1395,6 +1395,12 @@ struct WhereHit std::string text; // the trimmed source line (evidence, so the caller can judge) bool fromWorktree = false; // read from the WORKING-TREE copy of a path that differs from HEAD (ref="worktree") bool testLocal = false; // a TEST-LOCAL definition, demoted below the production ones (test_local="1") + // foldSharedBranchDefs: a kind="def" row outside the checkout whose (path, line text) other refs hold too is ONE + // definition held by N refs. The first row of such a group (lowest ref name) is its representative and carries + // defRefs = N (1 = this ref alone); every later row of the group is foldedDef and the definitions page skips it + // (listing=all still prints it). 0 = not a branch definition row. + std::uint32_t defRefs = 0; + bool foldedDef = false; // The CHECKOUT is HEAD's tree overlaid with the working copy of every path that differs from it: those rows // sort, label and count as one group, ahead of the other refs. Keyed on the flag, never on the ref name, so a @@ -1597,6 +1603,177 @@ inline bool insideArgumentList( std::string_view line, std::size_t at ) noexcept // rows do NOT come here at all when the caller supplies the index's def sites (see relabelHeadHitsFromIndex): // the honest split documented in --help is HEAD = parsed, refs = lexical, and this function is the lexical half. // False positives here cost a `kind="def"` that should have read `kind="ref"`, never a missed hit. +// A declaration marker (`def `, `fn `, `class `…) that HEADS the declaration of the name at `at`: its own word +// (`#undef x` holds no `def `, `defn ` no `fn `, `template_t` no `template`), reached from the line's start through +// declarator text only, and separated from the name by declarator text only — identifier bytes, spaces and the +// punctuation a declarator head carries (`: < > , * & ( ) . [ ] ~ '`: `func (r *T) name(`, `def self.name`, +// `impl<'a> Name<'a>`, `template T name(`). A quote, a `;`, an `=` or a brace between the two means the +// marker is inside a string, a comment or an earlier statement: the 26b sweep's definitions page carried +// `w.write( " 0 && isIdentByte( (unsigned char)line[ pos - 1 ] ) ) + { + continue; + } + if( end < line.size() && isIdentByte( (unsigned char)marker.back() ) && isIdentByte( (unsigned char)line[ end ] ) ) + { + continue; // `template` is a bare word: `template_t` is not it + } + if( line.substr( 0, pos ).find_first_of( kBreaksHead ) != std::string_view::npos ) + { + return false; // the first marker already sits inside a string, a comment or an expression + } + if( line.substr( end, at - end ).find_first_of( kBreaksBetween ) != std::string_view::npos ) + { + return false; + } + if( marker == "using " ) + { + std::size_t k = at; + while( k < line.size() && isIdentByte( (unsigned char)line[ k ] ) ) + { + ++k; + } + while( k < line.size() && std::isspace( (unsigned char)line[ k ] ) ) + { + ++k; + } + return k < line.size() && line[ k ] == '='; + } + return true; + } + return false; +} + +// Is the text BEFORE the name an expression or a statement, so that `name(` is a CALL in it? A definition's prefix +// is a return type, qualifiers, a scope or an attribute — identifier bytes, `< > , : * & ~ [ ]` and spaces, never an +// operator, a brace, a paren or a statement keyword. Every byte below is one no definition line carries before its +// name: `out += escapeXml( task, esc ); }` (the 26b sweep's 59 false kind="def" rows), `if( x ) { f( a ); }`, +// `x = f( a )`, `a ? f( b ) : c`, `// f( a )`. The keywords are the statement heads the C-family, Python, JS/TS, Ruby, +// Go, Rust and Swift grammars put before a call with no paren of their own (`return f( a )`, `if f( a ):`, `await f( a )`, +// `match f( a ) {`); a type or qualifier word (`static`, `const`, `inline`, `public`, `async`, `operator`…) is not here. +inline bool expressionBeforeName( std::string_view line, std::size_t at ) noexcept +{ + static constexpr std::string_view kStatementHeads[] = { + "return", "throw", "throws", "else", "new", "delete", "await", "yield", "raise", "print", "echo", "if", "elif", + "while", "for", "with", "assert", "not", "and", "or", "in", "is", "lambda", "case", "switch", "match", "guard", + "unless", "until", "typeof", "sizeof", "instanceof", "co_return", "co_await", "co_yield", "goto", "do", "then", + }; + const std::string_view before = line.substr( 0, at ); + if( before.find_first_of( "=;{}()+!?|^%/\"'" ) != std::string_view::npos ) + { + return true; + } + for( std::size_t i = 0; i < before.size(); ) + { + if( !isIdentByte( (unsigned char)before[ i ] ) ) + { + ++i; + continue; + } + std::size_t j = i; + while( j < before.size() && isIdentByte( (unsigned char)before[ j ] ) ) + { + ++j; + } + const std::string_view word = before.substr( i, j - i ); + for( std::string_view head : kStatementHeads ) + { + if( word == head ) + { + return true; + } + } + i = j; + } + return false; +} + +// What follows the parameter list's closing paren decides between a definition and a call whose line happens to end +// in a definition's terminator. After a DEFINITION's `)` come only: the body `{`, an init list `:`, a trailing return +// `->`, a qualifier (`const`, `noexcept`, `&`, `&&`, `override`…), `= 0|default|delete`, an attribute `[[`, a comment, +// or the end of the line (the body opens on the next line). Anything else — `;` `,` `)` `.` `[` `?` an operator, `}`, +// a word such as `do` or `then` — means the name was called: `f( a ).then( b )`, `{ f( a ) }`, `f( a ) do |x|`. +// `open` is the index of the `(` after the name. An unbalanced list (the signature wraps) decides nothing: false. +inline bool callShapedAfterParams( std::string_view line, std::size_t open ) noexcept +{ + static constexpr std::string_view kAfterDeclWords[] = { + "const", "noexcept", "override", "final", "volatile", "mutable", "requires", "throw", "throws", "try", "where", + "__attribute__", "asm", "default", "delete", + }; + EXPECTS( open < line.size() && line[ open ] == '(', "the caller found the name's parameter list opening at `open`" ); + int depth = 0; + std::size_t close = std::string_view::npos; + for( std::size_t i = open; i < line.size(); ++i ) + { + if( line[ i ] == '(' ) + { + ++depth; + } + else if( line[ i ] == ')' && --depth == 0 ) + { + close = i; + break; + } + } + if( close == std::string_view::npos ) + { + return false; + } + std::size_t k = close + 1; + while( k < line.size() && std::isspace( (unsigned char)line[ k ] ) ) + { + ++k; + } + if( k >= line.size() ) + { + return false; + } + const char c = line[ k ]; + const char next = ( k + 1 < line.size() ) ? line[ k + 1 ] : '\0'; + if( c == '{' || c == ':' || c == '&' || ( c == '-' && next == '>' ) || ( c == '[' && next == '[' ) + || ( c == '/' && ( next == '/' || next == '*' ) ) ) + { + return false; + } + if( c == '=' ) + { + std::size_t v = k + 1; + while( v < line.size() && std::isspace( (unsigned char)line[ v ] ) ) + { + ++v; + } + const std::string_view rest = line.substr( v ); + return !( rest.starts_with( "0" ) || rest.starts_with( "default" ) || rest.starts_with( "delete" ) ); + } + if( isIdentByte( (unsigned char)c ) ) + { + std::size_t j = k; + while( j < line.size() && isIdentByte( (unsigned char)line[ j ] ) ) + { + ++j; + } + const std::string_view word = line.substr( k, j - k ); + for( std::string_view w : kAfterDeclWords ) + { + if( word == w ) + { + return false; + } + } + return true; + } + return true; +} + inline bool definitionShaped( std::string_view line, std::string_view sym, std::size_t at ) { static constexpr std::string_view kDeclMarkers[] = { @@ -1605,7 +1782,7 @@ inline bool definitionShaped( std::string_view line, std::string_view sym, std:: }; for( std::string_view m : kDeclMarkers ) { - if( line.find( m ) != std::string_view::npos && line.find( m ) < at ) + if( declMarkerBefore( line, m, at ) ) { return true; } @@ -1617,6 +1794,13 @@ inline bool definitionShaped( std::string_view line, std::string_view sym, std:: // which are references, not definitions. The accepted terminators are '{' (body opens here), '}' (whole // one-line body), ')' (signature wraps to the next line) and ':' (a constructor's init list follows), // tested AFTER declaratorEnd() has stripped any trailing specifier tail. + // + // Two more tests, both lexical (the 26b sweep: with the definitions page listing EVERY kind="def" row of every + // ref, 59 of the 60 rows for a defs="1" symbol were call sites that merely ended in `}` or `)`): the text + // before the name must read as a declarator prefix, not an expression or a statement (expressionBeforeName), + // and what follows the parameter list must be a definition's tail, not a call's (callShapedAfterParams). + // What stays a floor: a bare `name( args )` alone on its line with nothing before or after it is a wrapped + // signature in C and a semicolon-free call in JS or Python, and one line cannot tell them apart. const std::size_t after = at + sym.size(); if( after < line.size() && line[ after ] == '(' ) { @@ -1624,7 +1808,8 @@ inline bool definitionShaped( std::string_view line, std::string_view sym, std:: const char last = ( e > 0 ) ? line[ e - 1 ] : ';'; const bool endsDecl = last == '{' || last == '}' || last == ')' || last == ':'; const bool isCall = at >= 1 && ( line[ at - 1 ] == '.' || ( at >= 2 && line[ at - 2 ] == '-' && line[ at - 1 ] == '>' ) ); - return endsDecl && !isCall && at > 0 && !insideArgumentList( line, at ); + return endsDecl && !isCall && at > 0 && !insideArgumentList( line, at ) && !expressionBeforeName( line, at ) + && !callShapedAfterParams( line, after ); } // ObjC method: "- (ret) sym" / "+ (ret) sym" if( !line.empty() && ( line[0] == '-' || line[0] == '+' ) && line.find( ')' ) < at ) @@ -1667,28 +1852,38 @@ inline void scanLineForSymbol( std::string_view line, std::string_view sym, cons // perfectly legal git content, and evaluating only on '\n' silently skipped it. A symbol defined on that // final line then reported hits="0" — which this verb's own help text tells the reader means "this repo // never had the name", the single most misleading answer it can give. +// +// The walk is OCCURRENCE-driven, not line-driven: one search finds the next occurrence of `sym` anywhere in the +// blob, the newlines skipped over are counted (the line number) and only the line holding the occurrence is handed +// to scanLineForSymbol — a line without the symbol is never visited on its own. The line-by-line walk this replaces +// paid two searches per line (the newline, then the symbol) over every line of every blob that held the symbol at +// all, and on a 153-ref repo that was the larger half of a 22 s --whereis. Same hits, same order, one per line. inline void scanBlobForSymbol( std::string_view bytes, std::string_view sym, const RefInfo& ref, const std::string& path, std::vector& out ) { - std::size_t lineStart = 0; + std::size_t lineStart = 0; // the first byte of the line that holds `counted` + std::size_t counted = 0; // every newline before this offset is in lineNo std::uint32_t lineNo = 1; - while( lineStart <= bytes.size() ) + for( std::size_t pos = bytes.find( sym ); pos != std::string_view::npos; pos = bytes.find( sym, lineStart ) ) { - std::size_t end = bytes.find( '\n', lineStart ); - const bool isFinal = ( end == std::string_view::npos ); - if( isFinal ) + lineNo += std::uint32_t( std::count( bytes.begin() + std::ptrdiff_t( counted ), bytes.begin() + std::ptrdiff_t( pos ), '\n' ) ); + if( const std::size_t nl = bytes.rfind( '\n', pos ); nl != std::string_view::npos && nl >= counted ) { - end = bytes.size(); + lineStart = nl + 1; + } + std::size_t end = bytes.find( '\n', pos ); + if( end == std::string_view::npos ) + { + end = bytes.size(); // a final line with no trailing '\n' is legal git content and is scanned like any other } - scanLineForSymbol( bytes.substr( lineStart, end - lineStart ), sym, ref, path, lineNo, out ); - - if( isFinal ) + if( end >= bytes.size() ) { break; } + ++lineNo; // the line is done: one hit per line lineStart = end + 1; - ++lineNo; + counted = lineStart; } } @@ -2194,6 +2389,66 @@ inline bool demoteTestLocalDefs( std::vector& hits ) return false; } +// The 26b sweep's definitions page: a defs="1" symbol listed 2113 "definitions", because every one of 153 refs holds +// its own copy of the same `inline … escapeXml( … )` line and each copy was a row. Identical definition lines across +// the OTHER refs — the same path, the same line text, kind="def", outside the checkout — are ONE definition held by +// N refs. In the sorted order (refs by name) the first row of such a group is its representative: it carries +// defRefs = N and the definitions page prints it once with refs="N"; every later row of the group is foldedDef and +// that page skips it. Nothing is erased: hits= still counts every row and listing=all prints each ref's own row. +// A ref that holds the same text at TWO lines of one path (a macro twin, a copy in a #if branch) is two groups — +// the k-th such line of a ref joins the k-th group — so N is exactly the number of refs holding the row, never more. +// The checkout's rows (HEAD and worktree=) are the parsed answer and never fold; nor does any kind="ref" row. +inline void foldSharedBranchDefs( std::vector& hits ) +{ + struct Group + { + std::string lastRef; // the ref whose rows were last seen for this (path, text) + std::size_t ordinalInRef = 0; // how many rows of that ref this key has had so far + std::vector representatives; // one per ordinal: the hit index that stands for it + }; + gtl::btree_map groups; + std::string key; + std::size_t branchDefs = 0, represented = 0; // every branch def row is one representative's count + for( std::size_t i = 0; i < hits.size(); ++i ) + { + WhereHit& h = hits[ i ]; + if( !h.isDef || h.inCheckout() ) + { + continue; + } + ++branchDefs; + key.assign( h.path ); + key += '\n'; // a path never holds a newline, and text is one trimmed line: the join is unambiguous + key += h.text; + Group& g = groups[ key ]; + if( g.lastRef != h.ref ) + { + g.lastRef = h.ref; + g.ordinalInRef = 0; + } + else + { + ++g.ordinalInRef; + } + if( g.ordinalInRef < g.representatives.size() ) + { + WhereHit& rep = hits[ g.representatives[ g.ordinalInRef ] ]; + ++rep.defRefs; + h.foldedDef = true; + } + else + { + g.representatives.push_back( i ); + h.defRefs = 1; + } + } + for( const WhereHit& h : hits ) + { + represented += h.foldedDef ? 0 : h.defRefs; + } + ENSURES( represented == branchDefs, "the representatives' refs= counts add up to every branch definition row: nothing is dropped, only counted" ); +} + // The emitted ORDER of --whereis rows (see computeWhereis' header): the checkout (HEAD plus its worktree rows) first // as ONE group — a definition the edit just added sorts among HEAD's definitions, not after them; the two never // share a path, so the group needs no ref order inside it — then the other refs by name; within a group SOURCE @@ -2280,16 +2535,24 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym struct Site { std::uint32_t refIndex; std::string path; bool replaced; }; bool anyEmptyTree = false; // T1: a zero-row ls-tree could be a FAILED listing — it forfeits complete= gtl::btree_map> sites; - for( std::uint32_t i = 0; i < refs.size(); ++i ) { - const std::vector rows = lsTree( root, refs[i].tip ); - if( rows.empty() ) + // One `git ls-tree -r` process per ref, run through the same pool the stray-content sweep uses: each worker + // writes only its own slot, and the fan-out below reads the slots in ref order, so the map (and the answer) + // is the serial one by construction. Measured on a 153-ref repo: 7.0 s of serial ls-tree wall → 2.1 s. + std::vector> trees( refs.size() ); + ParallelSweep treeSweep; + parallelIndexed( refs.size(), [ & ]( std::size_t i ) { trees[ i ] = lsTree( root, refs[ i ].tip ); }, treeSweep ); + for( std::uint32_t i = 0; i < refs.size(); ++i ) { - anyEmptyTree = true; - } - for( const RawRow& r : rows ) - { - sites[ r.bSha ].push_back( Site{ i, r.path, i == 0 && std::binary_search( worktree.replaced.begin(), worktree.replaced.end(), r.path ) } ); + const std::vector& rows = trees[ i ]; + if( rows.empty() || !treeSweep.isDone( i ) ) + { + anyEmptyTree = true; // a slot a worker never reached is an unlisted tree: it forfeits complete= like an empty one + } + for( const RawRow& r : rows ) + { + sites[ r.bSha ].push_back( Site{ i, r.path, i == 0 && std::binary_search( worktree.replaced.begin(), worktree.replaced.end(), r.path ) } ); + } } } @@ -2333,6 +2596,7 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym // The checkout first, then refs by name; within a group, SOURCE before test before docs (§P11.5, see this // function's header), then definitions before references, then path/line — whereHitBefore states it. std::sort( result.hits.begin(), result.hits.end(), []( const WhereHit& a, const WhereHit& b ) { return whereHitBefore( a, b ); } ); + foldSharedBranchDefs( result.hits ); // after the sort: the representative of a group is its first row in this order return result; } @@ -2735,8 +2999,17 @@ inline std::string whereisDottedRetryOf( const IngestResult& ing, std::string_vi // The legend's CONDITIONAL tail: each paragraph rides only an answer that carries what it defines, so a plain // answer pays no bytes for the with_history lane, test-local rows or the worktree overlay. Split out of // writeWhereisPage so the page writer stays a page writer. -inline void writeWhereisLegendTail( std::FILE* out, const WhereResult& res ) +inline void writeWhereisLegendTail( std::FILE* out, const WhereResult& res, bool sharedDefs ) { + // refs= on a hit rides only a definitions page that printed one (foldSharedBranchDefs), and so does its reading. + if( sharedDefs ) + { + rw::emitRaw( out, "SHARED DEFINITIONS: refs=\"N\" on a kind=\"def\" row outside the checkout means N scanned refs hold this same " + "definition line (the same path and the same line text); the row is printed once, for the first of those " + "refs by name, whose ref=, tip=, date= and l= it carries (another of the N may hold the line at a different " + "line number). It appears only under listing=\"defs\" and only when N is at least 2: a definition one ref " + "alone holds carries no refs=. hits= still counts every copy, and listing=all prints each ref's own row. " ); + } // §L10b: the with_history lane's own element, previously undefined on this legend — shared // verbatim with --doc-drift's copy (gitoracle.h kHistoryProbeLegend) so the two cannot drift. Only // when res.history actually made that element reachable — an unconditional splice would cost every @@ -2850,6 +3123,7 @@ struct ListedHits std::vector rows; std::string_view attr; std::size_t refsElided = 0; + std::size_t defsFolded = 0; // def rows of other refs a listed row's refs= stands for (folded="N" on the root) }; inline ListedHits listedHits( const WhereResult& res, WhereisListing listing ) @@ -2867,14 +3141,21 @@ inline ListedHits listedHits( const WhereResult& res, WhereisListing listing ) for( std::size_t i = 0; i < res.hits.size(); ++i ) { out.rows[ i ] = i; } return out; } - const bool wantDefs = lean; + // The definitions page lists each shared branch definition ONCE (foldSharedBranchDefs): a folded row's line is + // already on the page, on its representative, whose refs= counts it. The whole list (listing=all) keeps every row. + const bool wantDefs = lean; + std::size_t folded = 0; for( std::size_t i = 0; i < res.hits.size(); ++i ) { - if( res.hits[ i ].isDef == wantDefs ) { out.rows.push_back( i ); } + const WhereHit& h = res.hits[ i ]; + if( h.isDef != wantDefs ) { continue; } + if( wantDefs && h.foldedDef ) { ++folded; continue; } + out.rows.push_back( i ); } out.attr = wantDefs ? std::string_view( "defs" ) : std::string_view( "refs" ); out.refsElided = wantDefs ? refs : 0; - ENSURES( out.rows.size() == ( wantDefs ? defs : refs ), "a listing holds exactly the rows of its kind" ); + out.defsFolded = folded; + ENSURES( out.rows.size() + folded == ( wantDefs ? defs : refs ), "a listing holds exactly the rows of its kind, a folded def on its representative" ); return out; } @@ -2963,7 +3244,11 @@ inline void writeWhereisRoot( std::FILE* out, const WhereResult& res, const Wher { anyHoist = anyHoist || whereisRowOnHeadCommit( res.hits[ listed.rows[ k ] ], res.headSha, headDate ); } - const std::string listingAttr = listed.attr.empty() ? std::string() : ( " listing=\"" + std::string( listed.attr ) + "\"" ); + // folded= (foldSharedBranchDefs): the def rows of other refs that a listed row's refs= stands for, so the root's + // arithmetic stays checkable in one place: shown + more + folded + refs count = hits. Only when a row folded, + // so an answer with no shared branch definition is byte-identical to before. + const std::string listingAttr = ( listed.attr.empty() ? std::string() : ( " listing=\"" + std::string( listed.attr ) + "\"" ) ) + + ( listed.defsFolded > 0 ? ( " folded=\"" + std::to_string( listed.defsFolded ) + "\"" ) : std::string() ); const std::string headDateAttr = anyHoist ? ( " head_date=\"" + ex( headDate ) + "\"" ) : std::string(); rw::emitTo( out, "", ex( res.sym ).c_str(), res.onHead ? 1 : 0, res.refsScanned, res.distinctBlobs, res.hits.size(), @@ -2999,9 +3284,12 @@ inline void writeWhereisRows( std::FILE* out, const WhereResult& res, const List h.line, h.isDef ? "def" : "ref", h.testLocal ? " test_local=\"1\"" : "", ex( h.text ).c_str() ); continue; } - rw::emitTo( out, "", + // refs="N" rides ONLY the definitions page (foldSharedBranchDefs): the row stands for N refs' copies of the same + // definition line. listing=all prints each copy as its own row and never carries it; a lone copy carries none. + const std::string sharedAttr = ( listed.attr == "defs" && h.defRefs >= 2 ) ? ( " refs=\"" + std::to_string( h.defRefs ) + "\"" ) : std::string(); + rw::emitTo( out, "", ex( h.ref ).c_str(), h.tip.c_str(), ex( h.date ).c_str(), ex( h.path ).c_str(), - h.line, h.isDef ? "def" : "ref", h.testLocal ? " test_local=\"1\"" : "", ex( h.text ).c_str() ); + h.line, h.isDef ? "def" : "ref", h.testLocal ? " test_local=\"1\"" : "", sharedAttr.c_str(), ex( h.text ).c_str() ); } ASSUME( shownCount == hitPage.end - hitPage.begin ); // = the LISTED rows AFTER this page, so shown + more == the listed rows from this page's offset @@ -3104,7 +3392,10 @@ inline void writeWhereisListedPage( std::FILE* out, const WhereResult& res, std: "the same rows byte for byte that listing=all prints). listing= is absent when every hit is listed: the hit " "list holds no kind=\"ref\" row, or no kind=\"def\" row (then the mentions are the answer), or the defs page " "would neither list more definitions nor be shorter, or the whole list was asked for (the whereis-listing flag, value all; its value defs " - "lists the definitions whatever the size). kind=\"def\" on a HEAD row is the parser's label, not a proof: a " + "lists the definitions whatever the size). Under listing=\"defs\" a definition line that several refs hold " + "(the same path and line text, outside the checkout) is one row with refs=\"N\", defined below when one rides, " + "and folded= on the root counts the rows so folded: shown plus more plus folded plus the refs count equals hits. " + "kind=\"def\" on a HEAD row is the parser's label, not a proof: a " "definition the parser does not model (a Ruby define_method, a setattr, a name bound by assignment such as an " "alias in a class body) is a kind=\"ref\" row, so under listing=\"defs\" it is among the counted refs. " "shown=, capped=, the paging attributes and the more " @@ -3121,7 +3412,10 @@ inline void writeWhereisListedPage( std::FILE* out, const WhereResult& res, std: "Binary blobs are outside the claim (a text symbol cannot occur in one); an oversized TEXT blob suppresses " "the claim instead of being silently skipped. Its ABSENCE claims nothing. " "raise the default cap with limit=N (offset=M pages; a cut listing carries total=/has_more=/next_offset= so a paging loop can continue from it). " ); - writeWhereisLegendTail( out, res ); + const bool sharedDefs = listed.attr == "defs" + && std::any_of( listed.rows.begin() + std::ptrdiff_t( hitPage.begin ), listed.rows.begin() + std::ptrdiff_t( hitPage.end ), + [ & ]( std::size_t i ) { return res.hits[ i ].defRefs >= 2; } ); + writeWhereisLegendTail( out, res, sharedDefs ); std::fputs( "-->", out ); const std::string headDate = whereisHeadDate( res ); writeWhereisRoot( out, res, WhereisPageView{ listed, hitPage, pageLimit, pageOffset }, headDate, ex ); @@ -3170,11 +3464,22 @@ inline ListedHits whereisServedListing( const WhereResult& res, std::size_t maxH // lists are in hit-list order and Defs is the def subsequence, so the definitions the All page shows are a PREFIX // of the Defs page's: comparing the counts compares the sets. More definitions wins whatever its bytes; only equal // sets fall to the bytes rule below. + // Counted as DEFINITION ROWS OF THE WHOLE LIST: on the Defs page a representative row stands for defRefs rows + // (foldSharedBranchDefs), on the All page every row is its own. The prefix argument still holds: each def row + // the All page shows is either a representative (on the Defs page no later than its own def-subsequence + // position, so within the first n) or folds into one that precedes it, and every (representative, ref) pair + // is counted once in defRefs — so the Defs page's weighted count is at least the All page's. const auto defsShown = [ & ]( const ListedHits& l ) { - const std::size_t n = std::min( l.rows.size(), maxHits ); - return std::size_t( std::count_if( l.rows.begin(), l.rows.begin() + std::ptrdiff_t( n ), - [ & ]( std::size_t i ) { return res.hits[ i ].isDef; } ) ); + const std::size_t n = std::min( l.rows.size(), maxHits ); + const bool weighted = l.attr == "defs"; + std::size_t shown = 0; + for( std::size_t k = 0; k < n; ++k ) + { + const WhereHit& h = res.hits[ l.rows[ k ] ]; + shown += !h.isDef ? 0 : ( weighted ? std::max( h.defRefs, 1 ) : 1 ); + } + return shown; }; const std::size_t defsOnDefsPage = defsShown( defs ); const std::size_t defsOnAllPage = defsShown( all ); diff --git a/test/crossrefcheck.sh b/test/crossrefcheck.sh index f210cde53..5dd1f6232 100755 --- a/test/crossrefcheck.sh +++ b/test/crossrefcheck.sh @@ -231,14 +231,31 @@ srcRow="$( grep -n 'engine\.' "$TMP/w2rows" | tail -1 | cut -d: -f1 )" # TWIN on the default listing (lean-answers lane): the same def rows in the same order (source before the doc), the # doc-quoting kind="ref" row not dropped but COUNTED — refs count= equals the whole list's kind="ref" rows. +# +# FOLD UPDATE (lane whereis-defs-fix): the three branches all inherit base's `int computeBudget( int frames )` at +# engine.cpp:3, so the definitions page lists that line ONCE, on the first branch by name, with refs="3" +# (foldSharedBranchDefs); the whole list above (--whereis-listing=all, the untouched twin) still prints each +# branch's own row. The arm now asserts the fold's arithmetic instead of row-for-row equality: every def row of the +# default is a def row of the whole list, in the same order, and the refs= counts (1 when absent) add up to the +# whole list's def rows — nothing is dropped, it is counted. "$BIN" "$R" --whereis=computeBudget >"$TMP/w2d" 2>/dev/null tr '<' '\n' <"$TMP/w2d" | sed -n 's/^hit ref="\([^"]*\)".* p="\([^"]*\)" l="\([0-9]*\)" kind="\([a-z]*\)".*/\1 \4 \2:\3/p' >"$TMP/w2dall" +tr '<' '\n' <"$TMP/w2d" | grep '^hit ' | grep 'kind="def"' | awk '{ n = 1; if( match( $0, / refs="[0-9]+"/ ) ) { n = substr( $0, RSTART + 7, RLENGTH - 8 ) } print n }' >"$TMP/w2dweights" W2REFS="$( grep -c ' ref ' "$TMP/w2all" )" -{ [ "$( grep ' def ' "$TMP/w2dall" )" = "$( grep ' def ' "$TMP/w2all" )" ] && ! grep -q ' ref ' "$TMP/w2dall" \ +W2DEFS_ALL="$( grep -c ' def ' "$TMP/w2all" )" +W2DEFS_SUM="$( awk '{s+=$1} END{print s+0}' "$TMP/w2dweights" )" +{ [ "$( grep ' def ' "$TMP/w2dall" )" = "$( grep ' def ' "$TMP/w2all" | awk '{ k = ( $1 == "HEAD" ) ? "HEAD" NR : $2 " " $3 } !seen[k]++' )" ] && ! grep -q ' ref ' "$TMP/w2dall" \ + && [ "$W2DEFS_SUM" = "$W2DEFS_ALL" ] && [ "$W2DEFS_ALL" -ge 2 ] \ && [ "$( sed -n 1p "$TMP/w2dall" )" = "HEAD def engine.cpp:3" ] && [ "$W2REFS" -ge 1 ] \ && grep -q "" \ - || { no "whereis (default listing twin): the default lost a def row or miscounted the refs"; cat "$TMP/w2dall"; grep -o ']*>' "$TMP/w2d"; } + && ok "whereis (default listing twin): the def rows of the whole list, source first, shared branch lines folded (refs= sums to $W2DEFS_ALL def rows); the $W2REFS kind=\"ref\" row(s), the doc row among them, counted by " \ + || { no "whereis (default listing twin): the default lost a def row, folded wrongly (refs= sum $W2DEFS_SUM vs $W2DEFS_ALL def rows) or miscounted the refs"; cat "$TMP/w2dall"; grep -o ']*>' "$TMP/w2d"; } +# The fold's witness on this fixture: the three branches' identical engine.cpp:3 line is one row, refs="3", on the +# first branch by name, and the whole list keeps all three (a count, never a drop). +W2FOLDED="$( tr '<' '\n' <"$TMP/w2d" | grep -c '^hit ref="feat-merged" .*kind="def" refs="3" ' )" +[ "$W2FOLDED" -eq 1 ] && [ "$( grep -c ' def engine.cpp:3' "$TMP/w2all" )" -eq 4 ] && ! grep -q ' refs="' "$TMP/w2" \ + && ok 'whereis (fold): the branches'"'"' shared engine.cpp:3 definition is one row refs="3" on feat-merged; listing=all keeps all four rows and carries no refs=' \ + || { no "whereis (fold): expected one feat-merged def row with refs=\"3\" (got $W2FOLDED) and 4 engine.cpp:3 def rows in the whole list"; cat "$TMP/w2dall"; } # The finding's own repro, on this repo, when it is a git tree deep enough to answer. Skipped rather # than failed on a shallow/absent checkout: this arm is a bonus over the fixture arms above, which @@ -256,6 +273,121 @@ else ok "whereis: repo root is not a git tree — real-repo arm skipped" fi +# ── 5b) --whereis on the OTHER refs: the lexical shape test says kind="def" only for a definition's shape, and the +# definitions page lists a line that several refs hold ONCE (refs="N") ────────────────────────────────── +# The 26b sweep (ideas-fable-26b #9): with the definitions page listing every kind="def" row of every ref, a defs="1" +# symbol listed 2113 "definitions" — 59 of the first 60 were call sites that merely ended in `}` or `)` +# (`if( !task.empty() ) { out += escapeXml( task, esc ); }`), a string literal's `impl `, a comment's `def `, and every +# ref's own copy of the one real line. Its own fixture: three branches hold the same shapes.cpp / calls.js (shape-b +# byte-identical to shape-a, shape-c with one extra comment line so its lines are numbered differently), beside the +# call shapes of C++, Python, JS, a string literal, a shell comment and a using-declaration. HEAD defines the name in +# budget.cpp, so HEAD's label comes from the index and every branch row is the lexical heuristic's. +SH="$TMP/shapes"; mkdir -p "$SH" +gs(){ git -C "$SH" "$@" >/dev/null 2>&1; } +gs init -q -b main +gs config commit.gpgsign false +printf '#pragma once\nint computeBudget( int frames );\n' > "$SH/budget.h" +printf 'int computeBudget( int frames )\n{\n return frames * 16;\n}\n' > "$SH/budget.cpp" +gs add budget.h budget.cpp +gs commit -qm base +write_shapes(){ # $1 = extra leading comment lines for shapes.cpp (shifts every line number) + { printf '%s' "$1"; cat <<'EOF' +#include "budget.h" +static int computeBudget( int frames ) { return frames * 32; } +auto computeBudget( long frames ) -> long { return frames; } +int notADef( int total ) +{ + if( total == 0 ) { total += computeBudget( 1 ); } + if( total == 1 ) { computeBudget( 2 ); } + return computeBudget( 3 ); +} +EOF + } > "$SH/shapes.cpp" + printf 'if computeBudget( 7 ):\n pass\nx = computeBudget( 8 )\n' > "$SH/calls.py" + printf 'await computeBudget( 9 )\ncomputeBudget( 10 ).then( x => x )\nclass Budget {\n computeBudget( frames ) {\n return frames\n }\n}\n' > "$SH/calls.js" + printf 'w.write( " "$SH/w.cpp" + printf '# 0 def rows in computeBudget( 12 ) hits\n' > "$SH/note.sh" +} +gs checkout -qb shape-a +write_shapes "" +gs add shapes.cpp calls.py calls.js w.cpp note.sh +gs commit -qm "the shapes" +gs checkout -qb shape-b +printf 'unrelated\n' > "$SH/other.txt" +gs add other.txt +gs commit -qm "shape-b: the same shapes, plus an unrelated file" +gs checkout -q main +gs checkout -qb shape-c +write_shapes "// shape-c: one more line above, so every line number differs from shape-a's +" +gs add shapes.cpp calls.py calls.js w.cpp note.sh +gs commit -qm "shape-c: the same lines, numbered differently" +gs checkout -q main + +"$BIN" "$SH" --whereis=computeBudget >"$TMP/sd" 2>/dev/null; rcSd=$? +"$BIN" "$SH" --whereis=computeBudget --whereis-listing=all >"$TMP/sa" 2>/dev/null; rcSa=$? +rows_of(){ tr '<' '\n' <"$1" | sed -n 's/^hit ref="\([^"]*\)".* p="\([^"]*\)" l="\([0-9]*\)" kind="\([a-z]*\)"\( test_local="1"\)\{0,1\}\( refs="[0-9]*"\)\{0,1\} t=.*/\1 \4 \2:\3\6/p'; } +rows_of "$TMP/sd" >"$TMP/sdrows"; rows_of "$TMP/sa" >"$TMP/sarows" +if [ $rcSd -ne 0 ] || [ $rcSa -ne 0 ] || ! grep -q ' root (rc $rcSd / $rcSa)" +else + # (i) RED on 93aeab1d: the nine call shapes read kind="def" on every branch. Every branch def row is one of the + # three definition shapes — the one-line body, the trailing return and the JS method — and no other line. + DEF_T="$( tr '<' '\n' <"$TMP/sa" | grep '^hit ref="shape-' | grep 'kind="def"' | sed -n 's/.* t="\(.*\)"\/>$/\1/p' | sort -u )" + EXPECT_T="$( printf '%s\n' 'computeBudget( frames ) {' 'auto computeBudget( long frames ) -> long { return frames; }' 'static int computeBudget( int frames ) { return frames * 32; }' 'int computeBudget( int frames )' | sort -u )" + [ "$DEF_T" = "$EXPECT_T" ] \ + && ok 'whereis (shapes): on the branches only the definition shapes read kind="def" (base'"'"'s budget.cpp, the one-line body, the trailing return, the JS method)' \ + || { no 'whereis (shapes): a call shape reads kind="def" on a branch, or a definition shape lost its label'; printf '%s\n' "$DEF_T"; } + # (ii) each call shape is a kind="ref" row (present, labelled ref): the fix relabels, it never drops. + missing="" + for shape in 'total += computeBudget( 1 ); }' '{ computeBudget( 2 ); }' 'return computeBudget( 3 );' 'if computeBudget( 7 ):' 'x = computeBudget( 8 )' \ + 'await computeBudget( 9 )' 'computeBudget( 10 ).then' '<impl n=' '0 def rows' 'using ns::computeBudget;'; do + tr '<' '\n' <"$TMP/sa" | grep '^hit ref="shape-a"' | grep 'kind="ref"' | grep -qF "$shape" || missing="$missing [$shape]" + done + [ -z "$missing" ] \ + && ok 'whereis (shapes): the C++, Python, JS, string-literal, comment and using-declaration call shapes are kind="ref" rows on shape-a' \ + || { no "whereis (shapes): call shapes not found as kind=\"ref\" rows:$missing"; grep '^shape-a' "$TMP/sarows"; } + # (iii) the fold: the definitions page lists each shared line once, on shape-a (first by name), refs="3" — shape-c + # holds the same text at other line numbers and still folds (the key is path + text, not the line). + # The base's budget.cpp:1 is on every branch too, so four shared lines: HEAD's two index rows + four shape-a rows. + [ "$( grep -c '^shape-a def .* refs="3"' "$TMP/sdrows" )" -eq 4 ] && ! grep -q '^shape-[bc] ' "$TMP/sdrows" \ + && [ "$( sed -n 1p "$TMP/sdrows" )" = "HEAD def budget.cpp:1" ] && [ "$( grep -c ' def ' "$TMP/sdrows" )" -eq 6 ] \ + && grep -q '^shape-c def shapes.cpp:3$' "$TMP/sarows" && grep -q '^shape-a def shapes.cpp:2 refs="3"' "$TMP/sdrows" \ + && grep -o ']*>' "$TMP/sd" | grep -q ' listing="defs" folded="8" ' \ + && ok 'whereis (fold): the four shared definition lines are one row each on shape-a with refs="3" (folded="8" on the root); shape-c holds one at another line number and folds by text' \ + || { no 'whereis (fold): expected HEAD + four shape-a rows refs="3" and folded="8" on the definitions page'; cat "$TMP/sdrows"; grep -o ']*>' "$TMP/sd"; } + # (iv) the whole list is the untouched twin: every branch's own row, no refs=/folded= anywhere, rows == hits=. + SA_HITS="$( grep -o ']*' "$TMP/sa" | grep -o ' hits="[0-9]*"' | grep -o '[0-9]*' )" + SA_ROWS="$( grep -c . "$TMP/sarows" )" + [ -n "$SA_HITS" ] && [ "$SA_ROWS" = "$SA_HITS" ] && ! grep -q ' refs="' "$TMP/sa" && ! grep -q ' folded="' "$TMP/sa" && [ "$( grep -c ' def ' "$TMP/sarows" )" -eq 14 ] \ + && ok "whereis (fold twin): --whereis-listing=all prints every row ($SA_ROWS == hits=$SA_HITS), 14 def rows, no refs= or folded=" \ + || { no "whereis (fold twin): rows $SA_ROWS vs hits=$SA_HITS, def rows $( grep -c ' def ' "$TMP/sarows" ) (want 14), or refs=/folded= leaked into the whole list"; grep -o ']*>' "$TMP/sa"; } + # (v) the counts agree across the two pages: hits= identical, and on the default equals the whole list's ref rows. + SD_HITS="$( grep -o ']*' "$TMP/sd" | grep -o ' hits="[0-9]*"' | grep -o '[0-9]*' )" + [ "$SD_HITS" = "$SA_HITS" ] && grep -q " equals the whole list's kind=\"ref\" rows" \ + || { no "whereis (fold): hits= or disagree between the pages"; grep -o ']*>' "$TMP/sd"; grep -o ']*>' "$TMP/sd"; } + # (vi) the legend defines refs= where it rides (full prose and compact dictionary), and the full page stays well-formed XML + # with the SHARED DEFINITIONS clause spliced into its comment (checklist 25: no `--` inside it). + "$BIN" "$SH" --whereis=computeBudget --legend=full >"$TMP/sdf" 2>/dev/null + "$BIN" "$SH" --whereis=computeBudget --legend=compact >"$TMP/sdc" 2>/dev/null + if command -v xmllint >/dev/null 2>&1; then + xmllint --noout "$TMP/sdf" 2>/dev/null && grep -q 'SHARED DEFINITIONS: refs="N"' "$TMP/sdf" \ + && ok 'whereis (fold legend): the full legend defines refs= (SHARED DEFINITIONS) and the page is well-formed XML' \ + || no 'whereis (fold legend): the full page is malformed or its legend does not define refs=' + else + grep -q 'SHARED DEFINITIONS: refs="N"' "$TMP/sdf" && ok 'whereis (fold legend): the full legend defines refs= (xmllint absent: well-formedness not checked)' \ + || no 'whereis (fold legend): the full legend does not define refs=' + fi + grep -q 'hit refs=N' "$TMP/sdc" \ + && ok 'whereis (fold legend): the compact dictionary defines hit refs=N on the page that carries it' \ + || no 'whereis (fold legend): the compact dictionary lacks the hit refs=N reading' + "$BIN" "$SH" --whereis=computeBudget --whereis-listing=all --legend=compact >"$TMP/sac" 2>/dev/null + ! grep -q 'hit refs=N' "$TMP/sac" && ! grep -q 'SHARED DEFINITIONS' "$TMP/sa" \ + && ok 'whereis (fold legend): neither legend carries the refs= reading on the whole list, which never prints it' \ + || no 'whereis (fold legend): the refs= reading rides a page without the attribute' +fi + # ── 6) refusals: bare --whereis, and a non-git root ──────────────────────────────────────────────────── if "$BIN" "$R" --whereis >/dev/null 2>&1; [ $? -eq 1 ]; then ok "bare --whereis refuses loudly (exit 1)"; else no "bare --whereis did not exit 1"; fi mkdir -p "$TMP/plain"; printf 'int main(){return 0;}\n' > "$TMP/plain/m.cpp" @@ -373,9 +505,16 @@ WD_HITS="$( printf '%s' "$W1D" | grep -oE ']*' | grep -oE ' hits="[ WD_MORE="$( printf '%s' "$W1D" | grep -oE '//' | grep -oE ']*' | grep -oE ' folded="[0-9]+"' | grep -oE '[0-9]+' )" +{ [ -n "$WD_MORE" ] && [ -n "$WD_REFS" ] && [ "$(( WD_SHOWN + WD_MORE + ${WD_FOLDED:-0} + WD_REFS ))" = "$WD_HITS" ] && [ "$WD_ROWS" = "$WD_SHOWN" ]; } \ + && ok "§B8.2 whereis (default listing twin): shown($WD_SHOWN) + more($WD_MORE) + folded(${WD_FOLDED:-0}) + refs count($WD_REFS) == hits($WD_HITS)" \ + || no "§B8.2 whereis (default listing twin): shown=$WD_SHOWN more=$WD_MORE folded=${WD_FOLDED:-0} refs=$WD_REFS hits=$WD_HITS rows=$WD_ROWS" +[ "$WD_FOLDED" = "2" ] \ + && ok '§B8.2 whereis (default listing twin): folded="2" on the root — the two branch copies of engine.cpp:3 behind the refs="3" row' \ + || no "§B8.2 whereis (default listing twin): folded=\"${WD_FOLDED:-absent}\" on the root, want 2" WDEND="$( "$BIN" "$R" --whereis=computeBudget --limit=1 --offset="$WD_HITS" 2>/dev/null )" if ! printf '%s' "$WDEND" | grep -q ' root" @@ -690,15 +829,24 @@ fi # explicit pages of the same binary, byte for byte. defs_in(){ hits_of def | wc -l | tr -d ' '; } dw(){ "$BIN" "$1" --whereis="$2" --no-cache "${@:3}" 2>/dev/null; } -# (L15) the cap bites: HEAD defines zqCap once and calls it 70 times; 64 branches sit at the previous commit (same files) -# and HEAD adds one unrelated commit, so every branch row carries tip=/date=. The all page lists HEAD's definition and 59 -# references (60 rows), the defs page 60 definitions, and the defs page is the LONGER one in bytes — the default is defs. +# (L15) the cap bites: HEAD defines zqCap once and calls it 70 times; 64 branches each hold their OWN definition line +# (`return x + N`: 64 distinct lines, so none folds into another — foldSharedBranchDefs lists a line several refs share +# once, which is the lane whereis-defs-fix twin below), and HEAD adds one unrelated commit, so every branch row carries +# tip=/date=. The all page lists HEAD's definition and 59 references (60 rows), the defs page 60 definitions, and the +# defs page is the LONGER one in bytes — the default is defs. D15="$TMP/cap15"; mkdir -p "$D15"; git -C "$D15" init -q -b main >/dev/null 2>&1; git -C "$D15" config commit.gpgsign false printf 'int zqCap( int x ) { return x; }\n' >"$D15/def.c" { printf 'int useAll( void )\n{\n int s = 0;\n'; i=0; while [ $i -lt 70 ]; do printf ' s += zqCap( %d );\n' $i; i=$((i+1)); done; printf ' return s;\n}\n'; } >"$D15/use.c" printf '// head\n' >"$D15/other.c" git -C "$D15" add -A >/dev/null 2>&1; git -C "$D15" commit -qm cap1 >/dev/null 2>&1 -i=0; while [ $i -lt 64 ]; do git -C "$D15" branch "b$i" >/dev/null 2>&1; i=$((i+1)); done +i=0; while [ $i -lt 64 ]; do + git -C "$D15" checkout -qb "b$i" >/dev/null 2>&1 + printf 'int zqCap( int x ) { return x + %d; }\n' $i >"$D15/def.c" + git -C "$D15" commit -qam "b$i" >/dev/null 2>&1 + git -C "$D15" checkout -q main >/dev/null 2>&1 + i=$((i+1)) +done +printf 'int zqCap( int x ) { return x; }\n' >"$D15/def.c" printf '// head, later\n' >"$D15/other.c"; git -C "$D15" add -A >/dev/null 2>&1 GIT_COMMITTER_DATE="2026-02-02T00:00:00Z" GIT_AUTHOR_DATE="2026-02-02T00:00:00Z" git -C "$D15" commit -qm cap2 >/dev/null 2>&1 C_D="$( dw "$D15" zqCap )"; C_A="$( dw "$D15" zqCap --whereis-listing=all )"; C_F="$( dw "$D15" zqCap --whereis-listing=defs )" @@ -711,6 +859,24 @@ if [ "$CD_F" -gt "$CD_A" ] 2>/dev/null && [ "$( nbytes "$C_F" )" -gt "$( nbytes else no "LEAN (L15): premises not met: defs shown $CD_F vs all $CD_A, bytes $( nbytes "$C_F" ) vs $( nbytes "$C_A" ), all capped?" fi +# (L15-FOLD, lane whereis-defs-fix) the same shape with the 64 branches at ONE commit: their 64 copies of the definition +# line are one row refs="64" (folded="63" on the root), the defs page is now the SHORTER page (2 rows) and is still the +# default — it lists more definitions (65 represented) than the capped all page (HEAD's one). RED on 93aeab1d: 60 rows, +# every one the same line, 4 more behind . +D15F="$TMP/cap15fold"; mkdir -p "$D15F"; git -C "$D15F" init -q -b main >/dev/null 2>&1; git -C "$D15F" config commit.gpgsign false +cp "$D15/def.c" "$D15/use.c" "$D15F/"; printf '// head\n' >"$D15F/other.c" +git -C "$D15F" add -A >/dev/null 2>&1; git -C "$D15F" commit -qm cap1 >/dev/null 2>&1 +i=0; while [ $i -lt 64 ]; do git -C "$D15F" branch "b$i" >/dev/null 2>&1; i=$((i+1)); done +printf '// head, later\n' >"$D15F/other.c"; git -C "$D15F" add -A >/dev/null 2>&1 +GIT_COMMITTER_DATE="2026-02-02T00:00:00Z" GIT_AUTHOR_DATE="2026-02-02T00:00:00Z" git -C "$D15F" commit -qm cap2 >/dev/null 2>&1 +C_FD="$( dw "$D15F" zqCap )" +if printf '%s' "$C_FD" | lroot | grep -q ' listing="defs" folded="63" ' && [ "$( printf '%s' "$C_FD" | defs_in )" = "2" ] \ + && [ "$( printf '%s' "$C_FD" | tr '<' '\n' | grep -c '^hit ref="b0" .*kind="def" refs="64" ' )" = "1" ] \ + && ! printf '%s' "$C_FD" | grep -q '' +else + no 'LEAN (L15-FOLD): the shared definition line is not folded into one refs="64" row on a 2-row defs page'; printf '%s\n' "$C_FD" | lroot; printf '%s' "$C_FD" | tr '<' '\n' | grep -c '^hit ' +fi # (L15-MCP) the same symbol over MCP (no `listing`): byte-identical to the CLI defs page. if command -v python3 >/dev/null 2>&1; then { [ -n "$C_F" ] && [ "$( mcpq "$D15" zqCap '' )" = "$C_F" ] && [ "$( mcpq "$D15" zqCap ',"listing":"all"' )" = "$C_A" ]; } \ diff --git a/test/pagingsweepcheck.sh b/test/pagingsweepcheck.sh index a60dde490..438efdffc 100755 --- a/test/pagingsweepcheck.sh +++ b/test/pagingsweepcheck.sh @@ -215,7 +215,11 @@ mkPagingFixture(){ local b for b in 1 2 3 4 5 6 7 8; do git checkout -q -b "stray$b" || exit 1 - printf 'int strayFn%d(){ return %d; }\n' "$b" "$b" > "src/stray$b.cpp" + # Each branch also carries its OWN renderWidget definition line (`n + b`, distinct text per branch): the + # definitions page lists a line several refs share ONCE (crossref.h foldSharedBranchDefs, lane + # whereis-defs-fix), so the 8 inherited copies of widget.cpp's line are one row with refs="8" — too few rows + # to page with --limit=3. Eight distinct branch definitions keep the default page at 10 def rows. + printf 'int strayFn%d(){ return %d; }\nstatic int renderWidget( int n ){ return n + %d; }\n' "$b" "$b" "$b" > "src/stray$b.cpp" git add -A && git commit -qm "stray work $b" || exit 1 git checkout -q "$home" || exit 1 done From f1f11a7b842603b0f8caa8a7b8d8f5f50fa8de50 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 12:50:26 -0400 Subject: [PATCH 117/170] whereis: the shared-definitions legend clause and refs= attribute each live in one helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit whereisSharedAttr() decides the ` refs="N"` a row carries, and the legend clause is a constant writeWhereisListedPage splices when a printed row carries one — writeWhereisLegendTail keeps its two-argument contract and writeWhereisRows its shape. callShapedAfterParams() is split into matchingParen() and definitionTailAt(). No output byte changes. Co-Authored-By: Claude Fable 5.1 --- src/crossref.h | 118 +++++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/src/crossref.h b/src/crossref.h index e3106b924..74584482c 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -1703,15 +1703,10 @@ inline bool expressionBeforeName( std::string_view line, std::size_t at ) noexce // or the end of the line (the body opens on the next line). Anything else — `;` `,` `)` `.` `[` `?` an operator, `}`, // a word such as `do` or `then` — means the name was called: `f( a ).then( b )`, `{ f( a ) }`, `f( a ) do |x|`. // `open` is the index of the `(` after the name. An unbalanced list (the signature wraps) decides nothing: false. -inline bool callShapedAfterParams( std::string_view line, std::size_t open ) noexcept +inline std::size_t matchingParen( std::string_view line, std::size_t open ) noexcept { - static constexpr std::string_view kAfterDeclWords[] = { - "const", "noexcept", "override", "final", "volatile", "mutable", "requires", "throw", "throws", "try", "where", - "__attribute__", "asm", "default", "delete", - }; EXPECTS( open < line.size() && line[ open ] == '(', "the caller found the name's parameter list opening at `open`" ); - int depth = 0; - std::size_t close = std::string_view::npos; + int depth = 0; for( std::size_t i = open; i < line.size(); ++i ) { if( line[ i ] == '(' ) @@ -1720,29 +1715,25 @@ inline bool callShapedAfterParams( std::string_view line, std::size_t open ) noe } else if( line[ i ] == ')' && --depth == 0 ) { - close = i; - break; + return i; } } - if( close == std::string_view::npos ) - { - return false; - } - std::size_t k = close + 1; - while( k < line.size() && std::isspace( (unsigned char)line[ k ] ) ) - { - ++k; - } - if( k >= line.size() ) - { - return false; - } + return std::string_view::npos; +} + +// Is the text at `k` (the first non-space byte after the parameter list) a definition's tail? See callShapedAfterParams. +inline bool definitionTailAt( std::string_view line, std::size_t k ) noexcept +{ + static constexpr std::string_view kAfterDeclWords[] = { + "const", "noexcept", "override", "final", "volatile", "mutable", "requires", "throw", "throws", "try", "where", + "__attribute__", "asm", + }; const char c = line[ k ]; const char next = ( k + 1 < line.size() ) ? line[ k + 1 ] : '\0'; if( c == '{' || c == ':' || c == '&' || ( c == '-' && next == '>' ) || ( c == '[' && next == '[' ) || ( c == '/' && ( next == '/' || next == '*' ) ) ) { - return false; + return true; } if( c == '=' ) { @@ -1752,26 +1743,30 @@ inline bool callShapedAfterParams( std::string_view line, std::size_t open ) noe ++v; } const std::string_view rest = line.substr( v ); - return !( rest.starts_with( "0" ) || rest.starts_with( "default" ) || rest.starts_with( "delete" ) ); + return rest.starts_with( "0" ) || rest.starts_with( "default" ) || rest.starts_with( "delete" ); } - if( isIdentByte( (unsigned char)c ) ) + std::size_t j = k; + while( j < line.size() && isIdentByte( (unsigned char)line[ j ] ) ) { - std::size_t j = k; - while( j < line.size() && isIdentByte( (unsigned char)line[ j ] ) ) - { - ++j; - } - const std::string_view word = line.substr( k, j - k ); - for( std::string_view w : kAfterDeclWords ) - { - if( word == w ) - { - return false; - } - } - return true; + ++j; } - return true; + const std::string_view word = line.substr( k, j - k ); + return !word.empty() && std::find( std::begin( kAfterDeclWords ), std::end( kAfterDeclWords ), word ) != std::end( kAfterDeclWords ); +} + +inline bool callShapedAfterParams( std::string_view line, std::size_t open ) noexcept +{ + const std::size_t close = matchingParen( line, open ); + if( close == std::string_view::npos ) + { + return false; + } + std::size_t k = close + 1; + while( k < line.size() && std::isspace( (unsigned char)line[ k ] ) ) + { + ++k; + } + return k < line.size() && !definitionTailAt( line, k ); } inline bool definitionShaped( std::string_view line, std::string_view sym, std::size_t at ) @@ -2999,17 +2994,17 @@ inline std::string whereisDottedRetryOf( const IngestResult& ing, std::string_vi // The legend's CONDITIONAL tail: each paragraph rides only an answer that carries what it defines, so a plain // answer pays no bytes for the with_history lane, test-local rows or the worktree overlay. Split out of // writeWhereisPage so the page writer stays a page writer. -inline void writeWhereisLegendTail( std::FILE* out, const WhereResult& res, bool sharedDefs ) +// refs= on a hit rides only a definitions page that printed one (foldSharedBranchDefs), and so does its reading: the +// SHARED DEFINITIONS clause of the legend, spliced by writeWhereisListedPage beside the tail below. +inline constexpr char kWhereisSharedDefsLegend[] = + "SHARED DEFINITIONS: refs=\"N\" on a kind=\"def\" row outside the checkout means N scanned refs hold this same " + "definition line (the same path and the same line text); the row is printed once, for the first of those " + "refs by name, whose ref=, tip=, date= and l= it carries (another of the N may hold the line at a different " + "line number). It appears only under listing=\"defs\" and only when N is at least 2: a definition one ref " + "alone holds carries no refs=. hits= still counts every copy, and listing=all prints each ref's own row. "; + +inline void writeWhereisLegendTail( std::FILE* out, const WhereResult& res ) { - // refs= on a hit rides only a definitions page that printed one (foldSharedBranchDefs), and so does its reading. - if( sharedDefs ) - { - rw::emitRaw( out, "SHARED DEFINITIONS: refs=\"N\" on a kind=\"def\" row outside the checkout means N scanned refs hold this same " - "definition line (the same path and the same line text); the row is printed once, for the first of those " - "refs by name, whose ref=, tip=, date= and l= it carries (another of the N may hold the line at a different " - "line number). It appears only under listing=\"defs\" and only when N is at least 2: a definition one ref " - "alone holds carries no refs=. hits= still counts every copy, and listing=all prints each ref's own row. " ); - } // §L10b: the with_history lane's own element, previously undefined on this legend — shared // verbatim with --doc-drift's copy (gitoracle.h kHistoryProbeLegend) so the two cannot drift. Only // when res.history actually made that element reachable — an unconditional splice would cost every @@ -3126,6 +3121,14 @@ struct ListedHits std::size_t defsFolded = 0; // def rows of other refs a listed row's refs= stands for (folded="N" on the root) }; +// The ` refs="N"` a row carries, or "": it rides ONLY the definitions page (foldSharedBranchDefs), where the row stands +// for N refs' copies of the same definition line. listing=all prints each copy as its own row and never carries it; a +// lone copy carries none. The legend's SHARED DEFINITIONS clause rides exactly the pages where this is non-empty. +inline std::string whereisSharedAttr( const ListedHits& listed, const WhereHit& h ) +{ + return ( listed.attr == "defs" && h.defRefs >= 2 ) ? ( " refs=\"" + std::to_string( h.defRefs ) + "\"" ) : std::string(); +} + inline ListedHits listedHits( const WhereResult& res, WhereisListing listing ) { EXPECTS( listing != WhereisListing::ShorterOfDefsAll, "the default is resolved to Defs or All (whereisServedListing) before the rows are picked" ); @@ -3284,12 +3287,9 @@ inline void writeWhereisRows( std::FILE* out, const WhereResult& res, const List h.line, h.isDef ? "def" : "ref", h.testLocal ? " test_local=\"1\"" : "", ex( h.text ).c_str() ); continue; } - // refs="N" rides ONLY the definitions page (foldSharedBranchDefs): the row stands for N refs' copies of the same - // definition line. listing=all prints each copy as its own row and never carries it; a lone copy carries none. - const std::string sharedAttr = ( listed.attr == "defs" && h.defRefs >= 2 ) ? ( " refs=\"" + std::to_string( h.defRefs ) + "\"" ) : std::string(); rw::emitTo( out, "", ex( h.ref ).c_str(), h.tip.c_str(), ex( h.date ).c_str(), ex( h.path ).c_str(), - h.line, h.isDef ? "def" : "ref", h.testLocal ? " test_local=\"1\"" : "", sharedAttr.c_str(), ex( h.text ).c_str() ); + h.line, h.isDef ? "def" : "ref", h.testLocal ? " test_local=\"1\"" : "", whereisSharedAttr( listed, h ).c_str(), ex( h.text ).c_str() ); } ASSUME( shownCount == hitPage.end - hitPage.begin ); // = the LISTED rows AFTER this page, so shown + more == the listed rows from this page's offset @@ -3412,10 +3412,12 @@ inline void writeWhereisListedPage( std::FILE* out, const WhereResult& res, std: "Binary blobs are outside the claim (a text symbol cannot occur in one); an oversized TEXT blob suppresses " "the claim instead of being silently skipped. Its ABSENCE claims nothing. " "raise the default cap with limit=N (offset=M pages; a cut listing carries total=/has_more=/next_offset= so a paging loop can continue from it). " ); - const bool sharedDefs = listed.attr == "defs" - && std::any_of( listed.rows.begin() + std::ptrdiff_t( hitPage.begin ), listed.rows.begin() + std::ptrdiff_t( hitPage.end ), - [ & ]( std::size_t i ) { return res.hits[ i ].defRefs >= 2; } ); - writeWhereisLegendTail( out, res, sharedDefs ); + if( std::any_of( listed.rows.begin() + std::ptrdiff_t( hitPage.begin ), listed.rows.begin() + std::ptrdiff_t( hitPage.end ), + [ & ]( std::size_t i ) { return !whereisSharedAttr( listed, res.hits[ i ] ).empty(); } ) ) + { + rw::emitRaw( out, kWhereisSharedDefsLegend ); + } + writeWhereisLegendTail( out, res ); std::fputs( "-->", out ); const std::string headDate = whereisHeadDate( res ); writeWhereisRoot( out, res, WhereisPageView{ listed, hitPage, pageLimit, pageOffset }, headDate, ex ); From fc5e58dc82c3298bcb06a88331e9f06290264ac3 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Fri, 9 Oct 2026 21:12:22 -0400 Subject: [PATCH 118/170] fix(K51): the history_unread comment names the walk, not a git command line The disclosure's cause clauses began with a string literal reading "git ...", which githardencheck arm (L) treats as a git invocation spelled outside rw::gitCmd(). The clauses now say "the walk did not run" / "the walk stopped part-way"; the sentence already names the git history walk, so the meaning (churn= absent, amp= a floor) is unchanged. qchurncheck pins the new wording. Co-Authored-By: Claude Opus 5.5 --- src/historyread.h | 4 ++-- test/qchurncheck.sh | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/historyread.h b/src/historyread.h index f5545f2c0..aa75ef315 100644 --- a/src/historyread.h +++ b/src/historyread.h @@ -81,11 +81,11 @@ inline std::string renderHistoryUnread( DiscloseAs as ) std::string comment = ""; diff --git a/test/qchurncheck.sh b/test/qchurncheck.sh index c2ed160d0..41e1d47ad 100755 --- a/test/qchurncheck.sh +++ b/test/qchurncheck.sh @@ -219,7 +219,7 @@ if [ -f "$kgpobj" ] && [ -n "$khealthy" ]; then else no "K51: the prefix churn= is not a floor below the healthy count (prefix='$kpre', healthy=$khealthy)" fi - if grep -q 'history_unread=1: the git history walk.*(git stopped part-way), so churn= and amp= count only the commits that were read: both are floors' "$TMP/k_pre.out"; then + if grep -q 'history_unread=1: the git history walk.*(the walk stopped part-way), so churn= and amp= count only the commits that were read: both are floors' "$TMP/k_pre.out"; then ok "K51: the prefix comment names the cause and says churn=/amp= are floors" else no "K51: the prefix comment does not call churn=/amp= floors: $( grep -o '', text ) + shown = re.search( r'\bshown=(\d+)', hdr.group( 0 ) ) if hdr else None + total = re.search( r'\bsymbols=(\d+)', hdr.group( 0 ) ) if hdr else None keys = set(); cur = "" for m in re.finditer( r'<(f|s) ([^>]*)>', text ): attrs = dict( re.findall( r'([a-z_]+)="([^"]*)"', m.group( 2 ) ) ) if m.group( 1 ) == "f": cur = attrs.get( "p", "" ); continue keys.add( attrs.get( "id" ) or f'{cur}::{attrs.get("t")}::{attrs.get("n")}' ) - return ( int( est.group( 1 ) ) if est else -1 ), keys -bareEst, bare = rows( sys.argv[ 1 ] ) -budEst, bud = rows( sys.argv[ 2 ] ) + return ( int( est.group( 1 ) ) if est else -1 ), keys, ( int( shown.group( 1 ) ) if shown else -1 ), ( int( total.group( 1 ) ) if total else -1 ) +bareEst, bare, _, _ = rows( sys.argv[ 1 ] ) +budEst, bud, shown, total = rows( sys.argv[ 2 ] ) problems = [] -if len( bud ) < 20: problems.append( f"budgeted map has only {len(bud)} rows (presence guard)" ) -if not bud <= bare: problems.append( f"{len(bud - bare)} budgeted row(s) absent from the bare map — not a head, a different ranking" ) -if not 0 < budEst <= 4500: problems.append( f"budgeted est_tokens={budEst}, ceiling 4500" ) -if not bareEst > 4500: problems.append( f"bare est_tokens={bareEst} is already under the 4500 ceiling — the recommendation saves nothing" ) -print( ( "FAIL " + "; ".join( problems ) ) if problems else f"OK bare={bareEst} budgeted={budEst} rows={len(bud)}/{len(bare)}" ) +if len( bud ) < 20: problems.append( f"cut map has only {len(bud)} rows (presence guard)" ) +if not bud <= bare: problems.append( f"{len(bud - bare)} cut-map row(s) absent from the plain map — not a head, a different ranking" ) +if not 0 < budEst < bareEst: problems.append( f"cut est_tokens={budEst} is not below the plain map's {bareEst} — the budget cut nothing" ) +if not ( 0 <= shown < total ): problems.append( f"the cut map's header does not disclose the cut (shown={shown} symbols={total})" ) +print( ( "FAIL " + "; ".join( problems ) ) if problems else f"OK plain={bareEst} cut={budEst} rows={len(bud)}/{len(bare)} shown={shown} of {total}" ) PY )" - case "$verdict" in - OK*) ok "(G4) $budget_flag keeps the bare map's head under the ceiling (${verdict#OK })" ;; - *) no "(G4) $budget_flag does not keep the promise the start-here comment makes: ${verdict#FAIL }" ;; - esac -fi +case "$verdict" in + OK*) ok "(G4) --max-tokens=3000 keeps the plain map's head and discloses the cut (${verdict#OK })" ;; + *) no "(G4) --max-tokens=3000 does not keep the promise the orient comment makes: ${verdict#FAIL }" ;; +esac # ── (H) SUMMARY-LINE NUMBERS ───────────────────────────────────────────────────────────────────── # WHY THIS ARM EXISTS. On 2026-09-07 the lineage section's read "34 repositories, 67 papers From 4f05fced101888f69ddf003b8159c35e9e28a779 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 06:22:11 -0400 Subject: [PATCH 147/170] test(forrootlegendcheck): arm 2's tight budget 850 -> 1000 on the merged tree At 850 the tiny fixture is the 4-row floor with its next= unpaid, labelled at est_tokens=967 in the full legend (lego-charge's charge + #362's tail next=; red on two macOS shards of CI run 38041394072 and locally). Swept 900..1200: 1000 serves six rows at 940 in the full legend (60 tokens of headroom, the dialect this arm measures), 989 at the default. Assertions unchanged. Co-Authored-By: Claude Fable 5.1 --- test/forrootlegendcheck.sh | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/test/forrootlegendcheck.sh b/test/forrootlegendcheck.sh index 88ecb160e..b4b6fe245 100755 --- a/test/forrootlegendcheck.sh +++ b/test/forrootlegendcheck.sh @@ -76,24 +76,30 @@ done # rows the header then cannot pay for: on this very fixture the a4a58141 default (before those definitions) was over its # budget at 870, 930 and 1000 tokens and fit 850 by three tokens. That overshoot is labelled over_ceiling="1" and counted in # the CHANGELOG; the root= clause's own cost is what this arm is about, and the full dialect measures it. -OUT2="$( "$BIN" "$TMP/tiny" --for="widget routine dispatcher" --token-budget=850 --no-cache --legend=full 2>/dev/null )" +# RE-ANCHORED 2026-10-10 (train 26c): 850 -> 1000. lego-charge charges / at their served size and #362's +# rides the bundle, so at 850 this fixture is the 4-row floor with its next= unpaid, labelled over_ceiling="1" +# at est_tokens=967 in the full legend (red on two macOS shards of run 38041394072 and on a 10-core host alike). Swept +# 900..1200 step 25 on the merged binary (full legend / default): 925 serves 5 rows at 892 / 880, 950..975 six rows at 939 / +# 942, 1000 six rows at 940 / 989 — 60 tokens of headroom in the full legend, the dialect this arm measures (the 2026-09-12 +# re-anchor's 36-token posture or better); 1025+ seven rows at 987. The assertions are unchanged. +OUT2="$( "$BIN" "$TMP/tiny" --for="widget routine dispatcher" --token-budget=1000 --no-cache --legend=full 2>/dev/null )" EST2="$( printf '%s' "$OUT2" | grep -o 'est_tokens="[0-9]*"' | head -1 | tr -dc '0-9' )" -if [ -n "$EST2" ] && [ "$EST2" -le 850 ]; then - ok "arm2: --token-budget=850 fits the ceiling in the full legend (est_tokens=$EST2)" +if [ -n "$EST2" ] && [ "$EST2" -le 1000 ]; then + ok "arm2: --token-budget=1000 fits the ceiling in the full legend (est_tokens=$EST2)" else - no "arm2: --token-budget=850 est_tokens=${EST2:-unreadable} exceeds the ceiling — the legend clause is too expensive" + no "arm2: --token-budget=1000 est_tokens=${EST2:-unreadable} exceeds the ceiling — the legend clause is too expensive" fi -OUT2D="$( "$BIN" "$TMP/tiny" --for="widget routine dispatcher" --token-budget=850 --no-cache 2>/dev/null )" +OUT2D="$( "$BIN" "$TMP/tiny" --for="widget routine dispatcher" --token-budget=1000 --no-cache 2>/dev/null )" EST2D="$( printf '%s' "$OUT2D" | grep -o 'est_tokens="[0-9]*"' | head -1 | tr -dc '0-9' )" -if [ -n "$EST2D" ] && { [ "$EST2D" -le 850 ] || printf '%s' "$OUT2D" | head -c 600 | grep -q ' over_ceiling="1"'; }; then - ok "arm2: the default answer at --token-budget=850 fits or says it does not (est_tokens=$EST2D)" +if [ -n "$EST2D" ] && { [ "$EST2D" -le 1000 ] || printf '%s' "$OUT2D" | head -c 600 | grep -q ' over_ceiling="1"'; }; then + ok "arm2: the default answer at --token-budget=1000 fits or says it does not (est_tokens=$EST2D)" else - no "arm2: the default answer at --token-budget=850 is over (est_tokens=${EST2D:-unreadable}) with no over_ceiling label" + no "arm2: the default answer at --token-budget=1000 is over (est_tokens=${EST2D:-unreadable}) with no over_ceiling label" fi if printf '%s' "$OUT2" | grep -qF "$CLAUSE_SNIPPET"; then - ok "arm2: the clause SURVIVES at --token-budget=850 (not dropped by the ceiling ladder)" + ok "arm2: the clause SURVIVES at --token-budget=1000 (not dropped by the ceiling ladder)" else - no "arm2: the clause is missing at --token-budget=850 — it was silently dropped instead of fitting" + no "arm2: the clause is missing at --token-budget=1000 — it was silently dropped instead of fitting" fi if printf '%s' "$OUT2" | xmllint --noout - 2>/dev/null; then ok "arm2: tight-budget output is well-formed (G4)"; else no "arm2: tight-budget output fails xmllint"; fi From fb6cec403a06d945b3dac0f322f0c30b79318d87 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 07:21:49 -0400 Subject: [PATCH 148/170] ingest: definitionLinesInBlobs runs the index's own extraction over in-memory blobs The tags path over blobs read from other git refs (for --whereis): the crawl's language rule with the .h -> ObjC sniff, the size/binary/nesting refusals, the member-macro re-parse, the #if 0 ranges, captureTagsFacts, the markdown sections, then the model build's dedupRawDefs / collapseObjCDeclDefs / partitionFieldDefs, so a blob answers what HEAD's index answers for the same bytes. Parallel over hardware_concurrency(), biggest blob first, one slot per blob; the tags queries a batch needs are installed single-threaded before any worker (callers in a long-lived server hold headSnapshotIngestMutex, like every other ingest). captureTagsFacts gains TagsCapture::DefinitionsOnly: the same definitions without the reference arm, import emission, complexityOf, the function-scope span, the IS-A / HAS-A / macro-body reference minting and Ruby's reference and bind passes (~60% of its samples on 104 C++ header blobs). The Full posture, the index's, is unchanged. Co-Authored-By: Claude Opus 5.5 --- src/ingest.h | 30 ++++++ src/ingest_astquery.h | 241 ++++++++++++++++++++++++++++++++++++++++++ src/ingest_sidecap.h | 30 +++++- 3 files changed, 296 insertions(+), 5 deletions(-) diff --git a/src/ingest.h b/src/ingest.h index 162938b60..8e454dc2a 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -881,4 +881,34 @@ inline bool astQueryNestsTooDeep( std::string_view query ) // local to the re-parsed substring. std::vector collectGatedLocalNames( std::string_view defBytes, std::uint32_t defStartLine, Lang lang ); +// ---- --whereis on other refs: where the TAGS PATH defines one name inside blobs the index never read ---- +// HEAD's rows take kind="def" from the index; a row on another ref comes from a git blob the index never saw. This +// runs the index's own extraction on such a blob — the crawl's extension → grammar rule (with the .h → ObjC content +// reroute), the nesting refusal, the member-macro re-parse, the `#if 0` filter, the language's tags query through +// captureTagsFacts, and the markdown section walk — so a branch definition is labelled by the same captures that +// label HEAD's. `lines` is the 1-based @name line of every definition named `name`, ascending (RawDef::line, which +// is Symbol::line: the line HEAD's relabel keys on). +struct BlobText +{ + std::string_view path; // the tree path: its extension picks the grammar, as in the crawl + std::string_view bytes; // the blob's content +}; +enum class BlobDefsStatus : std::uint8_t +{ + Parsed, // the tags path ran over the whole blob: `lines` is its complete answer for the name + NoGrammar, // no indexed language for this path (the index defines nothing in such a file either) + Unread, // not parsed: binary, over the crawl's default size ceiling, or a nesting refusal (the crawl skips these too) + Failed, // the parser or the tags query could not finish (a null tree, an unavailable query, a partial extract) +}; +struct BlobDefinitions +{ + std::vector lines; + BlobDefsStatus status = BlobDefsStatus::Failed; +}; +// Parallel over hardware_concurrency() workers, biggest blob first (spanTiersOfFiles' pool shape); each blob writes +// only its own slot, so the result is the serial one. The tags queries a batch needs are compiled and installed into +// the process-global query cache BEFORE any worker starts — the single-writer rule ingest() itself follows — so a +// long-lived server must call this under quality::headSnapshotIngestMutex, like every other ingest call site. +std::vector definitionLinesInBlobs( std::span blobs, std::string_view name ); + } // namespace rw diff --git a/src/ingest_astquery.h b/src/ingest_astquery.h index f1c0b8d7b..38cc690c5 100644 --- a/src/ingest_astquery.h +++ b/src/ingest_astquery.h @@ -2143,4 +2143,245 @@ std::vector collectGatedLocalNames( std::string_view defBytes, st return out; } +// ---- definitionLinesInBlobs (ingest.h): the tags path over blobs from other git refs ---- +// +// The DISCLOSE sink of one blob: a degrade sets its slot's status, which is exactly what the caller reads (a blob +// that is not Parsed never confirms a definition, and --whereis says how many such blobs it met). +struct BlobDefsSlot +{ + enum class DisclosureWhy : std::uint8_t + { + NoParser, // ts_parser_new / ts_query_cursor_new gave nothing: the worker parses no blob + GrammarRejected, // the grammar could not be set on the parser (ABI): this blob is not parsed + ParseNull, // tree-sitter returned no tree + ExtractPartial, // the tags pass disclosed a shortfall (ExtractShortfall): its definitions may be incomplete + WorkerThrew, // an exception inside the worker: the slot keeps Failed + }; + BlobDefinitions& slot; + void disclose( DisclosureWhy ) noexcept // every reason records the same fact: no complete answer for this blob + { + slot.lines.clear(); + slot.status = BlobDefsStatus::Failed; + } +}; + +// One blob through the crawl's language rule and the parse-pool worker's extraction steps, in the worker's order. +// `scan` is a one-slot scan the nesting refusal records into (its --skipped bookkeeping has no reader here: the +// slot's Unread status is this blob's disclosure). +inline void definitionLinesOfOneBlob( const BlobText& blob, std::string_view name, TSParser* parser, TSQueryCursor* cursor, + MemberMacroReparse& macroWork, IngestFileScan& scan, BlobDefinitions& slot ) +{ + const std::string ext = lowerExtensionOf( blob.path ); + const LangEntry* le = lookupLang( ext ); + if( le == nullptr ) + { + slot.status = BlobDefsStatus::NoGrammar; + return; + } + if( blob.bytes.size() > kDefaultMaxFileBytes || looksBinary( blob.bytes ) ) + { + slot.status = BlobDefsStatus::Unread; // the crawl's own ceiling and binary sniff: the index skips these too + return; + } + if( le->ext == ".h" && looksObjC( blob.bytes ) ) + { + if( const LangEntry* objcLe = lookupLang( ".m" ) ) + { + le = objcLe; + } + } + const std::string pathText( blob.path ); + scan.nestRefusedBytes[ 0 ] = 0; + if( refuseNesting( *le, blob.bytes, pathText.c_str(), 0, scan ) ) + { + slot.status = BlobDefsStatus::Unread; + return; + } + if( !prepareParserFor( parser, *le ) ) + { + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::GrammarRejected, "whereis: a blob's grammar could not be set — its rows confirm no definition" ); + return; + } + std::vector defs; + std::vector refs; + std::vector binds; + std::vector incs; + if( le->lang == Lang::Markdown ) + { + TreeGuard mdTree( parseTree( parser, blob.bytes ) ); + if( mdTree.get() == nullptr ) + { + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ParseNull, "whereis: a markdown blob did not parse — its rows confirm no definition" ); + return; + } + extractMarkdown( 0, blob.bytes, fs::path( pathText ).stem().string(), ts_tree_root_node( mdTree.get() ), defs, refs ); + } + else + { + IncludedRangeGuard rangeGuard; + const AstroFrontmatter astro = restrictAstroToFrontmatter( parser, *le, blob.bytes, rangeGuard ); + if( astro == AstroFrontmatter::Unterminated ) + { + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ExtractPartial, "whereis: an .astro blob opens a frontmatter fence it never closes — not extracted" ); + return; + } + if( astro == AstroFrontmatter::None ) + { + slot.status = BlobDefsStatus::Parsed; // a template-only .astro: the index extracts nothing from it either + return; + } + TreeGuard tree( parseTree( parser, blob.bytes ) ); + if( tree.get() == nullptr ) + { + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ParseNull, "whereis: a blob did not parse — its rows confirm no definition" ); + return; + } + if( ts_node_has_error( ts_tree_root_node( tree.get() ) ) ) // a clean parse has no error bytes: the re-parse never adopts + { + (void)measureHealthAdoptingMemberMacroReparse( parser, le->lang, blob.bytes, tree, macroWork ); // may adopt it, as the worker does + } + const TSNode root = ts_tree_root_node( tree.get() ); + const std::vector ppDead = preprocDeadRangesFor( *le, root, blob.bytes ); + ExtractShortfall shortfall; + captureTagsFacts( cursor, *le, 0, blob.bytes, root, defs, refs, binds, incs, ppDead, shortfall, TagsCapture::DefinitionsOnly ); + if( shortfall.isShort ) + { + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ExtractPartial, "whereis: the tags pass over a blob was partial — its rows confirm no definition" ); + return; + } + } + // The model build's steps that decide which captured definitions become SYMBOLS (ingest.cpp 3a..3a-ter), so a + // blob answers what HEAD's index answers for the same bytes: overlapping patterns collapse to one definition, an + // ObjC @interface declaration shadowed by its same-file @implementation drops, and a field leaves the symbol + // universe (fields are not Symbols, so on HEAD a field line is never a def site either). + dedupRawDefs( defs ); + collapseObjCDeclDefs( defs ); + (void)partitionFieldDefs( defs ); + for( const RawDef& d : defs ) + { + if( d.name == name ) + { + slot.lines.push_back( d.line ); + } + } + std::sort( slot.lines.begin(), slot.lines.end() ); + slot.status = BlobDefsStatus::Parsed; +} + +std::vector definitionLinesInBlobs( std::span blobs, std::string_view name ) +{ + std::vector out( blobs.size() ); + if( blobs.empty() ) + { + return out; + } + warmFieldIdTable(); // idempotent; the AST walks behind captureTagsFacts read it lock-free + + // The query set, single-threaded and BEFORE any worker: every grammar a blob can reach — its extension's, plus + // ObjC's for a .h (the content reroute is decided inside the worker). compiledQueryFor only ever READS the cache. + { + std::vector want; + const auto need = [ & ]( const LangEntry* le ) + { + if( le == nullptr || le->grammar == nullptr || le->querySub.empty() ) + { + return; + } + const TSLanguage* g = le->grammar(); + if( compiledQueryCache().find( g ) != compiledQueryCache().end() ) + { + return; + } + if( std::none_of( want.begin(), want.end(), [ & ]( const LangEntry* w ) { return w->grammar() == g; } ) ) + { + want.push_back( le ); + } + }; + for( const BlobText& b : blobs ) + { + const std::string ext = lowerExtensionOf( b.path ); + need( lookupLang( ext ) ); + if( ext == ".h" ) + { + need( lookupLang( ".m" ) ); + } + } + for( const LangEntry* le : want ) + { + if( TSQuery* q = compileQueryStandalone( *le ); q != nullptr ) // a compile error stays absent: captureTagsFacts discloses it + { + compiledQueryCache()[ le->grammar() ] = q; // the cache owns it from here (CompiledQueryCache frees it at teardown) + } + } + } + + std::vector walkOrder( blobs.size() ); + std::iota( walkOrder.begin(), walkOrder.end(), std::uint32_t( 0 ) ); + std::stable_sort( walkOrder.begin(), walkOrder.end(), [ & ]( std::uint32_t a, std::uint32_t b ) noexcept + { return blobs[ a ].bytes.size() > blobs[ b ].bytes.size(); } ); // biggest first: no straggler at the tail + + unsigned hw = std::thread::hardware_concurrency(); + if( hw == 0 ) + { + hw = 1; + } + const unsigned threadCount = static_cast( std::min( hw, blobs.size() ) ); + std::atomic nextSlot{ 0 }; + const auto worker = [ & ]() noexcept + { + ParserGuard pg; + TSQueryCursor* cursor = ts_query_cursor_new(); + if( pg.p == nullptr || cursor == nullptr ) + { + for( std::size_t s = nextSlot.fetch_add( 1, std::memory_order_relaxed ); s < blobs.size(); s = nextSlot.fetch_add( 1, std::memory_order_relaxed ) ) + { + DISCLOSE( ( BlobDefsSlot{ out[ walkOrder[ s ] ] } ), BlobDefsSlot::DisclosureWhy::NoParser, "whereis: no tree-sitter parser for a blob worker" ); + } + if( cursor != nullptr ) + { + ts_query_cursor_delete( cursor ); + } + return; + } + MemberMacroReparse macroWork; + IngestFileScan scan = makeFileScan( std::vector{ std::string() } ); + for( ;; ) + { + const std::size_t s = nextSlot.fetch_add( 1, std::memory_order_relaxed ); + if( s >= blobs.size() ) + { + break; + } + BlobDefinitions& slot = out[ walkOrder[ s ] ]; + try + { + definitionLinesOfOneBlob( blobs[ walkOrder[ s ] ], name, pg.p, cursor, macroWork, scan, slot ); + } + catch( ... ) // a throw escaping a worker thread is std::terminate: the blob degrades instead + { + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::WorkerThrew, "whereis: a blob worker threw — that blob confirms no definition" ); + } + } + ts_query_cursor_delete( cursor ); + }; + if( threadCount <= 1 ) + { + worker(); + } + else + { + std::vector pool; + pool.reserve( threadCount ); + for( unsigned t = 0; t < threadCount; ++t ) + { + pool.emplace_back( worker ); + } + for( std::thread& w : pool ) + { + w.join(); + } + } + return out; +} + } // namespace rw — ingest_astquery.h section of ingest.cpp diff --git a/src/ingest_sidecap.h b/src/ingest_sidecap.h index 6ea1140e7..d9a920ff7 100644 --- a/src/ingest_sidecap.h +++ b/src/ingest_sidecap.h @@ -1725,6 +1725,14 @@ inline bool isCFamilyTypeSpecifier( Lang lang, TSNode node ) noexcept return kindIs( t, "enum_specifier" ) || kindIs( t, "struct_specifier" ) || kindIs( t, "union_specifier" ) || kindIs( t, "class_specifier" ); } +// The two postures of one tags pass. Full: every fact the index keeps. DefinitionsOnly (definitionLinesInBlobs, --whereis on +// other refs): the SAME definitions — every step that decides a definition's existence, name, line, kind or scope runs +// unchanged — without the work only references, imports and metrics read: the @reference arm, @import emission, +// complexityOf, the function-scope span, the def arm's IS-A / HAS-A / macro-body reference minting and Ruby's +// reference and bind passes. Measured on 104 C++ header blobs: those were ~60% of this function's samples (cc_walk alone +// ~30%, the reference arm's qualifier / receiver parent walks ~30%). +enum class TagsCapture : std::uint8_t { Full, DefinitionsOnly }; + /// Append definitions and references captured by the language query, with language-specific filtering. /// Captured spans refer to src and root; a null cursor appends nothing. Existing output rows are retained. /// `ppDead` is this file's decided-dead byte ranges (preprocDeadRangesFor, computed ONCE per file by the @@ -1732,8 +1740,9 @@ inline bool isCFamilyTypeSpecifier( Lang lang, TSNode node ) noexcept /// file's tags pass runs later, at the prewarm flush). void captureTagsFacts( TSQueryCursor* cursor, const LangEntry& le, std::uint32_t fileId, std::string_view src, TSNode root, std::vector& defs, std::vector& refs, std::vector& binds, std::vector& includes, - const std::vector& ppDead, ExtractShortfall& shortfall ) + const std::vector& ppDead, ExtractShortfall& shortfall, TagsCapture mode = TagsCapture::Full ) { + const bool full = mode == TagsCapture::Full; if( cursor == nullptr ) { return; @@ -1860,7 +1869,10 @@ void captureTagsFacts( TSQueryCursor* cursor, const LangEntry& le, std::uint32_t // order while costing an allocation and a drain block. case CapRole::Import: { - emitCapturedImport( cap.node, fileId, le.lang, src, includes, refs, shortfall ); + if( full ) + { + emitCapturedImport( cap.node, fileId, le.lang, src, includes, refs, shortfall ); + } } break; } @@ -2101,7 +2113,7 @@ void captureTagsFacts( TSQueryCursor* cursor, const LangEntry& le, std::uint32_t // A literal-bound name measures its LITERAL; every other def measures defNode, as before. const bool literalBound = !ts_node_is_null( own.literal ); const TSNode metricNode = literalBound ? own.literal : defNode; - const auto [ cxVal, ccxVal, nestVal, localsVal, ppAltVal, humpsVal, deepVal, evVal, evWhyVal ] = fnOrMethod ? complexityOf( spanThroughBody ? body : metricNode, src, le.lang ) : Complexity{ 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, {} }; + const auto [ cxVal, ccxVal, nestVal, localsVal, ppAltVal, humpsVal, deepVal, evVal, evWhyVal ] = fnOrMethod && full ? complexityOf( spanThroughBody ? body : metricNode, src, le.lang ) : Complexity{ 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, {} }; d.cx = cxVal; d.ccx = ccxVal; d.locals = localsVal; // Phase 1: floor count, C/C++ only (model.h localsCountedLang) — 0 elsewhere, never emitted there @@ -2176,7 +2188,7 @@ void captureTagsFacts( TSQueryCursor* cursor, const LangEntry& le, std::uint32_t d.internalLinkage = internalLinkageBit( le.lang, defNode, src ); // A FUNCTION bound inside another function's body records that function's span (ingest_names.h // enclosingFunctionScope): graph.h reachableByName ranks it below every def a call outside it can name. - if( kind == SymKind::Function && !bindsOutsideItsFunction( roleNode, le.lang ) ) + if( full && kind == SymKind::Function && !bindsOutsideItsFunction( roleNode, le.lang ) ) { // Descend to the OUTER of the two def nodes: a C-family def's defNode climbed from its declarator (the role // node) to the function_definition, which must not read as the function enclosing itself; a multi-name @@ -2261,6 +2273,10 @@ void captureTagsFacts( TSQueryCursor* cursor, const LangEntry& le, std::uint32_t // holds an error (fileHasError, one O(1) flag test per file) — see parseRecoveredBits. d.recovered = fileHasError ? parseRecoveredBits( defNode, kind, le.lang, d.scope.empty() ) : std::uint8_t( 0 ); defs.push_back( std::move( d ) ); + if( !full ) + { + continue; // TagsCapture::DefinitionsOnly: the reference edges below feed no definition + } if( kind == SymKind::Class || isStructOrNamedType( kind ) || kind == SymKind::Interface ) { captureBases( defNode, fileId, le.lang, src, refs ); // IS-A: inheritance edges (derived → base) @@ -2271,7 +2287,7 @@ void captureTagsFacts( TSQueryCursor* cursor, const LangEntry& le, std::uint32_t captureMacroBodyCalls( roleNode, fileId, le.lang, src, refs ); // macro-edges: the graph connects THROUGH the macro } } - else if( isRef ) + else if( isRef && full ) { // H4: a C++ cast keyword is not a call — see isCppCastKeyword. Valid input, skipped, no alert. if( le.lang == Lang::Cpp && isCppCastKeyword( nameTxt ) ) @@ -2427,6 +2443,10 @@ void captureTagsFacts( TSQueryCursor* cursor, const LangEntry& le, std::uint32_t if( le.lang == Lang::Ruby ) { captureRubyAttrDefs( root, fileId, src, defs ); + if( !full ) + { + return; // TagsCapture::DefinitionsOnly: the passes below write references and binds only + } captureRubyBareCalls( root, fileId, src, refs ); captureRubyFactories( root, fileId, src, binds ); captureRubyHelperMethods( root, fileId, src, binds ); From 319648f2235dd14c9fa6278389691af9e94b1da7 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 07:21:49 -0400 Subject: [PATCH 149/170] whereis: another ref's kind="def" comes from a parser; a runaway guard discloses what it leaves; refs read from tree objects Round 1 labelled a non-HEAD row with a stricter lexical reading of its line, which lost real definitions (#define NAME(, decltype(auto) NAME(, @Test(...) public void NAME(), #[derive(Debug)] struct NAME) while the looser base reading kept calls (go f(a), @f(1), f( a, [&]( int x ) {). Now (labelBranchRowsByParse) each distinct (blob, path) behind such rows is labelled the way HEAD's rows are: HEAD's own blob at the same path takes HEAD's index labels; any other blob holding a parse-worthy line is parsed (definitionLinesInBlobs) and each definition of the name promotes the nearest row in relabelHeadHitsFromIndex's window. The lexical test only chooses blobs (parseWorthyLine, a looser superset of definitionShaped, which is restored to its 93aeab1d form for HEAD's lexical fallback). A runaway guard (256 blobs / 64 MB, most rows first; --detail=N lifts it) bounds the parse; a blob it leaves or the parser cannot finish confirms nothing: its parse-worthy rows read kind="text" (listed and folded on the defs page, never def) and counts them. ref_labels="parsed" rides any answer with another ref's row; the legend, compact readings, --help, the MCP description (701 characters kept) and the change-check skill say so. The "shown + more + folded + refs = hits" sentence now says "on the first page". Speed: every ref's tree comes from content-addressed tree objects, one git cat-file --batch per tree level (listTreesOfRefs, ls-tree's own order and quoting; a root below the work-tree top keeps ls-tree), the blob stream is read through four cat-file processes (streamBlobsInSlices) with each blob scanned once on its worker. --whereis=escapeXml on this repo (162 refs): 7.8-9.2 s at load 24-29, base 93aeab1d ~40-60 s at load 23-30. Arms: crossrefcheck 5c (a well-formed file per language family, branch == marker == HEAD's index on every marked line; the C1 shapes; the branch-only #define / annotated Java method; ref_labels; CLI = MCP), 5d (the guard), 5e (tree-walk spelling against ls-tree); 5b and the B8.2 twin restated with their reasons (rule 4); selectorhonestycheck A7's doc residual restated; rv nits N1/N2/N3. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 59 +- skills/ripwire-change-check/SKILL.md | 5 +- src/cli.h | 17 +- src/compactlegend.h | 12 +- src/crossref.h | 1008 ++++++++++++++++++++------ src/mcp.h | 2 +- src/verbs_change.h | 4 +- test/crossrefcheck.sh | 418 ++++++++++- test/mcpmanifestcheck.sh | 3 + test/pagingsweepcheck.sh | 7 +- test/selectorhonestycheck.sh | 24 +- 11 files changed, 1261 insertions(+), 298 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a00b0936e..5de0ba598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,31 +15,40 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] -### Changed — `--whereis` labels a branch row `kind="def"` only for a definition's shape, lists a line several refs share once, and scans refs in parallel - -Measured on this repository (153 local refs), `--whereis=escapeXml`, warm, base and this build run turn about on the same -loaded machine: the definitions page went from 60 rows (`total="2113"`, 59 of them call sites such as -`if( !task.empty() ) { out += escapeXml( task, esc ); }` labelled `kind="def"`) to 2 rows, both the declaration; wall -20.9–21.1 s → 10.1–10.8 s. Two repositories with no local refs answer byte for byte as before. - -- **The lexical shape test reads the whole line.** On a non-HEAD ref (and on HEAD when `head_labels="lexical"`) a line is - `kind="def"` only when the text before the name is a declarator prefix — no operator, brace, paren, quote or statement - keyword (`return`, `if`, `await`, `match`…) — and what follows the parameter list is a definition's tail (`{`, an init - list, `->`, a qualifier, the end of the line), not a call's (`;`, `,`, `)`, `.`, `do`…). A declaration marker (`def `, - `fn `, `impl `…) counts only when it heads the declaration, never from inside a string literal or a comment, and - `using ns::name;` is a reference. What stays a floor: a bare `name( args )` alone on its line is a wrapped signature - in C and a semicolon-free call in JS or Python, and one line cannot tell them apart. Language-neutral (one heuristic - serves every ref blob); probed on C++, Python, JS, Ruby and shell shapes. -- **A definition line several refs hold is one row.** Under `listing="defs"` a `kind="def"` row outside the checkout whose - path and line text other refs hold too is printed once, for the first of those refs by name, with `refs="N"`; the root's - `folded=` counts the rows it stands for (`shown + more + folded + refs count = hits`). `hits=` still counts every copy and - `--whereis-listing=all` prints each ref's own row. A ref holding the same text at two lines of one path is two rows. -- **Faster, with no ref cut.** The per-ref `git ls-tree -r` runs through the stray-content sweep's pool (7.0 s → 2.1 s - serial vs parallel on 153 refs), and the blob walk jumps from occurrence to occurrence instead of reading every line. - `complete=` keeps its meaning: a tree a worker never listed forfeits it like an empty one. - -Gates: `test/crossrefcheck.sh` (5b: the shapes and the fold, red on the previous build), `test/pagingsweepcheck.sh` (its -fixture's branches now define the symbol differently each, so the definitions page still pages). +### Changed — `--whereis` labels another ref's row by parsing its blob, lists a line several refs share once, and reads refs from tree objects + +A row on another ref used to take `kind="def"` from a lexical reading of its one line, and any such reading errs both ways: +a looser one called call sites definitions (`if( !task.empty() ) { out += escapeXml( task, esc ); }`, `go f(a)`, `@f(1)`), +a stricter one lost real definitions (`#define NAME(`, `decltype(auto) NAME(`, `@Test(…) public void NAME()`, +`#[derive(Debug)] struct NAME`). Measured on this repository (162 local refs), `--whereis=escapeXml`: the default page +went from 60 rows of `total="2197"` (54 of them call sites) to 2 rows, both the declaration. + +- **Another ref's `kind="def"` comes from a parser.** A ref holding HEAD's own blob of a path takes HEAD's index labels, + line for line. Any other blob is parsed once per distinct blob and path by the index's own extraction — the same + language rule, tags query and symbol model — and a definition it captures for the name labels the nearest row the way + HEAD's index labels HEAD's. Only a blob holding a line a loose lexical test calls worth parsing is parsed; a blob + without one reads `kind="ref"`. The root says `ref_labels="parsed"` whenever another ref's row is in the answer. + Arms: one well-formed file per language family (C, C++, ObjC, Python, TypeScript, JavaScript, Java, Ruby, Go, Rust, + Swift, C#, Bash, Markdown) whose every marked line reads as its marker says and as HEAD's index reads the same bytes; + the twelve definition shapes the stricter reading lost; a branch-only `#define` and annotated Java method. +- **A runaway guard on that parse, disclosed.** At most 256 blobs or 64 MB per answer, most rows first. A blob the guard + leaves, or one the parser cannot finish, confirms nothing: its parse-worthy lines read `kind="text"` (listed beside the + definitions, never `kind="def"`) and an `` element counts them; `--detail=1` lifts the guard. +- **A definition line several refs hold is one row.** Under `listing="defs"` a `kind="def"` (or `kind="text"`) row outside + the checkout whose path and line text other refs hold too is printed once, for the first of those refs by name, with + `refs="N"`; the root's `folded=` counts the rows it stands for (on the first page, `shown + more + folded + refs count = + hits`). `hits=` still counts every copy and `--whereis-listing=all` prints each ref's own row. +- **Faster.** Every ref's tree is read from git's content-addressed tree objects, each distinct tree once and a level at a + time through one `git cat-file --batch`, instead of one `git ls-tree -r` process per ref (the same rows in ls-tree's + order and spelling — an arm compares quoted and unquoted names with ls-tree itself); a blob several refs share is + scanned once. + +What stays a floor: a definition the loose lexical test misses (a signature wrapped before its parameter list closes, a +name alone on the line under its return type) is a counted ref when nothing else in its blob earns the blob a parse. +MCP `whereis` takes no `detail` argument, so there the guard's `next=` names the CLI flag. Its description keeps its 701 +characters, the sentence calling a branch row's label lexical replaced by the parser one, and the +`tools/list` manifest grows 46,930 → 46,925 B, a 5 B shrink (the dropped em dash is six bytes of JSON). Gates: `test/crossrefcheck.sh` (5b–5e), +`test/selectorhonestycheck.sh` (§A7), `test/pagingsweepcheck.sh`. ### Changed — a call bound by name alone keeps its rows, marked `via="name"`; typed receivers resolve diff --git a/skills/ripwire-change-check/SKILL.md b/skills/ripwire-change-check/SKILL.md index 957e79ebc..9cd43fb11 100644 --- a/skills/ripwire-change-check/SKILL.md +++ b/skills/ripwire-change-check/SKILL.md @@ -146,8 +146,9 @@ emits a flat `` top-K — identity + score + signatu 9c. **"Where does this content live?"** — `ripwire --whereis=SYM --legend=compact`. Which ref's tree defines or mentions a symbol, HEAD first; `on-head="0"` alongside branch hits is content that exists ONLY on a branch. Each distinct blob is read once (git is content-addressed), so 30 branches cost about one tree. `kind="def"` - on a branch row is a lexical heuristic — branch blobs are raw text, never ingested; for HEAD's parsed - answer use `--expand`/`--callers`. By default only the `kind="def"` rows are listed and the mentions are + is a parser's answer on every row — HEAD's from the index, a branch row's from the same extraction over that + branch's blob (only blobs holding a line a loose lexical test calls worth parsing are parsed; `kind="text"` is + such a line no parse read, never a definition). By default only the `kind="def"` rows are listed and the mentions are counted in one `` row (its `next=` lists them) when that page lists MORE definitions than the all page does under the same row cap (a capped all page can list fewer), or the same ones in strictly fewer bytes; otherwise, and with `--whereis-listing=all`, every row is listed. diff --git a/src/cli.h b/src/cli.h index 2d905d3ca..38e2e862a 100644 --- a/src/cli.h +++ b/src/cli.h @@ -2103,10 +2103,13 @@ inline constexpr char kHelpTail[] = " which REF's tree defines or mentions SYM — HEAD first, then every local branch, with\n" " on-head=\"0\" naming the case the verb exists for: content that lives only on a branch.\n" " Each distinct blob is read ONCE (content-addressed), so N branches cost ~one tree.\n" - " kind=\"def\" on a HEAD row is the PARSED index's answer (head_labels=\"index\"); on a REF row it\n" - " is a LEXICAL heuristic — ref blobs are raw text, never ingested, so a doc quoting a signature\n" - " still reads as a definition. head_labels=\"lexical\" ⇒ HEAD fell back to that heuristic too (no\n" - " indexed def of the name, or a working tree that drifted from HEAD). refs_scanned= is the SCAN\n" + " kind=\"def\" on a HEAD row is the PARSED index's answer (head_labels=\"index\"); on another\n" + " ref's row (ref_labels=\"parsed\") it is the same extraction run over that ref's blob, once per\n" + " blob and path, and only for a blob holding a line a loose lexical test calls worth parsing\n" + " (it only CHOOSES blobs): a blob without one reads kind=\"ref\". kind=\"text\" is such a line\n" + " no parse read ( counts them; --detail=1 lifts the parse guard), never a def.\n" + " head_labels=\"lexical\" ⇒ HEAD fell back to that lexical test (no indexed def of the name, or\n" + " a working tree that drifted from HEAD). refs_scanned= is the SCAN\n" " denominator (refs read besides HEAD), not a matched count. Read-only; single-root only.\n" " A checkout that differs from HEAD is read from disk: each changed path's rows say\n" " ref=\"worktree\" and replace HEAD's, at= gains +dirty, and worktree= says whether every one was read.\n" @@ -2115,17 +2118,17 @@ inline constexpr char kHelpTail[] = " is invisible. Add --with-history: a row then says v=\"never\" or v=\"removed\"\n" " with the commit, date and file that removed it. Remote-tracking refs are excluded\n" " (they mirror local ones); refs are capped, narrow with --stray-content=SUBSTR.\n" - " LISTING: by default only the kind=\"def\" rows are listed and the kind=\"ref\" rows\n" + " LISTING: by default only the kind=\"def\" (and kind=\"text\") rows are listed and the kind=\"ref\" rows\n" " counted in one element, when that page lists MORE definitions\n" " than the --whereis-listing=all page under the row cap, or the same ones in strictly\n" " fewer bytes; otherwise every hit is listed. On that page a definition line several refs\n" " hold (same path, same text) is ONE row with refs=\"N\", and folded= on the root counts\n" - " the rows it stands for (shown + more + folded + refs count = hits); listing=all prints each.\n" + " the rows it stands for (on the first page, shown + more + folded + refs count = hits); listing=all prints each.\n" " --whereis-listing=WHICH with --whereis: which rows to list, defs, refs or all (default: more defs, else shorter).\n" " The default lists defs when that page lists MORE definitions than all under the same\n" " row cap (references can fill a capped all page before the branch definitions arrive),\n" " whatever its bytes; when both list the same definitions, defs only when strictly shorter\n" - " in bytes, else all (a tie lists all). defs lists every kind=\"def\" row and COUNTS\n" + " in bytes, else all (a tie lists all). defs lists every kind=\"def\" (and kind=\"text\") row and COUNTS\n" " the kind=\"ref\" rows in one element whose next= lists exactly\n" " them (refs). kind=\"def\" is the parser's label: a definition it does not model\n" " (define_method, setattr, a name bound by assignment) is a counted ref. With no ref\n" diff --git a/src/compactlegend.h b/src/compactlegend.h index 40842e6c8..285efb0eb 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -938,7 +938,11 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = // whereis: src/crossref.h writeWhereisPage (root emit, trailing ) { "hits", "hits=N: occurrences in HEAD plus every scanned local ref's full tree (the total rows)", false, "whereis", MapHeaderRead::No, {}, "whereis" }, { "on-head", "on-head=1|0: whether HEAD's tree holds it; 0 beside hits = it lives only on a branch", false, "whereis", MapHeaderRead::No, {}, "whereis" }, - { "head_labels", "head_labels=index: HEAD kind= from the parsed index; lexical: text heuristic (non-HEAD rows always are)", false, "whereis", MapHeaderRead::No, {}, "whereis" }, + { "head_labels", "head_labels=index: HEAD kind= from the parsed index; lexical: text heuristic", false, "whereis", MapHeaderRead::No, {}, "whereis" }, + // fix round 2 of the whereis-defs lane (crossref.h labelBranchRowsByParse): present only when a row of another ref is. + { "ref_labels", "ref_labels=parsed: another ref's kind= from the index's own tags parse of its blob (one per blob and path; HEAD's own blob takes HEAD's labels); a blob with no line a loose lexical test calls worth parsing is not parsed and its rows read ref; kind=text: such a line no parse read, never a def", false, "whereis", MapHeaderRead::No, {}, "whereis" }, + { "blobs", "unparsed blobs=N: other refs' blobs holding a line worth parsing that no parse read (past the runaway guard, or the parser could not finish); those lines read kind=text; next= lifts the guard", true, "unparsed", MapHeaderRead::No, {}, "whereis" }, + { "rows", "unparsed rows=N: the kind=text rows those blobs gave", true, "unparsed", MapHeaderRead::No, {}, "whereis" }, // 2026-10-01 (selectorrefuse.h writeNotFoundAnswer): the not-found ANSWER the five answering verbs print beside exit 1. { "found", "found=0: no indexed definition matched the selector, nothing listed or counted (zero = none found, not none exists); exit stays 1", false, "callers", MapHeaderRead::No, {}, "callers" }, { "near", "near=: the indexed name to retry with", false, "callers", MapHeaderRead::No, {}, "callers" }, @@ -958,13 +962,13 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = { "missing", "missing=from|to|both: the endpoint(s) that matched nothing; near= retries the first", false, "path", MapHeaderRead::No, {}, "path" }, // 2026-10-01 (crossref.h demoteTestLocalDefs): present only when an answer holds production AND test-local defs. { "test_local", "hit test_local=1: a definition in a test scope or under a test/bench/fixture path, ordered after the production definitions (only when both exist; nothing dropped)", true, "hit", MapHeaderRead::No, {}, "whereis" }, - { "refs", "hit refs=N: N scanned refs hold this same definition line (path + text); printed once for the first by name, whose ref= tip= date= l= it carries; defs page only, absent = one ref; hits= counts every copy, listing=all prints each", true, "hit", MapHeaderRead::No, {}, "whereis" }, - { "folded", "folded=N: def rows of other refs a listed row's refs= stands for; shown + more + folded + refs count = hits", false, "whereis", MapHeaderRead::No, {}, "whereis" }, + { "refs", "hit refs=N: N scanned refs hold this same definition (or kind=text) line (path + text); printed once for the first by name, whose ref= tip= date= l= it carries; defs page only, absent = one ref; hits= counts every copy, listing=all prints each", true, "hit", MapHeaderRead::No, {}, "whereis" }, + { "folded", "folded=N: def (or text) rows of other refs a listed row's refs= stands for; shown + more + folded + refs count = hits on the first page", false, "whereis", MapHeaderRead::No, {}, "whereis" }, // 2026-10-01 freshness fix (crossref.h scanWorktree): present only on a checkout that differs from HEAD. { "worktree", "worktree=read|partial|unlisted: the checkout differs from HEAD (at= +dirty); each changed path is read from disk as ref=\"worktree\" rows replacing HEAD's, and on-head=/hits=/head_labels= count them; on-head= then reads the checkout, not HEAD's commit; partial = some changed path unreadable or a directory (nested repo, submodule), its HEAD rows may be stale; unlisted = git could not list the changes; either withholds complete=", false, "whereis", MapHeaderRead::No, {}, "whereis" }, { "hits", "more hits=N: rows after this page; page on with offset=next_offset", true, "more", MapHeaderRead::No, {}, "whereis" }, // lean-answers lane (crossref.h WhereisListing / the tip-date hoist): each rides only the answer that carries it. - { "listing", "listing=defs|refs: only those kind= rows listed; under defs counts the kind=ref rows and next= lists them; the window counts listed rows; default: defs if it lists more defs than all, else if shorter; a def the parser does not model (define_method, setattr, assignment) is a counted ref", false, "whereis", MapHeaderRead::No, {}, "whereis" }, + { "listing", "listing=defs|refs: only those kind= rows listed (defs: def and text); under defs counts the kind=ref rows and next= lists them; the window counts listed rows; default: defs if it lists more defs than all, else if shorter; a def the parser does not model (define_method, setattr, assignment) is a counted ref", false, "whereis", MapHeaderRead::No, {}, "whereis" }, { "head_date", "head_date=: a hit without tip= date= has tip= at=, date= this", false, "whereis", MapHeaderRead::No, {}, "whereis" }, // the GREY ZONE of the same sweep: attributes the compact prose named in passing ("in/out, cx/ccx", " groups") but never // DEFINED as name= — legendcoveragecheck's default rows hold the definitional predicate, so each gets its reading here. diff --git a/src/crossref.h b/src/crossref.h index 74584482c..e85b686af 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -103,6 +103,7 @@ #include "filter.h" // §P11.5: rw::pathTierOf — the shared source/test/doc ORDERING tier #include "infra/Diagnostics.h" // ASSUME / DISCLOSE #include "infra/sortutil.h" // svLess — the rename probe orders names in byte order, never via string_view operator< +#include "ingest.h" // definitionLinesInBlobs — the tags path over another ref's blob (labelBranchRowsByParse) #include "btree.hpp" // gtl::btree_map — sorted iteration (house rule: never std::map) @@ -113,6 +114,7 @@ #include #include // the worktree overlay: symlink_status / read_symlink / file_size on a changed path #include +#include // labelBranchRowsByParse holds quality::headSnapshotIngestMutex around the blob parse #include #include #include @@ -143,6 +145,13 @@ constexpr std::uint32_t kMaxRefs = 512; // refusal bound — a swee constexpr std::size_t kStrayFilesPerRef = 12; constexpr std::size_t kWhereisHits = 60; +// --whereis: the RUNAWAY GUARD on the tags parse of other refs' blobs (labelBranchRowsByParse; --detail=N lifts it). Only a +// blob holding a parse-worthy row (parseWorthyLine) is parsed, one parse per distinct (blob, path): ripwire's own most-called helper +// across 162 refs needs 104 parses and 43 MB (~1.7 s at load 20 on an M2 Pro); a typical name, a handful. Hitting +// either bound is disclosed on the answer (), and the blobs it leaves confirm no definition. +constexpr std::size_t kWhereisParseMaxBlobs = 256; +constexpr std::size_t kWhereisParseMaxBytes = std::size_t( 64 ) << 20; + // WHICH ROWS a --whereis answer lists (--whereis-listing=, MCP `listing`). A where-defined question is answered by // the kind="def" rows; the kind="ref" rows (8-59 per answer on the comparison-table cells, ~8 KB) supplied no gold // item there. So the default lists the definitions and COUNTS the references in one element whose @@ -519,7 +528,9 @@ inline void streamBlobs( const std::string& root, const std::vector return; } - const std::string listPath = quality::cacheDirLadder() + "/ripwire-crossref-" + std::to_string( os::getpid() ) + ".shas"; + static std::atomic listSeq{ 0 }; // concurrent batches (streamBlobsInSlices) each own their list file + const std::string listPath = quality::cacheDirLadder() + "/ripwire-crossref-" + std::to_string( os::getpid() ) + "-" + + std::to_string( listSeq.fetch_add( 1, std::memory_order_relaxed ) ) + ".shas"; { std::FILE* lf = std::fopen( listPath.c_str(), "wb" ); if( !lf ) @@ -942,6 +953,48 @@ inline void parallelIndexed( std::size_t count, Body body, ParallelSweep& sweep } } +// ── one blob stream through several git processes (streamBlobsInSlices) ────────────────────────────────────── +// streamBlobs reads every blob through ONE `git cat-file --batch` pipe. On ripwire's 162 refs that is ~900 MB, and under +// load the pipe's ping-pong, not git's decompression, was the larger cost: 1.9 s for git writing to /dev/null against +// 5.9 s through `| cat` at load 28, and ~9 s of a --whereis at load 20-25 spent waiting in fread. This reads `slices` +// contiguous slices of the sha list through as many processes at once (parallelIndexed's pool). `onBlob( index, sha, +// bytes, isText )` runs on the slice's worker thread, once per served sha, so it may write only slot `index` of its own +// storage. The stats merge as one batch's would: counts add, and a slice that failed, was cut short or was never reached +// withholds every completeness claim. +constexpr std::size_t kWhereisStreamSlices = 4; + +template +inline void streamBlobsInSlices( const std::string& root, const std::vector& shas, std::size_t slices, OnBlob onBlob, + StreamBlobStats& stats ) +{ + slices = std::max( 1, std::min( slices, shas.size() ) ); + std::vector sliceStats( slices ); + ParallelSweep sweep; + parallelIndexed( slices, [ & ]( std::size_t t ) + { + const std::size_t begin = shas.size() * t / slices; + const std::size_t end = shas.size() * ( t + 1 ) / slices; + const std::vector part( shas.begin() + std::ptrdiff_t( begin ), shas.begin() + std::ptrdiff_t( end ) ); + std::size_t next = begin; // streamBlobs serves its list in order, one call per served sha + streamBlobs( root, part, [ & ]( const std::string& sha, std::string_view bytes, bool isText ) { onBlob( next++, sha, bytes, isText ); }, + &sliceStats[ t ] ); + }, sweep ); + for( std::size_t t = 0; t < slices; ++t ) + { + const StreamBlobStats& st = sliceStats[ t ]; + stats.missing += st.missing; + stats.nonBlob += st.nonBlob; + stats.oversized += st.oversized; + stats.binary += st.binary; + stats.endedEarly = stats.endedEarly || st.endedEarly; + stats.startFailed = stats.startFailed || st.startFailed; + if( !sweep.isDone( t ) ) + { + DISCLOSE( stats, StreamBlobStats::DisclosureWhy::BatchNotStarted, "crossref: a blob-stream slice was never read — completeness is withheld" ); + } + } +} + // The DISTINCT (a,b) object-name pairs the sweep needs diffed, and their rows. Same reasoning the BlobStore // rests on: a diff between two IMMUTABLE object names is a pure function of that pair, so asking twice can // only ever spend a process for an answer already held. It pays because the sweep genuinely repeats itself — @@ -1391,10 +1444,14 @@ struct WhereHit std::string date; std::string path; std::uint32_t line = 0; - bool isDef = false; // LEXICAL definition heuristic — see definitionShaped() + bool isDef = false; // kind="def": HEAD's from the index (or, head_labels="lexical", definitionShaped); another + // ref's from the tags parse of its blob (labelBranchRowsByParse) std::string text; // the trimmed source line (evidence, so the caller can judge) bool fromWorktree = false; // read from the WORKING-TREE copy of a path that differs from HEAD (ref="worktree") bool testLocal = false; // a TEST-LOCAL definition, demoted below the production ones (test_local="1") + bool unconfirmed = false; // kind="text": another ref's PARSE-WORTHY row whose blob was not parsed (the + // runaway guard, or a parse that could not finish) — never kind="def", never a ref + bool parseWorthy = false; // parseWorthyLine(): this line makes its blob worth a tags parse (another ref's rows) // foldSharedBranchDefs: a kind="def" row outside the checkout whose (path, line text) other refs hold too is ONE // definition held by N refs. The first row of such a group (lowest ref name) is its representative and carries // defRefs = N (1 = this ref alone); every later row of the group is foldedDef and the definitions page skips it @@ -1456,6 +1513,18 @@ inline std::vector whereisIndexDefSites( const IngestResult& ing, // Unlisted git could not list what differs; every HEAD row may be stale — complete= is withheld enum class WorktreeOverlay : std::uint8_t { Clean, Read, Partial, Unlisted }; +// How the OTHER refs' rows got their kind= (labelBranchRowsByParse), counted per distinct (blob, path) — the unit one +// parse answers. `leftByGuard` and `failed` are the pairs that confirm nothing: their parse-worthy rows are the +// `textRows` (kind="text"), and the page discloses all three in one element. +struct BranchLabelCensus +{ + std::size_t parsed = 0; // parsed by the tags path (definitionLinesInBlobs) + std::size_t mirrored = 0; // HEAD's own blob at the same path: HEAD's index labels, line for line + std::size_t leftByGuard = 0; // parse-worthy, but past kWhereisParseMaxBlobs / kWhereisParseMaxBytes + std::size_t failed = 0; // parse-worthy, but the parser could not finish (BlobDefsStatus::Failed) + std::size_t textRows = 0; // rows of those two that read kind="text" +}; + struct WhereResult { bool ok = true; @@ -1464,6 +1533,7 @@ struct WhereResult std::string headSha; bool onHead = false; // SYM occurs in HEAD's tree bool headLabelsFromIndex = false; // §A7: HEAD rows' kind= came from the index, not the shape test + BranchLabelCensus branchLabels; // how the OTHER refs' rows got kind= (labelBranchRowsByParse; on the page) std::size_t refsScanned = 0; std::size_t distinctBlobs = 0; std::string filter; // H14/M6: the --stray-filter SUBSTR this scan was narrowed by ("" = none) @@ -1597,178 +1667,21 @@ inline bool insideArgumentList( std::string_view line, std::size_t at ) noexcept return depth > 0; } -// Is this line SHAPED like a definition of `sym`? Declarative marker table over the languages ripwire indexes -// — deliberately LEXICAL, not AST-parsed: this is the REF-BLOB side, raw text that was never ingested, and -// paying a tree-sitter parse per candidate blob to sharpen a hint would cost more than the hint is worth. HEAD -// rows do NOT come here at all when the caller supplies the index's def sites (see relabelHeadHitsFromIndex): -// the honest split documented in --help is HEAD = parsed, refs = lexical, and this function is the lexical half. -// False positives here cost a `kind="def"` that should have read `kind="ref"`, never a missed hit. -// A declaration marker (`def `, `fn `, `class `…) that HEADS the declaration of the name at `at`: its own word -// (`#undef x` holds no `def `, `defn ` no `fn `, `template_t` no `template`), reached from the line's start through -// declarator text only, and separated from the name by declarator text only — identifier bytes, spaces and the -// punctuation a declarator head carries (`: < > , * & ( ) . [ ] ~ '`: `func (r *T) name(`, `def self.name`, -// `impl<'a> Name<'a>`, `template T name(`). A quote, a `;`, an `=` or a brace between the two means the -// marker is inside a string, a comment or an earlier statement: the 26b sweep's definitions page carried -// `w.write( " 0 && isIdentByte( (unsigned char)line[ pos - 1 ] ) ) - { - continue; - } - if( end < line.size() && isIdentByte( (unsigned char)marker.back() ) && isIdentByte( (unsigned char)line[ end ] ) ) - { - continue; // `template` is a bare word: `template_t` is not it - } - if( line.substr( 0, pos ).find_first_of( kBreaksHead ) != std::string_view::npos ) - { - return false; // the first marker already sits inside a string, a comment or an expression - } - if( line.substr( end, at - end ).find_first_of( kBreaksBetween ) != std::string_view::npos ) - { - return false; - } - if( marker == "using " ) - { - std::size_t k = at; - while( k < line.size() && isIdentByte( (unsigned char)line[ k ] ) ) - { - ++k; - } - while( k < line.size() && std::isspace( (unsigned char)line[ k ] ) ) - { - ++k; - } - return k < line.size() && line[ k ] == '='; - } - return true; - } - return false; -} - -// Is the text BEFORE the name an expression or a statement, so that `name(` is a CALL in it? A definition's prefix -// is a return type, qualifiers, a scope or an attribute — identifier bytes, `< > , : * & ~ [ ]` and spaces, never an -// operator, a brace, a paren or a statement keyword. Every byte below is one no definition line carries before its -// name: `out += escapeXml( task, esc ); }` (the 26b sweep's 59 false kind="def" rows), `if( x ) { f( a ); }`, -// `x = f( a )`, `a ? f( b ) : c`, `// f( a )`. The keywords are the statement heads the C-family, Python, JS/TS, Ruby, -// Go, Rust and Swift grammars put before a call with no paren of their own (`return f( a )`, `if f( a ):`, `await f( a )`, -// `match f( a ) {`); a type or qualifier word (`static`, `const`, `inline`, `public`, `async`, `operator`…) is not here. -inline bool expressionBeforeName( std::string_view line, std::size_t at ) noexcept -{ - static constexpr std::string_view kStatementHeads[] = { - "return", "throw", "throws", "else", "new", "delete", "await", "yield", "raise", "print", "echo", "if", "elif", - "while", "for", "with", "assert", "not", "and", "or", "in", "is", "lambda", "case", "switch", "match", "guard", - "unless", "until", "typeof", "sizeof", "instanceof", "co_return", "co_await", "co_yield", "goto", "do", "then", - }; - const std::string_view before = line.substr( 0, at ); - if( before.find_first_of( "=;{}()+!?|^%/\"'" ) != std::string_view::npos ) - { - return true; - } - for( std::size_t i = 0; i < before.size(); ) - { - if( !isIdentByte( (unsigned char)before[ i ] ) ) - { - ++i; - continue; - } - std::size_t j = i; - while( j < before.size() && isIdentByte( (unsigned char)before[ j ] ) ) - { - ++j; - } - const std::string_view word = before.substr( i, j - i ); - for( std::string_view head : kStatementHeads ) - { - if( word == head ) - { - return true; - } - } - i = j; - } - return false; -} - -// What follows the parameter list's closing paren decides between a definition and a call whose line happens to end -// in a definition's terminator. After a DEFINITION's `)` come only: the body `{`, an init list `:`, a trailing return -// `->`, a qualifier (`const`, `noexcept`, `&`, `&&`, `override`…), `= 0|default|delete`, an attribute `[[`, a comment, -// or the end of the line (the body opens on the next line). Anything else — `;` `,` `)` `.` `[` `?` an operator, `}`, -// a word such as `do` or `then` — means the name was called: `f( a ).then( b )`, `{ f( a ) }`, `f( a ) do |x|`. -// `open` is the index of the `(` after the name. An unbalanced list (the signature wraps) decides nothing: false. -inline std::size_t matchingParen( std::string_view line, std::size_t open ) noexcept -{ - EXPECTS( open < line.size() && line[ open ] == '(', "the caller found the name's parameter list opening at `open`" ); - int depth = 0; - for( std::size_t i = open; i < line.size(); ++i ) - { - if( line[ i ] == '(' ) - { - ++depth; - } - else if( line[ i ] == ')' && --depth == 0 ) - { - return i; - } - } - return std::string_view::npos; -} - -// Is the text at `k` (the first non-space byte after the parameter list) a definition's tail? See callShapedAfterParams. -inline bool definitionTailAt( std::string_view line, std::size_t k ) noexcept -{ - static constexpr std::string_view kAfterDeclWords[] = { - "const", "noexcept", "override", "final", "volatile", "mutable", "requires", "throw", "throws", "try", "where", - "__attribute__", "asm", - }; - const char c = line[ k ]; - const char next = ( k + 1 < line.size() ) ? line[ k + 1 ] : '\0'; - if( c == '{' || c == ':' || c == '&' || ( c == '-' && next == '>' ) || ( c == '[' && next == '[' ) - || ( c == '/' && ( next == '/' || next == '*' ) ) ) - { - return true; - } - if( c == '=' ) - { - std::size_t v = k + 1; - while( v < line.size() && std::isspace( (unsigned char)line[ v ] ) ) - { - ++v; - } - const std::string_view rest = line.substr( v ); - return rest.starts_with( "0" ) || rest.starts_with( "default" ) || rest.starts_with( "delete" ); - } - std::size_t j = k; - while( j < line.size() && isIdentByte( (unsigned char)line[ j ] ) ) - { - ++j; - } - const std::string_view word = line.substr( k, j - k ); - return !word.empty() && std::find( std::begin( kAfterDeclWords ), std::end( kAfterDeclWords ), word ) != std::end( kAfterDeclWords ); -} - -inline bool callShapedAfterParams( std::string_view line, std::size_t open ) noexcept -{ - const std::size_t close = matchingParen( line, open ); - if( close == std::string_view::npos ) - { - return false; - } - std::size_t k = close + 1; - while( k < line.size() && std::isspace( (unsigned char)line[ k ] ) ) - { - ++k; - } - return k < line.size() && !definitionTailAt( line, k ); -} - +// Is this line SHAPED like a definition of `sym`? A declarative marker table over the languages ripwire indexes, +// read LEXICALLY over raw text. It labels NO row on another ref: there parseWorthyLine, a looser superset of it, only +// CHOOSES which blobs the tags path parses (labelBranchRowsByParse — a blob with no such row is not parsed, and its rows +// read kind="ref"). Its one +// remaining LABELLING role is HEAD's fallback, head_labels="lexical" (no index def of the name, or a working tree that +// drifted from HEAD). It errs in BOTH directions, and each costs something different in each role: +// * a quoted signature in a doc, a call on a line ending in `}` or `)` (`if( x ) { f( a ); }`), `go f(a)`, `@f(1)`, +// `f( a, [&]( int x ) {` read as definition-shaped: a false kind="def" on the HEAD fallback (as a chooser, one +// wasted parse that then labels the row a reference); +// * a definition it does not shape — a signature wrapped before its `)` or ending in `,`, a declarator followed by a +// comment, `fun f(x) = x + 1`, `export const f = (a) =>`, a `name() {` at column 0 — reads as a reference: on the +// HEAD fallback a definition missing from the definitions page (counted among the refs). parseWorthyLine takes the +// cheap ones of these back in for the chooser; the rest stay a disclosed floor there too. +// A stricter reading of the line only trades one error for the other (the round-1 prefix and tail rules lost +// `#define NAME(`, `decltype(auto) NAME(`, `@Test(…) public void NAME()` …): the parser is the fix, not the line. inline bool definitionShaped( std::string_view line, std::string_view sym, std::size_t at ) { static constexpr std::string_view kDeclMarkers[] = { @@ -1777,7 +1690,7 @@ inline bool definitionShaped( std::string_view line, std::string_view sym, std:: }; for( std::string_view m : kDeclMarkers ) { - if( declMarkerBefore( line, m, at ) ) + if( line.find( m ) != std::string_view::npos && line.find( m ) < at ) { return true; } @@ -1789,13 +1702,6 @@ inline bool definitionShaped( std::string_view line, std::string_view sym, std:: // which are references, not definitions. The accepted terminators are '{' (body opens here), '}' (whole // one-line body), ')' (signature wraps to the next line) and ':' (a constructor's init list follows), // tested AFTER declaratorEnd() has stripped any trailing specifier tail. - // - // Two more tests, both lexical (the 26b sweep: with the definitions page listing EVERY kind="def" row of every - // ref, 59 of the 60 rows for a defs="1" symbol were call sites that merely ended in `}` or `)`): the text - // before the name must read as a declarator prefix, not an expression or a statement (expressionBeforeName), - // and what follows the parameter list must be a definition's tail, not a call's (callShapedAfterParams). - // What stays a floor: a bare `name( args )` alone on its line with nothing before or after it is a wrapped - // signature in C and a semicolon-free call in JS or Python, and one line cannot tell them apart. const std::size_t after = at + sym.size(); if( after < line.size() && line[ after ] == '(' ) { @@ -1803,8 +1709,7 @@ inline bool definitionShaped( std::string_view line, std::string_view sym, std:: const char last = ( e > 0 ) ? line[ e - 1 ] : ';'; const bool endsDecl = last == '{' || last == '}' || last == ')' || last == ':'; const bool isCall = at >= 1 && ( line[ at - 1 ] == '.' || ( at >= 2 && line[ at - 2 ] == '-' && line[ at - 1 ] == '>' ) ); - return endsDecl && !isCall && at > 0 && !insideArgumentList( line, at ) && !expressionBeforeName( line, at ) - && !callShapedAfterParams( line, after ); + return endsDecl && !isCall && at > 0 && !insideArgumentList( line, at ); } // ObjC method: "- (ret) sym" / "+ (ret) sym" if( !line.empty() && ( line[0] == '-' || line[0] == '+' ) && line.find( ')' ) < at ) @@ -1814,6 +1719,65 @@ inline bool definitionShaped( std::string_view line, std::string_view sym, std:: return false; } +// Does this line make its blob worth a tags PARSE (labelBranchRowsByParse)? A deliberately LOOSE superset of +// definitionShaped(), because the two errors cost different things here: a wrong "yes" costs one parse, which then +// labels the row a reference; a wrong "no" leaves a definition unparsed when it is the only such line of its blob, and +// that definition reads kind="ref". So the shape test's own misses that are cheap to take in are taken in: +// * a comment after the declarator (`int f( int a ) { // note`, `#define f( a ) ( a ) /* note */`): retried without it; +// * the name opening the line (`f() {` in a shell script, a GNU-style C signature `f (int a)` under its return type); +// * the name bound by `=` to a function, an arrow or a lambda (`export const f = (a) =>`, `f = function (`, `f = lambda`); +// * Kotlin's `fun ` marker (`fun f(x) = x + 1`); +// * a markdown heading that opens with the name (`## f`): the index makes a section symbol of it. +// What is still missed is named in definitionShaped's header (a signature wrapped before its parameter list closes, a +// name alone on a line with only a type above it…); those definitions stay counted refs when nothing else in their +// blob earns it a parse. +inline bool parseWorthyLine( std::string_view line, std::string_view sym, std::size_t at ) +{ + if( at == 0 || definitionShaped( line, sym, at ) ) + { + return true; + } + const std::size_t after = at + sym.size(); + for( const std::string_view opener : { std::string_view( "//" ), std::string_view( "/*" ), std::string_view( " #" ) } ) + { + const std::size_t c = line.find( opener, after ); + if( c != std::string_view::npos && definitionShaped( line.substr( 0, c ), sym, at ) ) + { + return true; + } + } + std::size_t k = after; + while( k < line.size() && std::isspace( (unsigned char)line[ k ] ) ) + { + ++k; + } + if( k + 1 < line.size() && line[ k ] == '=' && line[ k + 1 ] != '=' && line[ k + 1 ] != '>' ) + { + std::size_t v = k + 1; + while( v < line.size() && std::isspace( (unsigned char)line[ v ] ) ) + { + ++v; + } + const std::string_view rhs = line.substr( v ); + if( rhs.starts_with( "(" ) || rhs.starts_with( "function" ) || rhs.starts_with( "async" ) || rhs.starts_with( "lambda" ) ) + { + return true; + } + } + const std::size_t fun = line.find( "fun " ); + if( fun != std::string_view::npos && fun < at ) + { + return true; + } + const std::size_t hashes = line.find_first_not_of( " \t" ); // `#`s, then spaces, then the name: a heading + std::size_t h = hashes; + while( h < at && line[ h ] == '#' ) + { + ++h; + } + return hashes != std::string_view::npos && h > hashes && h < at && line.find_first_not_of( " \t", h ) == at; +} + // Record every whole-word occurrence of `sym` in one blob's bytes, as (line number, trimmed line). // One line's worth of the scan, split out so the line WALK below can stay a plain segmentation with no // duplicated body — the duplication is what let the final-line case drift out of sync in the first place. @@ -1835,8 +1799,9 @@ inline void scanLineForSymbol( std::string_view line, std::string_view sym, cons { --b; } - out.push_back( WhereHit{ ref.name, ref.tip, ref.date, path, lineNo, - definitionShaped( line, sym, at ), std::string( line.substr( a, b - a ) ), false } ); + WhereHit& h = out.emplace_back( WhereHit{ ref.name, ref.tip, ref.date, path, lineNo, + definitionShaped( line, sym, at ), std::string( line.substr( a, b - a ) ), false } ); + h.parseWorthy = parseWorthyLine( line, sym, at ); return; // one hit per line — the line IS the evidence } } @@ -1910,6 +1875,264 @@ inline std::vector lsTree( const std::string& root, const std::string& r return out; } +// ── every ref's full tree from content-addressed tree objects (listTreesOfRefs) ─────────────────────────── +// +// lsTree above spawns one `git ls-tree -r` per ref. Measured on ripwire's 162 local refs at load 25: that phase was +// 16.5 s of a 31 s --whereis (process start-up under load, each process re-listing subtrees most refs share). Trees are +// content-addressed, so a level-order walk reads each DISTINCT tree object ONCE — all of one level through one +// `git cat-file --batch` — and every ref's listing is rebuilt from those objects in ls-tree's own order (a tree's +// stored entry order, recursing in place) and spelling (gitQuotedPath: core.quotepath=false, as gitCapture runs it). +// Anything this walk cannot read exactly — a root that is not the top of the work tree, an object git does not +// serve, a malformed tree — returns nullopt and the caller lists with lsTree instead: the same rows, only slower. +struct GitTreeEntry +{ + std::string name; + std::string sha; + bool isTree = false; // a subtree; otherwise a blob of any mode (file, executable, symlink) — ls-tree's "blob" +}; + +// ls-tree's path spelling under core.quotepath=false: a path holding a control byte, `"` or `\` is written as a C +// string — quoted, with \a \b \t \n \v \f \r \" \\ and three-digit octal for the other control bytes and DEL — and every +// other path (bytes >= 0x80 included) is written as is (git quote.c, cq_lookup). +inline std::string gitQuotedPath( std::string_view path ) +{ + const auto needs = []( unsigned char c ) noexcept { return c < 0x20 || c == '"' || c == '\\' || c == 0x7f; }; + if( std::none_of( path.begin(), path.end(), [ & ]( char c ) { return needs( (unsigned char)c ); } ) ) + { + return std::string( path ); + } + std::string out = "\""; + for( const char ch : path ) + { + const unsigned char c = (unsigned char)ch; + static constexpr std::string_view kLetters = "abtnvfr"; // \a \b \t \n \v \f \r for bytes 7..13 + if( c >= 7 && c <= 13 ) + { + out += '\\'; + out += kLetters[ c - 7 ]; + } + else if( c == '"' || c == '\\' ) + { + out += '\\'; + out += ch; + } + else if( needs( c ) ) + { + const char oct[] = { '\\', char( '0' + ( c >> 6 ) ), char( '0' + ( ( c >> 3 ) & 7 ) ), char( '0' + ( c & 7 ) ) }; + out.append( oct, sizeof( oct ) ); + } + else + { + out += ch; + } + } + out += '"'; + return out; +} + +// Parse one tree object: ` \0` repeated. The raw id is 20 bytes for a SHA-1 repository and +// 32 for SHA-256; `idBytes` comes from the hex length of the tree's own name. False on any malformed entry (external +// input: VALIDATEd, never assumed). Gitlinks (mode 160000, a submodule's commit) are skipped, as ls-tree's blob filter does. +inline bool parseGitTree( std::string_view bytes, std::size_t idBytes, std::vector& out ) +{ + static constexpr std::string_view kHex = "0123456789abcdef"; + std::size_t pos = 0; + while( pos < bytes.size() ) + { + const std::size_t sp = bytes.find( ' ', pos ); + const std::size_t nul = sp == std::string_view::npos ? sp : bytes.find( '\0', sp + 1 ); + if( !VALIDATE( nul != std::string_view::npos && nul + 1 + idBytes <= bytes.size() ) ) // an entry cut short: not a tree we can read + { + return false; + } + const std::string_view mode = bytes.substr( pos, sp - pos ); + GitTreeEntry e; + e.name.assign( bytes.substr( sp + 1, nul - sp - 1 ) ); + e.isTree = mode == "40000"; + e.sha.reserve( idBytes * 2 ); + for( std::size_t k = 0; k < idBytes; ++k ) + { + const unsigned char b = (unsigned char)bytes[ nul + 1 + k ]; + e.sha += kHex[ b >> 4 ]; + e.sha += kHex[ b & 15 ]; + } + pos = nul + 1 + idBytes; + if( mode != "160000" ) + { + out.push_back( std::move( e ) ); + } + } + return true; +} + +// One `git cat-file --batch` over `shas` (in order): onObject( sha, type, bytes ) per object. False when the batch did not +// serve every object in full (a start failure, a missing object, a short read): the caller then cannot trust its walk. +template +inline bool catFileBatch( const std::string& root, const std::vector& shas, OnObject onObject ) +{ + if( shas.empty() ) + { + return true; + } + static std::atomic listSeq{ 0 }; + const std::string listPath = quality::cacheDirLadder() + "/ripwire-crossref-trees-" + std::to_string( os::getpid() ) + "-" + + std::to_string( listSeq.fetch_add( 1, std::memory_order_relaxed ) ) + ".shas"; + { + std::FILE* lf = std::fopen( listPath.c_str(), "wb" ); + if( !lf ) + { + return false; + } + for( const std::string& s : shas ) + { + rw::emitTo( lf, "{}\n", s.c_str() ); + } + std::fclose( lf ); + } + const std::string cmd = gitCmd( " -C " ) + shSingleQuote( root ) + " cat-file --batch < " + shSingleQuote( listPath ) + " 2>/dev/null"; + std::FILE* pipe = os::popen( cmd.c_str(), "r" ); + if( !pipe ) + { + os::unlink( listPath.c_str() ); + return false; + } + bool ok = true; + std::string header; + std::vector body; + for( std::size_t served = 0; served < shas.size() && ok; ++served ) + { + header.clear(); + for( int c = std::fgetc( pipe ); c != EOF && c != '\n'; c = std::fgetc( pipe ) ) + { + header.push_back( char( c ) ); + } + const std::size_t sp2 = header.rfind( ' ' ); + const std::size_t sp1 = sp2 == std::string::npos || sp2 == 0 ? std::string::npos : header.rfind( ' ', sp2 - 1 ); + const long long size = sp1 == std::string::npos ? -1 : std::strtoll( header.c_str() + sp2 + 1, nullptr, 10 ); + if( size < 0 || header.compare( 0, sp1, shas[ served ] ) != 0 ) + { + ok = false; // " missing", an empty header (the pipe ended), or an answer for another object + break; + } + body.resize( std::size_t( size ) ); + const std::size_t got = size > 0 ? std::fread( body.data(), 1, std::size_t( size ), pipe ) : 0; + (void)std::fgetc( pipe ); // the framing LF after the payload + ok = got == std::size_t( size ) && onObject( shas[ served ], std::string_view( header ).substr( sp1 + 1, sp2 - sp1 - 1 ), + std::string_view( body.data(), got ) ); + } + os::pclose( pipe ); + os::unlink( listPath.c_str() ); + return ok; +} + +// Every ref's `ls-tree -r` rows, by the level-order walk above; nullopt when the walk cannot answer exactly. +inline std::optional>> listTreesOfRefs( const std::string& root, const std::vector& refs ) +{ + // ls-tree lists the CURRENT DIRECTORY's subtree, relative to it: from a root below the top of the work tree the walk + // would have to start one subtree down and re-spell every path. That case keeps lsTree. + if( !gitCapture( root, "rev-parse --show-prefix 2>/dev/null" ).empty() ) + { + return std::nullopt; + } + std::vector tips; + for( const RefInfo& r : refs ) + { + tips.push_back( r.tip ); + } + std::sort( tips.begin(), tips.end() ); + tips.erase( std::unique( tips.begin(), tips.end() ), tips.end() ); + + gtl::btree_map rootOf; // commit → its tree + const bool commitsRead = catFileBatch( root, tips, [ & ]( const std::string& sha, std::string_view type, std::string_view bytes ) + { + const bool shaped = type == "commit" && bytes.starts_with( "tree " ) && bytes.find( '\n' ) != std::string_view::npos; + if( !VALIDATE( shaped ) ) + { + return false; + } + rootOf[ sha ] = std::string( bytes.substr( 5, bytes.find( '\n' ) - 5 ) ); + return isBlobSha( rootOf[ sha ] ); + } ); + if( !commitsRead ) + { + return std::nullopt; + } + + gtl::btree_map> trees; // every distinct tree object any ref reaches + std::vector level; + for( const auto& [ commit, tree ] : rootOf ) + { + (void)commit; + level.push_back( tree ); + } + while( !level.empty() ) + { + std::sort( level.begin(), level.end() ); + level.erase( std::unique( level.begin(), level.end() ), level.end() ); + std::vector next; + const bool read = catFileBatch( root, level, [ & ]( const std::string& sha, std::string_view type, std::string_view bytes ) + { + std::vector entries; + if( type != "tree" || !parseGitTree( bytes, sha.size() / 2, entries ) ) + { + return false; + } + for( const GitTreeEntry& e : entries ) + { + if( e.isTree && trees.find( e.sha ) == trees.end() ) + { + next.push_back( e.sha ); + } + } + trees.emplace( sha, std::move( entries ) ); + return true; + } ); + if( !read ) + { + return std::nullopt; + } + level.clear(); + for( std::string& s : next ) + { + if( trees.find( s ) == trees.end() ) + { + level.push_back( std::move( s ) ); + } + } + } + + std::vector> out( refs.size() ); + for( std::size_t i = 0; i < refs.size(); ++i ) + { + // depth-first in stored order, a subtree listed where it stands: ls-tree -r's own order + struct Frame { const std::vector* entries; std::size_t next; std::size_t prefixLen; }; + std::string prefix; + std::vector stack{ Frame{ &trees.at( rootOf.at( refs[ i ].tip ) ), 0, 0 } }; + while( !stack.empty() ) + { + Frame& f = stack.back(); + if( f.next == f.entries->size() ) + { + stack.pop_back(); + continue; + } + const GitTreeEntry& e = ( *f.entries )[ f.next++ ]; + prefix.resize( f.prefixLen ); + prefix += e.name; + if( e.isTree ) + { + const std::size_t childPrefix = prefix.size() + 1; + prefix += '/'; + stack.push_back( Frame{ &trees.at( e.sha ), 0, childPrefix } ); + continue; + } + out[ i ].push_back( RawRow{ gitQuotedPath( prefix ), std::string{}, e.sha } ); + } + } + ENSURES( out.size() == refs.size(), "one listing per ref, in the refs' order (computeWhereis reads slot i as refs[i])" ); + return out; +} + // A git tree path ("src/graph.h") against an index path relativised to the ingest ROOT ("graph.h" when the // tree was ingested as `ripwire src`): equal, or the git path ends with the index path on a component // boundary. Never a realpath (determinism, same reasoning as arch.h::relForHash). @@ -1986,6 +2209,195 @@ inline bool relabelHeadHitsFromIndex( std::vector& hits, std::span counts them. +// A path in no indexed language, or one the crawl would skip (binary, oversized, a nesting refusal), is labelled as +// the index labels such a file on HEAD: no definition. +struct WhereisParseBudget +{ + std::size_t maxBlobs = kWhereisParseMaxBlobs; + std::size_t maxBytes = kWhereisParseMaxBytes; +}; + +struct BranchParseJob +{ + std::string sha; // the blob (with `path`, the unit one parse answers) + std::string path; // the tree path: its extension picks the grammar + std::string bytes; // the blob, kept only when it holds a parse-worthy row + std::vector hitLines; // the blob's hit lines, ascending (one hit per line) + std::vector rows; // every WhereHit this (blob, path) produced on a non-HEAD ref + std::size_t headFirst = 0, headEnd = 0; // HEAD's rows of this same (blob, path), when HEAD holds it + bool shaped = false; // some row is parse-worthy (parseWorthyLine): worth a parse +}; + +// The hit lines a parse's definition lines promote: per definition line, the nearest hit line in its window (the lower +// line on a tie — relabelHeadHitsFromIndex walks HEAD's rows in ascending line order with the same strict `<`). +inline std::vector promotedHitLines( std::span hitLines, std::span defLines ) +{ + EXPECTS( std::is_sorted( hitLines.begin(), hitLines.end() ), "one blob's hit lines, in the scan's ascending order (the tie rule reads them so)" ); + std::vector promoted; + for( const std::uint32_t defLine : defLines ) + { + std::size_t best = hitLines.size(); + std::uint32_t bestDist = 0; + for( std::size_t i = 0; i < hitLines.size(); ++i ) + { + const std::uint32_t h = hitLines[ i ]; + if( h + kHeadDefLineAbove < defLine || h > defLine + kHeadDefLineBelow ) + { + continue; + } + const std::uint32_t dist = ( h > defLine ) ? h - defLine : defLine - h; + if( best == hitLines.size() || dist < bestDist ) { best = i; bestDist = dist; } + } + if( best != hitLines.size() ) + { + promoted.push_back( hitLines[ best ] ); + } + } + std::sort( promoted.begin(), promoted.end() ); + return promoted; +} + +// Label one job's rows: kind="def" exactly on `defLines` (sorted), else kind="ref" — or, when the job confirmed nothing +// (`confirmed` false), kind="text" on its parse-worthy rows (parseWorthyLine), the lines that asked for the parse. +inline void labelJobRows( WhereResult& result, const BranchParseJob& job, std::span defLines, bool confirmed ) +{ + EXPECTS( std::all_of( job.rows.begin(), job.rows.end(), [ & ]( std::size_t r ) { return r < result.hits.size() && !result.hits[ r ].inCheckout(); } ), + "a job's rows are rows of other refs this scan produced (HEAD's are the index's to label)" ); + for( const std::size_t r : job.rows ) + { + WhereHit& h = result.hits[ r ]; + if( !confirmed ) + { + h.unconfirmed = h.parseWorthy; + result.branchLabels.textRows += h.parseWorthy ? 1 : 0; + } + h.isDef = confirmed && std::binary_search( defLines.begin(), defLines.end(), h.line ); + } +} + +// The parse batch: the parse-worthy jobs no mirror answered, most rows first (then path, then blob — a fact of the +// answer, never of thread timing), up to the guard. Returns the jobs it parsed, in batch order. +inline std::vector branchParseBatch( const std::vector& jobs, std::span answered, + const WhereisParseBudget& budget, BranchLabelCensus& census ) +{ + std::vector want; + for( std::size_t j = 0; j < jobs.size(); ++j ) + { + if( answered[ j ] == 0 && jobs[ j ].shaped ) + { + want.push_back( j ); + } + } + std::sort( want.begin(), want.end(), [ & ]( std::size_t a, std::size_t b ) + { + const BranchParseJob& x = jobs[ a ]; + const BranchParseJob& y = jobs[ b ]; + if( x.rows.size() != y.rows.size() ) { return x.rows.size() > y.rows.size(); } + if( x.path != y.path ) { return x.path < y.path; } + return x.sha < y.sha; + } ); + std::vector batch; + std::size_t bytes = 0; + for( const std::size_t j : want ) + { + const std::size_t size = jobs[ j ].bytes.size(); + if( batch.size() >= budget.maxBlobs || size > budget.maxBytes - bytes ) + { + break; // the guard: this job and every later one confirm nothing (counted below by the caller) + } + batch.push_back( j ); + bytes += size; + } + census.leftByGuard = want.size() - batch.size(); + ENSURES( batch.size() <= budget.maxBlobs && bytes <= budget.maxBytes, "the batch stays inside the runaway guard" ); + return batch; +} + +inline void labelBranchRowsByParse( WhereResult& result, std::vector& jobs, const WhereisParseBudget& budget ) +{ + std::vector answered( jobs.size(), 0 ); + for( std::size_t j = 0; j < jobs.size(); ++j ) + { + BranchParseJob& job = jobs[ j ]; + if( job.headEnd > job.headFirst && result.headLabelsFromIndex ) + { + std::vector headDefs; // HEAD's rows are ascending by line: one blob, one scan + for( std::size_t r = job.headFirst; r < job.headEnd; ++r ) + { + if( result.hits[ r ].isDef ) { headDefs.push_back( result.hits[ r ].line ); } + } + labelJobRows( result, job, headDefs, true ); + ++result.branchLabels.mirrored; + answered[ j ] = 1; + } + else if( !job.shaped || sliceGrammarForFile( job.path ) == nullptr ) + { + labelJobRows( result, job, {}, true ); // nothing parse-worthy, or no indexed language: as the index would say + answered[ j ] = 1; + } + } + const std::vector batch = branchParseBatch( jobs, answered, budget, result.branchLabels ); + std::vector blobs; + blobs.reserve( batch.size() ); + for( const std::size_t j : batch ) + { + blobs.push_back( BlobText{ jobs[ j ].path, jobs[ j ].bytes } ); + } + std::vector parsed; + if( !blobs.empty() ) + { + // the tags queries live in a process-global, single-writer cache: a long-lived server serializes every ingest on this lock + std::lock_guard ingestLk( quality::headSnapshotIngestMutex() ); + parsed = definitionLinesInBlobs( blobs, result.sym ); + } + ENSURES( parsed.size() == batch.size(), "one answer per (blob, path) handed to the parser" ); + for( std::size_t k = 0; k < batch.size(); ++k ) + { + const std::size_t j = batch[ k ]; + answered[ j ] = 1; + switch( parsed[ k ].status ) + { + case BlobDefsStatus::Parsed: + labelJobRows( result, jobs[ j ], promotedHitLines( jobs[ j ].hitLines, parsed[ k ].lines ), true ); + ++result.branchLabels.parsed; + break; + case BlobDefsStatus::NoGrammar: + case BlobDefsStatus::Unread: + labelJobRows( result, jobs[ j ], {}, true ); // the crawl defines nothing in such a file on HEAD either + break; + case BlobDefsStatus::Failed: + labelJobRows( result, jobs[ j ], {}, false ); + ++result.branchLabels.failed; + break; + } + } + for( std::size_t j = 0; j < jobs.size(); ++j ) + { + if( answered[ j ] == 0 ) + { + labelJobRows( result, jobs[ j ], {}, false ); // left by the guard: confirms nothing + } + } +} + // ── the working-tree overlay (2026-10-01 freshness fix) ────────────────────────────────────────────────── // // A git listing whose SUCCESS is known. gitOneLine answers "" both for "nothing to list" and for "git failed", @@ -2392,7 +2804,9 @@ inline bool demoteTestLocalDefs( std::vector& hits ) // that page skips it. Nothing is erased: hits= still counts every row and listing=all prints each ref's own row. // A ref that holds the same text at TWO lines of one path (a macro twin, a copy in a #if branch) is two groups — // the k-th such line of a ref joins the k-th group — so N is exactly the number of refs holding the row, never more. -// The checkout's rows (HEAD and worktree=) are the parsed answer and never fold; nor does any kind="ref" row. +// The checkout's rows (HEAD and worktree=) are the parsed answer and never fold; nor does any kind="ref" row. A +// kind="text" row (a parse-worthy line whose blob was not parsed, see labelBranchRowsByParse) folds the same way, +// in groups of its own: the same line on N refs is one unconfirmed row with refs="N", never merged into a definition. inline void foldSharedBranchDefs( std::vector& hits ) { struct Group @@ -2407,12 +2821,13 @@ inline void foldSharedBranchDefs( std::vector& hits ) for( std::size_t i = 0; i < hits.size(); ++i ) { WhereHit& h = hits[ i ]; - if( !h.isDef || h.inCheckout() ) + if( ( !h.isDef && !h.unconfirmed ) || h.inCheckout() ) { continue; } ++branchDefs; - key.assign( h.path ); + key.assign( h.unconfirmed ? "text\n" : "def\n" ); // a confirmed and an unconfirmed copy of a line never share a group + key += h.path; key += '\n'; // a path never holds a newline, and text is one trimmed line: the join is unambiguous key += h.text; Group& g = groups[ key ]; @@ -2464,8 +2879,9 @@ inline bool whereHitBefore( const WhereHit& a, const WhereHit& b ) { return at < bt; } - // Production definitions, then test-local ones (test_local="1", see demoteTestLocalDefs), then references. - const auto defRank = []( const WhereHit& h ) { return !h.isDef ? 2 : ( h.testLocal ? 1 : 0 ); }; + // Production definitions, then test-local ones (test_local="1", see demoteTestLocalDefs), then the unconfirmed + // parse-worthy rows (kind="text", labelBranchRowsByParse), then references. + const auto defRank = []( const WhereHit& h ) { return h.isDef ? ( h.testLocal ? 1 : 0 ) : ( h.unconfirmed ? 2 : 3 ); }; if( defRank( a ) != defRank( b ) ) { return defRank( a ) < defRank( b ); @@ -2498,7 +2914,7 @@ inline bool whereHitBefore( const WhereHit& a, const WhereHit& b ) // code. Nothing is dropped and no row's attributes change; a reader who wants the doc evidence still gets // every row of it. inline WhereResult computeWhereis( const std::string& root, std::string_view sym, std::string_view filter, - WhereisEvidence evidence = {} ) + WhereisEvidence evidence = {}, const WhereisParseBudget& budget = {} ) { WhereResult result; result.sym = std::string( sym ); @@ -2536,7 +2952,16 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym // is the serial one by construction. Measured on a 153-ref repo: 7.0 s of serial ls-tree wall → 2.1 s. std::vector> trees( refs.size() ); ParallelSweep treeSweep; - parallelIndexed( refs.size(), [ & ]( std::size_t i ) { trees[ i ] = lsTree( root, refs[ i ].tip ); }, treeSweep ); + if( std::optional>> walked = listTreesOfRefs( root, refs ) ) + { + trees = std::move( *walked ); // every distinct tree object read once (listTreesOfRefs); treeSweep never ran, so isDone holds + } + else + { + DISCLOSE( Diagnostics::answerUnchanged, "lsTree lists the same rows per ref: only slower", + "crossref: the tree-object walk could not read every tree exactly — listing each ref with git ls-tree" ); + parallelIndexed( refs.size(), [ & ]( std::size_t i ) { trees[ i ] = lsTree( root, refs[ i ].tip ); }, treeSweep ); + } for( std::uint32_t i = 0; i < refs.size(); ++i ) { const std::vector& rows = trees[ i ]; @@ -2558,24 +2983,108 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym result.distinctBlobs = shas.size(); result.refsScanned = refs.size() - 1; // HEAD is not one of the swept refs - StreamBlobStats blobStats; // T1: the degrade census that decides whether this scan may claim complete= - streamBlobs( root, shas, [ & ]( const std::string& sha, std::string_view bytes, bool isText ) - { - if( !isText || bytes.find( sym ) == std::string_view::npos ) { return; // cheap reject before the line walk -} + // The scan of every blob holding the name runs on the stream's own worker threads (streamBlobsInSlices): a pure + // function of the bytes — its hit rows (stamped with no ref yet), and the bytes themselves only when some row is + // parse-worthy. The loop after it walks the blobs in sha order on this thread, so the answer is the serial one. + struct BlobScan + { + bool hasSym = false; // a text blob holding the name's bytes (the cheap reject passed) + std::vector rows; // its hits; empty ⇔ no whole-word occurrence (hasWholeWord) + std::string bytes; // kept only for a parse-worthy blob (labelBranchRowsByParse) + }; + std::vector scans( shas.size() ); + StreamBlobStats blobStats; // T1: the degrade census that decides whether this scan may claim complete= + streamBlobsInSlices( root, shas, kWhereisStreamSlices, [ & ]( std::size_t i, const std::string&, std::string_view bytes, bool isText ) + { + if( !isText || bytes.find( sym ) == std::string_view::npos ) + { + return; // cheap reject before the line walk + } + BlobScan& b = scans[ i ]; + b.hasSym = true; + scanBlobForSymbol( bytes, sym, RefInfo{}, std::string(), b.rows ); + if( std::any_of( b.rows.begin(), b.rows.end(), []( const WhereHit& h ) { return h.parseWorthy; } ) ) + { + b.bytes.assign( bytes ); + } + }, blobStats ); + std::vector branchJobs; // one per distinct (blob, path) behind a non-HEAD row: labelBranchRowsByParse + for( std::size_t blobIndex = 0; blobIndex < shas.size(); ++blobIndex ) + { + const BlobScan& scan = scans[ blobIndex ]; + if( !scan.hasSym ) + { + continue; + } + const std::string& sha = shas[ blobIndex ]; const auto it = sites.find( sha ); - if( it == sites.end() ) { return; -} + if( it == sites.end() ) + { + continue; + } + const std::vector& blobRows = scan.rows; + const std::size_t firstJobOfBlob = branchJobs.size(); + struct HeadRows { std::string_view path; std::size_t first, end; }; + std::vector headRows; // HEAD's rows of THIS blob, per path (HEAD is the first site: refs[0]) for( const Site& s : it->second ) { const bool head = refs[ s.refIndex ].name == "HEAD"; - result.headHolds = result.headHolds || ( head && ( !s.replaced || hasWholeWord( bytes, sym ) ) ); + result.headHolds = result.headHolds || ( head && ( !s.replaced || !blobRows.empty() ) ); result.onHead = result.onHead || ( head && !s.replaced ); - if( !s.replaced ) + if( s.replaced ) + { + continue; + } + const std::size_t firstRow = result.hits.size(); + for( const WhereHit& row : blobRows ) { - scanBlobForSymbol( bytes, sym, refs[ s.refIndex ], s.path, result.hits ); + WhereHit& h = result.hits.emplace_back( row ); + h.ref = refs[ s.refIndex ].name; + h.tip = refs[ s.refIndex ].tip; + h.date = refs[ s.refIndex ].date; + h.path = s.path; } - } }, &blobStats ); + if( head ) + { + headRows.push_back( HeadRows{ s.path, firstRow, result.hits.size() } ); // the index labels these (relabelHeadHitsFromIndex) + continue; + } + if( firstRow == result.hits.size() ) + { + continue; + } + auto job = std::find_if( branchJobs.begin() + std::ptrdiff_t( firstJobOfBlob ), branchJobs.end(), + [ & ]( const BranchParseJob& j ) { return j.path == s.path; } ); + if( job == branchJobs.end() ) + { + BranchParseJob fresh; + fresh.sha = sha; + fresh.path = s.path; + for( std::size_t r = firstRow; r < result.hits.size(); ++r ) + { + fresh.hitLines.push_back( result.hits[ r ].line ); + fresh.shaped = fresh.shaped || result.hits[ r ].parseWorthy; // parseWorthyLine(): worth a parse + } + const auto headCopy = std::find_if( headRows.begin(), headRows.end(), [ & ]( const HeadRows& h ) { return h.path == s.path; } ); + if( headCopy != headRows.end() ) + { + fresh.headFirst = headCopy->first; + fresh.headEnd = headCopy->end; + } + if( fresh.shaped ) + { + fresh.bytes = scan.bytes; // kept for the parse batch (a mirror of HEAD's labels may yet make it unneeded) + } + branchJobs.push_back( std::move( fresh ) ); + job = branchJobs.end() - 1; + } + for( std::size_t r = firstRow; r < result.hits.size(); ++r ) + { + job->rows.push_back( r ); + } + } + } + std::vector().swap( scans ); // the rows are stamped and the parse-worthy bytes copied into their jobs // T1: exhaustive-over-text iff every sha streamed clean AND no ref's tree listing was suspect. An empty // sha list (every scanned tree empty, or none) trivially streamed clean — anyEmptyTree covers that shape. @@ -2586,6 +3095,8 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym // §A7: HEAD's rows are the INDEX's answer, not the shape test's — before the sort, because "definitions // before references" is a sort key and a wrong label re-orders the first screen. result.headLabelsFromIndex = relabelHeadHitsFromIndex( result.hits, evidence.indexDefs ); + // …and the other refs' rows are a PARSER's answer too, after HEAD's: a ref holding HEAD's own blob takes HEAD's labels. + labelBranchRowsByParse( result, branchJobs, budget ); demoteTestLocalDefs( result.hits ); // The checkout first, then refs by name; within a group, SOURCE before test before docs (§P11.5, see this @@ -2998,7 +3509,8 @@ inline std::string whereisDottedRetryOf( const IngestResult& ing, std::string_vi // SHARED DEFINITIONS clause of the legend, spliced by writeWhereisListedPage beside the tail below. inline constexpr char kWhereisSharedDefsLegend[] = "SHARED DEFINITIONS: refs=\"N\" on a kind=\"def\" row outside the checkout means N scanned refs hold this same " - "definition line (the same path and the same line text); the row is printed once, for the first of those " + "definition line (the same path and the same line text; on a kind=\"text\" row, the same unparsed line, never merged " + "with a confirmed one); the row is printed once, for the first of those " "refs by name, whose ref=, tip=, date= and l= it carries (another of the N may hold the line at a different " "line number). It appears only under listing=\"defs\" and only when N is at least 2: a definition one ref " "alone holds carries no refs=. hits= still counts every copy, and listing=all prints each ref's own row. "; @@ -3022,6 +3534,17 @@ inline void writeWhereisLegendTail( std::FILE* out, const WhereResult& res ) "holds a production definition, and those rows are ordered after the production definitions and before " "the references of their tier. Nothing is dropped. " ); } + // The unparsed element's vocabulary (writeWhereisUnparsed), only on an answer that carries the element. No literal + // double dash inside this XML comment: the flag is spelled by name, the next= attribute carries its real spelling. + if( res.branchLabels.leftByGuard + res.branchLabels.failed > 0 ) + { + rw::emitTo( out, "UNPARSED: the unparsed element counts the blobs of other refs that hold a line worth parsing but that " + "no parse read: blobs= of them, either past the runaway guard on the parse batch ({} blobs or {} MB, filled " + "most rows first, then by path and blob) or a parse that could not finish; rows= is the kind=\"text\" rows they " + "gave. Those rows are neither a confirmed definition nor a confirmed reference, and the blobs' other rows read " + "kind=\"ref\". Its next= rides when the guard left some: the same question with the detail flag at 1, which " + "lifts the guard. ", kWhereisParseMaxBlobs, kWhereisParseMaxBytes >> 20 ); + } // The overlay's own vocabulary, only on an answer that carries it — a clean checkout pays no bytes for it. if( res.worktree != WorktreeOverlay::Clean ) { @@ -3121,6 +3644,13 @@ struct ListedHits std::size_t defsFolded = 0; // def rows of other refs a listed row's refs= stands for (folded="N" on the root) }; +// kind= of a row: "def" (a parser's definition), "text" (a parse-worthy line on another ref no parse confirmed, +// labelBranchRowsByParse) or "ref". +inline const char* whereisKindOf( const WhereHit& h ) noexcept +{ + return h.isDef ? "def" : ( h.unconfirmed ? "text" : "ref" ); +} + // The ` refs="N"` a row carries, or "": it rides ONLY the definitions page (foldSharedBranchDefs), where the row stands // for N refs' copies of the same definition line. listing=all prints each copy as its own row and never carries it; a // lone copy carries none. The legend's SHARED DEFINITIONS clause rides exactly the pages where this is non-empty. @@ -3132,7 +3662,10 @@ inline std::string whereisSharedAttr( const ListedHits& listed, const WhereHit& inline ListedHits listedHits( const WhereResult& res, WhereisListing listing ) { EXPECTS( listing != WhereisListing::ShorterOfDefsAll, "the default is resolved to Defs or All (whereisServedListing) before the rows are picked" ); - const std::size_t defs = std::size_t( std::count_if( res.hits.begin(), res.hits.end(), []( const WhereHit& h ) { return h.isDef; } ) ); + // The definitions page lists the kind="def" rows AND the kind="text" ones (parse-worthy lines on another ref + // that no parse confirmed or refuted, labelBranchRowsByParse): a possible definition is printed, labelled, never + // counted away among the references. listing=refs and are the kind="ref" rows exactly. + const std::size_t defs = std::size_t( std::count_if( res.hits.begin(), res.hits.end(), []( const WhereHit& h ) { return h.isDef || h.unconfirmed; } ) ); const std::size_t refs = res.hits.size() - defs; ListedHits out; // Defs with nothing to elide (no ref row) IS the whole list; Defs with no def row would list nothing but a count, @@ -3151,7 +3684,7 @@ inline ListedHits listedHits( const WhereResult& res, WhereisListing listing ) for( std::size_t i = 0; i < res.hits.size(); ++i ) { const WhereHit& h = res.hits[ i ]; - if( h.isDef != wantDefs ) { continue; } + if( ( h.isDef || h.unconfirmed ) != wantDefs ) { continue; } if( wantDefs && h.foldedDef ) { ++folded; continue; } out.rows.push_back( i ); } @@ -3250,8 +3783,12 @@ inline void writeWhereisRoot( std::FILE* out, const WhereResult& res, const Wher // folded= (foldSharedBranchDefs): the def rows of other refs that a listed row's refs= stands for, so the root's // arithmetic stays checkable in one place: shown + more + folded + refs count = hits. Only when a row folded, // so an answer with no shared branch definition is byte-identical to before. + // ref_labels="parsed": the answer holds rows of OTHER refs, whose kind= a parser gave (labelBranchRowsByParse). Only + // then, so an answer with no branch row — a repo with no other ref — is byte-identical to before. + const bool anyBranchRow = std::any_of( res.hits.begin(), res.hits.end(), []( const WhereHit& h ) { return !h.inCheckout(); } ); const std::string listingAttr = ( listed.attr.empty() ? std::string() : ( " listing=\"" + std::string( listed.attr ) + "\"" ) ) - + ( listed.defsFolded > 0 ? ( " folded=\"" + std::to_string( listed.defsFolded ) + "\"" ) : std::string() ); + + ( listed.defsFolded > 0 ? ( " folded=\"" + std::to_string( listed.defsFolded ) + "\"" ) : std::string() ) + + ( anyBranchRow ? std::string( " ref_labels=\"parsed\"" ) : std::string() ); const std::string headDateAttr = anyHoist ? ( " head_date=\"" + ex( headDate ) + "\"" ) : std::string(); rw::emitTo( out, "", ex( res.sym ).c_str(), res.onHead ? 1 : 0, res.refsScanned, res.distinctBlobs, res.hits.size(), @@ -3265,6 +3802,30 @@ inline void writeWhereisRoot( std::FILE* out, const WhereResult& res, const Wher } +// : the other refs' (blob, path) pairs that held a parse-worthy row and confirm +// nothing — the runaway guard left them (WhereisParseBudget) or the parser could not finish — and the rows of theirs +// that read kind="text". next= rides only when the guard left some: --detail=1 lifts it. Absent when every +// parse-worthy blob was parsed (or mirrored HEAD's), so an answer the guard never touched is byte-identical. +inline void writeWhereisUnparsed( std::FILE* out, const WhereResult& res ) +{ + const BranchLabelCensus& c = res.branchLabels; + if( c.leftByGuard + c.failed == 0 ) + { + return; + } + std::string next; + if( c.leftByGuard > 0 ) + { + std::string call = nextFlag( "--whereis=", res.sym ); + if( !res.filter.empty() ) + { + call += " " + nextFlag( "--stray-content=", res.filter ); // the ref filter that made this page + } + next = nextAttrXml( call + " --detail=1" ); + } + rw::emitTo( out, "", c.leftByGuard + c.failed, c.textRows, next.c_str() ); +} + // The rows of the page, its remainder and the element (writeWhereisPage). inline void writeWhereisRows( std::FILE* out, const WhereResult& res, const ListedHits& listed, const PageWindow& hitPage, const std::string& headDate, const XmlEscaper& ex ) @@ -3284,12 +3845,12 @@ inline void writeWhereisRows( std::FILE* out, const WhereResult& res, const List { rw::emitTo( out, "", ex( h.ref ).c_str(), ex( h.path ).c_str(), - h.line, h.isDef ? "def" : "ref", h.testLocal ? " test_local=\"1\"" : "", ex( h.text ).c_str() ); + h.line, whereisKindOf( h ), h.testLocal ? " test_local=\"1\"" : "", ex( h.text ).c_str() ); continue; } rw::emitTo( out, "", ex( h.ref ).c_str(), h.tip.c_str(), ex( h.date ).c_str(), ex( h.path ).c_str(), - h.line, h.isDef ? "def" : "ref", h.testLocal ? " test_local=\"1\"" : "", whereisSharedAttr( listed, h ).c_str(), ex( h.text ).c_str() ); + h.line, whereisKindOf( h ), h.testLocal ? " test_local=\"1\"" : "", whereisSharedAttr( listed, h ).c_str(), ex( h.text ).c_str() ); } ASSUME( shownCount == hitPage.end - hitPage.begin ); // = the LISTED rows AFTER this page, so shown + more == the listed rows from this page's offset @@ -3335,14 +3896,18 @@ inline void writeWhereisListedPage( std::FILE* out, const WhereResult& res, std: rw::emitRaw( out, " +EOF +gp add -A; gp commit -qm base +gp checkout -qb sib +mkdir -p "$PB/sib"; cp -R "$PB/head/." "$PB/sib/" +mkp c1/m1.h <<'EOF' +#define probeName( a ) ( (a) + 1 ) // @D +EOF +mkp c1/m2.h <<'EOF' +#define probeName( a ) do { f( a ); } while( 0 ) // @D +EOF +mkp c1/d1.cpp <<'EOF' +decltype(auto) probeName( int a ) { return a; } // @D +EOF +mkp c1/d2.cpp <<'EOF' +__attribute__((noinline)) static int probeName( int a ) { return a; } // @D +EOF +mkp c1/d3.cpp <<'EOF' +#include +std::function probeName( int a ) { return [](){}; } // @D +EOF +mkp c1/d4.cpp <<'EOF' +template T probeName( T a ) { return a; } // @D +EOF +mkp c1/T.java <<'EOF' +public class T { + @Test(expected = Foo.class) public void probeName() { // @D + } +} +EOF +mkp c1/c.ts <<'EOF' +export class C { + @HostListener('click') probeName() { // @D + } +} +EOF +mkp c1/a.cs <<'EOF' +public class A +{ + [HttpGet("x")] public IActionResult probeName() { return null; } // @D +} +EOF +mkp c1/s.rs <<'EOF' +#[derive(Debug)] pub struct probeName { // @D + a: i32, +} +EOF +mkp c1/e.js <<'EOF' +module.exports = function probeName(req, res) { // @D + return req; +}; +EOF +mkp c1/h.js <<'EOF' +const handler = function probeName() { // @D + return 1; +}; +EOF +# the reviewer's branch-only repro (rv C1 repro 1): names HEAD never holds, defined only on this branch +printf '#define clampBudget( x ) ( (x) > 9 ? 9 : (x) )\n' >"$PB/c1/b.h" +printf 'int use( int a ) { return clampBudget( a ); }\n' >"$PB/c1/u.c" +printf 'public class T2 {\n @Test(timeout = 10) public void checkBudget() {\n }\n}\n' >"$PB/c1/T2.java" +gp add -A; gp commit -qm sib; gp checkout -q main +"$BIN" "$PB" --whereis=probeName --whereis-listing=all --limit=1000 --no-cache >"$TMP/pb.all" 2>/dev/null; rcPb=$? +if [ $rcPb -ne 0 ] || ! grep -q ' root (rc $rcPb)" +else + sed 's///' "$TMP/pb.all" | tr '<' '\n' | sed -n 's/^hit ref="\([^"]*\)".* p="\([^"]*\)" l="\([0-9]*\)" kind="\([a-z]*\)".*/\1 \2:\3 \4/p' >"$TMP/pb.kinds" + PB_WRONG=""; PB_SPLIT=""; PB_N=0 + for f in $( git -C "$PB" ls-tree -r --name-only sib -- head c1 | grep -v '^c1/\(b\.h\|u\.c\|T2\.java\)$' ); do + case "$f" in head/*) bp="sib/${f#head/}"; hp="$f";; *) bp="$f"; hp="";; esac + ln=0 + while IFS= read -r line || [ -n "$line" ]; do + ln=$((ln+1)) + case "$line" in *probeName*) ;; *) continue;; esac + want=ref; case "$line" in *@D*) want=def;; esac + case "$f:$line" in *.md:"# probeName") want=def;; esac # a markdown heading is a section symbol (no room for a marker) + bk="$( awk -v k="$bp:$ln" '$1=="sib" && $2==k {print $3}' "$TMP/pb.kinds" | head -1 )" + PB_N=$((PB_N+1)) + [ "$bk" = "$want" ] || PB_WRONG="$PB_WRONG [$bp:$ln $bk want $want]" + if [ -n "$hp" ]; then + hk="$( awk -v k="$hp:$ln" '$1=="HEAD" && $2==k {print $3}' "$TMP/pb.kinds" | head -1 )" + [ "$hk" = "$bk" ] || PB_SPLIT="$PB_SPLIT [$bp:$ln branch $bk HEAD $hk]" + fi + done < <( git -C "$PB" show "sib:$f" ) + done + { [ "$PB_N" -ge 50 ] && [ -z "$PB_WRONG" ]; } \ + && ok "whereis (parsed labels): $PB_N marked lines over C, C++, ObjC, Python, TS, JS, Java, Ruby, Go, Rust, Swift, C#, Bash and Markdown, plus the 12 C1 definition shapes — every branch row says what its marker says" \ + || no "whereis (parsed labels): branch rows mislabelled (of $PB_N):$PB_WRONG" + [ -z "$PB_SPLIT" ] \ + && ok 'whereis (parsed labels): every sib/ branch row says what HEAD'"'"'s index row says for the same bytes (the same way HEAD is labelled)' \ + || no "whereis (parsed labels): the branch parse and HEAD's index disagree on the same bytes:$PB_SPLIT" + grep -o ']*>' "$TMP/pb.all" | grep -q ' ref_labels="parsed" ' && ! grep -q ' (every parse-worthy blob was read)' \ + || no 'whereis (parsed labels): ref_labels= missing, or an element on an answer the guard never touched' +fi +# the branch-only definitions (rv C1 repro 1): the definition is the answer, and the call beside it is not +CB="$( "$BIN" "$PB" --whereis=clampBudget --whereis-listing=all --no-cache 2>/dev/null )" +KB="$( "$BIN" "$PB" --whereis=checkBudget --whereis-listing=all --no-cache 2>/dev/null )" +{ printf '%s' "$CB" | grep -q ']* on-head="0"' && printf '%s' "$CB" | grep -q ']*p="c1/b.h" l="1" kind="def"' \ + && printf '%s' "$CB" | grep -q ']*p="c1/u.c" l="1" kind="ref"' \ + && printf '%s' "$KB" | grep -q ']*p="c1/T2.java" l="2" kind="def"'; } \ + && ok 'whereis (branch-only): `#define clampBudget( x )` and `@Test(timeout = 10) public void checkBudget()` read kind="def" on the only ref holding them; the call beside the macro reads kind="ref"' \ + || { no 'whereis (branch-only): a branch-only definition lost its label, or its call site gained one'; printf '%s\n%s\n' "$CB" "$KB" | sed 's///'; } +# ref_labels= rides only an answer with another ref's row: a repo with no other ref answers as before +NR="$TMP/norefs"; mkdir -p "$NR"; git -C "$NR" init -q -b main >/dev/null 2>&1; git -C "$NR" config commit.gpgsign false +printf 'int lonelyName( int a ) { return a; }\nint z( void ) { return lonelyName( 1 ); }\n' >"$NR/x.c" +git -C "$NR" add -A >/dev/null 2>&1; git -C "$NR" commit -qm one >/dev/null 2>&1 +NRO="$( "$BIN" "$NR" --whereis=lonelyName --no-cache 2>/dev/null )"; rcNr=$? +[ $rcNr -eq 0 ] && printf '%s' "$NRO" | grep -q ' rides an answer without a branch row' +# CLI = MCP: the MCP whereis default is byte-identical to the CLI default on the parsed fixture +if command -v python3 >/dev/null 2>&1; then + PBD="$( "$BIN" "$PB" --whereis=probeName --no-cache 2>/dev/null )" + PBM="$( printf '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"whereis","arguments":{"path":"%s","symbol":"probeName"}}}\n' "$PB" \ + | "$BIN" --mcp 2>/dev/null | python3 -c 'import sys,json +for line in sys.stdin: + line=line.strip() + if not line: continue + d=json.loads(line); c=d.get("result",{}).get("content") + if c: print(c[0].get("text",""),end="")' )" + [ -n "$PBD" ] && [ "$PBD" = "$PBM" ] \ + && ok 'whereis (parsed labels): MCP whereis serves the CLI default byte for byte (one labeller on both surfaces)' \ + || no 'whereis (parsed labels): the MCP answer differs from the CLI default on the parsed fixture' +else + printf ' SKIP whereis (parsed labels) MCP twin (no python3)\n' +fi + +# ── 5d) the RUNAWAY GUARD on the parse batch (kWhereisParseMaxBlobs = 256) and what it leaves ───────────────────────── +# 300 distinct blobs on one branch, each a one-line definition of guardName (g.h is HEAD's own blob there: mirrored, never +# in the batch): the batch takes 256 (most rows first, then path: f000..f255), the other 44 confirm NOTHING — their parse-worthy rows read kind="text", never kind="def", and +# says so. --detail=1 lifts the guard: all 300 are definitions and the +# element is gone. The full legend carries the UNPARSED clause on exactly that answer and stays well-formed XML. +GG="$TMP/guard"; mkdir -p "$GG"; git -C "$GG" init -q -b main >/dev/null 2>&1; git -C "$GG" config commit.gpgsign false +printf 'int guardName( int a );\n' >"$GG/g.h"; git -C "$GG" add -A >/dev/null 2>&1; git -C "$GG" commit -qm base >/dev/null 2>&1 +git -C "$GG" checkout -qb many >/dev/null 2>&1 +i=0; while [ $i -lt 300 ]; do n=$( printf '%03d' $i ); printf 'int guardName( int a ) { return a + %d; }\n' $i >"$GG/f$n.c"; i=$((i+1)); done +git -C "$GG" add -A >/dev/null 2>&1; git -C "$GG" commit -qm many >/dev/null 2>&1; git -C "$GG" checkout -q main >/dev/null 2>&1 +GA="$( "$BIN" "$GG" --whereis=guardName --whereis-listing=all --limit=1000 --no-cache 2>/dev/null )"; rcGa=$? +GD="$( "$BIN" "$GG" --whereis=guardName --whereis-listing=all --limit=1000 --detail=1 --no-cache 2>/dev/null )"; rcGd=$? +GT="$( printf '%s' "$GA" | sed 's///' | tr '<' '\n' | grep '^hit ref="many" .* p="f' | grep -c 'kind="text"' )" +GF="$( printf '%s' "$GA" | sed 's///' | tr '<' '\n' | grep '^hit ref="many" .* p="f' | grep -c 'kind="def"' )" +GTL="$( printf '%s' "$GA" | sed 's///' | tr '<' '\n' | grep '^hit ref="many" ' | grep 'kind="text"' | sed -n 's/.* p="\([^"]*\)".*/\1/p' | sort | sed -n '1p;$p' | tr '\n' ' ' )" +if [ $rcGa -ne 0 ] || [ $rcGd -ne 0 ] || ! printf '%s' "$GA" | grep -q ' root (rc $rcGa / $rcGd)" +else + { [ "$GT" = "44" ] && [ "$GF" = "256" ] && [ "$GTL" = "f256.c f299.c " ] \ + && printf '%s' "$GA" | grep -q ''; } \ + && ok 'whereis (guard): 256 parsed (f000..f255, most rows first then path), 44 left: their rows read kind="text" (never def) and discloses them' \ + || { no "whereis (guard): want 256 def + 44 text rows (f256..f299) and the element; got def=$GF text=$GT [$GTL]"; printf '%s' "$GA" | grep -o ']*>'; } + { [ "$( printf '%s' "$GD" | sed 's///' | tr '<' '\n' | grep '^hit ref="many" .* p="f' | grep -c 'kind="def"' )" = "300" ] \ + && ! printf '%s' "$GD" | grep -q 'kind="text"' && ! printf '%s' "$GD" | grep -q '' \ + || no 'whereis (guard): --detail=1 did not parse every blob' + GL="$( "$BIN" "$GG" --whereis=guardName --legend=full --no-cache 2>/dev/null )" + GC="$( "$BIN" "$GG" --whereis=guardName --legend=compact --no-cache 2>/dev/null )" + { printf '%s' "$GL" | grep -q 'UNPARSED: the unparsed element' && printf '%s' "$GC" | grep -q 'unparsed blobs=N' && printf '%s' "$GC" | grep -q 'unparsed rows=N' \ + && printf '%s' "$GC" | grep -q 'ref_labels=parsed' && ! printf '%s' "$GD" | grep -q 'UNPARSED'; } \ + && ok 'whereis (guard): the full legend explains and the compact dictionary reads blobs=/rows=/ref_labels= on the answer that carries them (and not on the --detail=1 answer)' \ + || no 'whereis (guard): a legend lacks the / ref_labels= readings, or carries them where the element is absent' + if command -v xmllint >/dev/null 2>&1; then + printf '%s' "$GL" | xmllint --noout - 2>/dev/null \ + && ok 'whereis (guard): the full-legend page with the UNPARSED clause spliced in is well-formed XML (checklist 25)' \ + || no 'whereis (guard): the full-legend page with the UNPARSED clause is malformed XML' + else + printf ' SKIP whereis (guard): xmllint absent — the UNPARSED clause'"'"'s well-formedness is not checked\n' + fi +fi + +# ── 5e) every ref's tree from content-addressed tree objects (crossref.h listTreesOfRefs): ls-tree's own spelling ──── +# One branch holds files whose NAMES git must quote under core.quotepath=false (a double quote, a backslash, a tab) and +# ones it must not (a space, UTF-8, a leading dash). The p= of every row equals `git ls-tree -r` for that ref, path for +# path (XML-unescaped). RED on any walk that spells a path its own way. +OD="$TMP/oddnames"; mkdir -p "$OD/d"; git -C "$OD" init -q -b main >/dev/null 2>&1; git -C "$OD" config commit.gpgsign false +printf 'int oddName( int a );\n' >"$OD/d/plain.h"; git -C "$OD" add -A >/dev/null 2>&1; git -C "$OD" commit -qm base >/dev/null 2>&1 +git -C "$OD" checkout -qb odd >/dev/null 2>&1 +for nm in 'q"uote.h' 'back\slash.h' "$( printf 'ta\tb.h' )" 'sp ace.h' 'caf'"$( printf '\303\251' )"'.h' -- '-lead.h'; do + [ "$nm" = "--" ] && continue + printf 'int oddName( int a );\n' >"$OD/d/$nm" +done +git -C "$OD" add -A >/dev/null 2>&1; git -C "$OD" commit -qm odd >/dev/null 2>&1; git -C "$OD" checkout -q main >/dev/null 2>&1 +OO="$( "$BIN" "$OD" --whereis=oddName --whereis-listing=all --no-cache 2>/dev/null )"; rcOo=$? +OGOT="$( printf '%s' "$OO" | sed 's///' | tr '<' '\n' | grep '^hit ref="odd" ' | sed -n 's/.* p="\([^"]*\)" l=.*/\1/p' \ + | sed 's/"/"/g; s/'/'"'"'/g; s/<//g; s/&/\&/g' | sort )" +OWANT="$( git -c core.quotepath=false -C "$OD" ls-tree -r --name-only odd | sort )" +{ [ $rcOo -eq 0 ] && [ "$( printf '%s\n' "$OWANT" | grep -c . )" -ge 6 ] && [ "$OGOT" = "$OWANT" ]; } \ + && ok 'whereis (tree walk): every row'"'"'s p= is git ls-tree'"'"'s own spelling — quoted (", \, tab) and unquoted (space, UTF-8, leading dash) alike' \ + || { no "whereis (tree walk): p= differs from git ls-tree's spelling (rc $rcOo)"; printf 'got:\n%s\nwant:\n%s\n' "$OGOT" "$OWANT"; } + if command -v xmllint >/dev/null 2>&1; then if printf '%s' "$SW" | xmllint --noout - 2>/dev/null; then ok "capped stray-content still G4 clean"; else no "capped stray-content XML malformed"; fi if printf '%s' "$W1" | xmllint --noout - 2>/dev/null; then ok "paged whereis still G4 clean"; else no "paged whereis XML malformed"; fi diff --git a/test/mcpmanifestcheck.sh b/test/mcpmanifestcheck.sh index e62877ba5..e81968bdf 100755 --- a/test/mcpmanifestcheck.sh +++ b/test/mcpmanifestcheck.sh @@ -310,6 +310,9 @@ tools = json.loads( line )[ "result" ][ "tools" ] # 46,732: the quality_delta description's twelfth kind, +61 B) — and the merged tree carries both: 46,869 + 61 = 46,930, # every byte attributed (whereis schema 851 B and quality_delta's description as their lanes measured them; no other # tool moved; knob-honesty and the path-gap fix touch no tool description or schema). Headroom after this line: 20 B. +# RE-MEASURED 2026-10-10 (lane whereis-defs-fix fix round 2), CEILING UNMOVED: 46,930 -> 46,925 B. whereis's description says +# what labels another ref's row now (a parser, not a lexical heuristic) in the same 701 characters — (1c) holds — and the +# sentence it replaced carried an em dash, six bytes as JSON. Every other tool unchanged. Headroom after this line: 25 B. CEILING = 46950 manifest = len( json.dumps( { "tools": tools }, separators = ( ",", ":" ) ) ) descBytes = sum( len( t[ "description" ] ) for t in tools ) diff --git a/test/pagingsweepcheck.sh b/test/pagingsweepcheck.sh index 438efdffc..564201398 100755 --- a/test/pagingsweepcheck.sh +++ b/test/pagingsweepcheck.sh @@ -350,9 +350,10 @@ page_verb "graph-query" ' "$R/alt.cpp" cat > "$R/DOC.md" <<'EOF' # Design @@ -123,10 +126,13 @@ grep -q 'head_labels="index"' "$W" \ && ok '§A7: head_labels="index" — HEAD rows are the parsed answer, and say so' \ || { no '§A7: head_labels="index" missing'; grep -o ']*>' "$W"; } -# ── §A7(ii) the ref-blob side keeps its LEXICAL heuristic — and now reads the same shape correctly ────── -grep -q '^feat-doc def DOC\.md:' "$TMP/rows" \ - && ok '§A7: a branch DOC quoting the signature still reads kind="def" (the lexical residual is intact, and now handles noexcept)' \ - || { no '§A7: the feat-doc DOC.md row is not a def row (the blob-side heuristic still misreads the house style)'; cat "$TMP/rows"; } +# ── §A7(ii) the ref-blob side reads the house style correctly ────────────────────────────────────────── +# Fix round 2 (rule 4, by design): this arm asserted the lexical residual — "a branch DOC quoting the signature still reads +# kind="def"" — and that residual is what the parser removed. Its two halves now: the doc's quoted signature is a ref on the +# branch (as on HEAD), and the noexcept-terminated definition on the branch is a def (the chooser keeps the house style). +{ grep -q '^feat-doc ref DOC\.md:' "$TMP/rows" && ! grep -q '^feat-doc def DOC\.md:' "$TMP/rows" && grep -q '^feat-doc def alt\.cpp:1$' "$TMP/rows"; } \ + && ok '§A7: on the branch, the DOC quoting the signature reads kind="ref" (as on HEAD) and the noexcept-terminated alt.cpp definition reads kind="def"' \ + || { no '§A7: the branch DOC.md row is still a def, or the noexcept-terminated branch definition lost its label'; cat "$TMP/rows"; } # ── §A7(iii) the scan denominator names its noun ──────────────────────────────────────────────────────── { grep -q 'refs_scanned="' "$W" && ! grep -q ']* refs="' "$W"; } \ @@ -134,16 +140,16 @@ grep -q '^feat-doc def DOC\.md:' "$TMP/rows" \ || { no '§A7: whereis root still spells the scan denominator refs='; grep -o ']*>' "$W"; } # determinism + well-formedness of the changed verb -# TWIN on the default listing: the same def rows (HEAD's exactly the index def site, the branch doc's lexical def), no +# TWIN on the default listing: the same def rows (HEAD's exactly the index def site, the branch's alt.cpp definition), no # ref row printed, and the ref rows of the whole list — core.cpp:11 among them — counted exactly by . WD="$TMP/wd.xml" "$BIN" "$R" --whereis=parseArgs --limit=200 >"$WD" 2>/dev/null tr '<' '\n' <"$WD" | sed -n 's/^hit ref="\([^"]*\)".* p="\([^"]*\)" l="\([0-9]*\)" kind="\([a-z]*\)".*/\1 \4 \2:\3/p' >"$TMP/rowsd" NREFALL="$( grep -c ' ref ' "$TMP/rows" )" { [ "$( grep ' def ' "$TMP/rowsd" )" = "$( grep ' def ' "$TMP/rows" )" ] && ! grep -q ' ref ' "$TMP/rowsd" \ - && [ "$( sed -n 's/^HEAD //p' "$TMP/rowsd" | grep '^def ' )" = "def core.cpp:4" ] && grep -q '^feat-doc def DOC\.md:' "$TMP/rowsd" \ + && [ "$( sed -n 's/^HEAD //p' "$TMP/rowsd" | grep '^def ' )" = "def core.cpp:4" ] && grep -q '^feat-doc def alt\.cpp:1$' "$TMP/rowsd" \ && grep -q '^HEAD ref core\.cpp:11$' "$TMP/rows" && grep -q "]*>' "$WD"; } "$BIN" "$R" --whereis=parseArgs --limit=200 --whereis-listing=all >"$TMP/w2.xml" 2>/dev/null From 61cf4866fbee417f2fd76c57e4c6e4a94777d94d Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 07:35:04 -0400 Subject: [PATCH 150/170] whereis: computeWhereis, the tree walk, the blob parse batch and the chooser each split into named steps No output byte moves (row sets of four symbols on this repo equal 93aeab1d's; crossrefcheck 111/111). computeWhereis keeps the order of operations and hands each one to a helper (whereisSitesOf, scanWhereisBlobs, stampWhereisBlob, noteBranchRows); listTreesOfRefs reads root trees, tree levels and one ref's listing in three functions, with a checked cat-file header parser; labelBranchRowsByParse's mirror pass and status switch, parseWorthyLine's two extra tests, definitionLinesInBlobs' query install and worker, and the blob's tags step each get their own function. Co-Authored-By: Claude Opus 5.5 --- src/crossref.h | 607 +++++++++++++++++++++++++----------------- src/ingest_astquery.h | 249 +++++++++-------- 2 files changed, 481 insertions(+), 375 deletions(-) diff --git a/src/crossref.h b/src/crossref.h index e85b686af..c5af74a95 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -114,6 +114,7 @@ #include #include // the worktree overlay: symlink_status / read_symlink / file_size on a changed path #include +#include // parseCatFileHeader: the size field never overflows #include // labelBranchRowsByParse holds quality::headSnapshotIngestMutex around the blob parse #include #include @@ -1731,45 +1732,31 @@ inline bool definitionShaped( std::string_view line, std::string_view sym, std:: // What is still missed is named in definitionShaped's header (a signature wrapped before its parameter list closes, a // name alone on a line with only a type above it…); those definitions stay counted refs when nothing else in their // blob earns it a parse. -inline bool parseWorthyLine( std::string_view line, std::string_view sym, std::size_t at ) +// `name = (…`, `name = function`, `name = async`, `name = lambda`: the name bound by `=` to a function, an arrow or a lambda. +inline bool boundToFunctionAfter( std::string_view line, std::size_t after ) noexcept { - if( at == 0 || definitionShaped( line, sym, at ) ) - { - return true; - } - const std::size_t after = at + sym.size(); - for( const std::string_view opener : { std::string_view( "//" ), std::string_view( "/*" ), std::string_view( " #" ) } ) - { - const std::size_t c = line.find( opener, after ); - if( c != std::string_view::npos && definitionShaped( line.substr( 0, c ), sym, at ) ) - { - return true; - } - } std::size_t k = after; while( k < line.size() && std::isspace( (unsigned char)line[ k ] ) ) { ++k; } - if( k + 1 < line.size() && line[ k ] == '=' && line[ k + 1 ] != '=' && line[ k + 1 ] != '>' ) + if( k + 1 >= line.size() || line[ k ] != '=' || line[ k + 1 ] == '=' || line[ k + 1 ] == '>' ) { - std::size_t v = k + 1; - while( v < line.size() && std::isspace( (unsigned char)line[ v ] ) ) - { - ++v; - } - const std::string_view rhs = line.substr( v ); - if( rhs.starts_with( "(" ) || rhs.starts_with( "function" ) || rhs.starts_with( "async" ) || rhs.starts_with( "lambda" ) ) - { - return true; - } + return false; } - const std::size_t fun = line.find( "fun " ); - if( fun != std::string_view::npos && fun < at ) + std::size_t v = k + 1; + while( v < line.size() && std::isspace( (unsigned char)line[ v ] ) ) { - return true; + ++v; } - const std::size_t hashes = line.find_first_not_of( " \t" ); // `#`s, then spaces, then the name: a heading + const std::string_view rhs = line.substr( v ); + return rhs.starts_with( "(" ) || rhs.starts_with( "function" ) || rhs.starts_with( "async" ) || rhs.starts_with( "lambda" ); +} + +// `#`s, then spaces, then the name at `at`: a markdown heading that opens with it. +inline bool headingOpensWith( std::string_view line, std::size_t at ) noexcept +{ + const std::size_t hashes = line.find_first_not_of( " \t" ); std::size_t h = hashes; while( h < at && line[ h ] == '#' ) { @@ -1778,6 +1765,25 @@ inline bool parseWorthyLine( std::string_view line, std::string_view sym, std::s return hashes != std::string_view::npos && h > hashes && h < at && line.find_first_not_of( " \t", h ) == at; } +inline bool parseWorthyLine( std::string_view line, std::string_view sym, std::size_t at ) +{ + if( at == 0 || definitionShaped( line, sym, at ) ) + { + return true; + } + const std::size_t after = at + sym.size(); + for( const std::string_view opener : { std::string_view( "//" ), std::string_view( "/*" ), std::string_view( " #" ) } ) + { + const std::size_t c = line.find( opener, after ); + if( c != std::string_view::npos && definitionShaped( line.substr( 0, c ), sym, at ) ) + { + return true; // the shape, once the comment after the declarator is set aside + } + } + const std::size_t fun = line.find( "fun " ); + return boundToFunctionAfter( line, after ) || ( fun != std::string_view::npos && fun < at ) || headingOpensWith( line, at ); +} + // Record every whole-word occurrence of `sym` in one blob's bytes, as (line number, trimmed line). // One line's worth of the scan, split out so the line WALK below can stay a plain segmentation with no // duplicated body — the duplication is what let the final-line case drift out of sync in the first place. @@ -1965,6 +1971,33 @@ inline bool parseGitTree( std::string_view bytes, std::size_t idBytes, std::vect return true; } +// A `git cat-file --batch` header, ` `: the type and the size, or size -1 when it is not the header of +// `expected` (an object git reports missing, an empty header because the pipe ended, an answer for another object). +struct CatFileHeader +{ + std::string_view type; + long long size = -1; +}; +inline CatFileHeader parseCatFileHeader( std::string_view header, std::string_view expected ) noexcept +{ + const std::size_t sp2 = header.rfind( ' ' ); + const std::size_t sp1 = ( sp2 == std::string_view::npos || sp2 == 0 ) ? std::string_view::npos : header.rfind( ' ', sp2 - 1 ); + if( sp1 == std::string_view::npos || header.substr( 0, sp1 ) != expected ) + { + return {}; + } + long long size = 0; + for( std::size_t k = sp2 + 1; k < header.size(); ++k ) + { + if( header[ k ] < '0' || header[ k ] > '9' || size > ( std::numeric_limits::max() - 9 ) / 10 ) + { + return {}; + } + size = size * 10 + ( header[ k ] - '0' ); + } + return CatFileHeader{ header.substr( sp1 + 1, sp2 - sp1 - 1 ), sp2 + 1 < header.size() ? size : -1 }; +} + // One `git cat-file --batch` over `shas` (in order): onObject( sha, type, bytes ) per object. False when the batch did not // serve every object in full (a start failure, a missing object, a short read): the caller then cannot trust its walk. template @@ -2006,65 +2039,50 @@ inline bool catFileBatch( const std::string& root, const std::vector missing", an empty header (the pipe ended), or an answer for another object break; } - body.resize( std::size_t( size ) ); - const std::size_t got = size > 0 ? std::fread( body.data(), 1, std::size_t( size ), pipe ) : 0; + body.resize( std::size_t( h.size ) ); + const std::size_t got = h.size > 0 ? std::fread( body.data(), 1, std::size_t( h.size ), pipe ) : 0; (void)std::fgetc( pipe ); // the framing LF after the payload - ok = got == std::size_t( size ) && onObject( shas[ served ], std::string_view( header ).substr( sp1 + 1, sp2 - sp1 - 1 ), - std::string_view( body.data(), got ) ); + ok = got == std::size_t( h.size ) && onObject( shas[ served ], h.type, std::string_view( body.data(), got ) ); } os::pclose( pipe ); os::unlink( listPath.c_str() ); return ok; } -// Every ref's `ls-tree -r` rows, by the level-order walk above; nullopt when the walk cannot answer exactly. -inline std::optional>> listTreesOfRefs( const std::string& root, const std::vector& refs ) +// The root tree of every distinct commit in `tips` (`tree ` heads a commit object), or nullopt. +inline std::optional> rootTreesOf( const std::string& root, std::vector tips ) { - // ls-tree lists the CURRENT DIRECTORY's subtree, relative to it: from a root below the top of the work tree the walk - // would have to start one subtree down and re-spell every path. That case keeps lsTree. - if( !gitCapture( root, "rev-parse --show-prefix 2>/dev/null" ).empty() ) - { - return std::nullopt; - } - std::vector tips; - for( const RefInfo& r : refs ) - { - tips.push_back( r.tip ); - } std::sort( tips.begin(), tips.end() ); tips.erase( std::unique( tips.begin(), tips.end() ), tips.end() ); - gtl::btree_map rootOf; // commit → its tree - const bool commitsRead = catFileBatch( root, tips, [ & ]( const std::string& sha, std::string_view type, std::string_view bytes ) + const bool read = catFileBatch( root, tips, [ & ]( const std::string& sha, std::string_view type, std::string_view bytes ) { - const bool shaped = type == "commit" && bytes.starts_with( "tree " ) && bytes.find( '\n' ) != std::string_view::npos; + const std::size_t nl = bytes.find( '\n' ); + const bool shaped = type == "commit" && bytes.starts_with( "tree " ) && nl != std::string_view::npos; if( !VALIDATE( shaped ) ) { return false; } - rootOf[ sha ] = std::string( bytes.substr( 5, bytes.find( '\n' ) - 5 ) ); - return isBlobSha( rootOf[ sha ] ); + std::string& tree = rootOf[ sha ]; + tree.assign( bytes.substr( 5, nl - 5 ) ); + return isBlobSha( tree ); } ); - if( !commitsRead ) - { - return std::nullopt; - } + return read ? std::optional{ std::move( rootOf ) } : std::nullopt; +} - gtl::btree_map> trees; // every distinct tree object any ref reaches - std::vector level; - for( const auto& [ commit, tree ] : rootOf ) - { - (void)commit; - level.push_back( tree ); - } +using GitTreeTable = gtl::btree_map>; // tree id → its entries, stored order + +// Every tree object reachable from `level` (the root trees), read a level at a time: each level's DISTINCT ids through one +// `git cat-file --batch`, a tree already read never asked for again. nullopt when a level could not be read exactly. +inline std::optional readTreeLevels( const std::string& root, std::vector level ) +{ + GitTreeTable trees; while( !level.empty() ) { std::sort( level.begin(), level.end() ); @@ -2079,7 +2097,7 @@ inline std::optional>> listTreesOfRefs( const st } for( const GitTreeEntry& e : entries ) { - if( e.isTree && trees.find( e.sha ) == trees.end() ) + if( e.isTree ) { next.push_back( e.sha ); } @@ -2092,42 +2110,82 @@ inline std::optional>> listTreesOfRefs( const st return std::nullopt; } level.clear(); - for( std::string& s : next ) + for( std::string& id : next ) { - if( trees.find( s ) == trees.end() ) + if( trees.find( id ) == trees.end() ) { - level.push_back( std::move( s ) ); + level.push_back( std::move( id ) ); } } } + return trees; +} - std::vector> out( refs.size() ); - for( std::size_t i = 0; i < refs.size(); ++i ) +// One ref's `ls-tree -r` rows from the table: depth-first in stored order, a subtree listed where it stands (ls-tree -r's +// own order), each path spelled as ls-tree spells it. +inline std::vector listTreeRows( const GitTreeTable& trees, const std::string& rootTree ) +{ + struct Frame { const std::vector* entries; std::size_t next; std::size_t prefixLen; }; + std::vector out; + std::string prefix; + std::vector stack{ Frame{ &trees.at( rootTree ), 0, 0 } }; + while( !stack.empty() ) { - // depth-first in stored order, a subtree listed where it stands: ls-tree -r's own order - struct Frame { const std::vector* entries; std::size_t next; std::size_t prefixLen; }; - std::string prefix; - std::vector stack{ Frame{ &trees.at( rootOf.at( refs[ i ].tip ) ), 0, 0 } }; - while( !stack.empty() ) + Frame& f = stack.back(); + if( f.next == f.entries->size() ) { - Frame& f = stack.back(); - if( f.next == f.entries->size() ) - { - stack.pop_back(); - continue; - } - const GitTreeEntry& e = ( *f.entries )[ f.next++ ]; - prefix.resize( f.prefixLen ); - prefix += e.name; - if( e.isTree ) - { - const std::size_t childPrefix = prefix.size() + 1; - prefix += '/'; - stack.push_back( Frame{ &trees.at( e.sha ), 0, childPrefix } ); - continue; - } - out[ i ].push_back( RawRow{ gitQuotedPath( prefix ), std::string{}, e.sha } ); + stack.pop_back(); + continue; + } + const GitTreeEntry& e = ( *f.entries )[ f.next++ ]; + prefix.resize( f.prefixLen ); + prefix += e.name; + if( e.isTree ) + { + const std::size_t childPrefix = prefix.size() + 1; + prefix += '/'; + stack.push_back( Frame{ &trees.at( e.sha ), 0, childPrefix } ); + continue; } + out.push_back( RawRow{ gitQuotedPath( prefix ), std::string{}, e.sha } ); + } + return out; +} + +// Every ref's `ls-tree -r` rows, by the level-order walk above; nullopt when the walk cannot answer exactly. +inline std::optional>> listTreesOfRefs( const std::string& root, const std::vector& refs ) +{ + // ls-tree lists the CURRENT DIRECTORY's subtree, relative to it: from a root below the top of the work tree the walk + // would have to start one subtree down and re-spell every path. That case keeps lsTree. + if( !gitCapture( root, "rev-parse --show-prefix 2>/dev/null" ).empty() ) + { + return std::nullopt; + } + std::vector tips; + for( const RefInfo& r : refs ) + { + tips.push_back( r.tip ); + } + const std::optional> rootOf = rootTreesOf( root, tips ); + if( !rootOf ) + { + return std::nullopt; + } + std::vector roots; + for( const auto& [ commit, tree ] : *rootOf ) + { + (void)commit; + roots.push_back( tree ); + } + const std::optional trees = readTreeLevels( root, std::move( roots ) ); + if( !trees ) + { + return std::nullopt; + } + std::vector> out( refs.size() ); + for( std::size_t i = 0; i < refs.size(); ++i ) + { + out[ i ] = listTreeRows( *trees, rootOf->at( refs[ i ].tip ) ); } ENSURES( out.size() == refs.size(), "one listing per ref, in the refs' order (computeWhereis reads slot i as refs[i])" ); return out; @@ -2331,15 +2389,18 @@ inline std::vector branchParseBatch( const std::vector& jobs, const WhereisParseBudget& budget ) +// The jobs a parse is not needed for, labelled here: HEAD's own blob at the same path (when HEAD's rows came from the index) +// takes HEAD's labels line for line; a job with no parse-worthy row, or in no indexed language, holds no definition the +// index would see. Returns which jobs are answered (1) — the rest are the parse batch's to answer. +inline std::vector answerWithoutParse( WhereResult& result, const std::vector& jobs ) { std::vector answered( jobs.size(), 0 ); for( std::size_t j = 0; j < jobs.size(); ++j ) { - BranchParseJob& job = jobs[ j ]; + const BranchParseJob& job = jobs[ j ]; if( job.headEnd > job.headFirst && result.headLabelsFromIndex ) { - std::vector headDefs; // HEAD's rows are ascending by line: one blob, one scan + std::vector headDefs; // HEAD's rows of one blob, ascending by line for( std::size_t r = job.headFirst; r < job.headEnd; ++r ) { if( result.hits[ r ].isDef ) { headDefs.push_back( result.hits[ r ].line ); } @@ -2354,7 +2415,33 @@ inline void labelBranchRowsByParse( WhereResult& result, std::vector batch = branchParseBatch( jobs, answered, budget, result.branchLabels ); + return answered; +} + +// One parsed job's labels from what definitionLinesInBlobs found in its blob. +inline void applyBlobDefinitions( WhereResult& result, const BranchParseJob& job, const BlobDefinitions& found ) +{ + switch( found.status ) + { + case BlobDefsStatus::Parsed: + labelJobRows( result, job, promotedHitLines( job.hitLines, found.lines ), true ); + ++result.branchLabels.parsed; + break; + case BlobDefsStatus::NoGrammar: + case BlobDefsStatus::Unread: + labelJobRows( result, job, {}, true ); // the crawl defines nothing in such a file on HEAD either + break; + case BlobDefsStatus::Failed: + labelJobRows( result, job, {}, false ); + ++result.branchLabels.failed; + break; + } +} + +inline void labelBranchRowsByParse( WhereResult& result, std::vector& jobs, const WhereisParseBudget& budget ) +{ + std::vector answered = answerWithoutParse( result, jobs ); + const std::vector batch = branchParseBatch( jobs, answered, budget, result.branchLabels ); std::vector blobs; blobs.reserve( batch.size() ); for( const std::size_t j : batch ) @@ -2371,23 +2458,8 @@ inline void labelBranchRowsByParse( WhereResult& result, std::vector> bySha; + bool anyEmptyTree = false; // T1: a zero-row listing could be a FAILED one — it forfeits complete= +}; + +// Every ref's tree: from content-addressed tree objects (listTreesOfRefs), or — when that walk cannot answer exactly — +// one `git ls-tree -r` per ref through the stray-content sweep's pool (each worker writes only its own slot). The fan-out +// reads the slots in ref order, so the map (and the answer) is the serial one by construction. +inline WhereisSites whereisSitesOf( const std::string& root, const std::vector& refs, const std::vector& replaced ) +{ + std::vector> trees( refs.size() ); + ParallelSweep treeSweep; + if( std::optional>> walked = listTreesOfRefs( root, refs ) ) + { + trees = std::move( *walked ); // every distinct tree object read once; treeSweep never ran, so isDone holds + } + else + { + DISCLOSE( Diagnostics::answerUnchanged, "lsTree lists the same rows per ref: only slower", + "crossref: the tree-object walk could not read every tree exactly — listing each ref with git ls-tree" ); + parallelIndexed( refs.size(), [ & ]( std::size_t i ) { trees[ i ] = lsTree( root, refs[ i ].tip ); }, treeSweep ); + } + WhereisSites out; + for( std::uint32_t i = 0; i < refs.size(); ++i ) + { + // a slot a worker never reached is an unlisted tree: it forfeits complete= like an empty one + out.anyEmptyTree = out.anyEmptyTree || trees[ i ].empty() || !treeSweep.isDone( i ); + for( const RawRow& r : trees[ i ] ) + { + const bool isReplaced = i == 0 && std::binary_search( replaced.begin(), replaced.end(), r.path ); + out.bySha[ r.bSha ].push_back( WhereisSite{ i, r.path, isReplaced } ); + } + } + return out; +} + +// One blob's scan, done on the stream's own worker thread (streamBlobsInSlices): a pure function of the bytes — its hit +// rows (stamped with no ref yet) and the bytes themselves only when some row is parse-worthy. +struct WhereisBlobScan +{ + bool hasSym = false; // a text blob holding the name's bytes (the cheap reject passed) + std::vector rows; // its hits; empty ⇔ no whole-word occurrence (hasWholeWord) + std::string bytes; // kept only for a parse-worthy blob (labelBranchRowsByParse) +}; + +inline std::vector scanWhereisBlobs( const std::string& root, const std::vector& shas, std::string_view sym, + StreamBlobStats& stats ) +{ + std::vector scans( shas.size() ); + streamBlobsInSlices( root, shas, kWhereisStreamSlices, [ & ]( std::size_t i, const std::string&, std::string_view bytes, bool isText ) + { + if( !isText || bytes.find( sym ) == std::string_view::npos ) + { + return; // cheap reject before the line walk + } + WhereisBlobScan& b = scans[ i ]; + b.hasSym = true; + scanBlobForSymbol( bytes, sym, RefInfo{}, std::string(), b.rows ); + if( std::any_of( b.rows.begin(), b.rows.end(), []( const WhereHit& h ) { return h.parseWorthy; } ) ) + { + b.bytes.assign( bytes ); + } + }, stats ); + return scans; +} + +// HEAD's rows of one blob at one path: the index labels them (relabelHeadHitsFromIndex), and a ref holding the same +// blob at the same path takes those labels (labelBranchRowsByParse's mirror). +struct WhereisHeadRows +{ + std::string_view path; + std::size_t first = 0, end = 0; +}; + +// Rows [firstRow, end of hits) are one non-HEAD site's copy of a blob: add them to the (blob, path) job, opening it +// (with the blob's hit lines, its parse-worthiness, HEAD's rows of the same path and the bytes a parse would need) +// the first time that path is seen for this blob. +inline void noteBranchRows( std::vector& jobs, std::size_t firstJobOfBlob, const WhereResult& result, std::size_t firstRow, + const std::string& sha, const WhereisSite& site, std::span headRows, const WhereisBlobScan& scan ) +{ + auto job = std::find_if( jobs.begin() + std::ptrdiff_t( firstJobOfBlob ), jobs.end(), [ & ]( const BranchParseJob& j ) { return j.path == site.path; } ); + if( job == jobs.end() ) + { + BranchParseJob fresh; + fresh.sha = sha; + fresh.path = site.path; + for( std::size_t r = firstRow; r < result.hits.size(); ++r ) + { + fresh.hitLines.push_back( result.hits[ r ].line ); + fresh.shaped = fresh.shaped || result.hits[ r ].parseWorthy; // parseWorthyLine(): worth a parse + } + const auto headCopy = std::find_if( headRows.begin(), headRows.end(), [ & ]( const WhereisHeadRows& h ) { return h.path == site.path; } ); + if( headCopy != headRows.end() ) + { + fresh.headFirst = headCopy->first; + fresh.headEnd = headCopy->end; + } + if( fresh.shaped ) + { + fresh.bytes = scan.bytes; // kept for the parse batch (a mirror of HEAD's labels may yet make it unneeded) + } + jobs.push_back( std::move( fresh ) ); + job = jobs.end() - 1; + } + for( std::size_t r = firstRow; r < result.hits.size(); ++r ) + { + job->rows.push_back( r ); + } +} + +// One blob's rows, stamped for every (ref, path) holding it (HEAD first: refs[0]), and the facts HEAD's sites carry. +inline void stampWhereisBlob( WhereResult& result, const std::vector& refs, const std::string& sha, const std::vector& sites, + const WhereisBlobScan& scan, std::vector& jobs ) +{ + const std::size_t firstJobOfBlob = jobs.size(); + std::vector headRows; + for( const WhereisSite& s : sites ) + { + const bool head = refs[ s.refIndex ].name == "HEAD"; // WhereHit::inCheckout's own test, so a job never holds a checkout row + result.headHolds = result.headHolds || ( head && ( !s.replaced || !scan.rows.empty() ) ); + result.onHead = result.onHead || ( head && !s.replaced ); + if( s.replaced ) + { + continue; + } + const std::size_t firstRow = result.hits.size(); + for( const WhereHit& row : scan.rows ) + { + WhereHit& h = result.hits.emplace_back( row ); + h.ref = refs[ s.refIndex ].name; + h.tip = refs[ s.refIndex ].tip; + h.date = refs[ s.refIndex ].date; + h.path = s.path; + } + if( head ) + { + headRows.push_back( WhereisHeadRows{ s.path, firstRow, result.hits.size() } ); + } + else if( firstRow < result.hits.size() ) + { + noteBranchRows( jobs, firstJobOfBlob, result, firstRow, sha, s, headRows, scan ); + } + } +} + // The whole --whereis computation. Every ref's FULL tree is enumerated, but each distinct blob is READ once: // a `(blob sha → the paths/refs that point at it)` fan-out map is built first, then one streaming pass scans // each blob's bytes and attributes its hits to every (ref, path) that shares it. That is the content-addressed @@ -2940,151 +3167,29 @@ inline WhereResult computeWhereis( const std::string& root, std::string_view sym result.worktree = worktree.state; result.onHead = worktree.mentions; - // blob sha → every (ref index, path) that points at it, in a deterministic order. `replaced`: HEAD's blob for a - // path the working tree changed — it prints no row (the overlay answers for that path), but it is still read for - // ONE fact the fate lane needs: whether HEAD's COMMITTED tree holds the name (headHolds; review M8). - struct Site { std::uint32_t refIndex; std::string path; bool replaced; }; - bool anyEmptyTree = false; // T1: a zero-row ls-tree could be a FAILED listing — it forfeits complete= - gtl::btree_map> sites; - { - // One `git ls-tree -r` process per ref, run through the same pool the stray-content sweep uses: each worker - // writes only its own slot, and the fan-out below reads the slots in ref order, so the map (and the answer) - // is the serial one by construction. Measured on a 153-ref repo: 7.0 s of serial ls-tree wall → 2.1 s. - std::vector> trees( refs.size() ); - ParallelSweep treeSweep; - if( std::optional>> walked = listTreesOfRefs( root, refs ) ) - { - trees = std::move( *walked ); // every distinct tree object read once (listTreesOfRefs); treeSweep never ran, so isDone holds - } - else - { - DISCLOSE( Diagnostics::answerUnchanged, "lsTree lists the same rows per ref: only slower", - "crossref: the tree-object walk could not read every tree exactly — listing each ref with git ls-tree" ); - parallelIndexed( refs.size(), [ & ]( std::size_t i ) { trees[ i ] = lsTree( root, refs[ i ].tip ); }, treeSweep ); - } - for( std::uint32_t i = 0; i < refs.size(); ++i ) - { - const std::vector& rows = trees[ i ]; - if( rows.empty() || !treeSweep.isDone( i ) ) - { - anyEmptyTree = true; // a slot a worker never reached is an unlisted tree: it forfeits complete= like an empty one - } - for( const RawRow& r : rows ) - { - sites[ r.bSha ].push_back( Site{ i, r.path, i == 0 && std::binary_search( worktree.replaced.begin(), worktree.replaced.end(), r.path ) } ); - } - } - } + const WhereisSites sites = whereisSitesOf( root, refs, worktree.replaced ); + const bool anyEmptyTree = sites.anyEmptyTree; std::vector shas; - shas.reserve( sites.size() ); - for( const auto& [ sha, s ] : sites ) { (void)s; shas.push_back( sha ); } + shas.reserve( sites.bySha.size() ); + for( const auto& [ sha, s ] : sites.bySha ) { (void)s; shas.push_back( sha ); } result.distinctBlobs = shas.size(); result.refsScanned = refs.size() - 1; // HEAD is not one of the swept refs - // The scan of every blob holding the name runs on the stream's own worker threads (streamBlobsInSlices): a pure - // function of the bytes — its hit rows (stamped with no ref yet), and the bytes themselves only when some row is - // parse-worthy. The loop after it walks the blobs in sha order on this thread, so the answer is the serial one. - struct BlobScan - { - bool hasSym = false; // a text blob holding the name's bytes (the cheap reject passed) - std::vector rows; // its hits; empty ⇔ no whole-word occurrence (hasWholeWord) - std::string bytes; // kept only for a parse-worthy blob (labelBranchRowsByParse) - }; - std::vector scans( shas.size() ); StreamBlobStats blobStats; // T1: the degrade census that decides whether this scan may claim complete= - streamBlobsInSlices( root, shas, kWhereisStreamSlices, [ & ]( std::size_t i, const std::string&, std::string_view bytes, bool isText ) - { - if( !isText || bytes.find( sym ) == std::string_view::npos ) - { - return; // cheap reject before the line walk - } - BlobScan& b = scans[ i ]; - b.hasSym = true; - scanBlobForSymbol( bytes, sym, RefInfo{}, std::string(), b.rows ); - if( std::any_of( b.rows.begin(), b.rows.end(), []( const WhereHit& h ) { return h.parseWorthy; } ) ) - { - b.bytes.assign( bytes ); - } - }, blobStats ); std::vector branchJobs; // one per distinct (blob, path) behind a non-HEAD row: labelBranchRowsByParse - for( std::size_t blobIndex = 0; blobIndex < shas.size(); ++blobIndex ) { - const BlobScan& scan = scans[ blobIndex ]; - if( !scan.hasSym ) - { - continue; - } - const std::string& sha = shas[ blobIndex ]; - const auto it = sites.find( sha ); - if( it == sites.end() ) - { - continue; - } - const std::vector& blobRows = scan.rows; - const std::size_t firstJobOfBlob = branchJobs.size(); - struct HeadRows { std::string_view path; std::size_t first, end; }; - std::vector headRows; // HEAD's rows of THIS blob, per path (HEAD is the first site: refs[0]) - for( const Site& s : it->second ) + const std::vector scans = scanWhereisBlobs( root, shas, sym, blobStats ); + for( std::size_t blobIndex = 0; blobIndex < shas.size(); ++blobIndex ) // sha order, on this thread: the serial answer { - const bool head = refs[ s.refIndex ].name == "HEAD"; - result.headHolds = result.headHolds || ( head && ( !s.replaced || !blobRows.empty() ) ); - result.onHead = result.onHead || ( head && !s.replaced ); - if( s.replaced ) - { - continue; - } - const std::size_t firstRow = result.hits.size(); - for( const WhereHit& row : blobRows ) - { - WhereHit& h = result.hits.emplace_back( row ); - h.ref = refs[ s.refIndex ].name; - h.tip = refs[ s.refIndex ].tip; - h.date = refs[ s.refIndex ].date; - h.path = s.path; - } - if( head ) - { - headRows.push_back( HeadRows{ s.path, firstRow, result.hits.size() } ); // the index labels these (relabelHeadHitsFromIndex) - continue; - } - if( firstRow == result.hits.size() ) - { - continue; - } - auto job = std::find_if( branchJobs.begin() + std::ptrdiff_t( firstJobOfBlob ), branchJobs.end(), - [ & ]( const BranchParseJob& j ) { return j.path == s.path; } ); - if( job == branchJobs.end() ) - { - BranchParseJob fresh; - fresh.sha = sha; - fresh.path = s.path; - for( std::size_t r = firstRow; r < result.hits.size(); ++r ) - { - fresh.hitLines.push_back( result.hits[ r ].line ); - fresh.shaped = fresh.shaped || result.hits[ r ].parseWorthy; // parseWorthyLine(): worth a parse - } - const auto headCopy = std::find_if( headRows.begin(), headRows.end(), [ & ]( const HeadRows& h ) { return h.path == s.path; } ); - if( headCopy != headRows.end() ) - { - fresh.headFirst = headCopy->first; - fresh.headEnd = headCopy->end; - } - if( fresh.shaped ) - { - fresh.bytes = scan.bytes; // kept for the parse batch (a mirror of HEAD's labels may yet make it unneeded) - } - branchJobs.push_back( std::move( fresh ) ); - job = branchJobs.end() - 1; - } - for( std::size_t r = firstRow; r < result.hits.size(); ++r ) + const auto it = sites.bySha.find( shas[ blobIndex ] ); + if( scans[ blobIndex ].hasSym && it != sites.bySha.end() ) { - job->rows.push_back( r ); + stampWhereisBlob( result, refs, shas[ blobIndex ], it->second, scans[ blobIndex ], branchJobs ); } } } - std::vector().swap( scans ); // the rows are stamped and the parse-worthy bytes copied into their jobs // T1: exhaustive-over-text iff every sha streamed clean AND no ref's tree listing was suspect. An empty // sha list (every scanned tree empty, or none) trivially streamed clean — anyEmptyTree covers that shape. @@ -3659,13 +3764,19 @@ inline std::string whereisSharedAttr( const ListedHits& listed, const WhereHit& return ( listed.attr == "defs" && h.defRefs >= 2 ) ? ( " refs=\"" + std::to_string( h.defRefs ) + "\"" ) : std::string(); } +// A row the definitions page lists: a definition, or a parse-worthy line on another ref no parse read (kind="text"). +inline bool whereisDefsPageRow( const WhereHit& h ) noexcept +{ + return h.isDef || h.unconfirmed; +} + inline ListedHits listedHits( const WhereResult& res, WhereisListing listing ) { EXPECTS( listing != WhereisListing::ShorterOfDefsAll, "the default is resolved to Defs or All (whereisServedListing) before the rows are picked" ); // The definitions page lists the kind="def" rows AND the kind="text" ones (parse-worthy lines on another ref // that no parse confirmed or refuted, labelBranchRowsByParse): a possible definition is printed, labelled, never // counted away among the references. listing=refs and are the kind="ref" rows exactly. - const std::size_t defs = std::size_t( std::count_if( res.hits.begin(), res.hits.end(), []( const WhereHit& h ) { return h.isDef || h.unconfirmed; } ) ); + const std::size_t defs = std::size_t( std::count_if( res.hits.begin(), res.hits.end(), whereisDefsPageRow ) ); const std::size_t refs = res.hits.size() - defs; ListedHits out; // Defs with nothing to elide (no ref row) IS the whole list; Defs with no def row would list nothing but a count, @@ -3684,7 +3795,7 @@ inline ListedHits listedHits( const WhereResult& res, WhereisListing listing ) for( std::size_t i = 0; i < res.hits.size(); ++i ) { const WhereHit& h = res.hits[ i ]; - if( ( h.isDef || h.unconfirmed ) != wantDefs ) { continue; } + if( whereisDefsPageRow( h ) != wantDefs ) { continue; } if( wantDefs && h.foldedDef ) { ++folded; continue; } out.rows.push_back( i ); } diff --git a/src/ingest_astquery.h b/src/ingest_astquery.h index 38cc690c5..d3a38808d 100644 --- a/src/ingest_astquery.h +++ b/src/ingest_astquery.h @@ -2165,6 +2165,49 @@ struct BlobDefsSlot } }; +// The worker's non-markdown extraction over one blob, in its order: the .astro frontmatter restriction, the parse, the +// member-macro re-parse when the first parse holds errors, the `#if 0` ranges, the definitions-only tags pass. False when +// the slot is already final (a template-only .astro, or a degrade it discloses). +inline bool tagsDefinitionsOfBlob( std::string_view bytes, const LangEntry& le, TSParser* parser, TSQueryCursor* cursor, MemberMacroReparse& macroWork, + std::vector& defs, BlobDefinitions& slot ) +{ + IncludedRangeGuard rangeGuard; + const AstroFrontmatter astro = restrictAstroToFrontmatter( parser, le, bytes, rangeGuard ); + if( astro == AstroFrontmatter::Unterminated ) + { + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ExtractPartial, "whereis: an .astro blob opens a frontmatter fence it never closes — not extracted" ); + return false; + } + if( astro == AstroFrontmatter::None ) + { + slot.status = BlobDefsStatus::Parsed; // a template-only .astro: the index extracts nothing from it either + return false; + } + TreeGuard tree( parseTree( parser, bytes ) ); + if( tree.get() == nullptr ) + { + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ParseNull, "whereis: a blob did not parse — its rows confirm no definition" ); + return false; + } + if( ts_node_has_error( ts_tree_root_node( tree.get() ) ) ) // a clean parse has no error bytes: the re-parse never adopts + { + (void)measureHealthAdoptingMemberMacroReparse( parser, le.lang, bytes, tree, macroWork ); // may adopt it, as the worker does + } + const TSNode root = ts_tree_root_node( tree.get() ); + const std::vector ppDead = preprocDeadRangesFor( le, root, bytes ); + std::vector refs; + std::vector binds; + std::vector incs; + ExtractShortfall shortfall; + captureTagsFacts( cursor, le, 0, bytes, root, defs, refs, binds, incs, ppDead, shortfall, TagsCapture::DefinitionsOnly ); + if( shortfall.isShort ) + { + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ExtractPartial, "whereis: the tags pass over a blob was partial — its rows confirm no definition" ); + return false; + } + return true; +} + // One blob through the crawl's language rule and the parse-pool worker's extraction steps, in the worker's order. // `scan` is a one-slot scan the nesting refusal records into (its --skipped bookkeeping has no reader here: the // slot's Unread status is this blob's disclosure). @@ -2202,10 +2245,7 @@ inline void definitionLinesOfOneBlob( const BlobText& blob, std::string_view nam DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::GrammarRejected, "whereis: a blob's grammar could not be set — its rows confirm no definition" ); return; } - std::vector defs; - std::vector refs; - std::vector binds; - std::vector incs; + std::vector defs; if( le->lang == Lang::Markdown ) { TreeGuard mdTree( parseTree( parser, blob.bytes ) ); @@ -2214,41 +2254,12 @@ inline void definitionLinesOfOneBlob( const BlobText& blob, std::string_view nam DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ParseNull, "whereis: a markdown blob did not parse — its rows confirm no definition" ); return; } + std::vector refs; extractMarkdown( 0, blob.bytes, fs::path( pathText ).stem().string(), ts_tree_root_node( mdTree.get() ), defs, refs ); } - else + else if( !tagsDefinitionsOfBlob( blob.bytes, *le, parser, cursor, macroWork, defs, slot ) ) { - IncludedRangeGuard rangeGuard; - const AstroFrontmatter astro = restrictAstroToFrontmatter( parser, *le, blob.bytes, rangeGuard ); - if( astro == AstroFrontmatter::Unterminated ) - { - DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ExtractPartial, "whereis: an .astro blob opens a frontmatter fence it never closes — not extracted" ); - return; - } - if( astro == AstroFrontmatter::None ) - { - slot.status = BlobDefsStatus::Parsed; // a template-only .astro: the index extracts nothing from it either - return; - } - TreeGuard tree( parseTree( parser, blob.bytes ) ); - if( tree.get() == nullptr ) - { - DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ParseNull, "whereis: a blob did not parse — its rows confirm no definition" ); - return; - } - if( ts_node_has_error( ts_tree_root_node( tree.get() ) ) ) // a clean parse has no error bytes: the re-parse never adopts - { - (void)measureHealthAdoptingMemberMacroReparse( parser, le->lang, blob.bytes, tree, macroWork ); // may adopt it, as the worker does - } - const TSNode root = ts_tree_root_node( tree.get() ); - const std::vector ppDead = preprocDeadRangesFor( *le, root, blob.bytes ); - ExtractShortfall shortfall; - captureTagsFacts( cursor, *le, 0, blob.bytes, root, defs, refs, binds, incs, ppDead, shortfall, TagsCapture::DefinitionsOnly ); - if( shortfall.isShort ) - { - DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::ExtractPartial, "whereis: the tags pass over a blob was partial — its rows confirm no definition" ); - return; - } + return; // the slot says why (template-only .astro: Parsed with nothing; a degrade: Failed) } // The model build's steps that decide which captured definitions become SYMBOLS (ingest.cpp 3a..3a-ter), so a // blob answers what HEAD's index answers for the same bytes: overlapping patterns collapse to one definition, an @@ -2268,119 +2279,103 @@ inline void definitionLinesOfOneBlob( const BlobText& blob, std::string_view nam slot.status = BlobDefsStatus::Parsed; } -std::vector definitionLinesInBlobs( std::span blobs, std::string_view name ) +// The query set, single-threaded and BEFORE any worker: every grammar a blob can reach — its extension's, plus ObjC's for a +// .h (the content reroute is decided inside the worker). compiledQueryFor only ever READS the cache, so a grammar is +// compiled here once and the cache owns it from then on (CompiledQueryCache frees it at teardown). +inline void installBlobQueries( std::span blobs ) { - std::vector out( blobs.size() ); - if( blobs.empty() ) + std::vector want; + const auto need = [ & ]( const LangEntry* le ) { - return out; - } - warmFieldIdTable(); // idempotent; the AST walks behind captureTagsFacts read it lock-free - - // The query set, single-threaded and BEFORE any worker: every grammar a blob can reach — its extension's, plus - // ObjC's for a .h (the content reroute is decided inside the worker). compiledQueryFor only ever READS the cache. - { - std::vector want; - const auto need = [ & ]( const LangEntry* le ) + if( le == nullptr || le->grammar == nullptr || le->querySub.empty() || compiledQueryCache().find( le->grammar() ) != compiledQueryCache().end() ) { - if( le == nullptr || le->grammar == nullptr || le->querySub.empty() ) - { - return; - } - const TSLanguage* g = le->grammar(); - if( compiledQueryCache().find( g ) != compiledQueryCache().end() ) - { - return; - } - if( std::none_of( want.begin(), want.end(), [ & ]( const LangEntry* w ) { return w->grammar() == g; } ) ) - { - want.push_back( le ); - } - }; - for( const BlobText& b : blobs ) + return; + } + if( std::none_of( want.begin(), want.end(), [ & ]( const LangEntry* w ) { return w->grammar() == le->grammar(); } ) ) { - const std::string ext = lowerExtensionOf( b.path ); - need( lookupLang( ext ) ); - if( ext == ".h" ) - { - need( lookupLang( ".m" ) ); - } + want.push_back( le ); } - for( const LangEntry* le : want ) + }; + for( const BlobText& b : blobs ) + { + const std::string ext = lowerExtensionOf( b.path ); + need( lookupLang( ext ) ); + if( ext == ".h" ) { - if( TSQuery* q = compileQueryStandalone( *le ); q != nullptr ) // a compile error stays absent: captureTagsFacts discloses it - { - compiledQueryCache()[ le->grammar() ] = q; // the cache owns it from here (CompiledQueryCache frees it at teardown) - } + need( lookupLang( ".m" ) ); } } - - std::vector walkOrder( blobs.size() ); - std::iota( walkOrder.begin(), walkOrder.end(), std::uint32_t( 0 ) ); - std::stable_sort( walkOrder.begin(), walkOrder.end(), [ & ]( std::uint32_t a, std::uint32_t b ) noexcept - { return blobs[ a ].bytes.size() > blobs[ b ].bytes.size(); } ); // biggest first: no straggler at the tail - - unsigned hw = std::thread::hardware_concurrency(); - if( hw == 0 ) + for( const LangEntry* le : want ) { - hw = 1; + if( TSQuery* q = compileQueryStandalone( *le ); q != nullptr ) // a compile error stays absent: captureTagsFacts discloses it + { + compiledQueryCache()[ le->grammar() ] = q; + } } - const unsigned threadCount = static_cast( std::min( hw, blobs.size() ) ); - std::atomic nextSlot{ 0 }; - const auto worker = [ & ]() noexcept +} + +// One parse worker: its own parser, query cursor and scratch, claiming blobs in `walkOrder` until none is left. Each blob +// writes only its own slot of `out`. +inline void definitionLinesWorker( std::span blobs, std::string_view name, std::span walkOrder, + std::atomic& nextSlot, std::span out ) noexcept +{ + ParserGuard pg; + TSQueryCursor* cursor = ts_query_cursor_new(); + if( pg.p == nullptr || cursor == nullptr ) { - ParserGuard pg; - TSQueryCursor* cursor = ts_query_cursor_new(); - if( pg.p == nullptr || cursor == nullptr ) + for( std::size_t s = nextSlot.fetch_add( 1, std::memory_order_relaxed ); s < blobs.size(); s = nextSlot.fetch_add( 1, std::memory_order_relaxed ) ) { - for( std::size_t s = nextSlot.fetch_add( 1, std::memory_order_relaxed ); s < blobs.size(); s = nextSlot.fetch_add( 1, std::memory_order_relaxed ) ) - { - DISCLOSE( ( BlobDefsSlot{ out[ walkOrder[ s ] ] } ), BlobDefsSlot::DisclosureWhy::NoParser, "whereis: no tree-sitter parser for a blob worker" ); - } - if( cursor != nullptr ) - { - ts_query_cursor_delete( cursor ); - } - return; + DISCLOSE( ( BlobDefsSlot{ out[ walkOrder[ s ] ] } ), BlobDefsSlot::DisclosureWhy::NoParser, "whereis: no tree-sitter parser for a blob worker" ); } - MemberMacroReparse macroWork; - IngestFileScan scan = makeFileScan( std::vector{ std::string() } ); - for( ;; ) + if( cursor != nullptr ) { - const std::size_t s = nextSlot.fetch_add( 1, std::memory_order_relaxed ); - if( s >= blobs.size() ) - { - break; - } - BlobDefinitions& slot = out[ walkOrder[ s ] ]; - try - { - definitionLinesOfOneBlob( blobs[ walkOrder[ s ] ], name, pg.p, cursor, macroWork, scan, slot ); - } - catch( ... ) // a throw escaping a worker thread is std::terminate: the blob degrades instead - { - DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::WorkerThrew, "whereis: a blob worker threw — that blob confirms no definition" ); - } + ts_query_cursor_delete( cursor ); } - ts_query_cursor_delete( cursor ); - }; - if( threadCount <= 1 ) - { - worker(); + return; } - else + MemberMacroReparse macroWork; + IngestFileScan scan = makeFileScan( std::vector{ std::string() } ); + for( std::size_t s = nextSlot.fetch_add( 1, std::memory_order_relaxed ); s < blobs.size(); s = nextSlot.fetch_add( 1, std::memory_order_relaxed ) ) { - std::vector pool; - pool.reserve( threadCount ); - for( unsigned t = 0; t < threadCount; ++t ) + BlobDefinitions& slot = out[ walkOrder[ s ] ]; + try { - pool.emplace_back( worker ); + definitionLinesOfOneBlob( blobs[ walkOrder[ s ] ], name, pg.p, cursor, macroWork, scan, slot ); } - for( std::thread& w : pool ) + catch( ... ) // a throw escaping a worker thread is std::terminate: the blob degrades instead { - w.join(); + DISCLOSE( ( BlobDefsSlot{ slot } ), BlobDefsSlot::DisclosureWhy::WorkerThrew, "whereis: a blob worker threw — that blob confirms no definition" ); } } + ts_query_cursor_delete( cursor ); +} + +std::vector definitionLinesInBlobs( std::span blobs, std::string_view name ) +{ + std::vector out( blobs.size() ); + if( blobs.empty() ) + { + return out; + } + warmFieldIdTable(); // idempotent; the AST walks behind captureTagsFacts read it lock-free + installBlobQueries( blobs ); + + std::vector walkOrder( blobs.size() ); + std::iota( walkOrder.begin(), walkOrder.end(), std::uint32_t( 0 ) ); + std::stable_sort( walkOrder.begin(), walkOrder.end(), [ & ]( std::uint32_t a, std::uint32_t b ) noexcept + { return blobs[ a ].bytes.size() > blobs[ b ].bytes.size(); } ); // biggest first: no straggler at the tail + const unsigned threadCount = static_cast( std::min( std::max( 1u, std::thread::hardware_concurrency() ), blobs.size() ) ); + std::atomic nextSlot{ 0 }; + std::vector pool; + pool.reserve( threadCount ); + for( unsigned t = 0; t < threadCount; ++t ) + { + pool.emplace_back( [ & ]() noexcept { definitionLinesWorker( blobs, name, walkOrder, nextSlot, out ); } ); + } + for( std::thread& w : pool ) + { + w.join(); + } return out; } From 933c4390cdf7599e9b8a8d0aced472dc500bc1c4 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 07:49:12 -0400 Subject: [PATCH 151/170] docs/test: LIMITS.md and README's cap count for kWhereisParseMaxBlobs; the --help=all pin for the whereis help sentences docs/LIMITS.md regenerated (python3 docs/limits_build.py: 241 caps, 7 parameters), README's cap sentence follows, and test/printf_parity.manifest pins the --help=all bytes the --whereis / --whereis-listing help changes produce (an intended text change, not a printf conversion). docs/COMMANDS.md and the showcase capture's --legend-dict figure are the train's one regeneration from the merged binary, as for every lane. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- docs/LIMITS.md | 11 ++++++----- test/printf_parity.manifest | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0f9f2afd6..62bf93c77 100644 --- a/README.md +++ b/README.md @@ -2841,7 +2841,7 @@ python3 test/pargates.py . ./build/ripwire -j 6 A new gate script must be added to `test/regression.sh` in the same change. The gate `test/manifestcheck.sh` enforces this rule. -Another gate derives the cap inventory. The tool has 240 compile-time caps and 7 ranking parameters. +Another gate derives the cap inventory. The tool has 241 compile-time caps and 7 ranking parameters. `docs/LIMITS.md` lists each cap, its value, and whether the file discloses a truncation when the cap fires, and `python3 docs/limits_build.py --check` proves that list against `src/`. `docs/TUNING.md` lists the measured cost of each cap. diff --git a/docs/LIMITS.md b/docs/LIMITS.md index 40041f310..75d7dec22 100644 --- a/docs/LIMITS.md +++ b/docs/LIMITS.md @@ -17,10 +17,10 @@ it once, marked `×N`. | total caps | files | caps whose file discloses | caps whose file discloses NOTHING | | --- | --- | --- | --- | -| 240 | 96 | 140 | **100** | +| 241 | 96 | 140 | **101** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 240 + 7 is the 247 constants this generator parses out of `src/`. +are not counted as caps, and 241 + 7 is the 248 constants this generator parses out of `src/`. ## INDEXING, OUTPUT or BOUNDARY — which half of the answer a cap bounds @@ -38,8 +38,8 @@ None of them truncates anything, so none can be judged by `shown=`/`total=` and a disclosure — labelling them OUTPUT would ask for a `capped="1"` that could never honestly fire. The distinction was named in review on #108 and the rows below now carry it. -The `class` column below carries that answer where it is known. **132 of 240 caps are classified -(47 INDEXING, 41 OUTPUT, 44 BOUNDARY); the remaining 108 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **132 of 241 caps are classified +(47 INDEXING, 41 OUTPUT, 44 BOUNDARY); the remaining 109 render `—`, which means NOT YET CLASSIFIED — never "neither".** Classifications live in `docs/limits_classes.tsv`, a sidecar with a known expiry: the tag belongs on the declaration itself, and this file exists only because the round that @@ -89,7 +89,7 @@ refuse to write, so the column cannot be satisfied by pointing at nothing. ## Caps, by file -One table for each of the 96 files that declare a cap — the 240 caps counted above, and no parameter. +One table for each of the 96 files that declare a cap — the 241 caps counted above, and no parameter. ### `src/abicheck.h` @@ -190,6 +190,7 @@ Discloses: **none** | `kMaxRenameProbeFiles` | `32` | INDEXING | HEAD copies one rename probe reads; past it the probe stops (a hint, never a claim) | | `kMaxWorktreePaths` | `8192` | INDEXING | — | | `kWhereisHits` | `60` | OUTPUT | — | +| `kWhereisParseMaxBlobs` | `256` | — | — | ### `src/darkflags.h` diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index c44bce03f..ab26d0bb8 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -38,5 +38,5 @@ safe_delete 0 d010f9ca7e45d5bedcde9be49875bbc705632b3011cb9c8c4185f47957d51504 e verify_layer 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5288c345d7d6e335f88b9c1daa8935db22e1dcf89c0c8bc1f6140d4cb5af0b48 graph_query 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 bfa4319feb9dee09cfbd3991cf6fbd752297e99d14de9e75768820d2a9c8832f callers_limit 0 e40ee1ddba0fbf548c58c98b91fe052af8c34f8913edb3edd85245b12a6967b9 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help_all 0 d6c3f9a001f3eb341b5bc7cec50a6647b7f2814cb28726ae310640ed977a7293 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help_all 0 d40de547cbe4fa84933d4627ba3b6145605e9c84f32b9854c8a44a86161281dc e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help_one 0 938ff4f8802a8f5d9cb10f9a1f3804ac755be44c8ab00e24e45bfe8255224a21 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 From 960db34a1b88f5ee35473f64a6919ff220be0084 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 10:59:17 -0400 Subject: [PATCH 152/170] refactor(crossref): --whereis and the cross-branch verbs refuse an unread blob batch in one sentence crossref::blobsUnreadCause( noun ) is the one renderer for both blob readers (the trees' blobs, the branches' blobs): the --whereis CLI refusal now asks about TMPDIR and its MCP twin names the cache directory, as the cross-branch refusal always did. crossrefdegradecheck (6e) compares the two sentences, CLI and MCP. The cli-perf-068 CHANGELOG entry no longer says the cross-branch verbs answer without saying the read failed (honesty-small-b's K75 fixed that). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 ++-- src/crossref.h | 11 ++++++++--- src/mcp.h | 5 ++--- src/verbs_change.h | 7 +++---- test/crossrefdegradecheck.sh | 19 +++++++++++++++++++ 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 056c19e8c..6e343491b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -263,8 +263,8 @@ When that directory was unusable (for example `TMPDIR` naming a directory that d started and the answer said `on-head="0" hits="0"`, with a near-miss note suggesting the name it had been given, on a repository whose HEAD defines it. The CLI now exits 1 and names the cause on stderr, and the MCP `whereis` tool answers error -32603. A genuine zero over a working cache directory is answered as before. -`--stray-content` and `--abi` read blobs the same way and still answer without saying the read failed. That is -known and not fixed here. +The cross-branch verbs read blobs the same way and now refuse the same way (the entry above); both verbs, CLI and MCP, +say it in one sentence that differs only in what was not read and names the cache directory and `TMPDIR`. ### Added — a Ruby `db/schema.rb` renders its columns as Section definitions A Rails schema is recognized BY CONTENT, never by path: a Ruby file whose tree holds a diff --git a/src/crossref.h b/src/crossref.h index 9522c5727..a08fff795 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -709,9 +709,14 @@ class BlobStore // K75: the words every host of a blob-reading verb refuses in when the one `git cat-file --batch` behind it did not // serve every blob. The cause is named WHERE the reader looks (stderr + exit 1, or an MCP error), never as a zero. -inline std::string blobsUnreadCause() -{ - return "could not read the branches' blobs (the one `git cat-file --batch` did not start or stopped early; its sha list is written under " +// ONE renderer for both blob readers (the prconverge.h one-renderer rule): `blobs` names what was not read — the +// cross-branch sweep's kStrayBlobs or --whereis's kWhereisBlobs — and every other word is shared, so the CLI and MCP +// hosts of both verbs name the same cause, the cache directory and TMPDIR. +inline constexpr std::string_view kStrayBlobs = "the branches' blobs"; +inline constexpr std::string_view kWhereisBlobs = "the trees' blobs"; +inline std::string blobsUnreadCause( std::string_view blobs ) +{ + return "could not read " + std::string( blobs ) + " (the one `git cat-file --batch` did not start or stopped early; its sha list is written under " + quality::cacheDirLadder() + " - is TMPDIR a writable directory?)"; } diff --git a/src/mcp.h b/src/mcp.h index 15531fe1d..844201b95 100644 --- a/src/mcp.h +++ b/src/mcp.h @@ -2337,8 +2337,7 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo if( blobFault ) { DISCLOSE( Diagnostics::answerRefused, "whereis (MCP): the blob stream did not serve every blob — a -32603 error names it, no hit count" ); - return errResult( -32603, "could not read the trees' blobs (git cat-file --batch did not start or stopped early; is the cache " - "dir under TMPDIR writable?) — no hit count rather than one nothing measured" ); + return errResultMsg( -32603, crossref::blobsUnreadCause( crossref::kWhereisBlobs ) + " — no hit count rather than one nothing measured" ); } return t.empty() ? errResult( -32602, "not a git repository (or no HEAD commit) — no refs to search" ) : textResult( t ); } ); @@ -2353,7 +2352,7 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo if( blobFault ) { DISCLOSE( Diagnostics::answerRefused, "stray_content (MCP): the blob batch did not serve every blob - a -32603 error names it, no verdicts" ); - return errResultMsg( -32603, crossref::blobsUnreadCause() ); + return errResultMsg( -32603, crossref::blobsUnreadCause( crossref::kStrayBlobs ) ); } return t.empty() ? errResult( -32602, "not a git repository (or no HEAD commit) — no refs to compare" ) : textResult( t ); } ); diff --git a/src/verbs_change.h b/src/verbs_change.h index 8cf2ece8f..5bd10eda9 100644 --- a/src/verbs_change.h +++ b/src/verbs_change.h @@ -693,7 +693,7 @@ inline constexpr int kRunTraceExitCommandFailed = 4; inline void printBlobsUnreadRefusal( const char* hostPrefix ) { DISCLOSE( Diagnostics::answerRefused, "the blob batch behind the cross-branch sweep did not serve every blob, so no verdict is printed - stderr names the cause, exit 1" ); - rw::emitTo( stderr, "ripwire: {}{} - refusing rather than print verdicts nothing measured\n", hostPrefix, rw::crossref::blobsUnreadCause() ); + rw::emitTo( stderr, "ripwire: {}{} - refusing rather than print verdicts nothing measured\n", hostPrefix, rw::crossref::blobsUnreadCause( rw::crossref::kStrayBlobs ) ); } inline void printStrayFilterNoMatch( const char* hostPrefix, std::string_view filter ) { @@ -1639,9 +1639,8 @@ std::optional runCrossRef( const MainDispatch& d ) if( result.blobsUnread ) { DISCLOSE( Diagnostics::answerRefused, "whereis: the blob stream did not serve every blob, so no hit count is printed — stderr names the cause, exit 1" ); - rw::emitTo( stderr, "ripwire: --whereis: could not read the trees' blobs (the one `git cat-file --batch` did not start or stopped early; " - "its sha list is written under {}) — refusing rather than print a hit count nothing measured\n", - quality::cacheDirLadder() ); + rw::emitTo( stderr, "ripwire: --whereis: {} — refusing rather than print a hit count nothing measured\n", + crossref::blobsUnreadCause( crossref::kWhereisBlobs ) ); // K75's one renderer: the sweep's words, this verb's noun return 1; } result.seedSpec = std::move( whereisSeed ); diff --git a/test/crossrefdegradecheck.sh b/test/crossrefdegradecheck.sh index d2ee90ce3..abdb3cc1c 100755 --- a/test/crossrefdegradecheck.sh +++ b/test/crossrefdegradecheck.sh @@ -378,6 +378,25 @@ PY esac fi +# 6e (train 26c: the K75 x cli-perf-068 one-renderer item): --whereis and the cross-branch sweep refuse in ONE sentence that +# differs only in its noun (crossref::blobsUnreadCause), so the --whereis refusal names TMPDIR and its MCP twin names the cache +# directory, as the sweep's refusal (the K75 loop above, its last stderr) always did. Red before the fold: the --whereis CLI +# sentence never asked about TMPDIR, and its MCP twin never named the directory the sha list is written under. +K75WORDS="(the one \`git cat-file --batch\` did not start or stopped early; its sha list is written under " +K75ASK=" - is TMPDIR a writable directory?)" +if grep -qF "could not read the trees' blobs $K75WORDS" "$TMP/b6b.err" && grep -qF "$K75ASK" "$TMP/b6b.err" \ + && grep -qF "could not read the branches' blobs $K75WORDS" "$TMP/k75_bad.out.err" && grep -qF "$K75ASK" "$TMP/k75_bad.out.err"; then + ok "6e one renderer: the --whereis and cross-branch refusals share every word but the noun, TMPDIR named in both" +else + no "6e one renderer: the two refusals differ beyond the noun — whereis: $( head -c 260 "$TMP/b6b.err" ) | sweep: $( grep 'could not read' "$TMP/k75_bad.out.err" | head -c 260 )" +fi +if [ -n "${M6:-}" ]; then + case "$M6" in + "ERROR "*"could not read the trees' blobs $K75WORDS"*"$K75ASK"*) ok "6e MCP: the whereis error carries the same sentence (the directory and TMPDIR named)" ;; + *) no "6e MCP: the whereis error is not the shared sentence: $( printf '%s' "$M6" | head -c 260 )" ;; + esac +fi + # G4: every emitted document must survive xmllint if command -v xmllint >/dev/null 2>&1; then g4=0 From a807cea9e7ad9b165663b4adf6990db5ecfe889b Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 10:59:17 -0400 Subject: [PATCH 153/170] fix(qchurn): kQChurnCacheScheme 2 -> 3, so a failed walk an earlier build cached is not served A binary before K51 stored a git log walk that git could not finish (an empty stream or the newest prefix) under the same (repo, HEAD, window) key, and a K51 binary on scheme 2 served that blob warm: no walk, no churn=, no history_unread= until HEAD moved. The bump makes every such blob a clean miss (one cold walk per repository). qchurncheck "K51 scheme" arm: a blob written by RIPWIRE_BASE_BIN (a pre-K51 binary) through a git shim whose walk dies is not served; skipped by name without one. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 9 ++++++--- src/quality.h | 6 +++++- test/qchurncheck.sh | 29 +++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e343491b..932390fd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,7 +105,8 @@ cause and `TMPDIR`, with nothing on stdout, the way `--whereis` refuses on the s A `git log --name-only` that failed (an unreadable object, a bad ref) came back as an empty stream or as the newest part of the window and was stored under the (repo, HEAD, window) key, so `churn=` and `amp=` read as "no change history", or as the whole window's count, until HEAD moved, with no tell. A git that would not run at all (not on `PATH`) in a repository read -the same way, as an unborn branch. A walk git could not finish is now never cached, so the next call walks again, and every +the same way, as an unborn branch. A walk git could not finish is now never cached, so the next call walks again (a blob an earlier build stored from such a +walk is not read either: the history-walk cache scheme moves, see the versions note), and every document that prints `churn=` or `amp=` (the map under `--metrics`, `--for`, `--pack-task` and `--grep` with `--metrics`, `--around`) carries `history_unread="1"` on its root (`"history_unread":true` in JSON) beside a comment that defines it and names the cause. It means `churn=` and the co-change half of `amp=` count only the commits that were read, so both are @@ -1887,8 +1888,10 @@ columns (#339, carried as 122 to 149 on its branch) land at 158, above 156's ful (the function-literal fix's record changes, then the false-edge fix's member-call fields, then the receiver chain `memberPath`/`memberCtor`; the Ruby branch's appended binding kinds need no bump of their own) and `kQSnapCacheScheme` 15 → 19 (the `--quality-delta` error-masking and placeholder changes, then the dead kind agreeing with `--dead-code` on functions -held as values, then on a function that only stores itself, then the defect-shape records of the twelfth kind). Every ingest cache written by an earlier build is refused and re-indexed once, and every -cached quality snapshot is recomputed. The session legend dictionary is `dictv=593769ab691623d8 entries=806`. +held as values, then on a function that only stores itself, then the defect-shape records of the twelfth kind), and the +history-walk cache's `kQChurnCacheScheme` 2 → 3 (an earlier build stored a `git log` walk that git could not finish as if it +were the window). Every ingest cache written by an earlier build is refused and re-indexed once, every +cached quality snapshot is recomputed, and every repository's history is walked once more. The session legend dictionary is `dictv=593769ab691623d8 entries=806`. ### Fixed — a call the language resolves outside the tree no longer binds to a same-named in-repo definition diff --git a/src/quality.h b/src/quality.h index efb967936..b6842aafb 100644 --- a/src/quality.h +++ b/src/quality.h @@ -4479,7 +4479,11 @@ computeWindowRefBodyHashes( const std::string& root, std::uint32_t days, // would keep serving the old stream to a fixed binary: the exact silent-zero the fix removes, restored by // cache. Bumping the scheme makes every pre-existing blob a clean miss (deserialize checks it), not a wrong // answer. This is the qchurn cache's OWN version — not kParserVer, which keys extraction and is untouched. -constexpr std::uint32_t kQChurnCacheScheme = 2; +// SCHEME 3 (2026-10-10, train 26c, K51): a binary before K51 stored a walk git could not finish (an empty stream, or the +// newest prefix of the window) as if it were the window, and the key cannot tell that blob from a real read. K51 never +// stores one, but a blob already on disk would keep serving its silent zero or prefix, with no history_unread=, until HEAD +// moved. The bump makes every pre-K51 blob a clean miss; the cost is one cold walk per repository. +constexpr std::uint32_t kQChurnCacheScheme = 3; constexpr char kQChurnMagic[4] = { 'Q', 'C', 'H', 'N' }; inline std::string serializeRawCommitStream( const RawCommitStream& raw, const std::string& keyMat ) diff --git a/test/qchurncheck.sh b/test/qchurncheck.sh index 41e1d47ad..dfdaf55ab 100755 --- a/test/qchurncheck.sh +++ b/test/qchurncheck.sh @@ -262,6 +262,35 @@ env TMPDIR="$QTMP" "$BIN" "$UNBORN" --for=helper --no-cache >"$TMP/k_unborn.out" || no "K51: an unborn repository was disclosed as unread, or failed (rc=$rcU)" rm -rf "$UNBORN" +# ── K51 scheme bump (train 26c): a failed walk a pre-K51 binary STORED is not served by this one ───────────────────────────── +# A pre-K51 binary cached the empty stream of a walk git could not finish under the same (repo, HEAD, window) key, and a +# K51 binary on scheme 2 served that blob warm: no walk, no churn=, no history_unread= (rv-honesty-small-b N1, seen by hand). +# kQChurnCacheScheme 3 makes it a clean miss. Needs RIPWIRE_BASE_BIN = a binary from before K51 (it stores the failed walk); +# skipped by name without one, and skipped by name when the base binary is itself K51-fixed (it stores nothing to serve). +# Red on the merged train binary before the bump (scheme 2: the warm run walks 0 times and prints no churn=). +if [ -n "${RIPWIRE_BASE_BIN:-}" ] && [ -x "${RIPWIRE_BASE_BIN:-}" ]; then + printf '// k51s\n' >> "$REPO/src/lib.cpp" + git -C "$REPO" commit -qam "k51s" # a fresh HEAD: no qchurn blob exists for it yet + KSTMP="$TMP/kstale"; mkdir -p "$KSTMP"; QCACHE_SAVE="$QCACHE"; QCACHE="$KSTMP/ripwire" + ks0="$( nqchurn )" + env PATH="$KSHIM:$PATH" TMPDIR="$KSTMP" "$RIPWIRE_BASE_BIN" "$REPO" --for=helper --no-cache >/dev/null 2>&1 # the walk dies; pre-K51 stores it + ks1="$( nqchurn )" + if [ "$ks1" -gt "$ks0" ] 2>/dev/null; then + : > "$TMP/k_stale.log" + env TMPDIR="$KSTMP" GIT_TRACE="$TMP/k_stale.log" "$BIN" "$REPO" --for=helper --no-cache >"$TMP/k_stale.out" 2>/dev/null; rcS=$? + if [ "$rcS" -eq 0 ] && [ "$( name_only_count "$TMP/k_stale.log" )" -ge 1 ] && grep -q ' churn="[0-9]' "$TMP/k_stale.out"; then + ok "K51 scheme: a failed walk a pre-K51 binary cached is a miss here — the walk runs and churn= is printed" + else + no "K51 scheme: the pre-K51 binary's cached failed walk was served (rc=$rcS, walks=$( name_only_count "$TMP/k_stale.log" ), churn= $( grep -c ' churn="[0-9]' "$TMP/k_stale.out" ))" + fi + else + echo " SKIP K51 scheme: RIPWIRE_BASE_BIN stored no blob for the failed walk (blobs $ks0 -> $ks1): it is K51-fixed, so there is no stale blob to serve" + fi + QCACHE="$QCACHE_SAVE" +else + echo " SKIP K51 scheme: RIPWIRE_BASE_BIN (a binary from before K51) is not set — the stale-blob arm needs one to write the blob" +fi + # ── xmllint clean (sanity — --for output is still well-formed XML under the cached path) ─────────────── "$BIN" "$REPO" --for="helper" --no-cache 2>/dev/null | xmllint --noout - 2>/dev/null \ && ok "xmllint clean" || no "xmllint reported malformed XML" From 064e3c60670bb92f13fbc2945133a47574b20224 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 11:01:12 -0400 Subject: [PATCH 154/170] =?UTF-8?q?whereis:=20the=20?= =?UTF-8?q?=20replays=20the=20same=20question=20=E2=80=94=20an=20explicit?= =?UTF-8?q?=20listing=20rides,=20the=20MCP=20kind=20filter=20does=20not?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The next= named --stray-content= for an MCP kind filter, a different verb that takes precedence over --whereis, and dropped an explicit --whereis-listing=, so it replayed the default listing. It now carries the listing the question named and, like , no ref filter (the CLI has no whereis ref filter); the UNPARSED legend clause and the CHANGELOG say so. crossrefcheck 5d: the next= of a listing=all answer, run as written, is byte for byte the listing=all --detail=1 page; the default page's next= names no listing; an MCP kind= answer's next= runs whereis with the guard lifted. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 +++- src/crossref.h | 25 ++++++++++++++++++------- test/crossrefcheck.sh | 37 ++++++++++++++++++++++++++++++++++--- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de0ba598..674727de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,9 @@ went from 60 rows of `total="2197"` (54 of them call sites) to 2 rows, both the the twelve definition shapes the stricter reading lost; a branch-only `#define` and annotated Java method. - **A runaway guard on that parse, disclosed.** At most 256 blobs or 64 MB per answer, most rows first. A blob the guard leaves, or one the parser cannot finish, confirms nothing: its parse-worthy lines read `kind="text"` (listed beside the - definitions, never `kind="def"`) and an `` element counts them; `--detail=1` lifts the guard. + definitions, never `kind="def"`) and an `` element counts them; `--detail=1` lifts the guard. Its `next=` replays the same question with `--detail=1` (an explicit + `--whereis-listing=` rides with it); an MCP `kind` ref filter is not carried, as `` does not carry it: the CLI + has no whereis ref filter. - **A definition line several refs hold is one row.** Under `listing="defs"` a `kind="def"` (or `kind="text"`) row outside the checkout whose path and line text other refs hold too is printed once, for the first of those refs by name, with `refs="N"`; the root's `folded=` counts the rows it stands for (on the first page, `shown + more + folded + refs count = diff --git a/src/crossref.h b/src/crossref.h index c5af74a95..ebc6bfee0 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -3647,8 +3647,9 @@ inline void writeWhereisLegendTail( std::FILE* out, const WhereResult& res ) "no parse read: blobs= of them, either past the runaway guard on the parse batch ({} blobs or {} MB, filled " "most rows first, then by path and blob) or a parse that could not finish; rows= is the kind=\"text\" rows they " "gave. Those rows are neither a confirmed definition nor a confirmed reference, and the blobs' other rows read " - "kind=\"ref\". Its next= rides when the guard left some: the same question with the detail flag at 1, which " - "lifts the guard. ", kWhereisParseMaxBlobs, kWhereisParseMaxBytes >> 20 ); + "kind=\"ref\". Its next= rides when the guard left some: the same question (the symbol and an explicitly named " + "listing) with the detail flag at 1, which lifts the guard; a ref filter (the MCP kind argument) is not carried, " + "as the CLI has no whereis ref filter. ", kWhereisParseMaxBlobs, kWhereisParseMaxBytes >> 20 ); } // The overlay's own vocabulary, only on an answer that carries it — a clean checkout pays no bytes for it. if( res.worktree != WorktreeOverlay::Clean ) @@ -3747,6 +3748,7 @@ struct ListedHits std::string_view attr; std::size_t refsElided = 0; std::size_t defsFolded = 0; // def rows of other refs a listed row's refs= stands for (folded="N" on the root) + WhereisListing asked = WhereisListing::ShorterOfDefsAll; // the listing the question named (whereisServedListing) }; // kind= of a row: "def" (a parser's definition), "text" (a parse-worthy line on another ref no parse confirmed, @@ -3917,7 +3919,11 @@ inline void writeWhereisRoot( std::FILE* out, const WhereResult& res, const Wher // nothing — the runaway guard left them (WhereisParseBudget) or the parser could not finish — and the rows of theirs // that read kind="text". next= rides only when the guard left some: --detail=1 lifts it. Absent when every // parse-worthy blob was parsed (or mirrored HEAD's), so an answer the guard never touched is byte-identical. -inline void writeWhereisUnparsed( std::FILE* out, const WhereResult& res ) +// The replays the SAME question with the guard lifted: the symbol, the listing the question named +// (an explicit one; the default stays the default) and the detail flag. A ref filter (the MCP `kind`) is NOT carried, +// as does not carry it: the CLI has no whereis ref filter (--stray-content= is another verb, and naming it +// would run that verb instead), and the legend's UNPARSED clause says so. +inline void writeWhereisUnparsed( std::FILE* out, const WhereResult& res, WhereisListing asked ) { const BranchLabelCensus& c = res.branchLabels; if( c.leftByGuard + c.failed == 0 ) @@ -3928,9 +3934,12 @@ inline void writeWhereisUnparsed( std::FILE* out, const WhereResult& res ) if( c.leftByGuard > 0 ) { std::string call = nextFlag( "--whereis=", res.sym ); - if( !res.filter.empty() ) + switch( asked ) { - call += " " + nextFlag( "--stray-content=", res.filter ); // the ref filter that made this page + case WhereisListing::Defs: call += " --whereis-listing=defs"; break; + case WhereisListing::Refs: call += " --whereis-listing=refs"; break; + case WhereisListing::All: call += " --whereis-listing=all"; break; + case WhereisListing::ShorterOfDefsAll: break; } next = nextAttrXml( call + " --detail=1" ); } @@ -4110,7 +4119,7 @@ inline void writeWhereisListedPage( std::FILE* out, const WhereResult& res, std: // text hit), which is a claim strong enough to override the line-removal oracle outright. So the fate // row is printed unless the index itself already proved the symbol is defined on HEAD. writeWhereisFate( out, res, ex ); - writeWhereisUnparsed( out, res ); + writeWhereisUnparsed( out, res, listed.asked ); writeWhereisRows( out, res, listed, hitPage, headDate, ex ); rw::emitRaw( out, "" ); @@ -4130,7 +4139,9 @@ inline ListedHits whereisServedListing( const WhereResult& res, std::size_t maxH { if( listing != WhereisListing::ShorterOfDefsAll ) { - return listedHits( res, listing ); + ListedHits named = listedHits( res, listing ); + named.asked = listing; // an explicit listing rides the (writeWhereisUnparsed) + return named; } ListedHits defs = listedHits( res, WhereisListing::Defs ); if( defs.attr.empty() ) diff --git a/test/crossrefcheck.sh b/test/crossrefcheck.sh index 30cbf8055..28d957bc4 100755 --- a/test/crossrefcheck.sh +++ b/test/crossrefcheck.sh @@ -1223,8 +1223,12 @@ fi # in the batch): the batch takes 256 (most rows first, then path: f000..f255), the other 44 confirm NOTHING — their parse-worthy rows read kind="text", never kind="def", and # says so. --detail=1 lifts the guard: all 300 are definitions and the # element is gone. The full legend carries the UNPARSED clause on exactly that answer and stays well-formed XML. +# u.c (HEAD's, 20 calls) gives the answer kind="ref" rows, so the default listing (defs) and listing=all are different +# pages: the next= replay arm below tells "the same question" from "the default question". GG="$TMP/guard"; mkdir -p "$GG"; git -C "$GG" init -q -b main >/dev/null 2>&1; git -C "$GG" config commit.gpgsign false -printf 'int guardName( int a );\n' >"$GG/g.h"; git -C "$GG" add -A >/dev/null 2>&1; git -C "$GG" commit -qm base >/dev/null 2>&1 +printf 'int guardName( int a );\n' >"$GG/g.h" +i=0; : >"$GG/u.c"; while [ $i -lt 20 ]; do printf 'int use%d( void ) { return guardName( %d ); }\n' $i $i >>"$GG/u.c"; i=$((i+1)); done +git -C "$GG" add -A >/dev/null 2>&1; git -C "$GG" commit -qm base >/dev/null 2>&1 git -C "$GG" checkout -qb many >/dev/null 2>&1 i=0; while [ $i -lt 300 ]; do n=$( printf '%03d' $i ); printf 'int guardName( int a ) { return a + %d; }\n' $i >"$GG/f$n.c"; i=$((i+1)); done git -C "$GG" add -A >/dev/null 2>&1; git -C "$GG" commit -qm many >/dev/null 2>&1; git -C "$GG" checkout -q main >/dev/null 2>&1 @@ -1237,13 +1241,40 @@ if [ $rcGa -ne 0 ] || [ $rcGd -ne 0 ] || ! printf '%s' "$GA" | grep -q ' root (rc $rcGa / $rcGd)" else { [ "$GT" = "44" ] && [ "$GF" = "256" ] && [ "$GTL" = "f256.c f299.c " ] \ - && printf '%s' "$GA" | grep -q ''; } \ - && ok 'whereis (guard): 256 parsed (f000..f255, most rows first then path), 44 left: their rows read kind="text" (never def) and discloses them' \ + && printf '%s' "$GA" | grep -q ''; } \ + && ok 'whereis (guard): 256 parsed (f000..f255, most rows first then path), 44 left: their rows read kind="text" (never def) and discloses them' \ || { no "whereis (guard): want 256 def + 44 text rows (f256..f299) and the element; got def=$GF text=$GT [$GTL]"; printf '%s' "$GA" | grep -o ']*>'; } { [ "$( printf '%s' "$GD" | sed 's///' | tr '<' '\n' | grep '^hit ref="many" .* p="f' | grep -c 'kind="def"' )" = "300" ] \ && ! printf '%s' "$GD" | grep -q 'kind="text"' && ! printf '%s' "$GD" | grep -q '' \ || no 'whereis (guard): --detail=1 did not parse every blob' + # X1 (rule 5, recoverable via next=): the next= REPLAYS the question. Its argv, run as written, answers the same + # listing with the guard lifted — byte for byte the explicit --whereis-listing=all --detail=1 page ($GD), not the + # default (defs) page — and draws no "IGNORED" verb-precedence note. The default page's next= names no listing. + GN="$( printf '%s' "$GA" | sed -n 's/.*]* next="\([^"]*\)".*/\1/p' )" + GR="$( "$BIN" "$GG" $GN --limit=1000 --no-cache 2>"$TMP/guard.replay.err" )"; rcGr=$? + { [ -n "$GN" ] && [ $rcGr -eq 0 ] && printf '%s' "$GR" | grep -q ' of a listing=all answer ($GN) replays it: the same listing=all page with the guard lifted" \ + || { no "whereis (guard): next='$GN' (rc $rcGr) does not replay the listing=all question with the guard lifted"; grep -o ']*>' <<<"$GR"; cat "$TMP/guard.replay.err"; } + GDN="$( "$BIN" "$GG" --whereis=guardName --no-cache 2>/dev/null | grep -o ']*>' )" + [ "$GDN" = '' ] \ + && ok 'whereis (guard): the default page'"'"'s next= names no listing (the default stays the default)' \ + || no "whereis (guard): the default page's is '$GDN'" + if command -v python3 >/dev/null 2>&1; then + # The MCP `kind` ref filter has no CLI whereis spelling: next= drops it (as does) instead of naming + # --stray-content=, a different verb that would take precedence over --whereis. + GM="$( mcpq "$GG" guardName ',"kind":"man","listing":"all"' )" + GMN="$( printf '%s' "$GM" | sed -n 's/.*]* next="\([^"]*\)".*/\1/p' )" + GMR="$( "$BIN" "$GG" $GMN --limit=1000 --no-cache 2>"$TMP/guard.mcpreplay.err" )"; rcGmr=$? + { printf '%s' "$GM" | grep -q ']* filter="man"' && [ "$GMN" = '--whereis=guardName --whereis-listing=all --detail=1' ] \ + && [ $rcGmr -eq 0 ] && printf '%s' "$GMR" | grep -q '/dev/null )" GC="$( "$BIN" "$GG" --whereis=guardName --legend=compact --no-cache 2>/dev/null )" { printf '%s' "$GL" | grep -q 'UNPARSED: the unparsed element' && printf '%s' "$GC" | grep -q 'unparsed blobs=N' && printf '%s' "$GC" | grep -q 'unparsed rows=N' \ From f2eaae36951614c2bd15d5ff1e0bccc7abfe30d4 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 11:01:50 -0400 Subject: [PATCH 155/170] =?UTF-8?q?whereis:=20name=20both=20floors=20a=20b?= =?UTF-8?q?ranch=20row's=20label=20carries=20=E2=80=94=20the=20chooser's?= =?UTF-8?q?=20wider=20misses=20and=20an=20ill-formed=20blob?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The loose lexical test that chooses which blobs to parse also misses a definition with no parameter list and no declaration keyword before the name (`name: function (`, Rust `pub const NAME`, a C# property); alone in its blob such a definition reads kind="ref". And a parsed blob is labelled as HEAD's index labels the same bytes, so a file its grammar cannot parse cleanly can lose a definition or show a false one exactly as HEAD's index would. Both are now named in the CHANGELOG floor paragraph, the full legend and parseWorthyLine's header. Wording only. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 5 ++++- src/crossref.h | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 674727de9..bfba70537 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,10 @@ went from 60 rows of `total="2197"` (54 of them call sites) to 2 rows, both the scanned once. What stays a floor: a definition the loose lexical test misses (a signature wrapped before its parameter list closes, a -name alone on the line under its return type) is a counted ref when nothing else in its blob earns the blob a parse. +name alone on the line under its return type, or a definition with no parameter list and no declaration keyword before +the name: `name: function (`, `pub const NAME`, a C# property) is a counted ref when nothing else in its blob earns the +blob a parse. A parsed blob is labelled as HEAD's index labels the same bytes, so a file its grammar cannot parse +cleanly (a fragment, a file mid-edit) can lose a definition or show a false one exactly as HEAD's index would. MCP `whereis` takes no `detail` argument, so there the guard's `next=` names the CLI flag. Its description keeps its 701 characters, the sentence calling a branch row's label lexical replaced by the parser one, and the `tools/list` manifest grows 46,930 → 46,925 B, a 5 B shrink (the dropped em dash is six bytes of JSON). Gates: `test/crossrefcheck.sh` (5b–5e), diff --git a/src/crossref.h b/src/crossref.h index ebc6bfee0..b0e5c076f 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -1730,8 +1730,13 @@ inline bool definitionShaped( std::string_view line, std::string_view sym, std:: // * Kotlin's `fun ` marker (`fun f(x) = x + 1`); // * a markdown heading that opens with the name (`## f`): the index makes a section symbol of it. // What is still missed is named in definitionShaped's header (a signature wrapped before its parameter list closes, a -// name alone on a line with only a type above it…); those definitions stay counted refs when nothing else in their -// blob earns it a parse. +// name alone on a line with only a type above it…) and, beyond it, a definition with no parameter list and no +// declaration keyword before the name (`name: function (` in an object literal, Rust `pub const NAME`, a C# property +// `public int NAME { get; set; }`); those definitions stay counted refs when nothing else in their blob earns it a +// parse. A second floor is the parser's own, not this test's: a parsed blob is labelled as HEAD's index labels the +// same bytes, so a file its grammar cannot parse cleanly (a fragment, a file mid-edit) loses a definition or gains a +// false one exactly as HEAD's index would (the review's siblings probe: 20 definitions lost, one call read as a +// definition, each matching HEAD's index for the same bytes). // `name = (…`, `name = function`, `name = async`, `name = lambda`: the name bound by `=` to a function, an arrow or a lambda. inline bool boundToFunctionAfter( std::string_view line, std::size_t after ) noexcept { @@ -4022,8 +4027,11 @@ inline void writeWhereisListedPage( std::FILE* out, const WhereResult& res, std: "extraction, run over that ref's blob, captures a definition of the name there (once per distinct blob and path; " "a ref holding HEAD's own blob of the path takes HEAD's labels). Only a blob holding a line a loose LEXICAL test calls " "worth parsing is parsed: every row of a blob without one is kind=\"ref\", so a definition that test misses (a " - "signature wrapped before its parameter list closes, a name alone under its return type), alone in its blob, is " - "a counted ref. kind=\"text\" is a line worth parsing whose blob no parse read (the unparsed " + "signature wrapped before its parameter list closes, a name alone under its return type, or a definition with no " + "parameter list and no declaration keyword before the name: name: function (, pub const NAME, a C# property), " + "alone in its blob, is a counted ref. A parsed blob is labelled as HEAD's index labels the same bytes, so a file " + "its grammar cannot parse cleanly (a fragment, a file mid-edit) can lose a definition or show a false one exactly " + "as HEAD's index would. kind=\"text\" is a line worth parsing whose blob no parse read (the unparsed " "element counts them and says why): not a confirmed definition, not a confirmed reference. With " "head_labels=\"lexical\" (no index was supplied, the index knows no def of this name, or the working tree has " "drifted from HEAD) HEAD's rows fall back to a stricter lexical shape test, which reads a quoted signature in a doc as a " From 409591bdd1813289a8a48a92a7b91471d5564e44 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 11:03:22 -0400 Subject: [PATCH 156/170] test(crossrefdegradecheck): 6e's failure line quotes the refusal it compared, not the first stderr line Co-Authored-By: Claude Opus 5.5 --- test/crossrefdegradecheck.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/crossrefdegradecheck.sh b/test/crossrefdegradecheck.sh index abdb3cc1c..30cd16845 100755 --- a/test/crossrefdegradecheck.sh +++ b/test/crossrefdegradecheck.sh @@ -388,7 +388,7 @@ if grep -qF "could not read the trees' blobs $K75WORDS" "$TMP/b6b.err" && grep - && grep -qF "could not read the branches' blobs $K75WORDS" "$TMP/k75_bad.out.err" && grep -qF "$K75ASK" "$TMP/k75_bad.out.err"; then ok "6e one renderer: the --whereis and cross-branch refusals share every word but the noun, TMPDIR named in both" else - no "6e one renderer: the two refusals differ beyond the noun — whereis: $( head -c 260 "$TMP/b6b.err" ) | sweep: $( grep 'could not read' "$TMP/k75_bad.out.err" | head -c 260 )" + no "6e one renderer: the two refusals differ beyond the noun — whereis: $( grep 'could not read' "$TMP/b6b.err" | head -c 260 ) | sweep: $( grep 'could not read' "$TMP/k75_bad.out.err" | head -c 260 )" fi if [ -n "${M6:-}" ]; then case "$M6" in From 211125b3cabf7ae6591334dc1516b78da0b06804 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 11:11:01 -0400 Subject: [PATCH 157/170] refactor(crossref): blobsUnreadCause formats its one sentence (std::format), not a concatenation chain The concatenation shape read as a 24-26 token clone of four short string builders elsewhere (--quality-delta duplication, gating). Same bytes: crossrefdegradecheck 6e compares them. Co-Authored-By: Claude Opus 5.5 --- src/crossref.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/crossref.h b/src/crossref.h index a08fff795..b9ed602e2 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -112,6 +112,7 @@ #include #include #include // the worktree overlay: symlink_status / read_symlink / file_size on a changed path +#include // blobsUnreadCause — the one refusal sentence #include #include #include @@ -716,8 +717,9 @@ inline constexpr std::string_view kStrayBlobs = "the branches' blobs"; inline constexpr std::string_view kWhereisBlobs = "the trees' blobs"; inline std::string blobsUnreadCause( std::string_view blobs ) { - return "could not read " + std::string( blobs ) + " (the one `git cat-file --batch` did not start or stopped early; its sha list is written under " - + quality::cacheDirLadder() + " - is TMPDIR a writable directory?)"; + return std::format( "could not read {} (the one `git cat-file --batch` did not start or stopped early; its sha list is written under {} - is TMPDIR " + "a writable directory?)", + blobs, quality::cacheDirLadder() ); } // ── git plumbing (read-only) ───────────────────────────────────────────────────────────────────────────── From 28b0b224ab66e1f175e86687a1d9eb4f575ca76d Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 11:16:11 -0400 Subject: [PATCH 158/170] test: crossrefcheck arms for the C2 call shapes, the checklist-29 near-misses and a parse that cannot finish The parsed-labels fixture gains eight well-formed files, each holding a true definition of probeName beside a call shape: a Python decorator call, a Java annotation with arguments, `#define WRAP( x ) probeName( x )`, a call inside an `if(` that opens a block, a C++ call whose lambda argument opens a block, a doc-comment `@see`, a base class, JS `probeName(function () {` / `probeName('y', () => {` and `export default probeName(3)`, a TS decorator call and a Ruby parameter. Each definition reads def and each call ref, on the branch and on HEAD's index alike; the call lines carry no marker so the shape is bare. Ten of them read def on 93aeab1d and on the lexical-promotion mutant. A new arm holds the "parser could not finish" claim: a branch .astro with an unclosed frontmatter fence reads kind="text" with and no next=, while its closed twin reads def. Co-Authored-By: Claude Opus 5.5 --- test/crossrefcheck.sh | 107 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/test/crossrefcheck.sh b/test/crossrefcheck.sh index 28d957bc4..346dbdb01 100755 --- a/test/crossrefcheck.sh +++ b/test/crossrefcheck.sh @@ -1092,6 +1092,93 @@ mkp head/o.md <<'EOF' Call `probeName()` from the loop. EOF +# The C2 call shapes and the review's checklist-29 near-misses (rv-whereis-defs-fix2 X3), each beside a true definition +# of the same name in a well-formed file of its language, so each file reads both ways: the definition def, the call ref. +# The call lines carry NO marker (an unmarked line wants ref): a trailing comment would change the shape a lexical reading +# sees, and the premise below is the bare shape. +# RED on 93aeab1d (its lexical reading calls several of these calls definitions). +mkp head/q.py <<'EOF' +def probeName(n): # @D + def wrap(f): + return f + return wrap + + +@probeName(1) +def other(): + return 2 +EOF +mkp head/P.java <<'EOF' +public class P { + public static int probeName(int a) { // @D + return a; + } + @probeName(value = 1) + int run(int a) { + return a; + } +} +EOF +mkp head/t.c <<'EOF' +#define probeName( x ) ( (x) + 1 ) /* @D */ +#define WRAP( x ) probeName( x ) +int useA( int y ) +{ + if( probeName( y ) > 2 ) { + return WRAP( y ); + } + return 0; +} +EOF +mkp head/u.cpp <<'EOF' +template int probeName( int a, F f ) { return f( a ); } // @D +/** + * @see probeName( a ) + */ +int run( int y ) +{ + return probeName( y, [&]( int x ) { + return x; + } ); +} +EOF +mkp head/v.cpp <<'EOF' +struct probeName { int v; }; // @D +class Foo : public probeName { +}; +EOF +mkp head/w.js <<'EOF' +export function probeName(a) { // @D + return a; +} +function suite() { + probeName(function () { + return 1; + }); + probeName('y', () => { + return 2; + }); +} +export default probeName(3); +EOF +mkp head/x.ts <<'EOF' +export function probeName(n: number) { // @D + return (t: any, k: string) => {}; +} +export class Box { + @probeName(1) handle() { + return 3; + } +} +EOF +mkp head/y.rb <<'EOF' +def probeName(a) # @D + a +end +def other(probeName) + 1 +end +EOF gp add -A; gp commit -qm base gp checkout -qb sib mkdir -p "$PB/sib"; cp -R "$PB/head/." "$PB/sib/" @@ -1193,6 +1280,26 @@ KB="$( "$BIN" "$PB" --whereis=checkBudget --whereis-listing=all --no-cache 2>/de && printf '%s' "$KB" | grep -q ']*p="c1/T2.java" l="2" kind="def"'; } \ && ok 'whereis (branch-only): `#define clampBudget( x )` and `@Test(timeout = 10) public void checkBudget()` read kind="def" on the only ref holding them; the call beside the macro reads kind="ref"' \ || { no 'whereis (branch-only): a branch-only definition lost its label, or its call site gained one'; printf '%s\n%s\n' "$CB" "$KB" | sed 's///'; } +# "One the parser cannot finish" (CHANGELOG, the UNPARSED clause): a branch .astro that opens a frontmatter fence and never +# closes it confirms nothing: its parse-worthy line reads kind="text", never def, and counts +# it with no next= (the guard left nothing, so there is nothing to lift). Its closed twin reads def. RED on 93aeab1d (no +# ; the unclosed line read def). +AS="$TMP/astro"; mkdir -p "$AS"; git -C "$AS" init -q -b main >/dev/null 2>&1; git -C "$AS" config commit.gpgsign false +printf 'x\n' >"$AS/a.txt"; git -C "$AS" add -A >/dev/null 2>&1; git -C "$AS" commit -qm base >/dev/null 2>&1; git -C "$AS" checkout -qb feat >/dev/null 2>&1 +printf -- '---\nfunction astroName(a) { return a; }\n---\n
{astroName(1)}
\n' >"$AS/ok.astro" +printf -- '---\nfunction astroOpen(a) { return a; }\n
{astroOpen(1)}
\n' >"$AS/p.astro" +git -C "$AS" add -A >/dev/null 2>&1; git -C "$AS" commit -qm feat >/dev/null 2>&1; git -C "$AS" checkout -q main >/dev/null 2>&1 +AO="$( "$BIN" "$AS" --whereis=astroOpen --whereis-listing=all --no-cache 2>/dev/null )"; rcAo=$? +AN="$( "$BIN" "$AS" --whereis=astroName --whereis-listing=all --no-cache 2>/dev/null )"; rcAn=$? +if [ $rcAo -ne 0 ] || [ $rcAn -ne 0 ] || ! printf '%s' "$AO" | grep -q ' root (rc $rcAo / $rcAn)" +else + { printf '%s' "$AO" | grep -q '' && printf '%s' "$AO" | grep -q ']*p="p.astro" l="2" kind="text"' \ + && ! printf '%s' "$AO" | grep -q 'kind="def"' \ + && printf '%s' "$AN" | grep -q ']*p="ok.astro" l="2" kind="def"' && ! printf '%s' "$AN" | grep -q ' (no next=); its closed twin reads def' \ + || { no 'whereis (parser could not finish): the unclosed .astro blob was labelled, or not disclosed, or the closed twin lost its def'; printf '%s\n%s\n' "$AO" "$AN" | sed 's///'; } +fi # ref_labels= rides only an answer with another ref's row: a repo with no other ref answers as before NR="$TMP/norefs"; mkdir -p "$NR"; git -C "$NR" init -q -b main >/dev/null 2>&1; git -C "$NR" config commit.gpgsign false printf 'int lonelyName( int a ) { return a; }\nint z( void ) { return lonelyName( 1 ); }\n' >"$NR/x.c" From 574f7583fb810838c222d5513b5728e09d73749f Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 11:16:29 -0400 Subject: [PATCH 159/170] CHANGELOG: the tools/list manifest shrinks; the parse guard applies under every listing, the default page included Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfba70537..b449bf4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,11 +31,12 @@ went from 60 rows of `total="2197"` (54 of them call sites) to 2 rows, both the Arms: one well-formed file per language family (C, C++, ObjC, Python, TypeScript, JavaScript, Java, Ruby, Go, Rust, Swift, C#, Bash, Markdown) whose every marked line reads as its marker says and as HEAD's index reads the same bytes; the twelve definition shapes the stricter reading lost; a branch-only `#define` and annotated Java method. -- **A runaway guard on that parse, disclosed.** At most 256 blobs or 64 MB per answer, most rows first. A blob the guard - leaves, or one the parser cannot finish, confirms nothing: its parse-worthy lines read `kind="text"` (listed beside the - definitions, never `kind="def"`) and an `` element counts them; `--detail=1` lifts the guard. Its `next=` replays the same question with `--detail=1` (an explicit - `--whereis-listing=` rides with it); an MCP `kind` ref filter is not carried, as `` does not carry it: the CLI - has no whereis ref filter. +- **A runaway guard on that parse, disclosed.** At most 256 blobs or 64 MB per answer, most rows first, under every + listing, the default page included. A blob the guard leaves, or one the parser cannot finish, confirms nothing: its + parse-worthy lines read `kind="text"` (listed beside the definitions, never `kind="def"`) and an `` element counts them; `--detail=1` lifts the guard. Its `next=` replays the same question with + `--detail=1` (an explicit `--whereis-listing=` rides with it); an MCP `kind` ref filter is not carried, as `` does not carry it: the CLI has no whereis ref filter. - **A definition line several refs hold is one row.** Under `listing="defs"` a `kind="def"` (or `kind="text"`) row outside the checkout whose path and line text other refs hold too is printed once, for the first of those refs by name, with `refs="N"`; the root's `folded=` counts the rows it stands for (on the first page, `shown + more + folded + refs count = @@ -52,7 +53,7 @@ blob a parse. A parsed blob is labelled as HEAD's index labels the same bytes, s cleanly (a fragment, a file mid-edit) can lose a definition or show a false one exactly as HEAD's index would. MCP `whereis` takes no `detail` argument, so there the guard's `next=` names the CLI flag. Its description keeps its 701 characters, the sentence calling a branch row's label lexical replaced by the parser one, and the -`tools/list` manifest grows 46,930 → 46,925 B, a 5 B shrink (the dropped em dash is six bytes of JSON). Gates: `test/crossrefcheck.sh` (5b–5e), +`tools/list` manifest shrinks 46,930 → 46,925 B (the dropped em dash is six bytes of JSON). Gates: `test/crossrefcheck.sh` (5b–5e), `test/selectorhonestycheck.sh` (§A7), `test/pagingsweepcheck.sh`. ### Changed — a call bound by name alone keeps its rows, marked `via="name"`; typed receivers resolve From 11674d1602219c0c1b1aebdabbe921193667ac36 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 12:12:30 -0400 Subject: [PATCH 160/170] refactor(crossref): blobsUnreadCause takes which batch went unread (an enum), not a noun string 211125b3's one-statement std::format body still read as a 21-token near-clone of os_win32.cpp's Unique::release (--quality-delta duplication + new-clone-of-reused-helper, gating 2 vs the merge base). The renderer now picks the noun itself from UnreadBlobBatch { Branches, Trees }, so its callers pass a typed choice and the body is its own shape (gating 0). Same bytes: crossrefdegradecheck 6e compares the CLI and MCP sentences of both verbs. Co-Authored-By: Claude Opus 5.5 --- src/crossref.h | 18 +++++++++++------- src/mcp.h | 4 ++-- src/verbs_change.h | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/crossref.h b/src/crossref.h index b9ed602e2..11c3f37e4 100644 --- a/src/crossref.h +++ b/src/crossref.h @@ -710,16 +710,20 @@ class BlobStore // K75: the words every host of a blob-reading verb refuses in when the one `git cat-file --batch` behind it did not // serve every blob. The cause is named WHERE the reader looks (stderr + exit 1, or an MCP error), never as a zero. -// ONE renderer for both blob readers (the prconverge.h one-renderer rule): `blobs` names what was not read — the -// cross-branch sweep's kStrayBlobs or --whereis's kWhereisBlobs — and every other word is shared, so the CLI and MCP -// hosts of both verbs name the same cause, the cache directory and TMPDIR. -inline constexpr std::string_view kStrayBlobs = "the branches' blobs"; -inline constexpr std::string_view kWhereisBlobs = "the trees' blobs"; -inline std::string blobsUnreadCause( std::string_view blobs ) +// ONE renderer for both blob readers (the prconverge.h one-renderer rule): `batch` names which reader's blobs were not +// read — the cross-branch sweep's (--stray-content, --plan, --abi, --eval-stray) or --whereis's — and every other word is +// shared, so the CLI and MCP hosts of both verbs name the same cause, the cache directory and TMPDIR. +enum class UnreadBlobBatch : std::uint8_t { + Branches, // the cross-branch sweep's batch: every ref's side of every path it compares + Trees, // --whereis's batch: the trees it searches for the name +}; +inline std::string blobsUnreadCause( UnreadBlobBatch batch ) +{ + const std::string_view unread = batch == UnreadBlobBatch::Trees ? "the trees' blobs" : "the branches' blobs"; return std::format( "could not read {} (the one `git cat-file --batch` did not start or stopped early; its sha list is written under {} - is TMPDIR " "a writable directory?)", - blobs, quality::cacheDirLadder() ); + unread, quality::cacheDirLadder() ); } // ── git plumbing (read-only) ───────────────────────────────────────────────────────────────────────────── diff --git a/src/mcp.h b/src/mcp.h index 844201b95..286f93276 100644 --- a/src/mcp.h +++ b/src/mcp.h @@ -2337,7 +2337,7 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo if( blobFault ) { DISCLOSE( Diagnostics::answerRefused, "whereis (MCP): the blob stream did not serve every blob — a -32603 error names it, no hit count" ); - return errResultMsg( -32603, crossref::blobsUnreadCause( crossref::kWhereisBlobs ) + " — no hit count rather than one nothing measured" ); + return errResultMsg( -32603, crossref::blobsUnreadCause( crossref::UnreadBlobBatch::Trees ) + " — no hit count rather than one nothing measured" ); } return t.empty() ? errResult( -32602, "not a git repository (or no HEAD commit) — no refs to search" ) : textResult( t ); } ); @@ -2352,7 +2352,7 @@ inline McpDispatchResult dispatchMcpLine( const std::string& line, int topK, boo if( blobFault ) { DISCLOSE( Diagnostics::answerRefused, "stray_content (MCP): the blob batch did not serve every blob - a -32603 error names it, no verdicts" ); - return errResultMsg( -32603, crossref::blobsUnreadCause( crossref::kStrayBlobs ) ); + return errResultMsg( -32603, crossref::blobsUnreadCause( crossref::UnreadBlobBatch::Branches ) ); } return t.empty() ? errResult( -32602, "not a git repository (or no HEAD commit) — no refs to compare" ) : textResult( t ); } ); diff --git a/src/verbs_change.h b/src/verbs_change.h index 5bd10eda9..8f017cdd2 100644 --- a/src/verbs_change.h +++ b/src/verbs_change.h @@ -693,7 +693,7 @@ inline constexpr int kRunTraceExitCommandFailed = 4; inline void printBlobsUnreadRefusal( const char* hostPrefix ) { DISCLOSE( Diagnostics::answerRefused, "the blob batch behind the cross-branch sweep did not serve every blob, so no verdict is printed - stderr names the cause, exit 1" ); - rw::emitTo( stderr, "ripwire: {}{} - refusing rather than print verdicts nothing measured\n", hostPrefix, rw::crossref::blobsUnreadCause( rw::crossref::kStrayBlobs ) ); + rw::emitTo( stderr, "ripwire: {}{} - refusing rather than print verdicts nothing measured\n", hostPrefix, rw::crossref::blobsUnreadCause( rw::crossref::UnreadBlobBatch::Branches ) ); } inline void printStrayFilterNoMatch( const char* hostPrefix, std::string_view filter ) { @@ -1640,7 +1640,7 @@ std::optional runCrossRef( const MainDispatch& d ) { DISCLOSE( Diagnostics::answerRefused, "whereis: the blob stream did not serve every blob, so no hit count is printed — stderr names the cause, exit 1" ); rw::emitTo( stderr, "ripwire: --whereis: {} — refusing rather than print a hit count nothing measured\n", - crossref::blobsUnreadCause( crossref::kWhereisBlobs ) ); // K75's one renderer: the sweep's words, this verb's noun + crossref::blobsUnreadCause( crossref::UnreadBlobBatch::Trees ) ); // K75's one renderer: the sweep's words, this verb's noun return 1; } result.seedSpec = std::move( whereisSeed ); From 11cc36855ecbcf8c4946795f9bdbda853ade94e5 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 12:26:35 -0400 Subject: [PATCH 161/170] docs: regenerate LIMITS.md (245 caps) and the README cap sentence on the merged tree python3 docs/limits_build.py (+1 cap: whereis's kWhereisParseMaxBlobs, the same row the lane generated); python3 bench/capsweep/capsweep.py emit leaves TUNING.md unchanged; README's cap sentence set to the printed count (244 -> 245). limitstablecheck rc=0. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- docs/LIMITS.md | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bd32283f2..f0f163196 100644 --- a/README.md +++ b/README.md @@ -2877,7 +2877,7 @@ python3 test/pargates.py . ./build/ripwire -j 6 A new gate script must be added to `test/regression.sh` in the same change. The gate `test/manifestcheck.sh` enforces this rule. -Another gate derives the cap inventory. The tool has 244 compile-time caps and 7 ranking parameters. +Another gate derives the cap inventory. The tool has 245 compile-time caps and 7 ranking parameters. `docs/LIMITS.md` lists each cap, its value, and whether the file discloses a truncation when the cap fires, and `python3 docs/limits_build.py --check` proves that list against `src/`. `docs/TUNING.md` lists the measured cost of each cap. diff --git a/docs/LIMITS.md b/docs/LIMITS.md index 29d163a84..b2c7bc988 100644 --- a/docs/LIMITS.md +++ b/docs/LIMITS.md @@ -17,10 +17,10 @@ it once, marked `×N`. | total caps | files | caps whose file discloses | caps whose file discloses NOTHING | | --- | --- | --- | --- | -| 244 | 97 | 145 | **99** | +| 245 | 97 | 145 | **100** | Plus 7 ranking and apportionment parameters, in their own table below: they are not caps, they -are not counted as caps, and 244 + 7 is the 251 constants this generator parses out of `src/`. +are not counted as caps, and 245 + 7 is the 252 constants this generator parses out of `src/`. ## INDEXING, OUTPUT or BOUNDARY — which half of the answer a cap bounds @@ -38,8 +38,8 @@ None of them truncates anything, so none can be judged by `shown=`/`total=` and a disclosure — labelling them OUTPUT would ask for a `capped="1"` that could never honestly fire. The distinction was named in review on #108 and the rows below now carry it. -The `class` column below carries that answer where it is known. **134 of 244 caps are classified -(47 INDEXING, 43 OUTPUT, 44 BOUNDARY); the remaining 110 render `—`, which means NOT YET +The `class` column below carries that answer where it is known. **134 of 245 caps are classified +(47 INDEXING, 43 OUTPUT, 44 BOUNDARY); the remaining 111 render `—`, which means NOT YET CLASSIFIED — never "neither".** Classifications live in `docs/limits_classes.tsv`, a sidecar with a known expiry: the tag belongs on the declaration itself, and this file exists only because the round that @@ -89,7 +89,7 @@ refuse to write, so the column cannot be satisfied by pointing at nothing. ## Caps, by file -One table for each of the 97 files that declare a cap — the 244 caps counted above, and no parameter. +One table for each of the 97 files that declare a cap — the 245 caps counted above, and no parameter. ### `src/abicheck.h` @@ -190,6 +190,7 @@ Discloses: **none** | `kMaxRenameProbeFiles` | `32` | INDEXING | HEAD copies one rename probe reads; past it the probe stops (a hint, never a claim) | | `kMaxWorktreePaths` | `8192` | INDEXING | — | | `kWhereisHits` | `60` | OUTPUT | — | +| `kWhereisParseMaxBlobs` | `256` | — | — | ### `src/darkflags.h` From 39fd059eb530cf1379ae535c880e71d268fa95cd Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 12:28:58 -0400 Subject: [PATCH 162/170] test: re-pin printf_parity help_all and re-measure tools/list (47,109 B) on the merged tree From the merged binary (built_from=65d62fe5): printffmtparitycheck UPDATE_GOLDEN moved exactly {help_all} (whereis-defs-fix's --whereis help sentences: parser labels, kind="text", folded=). mcpmanifestcheck: 47,109 B, ceiling 47,150 unmoved; per-tool JSON bytes before/after the merge differ only in whereis (1,689 -> 1,684, the lane's -5). CHANGELOG: the whereis entry states the merged figure, and #362's restated figure (the one (1b) reads) is 46,930 -> 47,109 with both deltas attributed. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 11 ++++++----- test/mcpmanifestcheck.sh | 3 +++ test/printf_parity.manifest | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0e378990..8c4e407a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -163,7 +163,8 @@ blob a parse. A parsed blob is labelled as HEAD's index labels the same bytes, s cleanly (a fragment, a file mid-edit) can lose a definition or show a false one exactly as HEAD's index would. MCP `whereis` takes no `detail` argument, so there the guard's `next=` names the CLI flag. Its description keeps its 701 characters, the sentence calling a branch row's label lexical replaced by the parser one, and the -`tools/list` manifest shrinks 46,930 → 46,925 B (the dropped em dash is six bytes of JSON). Gates: `test/crossrefcheck.sh` (5b–5e), +`tools/list` manifest shrinks by 5 B, to 47,109 B on this release's tree (the dropped em dash is six bytes of JSON; every +other tool is unchanged). Gates: `test/crossrefcheck.sh` (5b–5e), `test/selectorhonestycheck.sh` (§A7), `test/pagingsweepcheck.sh`. ### Changed — a call bound by name alone keeps its rows, marked `via="name"`; typed receivers resolve @@ -2540,10 +2541,10 @@ handle is a CLI argv now, and it walks the same list. Every accepted ranking fla (the CLI's `--no-route`, `--no-mention-boost`, `--no-doc-mention`, `--cochange-boost`; the twins' `no_route`) rides `next=`, so a pasted continuation re-ranks identically — the `test/pagingsweepcheck.sh` walk arms pin all five surfaces to `has_more="0"` with identical row -sequences and pin each flag's echo. The `tools/list` manifest grows 46,930 → 47,114 B (measured -against train 25's `main`; descriptions identical, and the +184 B of schema is `explore`'s -`limit`/`offset` alone — `for` already declared the pair, `pack_task` is a dispatch alias, not a -listed tool), and the ceiling moves 46,950 → 47,150 B +sequences and pin each flag's echo. The `tools/list` manifest grows 46,930 → 47,109 B (measured +on this release's tree: the +184 B of schema is `explore`'s `limit`/`offset` alone — `for` already +declared the pair, `pack_task` is a dispatch alias, not a listed tool — and `whereis`'s description +is 5 B shorter, the parser-label entry above), and the ceiling moves 46,950 → 47,150 B - the one sanctioned case that moves it: `explore`'s two declared schema properties (`test/mcpmanifestcheck.sh` arm `(1b)` asserts the figure against a live measurement). ### Fixed — the Linux G1 sanitizer ritual completes: five string_view comparator lambdas stop wrapping, and the GCC ASan path builds (#342) diff --git a/test/mcpmanifestcheck.sh b/test/mcpmanifestcheck.sh index 4310e971c..2198165bb 100755 --- a/test/mcpmanifestcheck.sh +++ b/test/mcpmanifestcheck.sh @@ -323,6 +323,9 @@ tools = json.loads( line )[ "result" ][ "tools" ] # RE-MEASURED 2026-10-10 (lane whereis-defs-fix fix round 2), CEILING UNMOVED: 46,930 -> 46,925 B. whereis's description says # what labels another ref's row now (a parser, not a lexical heuristic) in the same 701 characters — (1c) holds — and the # sentence it replaced carried an em dash, six bytes as JSON. Every other tool unchanged. Headroom after this line: 25 B. +# RE-MEASURED 2026-10-10 (train 26c, whereis-defs-fix merged onto the 47,114 B tree), CEILING UNMOVED: 47,114 -> 47,109 B. Per-tool +# JSON bytes, the train binary before the merge vs the merged one: whereis 1,689 -> 1,684 (the lane's -5), every other tool +# identical. Headroom after this line: 41 B. CEILING = 47150 manifest = len( json.dumps( { "tools": tools }, separators = ( ",", ":" ) ) ) descBytes = sum( len( t[ "description" ] ) for t in tools ) diff --git a/test/printf_parity.manifest b/test/printf_parity.manifest index 4349a9a3b..6cf5c4ac9 100644 --- a/test/printf_parity.manifest +++ b/test/printf_parity.manifest @@ -38,5 +38,5 @@ safe_delete 0 d010f9ca7e45d5bedcde9be49875bbc705632b3011cb9c8c4185f47957d51504 e verify_layer 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 5288c345d7d6e335f88b9c1daa8935db22e1dcf89c0c8bc1f6140d4cb5af0b48 graph_query 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 bfa4319feb9dee09cfbd3991cf6fbd752297e99d14de9e75768820d2a9c8832f callers_limit 0 e40ee1ddba0fbf548c58c98b91fe052af8c34f8913edb3edd85245b12a6967b9 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -help_all 0 cc6ec90c5741362adf8631201fa73676bf8802bbda9ceb25549d8e0a59cac22e e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +help_all 0 4ea0630105a027c95ef51d7acea5caeba5963903f754f7341941583476625d1a e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 help_one 0 938ff4f8802a8f5d9cb10f9a1f3804ac755be44c8ab00e24e45bfe8255224a21 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 From b6f7ef389d4de21be373669a38d9dadf2d8da7b7 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 10 Oct 2026 12:44:56 -0400 Subject: [PATCH 163/170] =?UTF-8?q?docs:=20the=20train=2026c=20regeneratio?= =?UTF-8?q?n,=20once,=20from=20the=20merged=20binary=20=E2=80=94=20showcas?= =?UTF-8?q?e=20capture,=20COMMANDS.md,=20dictv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From build/ripwire at 65d62fe5 (every 26c lane merged; src unchanged since): - docs/captures/COMMANDS_showcase_2026-10-10.md captured (test/showcase_capture.py, 261 commands): it covers --no-inventory, re-derives the --at seed (rankGraphTeleport's line after #339 and cli-perf moved graph.h) and records dictv=74f30d013d02c2a2 entries=829. - docs/COMMANDS.md rebuilt from it (docs/docs_commands_build.py: 185 flags, 168 samples). - CHANGELOG versions paragraph: the session legend dictionary is dictv=74f30d013d02c2a2 entries=829 (`ripwire --legend-dict`). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- docs/COMMANDS.md | 1062 +-- docs/captures/COMMANDS_showcase_2026-10-10.md | 7677 +++++++++++++++++ 3 files changed, 8214 insertions(+), 527 deletions(-) create mode 100644 docs/captures/COMMANDS_showcase_2026-10-10.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c4e407a1..dedd8d827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1934,7 +1934,7 @@ columns (#339, carried as 122 to 149 on its branch) land at 158, above 156's ful held as values, then on a function that only stores itself, then the defect-shape records of the twelfth kind), and the history-walk cache's `kQChurnCacheScheme` 2 → 3 (an earlier build stored a `git log` walk that git could not finish as if it were the window). Every ingest cache written by an earlier build is refused and re-indexed once, every -cached quality snapshot is recomputed, and every repository's history is walked once more. The session legend dictionary is `dictv=593769ab691623d8 entries=806`. +cached quality snapshot is recomputed, and every repository's history is walked once more. The session legend dictionary is `dictv=74f30d013d02c2a2 entries=829`. ### Fixed — a call the language resolves outside the tree no longer binds to a same-named in-repo definition diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 9cc57f07f..00d9ffe06 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-08.md`), trimmed to the first few lines and +Sample output is lifted from a real recorded run (`docs/captures/COMMANDS_showcase_2026-10-10.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. @@ -68,21 +68,21 @@ _Same map, capped to the 5 highest-ranked symbols._ ``` $ ./build/ripwire . --top-k=5 - + - - + + - + - + -... [11 more line(s); run it to see the whole thing] +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--token-budget`, `--for`, `--recall`, `--graph-query`, `--pack-signatures`, `--expand`, `--from-trace`, `--run-trace` @@ -117,10 +117,10 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ $ ./build/ripwire . --max-tokens=1500 - + - + @@ -147,10 +147,10 @@ _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`, `--recall`, `--handoff`, `--pr-context`, `--from-trace`, `--run-trace` +**Shaped by:** `--top-k`, `--max-tokens`, `--for`, `--sections`, `--recall`, `--handoff`, `--pr-context`, `--from-trace` **Caveats (stated by the binary):** @@ -184,7 +184,7 @@ $ ./build/ripwire . --help-task="write a cheerful release announcement" **Answers:** the task lens: say what you are doing, get the signatures and bodies to read first the task lens: ranked signatures + metrics framed for reuse. -The bundle enforces a ~7.5KB default payload budget (tail entries trim first; marks it: T rows handed to the trim, S printed). The r=1 (top-ranked) row carries next="--expand=FILE:NAME" — the one pasteable follow-up, the body that ends the search (defined here, not in the bundle's own header, which the token ladder does not charge). An explicit --token-budget=N overrides the default at the conservative byte rate (SHAPES, exit 0; see --token-budget above) and the header reports the delivered est_tokens. TERMINAL BY DEFAULT: after the signatures, the top-ranked symbols' FULL bodies ride inline (CDATA + callee signatures, the --expand shape) under a fixed extra body allowance — whole-body-or-not-at-all, rank-first, capped at the --pack-task candidate cap (6). The root discloses it: bundle="auto" bodies="N" (bodies="0" reason="budget" when none fit) — on EVERY auto-mode run: a ceiling the signatures alone exhaust still carries the attribute (legend and empty shell dropped there; only the attribute has reserved bytes), and --for --json, which serves no bodies by design, says so with "bundle":"sigs". Only the caller-chosen postures (--signatures-only, --detail=N) are attribute-free. ANCHOR-ONLY when the route names one: a query that NAMES a symbol gets THAT symbol's own body or NO body — never a same-named doc section, type stub or re-export shim from another file standing in for it. If the anchor's own body does not fit, the bundle serves nothing and says so, and the per-item over-budget comment names what was dropped. COMPACT ON THE CONCEPTUAL ROUTE: a query that anchors nothing (subtoken+body) gets the ranked map plus a section — the same candidate head's ONE-HOP callee signatures, the block a body carries — and NO body CDATA, disclosed as bundle="compact" bodies="0" reason="compact-route". Read the map, then --expand=SYM the one you want. --auto-bodies restores the body walk there. That shape discloses on every run too: a ceiling the signatures alone exhaust carries bundle="compact" bodies="0" reason="budget" — three distinct reasons, never collapsed (compact-route = the route chose edges, no_candidates = nothing scored, budget = the ceiling was spent). An explicit --token-budget=N is a hard ceiling, split so a wider ceiling never buys less: the signature side's claim is capped at the DEFAULT ~7.5KB sig budget and every byte beyond it flows to the enrichment — at any ceiling at or above the default's effective total the block is byte-identical to the default run's, so every body (or hop row) the default serves still fits. An explicit --pack-top-n is an explicit SIG posture and keeps the whole-ceiling sig claim. --compress composes: the served bodies (auto/anchor and --detail=N alike) go through the same comment-strip --expand uses, disclosed as compress="1" on the element (nothing to strip on the compact route). RANKING CONFIDENCE, disclosed not scored: the root always carries confidence="high|low" margin_pct="N" — derived from the SAME relevance-cliff gap statistic --adaptive cuts at (no new scorer, no behavior change; the --json dialect carries the same two keys). low means the ranking is FLAT (no material score cliff and more positive matches than the head shows) — treat the set as a starting point, not an answer; high means a material cliff inside the served head (margin_pct= is that drop as a whole percent) or every positive match already shown. COVERAGE, and the widening page: on a THIN answer only, the root also carries coverage="N" — the IDF-weighted share (whole percent) of the task's subtokens found in the top-ranked symbol's name, doc or body; an unmatched subtoken weighs as the rarest, so a "(#12147)" token honestly lowers it. THIN = coverage under 50, or a ranked head spread over fewer than 3 files: coverage= and its legend clause ride the root (present-only: a confident answer carries neither) and the r=1 row's next= names --for=TASK --limit=40 instead of --expand — the FILE-GRAIN WIDENING PAGE: …, ONE row per positive-score file (~100 B each), ranked file-first by score= = the IDF-weighted share of the task's subtokens the file's top 8 symbols cover between them (a term counts once however often it recurs, so one huge file cannot monopolise; ties by the best symbol's lens score, then path), n= its positive-score symbols, sym= its top symbols. --limit=N sets the rows, --offset=M pages (the house quintet + next= on a cut page). The page takes NO bundle-shaping flag (--json, --format=candidates, --detail, --signatures-only, --auto-bodies, --adaptive, --token-budget, --top-k …): each is refused beside --limit, never ignored. Measured on the routing-loop ladder (RocksDB, frozen 30): the follow-up that completes answers where a body cannot. / COLLAPSE TO A COUNTED STUB BY DEFAULT (L2, a disclosed cut, never a silent one): when the ranked set names an interface with implementors, or a HAS-A field edge into/out of it, the bundle used to spend the full // method contract or row list on it — the STUB instead says only how many rows the section held: (same shape for ). No stub for a section that would be EMPTY (an absent section stays absent). total= is that section's own pre-cap row count (packLego's post-dedup interface count; every matched HAS-A edge for compose, which caps nothing). next= carries the one restoring spelling that returns BOTH sections byte-identical to the un-stubbed render, in ONE call: --sections=lego,compose. +The bundle enforces a ~7.5KB default payload budget (tail entries trim first; marks it: T rows handed to the trim, S printed). The r=1 (top-ranked) row carries next="--expand=FILE:NAME" — the one pasteable follow-up, the body that ends the search (defined here, not in the bundle's own header, which the token ladder does not charge). An explicit --token-budget=N overrides the default at the conservative byte rate (SHAPES, exit 0; see --token-budget above) and the header reports the delivered est_tokens. TERMINAL BY DEFAULT: after the signatures, the top-ranked symbols' FULL bodies ride inline (CDATA + callee signatures, the --expand shape) under a fixed extra body allowance — whole-body-or-not-at-all, rank-first, capped at the --pack-task candidate cap (6). The root discloses it: bundle="auto" bodies="N" (bodies="0" reason="budget" when none fit) — on EVERY auto-mode run: a ceiling the signatures alone exhaust still carries the attribute (legend and empty shell dropped there; only the attribute has reserved bytes), and --for --json, which serves no bodies by design, says so with "bundle":"sigs". Only the caller-chosen postures (--signatures-only, --detail=N) are attribute-free. ANCHOR-ONLY when the route names one: a query that NAMES a symbol gets THAT symbol's own body or NO body — never a same-named doc section, type stub or re-export shim from another file standing in for it. If the anchor's own body does not fit, the bundle serves nothing and says so, and the per-item over-budget comment names what was dropped. COMPACT ON THE CONCEPTUAL ROUTE: a query that anchors nothing (subtoken+body) gets the ranked map plus a section — the same candidate head's ONE-HOP callee signatures, the block a body carries — and NO body CDATA, disclosed as bundle="compact" bodies="0" reason="compact-route". Read the map, then --expand=SYM the one you want. A ranked function or method whose whole name is a word of the query (by name, not resolved) gets the first rows, past that head: up to 3, each listing its callees to 100 names (capped=1 next= past it); more are counted as qword_cut= with next=. --auto-bodies restores the body walk there. That shape discloses on every run too: a ceiling the signatures alone exhaust carries bundle="compact" bodies="0" reason="budget" — three distinct reasons, never collapsed (compact-route = the route chose edges, no_candidates = nothing scored, budget = the ceiling was spent). An explicit --token-budget=N is a hard ceiling, split so a wider ceiling never buys less: the signature side's claim is capped at the DEFAULT ~7.5KB sig budget and every byte beyond it flows to the enrichment — at any ceiling at or above the default's effective total the block is byte-identical to the default run's, so every body (or hop row) the default serves still fits. An explicit --pack-top-n is an explicit SIG posture and keeps the whole-ceiling sig claim. --compress composes: the served bodies (auto/anchor and --detail=N alike) go through the same comment-strip --expand uses, disclosed as compress="1" on the element (nothing to strip on the compact route). RANKING CONFIDENCE, disclosed not scored: the root always carries confidence="high|low" margin_pct="N" — derived from the SAME relevance-cliff gap statistic --adaptive cuts at (no new scorer, no behavior change; the --json dialect carries the same two keys). low means the ranking is FLAT (no material score cliff and more positive matches than the head shows) — treat the set as a starting point, not an answer; high means a material cliff inside the served head (margin_pct= is that drop as a whole percent) or every positive match already shown. COVERAGE, and the widening page: on a THIN answer only, the root also carries coverage="N" — the IDF-weighted share (whole percent) of the task's subtokens found in the top-ranked symbol's name, doc or body; an unmatched subtoken weighs as the rarest, so a "(#12147)" token honestly lowers it. THIN = coverage under 50, or a ranked head spread over fewer than 3 files: coverage= and its legend clause ride the root (present-only: a confident answer carries neither) and the r=1 row's next= names --for=TASK --limit=40 instead of --expand — the FILE-GRAIN WIDENING PAGE: …, ONE row per positive-score file (~100 B each), ranked file-first by score= = the IDF-weighted share of the task's subtokens the file's top 8 symbols cover between them (a term counts once however often it recurs, so one huge file cannot monopolise; ties by the best symbol's lens score, then path), n= its positive-score symbols, sym= its top symbols. --limit=N sets the rows, --offset=M pages (the house quintet + next= on a cut page). The page takes NO bundle-shaping flag (--json, --format=candidates, --detail, --signatures-only, --auto-bodies, --adaptive, --token-budget, --top-k …): each is refused beside --limit, never ignored. Measured on the routing-loop ladder (RocksDB, frozen 30): the follow-up that completes answers where a body cannot. / COLLAPSE TO A COUNTED STUB BY DEFAULT (L2, a disclosed cut, never a silent one): when the ranked set names an interface with implementors, or a HAS-A field edge into/out of it, the bundle used to spend the full // method contract or row list on it — the STUB instead says only how many rows the section held: (same shape for ). No stub for a section that would be EMPTY (an absent section stays absent). total= is that section's own pre-cap row count (packLego's post-dedup interface count; every matched HAS-A edge for compose, which caps nothing). next= carries the one restoring spelling that returns BOTH sections byte-identical to the un-stubbed render, in ONE call: --sections=lego,compose. **Try it** @@ -192,19 +192,19 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` - + & p, float alpha = 0.85f ) ... [17 more line(s); run it to see the whole thing] ``` @@ -221,7 +221,7 @@ $ ./build/ripwire . --for="rankGraphTeleport" **Answers:** (with --for) opt back into the full / render the stub above replaces (with --for=TASK) restore the / sections the ranked bundle collapses to a counted stub by default — this IS the stub's own next= spelling, so pasting it back unchanged is enough. -A comma-separated, order-insensitive, closed set (lego and/or compose, each named at most once); every other section of the bundle is unaffected. +A comma-separated, order-insensitive, closed set (lego and/or compose, each named at most once); every other section of the bundle is unaffected — the budget is the stubbed answer's. Under an explicit --token-budget the restored sections are charged in full, so can carry fewer rows there. **Try it** @@ -229,14 +229,14 @@ _Restore the / sections the --for bundle collapses to a counted s ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --sections=lego,compose - + - - + + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() ... [21 more line(s); run it to see the whole thing] @@ -256,14 +256,14 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] #### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -288,17 +288,17 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - + - - + + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() -struct FileHealth +struct FileHealth ... [20 more line(s); run it to see the whole thing] ``` @@ -321,16 +321,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] ``` @@ -353,14 +353,14 @@ _Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothin ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --adaptive - + - - + + inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) +TSTree* parseTree( TSParser* parser, std::string_view src ) Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() ... [21 more line(s); run it to see the whole thing] @@ -384,10 +384,10 @@ _Same task with the anchor disabled — the contrast the flag exists for._ ``` $ ./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25" --no-mention-boost - + - + The name-exact ranker AS THE RETRIEVAL LENS SERVES IT: whole-name BM25 plus the definition-over-…inline std::vector<float> lexicalScoresNameExactRanked( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) @@ -415,11 +415,11 @@ _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 - + -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfSc … [line truncated: 14 more bytes on this line] runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) @@ -445,7 +445,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -475,7 +475,7 @@ _The repo's best-in-class instance to imitate before writing new code (picked by $ ./build/ripwire . --exemplar="format byte sizes for humans" - + @@ -669,7 +674,7 @@ _Stable (path/id) emit order — provider KV-cache hits across re-runs._ ``` $ ./build/ripwire . --order=stable --top-k=5 - + @@ -683,7 +688,7 @@ $ ./build/ripwire . --order=stable --top-k=5 -... [11 more line(s); run it to see the whole thing] +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-stable` @@ -700,20 +705,21 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable ``` $ ./build/ripwire . --no-stable --top-k=3 - + - - + + - + - + - + +... [17 more line(s); run it to see the whole thing] ``` --- @@ -735,8 +741,8 @@ $ ./build/ripwire . --around=rankGraphTeleport - - + + @@ -786,8 +792,8 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - - + + @@ -817,16 +823,16 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - + - - + + - - - - - + + + + + ``` @@ -851,17 +857,17 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - + - + + - - + - - + + ... [17 more line(s); run it to see the whole thing] @@ -888,19 +894,19 @@ _Names referenced but never defined in-corpus (stdlib/third-party surface). The ``` $ ./build/ripwire . --external-surface - - - - - - - + + + + + + + - - - - - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -923,12 +929,12 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - - - - - - + + + + + + ``` @@ -945,10 +951,10 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + - + @@ -973,16 +979,16 @@ $ ./build/ripwire . --impact=rankGraphTeleport - - - - - + + + + + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1055,7 +1061,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - @@ -1117,7 +1123,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: f1e5c2e76 +at: 39fd059eb (0 changed files — working tree is clean, nothing to analyze) ``` @@ -1135,19 +1141,19 @@ _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] ``` @@ -1171,7 +1177,7 @@ _Pre-PR gate on a CLEAN tree: no obligations, exit 0._ ``` $ ./build/ripwire . --test-gate - ``` @@ -1196,15 +1202,15 @@ _Literal trigram-indexed search. Each hit carries its MATCHED line as the $ ./build/ripwire . --grep=DISCLOSE - + - + - + @@ -1498,10 +1504,10 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + - - + + NodeId order. Renumbering an otherwise-identical graph (the exact "same probe file, different so…inline constexpr float kScoreTieAbsEps = 1e-6f @@ -1533,12 +1539,12 @@ $ ./build/ripwire . --pack-signatures --top-k=10 - + - + - + @@ -1564,7 +1570,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)" ); @@ -1595,7 +1601,7 @@ $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport @@ -1700,10 +1706,10 @@ $ ./build/ripwire . --expand=readAckRecords --top-k=0 --no-redact + - - + + - + - + - + @@ -1767,11 +1773,11 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - - - + + + + + @@ -1779,7 +1785,7 @@ $ ./build/ripwire . --deps - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1804,18 +1810,18 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - - - - - - - + + + + + + + - - - - + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1832,7 +1838,7 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - + @@ -1863,12 +1869,12 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - - - - - - + + + + + + @@ -1896,7 +1902,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -1931,18 +1937,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] ``` @@ -1962,18 +1968,18 @@ _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] ``` @@ -1999,13 +2005,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] ``` @@ -2030,8 +2036,8 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - - + + @@ -2041,7 +2047,7 @@ $ ./build/ripwire . --naming-calibration

-

+

... [14 more line(s); run it to see the whole thing] ``` @@ -2065,18 +2071,18 @@ _The corpus's OWN case-convention vote per (language, kind) group; off-conventio ``` $ ./build/ripwire . --naming-consistency --limit=8 - - - - + + + + - + - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2102,19 +2108,19 @@ _The opt-in --lint modifier: naming predicates over LOCAL variable names too, C/ ``` $ ./build/ripwire . --lint --naming-locals - - + + - - + + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2137,9 +2143,9 @@ _Functions WITH a doc comment, most name-restating first: c_coeff (high = the co ``` $ ./build/ripwire . --comment-coherence --limit=8 - + - + @@ -2170,16 +2176,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - + - - - + + + - + ... [22 more line(s); run it to see the whole thing] ``` @@ -2202,16 +2208,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] ``` @@ -2228,17 +2234,17 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - - + + - + - + ... [17 more line(s); run it to see the whole thing] @@ -2257,19 +2263,19 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - - - - - - - - - - - - - + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -2295,7 +2301,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2325,7 +2331,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2351,7 +2357,7 @@ _Enforce layering rules (exit 2 on violation) — run against the repo's own tes $ ./build/ripwire . --arch=test/archfix/rules.txt - @@ -2378,19 +2384,19 @@ _Built-in AST checks (c-cast, goto, unsafe-c-fn, ...)._ ``` $ ./build/ripwire . --lint - - + + - - + + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2487,19 +2493,19 @@ _DROP two families, applied after selection; the raw select=/ignore= you passed ``` $ ./build/ripwire . --lint --lint-ignore=naming-,cache- - + - - + + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2601,19 +2607,19 @@ _Cluster the call graph into cohesive modules._ ``` $ ./build/ripwire . --communities - + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2632,8 +2638,8 @@ _Drill into ONE call-graph community by id — the drill= the --communities outp ``` $ ./build/ripwire . --community=0 - + ``` @@ -2657,19 +2663,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] ``` @@ -2694,16 +2700,16 @@ $ ./build/ripwire . --report # ripwire architecture report -2688 files · 25558 symbols · 40392 edges · 2494 modules (11469 call-graph isolated) +2787 files · 26903 symbols · 41775 edges · 2640 modules (11994 call-graph isolated) Root: `.` -Call-graph isolate provenance: 3087 declaration, 1678 header, 2119 source, 4585 document; 0 connected Louvain singletons +Call-graph isolate provenance: 3221 declaration, 1908 header, 2215 source, 4650 document; 0 connected Louvain singletons -## Modules (call-graph clusters; showing 12 of 2494) -- **src::emitTo@infra/emit.h:53:2760 [write,run,emit]** — 1685 symbols -- **src::append@elixir_resolve.h:111:4875 [add,resolve,compute]** — 1021 symbols -- **src::emplace@infra/svector.h:408:22477 [resolve,scan,find]** — 605 symbols +## Modules (call-graph clusters; showing 12 of 2640) +- **src::emitTo@infra/emit.h:53:2760 [write,run,resolve]** — 1714 symbols +- **src::ASSUME@infra/Diagnostics.h:386:25510 [resolve,collect,add]** — 934 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,scan,read]** — 644 symbols ... [17 more line(s); run it to see the whole thing] ``` @@ -2720,13 +2726,13 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - - - + + + + - @@ -2751,17 +2757,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
7731"] + n101["src
7915"] n102["src/infra
1280"] end subgraph sg1 ["test"] - n103["test
5057"] + n103["test
5126"] + n392["test/legochargefix
546"] n333["test/fixtures/recallpassage
314"] + n448["test/ownerhopfix/py/pkg
158"] n280["test/expandmodefix
151"] n282["test/expandsibsfix
149"] - n535["test/rubyattrsfix
85"] - n411["test/massfix
77"] - n492["test/receiverevidencefix/js/lib
74"] + n589["test/rubyschemafix
99"] ... [17 more line(s); run it to see the whole thing] ``` @@ -2780,19 +2786,19 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - - - - + + + + - + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -2811,7 +2817,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2876,7 +2882,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - @@ -2913,7 +2919,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - @@ -2950,7 +2956,7 @@ _The Delta Maintainability Model scalar for the WORKING TREE vs HEAD — recorde ``` $ ./build/ripwire . --dmm -

@@ -3020,7 +3026,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3050,13 +3056,13 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - - - - - + + + + + ``` @@ -3104,12 +3110,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":12495,"graph_unresolved":12552,"graph_unindexed":255,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"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":743,"graph_ambiguous":12495,"graph_unresolved":12552,"graph_unindexed":255,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3125,12 +3131,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":12495,"graph_unresolved":12552,"graph_unindexed":255,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"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":743,"graph_ambiguous":12495,"graph_unresolved":12552,"graph_unindexed":255,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3176,12 +3182,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":12495,"graph_unresolved":12552,"graph_unindexed":255,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"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":743,"graph_ambiguous":12495,"graph_unresolved":12552,"graph_unindexed":255,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3240,11 +3246,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":12495,"graph_unresolved":12552,"graph_unindexed":255,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 529 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 538 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":12495,"graph_unresolved":12552,"graph_unindexed":255,"counts_floor":true}}]} +"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3290,19 +3296,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] ``` @@ -3327,17 +3333,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] @@ -3381,10 +3387,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:7926 +$ ./build/ripwire . --at=src/graph.h:8051 - - + + ``` @@ -3408,7 +3414,7 @@ _No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - ``` @@ -3434,11 +3440,11 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + @@ -3513,7 +3519,7 @@ _Which refs of the `lane/` ref family still hold divergent authored work vs HEAD ``` $ ./build/ripwire . --stray-content=lane/ - + @@ -3547,7 +3553,7 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + @@ -3558,7 +3564,7 @@ $ ./build/ripwire . --stray-content=lane/ --plan - + ... [17 more line(s); run it to see the whole thing] ``` @@ -3583,20 +3589,20 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi - - - - - - + + + + + + - - - + + + -... [2 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):** @@ -3609,7 +3615,7 @@ $ ./build/ripwire . --stray-content=lane/ --abi **Answers:** find which branch's tree defines or mentions SYM, HEAD first which REF's tree defines or mentions SYM — HEAD first, then every local branch, with on-head="0" naming the case the verb exists for: content that lives only on a branch. -Each distinct blob is read ONCE (content-addressed), so N branches cost ~one tree. kind="def" on a HEAD row is the PARSED index's answer (head_labels="index"); on a REF row it is a LEXICAL heuristic — ref blobs are raw text, never ingested, so a doc quoting a signature still reads as a definition. head_labels="lexical" ⇒ HEAD fell back to that heuristic too (no indexed def of the name, or a working tree that drifted from HEAD). refs_scanned= is the SCAN denominator (refs read besides HEAD), not a matched count. Read-only; single-root only. A checkout that differs from HEAD is read from disk: each changed path's rows say ref="worktree" and replace HEAD's, at= gains +dirty, and worktree= says whether every one was read. LIMITS: a TREE scan finds only what some ref STILL carries, so hits="0" alone cannot tell a name this repo never had from one it deleted, and content dropped by every tree is invisible. Add --with-history: a row then says v="never" or v="removed" with the commit, date and file that removed it. Remote-tracking refs are excluded (they mirror local ones); refs are capped, narrow with --stray-content=SUBSTR. LISTING: by default only the kind="def" rows are listed and the kind="ref" rows counted in one element, when that page lists MORE definitions than the --whereis-listing=all page under the row cap, or the same ones in strictly fewer bytes; otherwise every hit is listed. +Each distinct blob is read ONCE (content-addressed), so N branches cost ~one tree. kind="def" on a HEAD row is the PARSED index's answer (head_labels="index"); on another ref's row (ref_labels="parsed") it is the same extraction run over that ref's blob, once per blob and path, and only for a blob holding a line a loose lexical test calls worth parsing (it only CHOOSES blobs): a blob without one reads kind="ref". kind="text" is such a line no parse read ( counts them; --detail=1 lifts the parse guard), never a def. head_labels="lexical" ⇒ HEAD fell back to that lexical test (no indexed def of the name, or a working tree that drifted from HEAD). refs_scanned= is the SCAN denominator (refs read besides HEAD), not a matched count. Read-only; single-root only. A checkout that differs from HEAD is read from disk: each changed path's rows say ref="worktree" and replace HEAD's, at= gains +dirty, and worktree= says whether every one was read. LIMITS: a TREE scan finds only what some ref STILL carries, so hits="0" alone cannot tell a name this repo never had from one it deleted, and content dropped by every tree is invisible. Add --with-history: a row then says v="never" or v="removed" with the commit, date and file that removed it. Remote-tracking refs are excluded (they mirror local ones); refs are capped, narrow with --stray-content=SUBSTR. LISTING: by default only the kind="def" (and kind="text") rows are listed and the kind="ref" rows counted in one element, when that page lists MORE definitions than the --whereis-listing=all page under the row cap, or the same ones in strictly fewer bytes; otherwise every hit is listed. On that page a definition line several refs hold (same path, same text) is ONE row with refs="N", and folded= on the root counts the rows it stands for (on the first page, shown + more + folded + refs count = hits); listing=all prints each. **Try it** @@ -3617,29 +3623,29 @@ _Which ref's tree defines or mentions SYM — HEAD first, then every local branc ``` $ ./build/ripwire . --whereis=rankGraphTeleport - - - - - - -... [24 more line(s); run it to see the whole thing] + + + + + + + +... [14 more line(s); run it to see the whole thing] ``` **Shaped by:** `--whereis-listing`, `--with-history`, `--limit` **Caveats (stated by the binary):** -- on a REF row it is a LEXICAL heuristic — ref blobs are raw text, never ingested, so a doc quoting a signature still reads as a definition. -- head_labels="lexical" ⇒ HEAD fell back to that heuristic too (no indexed def of the name, or a working tree that drifted from HEAD). +- --detail=1 lifts the parse guard), never a def. - refs_scanned= is the SCAN denominator (refs read besides HEAD), not a matched count. +- LIMITS: a TREE scan finds only what some ref STILL carries, so hits="0" alone cannot tell a name this repo never had from one it deleted, and content dropped by every tree is invisible. ### `--whereis-listing=WHICH` **Answers:** with --whereis: which rows to list, defs, refs or all (default: more defs, else shorter). -The default lists defs when that page lists MORE definitions than all under the same row cap (references can fill a capped all page before the branch definitions arrive), whatever its bytes; when both list the same definitions, defs only when strictly shorter in bytes, else all (a tie lists all). defs lists every kind="def" row and COUNTS the kind="ref" rows in one element whose next= lists exactly them (refs). kind="def" is the parser's label: a definition it does not model (define_method, setattr, a name bound by assignment) is a counted ref. With no ref row, or no def row (the mentions are then the answer), every hit is listed and the root carries no listing=. all lists every row, the whole hit list. shown=/capped= and --limit/--offset window the LISTED rows; hits= counts every row. Refused without --whereis, and on an unknown value. +The default lists defs when that page lists MORE definitions than all under the same row cap (references can fill a capped all page before the branch definitions arrive), whatever its bytes; when both list the same definitions, defs only when strictly shorter in bytes, else all (a tie lists all). defs lists every kind="def" (and kind="text") row and COUNTS the kind="ref" rows in one element whose next= lists exactly them (refs). kind="def" is the parser's label: a definition it does not model (define_method, setattr, a name bound by assignment) is a counted ref. With no ref row, or no def row (the mentions are then the answer), every hit is listed and the root carries no listing=. all lists every row, the whole hit list. shown=/capped= and --limit/--offset window the LISTED rows; hits= counts every row. Refused without --whereis, and on an unknown value. **Try it** @@ -3647,13 +3653,13 @@ _The whole hit list asked for by name: under the same 60-row cap HEAD's referenc ``` $ ./build/ripwire . --whereis=rankGraphTeleport --whereis-listing=all - - - - + + + - - + @@ -3760,8 +3766,8 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - + @@ -3772,7 +3778,7 @@ $ ./build/ripwire . --field-affinity - + ... [17 more line(s); run it to see the whole thing] ``` @@ -3795,17 +3801,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - - - + + + + + + + + - - - - - - + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3830,17 +3836,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - - - + + + + + + + + - - - - - - + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3864,18 +3870,18 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - - - - + + + + + + + + + - - - - - - + + ... [18 more line(s); run it to see the whole thing] ``` @@ -3900,7 +3906,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -3935,10 +3941,10 @@ $ ./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:7926 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:7967 - #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1634 - #3 0x102e01a44 in main src/main.cpp:3999 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:8051 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:8092 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1751 + #3 0x102e01a44 in main src/main.cpp:4169 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) ==41337==ABORTING ``` @@ -3965,7 +3971,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4065,7 +4071,7 @@ $ ./build/ripwire . --pack-task="add a new output format flag to the CLI" (run= when derivable). 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). root=: p= relative to it. : that callee matched by name alone (receiver unprove … [line truncated: 552 more bytes on this line] - + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept claude's `mcp add … -- ripwire --mcp`: the flag goes on the end of that command line, ahead of its newlineinline std::string wrapCliAddPost( const AgentTarget& row, std::string_view toolsArg ) @@ -4092,13 +4098,13 @@ _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). : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. : 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. --> - + 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 ) @@ -4125,10 +4131,10 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - + - - + + NodeId order. Renumbering an otherwise-identical graph (the exact "same probe file, different so…inline constexpr float kScoreTieAbsEps = 1e-6f @@ -4199,14 +4205,14 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - - + - - + + - - + @@ -4233,14 +4239,14 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - - + - - + + - - + @@ -4268,7 +4274,7 @@ $ ./build/ripwire . --skipped - @@ -4356,15 +4362,15 @@ _Rank by git change-frequency prior instead of PageRank._ $ ./build/ripwire . --rank-by=churn --top-k=5 - - + + - + - + @@ -4392,19 +4398,19 @@ _Scope the recent-changes answer to ONE directory. The global block sta $ ./build/ripwire . --rank-by=churn-decay --since=HEAD~4 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - - - - - - - - + + + + + + + - - - -... [11 more line(s); run it to see the whole thing] + + + + +... [9 more line(s); run it to see the whole thing] ``` **Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` @@ -4466,7 +4472,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4494,7 +4500,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=593769ab691623d8 entries=806 +ripwire legend dictionary ripwire.dict/v1 dictv=74f30d013d02c2a2 entries=829 : 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=) @@ -4546,18 +4552,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] ``` @@ -4580,21 +4586,21 @@ _Drop matching paths (repeatable) before ranking._ ``` $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - + - - + + - + - + -... [11 more line(s); run it to see the whole thing] +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--expand`, `--skipped`, `--in`, `--index-out` @@ -4613,14 +4619,14 @@ _Full map re-ranked with teleport toward git-changed files — clean tree, so ch $ ./build/ripwire . --map-diff --top-k=5 - - + + - + - + @@ -4645,20 +4651,21 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ ``` $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - + - - + + - + - + - + +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--doctor`, `--index-out` @@ -4694,20 +4701,21 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co ``` $ ./build/ripwire . --no-cache --top-k=3 - + - - + + - + - + - + +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--doctor` @@ -4724,20 +4732,21 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses ``` $ ./build/ripwire . --no-ignore --top-k=3 - + - - + + - + - + - + +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--expand` @@ -4759,21 +4768,21 @@ _Skip files above a size bound before parsing (note the corpus shrink in the hea ``` $ ./build/ripwire . --max-file-size=8K --top-k=3 - + - - + + - + - + - -... [3 more line(s); run it to see the whole thing] + +... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--expand`, `--skipped` @@ -4839,20 +4848,21 @@ _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 - + - - + + - + - + - + +... [17 more line(s); run it to see the whole thing] ``` ### `--mcp` @@ -4894,10 +4904,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":" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [815 more display lines; full output is 26837 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --top-k=5` + +*Same map, capped to the 5 highest-ranked symbols.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [67 more display lines; full output is 8262 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --top-k=0 --expand=rankGraphTeleport` + +*NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map rides along with the body you asked for.* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --token-budget=100` + +*GATE form: exit 3 if the map's own est_tokens exceeds the budget (over-budget failure shape).* + +**exit code: 3** + +````` + + +````` + +stderr: + +````` +ripwire: --token-budget exceeded: withheld_est_tokens=10806 > 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: 2.60s** + +````` + + + + +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 + +the cached index for `root`, rebuilt only when stale (otherwise returned as-is, no parse, no gra…inline const McpIndex& getIndex( const std::string& root ) +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +inline std::uint64_t contentHash64( std::string_view s ) noexcept +struct CacheEntry +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 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 bool ingestCommitTree( const std::string& root, const std::string& sha, const std::vector<std::string>& excludes, std::size_t maxFileBytes, IngestResult& … [line truncated: 7 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::vector<std::vector<NodeId>> legoImplementorsOnSurface( const IngestResult& ing, const std::vector<std::vector<NodeId>>& impl … [line truncated: 63 more bytes on this line] +inline std::uint64_t indexContentHash( const std::vector<std::string>& files, const std::vector<long long>& fileMtime, const std::vector<std::uint64_t> … [line truncated: 25 more bytes on this line] +inline std::string makeHandle( const std::string& canonId, const std::string& path, const std::string& name, std::uint64_t contentHash ) +struct IngestFileScan +inline std::string spanTierMemoPath( const std::string& diskPath ) +inline RawFacts runParsePool( IngestResult& result, const char* rootDir, std::string_view cacheFile, bool captureValueUses, HashMap<std::string, FileFacts>& cac … [line truncated: 99 more bytes on this line] +inline void collectExtractPartials( const IngestFileScan& scan, IngestResult& result ) +inline std::uint64_t byteHash( const char* data, std::size_t n ) noexcept +… [48 more display lines; full output is 10037 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: 1.05s** + +````` + + + + + +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; + } + if( teleportMass > 0.0 ) + { +… [12 more display lines; full output is 4505 bytes on 29 raw line(s)] +````` + +## `./build/ripwire . --for="rankGraphTeleport" --no-route` + +*Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* + +**wall time: 1.03s** + +````` + + + + + +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 +… [105 more display lines; full output is 16395 bytes on 72 raw line(s)] +````` + +## `./build/ripwire . --for="rankGraphTeleport" --signatures-only` + +*T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribute) — contrast with the terminal default above.* + +**wall time: 1.03s** + +````` + + + + + +PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` + + + + +````` + +## `./build/ripwire . --for="tree-sitter parse of a source file" --adaptive` + +*Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothing is cut and the header says so ([adaptive: kept N of N]).* + +**wall time: 1.01s** + +````` + + + + +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 +enum class AstWalk : std::uint8_t +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar…constexpr std::array<LangEntry, 53> kLangTable = +struct SliceScan +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 bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +inline ModuleScan scanModule( std::string_view source, std::string_view path ) +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 void runParseWorker( ParsePoolShared& sh, unsigned t ) +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline bool hasPytestTable( std::string_view source ) +… [55 more display lines; full output is 9999 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25"` + +*Mention anchoring (default-on): a path and a Symbol literally named in the task get lifted; the header says what anchored.* + +**wall time: 1.06s** + +````` + + + + +The name-exact ranker AS THE RETRIEVAL LENS SERVES IT: whole-name BM25 plus the definition-over-…inline std::vector<float> lexicalScoresNameExactRanked( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) + +P11.12: the interpretive footer for --eval's ranker table, pulled into its own function so the 9…inline void printEvalRankerNote() +inline int runEvalRetrieval( const IngestResult& ing, const Graph& g ) + +calling agent knows to reformulate rather than trust the ranking. Calibrated empirically (2026-0…inline constexpr float kWeakLexicalScoreThreshold = 1.0f +inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) +inline std::string candidatesRootTag( std::size_t keep, std::size_t corpusCount, const CandidateProvenance& prov ) +inline std::vector<float> lexicalScoresNameExactTiered( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) +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 LensRanking +inline bool hasIdentifierShape( std::string_view w ) noexcept +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::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) +struct RecallSectionPick +inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept +… [75 more display lines; full output is 10887 bytes on 1 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.* + +````` + + + + +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 +struct LensRanking +inline bool hasIdentifierShape( std::string_view w ) noexcept +inline RouteChoice chooseForRanker( const IngestResult& ing, std::string_view query ) +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::string_view takeQualifiedIdent( std::string_view src, std::size_t& i ) +inline std::vector<float> lexicalScoresNameExact( const IngestResult& ing, std::string_view query ) +struct RecallSectionPick +inline bool canonicalIdMatches( std::string_view canonAbs, std::string_view spec ) noexcept +… [77 more display lines; full output is 10743 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lego=Vehicle` + +*Interface -> implementors view: every existing impl of the named interface; the method contract is extracted for the C-family/Java/TS/Python tiers — for a Rust trait (this fixture) it discloses caveat="not-extracted-for-lang" rather than an empty list.* + +````` + + + + + + + + + +````` + +## `./build/ripwire . --exemplar="format byte sizes for humans"` + +*The repo's best-in-class instance to imitate before writing new code (picked by ROLE).* + +````` + + + + +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.* + +````` + + + + +````` + +## `./build/ripwire . --recall="quality delta gating exit codes"` + +*Most relevant DOCS' full bodies (markdown only) — recall what is already written down.* + +````` +ripwire recall — "quality delta gating exit codes" — 113 relevant of 218 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5028 + +━━ README.md (relevance 8.079) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 217180 B; lines="2746-2758,2759-2766"; dropped_by_budget=23] +#### 6.2 Exit codes + +This is the part to wire into a script. + +| Code | Meaning | +| --- | --- | +| 0 | The command completed. | +| 1 | The command refused the request. A refusal names the reason on stderr. When `--callers`, `--callees`, `--uses`, `--impact` or `--path` refuse a selector that matches no indexed definition, they also print an answer on stdout: the verb's element with `found="0"` and the name to retry with. | +| 2 | A policy gate fired: `--arch` found a layering violation, `--scan-skill` found a CRITICAL, `--quality-delta` found new debt. | +| 3 | The output exceeded the token budget that you set. | +| 4 | `--test-gate` found an open obligation. | +| 5 | The memory guard stopped the run: over `--max-memory` (default 65% of the machine's memory) with no partial answer possible, or a verb other than the default map facing a partial index. One stderr line names the limit and the override. | + +#### 6.3 JSON output + +`--json` emits JSON instead of XML, with keys mirroring the XML attribute names one to one. It is an +allow-list: the default map, `--for`, `--pack-task`, `--callers`, `--callees`, `--impact`, +`--quality-delta`, `--test-gate`, `--metrics`, and `--plan-lanes`, which is JSON-native. Every other +verb refuses on stderr with exit 1 rather than silently falling back to XML, so a verb added tomorrow +refuses by default. + + + +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.427) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +### Root spelling (maintainer-owned; reproduce it once so you recognise it) + +```bash +… [150 more lines, 12829 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 1386497-byte scratch dir of DUMPED TOOL OUTPUT (a git log, this repo's own generated command reference, --help text, an architecture doc, a fabricated JSON access log) instead of a source repo — no index to build, no daemon.* + +````` +ripwire recall — "field affinity cache line data layout which fields are read together" — 2 relevant of 2 document files, best-first — total=2 shown=1 capped=1 truncated=1 max_tokens=1200 share_bytes=2040 est_tokens=952 + +━━ commands.md (relevance 11.075) ━━ [sections: 1 of 160 selected (189 in doc), section-granular; whole doc 562840 B; lines="531-556"; dropped_by_budget=159] [truncated: 1854 of 4257 bytes] +#### Pattern: a directory of dumped tool output as a knowledge base + +**Answers:** can `--recall` serve as a zero-setup knowledge base over dumped tool output — a +`git log`, an API response dump, a fetched doc, ` --help` text — sitting in a scratch +directory, instead of a source repo? + +Yes, unmodified. `--recall` never distinguishes "a codebase" from any other directory it can +walk: point it at the scratch dir and query it. No index to build, no daemon, no mutable store +between runs — the whole cost is one cold parse. Two conditions decide whether it works at all, +and both are yours, because the file you write is the only thing that sets them: + +1. **Dump to `.md`.** `--recall` ranks DOCUMENT files: `.md`, plus the docparse'd + `.ipynb`/`.html`/`.csv` (and Office/PDF through the optional markitdown bridge). `.txt`, + `.log`, `.json` and extensionless files are **not** documents to it. A directory of those + answers `0 relevant of 0 document files` and exits 0 — which reads like "nothing matched + your terms" when what happened is "nothing was indexed at all". Redirect to `notes.md`, + never `notes.txt`. The recorded run below is that rule's own demonstration: its scratch dir + holds five dumps, and the header says `2 relevant of 2 document files` because only the two + `.md` ones are documents — the `git log`, the `--help` text and the JSON access log are not + in the population at all. + +2. **Keep `##` headings in the dump.** A headed document is served as whole ranked SECTIONS, so + an answer buried mid-file arrives at a small `--max-tokens`, and — while the served document + SET stays fixed — a larger ceiling returns a strict superset of it; the `[sections: S of R + selected (N in doc) … lines="…"; dropped_by_budget=D]` note names the ranges you actually got + and what the ceiling cost. … + +(capped: 1 of 2 relevant document files omitted — raise --max-tokens/budget_tokens or narrow the query for 1 more (~2548-byte budget)) +````` + +stderr: + +````` +ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pass --no-redact to disable +````` + +## `./build/ripwire . --tree` + +*File-by-file orientation map (top symbols per file).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [367 more display lines; full output is 11224 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --html=/aux/map2.html` + +*Self-contained HTML force-directed call graph.* + +**wall time: 2.35s** + +````` +(empty) +````` + +Artifact written: + +````` + 159465 /aux/map2.html +````` + +## `./build/ripwire . --order=stable --top-k=5` + +*Stable (path/id) emit order — provider KV-cache hits across re-runs.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [67 more display lines; full output is 8280 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [198 more display lines; full output is 9131 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [2701 more display lines; full output is 80092 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [20 more display lines; full output is 4221 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --callers=rankGraphTeleport` + +*Who calls SYM (1-hop in-edges).* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --callers=rankGraphTeleport --legend=full` + +*The same rows under --legend=full: the default is the compact dialect (one compact comment plus schema="ripwire.callers/v1" on the root), and --legend=full restores the prose legend — every row byte and every completeness attribute (counts_floor=, graph_ambiguous=, next=) identical, ~3 KB more legend. Works on EVERY XML verb.* + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --callers=DoesNotExist` + +*Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distance.* + +**exit code: 1** + +````` + + +````` + +stderr: + +````` +ripwire: --callers symbol not found: DoesNotExist +````` + +## `./build/ripwire . --callees=rankGraphTeleport` + +*What SYM calls (1-hop out-edges).* + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --uses=rankGraphTeleport` + +*The resolvable use-sites (call/read/write/import/extends) with file:line; count= is a floor.* + +````` + + + + + + + + + + + + +````` + +## `./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))'` + +*Composable node-set query: functions within 2 caller-hops of rankGraphTeleport.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [28 more display lines; full output is 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5589 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --external-surface --include-builtins` + +*The restoring knob: --include-builtins keeps the shell builtins in the same 100-row window (they now compete for rows, so the window's tail differs).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [73 more display lines; full output is 5449 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --path=main,rankGraphTeleport` + +*Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_defs and resolves the right `main` (was reachable="0").* + +````` + + + + + + + + +````` + +## `./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex` + +*Minimal connecting subgraph over 3 symbols (finds shared-caller joins).* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --impact=rankGraphTeleport` + +*Transitive blast radius — everything that reaches SYM. NOW carries shown/capped.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [50 more display lines; full output is 5372 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --mentions=rankGraphTeleport` + +*Markdown docs that name SYM in a backtick (doc<->code edges).* + +````` + + + + + + + + + + +````` + +## `./build/ripwire . --affected=src/graph.h` + +*Test files that transitively reach the changed file.* + +````` + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --situ` + +*Mid-task situational report for the current git diff — recorded against a CLEAN tree (contrast with the sandbox run below).* + +````` +ripwire situational-awareness — 0 changed file(s), 0 symbols in them +root: . +at: 39fd059eb + (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: 2.15s** + +````` + + +````` + +## `./build/ripwire . --grep=DISCLOSE` + +*Literal trigram-indexed search. Each hit carries its MATCHED line as the element's own CDATA (the wrapper is gone), plus shown/capped/hits_capped and a pasteable next= on the root.* + +````` + +if data.get( "schema" ) != SCHEMA: raise SystemExit( f"{path}: unexpected schema {data.get('schema')!r} (expecte +if not str( data.get( "tasks_lock_content_sha256", "" ) ).startswith( "questions:" ): train_repos = select_tasks +if train_repos: raise SystemExit( f"{path}: records from repo(s) that re-derive to LocBench +if base and ctx and base["status"] == "ok" and ctx["status"] == "ok": paired.append( ( instance_id, base["re +if not repos: return 0.0, [] +if base["localization_hit"] is None or ctx["localization_hit"] is None: return 0.0 +if bv: ratios.append( cv / bv - 1 ) +if not ratios: return None, None +if rw is None or native is None: return None +if not paired: out["note"] = "zero complete paired (baseline,ripwire_cli) runs — nothing to analyze yet" +if contaminated: print( f" ** {contaminated} baseline run(s) invoked ripwire despite the no-ripwire contract " +if "note" in out: print( f" {out['note']}" ); return +if out["n_pairs"] != 27: failures.append( f"expected 27 paired runs, got {out['n_pairs']}" ) +if out["n_incomplete"] != 2: failures.append( f"expected 2 incomplete pairs (the orphan + the contaminated-basel +if out["n_repos"] != 3: failures.append( f"expected 3 repos, got {out['n_repos']}" ) +if out.get( "n_contaminated_baseline" ) != 1: failures.append( f"expected exactly 1 contaminated baseline run co +if not ( out["resolved_delta_mean"] > 0 ): failures.append( "expected a positive resolved-rate delta" ) +if not ( out["resolved_delta_bootstrap_95_lower"] > 0 ): failures.append( "expected a POSITIVE bootstrap 95% low +if out["tokens_out_ratio_p50"] is None or abs( out["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( f"e +if out.get( "n_resolved_pairs" ) != 27: failures.append( f"expected all 27 pairs resolution-scored, got {out.get +if out.get( "substitution_rate_baseline" ) != 0.0: failures.append( f"expected baseline substitution rate 0.0 (n +if out.get( "substitution_rate_ripwire" ) is None or abs( out["substitution_rate_ripwire"] - 0.75 ) > 1e-9: fail +if out.get( "n_substitution_ripwire" ) != 27: failures.append( f"expected 27 substitution-scored ripwire runs, g +if out3.get( "substitution_rate_ripwire" ) is not None or out3.get( "n_substitution_ripwire" ) != 0: failures.ap +if out2["n_pairs"] != 27: failures.append( f"evaluator-none: expected 27 pairs, got {out2['n_pairs']}" ) +if out2["n_resolved_pairs"] != 0: failures.append( f"evaluator-none: expected 0 resolution-scored pairs, got {ou +if out2["resolved_delta_mean"] is not None or out2["resolved_delta_bootstrap_95_lower"] is not None: failures.ap +if out2["tokens_out_ratio_p50"] is None or abs( out2["tokens_out_ratio_p50"] - 0.08 ) > 1e-6: failures.append( " +… [73 more display lines; full output is 16956 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --match='(if_statement) @i'` + +*The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* + +**wall time: 1.19s** + +````` + +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).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [24 more display lines; full output is 3484 bytes on 1 raw line(s)] +````` + + +--- + +# zoom the detail ladder + +## `./build/ripwire . --for="pagerank power iteration" --detail=2` + +*Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* + +**wall time: 1.01s** + +````` + + + + + +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] + +What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure +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 +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 +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 powerShellPathPrependHint( std::string_view programDir ) noexcept +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +handle_t leftmost_leaf() const +COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "the --match / - … [line truncated: 48 more bytes on this line] +inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) +inline std::string doctorPathPrependLine( const std::string& selfPath ) +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your agent is on a budget +… [108 more display lines; full output is 14910 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: 92.7% fewer bytes at top-10, 88.7% at top-50, 87.6% at top-100 (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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [132 more display lines; full output is 11518 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --outline=rankGraphTeleport --top-k=0` + +*Control-flow skeleton of one symbol, payload-only via the new --top-k=0.* + +````` + + + + +& p, float alpha = 0.85f ) +{ + PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); + const std::vector pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + ... + } + std::vector r( N, 0.f ); + std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); + return { std::move( r ), run.iterationCount, run.hasConverged }; +} +]]> +````` + +## `./build/ripwire . --outline=rankGraphTeleport:1-10 --top-k=0` + +*CHANGED: a line range on --outline is now STRIPPED with a stderr note (it used to refuse).* + +````` + + + + +& p, float alpha = 0.85f ) +{ + PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); + const std::vector pw = biasPrior( g, p ); + const std::size_t N = pw.size(); + std::vector teleport( pw.begin(), pw.end() ); + std::vector rankDouble( N, 0.0 ); + PageRankRun run{}; // an N == 0 graph never enters the kernel: { 0, converged } — see PageRankRun + if( N ) + { + ... + } + std::vector r( N, 0.f ); + std::transform( rankDouble.begin(), rankDouble.end(), r.begin(), []( double value ) { return float( value ); } ); + return { std::move( r ), run.iterationCount, run.hasConverged }; +} +]]> +````` + +stderr: + +````` +ripwire: --outline=rankGraphTeleport:1-10: --outline has no line-range form — outlining the whole symbol (use --expand=rankGraphTeleport:1-10 for a body slice) +````` + +## `./build/ripwire . --expand=rankGraphTeleport --top-k=0` + +*Full body + inline callee signatures.* + +````` + + + + +, with a BRANCH-FREE size(). Both, not one or the other. +// +// ── THE DESIGN, AND WHY IT IS THIS ONE ─────────────────────────────────────────────────────────────── +// The shape the host tree leans on hardest is `Map>` — many tiny id-lists (byName / +// canonByName / shard maps, and ~100 more structures after the conversion wave): WRITE-ONCE during the +// parse/merge, then READ-HOT during resolve. +// +// Three things matter for that shape, and the layout below gets all three: +// • no per-list malloc — the N small lists that would each allocate are inline; +// • a BRANCH-FREE size() — `return sz_`, because the size lives in its own field; +// • 16 BYTES per instance — because `inl_` and `heap_` are never both live, so they share storage. +// +// The union is the whole trick. The previous revision of this file paid 8 extra bytes (24 B) for the +// explicit size field, on the reasoning that a branch-free size() was worth it. That was a false +// choice: union the inline array with the heap pointer and the struct reaches 16 B — ankerl's size — +// while the size stays in its own field and size() stays branch-free. +// +// THE TRADE THAT REMAINS, stated honestly. At 16 bytes you can have: +// • ankerl::svector's 3 inline slots, with a size() that branches on is_direct() (and, once spilled, +// dereferences into the heap block to read the size — a dependent load, not just a branch); or +// • this type's 2 inline slots, with size() branch-free. +// The measurement chose the second. See bench/SVECTORAB.md; the short version is below. +// +… [1086 more display lines; full output is 66262 bytes on 1112 raw line(s)] +````` + +stderr: + +````` +ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchanged behavior for now) +````` + + +--- + +# assess quality / structure + +## `./build/ripwire . --metrics --top-k=10` + +*Fan-in/out + complexity annotations on the map.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [91 more display lines; full output is 10820 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --deps` + +*File->file dependency graph (god-files, cycles).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [898 more display lines; full output is 23488 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --hotspots` + +*Complexity x recent git churn (maintenance pain).* + +**wall time: 1.66s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [14 more display lines; full output is 5749 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --clones` + +*Token-normalized duplicate bodies.* + +**wall time: 3.13s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [306 more display lines; full output is 16116 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --cochange` + +*Files that change together in git (hidden coupling).* + +**wall time: 2.32s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --hotspots --since="2 weeks ago"` + +*Hotspots scoped to RECENT churn (the regression lens).* + +**wall time: 2.13s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [13 more display lines; full output is 5089 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [678 more display lines; full output is 99226 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint` + +*Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* + +**wall time: 2.74s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [695 more display lines; full output is 70057 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint-rules=test/lintrulesfix/rules` + +*User lint rules (YAML, ast-grep style) from a directory.* + +````` + + + + +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:395, 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [188 more display lines; full output is 17290 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=.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [171 more display lines; full output is 8794 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [707 more display lines; full output is 28629 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --report` + +*Architecture summary (modules, god-files, cycles) as markdown.* + +````` + + +# ripwire architecture report + +2787 files · 26903 symbols · 41775 edges · 2640 modules (11994 call-graph isolated) + +Root: `.` + +Call-graph isolate provenance: 3221 declaration, 1908 header, 2215 source, 4650 document; 0 connected Louvain singletons + +## Modules (call-graph clusters; showing 12 of 2640) +- **src::emitTo@infra/emit.h:53:2760 [write,run,resolve]** — 1714 symbols +- **src::ASSUME@infra/Diagnostics.h:386:25510 [resolve,collect,add]** — 934 symbols +- **src::append@elixir_resolve.h:111:4875 [resolve,scan,read]** — 644 symbols +- **src::kindIs@infra/nodekind.h:46:3271 [push,emit,collect]** — 366 symbols +- **src::escapeXml@serialize.h:155:10267 [run,emit,write]** — 201 symbols +- **docs::`--top-k=N`@COMMANDS.md:59:8950 [pack,allow,insert]** — 179 symbols +- **src/infra::fail@os_win32.cpp:63:2553 [close,open,release]** — 108 symbols +- **test/ownerhopfix/py/pkg::big_handler_001@broadcast.py:3:72** — 106 symbols +- **src::relForHash@model.h:1634:133594 [clone,compute,scan]** — 53 symbols +- **src/infra::DYNMAP_ASSUME@dynamic_map.hpp:90:4411 [erase,find,insert]** — 50 symbols +- **test/ownerhopfix/gain/pkg::target_01@fanout.py:74:1458 [push]** — 41 symbols +- **src::rowOffsets@infra/sparseCsr.h:248:8990 [connect,verify,make]** — 39 symbols + +## God files (most depended-on; showing 10 of 489) +- `test/lib/clean-env.sh` — 186 dependents +- `src/model.h` — 89 dependents +- `src/infra/emit.h` — 86 dependents +- `src/infra/Diagnostics.h` — 60 dependents +- `src/serialize.h` — 40 dependents +… [33 more lines, 3676 bytes total] +````` + +## `./build/ripwire . --seams` + +*Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [90 more display lines; full output is 12235 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --mermaid` + +*Module (directory) dependency graph as a Mermaid diagram.* + +````` +%% ripwire --mermaid: module (directory) dependency graph — node = dir (symbol count), edge = inter-module calls (>= 3). Render at mermaid.live. +flowchart LR + subgraph sg0 ["src"] + n101["src
7915"] + n102["src/infra
1280"] + end + subgraph sg1 ["test"] + n103["test
5126"] + n392["test/legochargefix
546"] + n333["test/fixtures/recallpassage
314"] + n448["test/ownerhopfix/py/pkg
158"] + n280["test/expandmodefix
151"] + n282["test/expandsibsfix
149"] + n589["test/rubyschemafix
99"] + n571["test/rubyattrsfix
85"] + n424["test/massfix
77"] + end + n73["docs
836"] + n2[".github
606"] + n0["(root)
547"] + 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"] + n62["bench/shotgun
96"] +… [20 more lines, 1599 bytes total] +````` + +## `./build/ripwire . --owners` + +*Bus-factor: recency-weighted author ownership per file.* + +**wall time: 2.18s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [921 more display lines; full output is 101244 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --dead-code=src` + +*Internal functions with no caller found in the index — a name-based graph reading, not a confidence score. NOTE the filter is a path-COMPONENT match: 'src' matches any .../src/... segment; use ./src to pin the root directory.* + +````` + + + + +````` + +## `./build/ripwire . --exercises=test/regression.sh` + +*Which symbols a TEST FILE exercises — the reverse direction of --affected.* + +````` + + + +````` + +## `./build/ripwire . --community=0` + +*Drill into ONE call-graph community by id — the drill= the --communities output itself advertises.* + +````` + + + +````` + +## `./build/ripwire . --quality-delta` + +*On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* + +**wall time: 20.06s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [120 more display lines; full output is 14810 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: 10.39s** + +````` + + + + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*No-LLM review-evidence bundle for the working-tree diff (clean tree = empty).* + +````` + + + +````` + +## `./build/ripwire . --pr-context=HEAD~1` + +*The BASEREF form: diffed against merge-base(BASEREF, HEAD), never the ref tip — here the previous commit on the current line (a ref with NO merge base falls back to a disclosed two-dot diff: anchor="ref-tip-two-dot").* + +**wall time: 3.01s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [34 more display lines; full output is 6424 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: 13.13s** + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --stray-content=lane/` + +*Which refs of the `lane/` ref family still hold divergent authored work vs HEAD, with verdicts.* + +**wall time: 5.06s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [218 more display lines; full output is 28936 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: 1.48s** + +````` + + + + + + + + + + + + + + + + + + +````` + +## `./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: 89.21s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [38564 more display lines; full output is 5687492 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.96s** + +````` +(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: 25.97s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 5.64s** + +````` + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 7.46s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [35 more display lines; full output is 14367 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.76s** + +````` + + + + + + + +````` + +## `./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.02s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [378 more display lines; full output is 23271 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --flags --flip=RIPWIRE_ASAN` + +*Blast radius of turning ONE gate on: live code, symbols, transitive reach, covering tests.* + +**wall time: 1.05s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.40s** + +````` +{"v":1,"verb":"plan-lanes","at":"39fd059eb","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":2787,"symbols":26903,"edges":417 … [line truncated: 339 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":651,"id":"./src/mcpverbs.h::rw::docDriftText"}, +{"p":"./src/verbs_change.h","n":"runDocDrift","scope":"","l":1675,"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/gitmine.h","n":"gitWindowBoundarySha","scope":"rw","key":"18627516699d7062","id":"./src/gitmine.h::rw::gitWindowBoundarySha","id_addressable":true,"id_collides_with":0,"l":1672,"ord":0,"overloads":1,"amb":3,"cx":3,"ccx":2,"churn":71,"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":3,"cx":6,"ccx":6,"churn":36,"tested":0}, +{"p":"./src/cli.h","n":"inHostActive","scope":"rw","key":"4446994deb74226c","id":"./src/cli.h::rw::inHostActive","id_addressable":true,"id_collides_with":0,"l":4589,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":469,"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":2,"cx":8,"ccx":8,"churn":32,"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":2,"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":3029,"ord":0,"overloads":1,"amb":33,"cx":8,"ccx":11,"churn":439,"tested":0}, +{"p":"./src/gitmine.h","n":"resolveSinceScope","scope":"rw","key":"9caaa3dbeaa688d0","id":"./src/gitmine.h::rw::resolveSinceScope","id_addressable":true,"id_collides_with":0,"l":331,"ord":0,"overloads":1,"amb":3,"cx":7,"ccx":7,"churn":71,"tested":0}, +{"p":"./src/main.cpp","n":"emitNoteAddUnprovenDefs","scope":"","key":"a950332ccfce6b8d","id":null,"id_addressable":false,"id_collides_with":0,"l":689,"ord":0,"overloads":1,"amb":1,"cx":2,"ccx":1,"churn":439,"tested":0}, +{"p":"./src/taskroute.h","n":"testCoverageTaskChoice","scope":"rw::taskroute","key":"bf72991aa28355c7","id":"./src/taskroute.h::rw::taskroute::testCoverageTaskChoice","id_addressable":true,"id_collides_with":0,"l":1388,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":41,"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":5469,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":359,"tested":0}, +{"p":"./src/cli.h","n":"validateModifierGuards","scope":"rw","key":"ec2848a4801493a2","id":"./src/cli.h::rw::validateModifierGuards","id_addressable":true,"id_collides_with":0,"l":4617,"ord":0,"overloads":1,"amb":33,"cx":73,"ccx":66,"churn":469,"tested":0}], +"files":[{"p":"./src/cli.h","symbols":2,"churn":469,"ccx":66,"hotspot_rank":6}, +{"p":"./src/darkflags.h","symbols":1,"churn":32,"ccx":8,"hotspot_rank":45}, +{"p":"./src/docdrift.h","symbols":2,"churn":36,"ccx":7,"hotspot_rank":32}, +{"p":"./src/gitmine.h","symbols":2,"churn":71,"ccx":9,"hotspot_rank":17}, +{"p":"./src/main.cpp","symbols":2,"churn":439,"ccx":12,"hotspot_rank":4}, +{"p":"./src/mcpverbs.h","symbols":1,"churn":359,"ccx":2,"hotspot_rank":5}, +{"p":"./src/taskroute.h","symbols":1,"churn":41,"ccx":2,"hotspot_rank":38}, +{"p":"./src/testmap.h","symbols":1,"churn":48,"ccx":4,"hotspot_rank":36}, +{"p":"./src/verbs_change.h","symbols":1,"churn":78,"ccx":9,"hotspot_rank":22}]},"blast_radius":{"reaches":47,"files_total":17,"capped":false,"files":["./src/cli.h","./src/docdrift.h","./src/editplan.h","./src/gitmine.h","./src/main.cpp","./src/mcp.h","./src/mcpedit.h","./src/mcpserver.h","./src/mcpv … [line truncated: 194 more bytes on this line] +"tests_total":0,"tests_capped":false,"tests_granularity":"claimed-symbols","untested":47,"module_span":1,"notes":[], +"execution":{"policy":"codex-lane/v1","model":"gpt-5.6-sol","reasoning":"xhigh","rule":"wide-contract-work","basis":"structural-only","signals":{"claims":13,"files":9,"module_span":1,"max_ccx":66,"sum_ccx":119,"ambiguous_calls":85,"blast_reaches":47,"contract_touches":1,"conflicts":0,"untested":47," … [line truncated: 140 more bytes on this line] +… [75 more display lines; full output is 20891 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.57s** + +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":"39fd059eb","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2787,"symbols":26903,"edges":41775,"ambiguous":12805,"unresolved":12824},"carve":null,"core":{"files … [line truncated: 5 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":651,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":359,"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":3255,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":469,"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":12,"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":13,"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":1675,"ord":0,"overloads":1,"amb":1,"cx":5,"ccx":4,"churn":78,"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":5469,"ord":0,"overloads":1,"amb":1,"cx":3,"ccx":2,"churn":359,"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":469,"ccx":0,"hotspot_rank":6}, +{"p":"./src/docdrift.h","symbols":6,"churn":36,"ccx":54,"hotspot_rank":32}, +{"p":"./src/ensemble.h","symbols":1,"churn":23,"ccx":0,"hotspot_rank":93}, +{"p":"./src/mcpverbs.h","symbols":2,"churn":359,"ccx":2,"hotspot_rank":5}, +{"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":56}, +{"p":"./src/verbs_change.h","symbols":1,"churn":78,"ccx":4,"hotspot_rank":22}], +"symbols_total":3230,"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":28,"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":121,"l":271,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":10, … [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":2878,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":469,"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":3255,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":469,"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":2957,"ord":0,"overloads":1,"amb":0,"cx":0,"ccx":0,"churn":469,"tested":0}, +{"p":"./docs/docs_commands_build.py","n":"main","scope":"","key":"4015853681ded3bc","id":null,"id_addressable":false,"id_collides_with":121,"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":43,"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":3022,"ord":0,"overloads":1,"amb":3,"cx":4,"ccx":3,"churn":469,"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}, +… [61 more display lines; full output is 18818 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).* + +````` + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + + + + +… [243 more display lines; full output is 25248 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [269 more display lines; full output is 26447 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [222 more display lines; full output is 25274 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:8051 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:8092 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1751 + #3 0x102e01a44 in main src/main.cpp:4169 + #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 5251 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 + +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 +struct AgentTarget + + + + + + + + + +… [59 more display lines; full output is 10197 bytes on 51 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). : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. : 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. --> + + + + + +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] + +What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure +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 +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 +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 powerShellPathPrependHint( std::string_view programDir ) noexcept +inline std::string powerShellSingleQuote( std::string_view s ) noexcept +inline std::string path_prepend_hint( std::string_view dir ) +handle_t leftmost_leaf() const +COLD_FILES = ( # ── ingest sections that are not on the per-file / per-symbol default path ──────────────────── ( "src/ingest_astquery.h", "the --match / - … [line truncated: 48 more bytes on this line] +inline void discloseUniformChurnFallback( bool hasChurnEvidence, bool stubbed, const char* verbLabel, const std::string& windowStamp ) +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +### Same answer, a fraction of the tokens — read this table first if your agent is on a budget +### Anchor-only auto bodies — T3 substitution round, PRE-REGISTERED 2026-08-22 (before any fix code)< … [line truncated: 3 more bytes on this line] +… [61 more display lines; full output is 10488 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.42s** + +````` +(empty) +````` + +Artifact written: + +````` + 425433 /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: 1.43s** + +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 ) +inline McpRebuildBaseline mcpRebuildBaseline( const McpIndex& ix, bool isIncrementalPass ) +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 const McpIndex& getIndex( const std::string& root ) +inline std::string mcpFreshFields( std::uint64_t passesAtEntry ) +struct McpIndex +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& ca … [line truncated: 67 more bytes on this line] +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 constexpr std::array<std::string_view, 8> kCacheRuleNames = +inline std::string oracleCachePath( const std::string& root, const std::string& headSha ) +… [59 more display lines; full output is 42445 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: 3.22s** + +````` + + + + + + + + + + +````` + + +--- + +# 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: 1.36s** + +````` + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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 +1165,8090,8736,171,1423,1462,1749 +fn,fn,fn,fn,fn,fn,fn +0,0,0,0,0,0,0 + + +````` + +## `./build/ripwire . --for="cache invalidation" --format=candidates --top-k=5` + +*Flat top-K export for an external reranker.* + +````` + + + +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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1165"}, +{"t":"fn","n":"rankGraph","p":"src/graph.h:8090"}, +{"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:8736"}, +{"t":"fn","n":"runEval","p":"src/eval.h:171"}, +{"t":"fn","n":"churnDecayRanking","p":"src/main.cpp:1423"}, +{"t":"fn","n":"churnRankedGraph","p":"src/main.cpp:1462"}, +{"t":"fn","n":"runDefaultMap","p":"src/main.cpp:1749"}]} +````` + +## `./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.39s** + +````` + + + + + + + +````` + +## `./build/ripwire . --ignore-tests --top-k=5` + +*Drop test paths from the corpus before ranking.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [67 more display lines; full output is 8173 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --exclude=present --exclude=bench --top-k=5` + +*Drop matching paths (repeatable) before ranking.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [65 more display lines; full output is 8099 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.23s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [56 more display lines; full output is 8005 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.82s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [56 more display lines; full output is 8005 bytes on 1 raw line(s)] +````` + +Artifact written: + +````` + 23737408 /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 4032 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: 1.43s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --eval` + +*Self-eval: co-change recall vs BM25.* + +**wall time: 17.07s** + +````` +ripwire --eval (co-change recovery, averaged over 80 historical commits) + ranker recall@5 recall@10 recall@20 + ripwire 0.2% 3.7% 8.4% + BM25 4.9% 6.9% 9.7% + BM25sub 9.4% 13.9% 25.9% + BM25body 5.5% 14.6% 26.6% + fused 7.7% 15.0% 24.6% + anchored 5.5% 14.6% 26.6% + same-dir 0.2% 0.8% 2.3% + random 0.2% 0.4% 0.7% <- floor (random ranking over F=2787 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: 143.59s** + +````` +ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) + sample: population=6641 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.683 55.0% 84.6% 90.1% + subtoken doc-phrase 0.911 88.9% 93.3% 94.4% + name-exact name 0.893 81.5% 95.5% 97.0% + name-exact doc-phrase 0.013 0.4% 1.7% 2.7% + anchored name 0.689 56.6% 83.8% 88.4% + anchored doc-phrase 0.907 88.3% 93.3% 94.0% + routed name 0.895 81.8% 95.7% 97.2% + routed doc-phrase 0.910 88.9% 93.0% 94.1% + note: routing chose name-exact on 3986/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: 11.61s** + +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.669) + 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.5 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=65d62fe5c) +````` + + +--- + +# navigate — seeds, claims, slices, shapes + +## `./build/ripwire . --at=src/graph.h:8051` + +*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:8051` + +*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 11947 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.47s** + +````` + +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.61s** + +````` + +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( 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 +DISCLOSE( out, CMakeScan::DisclosureWhy::RootWalkFailed, "flags: cannot walk root for CMake files — cmake gates omitte +… [73 more display lines; full output is 17835 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 +enum class AstWalk : std::uint8_t +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar…constexpr std::array<LangEntry, 53> kLangTable = +struct SliceScan +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 bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +inline ModuleScan scanModule( std::string_view source, std::string_view path ) +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 void runParseWorker( ParsePoolShared& sh, unsigned t ) +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline bool hasPytestTable( std::string_view source ) +… [55 more display lines; full output is 9884 bytes on 1 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.40s** + +````` + + + + +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 +enum class AstWalk : std::uint8_t +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar…constexpr std::array<LangEntry, 53> kLangTable = +struct SliceScan +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 bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +inline ModuleScan scanModule( std::string_view source, std::string_view path ) +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 void runParseWorker( ParsePoolShared& sh, unsigned t ) +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline bool hasPytestTable( std::string_view source ) +… [66 more display lines; full output is 10217 bytes on 1 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.77s** + +````` + + + + +inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) +The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u +TSTree* parseTree( TSParser* parser, std::string_view src ) + +Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() +struct FileHealth +enum class AstWalk : std::uint8_t +inline RevSide sliceAtRev( const std::string& root, const std::string& sha, const std::string& rel, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar…constexpr std::array<LangEntry, 53> kLangTable = +struct SliceScan +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 bool hasPhantomScopeSeparator( TSNode qualified ) noexcept +inline ModuleScan scanModule( std::string_view source, std::string_view path ) +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 void runParseWorker( ParsePoolShared& sh, unsigned t ) +inline SliceScan sliceScanDefinition( const std::string& src, const Symbol& sym, SliceFam fam, const ::TSLanguage* grammar, std::string_view varName ) +struct LintRule +enum class DisclosureWhy : std::uint8_t +inline std::string buildMemoryStopAttr( const IngestResult& ing, bool json ) +inline bool hasPytestTable( std::string_view source ) +… [93 more display lines; full output is 13498 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.08s** + +````` + + + + +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfSc … [line truncated: 14 more bytes on this line] + +runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) + +H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +struct QualityDeltaOutcome +inline std::string qualityAcksPath( const std::string& root ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +inline rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +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: 6 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which degraded to a BA … [line truncated: 62 more bytes on this line] +… [52 more display lines; full output is 9778 bytes on 1 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.19s** + +````` + + + + +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfSc … [line truncated: 14 more bytes on this line] + +runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) + +H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… +struct DeltaBasis +inline const char* kAcksFile = ".ripwire_quality_acks" +DirtyPinVerdict inspectDirtyBaselinePin( const MainDispatch& d, const std::string& acksFile ) +struct IdentityHealing +inline std::pair<std::string, std::string> qualityDeltaJson( const std::string& root ) +inline bool writeAckRecords( const std::string& path, const gtl::btree_map<std::string, AckRecord>& acks ) +std::optional<int> resolveDeltaBasis( const MainDispatch& d, const std::string& baselineFile, RefPairDelta& refs, DeltaBasis& out ) +inline std::string staleAcksXml( const std::vector<StaleAck>& staleAcks, EscapeFn esc ) +struct QualityDeltaOutcome +inline std::string qualityAcksPath( const std::string& root ) +inline bool atomicWriteFile( const std::string& path, const std::string& blob ) +inline constexpr const char* kAtStampLegend = "<!-- at= is the git commit these numbers were computed at +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 std::string renderAckRecords( const gtl::btree_map<std::string, AckRecord>& acks ) +inline rw::pathguard::NoFollowRead openRegularFileStream( std::string_view what, const std::string& path ) +int runQualityBaselinePin( const MainDispatch& d, const std::string& baselineFile, const std::string& acksFile ) +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: 6 more bytes on this line] +inline std::string qualityBaselinePath( const std::string& root ) +inline constexpr const char* kQdSchemeLegend = "A THIRD re-filing, git-independent: on 2026-08-25 the per-symbol quality key stopped being a " "canonical-id hash (which degraded to a BA … [line truncated: 62 more bytes on this line] +… [52 more display lines; full output is 9733 bytes on 1 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.88s** + +````` + + + + + + + + + + + + + +````` + +## `./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.20s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [417 more display lines; full output is 44640 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).* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [56 more display lines; full output is 8005 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [56 more display lines; full output is 8005 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable ordering the server turns on) — it changed nothing here; the CLI map orders important-first unless you pass --order=stable +````` + +## `./build/ripwire . --run-trace="cat /aux/asan_trace_now.txt; exit 1"` + +*EXEC-MODE --from-trace: run a command, and on a non-zero exit map its captured output onto indexed symbols in the same call — the whole fix-loop entry. ripwire exits 4 here because the wrapped command failed, which is the signal, not an incident.* + +**exit code: 4** + +````` + + + + + + const&, float) src/graph.h:8051 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:8092 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1751 + #3 0x102e01a44 in main src/main.cpp:4169 + #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: 524 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.43s** + +````` + + + + +````` + +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: 5.81s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [185 more display lines; full output is 15572 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.* + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --comment-coherence --limit=8` + +*Functions WITH a doc comment, most name-restating first: c_coeff (high = the comment repeats the name) and cic (Jaccard of comment vs identifier vocabulary), both reported, never collapsed.* + +````` + + + + + + + + + + + +````` + +## `./build/ripwire . --context-ratio --limit=8` + +*The local-reasoning lens: to understand this symbol, how much must you know that is NOT in front of you (ent_ratio= edge share, read_ratio= token-weighted).* + +**wall time: 1.62s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [21 more display lines; full output is 11182 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --nonlocal-state --limit=8` + +*Per function, the non-local MUTABLE state it can reach (transitively), most writes first — unsound by construction, and the legend says where.* + +**wall time: 3.73s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [41 more display lines; full output is 7594 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --ensemble --limit=8` + +*The family join: per function, which of four orthogonal evidence families fire, ranked by how many agree.* + +**wall time: 3.88s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [35 more display lines; full output is 7393 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --field-affinity` + +*The cache-locality lens over every aggregate: fields READ TOGETHER but declared FAR APART (split-line / straddle findings, Chilimbi separation weight) — advice only, never a rewrite.* + +**wall time: 2.19s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [510 more display lines; full output is 45864 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.79s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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 4349 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: 17.09s** + +````` + + + + + + + + + + + +

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+… [135 more display lines; full output is 14384 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.20s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [643 more display lines; full output is 72478 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 7764 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=74f30d013d02c2a2 entries=829 +: 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, +… [800 more display lines; full output is 84154 bytes on 830 raw line(s)] +````` + +## `./build/ripwire . --lint --lint-select=cache-` + +*Run ONLY one rule family; the root carries selected="K of N" so a filtered zero is never confusable with an unfiltered one.* + +**wall time: 3.08s** + +````` + + + + + + + + + + +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] ] +… [553 more display lines; full output is 66790 bytes on 1 raw line(s)] +````` + +## `./build/ripwire . --lint --lint-ignore=naming-,cache-` + +*DROP two families, applied after selection; the raw select=/ignore= you passed rides on the root.* + +**wall time: 3.38s** + +````` + + + + + + + + + + + + + + + + + + + + + + + +0.5 +1023u +main (90 lines) +20 +delta < 0.02 && delta > -0.02 +0.02 +… [727 more display lines; full output is 66126 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.12s** + +````` + + + +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: 3.21s** + +````` + + + +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 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: 3.09s** + +````` +(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.39s** + +````` +(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.46s** + +````` +{"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}}, +… [11013 more display lines; full output is 1551625 bytes on 1 raw line(s)] +````` + +Parsed summary of the same SARIF (past the display cut): + +````` +sarif 2.1.0 rules= 39 results= 5502 +````` + +## `./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: 6.29s** + +````` + + +

+

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

+

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

+

+ +````` + +## `./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: 3.80s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [787 more display lines; full output is 51999 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: 3.74s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 3.09s** + +````` +(empty) +````` + +Artifact written: + +````` + 159470 /aux/map2.html +````` + +## `./build/ripwire . --index-out=/aux/ci_index` + +*CI generate-and-exit: cold-parse and write BOTH committable cache families (lean + rich), no map on stdout.* + +**wall time: 7.24s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (23737408 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (49993364 bytes, rich family) +````` + +Artifact written: + +````` + 23737408 /aux/ci_index.lean.ripwirecache + 49993364 /aux/ci_index.rich.ripwirecache + 73730772 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.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [56 more display lines; full output is 8005 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.33s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [56 more display lines; full output is 8005 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. + 126149 /aux/pin_census.tsv +````` + +## `./build/ripwire . --plan-lint=test/planlintfix/wave.md` + +*The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when a card or ledger row gates.* + +**exit code: 2** — **wall time: 1.04s** + +````` + + + + + + + + + + + + + +````` + +## `./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** + +````` + + + + + + + +````` + +## `./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: 6.56s** + +````` + + + + + + + + + + + + + +````` + +## `./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: 5.11s** + +````` +ripwire situational-awareness — 1 changed file(s), 13 symbols in them +root: . +at: 39fd059eb+dirty + [1] blast radius: 303 symbols across 72 files transitively depend on these changes (showing 8 of 72 files — shown=8 total=72 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=72) + counts_floor=1 graph_ambiguous=12808 graph_unresolved=12824 graph_unindexed=256 (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 missin … [line truncated: 46 more bytes on this line] + src/mcpverbs.h (34 dependent symbols) + src/slice.h (30 dependent symbols) + src/crossref.h (13 dependent symbols) + src/verbs_for.h (12 dependent symbols) + src/quality.h (11 dependent symbols) + src/verbs_navigate.h (10 dependent symbols) + src/resolve.h (9 dependent symbols) + src/serialize.h (9 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=746 — 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="4362": + (none — 4362 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: 3.74s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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: 30.69s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [129 more display lines; full output is 16690 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: 12.18s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [129 more display lines; full output is 21568 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: 11.58s** + +````` +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":147,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"39fd059eb+dirty","renames":60,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 211 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":"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"}, +{"kind":"api-surface:new-symbol","key":"995375dfa4e63104","why":"target-gone"}, +… [126 more display lines; full output is 15341 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: 8.73s** + +````` +(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, 217 re-derived, 16 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1315 ineligible (no cu … [line truncated: 200 more bytes on this line] +ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written +````` + +## `./build/ripwire . --quality-delta --quality-ack --ack-only=api-surface` + +*NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* + +**wall time: 8.96s** + +````` +(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, 217 re-derived, 16 left UNVERIFIED (their group was not found here — a floor, not proof it is gone), 57 left legacy (member set not found cloning here — a floor, not proof it is gone), 1315 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: 15.75s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [128 more display lines; full output is 16393 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.14s** + +````` + + + + + + +````` + +## `./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.* + +````` + + + + + + +````` + +## `./build/ripwire . --pr-context` + +*The review-evidence bundle with an actual changed file.* + +**wall time: 2.97s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [89 more display lines; full output is 8590 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: 1.25s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./build/ripwire . --clones` + +*The duplicated helper the sandbox edit introduced shows up as a clone group.* + +**wall time: 2.28s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [306 more display lines; full output is 16116 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.35s** + +````` + + + + + +````` + +stderr: + +````` +[math degraded] crossref: no merge-base for ref (shallow clone or unrelated history?) — verdict is unknown, not merged (crossref.h:1257, 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: 1.64s** + +````` + + + + +````` + +stderr: + +````` +[math degraded] crossref: no merge-base for ref (shallow clone or unrelated history?) — verdict is unknown, not merged (crossref.h:1257, 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.20s** + +````` + + +

+

+ +````` + +## `./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: 13.43s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [131 more display lines; full output is 16660 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: 6.37s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=src/graph.h — refusing, and writing nothing at all: + api-surface src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey at src/infra/sortutil.h:109 + those rows belong to whoever is editing those paths. Acking them here writes their debt into a committed ledger under YOUR + reason string, which is how a per-finding ratchet becomes a rubber stamp. Narrow the pattern, or widen the scope if they really are yours. +````` + +## `./build/ripwire . --handoff` + +*The continuation packet with a REAL diff: verified changed symbols + blast radius + tests-to-run, then the heuristic rows.* + +**wall time: 2.04s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.* + +````` +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-10 keep this branch-free — it sits inside the PageRank sort comparator 39fd059eb530cf1379ae535c880e71d268fa95cd integration/train-26c +````` + +stderr: + +````` +ripwire: --note-add: tip: notes that say "chose X over Y because Z" surface better — consider adding the why +ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sortutil.h::rw::sortutil::lessByScoreDescId' — that is the id --for/--expand key notes by +````` + +## `./build/ripwire . --notes` + +*The note is listed under the canonical id, dangling="0" — i.e. it will actually surface. (Before the H1 fix the bare name was stored verbatim and read dangling="1": recorded, and surfaced nowhere.)* + +**wall time: 1.02s** + +````` + + + + + + + + + + + + + + + + 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-10 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 39fd059eb530cf1379ae535c880e71d268fa95cd integration/train-26c +````` + +## `./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: 2.53s** + +````` +{"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":12805,"graph_unresolved":12824,"graph_unindexed":256,"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":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"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.07s** + +````` +{"applied":"insert_after_symbol","symbol":"lessByScoreDescId","file":"src/infra/sortutil.h","span":{"start":2375,"end":2463},"lines":{"start":56,"end":58},"replaced_bytes":0,"old_file_bytes":10912,"new_file_bytes":11000,"file_eol":"lf","eol_normalized":false,"trailing_newline_folded":true,"separator … [line truncated: 572 more bytes on this line] +````` + +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: 2.97s** + +````` +{"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":12805,"graph_unresolved":12824,"graph_unindexed":256,"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":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"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.04s** + +````` +(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.09s** + +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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false, … [line truncated: 354 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: 2.35s** + +````` +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 538 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":12805,"graph_unresolved":12824,"graph_unindexed":256,"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: 9.74s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [125 more display lines; full output is 15757 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: 10.59s** + +````` +(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: 11.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 26062 per-symbol rows, 26905 indexed symbols total) +````` + +Artifact written: + +````` + 4419393 .ripwire_quality_baseline +# ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit +producer 01b0d59d63f2a402c75ee73a7c3b9e942e6094ebf92838a6833a4d824cfec943 +head 39fd059eb530cf1379ae535c880e71d268fa95cd +dirty 1 +absorbed 2 +ccx d19d7c13cbc7 2 +ccx 1f7edbff929e5 10 +ccx 6a732f6699bdb 0 +ccx 7d8862be4e +````` + +## `./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: 14.17s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [120 more display lines; full output is 14641 bytes on 1 raw line(s)] +````` + + +--- + +# 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 ceiling (the CLI --for --token-budget). The header carries route= (which ranker answere … [line truncated: 1053 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: 657 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: 2117 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: 1226 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: 1385 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= 47143 (~tokens at 4 bytes/token: 11785 ) +explore desc_bytes= 1322 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'] +flags desc_bytes= 881 schema_bytes= 720 required=[] +whereis desc_bytes= 701 schema_bytes= 851 required=['symbol'] +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= 829 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= 445 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=[] +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=[] +lego desc_bytes= 360 schema_bytes= 467 required=['type'] +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: 2117 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: 1226 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= 14166 (~tokens at 4 bytes/token: 3541 ) +explore desc_bytes= 1322 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= 829 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.03s** + +````` +{"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: 6526 more bytes on this line] +"_index":"[index: files=2787 symbols=26903 hash=f83e9172]","_fresh":"ok"}} +{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":" - + @@ -98,6 +98,29 @@ $ ./build/ripwire . --top-k=5 The default map appends after its ranked rows whenever they left out a file with code: rows (program entries by each language's own entry convention) and rows naming or counting every unshown file by directory. This flag drops it (the rows are unchanged either way). The tier never rides a map fitted to a token budget, nor a payload verb's ride-along map. +**Try it** + +_The same capped map without its inventory tier: --top-k=5 leaves files out, so the default map appends (program entries, and the files the ranked rows left out) after its rows; --no-inventory drops that tier, and the ranked rows are the same._ + +``` +$ ./build/ripwire . --top-k=5 --no-inventory + + + + + + + + + + + + + + +... [11 more line(s); run it to see the whole thing] +``` + **Caveats (stated by the binary):** - The default map appends after its ranked rows whenever they left out a file with code: rows (program entries by each language's own entry convention) and rows naming or counting every unshown file by directory. @@ -117,7 +140,7 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ $ ./build/ripwire . --max-tokens=1500 - + @@ -192,7 +215,7 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - + @@ -229,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 - + @@ -256,7 +279,7 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - + @@ -288,7 +311,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 - + @@ -321,7 +344,7 @@ _Same query with routing forced OFF (plain subtoken+body BM25) — contrast with ``` $ ./build/ripwire . --for="rankGraphTeleport" --no-route - + @@ -353,7 +376,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 - + @@ -384,7 +407,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 - @@ -415,7 +438,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 - + @@ -445,7 +468,7 @@ _Interface -> implementors view: every existing impl of the named interface; the $ ./build/ripwire . --lego=Vehicle - + @@ -506,7 +529,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" — 113 relevant of 218 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5028 -━━ README.md (relevance 8.079) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 217180 B; lines="2746-2758,2759-2766"; dropped_by_budget=23] +━━ README.md (relevance 8.080) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 217180 B; lines="2746-2758,2759-2766"; dropped_by_budget=23] #### 6.2 Exit codes This is the part to wire into a script. @@ -569,25 +592,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 1386497-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 1392154-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.075) ━━ [sections: 1 of 160 selected (189 in doc), section-granular; whole doc 562840 B; lines="531-556"; dropped_by_budget=159] [truncated: 1854 of 4257 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base +━━ commands.md (relevance 10.116) ━━ [sections: 1 of 160 selected (189 in doc), section-granular; whole doc 569309 B; lines="3756-3760"; dropped_by_budget=159] [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` @@ -707,7 +725,7 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable $ ./build/ripwire . --no-stable --top-k=3 - + @@ -741,7 +759,7 @@ $ ./build/ripwire . --around=rankGraphTeleport - + @@ -792,7 +810,7 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - + @@ -823,7 +841,7 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - + @@ -857,7 +875,7 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - + @@ -895,7 +913,7 @@ _Names referenced but never defined in-corpus (stdlib/third-party surface). The $ ./build/ripwire . --external-surface - + @@ -929,7 +947,7 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - + @@ -951,7 +969,7 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - + @@ -1061,7 +1079,7 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - @@ -1123,7 +1141,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: 39fd059eb +at: 6009ca693 (0 changed files — working tree is clean, nothing to analyze) ``` @@ -1142,18 +1160,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] ``` @@ -1472,14 +1490,14 @@ $ ./build/ripwire . --query="teleport pagerank" --top-k=5 - + - + - + @@ -1504,7 +1522,7 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - + @@ -1539,7 +1557,7 @@ $ ./build/ripwire . --pack-signatures --top-k=10 - + @@ -1737,7 +1755,7 @@ _Fan-in/out + complexity annotations on the map._ $ ./build/ripwire . --metrics --top-k=10 - + @@ -1810,7 +1828,7 @@ _Complexity x recent git churn (maintenance pain)._ $ ./build/ripwire . --hotspots - + @@ -1902,7 +1920,7 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - @@ -2036,7 +2054,7 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - + @@ -2176,16 +2194,16 @@ _Files that change together in git (hidden coupling)._ ``` $ ./build/ripwire . --cochange - + - + + + + + - - - - ... [22 more line(s); run it to see the whole thing] ``` @@ -2208,16 +2226,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] ``` @@ -2234,7 +2252,7 @@ _Modularity-violation GROUPS instead of pairs: "X co-changes with {A,B,C}, none ``` $ ./build/ripwire . --cochange --cochange-groups - + @@ -2263,7 +2281,7 @@ _Hotspots scoped to RECENT churn (the regression lens)._ ``` $ ./build/ripwire . --hotspots --since="2 weeks ago" - + @@ -2663,7 +2681,7 @@ _Nested module hierarchy (multi-level Louvain) + cross-module bridges — levels ``` $ ./build/ripwire . --zoom - @@ -2726,7 +2744,7 @@ _Cross-module call seams no test reaches. NOW carries seam_pairs/shown/capped._ ``` $ ./build/ripwire . --seams - @@ -2786,7 +2804,7 @@ _Bus-factor: recency-weighted author ownership per file._ ``` $ ./build/ripwire . --owners - + @@ -2817,7 +2835,7 @@ _Internal functions with no caller found in the index — a name-based graph rea ``` $ ./build/ripwire . --dead-code=src - + ``` @@ -2882,7 +2900,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - @@ -2919,7 +2937,7 @@ _On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox ``` $ ./build/ripwire . --quality-delta - @@ -2956,7 +2974,7 @@ _The Delta Maintainability Model scalar for the WORKING TREE vs HEAD — recorde ``` $ ./build/ripwire . --dmm -

-

+

@@ -5621,7 +5596,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: 3.20s** +**wall time: 1.99s** ````` @@ -5737,7 +5712,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: 3.08s** +**wall time: 1.54s** ````` @@ -5777,7 +5752,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: 3.38s** +**wall time: 1.51s** ````` @@ -5817,7 +5792,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: 2.12s** +**wall time: 1.55s** ````` @@ -5834,7 +5809,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: 3.21s** +**wall time: 1.51s** ````` @@ -5862,7 +5837,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: 3.09s** +**exit code: 1** — **wall time: 1.55s** ````` (empty) @@ -5878,7 +5853,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.39s** +**exit code: 1** — **wall time: 1.57s** ````` (empty) @@ -5894,7 +5869,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.46s** +**wall time: 1.56s** ````` {"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] @@ -5956,11 +5931,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: 6.29s** +**wall time: 2.73s** ````` -

@@ -5971,11 +5946,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: 13.54s** +**wall time: 6.32s** ````` -

@@ -5986,14 +5961,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: 10.73s** +**wall time: 6.25s** ````` - - -

-

+ + +

+

````` @@ -6001,11 +5976,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: 3.80s** +**wall time: 1.47s** ````` - + @@ -6041,40 +6016,40 @@ 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: 3.74s** +**wall time: 1.37s** ````` - + - - + + - - + + - - + + + - - - + + ````` @@ -6083,7 +6058,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: 3.09s** +**wall time: 1.53s** ````` (empty) @@ -6099,7 +6074,7 @@ Artifact written: *CI generate-and-exit: cold-parse and write BOTH committable cache families (lean + rich), no map on stdout.* -**wall time: 7.24s** +**wall time: 3.26s** ````` (empty) @@ -6108,16 +6083,16 @@ Artifact written: stderr: ````` -ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (23737408 bytes, lean family) -ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (49993364 bytes, rich family) +ripwire: --index-out wrote /aux/ci_index.lean.ripwirecache (23740310 bytes, lean family) +ripwire: --index-out wrote /aux/ci_index.rich.ripwirecache (49997444 bytes, rich family) ````` Artifact written: ````` - 23737408 /aux/ci_index.lean.ripwirecache - 49993364 /aux/ci_index.rich.ripwirecache - 73730772 total + 23740310 /aux/ci_index.lean.ripwirecache + 49997444 /aux/ci_index.rich.ripwirecache + 73737754 total ````` ## `./build/ripwire . --cache=/aux/ci_index.lean.ripwirecache --top-k=3` @@ -6127,7 +6102,7 @@ Artifact written: ````` - + @@ -6162,12 +6137,10 @@ Artifact written: *Eval-only: a per-call-site census of WHICH mechanism resolved each call, and the canonical id of every surviving target.* -**wall time: 1.33s** - ````` - + @@ -6209,18 +6182,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. - 126149 /aux/pin_census.tsv + 126155 /aux/pin_census.tsv ````` ## `./build/ripwire . --plan-lint=test/planlintfix/wave.md` *The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when a card or ledger row gates.* -**exit code: 2** — **wall time: 1.04s** +**exit code: 2** ````` - + @@ -6242,7 +6215,7 @@ Artifact written: ````` - + @@ -6254,17 +6227,17 @@ Artifact written: *--doctor plus a LIVE integration inspection for one agent: PATH binary, installed-skill manifest parity, hook executability, MCP wiring — read-only, fixed repair commands, never config contents; exit 1 when any check fails.* -**exit code: 1** — **wall time: 6.56s** +**exit code: 1** — **wall time: 3.44s** ````` - + - - + + - + @@ -6301,14 +6274,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: 5.11s** +**wall time: 4.89s** ````` ripwire situational-awareness — 1 changed file(s), 13 symbols in them root: . -at: 39fd059eb+dirty +at: 6009ca693+dirty [1] blast radius: 303 symbols across 72 files transitively depend on these changes (showing 8 of 72 files — shown=8 total=72 capped=1 prcontext_cap=20 (--pr-context's own per-file list is cut at 20 too); next: --situ --limit=72) - counts_floor=1 graph_ambiguous=12808 graph_unresolved=12824 graph_unindexed=256 (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 missin … [line truncated: 46 more bytes on this line] + counts_floor=1 graph_ambiguous=12808 graph_unresolved=12845 graph_unindexed=256 (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 missin … [line truncated: 46 more bytes on this line] src/mcpverbs.h (34 dependent symbols) src/slice.h (30 dependent symbols) src/crossref.h (13 dependent symbols) @@ -6324,8 +6297,8 @@ at: 39fd059eb+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=746 — 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="4362": - (none — 4362 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="4365": + (none — 4365 commits were mined and none co-edited a file outside your diff) next: --test-gate ````` @@ -6333,7 +6306,7 @@ at: 39fd059eb+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: 3.74s** +**exit code: 4** — **wall time: 3.85s** ````` @@ -6375,11 +6348,11 @@ at: 39fd059eb+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: 30.69s** +**exit code: 2** — **wall time: 20.42s** ````` - @@ -6414,6 +6387,7 @@ at: 39fd059eb+dirty stderr: ````` +ripwire: cache : over its 2048 MiB budget — evicted 266 blob(s) of other roots or other ripwire builds (this build's blobs for this root are kept) ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) ripwire: --quality-delta gating: 5 preexisting-worse major finding(s); first: api-surface src/infra/sortutil.h::rw::sortutil::nonNegativeFloatDescKey at src/infra/sortutil.h:109 (was=1 now=2) ````` @@ -6422,11 +6396,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: 12.18s** +**exit code: 2** — **wall time: 10.79s** ````` - @@ -6469,10 +6443,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: 11.58s** +**exit code: 2** — **wall time: 10.84s** ````` -{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":147,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"39fd059eb+dirty","renames":60,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 211 more bytes on this line] +{"baseline":"git-HEAD","regressions":9,"minor":1,"acked":0,"stale":147,"preexisting-worse":6,"new-symbol":3,"gating":5,"register-macro-excluded":62,"api-new-surface":2,"at":"6009ca693+dirty","renames":60,"rename_window_commits":400,"acked_by_rename":0,"acked_by_content":0,"renames_window_truncated": … [line truncated: 211 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"}, @@ -6516,7 +6490,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: 8.73s** +**exit code: 1** — **wall time: 7.15s** ````` (empty) @@ -6534,7 +6508,7 @@ ripwire: --ack-only=zzznope matched none of the 9 finding(s) — nothing written *NEW FLAG: ack only the api-surface findings — a per-finding ratchet instead of a rubber stamp.* -**wall time: 8.96s** +**wall time: 7.07s** ````` (empty) @@ -6552,11 +6526,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: 15.75s** +**exit code: 2** — **wall time: 10.84s** ````` - @@ -6615,11 +6589,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6631,9 +6605,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire row identical.* +**wall time: 1.46s** + ````` - @@ -6645,11 +6621,11 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire - @@ -6685,13 +6661,13 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire 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). changed=K: K indexed git-changed files seed the PageRank teleport (0: uniform, incl. no git). 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. : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. : N same-named via=name rows merged; callees=FILE:SYM lists all. 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. layer=: built-in arch layer (game|infra|render|math|audio|ai|test) from a dir name in p=; absent if none. --> - - + + @@ -6729,7 +6705,7 @@ ripwire: --ack-only=SUBSTR narrows --quality-ack — pass both (e.g. ripwire of ; dup_loc=/dup_pct=. window: shown= total= capped= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). root=: p= relative to it. groups=/type3=: Type-2 and Type-3 group totals over all groups; total= is their sum. exempt_groups=N: groups whose members all sit on fixture/shell-runner paths quality-delta duplication ignores. idiom_groups=/demoted_groups=: groups of one recognized idiom / those quality-delta demotes to minor; floors. clone_groups=N: clusters after merging pairs (rows sharing gid=); a floor under type3_capped=1. total_loc=N: lines of every function body the detector considered; dup_pct= is dup_loc= over it. group exempt=fixture|shell-runner: every member is on such a path; quality-delta duplication ignores it. group idiom=: the recognized shape every member spells: threshold-ladder, switch-name-table, builder-chain. --> @@ -6769,12 +6745,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. --> - - + + ````` @@ -6789,11 +6765,11 @@ stderr: *CHANGED: --plan surfaces those same refs as an row rather than silently dropping them.* -**wall time: 1.64s** +**wall time: 2.59s** ````` - + ````` @@ -6808,11 +6784,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.20s** +**wall time: 3.53s** ````` -

@@ -6823,11 +6799,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: 13.43s** +**wall time: 10.81s** ````` - @@ -6869,7 +6845,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.37s** +**exit code: 1** — **wall time: 6.86s** ````` (empty) @@ -6889,11 +6865,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: 2.04s** +**wall time: 3.36s** ````` - + @@ -6918,11 +6894,11 @@ ripwire: --ack-only=api-surface selects 1 finding(s) OUT OF SCOPE for --scope=sr - - - - - + + + + + ````` @@ -6932,7 +6908,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-10 keep this branch-free — it sits inside the PageRank sort comparator 39fd059eb530cf1379ae535c880e71d268fa95cd integration/train-26c +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-10 keep this branch-free — it sits inside the PageRank sort comparator 6009ca69336d0adceb8f7ce6019372f7fd413e5c integration/train-26c ````` stderr: @@ -6946,8 +6922,6 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort *The note is listed under the canonical id, dangling="0" — i.e. it will actually surface. (Before the H1 fix the bare name was stored verbatim and read dangling="1": recorded, and surfaced nowhere.)* -**wall time: 1.02s** - ````` @@ -6959,7 +6933,7 @@ ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sort - + @@ -7011,7 +6985,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-10 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 39fd059eb530cf1379ae535c880e71d268fa95cd integration/train-26c +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-10 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort 6009ca69336d0adceb8f7ce6019372f7fd413e5c integration/train-26c ````` ## `./build/ripwire . --expand=lessByScoreDescId --top-k=0` @@ -7055,10 +7029,10 @@ src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-10 chose the flat The element on the same output — past the 30-line display cut above: ````` - + - + ````` @@ -7083,7 +7057,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: 6.15s** +**wall time: 5.05s** Input file: @@ -7100,12 +7074,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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"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":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":746,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -7118,15 +7092,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.18s** +**wall time: 1.68s** ````` - + - + @@ -7140,16 +7114,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: 2.53s** +**wall time: 3.45s** ````` {"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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"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":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":746,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ````` stderr: @@ -7162,7 +7136,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.07s** +**wall time: 1.60s** ````` {"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] @@ -7178,19 +7152,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: 2.97s** +**wall time: 3.75s** ````` {"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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"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":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":746,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ````` stderr: @@ -7203,7 +7177,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.04s** +**exit code: 1** — **wall time: 1.59s** ````` (empty) @@ -7219,7 +7193,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.09s** +**wall time: 1.60s** Input file: @@ -7237,21 +7211,21 @@ Input file: ````` ````` -{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false, … [line truncated: 354 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"dry-run","edits":1,"files":1,"callers_union":4,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false, … [line truncated: 354 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: 2.35s** +**wall time: 3.36s** ````` -{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 538 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 538 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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true}}]} +"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true}}]} ````` ## `./build/ripwire . --edit-plan=/aux/edit_plan.json` @@ -7274,11 +7248,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: 9.74s** +**exit code: 2** — **wall time: 11.14s** ````` - @@ -7321,7 +7295,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: 10.59s** +**exit code: 1** — **wall time: 7.17s** ````` (empty) @@ -7340,7 +7314,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: 11.60s** +**wall time: 10.05s** ````` (empty) @@ -7356,10 +7330,10 @@ ripwire: wrote ./.ripwire_quality_baseline (snapshot of 26062 per-symbol rows, 2 Artifact written: ````` - 4419393 .ripwire_quality_baseline + 4419391 .ripwire_quality_baseline # ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit producer 01b0d59d63f2a402c75ee73a7c3b9e942e6094ebf92838a6833a4d824cfec943 -head 39fd059eb530cf1379ae535c880e71d268fa95cd +head 6009ca69336d0adceb8f7ce6019372f7fd413e5c dirty 1 absorbed 2 ccx d19d7c13cbc7 2 @@ -7372,11 +7346,11 @@ ccx 7d8862be4e *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: 14.17s** +**wall time: 10.00s** ````` - @@ -7527,38 +7501,34 @@ 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.03s** - ````` {"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: 6526 more bytes on this line] -"_index":"[index: files=2787 symbols=26903 hash=f83e9172]","_fresh":"ok"}} +"_index":"[index: files=2787 symbols=26903 hash=6fb46779]","_fresh":"ok"}} {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"

@@ -3026,7 +3044,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -3056,7 +3074,7 @@ _Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - @@ -3110,12 +3128,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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"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":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":746,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3131,12 +3149,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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"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":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":746,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3182,12 +3200,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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"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":746,"graph_ambiguous":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"run_first":1,"tests":5,"script_gates_unmodelled":746,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3246,11 +3264,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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 538 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"applied":1,"atomic_files":1,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"mul … [line truncated: 538 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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true}}]} +"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3296,7 +3314,7 @@ _Bare --slice=SYM: the INVENTORY of sliceable locals (), so a calle ``` $ ./build/ripwire . --slice=rankGraphTeleport - + @@ -3333,7 +3351,7 @@ _Forward flow: which statements the seed's value reaches, at the default depth b ``` $ ./build/ripwire . --slice=rankGraphTeleport:teleport --slice-flow=fwd - + @@ -3414,7 +3432,7 @@ _No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ ``` $ ./build/ripwire . --pr-context - ``` @@ -3440,11 +3458,11 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + @@ -3519,7 +3537,7 @@ _Which refs of the `lane/` ref family still hold divergent authored work vs HEAD ``` $ ./build/ripwire . --stray-content=lane/ - + @@ -3553,7 +3571,7 @@ _Select the genuinely-unmerged refs of the `lane/` ref family and feed them to m ``` $ ./build/ripwire . --stray-content=lane/ --plan - + @@ -3589,7 +3607,7 @@ _Cross-branch ABI-break gate over the `lane/` ref family: struct byte-contract d ``` $ ./build/ripwire . --stray-content=lane/ --abi - @@ -3624,8 +3642,8 @@ _Which ref's tree defines or mentions SYM — HEAD first, then every local branc ``` $ ./build/ripwire . --whereis=rankGraphTeleport - - + + @@ -3654,8 +3672,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 - - + + @@ -3801,17 +3819,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - + + - - - - - - - - + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3836,17 +3854,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - + + - - - - - - - - + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3870,18 +3888,18 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - - + + + - - - - - - - - + + + + + + + + ... [18 more line(s); run it to see the whole thing] ``` @@ -3906,7 +3924,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -3971,7 +3989,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -4131,7 +4149,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - + @@ -4205,13 +4223,13 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - + - - + + - + @@ -4239,13 +4257,13 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - + - - + + - + @@ -4362,8 +4380,8 @@ _Rank by git change-frequency prior instead of PageRank._ $ ./build/ripwire . --rank-by=churn --top-k=5 - - + + @@ -4395,14 +4413,14 @@ 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~7 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - + + - @@ -4472,7 +4490,7 @@ _The same gating report under --legend=full — same rows, same exit 2; the defa ``` $ ./build/ripwire . --quality-delta --legend=full - @@ -4588,7 +4606,7 @@ _Drop matching paths (repeatable) before ranking._ $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - + @@ -4619,8 +4637,8 @@ _Full map re-ranked with teleport toward git-changed files — clean tree, so ch $ ./build/ripwire . --map-diff --top-k=5 - - + + @@ -4653,7 +4671,7 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - + @@ -4703,7 +4721,7 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co $ ./build/ripwire . --no-cache --top-k=3 - + @@ -4734,7 +4752,7 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses $ ./build/ripwire . --no-ignore --top-k=3 - + @@ -4850,7 +4868,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 - + @@ -4905,9 +4923,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":" - + @@ -64,7 +64,7 @@ ````` - + @@ -95,6 +95,38 @@ … [67 more display lines; full output is 8262 bytes on 1 raw line(s)] ````` +## `./build/ripwire . --top-k=5 --no-inventory` + +*The same capped map without its inventory tier: --top-k=5 leaves files out, so the default map appends (program entries, and the files the ranked rows left out) after its rows; --no-inventory drops that tier, and the ranked rows are the same.* + +````` + + + + + + + + + + + + + + + + + + + + + + + + + +````` + ## `./build/ripwire . --top-k=0 --expand=rankGraphTeleport` *NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map rides along with the body you asked for.* @@ -159,7 +191,7 @@ ripwire: --top-k=0 means "no ranked map, payload only" — pass a payload verb ( ````` - + @@ -189,10 +221,10 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > budget=100 *The task lens: ranked signatures + quality metrics framed for the task.* -**wall time: 2.60s** +**wall time: 1.31s** ````` - @@ -229,10 +261,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > budget=100 *Name-shaped query: the router picks name-exact BM25 (header says which/why).* -**wall time: 1.05s** - ````` - + @@ -269,10 +299,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > budget=100 *Same query with routing forced OFF (plain subtoken+body BM25) — contrast with the routed run.* -**wall time: 1.03s** - ````` - + @@ -298,7 +326,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > budget=100 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] +### 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 @@ -309,10 +337,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > 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.03s** - ````` - + @@ -330,10 +356,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > 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.01s** - ````` - + @@ -370,10 +394,8 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > 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.06s** - ````` - @@ -411,7 +433,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > budget=100 *Same task with the anchor disabled — the contrast the flag exists for.* ````` - @@ -451,7 +473,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > budget=100 ````` - + @@ -517,7 +539,7 @@ ripwire: --token-budget exceeded: withheld_est_tokens=10806 > budget=100 ````` ripwire recall — "quality delta gating exit codes" — 113 relevant of 218 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5028 -━━ README.md (relevance 8.079) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 217180 B; lines="2746-2758,2759-2766"; dropped_by_budget=23] +━━ README.md (relevance 8.080) ━━ [sections: 2 of 25 selected (60 in doc), section-granular; whole doc 217180 B; lines="2746-2758,2759-2766"; dropped_by_budget=23] #### 6.2 Exit codes This is the part to wire into a script. @@ -541,7 +563,7 @@ refuses by default. -━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.427) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] +━━ prompts/help-wanted/quality-delta-unchanged-tree-zero.md (relevance 7.429) ━━ [sections: 1 of 18 selected (21 in doc), section-granular; whole doc 25463 B; lines="133-158"; dropped_by_budget=17] ### Root spelling (maintainer-owned; reproduce it once so you recognise it) ```bash @@ -550,38 +572,17 @@ refuses by default. ## `./build/ripwire /aux/kbcorpus --recall="field affinity cache line data layout which fields are read together" --top-k=3 --max-tokens=1200` -*The directory-as-knowledge-base pattern: --recall pointed at a 1386497-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 1392154-byte scratch dir of DUMPED TOOL OUTPUT (a git log, this repo's own generated command reference, --help text, an architecture doc, a fabricated JSON access log) instead of a source repo — no index to build, no daemon.* ````` -ripwire recall — "field affinity cache line data layout which fields are read together" — 2 relevant of 2 document files, best-first — total=2 shown=1 capped=1 truncated=1 max_tokens=1200 share_bytes=2040 est_tokens=952 - -━━ commands.md (relevance 11.075) ━━ [sections: 1 of 160 selected (189 in doc), section-granular; whole doc 562840 B; lines="531-556"; dropped_by_budget=159] [truncated: 1854 of 4257 bytes] -#### Pattern: a directory of dumped tool output as a knowledge base - -**Answers:** can `--recall` serve as a zero-setup knowledge base over dumped tool output — a -`git log`, an API response dump, a fetched doc, ` --help` text — sitting in a scratch -directory, instead of a source repo? +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 -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: +━━ commands.md (relevance 10.116) ━━ [sections: 1 of 160 selected (189 in doc), section-granular; whole doc 569309 B; lines="3756-3760"; dropped_by_budget=159] [truncated: 1865 of 7738 bytes] +### `--field-affinity[=STRUCT]` -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. +**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. -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)) ````` @@ -634,7 +635,7 @@ ripwire: redacted 1 secret from emitted context (keyword-gated-secret=1) — pas *Self-contained HTML force-directed call graph.* -**wall time: 2.35s** +**wall time: 1.51s** ````` (empty) @@ -697,7 +698,7 @@ Artifact written: - + @@ -735,7 +736,7 @@ Artifact written: - + @@ -773,7 +774,7 @@ Artifact written: - + @@ -809,7 +810,7 @@ Artifact written: ````` - + @@ -827,7 +828,7 @@ Artifact written: ````` - + @@ -861,7 +862,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + @@ -879,7 +880,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + @@ -898,7 +899,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + @@ -937,7 +938,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + @@ -975,8 +976,8 @@ ripwire: --callers symbol not found: DoesNotExist ````` - - + + @@ -1012,7 +1013,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + @@ -1027,7 +1028,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` - + @@ -1099,7 +1100,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` - @@ -1126,7 +1127,7 @@ ripwire: --callers symbol not found: DoesNotExist ````` ripwire situational-awareness — 0 changed file(s), 0 symbols in them root: . -at: 39fd059eb +at: 6009ca693 (0 changed files — working tree is clean, nothing to analyze) ````` @@ -1134,7 +1135,7 @@ at: 39fd059eb *Pre-PR gate on a CLEAN tree: no obligations, exit 0.* -**wall time: 2.15s** +**wall time: 2.25s** ````` @@ -1281,7 +1282,7 @@ at: 39fd059eb *Tree-sitter structural query WITHOUT a capture — a bare node query gets a capture AUTO-ADDED (auto_captured="1") and matches the same nodes the explicit form does.* -**wall time: 1.11s** +**wall time: 1.13s** ````` @@ -1321,7 +1322,7 @@ at: 39fd059eb *The same shape query WITH an explicit capture — identical hits, no auto_captured= attribute.* -**wall time: 1.19s** +**wall time: 1.26s** ````` @@ -1365,14 +1366,14 @@ at: 39fd059eb - + - + - + @@ -1384,12 +1385,12 @@ at: 39fd059eb - + - + … [24 more display lines; full output is 3484 bytes on 1 raw line(s)] @@ -1404,10 +1405,8 @@ at: 39fd059eb *Importance-weighted detail: FULL bodies for top-2, signatures for the rest.* -**wall time: 1.01s** - ````` - + @@ -1449,7 +1448,7 @@ at: 39fd059eb - + @@ -1739,7 +1738,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan ````` - + @@ -1812,12 +1811,12 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Complexity x recent git churn (maintenance pain).* -**wall time: 1.66s** +**wall time: 1.48s** ````` - + @@ -1852,7 +1851,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Token-normalized duplicate bodies.* -**wall time: 3.13s** +**wall time: 2.10s** ````` @@ -1892,34 +1891,34 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Files that change together in git (hidden coupling).* -**wall time: 2.32s** +**wall time: 1.52s** ````` - + - + + + + + - + + - + - - - - - - - + + - + @@ -1934,11 +1933,11 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Hotspots scoped to RECENT churn (the regression lens).* -**wall time: 2.13s** +**wall time: 1.36s** ````` - + @@ -2012,7 +2011,7 @@ ripwire: --pack-top-n is deprecated — use --pack-task/--detail instead (unchan *Built-in AST checks (c-cast, goto, unsafe-c-fn, ...).* -**wall time: 2.74s** +**wall time: 2.04s** ````` @@ -2170,7 +2169,7 @@ The joined finding — past the display cut above, extracted so the join is visi ````` - @@ -2208,7 +2207,7 @@ The joined finding — past the display cut above, extracted so the join is visi ````` - @@ -2284,7 +2283,7 @@ Call-graph isolate provenance: 3221 declaration, 1908 header, 2215 source, 4650 ````` - @@ -2358,11 +2357,11 @@ flowchart LR *Bus-factor: recency-weighted author ownership per file.* -**wall time: 2.18s** +**wall time: 1.97s** ````` - + @@ -2400,7 +2399,7 @@ flowchart LR ````` - + ````` @@ -2431,11 +2430,11 @@ flowchart LR *On a CLEAN tree: nothing got worse, exit 0. The gating shape is in the sandbox section below.* -**wall time: 20.06s** +**wall time: 12.88s** ````` - @@ -2477,11 +2476,9 @@ 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: 10.39s** - ````` - @@ -2498,7 +2495,7 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g ````` - ````` @@ -2507,55 +2504,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: 3.01s** +**wall time: 2.41s** ````` - - (t=modscope): a file's MODULE SCOPE — where a top-level call and an anonymous callback body's calls live; a CALLER, never a callee, with no body to expand. --> + - - - + + + - - - - - - - - - - - - - - - - - - - - - -… [34 more display lines; full output is 6424 bytes on 1 raw line(s)] + + + + + + + + + + + + + + + + + + + + + +… [258 more display lines; full output is 17245 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: 13.13s** +**wall time: 11.92s** ````` - - + + - + @@ -2567,11 +2564,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: 5.06s** +**wall time: 5.78s** ````` - + @@ -2607,11 +2604,9 @@ 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.48s** - ````` - + @@ -2634,11 +2629,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: 89.21s** +**wall time: 37.51s** ````` - + @@ -2674,7 +2669,7 @@ ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs g *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.96s** +**exit code: 1** ````` (empty) @@ -2691,11 +2686,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: 25.97s** +**exit code: 2** — **wall time: 18.79s** ````` - @@ -2731,12 +2726,12 @@ 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: 5.64s** +**wall time: 4.93s** ````` - - + + @@ -2746,14 +2741,14 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer - + - + ````` @@ -2761,12 +2756,12 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *The whole hit list asked for by name: under the same 60-row cap HEAD's references fill the list, so it shows far fewer definitions (here HEAD's one) than the default above (here 60). The default serves the all page only when it shows the same definitions as the defs page in strictly fewer bytes (a tie serves it too).* -**wall time: 7.46s** +**wall time: 5.04s** ````` - - + + @@ -2801,15 +2796,15 @@ ripwire: --plan: --stray-content=zzzz-no-such-ref matches no local ref — a zer *Same, plus a git-history row (never / removed-by-commit) for names no tree carries.* -**wall time: 17.76s** +**wall time: 11.73s** ````` - - + + - + ````` @@ -2817,8 +2812,6 @@ 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.02s** - ````` @@ -2857,8 +2850,6 @@ 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.05s** - ````` @@ -2912,10 +2903,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.40s** +**wall time: 1.95s** ````` -{"v":1,"verb":"plan-lanes","at":"39fd059eb","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":2787,"symbols":26903,"edges":417 … [line truncated: 339 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"6009ca693","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":2787,"symbols":26903,"edges":417 … [line truncated: 339 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"}, @@ -2952,7 +2943,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: 2.57s** +**wall time: 1.81s** Input file: @@ -2963,7 +2954,7 @@ write regression tests for the new filter ````` ````` -{"v":1,"verb":"plan-lanes","at":"39fd059eb","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2787,"symbols":26903,"edges":41775,"ambiguous":12805,"unresolved":12824},"carve":null,"core":{"files … [line truncated: 5 more bytes on this line] +{"v":1,"verb":"plan-lanes","at":"6009ca693","root":".","task":null,"source":"brief","requested":3,"lane_count":3,"claim_key":"path+scope+name","on_conflict":"producing-lane-rebases","corpus":{"files":2787,"symbols":26903,"edges":41775,"ambiguous":12805,"unresolved":12845},"carve":null,"core":{"files … [line truncated: 5 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":651,"ord":0,"overloads":1,"amb":0,"cx":1,"ccx":0,"churn":359,"tested":0}, @@ -2982,7 +2973,7 @@ write regression tests for the new filter {"p":"./src/mcpverbs.h","symbols":2,"churn":359,"ccx":2,"hotspot_rank":5}, {"p":"./src/recall.h","symbols":1,"churn":27,"ccx":4,"hotspot_rank":56}, {"p":"./src/verbs_change.h","symbols":1,"churn":78,"ccx":4,"hotspot_rank":22}], -"symbols_total":3230,"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":[], +"symbols_total":3231,"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":28,"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":121,"l":271,"ord":0,"overloads":1,"amb":0,"cx":23,"ccx":31,"churn":10, … [line truncated: 12 more bytes on this line] @@ -3074,20 +3065,20 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped ````` - - + + - - - - - - - - - - - + + + + + + + + + + + @@ -3103,7 +3094,7 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped -… [243 more display lines; full output is 25248 bytes on 1 raw line(s)] +… [243 more display lines; full output is 25250 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --doc-drift --gateability` @@ -3112,20 +3103,20 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped ````` - - + + - - - - - - - - - - - + + + + + + + + + + + @@ -3141,7 +3132,7 @@ ripwire: --layout: 'Lang' is an enum, --layout models structs (a scoped/unscoped -… [269 more display lines; full output is 26447 bytes on 1 raw line(s)] +… [269 more display lines; full output is 26449 bytes on 1 raw line(s)] ````` Tail of the same output — the `` section: @@ -3180,21 +3171,21 @@ Tail of the same output — the `` section: ````` - - - + + + - - - - - - - - - - - + + + + + + + + + + + @@ -3209,7 +3200,7 @@ Tail of the same output — the `` section: -… [222 more display lines; full output is 25274 bytes on 1 raw line(s)] +… [222 more display lines; full output is 25276 bytes on 1 raw line(s)] ````` ## `./build/ripwire . --from-trace=-` @@ -3367,10 +3358,10 @@ AddressSanitizer:DEADLYSIGNAL *Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges.* -**wall time: 1.00s** +**wall time: 1.09s** ````` - + @@ -3407,7 +3398,7 @@ AddressSanitizer:DEADLYSIGNAL *Per-file metrics as CodeCharta cc.json.* -**wall time: 1.42s** +**wall time: 1.71s** ````` (empty) @@ -3424,7 +3415,7 @@ Artifact written: *One-turn sweep: 4 newline-delimited verb:arg sub-queries answered in ONE deduped .* -**wall time: 1.43s** +**wall time: 1.21s** Input file: @@ -3439,7 +3430,7 @@ lego:Vehicle - + @@ -3478,17 +3469,17 @@ lego:Vehicle *Environment self-check: binary staleness, grammars, cache dir, git, tracked-binary staleness — exit 1 when any check fails (here: the PATH install is older than ./build).* -**exit code: 1** — **wall time: 3.22s** +**exit code: 1** — **wall time: 3.66s** ````` - + - - + + - + @@ -3541,13 +3532,13 @@ 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: 1.36s** +**wall time: 1.39s** ````` - - + + @@ -3569,7 +3560,7 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(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~7 --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.* @@ -3577,10 +3568,10 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s - + + - @@ -3590,12 +3581,12 @@ ripwire scan: 0 finding(s) total (26 skill file(s) scanned, 0 unscannable file(s - + - + ````` @@ -3621,7 +3612,7 @@ 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 @@ -3640,19 +3631,19 @@ ripwire: --rank-by: unknown value 'bogus' (supported: pagerank|authority|hub|rrf ````` - + 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 ) @@ -3679,7 +3670,7 @@ 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":12805,"graph_unresolved":12824,"graph_unindexed":256,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1165"}, +{"of":"rankGraphTeleport","defs":1,"count":7,"root":".","hop_tested":0,"hop_untested":7,"graph_ambiguous":12805,"graph_unresolved":12845,"graph_unindexed":256,"counts_floor":true,"callers":[{"t":"fn","n":"getIndex","p":"src/mcpindex.h:1165"}, {"t":"fn","n":"rankGraph","p":"src/graph.h:8090"}, {"t":"fn","n":"anchoredLexicalRank","p":"src/graph.h:8736"}, {"t":"fn","n":"runEval","p":"src/eval.h:171"}, @@ -3708,12 +3699,12 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m *Pagination: 3 items, skipping the first 3 (deterministic seams).* -**wall time: 2.39s** +**wall time: 1.89s** ````` - + @@ -3765,7 +3756,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m ````` - + @@ -3803,8 +3794,8 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m ````` - - + + @@ -3832,12 +3823,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: 2.23s** +**wall time: 2.16s** ````` - + @@ -3872,12 +3863,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: 2.82s** +**wall time: 1.90s** ````` - + @@ -3911,7 +3902,7 @@ ripwire: --json is not yet supported for --hotspots — supported: the default m Artifact written: ````` - 23737408 /aux/warm2.ripwirecache + 23740310 /aux/warm2.ripwirecache ````` ## `./build/ripwire . --max-file-size=8K --top-k=3` @@ -3972,12 +3963,10 @@ 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.43s** - ````` - + @@ -4008,17 +3997,17 @@ ripwire: --scip=does_not_exist.scip: cannot open the index — refusing rather t *Self-eval: co-change recall vs BM25.* -**wall time: 17.07s** +**wall time: 16.98s** ````` ripwire --eval (co-change recovery, averaged over 80 historical commits) ranker recall@5 recall@10 recall@20 ripwire 0.2% 3.7% 8.4% - BM25 4.9% 6.9% 9.7% + BM25 3.7% 5.7% 8.5% BM25sub 9.4% 13.9% 25.9% - BM25body 5.5% 14.6% 26.6% + BM25body 6.7% 14.6% 26.6% fused 7.7% 15.0% 24.6% - anchored 5.5% 14.6% 26.6% + anchored 6.7% 14.6% 26.6% same-dir 0.2% 0.8% 2.3% random 0.2% 0.4% 0.7% <- floor (random ranking over F=2787 files) note: `ripwire` here is the DEFAULT MAP's structural-only PageRank (importance, not @@ -4036,7 +4025,7 @@ ripwire --eval (co-change recovery, averaged over 80 historical commits) *Known-item retrieval eval: MRR + recall@k per ranker per query mode.* -**wall time: 143.59s** +**wall time: 129.75s** ````` ripwire --eval-retrieval (known-item, 4000 doc-commented symbols; gold is in-corpus by construction) @@ -4065,7 +4054,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: 11.61s** +**exit code: 3** — **wall time: 5.26s** Input file: @@ -4200,7 +4189,7 @@ ripwire 0.6.5 (dev, AppleClang 21.0.0.21000101, emit=std::print, built_from=65d6 ````` - + @@ -4233,7 +4222,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. ````` - + @@ -4245,7 +4234,7 @@ ripwire: the at flag's seed 'src/graph.h:999999' named no location (./src/graph. ````` - + @@ -4290,7 +4279,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - + @@ -4314,7 +4303,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - + @@ -4336,7 +4325,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - + @@ -4367,7 +4356,7 @@ ripwire: --verify claim not recognized: 'frobnicate(x)' — unknown shape word. ````` - + @@ -4442,7 +4431,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < ````` - + @@ -4462,7 +4451,7 @@ ripwire: --slice-depth bounds the --slice-flow BFS — pass both (e.g. ripwire < *Structural search written in CODE: $NAME binds one node; grammars=/shapes= disclose what the pattern became per grammar (a 3-argument call shape — the 2-argument spelling has no call site in this repo and correctly reports hits=0).* -**wall time: 1.47s** +**wall time: 1.03s** ````` @@ -4475,7 +4464,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.61s** +**wall time: 1.01s** ````` @@ -4661,8 +4650,6 @@ ripwire: --grep given twice; did you mean --grep='A' --and='B'? *A literal whose classified hits are all prose: the answer serves tier="comment+string" rather than an empty code tier, and tier_unclassified= says how many hits the fixed parse budget never classified.* -**wall time: 1.12s** - ````` @@ -4817,10 +4802,8 @@ 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.40s** - ````` - + @@ -4857,10 +4840,8 @@ 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.77s** - ````` - + @@ -4897,10 +4878,8 @@ 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.08s** - ````` - + @@ -4937,10 +4916,8 @@ 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.19s** - ````` - + @@ -5010,22 +4987,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.88s** +**wall time: 1.70s** ````` - + - + - - - + + + + ````` @@ -5034,16 +5011,16 @@ ripwire: --safe-delete symbol not found: DoesNotExist *The same packet under a hard ceiling: heuristic rows drop tail-first (withheld= disclosed), verified rows never drop.* -**wall time: 2.20s** +**wall time: 1.70s** ````` - + - + : that callee matched by name alone (receiver unproven); it does NOT mean the edge is false. : N same-named via=name rows merged; callees=FILE:SYM lists all. 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. --> - + @@ -5133,7 +5110,7 @@ ripwire: --safe-delete symbol not found: DoesNotExist ````` - + @@ -5180,7 +5157,7 @@ ripwire: --no-stable is read only by --mcp/--listen (it opts out of the stable o - + - + ````` @@ -5234,12 +5211,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.43s** +**exit code: 4** — **wall time: 2.41s** ````` - + ````` @@ -5274,7 +5251,7 @@ 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: 5.81s** +**wall time: 4.03s** ````` @@ -5350,8 +5327,6 @@ 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.62s** - ````` over rows. window: shown= total= capped= has_more= next_offset= offset= limit= (capped=1 cut; next_offset= pastes as offset=). cells_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. root=: p= relative to it. cells=N: mutable non-local cells found in the corpus (globals, statics, Python module globals; FLOOR). functions=N: functions reaching at least one cell, directly or via callees (all rows, before paging). direct_writes=/direct_reads=: the writes=/reads= cells this function's OWN body writes/reads. cells_total=N: distinct cells reached (read and written counts once); at most 12 cell rows print. at=/at_dir=: one own-body use site (may be more) and what own-body sites do (can be narrower than dir=). cells_shown=/cells_capped=1: only this many of cells_total= cell rows print (cap 12). unanalyzed_langs=/unanalyzed_files=: indexed languages (and their files) this lens skips; NOT zero cells. --> - @@ -5430,7 +5405,7 @@ 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: 3.88s** +**wall time: 2.09s** ````` @@ -5470,7 +5445,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The cache-locality lens over every aggregate: fields READ TOGETHER but declared FAR APART (split-line / straddle findings, Chilimbi separation weight) — advice only, never a rewrite.* -**wall time: 2.19s** +**wall time: 1.07s** ````` @@ -5510,7 +5485,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir *The same lens narrowed to ONE struct — the one --layout=Symbol shows the offsets for.* -**wall time: 1.79s** +**wall time: 1.04s** ````` @@ -5584,11 +5559,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: 17.09s** +**wall time: 8.91s** ````` - + @@ -5604,7 +5579,7 @@ ripwire: --run-timeout=SECONDS modifies --run-trace — pass it too (e.g. ripwir

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 G`2PVke{c=} literal 545141 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

@@ -2966,7 +2964,7 @@ _OWNERSHIP partition for a shared tree: every regression here lives in src/infra ``` $ ./build/ripwire . --quality-delta --scope=src/graph.h - @@ -2991,19 +2989,19 @@ A contract is PER DEFINITION, so a SYM matching several definition sites REFUSES **Try it** -_Fast per-symbol post-edit contract check vs git HEAD (unchanged on a clean tree)._ +_Fast per-symbol post-edit contract check vs git HEAD — recorded against a DIRTY tree, so the verdict describes the working copy, not HEAD alone, and the recorded exit code is the working copy's._ ``` $ ./build/ripwire . --edit-check=rankGraphTeleport - - - - - - - + + + + + + ``` @@ -3050,12 +3048,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":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` ### `--insert-after-symbol=TARGET` @@ -3071,12 +3069,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":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"bash test/greptiercheck.sh"} ``` **Caveats (stated by the binary):** @@ -3122,12 +3120,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":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true},"tests_to_run":[{"p":"test/verify_radix.cpp","hops":1,"run":"bash test/greptiercheck.sh"}, +"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +"order":"evidence","partners":0,"tests":5,"script_gates_unmodelled":722,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} ``` **Shaped by:** `--insert-after-symbol` @@ -3186,11 +3184,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":9496,"graph_unresolved":12841,"graph_unindexed":231,"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: 485 more bytes on this line] +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"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: 485 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":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true}}]} +"graph_ambiguous":9828,"graph_unresolved":13184,"graph_unindexed":231,"counts_floor":true}}]} ``` **Shaped by:** `--edit-check` @@ -3235,19 +3233,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] ``` @@ -3272,17 +3270,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] @@ -3326,10 +3324,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:4434 +$ ./build/ripwire . --at=src/graph.h:4647 - - + + ``` @@ -3348,12 +3346,12 @@ The bundle is BUDGETED by default (8000 tokens, budget_default="1" on the root; **Try it** -_No-LLM review-evidence bundle for the working-tree diff (clean tree = empty)._ +_No-LLM review-evidence bundle for the working-tree diff — recorded against a DIRTY tree, so it is populated rather than empty._ ``` $ ./build/ripwire . --pr-context - ``` @@ -3379,16 +3377,20 @@ _Pairwise cross-arm conflict sites + suggested landing order (any committish sha ``` $ ./build/ripwire . --merge-scout=HEAD~2,HEAD~1 - - + + - + + + + - - + + +... [2 more line(s); run it to see the whole thing] ``` **Shaped by:** `--plan-lanes`, `--plan` @@ -3457,21 +3459,13 @@ _Which refs of the one branch `main` (no ref family left to select on this check ``` $ ./build/ripwire . --stray-content=main - - - - - - - - - - - - - - -... [4 more line(s); run it to see the whole thing] + + + + + + + ``` **Shaped by:** `--plan`, `--abi`, `--whereis`, `--limit`, `--eval-stray` @@ -3495,19 +3489,19 @@ _Select the genuinely-unmerged refs of the one branch `main` (no ref family left ``` $ ./build/ripwire . --stray-content=main --plan - - - - - - - - - - - - - + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -3531,14 +3525,7 @@ _Cross-branch ABI-break gate over the one branch `main` (no ref family left to s ``` $ ./build/ripwire . --stray-content=main --abi - - - - - - - + - - - + + + + ... [25 more line(s); run it to see the whole thing] ``` @@ -3589,9 +3576,9 @@ _The dark-content dashboard: gates BUILT but OFF. CHANGED: no longer invents gat ``` $ ./build/ripwire . --flags - + - + @@ -3672,7 +3659,7 @@ _The cache-locality lens over every aggregate: fields READ TOGETHER but declared ``` $ ./build/ripwire . --field-affinity - @@ -3707,17 +3694,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - - - - - - - - - - + + + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3742,17 +3729,17 @@ _Which of this repo's doc claims are now false. CHANGED: row attribute at= renam ``` $ ./build/ripwire . --doc-drift - - - - - - - - - - - + + + + + + + + + + + ... [19 more line(s); run it to see the whole thing] ``` @@ -3776,18 +3763,18 @@ _Same report, with git history splitting stale mentions into deleted-by-commit v ``` $ ./build/ripwire . --doc-drift --with-history - - - - - - - - - - - - + + + + + + + + + + + + ... [18 more line(s); run it to see the whole thing] ``` @@ -3812,7 +3799,7 @@ _The house PLAN/DESIGN format's STRUCTURE check — never semantics; exit 2 when ``` $ ./build/ripwire . --plan-lint=test/planlintfix/wave.md - + @@ -3847,10 +3834,10 @@ $ ./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:4434 - #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4475 - #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1625 - #3 0x102e01a44 in main src/main.cpp:3935 + #0 0x102f4a1c8 in rw::rankGraphTeleport(Graph const&, std::vector const&, float) src/graph.h:4647 + #1 0x102f3e884 in rw::rankGraph(Graph const&, float) src/graph.h:4688 + #2 0x102e11f30 in runDefaultMap(MainDispatch const&) src/main.cpp:1631 + #3 0x102e01a44 in main src/main.cpp:3975 #4 0x1a2b3c0dc in start+0x9dc (dyld:arm64e+0x60dc) ==41337==ABORTING ``` @@ -3877,7 +3864,7 @@ _A command that exits 0: a minimal success record (exit, measured duration, disc $ ./build/ripwire . --run-trace="true" - + ``` @@ -3967,7 +3954,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 - -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: 104 more bytes on this line] + +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 ) ... [17 more line(s); run it to see the whole thing] ``` @@ -4004,18 +3991,18 @@ _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. --> - - - + + + - + Print the authoritative CLI usage and flag catalog to the caller-provided output streaminline void printUsage( std::FILE* out ) noexcept - -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 std::vector<std::string>& verbLines… - -knows; these are all OUTSIDE that table. DERIVED, NOT ENUMERATED — the argument htmlPreemptedB…inline constexpr std::string_view kInRideAlong[] = + +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 ... [19 more line(s); run it to see the whole thing] ``` @@ -4037,7 +4024,7 @@ _Task lens + a compact Mermaid flowchart of the top anchors' 1-hop edges._ ``` $ ./build/ripwire . --for="pagerank power iteration" --with-graph - + @@ -4112,14 +4099,14 @@ _Environment self-check: binary staleness, grammars, cache dir, git, tracked-bin ``` $ ./build/ripwire . --doctor - + - - + + - - + @@ -4146,14 +4133,14 @@ _--doctor plus a LIVE integration inspection for one agent: PATH binary, install ``` $ ./build/ripwire . --doctor --agent=claude - + - - + + - - + @@ -4180,7 +4167,7 @@ $ ./build/ripwire . --skipped - @@ -4207,7 +4194,9 @@ $ ./build/ripwire . --skipped ### `--scan-skill=FILE` -**Answers:** scan a single skill file before installing (any file, not just .md) +**Answers:** scan a single skill file before installing (any file, not just .md) EXFILTRATE:net-exfil (a network verb plus a $VAR or base64 on one fenced line) needs a destination: a verb named but not run, as in command -v curl, does not fire. + +It is CRITICAL only when a credential-shaped source is on that line: a credential-named var, an Authorization: header with a var, an env dump or a key file. Otherwise it is WARN and the row says why="no-cred-source". A sensitive file read fed into an upload (cat /etc/passwd | curl ... @-, curl -d @.env ...) is CRITICAL with or without a var: why="sensitive-read-upload". **Try it** @@ -4220,11 +4209,13 @@ $ ./build/ripwire --scan-skill=skills/ripwire-orient/SKILL.md ``` +**Shaped by:** `--scan-skills` + ### `--scan-skills[=DIR]` **Answers:** scan a skills directory before installing — every text file, .md and .sh alike scan DIR (or .agents/skills/ + ${CLAUDE_CONFIG_DIR:-~/.claude}/skills/ + ${CODEX_HOME:-~/.codex}/skills/). -EVERY text file, .md and .sh alike — a skill dir's executables are the files most worth scanning. skipped= counts what it could not scan (binary content, or unreadable); denylisted subtrees (.git, node_modules, build, ...) are not descended and the stderr tally says how many for vulnerabilities +EVERY text file, .md and .sh alike — a skill dir's executables are the files most worth scanning. skipped= counts what it could not scan (binary content, or unreadable); denylisted subtrees (.git, node_modules, build, ...) are not descended and the stderr tally says how many. The bare form never reads a positional root: one that is not the current directory is refused (exit 3, use --scan-skills=DIR), and its answer names the directories it walked in dirs= **Try it** @@ -4240,6 +4231,7 @@ $ ./build/ripwire --scan-skills=skills **Caveats (stated by the binary):** - skipped= counts what it could not scan (binary content, or unreadable); +- The bare form never reads a positional root: one that is not the current directory is refused (exit 3, use --scan-skills=DIR), and its answer names the directories it walked in dirs= ### `--force` @@ -4263,14 +4255,14 @@ _Rank by git change-frequency prior instead of PageRank._ $ ./build/ripwire . --rank-by=churn --top-k=5 - - + + - + - + @@ -4296,25 +4288,25 @@ 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~7 --exclude=test --exclude=docs --exclude=skills --in=src --limit=3 - - + + - - - - - - + + + + + + - - + + ... [5 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--external-surface`, `--doctor`, `--legend` +**Shaped by:** `--external-surface`, `--doctor`, `--legend`, `--max-memory` **Caveats (stated by the binary):** @@ -4372,8 +4364,8 @@ _The same gating report under --legend=full — same rows, same exit 2, but the ``` $ ./build/ripwire . --quality-delta --legend=full - - + @@ -4401,7 +4393,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=861226192676dedb entries=733 +ripwire legend dictionary ripwire.dict/v1 dictv=32cb666f84c1d715 entries=757 : 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=) @@ -4453,18 +4445,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] ``` @@ -4487,21 +4479,21 @@ _Drop matching paths (repeatable) before ranking._ ``` $ ./build/ripwire . --exclude=present --exclude=bench --top-k=5 - + - - - - - - - - + + + + - - + + + + -... [6 more line(s); run it to see the whole thing] + + +... [8 more line(s); run it to see the whole thing] ``` **Shaped by:** `--skipped`, `--in`, `--index-out` @@ -4514,25 +4506,25 @@ this is NOT a filter to only-changed symbols. changed="N" in the header names th **Try it** -_Full map re-ranked with teleport toward git-changed files — clean tree, so changed=0 and it degrades to the plain map._ +_Full map re-ranked with teleport toward git-changed files — recorded against a DIRTY tree, so changed= counts the working copy's files and the teleport is live._ ``` $ ./build/ripwire . --map-diff --top-k=5 - + - - - - - - - - + + + + - - + + + + -... [6 more line(s); run it to see the whole thing] + + +... [8 more line(s); run it to see the whole thing] ``` **Shaped by:** `--json`, `--limit` @@ -4552,21 +4544,19 @@ _Explicit incremental cache at a path OUTSIDE the repo (first call writes it)._ ``` $ ./build/ripwire . --cache=/aux/warm2.ripwirecache --top-k=3 - + - - - - + + + + - - + + - - -... [3 more line(s); run it to see the whole thing] + ``` **Shaped by:** `--doctor`, `--index-out` @@ -4602,21 +4592,19 @@ _Force a cold parse (bypass the warm TMPDIR cache) — shows the cold-vs-warm co ``` $ ./build/ripwire . --no-cache --top-k=3 - + - - - - + + + + - - + + - - -... [3 more line(s); run it to see the whole thing] + ``` **Shaped by:** `--doctor` @@ -4633,21 +4621,19 @@ _Crawl paths the repo's own .gitignore covers (default honours it and discloses ``` $ ./build/ripwire . --no-ignore --top-k=3 - + - - - - + + + + - - + + - - -... [3 more line(s); run it to see the whole thing] + ``` **Caveats (stated by the binary):** @@ -4667,20 +4653,20 @@ _Skip files above a size bound before parsing (note the corpus shrink in the hea ``` $ ./build/ripwire . --max-file-size=8K --top-k=3 - + - - - - + + + + - - + + - - + + ... [3 more line(s); run it to see the whole thing] ``` @@ -4692,6 +4678,20 @@ $ ./build/ripwire . --max-file-size=8K --top-k=3 - default 4MB skip files larger than N bytes (default 4MB; - files it drops are counted in the header's skipped_oversize= +### `--max-memory=N[K|M|G]` + +**Answers:** the memory guard's limit (default 65% of this machine's memory, env RIPWIRE_MAX_MEMORY) the memory guard is on for every run and silent on normal ones: it measures this process's footprint at most once per 5 s, from 5 s into an ingest. + +Past its lines the crawl (growth of limit/8) or the parse (half the limit) stops, and the default map answers from what was built, disclosed in its header (memory_stop=, memory_parsed=, memory_limit=; a parse stop keeps the first K slots it claimed of its work order — uncached files first, largest first within a tier, or path order when every grammar-bearing file was an ingest-cache hit — so a partial map repeats for a given memory_parsed=K); critical OS pressure stops them too. Every other verb (--html, --mermaid, --expand, --outline and, after a crawl stop, --in included) refuses a partial index, and so does any verb whose own internal ingest was cut; at the limit itself ripwire exits 5 with one line naming it. Nothing derived from a partial ingest is cached. The default is 65% of physical RAM (or of the cgroup's memory.max when lower); this flag, or RIPWIRE_MAX_MEMORY when the flag is absent, replaces it; below 64M is refused. + +**Shaped by:** `--mcp` + +**Caveats (stated by the binary):** + +- the memory guard's limit (default 65% of this machine's memory, env RIPWIRE_MAX_MEMORY) the memory guard is on for every run and silent on normal ones: it measures this process's footprint at most once per 5 s, from 5 s into an ingest. +- Past its lines the crawl (growth of limit/8) or the parse (half the limit) stops, and the default map answers from what was built, disclosed in its header (memory_stop=, memory_parsed=, memory_limit=; +- Every other verb (--html, --mermaid, --expand, --outline and, after a crawl stop, --in included) refuses a partial index, and so does any verb whose own internal ingest was cut; + ### `--refetch` **Answers:** when the root is a git URL, clone it fresh instead of reusing the cached copy when the root is a git URL, force a fresh clone instead of reusing the cached one (default: reuse forever; @@ -4733,26 +4733,26 @@ _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 - + - - - - + + + + - - + + - - -... [3 more line(s); run it to see the whole thing] + ``` ### `--mcp` -**Answers:** persistent index server (parse once, many warm queries) over stdio +**Answers:** persistent index server (parse once, many warm queries) over stdio persistent index server over stdio. + +Roots: a request's path= (or `paths`), else the startup root of `ripwire --mcp`, else the directory the server was launched in. A root that is $HOME itself (a git repository or not), a filesystem or drive root, or a system tree (/usr, /etc, /System, %WINDIR% ...) is refused with "no project root:

+ +````` + +## `./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: 19.40s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [107 more display lines; full output is 14316 bytes on 1 raw line(s)] +````` + +stderr: + +````` +ripwire: no ./.ripwire_quality_baseline — auto-comparing the working tree vs git HEAD (commit the baseline with --quality-baseline to pin it) +````` + +## `./build/ripwire . --quality-delta --quality-ack --scope=src/graph.h --ack-only=api-surface` + +*The rubber-stamp guard: an --ack-only that names an OUT-OF-SCOPE row refuses (exit 1) and writes nothing.* + +**exit code: 1** — **wall time: 12.87s** + +````` +(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: 5.65s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +````` + +## `./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.31s** + +````` +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-01 keep this branch-free — it sits inside the PageRank sort comparator ce5c0b3fcb8a50e1a755398450908bf6a790fd50 upstream-paging +````` + +stderr: + +````` +ripwire: --note-add: tip: notes that say "chose X over Y because Z" surface better — consider adding the why +ripwire: --note-add: target 'lessByScoreDescId' canonicalised to 'src/infra/sortutil.h::rw::sortutil::lessByScoreDescId' — that is the id --for/--expand key notes by +````` + +## `./build/ripwire . --notes` + +*The note is listed under the canonical id, dangling="0" — i.e. it will actually surface. (Before the H1 fix the bare name was stored verbatim and read dangling="1": recorded, and surfaced nowhere.)* + +**wall time: 1.24s** + +````` + + + + + + + + + + + + + + + + gate scripts' claim (~line 1305) is NOT enforced — the derived-vs-stated sibling loop here covers docs/EVALS.md only. It drifted 407→451 unnoticed (fixed 2026-08-23). To close: grep both files ('file:line:' parsing) in the gateCountClaims arm.]]> + + + + +````` + +## `./build/ripwire . --note-add="gitOneLine: which one?"` + +*Two definitions carry this name, so the write REFUSES rather than pick one: a note keys ONE canonical id, and an ambiguous selector is refused, never silently narrowed. Every candidate is named, with a runnable retry.* + +**exit code: 1** — **wall time: 1.57s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --note-add: tip: notes that say "chose X over Y because Z" surface better — consider adding the why +ripwire: --note-add: target 'gitOneLine' is ambiguous — it matches 2 definitions in 2 distinct contracts, and a note keys ONE canonical id (it would surface on one of them and look absent on the rest). Qualify one: ./src/handoff.h:gitOneLine, ./src/quality.h:gitOneLine — e.g. --note-add="./src/h … [line truncated: 33 more bytes on this line] +````` + +## `./build/ripwire . --note-add="lessByScoreDescIdd: typo"` + +*A name that resolves to nothing is refused with the read verbs' own did-you-mean — never written as a dead note.* + +**exit code: 1** — **wall time: 1.19s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --note-add: tip: notes that say "chose X over Y because Z" surface better — consider adding the why +ripwire: --note-add: target not found: lessByScoreDescIdd (did you mean 'lessByScoreDescId'?) — a note keys the canonical id a read verb resolves; to note a FILE instead, pass a path (one with a '/' or an extension), which may name a file that does not exist yet +````` + +## `./build/ripwire . --note-add="src/infra/sortutil.h::rw::sortutil::lessByScoreDescId: chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort"` + +*The same symbol addressed by its CANONICAL id: already canonical, so nothing is rewritten and both notes land on ONE target.* + +**wall time: 1.18s** + +````` +src/infra/sortutil.h::rw::sortutil::lessByScoreDescId 2026-10-01 chose the flat two-branch compare over the nested ladder because the comparator sits inside the PageRank sort ce5c0b3fcb8a50e1a755398450908bf6a790fd50 upstream-paging +````` + +## `./build/ripwire . --expand=lessByScoreDescId --top-k=0` + +*Both notes riding along with the symbol's body — the elements follow the body, past the display cut, so they are extracted below.* + +**wall time: 1.65s** + +````` + + + + + + + + + + + + + +````` + +## `./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: 6.56s** + +````` +{"applied":"insert_after_symbol","symbol":"lessByScoreDescId","file":"src/infra/sortutil.h","span":{"start":2375,"end":2463},"lines":{"start":56,"end":58},"replaced_bytes":0,"old_file_bytes":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":9496,"graph_unresolved":12841,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"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: 2.96s** + +````` +{"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: 6.47s** + +````` +{"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":9496,"graph_unresolved":12841,"graph_unindexed":231,"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,"tests":5,"script_gates_unmodelled":714,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"next":"--uses=src/infra/sortutil.h:nonNegativeFloatDescKey"} +````` + +stderr: + +````` +ripwire edit: applied atomically; receipt carries region, blob_sha, edit_check, tests_to_run; next: --uses=src/infra/sortutil.h:nonNegativeFloatDescKey +````` + +## `./build/ripwire . --replace-symbol-body=DoesNotExist --edit-payload=/aux/payload_note.h` + +*An unknown TARGET refuses and leaves every file byte-identical.* + +**exit code: 1** — **wall time: 2.92s** + +````` +(empty) +````` + +stderr: + +````` +ripwire: --replace-symbol-body: symbol 'DoesNotExist' not found +````` + +## `./build/ripwire . --edit-plan=/aux/edit_plan.json --dry-run` + +*A versioned multi-edit TRANSACTION preflighted without writing: the receipt shows what each op would read and touch.* + +**wall time: 2.97s** + +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":9496,"graph_unresolved":12841,"graph_unindexed":231,"counts_floor":true,"atomic_scope":"per-file","rollback_on_write_error":true,"recheck_before_each_write":true,"multifile_crash_atomic":false," … [line truncated: 301 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: 6.65s** + +````` +{"schema":"ripwire.edit-plan/v1","mode":"apply","edits":1,"files":1,"callers_union":4,"graph_ambiguous":9496,"graph_unresolved":12841,"graph_unindexed":231,"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: 485 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":9496,"graph_unresolved":12841,"graph_unindexed":231,"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: 23.20s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [101 more display lines; full output is 13413 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: 13.78s** + +````` +(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: 19.24s** + +````` +(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 21919 per-symbol rows, 22608 indexed symbols total) +````` + +Artifact written: + +````` +3668084 .ripwire_quality_baseline +# ripwire quality baseline v6 — regenerate with --quality-baseline; do not hand-edit +producer 11751c44735faf66f8983082afef4f6f49652cdc8e09888b9ea4272ebcb44e5a +head ce5c0b3fcb8a50e1a755398450908bf6a790fd50 +dirty 1 +absorbed 2 +ccx d19d7c13cbc7 1 +ccx 1f7edbff929e5 10 +ccx 6a732f6699bdb 0 +ccx 7d8862be4e +````` + +## `./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: 28.06s** + +````` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +… [96 more display lines; full output is 12278 bytes on 1 raw line(s)] +````` + + +--- + +# 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: 735 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: 1111 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: 874 more bytes on this line] +{"name":"grep","description":"Trigram literal search; each hit annotated with its enclosing symbol (which function/class it is in). pattern = the literal; in=any lifts the span tiering (by default the tightest non-empty tier is served and what was held back rides as suppressed_comment/suppressed_str … [line truncated: 827 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 ceiling (the CLI --for --token-budget). The header carries route= (which ranker answere … [line truncated: 1053 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: 657 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: 783 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, ranked by PageRank. Use before modifying or deleting a symbol; it beats find_referencing_symbols (direct callers only), and 'uses' catches the read/write/import sites calls miss. Call edges … [line truncated: 1306 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: 2117 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: 1226 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: 1204 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= 46711 (~tokens at 4 bytes/token: 11677 ) +explore desc_bytes= 1322 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'] +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= 802 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= 829 schema_bytes= 559 required=['trace'] +whereis desc_bytes= 655 schema_bytes= 714 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=[] +find_symbol desc_bytes= 682 schema_bytes= 590 required=['symbol'] +rank_by desc_bytes= 758 schema_bytes= 481 required=[] +for desc_bytes= 445 schema_bytes= 777 required=['task'] +fetch_body desc_bytes= 623 schema_bytes= 588 required=['handle'] +connect desc_bytes= 535 schema_bytes= 580 required=['symbols'] +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=[] +find_referencing_symbols desc_bytes= 434 schema_bytes= 590 required=['symbol'] +grep desc_bytes= 435 schema_bytes= 562 required=['pattern'] +memory_recall desc_bytes= 447 schema_bytes= 500 required=['task'] +quality_delta desc_bytes= 749 schema_bytes= 193 required=[] +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=[] +lego desc_bytes= 360 schema_bytes= 467 required=['type'] +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/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: 3.60s** + +````` +{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","serverInfo":{"name":"ripwire","version":"1.0"},"capabilities":{"tools":{},"resources":{}},"instructions":"Map before reading files. Start a new task with explore; use from_trace for an error; use impact plus uses before changing a sym … [line truncated: 735 more bytes on this line] +{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":" - - - - - - - - - - + + + + + + + + + + - + + -... [10 more line(s); run it to see the whole thing] +... [6 more line(s); run it to see the whole thing] ``` **Shaped by:** `--token-budget`, `--for`, `--recall`, `--graph-query`, `--pack-signatures`, `--expand`, `--from-trace`, `--run-trace` @@ -104,21 +104,15 @@ _SHAPE the map to fit ~1500 tokens (binary-search top-K)._ ``` $ ./build/ripwire . --max-tokens=1500 - - - - - - - - - - - - + + + + + + -... [1 more line(s); run it to see the whole thing] + ``` **Shaped by:** `--token-budget`, `--recall`, `--detail`, `--pr-context`, `--from-trace`, `--run-trace` @@ -141,7 +135,8 @@ _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`, `--recall`, `--handoff`, `--pr-context`, `--from-trace`, `--run-trace` @@ -164,8 +159,8 @@ _The honest half of the contract: a task with no ripwire-shaped evidence ABSTAIN ``` $ ./build/ripwire . --help-task="write a cheerful release announcement" - - + + ``` @@ -186,19 +181,19 @@ _Name-shaped query: the router picks name-exact BM25 (header says which/why)._ ``` $ ./build/ripwire . --for="rankGraphTeleport" - - + + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` - + & p, float alpha = 0.85f ) ... [17 more line(s); run it to see the whole thing] ``` @@ -231,14 +226,14 @@ _T3 opt-out: the signatures-only lens (no auto bodies, no bundle="auto" attribut ``` $ ./build/ripwire . --for="rankGraphTeleport" --signatures-only - - + + - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quality-biased through biasPrior() so all rank modes share one weighting seam; the transition matrix (edgesinline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>&am … [line truncated: 31 more bytes on this line] -#### The convergence disclosure contract -### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` +#### The convergence disclosure contract +### Wave-2 adversarial verification (2026-08-19) — six probes against `aa97c9e` @@ -263,20 +258,18 @@ _Opt OUT of compact conceptual serving: restore the rank-first auto wal ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --auto-bodies - - - - - + + + + +inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) - +TSTree* parseTree( TSParser* parser, std::string_view src ) + Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() - -errNodes/errBytes are a PARSER-STATE fact, never a syntax verdict: tree-sitter error recovery fi…struct FileHealth -enum class AstWalk : std::uint8_t -constexpr std::array<LangEntry, 48> kLangTable = -... [18 more line(s); run it to see the whole thing] +struct FileHealth +... [20 more line(s); run it to see the whole thing] ``` **Shaped by:** `--for` @@ -298,19 +291,18 @@ _Same query with routing forced OFF (plain subtoken+body BM25) — contrast with ``` $ ./build/ripwire . --for="rankGraphTeleport" --no-route - - - - - + + + + + uniform-teleport PageRank (the defaultinline RankedGraph rankGraph( const Graph& g, float alpha = 0.85f ) - + PageRank with an explicit teleport / personalization vector p (Σp = 1). The prior is name-quali…inline RankedGraph rankGraphTeleport( const Graph& g, const std::vector<float>& p, float alpha = 0.85f ) -inline ChurnRanking churnRankedGraph( const MainDispatch& d ) - +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 -... [19 more line(s); run it to see the whole thing] +... [20 more line(s); run it to see the whole thing] ``` **Caveats (stated by the binary):** @@ -331,20 +323,18 @@ _Cut the result at the relevance cliff (Adaptive-k) — on a flat ranking nothin ``` $ ./build/ripwire . --for="tree-sitter parse of a source file" --adaptive - - - - - + + + + +inline bool topLevelEvidence( std::string_view source, const TSLanguage* language, const Predicate& predicate ) + The crawl's per-file byte ceiling. A text file larger than this is skipped: at this size it is o…constexpr std::size_t kDefaultMaxFileBytes = 4u * 1024u * 1024u -TSTree* parseTree( TSParser* parser, std::string_view src ) - +TSTree* parseTree( TSParser* parser, std::string_view src ) + Exercise every registered grammar and its embedded query, reporting loaded and expected totalsinline DoctorGrammarProbe doctorProbeGrammars() - -errNodes/errBytes are a PARSER-STATE fact, never a syntax verdict: tree-sitter error recovery fi…struct FileHealth -enum class AstWalk : std::uint8_t -constexpr std::array<LangEntry, 48> kLangTable = -... [18 more line(s); run it to see the whole thing] +struct FileHealth +... [20 more line(s); run it to see the whole thing] ``` **Shaped by:** `--for`, `--detail` @@ -365,16 +355,16 @@ _Same task with the anchor disabled — the contrast the flag exists for._ ``` $ ./build/ripwire . --for="why does src/lexical.h chooseForRanker pick name-exact BM25" --no-mention-boost - - - - + + + + The name-exact ranker AS THE RETRIEVAL LENS SERVES IT: whole-name BM25 plus the definition-over-…inline std::vector<float> lexicalScoresNameExactRanked( const IngestResult& ing, std::string_view query, const std::vector<float>* symbolScoreMul ) - + P11.12: the interpretive footer for --eval's ranker table, pulled into its own function so the 9…inline void printEvalRankerNote() -inline int runEvalRetrieval( const IngestResult& ing, const Graph& g ) - +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 ... [20 more line(s); run it to see the whole thing] ``` @@ -397,16 +387,16 @@ _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 - - - - -inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) -std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] - + + + + +inline QualityDeltaOutcome computeQualityDelta( const std::string& root ) + runQualityViews was NOT a dispatch chain — it held two branches, one of which was 298 lines. T…std::optional<int> runQualityDelta( const MainDispatch& d ) - -WHAT this delta is measured AGAINST, and on WHICH tree — the one place --quality-delta decides…struct DeltaBasis +std::optional<int> refuseForeignAckSelection( const rw::Config& cfg, const rw::quality::Scope& scope, const std::vector<rw::quality::Regression>& outOfScope, st … [line truncated: 7 more bytes on this line] + +H10 (capture-audit 2026-09-04): the --quality-ack run that has NOTHING to accept. It used to re-…int ackNothingToAccept( const std::string& acksFile, const gtl::btree_map<std::string, rw::quality::AckRecord>& acks, const rw::quality::Scope& scope, std::size… ... [21 more line(s); run it to see the whole thing] ``` @@ -426,10 +416,9 @@ _Interface -> implementors view: every existing impl of the named interface; the ``` $ ./build/ripwire . --lego=Vehicle - - - - + + + @@ -457,10 +446,11 @@ _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 ) { // A fixed char[] must arrive as rw::cstr( buf ). printf's %s always meant "bytes to the first NUL"; @@ -470,8 +460,7 @@ $ ./build/ripwire . --exemplar="format byte sizes for humans" // 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>> ) ), -... [3 more line(s); run it to see the whole thing] +... [4 more line(s); run it to see the whole thing] ``` **Shaped by:** `--compress`, `--metrics`, `--doctor`, `--limit`, `--index-out` @@ -488,9 +477,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" — 107 relevant of 200 document files, best-first — total=107 shown=8 capped=1 truncated=6 generated_demoted=3 max_tokens=8000 share_bytes=2337 est_tokens=5372 +ripwire recall — "quality delta gating exit codes" — 113 relevant of 208 document files, best-first — total=113 shown=8 capped=1 truncated=7 generated_demoted=3 max_tokens=8000 share_bytes=2161 est_tokens=5307 -━━ README.md (relevance 8.863) ━━ [sections: 2 of 21 selected (60 in doc), section-granular; whole doc 180569 B; lines="2426-2437,2438-2445"; dropped_by_budget=19] +━━ README.md (relevance 8.782) ━━ [sections: 2 of 24 selected (60 in doc), section-granular; whole doc 204511 B; lines="2595-2606,2607-2614"; dropped_by_budget=22] #### 6.2 Exit codes This is the part to wire into a script. @@ -553,20 +542,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 1870195-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 1729927-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=2041 est_tokens=951 +ripwire recall — "field affinity cache line data layout which fields are read together" — 2 relevant of 2 document files, best-first — total=2 shown=1 capped=1 truncated=1 max_tokens=1200 share_bytes=2040 est_tokens=952 -━━ commands.md (relevance 9.817) ━━ [sections: 1 of 151 selected (180 in doc), section-granular; whole doc 703917 B; lines="3641-3645"; dropped_by_budget=150] [truncated: 1865 of 7256 bytes] -### `--field-affinity[=STRUCT]` +━━ commands.md (relevance 10.656) ━━ [sections: 1 of 154 selected (183 in doc), section-granular; whole doc 717765 B; lines="515-540"; dropped_by_budget=153] [truncated: 1854 of 4242 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 or narrow the query for 1 more (~2548-byte budget)) +... [17 more line(s); run it to see the whole thing] ``` ### `--tree` @@ -581,20 +575,20 @@ _File-by-file orientation map (top symbols per file)._ ``` $ ./build/ripwire . --tree - - - - - - - - - + + + - - + + + + + + + - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -620,7 +614,7 @@ $ ./build/ripwire . --html=/aux/map2.html (empty) ``` -**Shaped by:** `--color-by`, `--legend`, `--max-memory` +**Shaped by:** `--color-by`, `--legend` **Caveats (stated by the binary):** @@ -653,21 +647,21 @@ _Stable (path/id) emit order — provider KV-cache hits across re-runs._ ``` $ ./build/ripwire . --order=stable --top-k=5 - - - - - - - - + + + + + + + + + + - - -... [10 more line(s); run it to see the whole thing] +... [6 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-stable` @@ -684,21 +678,21 @@ _--no-stable outside --mcp: what the flag does (or says) when there is no stable ``` $ ./build/ripwire . --no-stable --top-k=3 - - - - - - - - - - - + + + + + + - + + + -... [2 more line(s); run it to see the whole thing] + + + +... [3 more line(s); run it to see the whole thing] ``` --- @@ -717,20 +711,20 @@ _Ego graph around one symbol — depth 1 BY DEFAULT now (the root's depth= says ``` $ ./build/ripwire . --around=rankGraphTeleport - - - - - - + + - - - + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -748,10 +742,10 @@ _Unknown-symbol REFUSAL shape (exit 1) with a did-you-mean from real edit distan ``` $ ./build/ripwire . --callers=DoesNotExist - +(empty) ``` -**Shaped by:** `--no-route`, `--callees`, `--uses`, `--impact`, `--expand`, `--dead-code`, `--edit-check`, `--slice-flow` +**Shaped by:** `--no-route`, `--callees`, `--uses`, `--impact`, `--expand`, `--edit-check`, `--slice-flow`, `--at` **Caveats (stated by the binary):** @@ -769,22 +763,20 @@ _What SYM calls (1-hop out-edges)._ ``` $ ./build/ripwire . --callees=rankGraphTeleport - - - - - - + + + + + - - + ``` -**Shaped by:** `--exercises`, `--format`, `--legend`, `--json`, `--limit` +**Shaped by:** `--impact`, `--exercises`, `--format`, `--legend`, `--json`, `--limit` **Caveats (stated by the binary):** @@ -802,22 +794,21 @@ _The resolvable use-sites (call/read/write/import/extends) with file:line; count ``` $ ./build/ripwire . --uses=rankGraphTeleport - - - + + + + + - - - - - - - - + + + + + ``` -**Shaped by:** `--no-route`, `--naming-consistency`, `--edit-check`, `--edit-target-file`, `--safe-delete`, `--doctor`, `--format`, `--legend` +**Shaped by:** `--no-route`, `--impact`, `--naming-consistency`, `--edit-check`, `--edit-target-file`, `--safe-delete`, `--doctor`, `--format` **Caveats (stated by the binary):** @@ -837,20 +828,20 @@ _Composable node-set query: functions within 2 caller-hops of rankGraphTeleport. ``` $ ./build/ripwire . --graph-query='and(callers(name("rankGraphTeleport"),2),kind(all,fn))' - - - - - - - - - - - - - - + + + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -874,20 +865,20 @@ _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] ``` @@ -909,14 +900,13 @@ _Shortest directed call-path SRC -> DST. CHANGED: now reports from_p/to_p/from_d ``` $ ./build/ripwire . --path=main,rankGraphTeleport - - - - - - - - + + + + + + + ``` @@ -932,14 +922,12 @@ _Minimal connecting subgraph over 3 symbols (finds shared-caller joins)._ ``` $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex - - - - + + - - + + @@ -950,9 +938,9 @@ $ ./build/ripwire . --connect=rankGraphTeleport,runEval,getIndex ### `--impact=SYM` -**Answers:** show everything that reaches SYM — the transitive blast radius before a change — the indexed symbols that reach SYM (a floor, see counts_floor). +**Answers:** show everything that reaches SYM — the transitive blast radius before a change transitive blast radius — the indexed symbols that reach SYM (a floor, see counts_floor). -file:name disambiguates like --callers rows run nearest first: d= is the hop depth (1 = a direct caller; printed where it changes), PageRank order within a depth; by_depth=k:n on the root counts reaches= per depth, so a cut drops the deepest first importers= is a SECOND, weaker reach beside it: the files that directly include/import a file defining SYM, emitted as rows (format=columnar carries the count only; --limit sizes it). NEVER added to reaches= — files and symbols are different units, and an importer may use a different symbol from that file, or none at all. lazy="1": every one of that importer's edges is written inside a closure — a TS/JS require()/import() inside a function body, a Ruby constant receiver or argument inside a method/lambda/block, a Ruby autoload or rescue class — not at load time: still a real dependency, weaker than a top-level one +file:name disambiguates like --callers importers= is a SECOND, weaker reach beside it: the files that directly include/import a file defining SYM, emitted as rows (format=columnar carries the count only; --limit sizes it). NEVER added to reaches= — files and symbols are different units, and an importer may use a different symbol from that file, or none at all. lazy="1": every one of that importer's edges is written inside a closure — a TS/JS require()/import() inside a function body, a Ruby constant receiver or argument inside a method/lambda/block, a Ruby autoload or rescue class — not at load time: still a real dependency, weaker than a top-level one counts_floor="1" every count on the graph verbs is a FLOOR, never a total; a 0 means none found on --callers/--callees/--uses/--impact/--edit-check every count is a FLOOR, never a total: the call graph is extracted from source text by name, so dynamic dispatch and declarations that parse without a call expression (C++ most-vexing-parse) contribute no edge; a call through a function pointer/callback is an edge only when ONE function is bound to that variable in scope (reassigned/table-indexed/lambda-bound/escaped — address-taken or reference-bound — pointers stay edge-less, C-family); a macro-generated call site contributes a role="macro" edge when its name uniquely names an indexed function-like #define (t="macro"); a shared name stays a plain call, an unindexed macro's site is no edge. Read a 0 as "none found", never as "none exists". Those five verbs also count DISTINCT (caller,callee) pairs, while --uses counts call SITES — see each verb's own legend pr_iters="N" how many PageRank iterations produced this ordering on every PageRank-ordered document (the map, and the tree, seams, communities, zoom, impact, graph-query and exercises verbs, plus their MCP twins): how many power iterations produced that ordering. The iteration stops when the L1 residual between successive rank vectors falls below tolerance, or at a fixed iteration ceiling, whichever comes first. pr_converged="0" is emitted ONLY on that second exit and means the ranking is a rank vector that stopped SHORT of tolerance, not the fixed point it approximates. ABSENCE MEANS IT CONVERGED (there is no pr_converged="1": the converged path is the normal one and must cost zero bytes), and absence of pr_iters= itself means the document was not ordered by a power iteration at all (a lexical query score, or a hub or authority HITS vector), never that the count is unknown **Try it** @@ -960,20 +948,20 @@ _Transitive blast radius — everything that reaches SYM. NOW carries shown/capp ``` $ ./build/ripwire . --impact=rankGraphTeleport - - - - - - - - - - - - + + + + + + + - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -981,8 +969,8 @@ $ ./build/ripwire . --impact=rankGraphTeleport **Caveats (stated by the binary):** -- show everything that reaches SYM — the transitive blast radius before a change — the indexed symbols that reach SYM (a floor, see counts_floor). -- file:name disambiguates like --callers rows run nearest first: d= is the hop depth (1 = a direct caller; +- show everything that reaches SYM — the transitive blast radius before a change transitive blast radius — the indexed symbols that reach SYM (a floor, see counts_floor). +- file:name disambiguates like --callers importers= is a SECOND, weaker reach beside it: the files that directly include/import a file defining SYM, emitted as rows (format=columnar carries the count only; - NEVER added to reaches= — files and symbols are different units, and an importer may use a different symbol from that file, or none at all. ### `--verify="CLAIM"` @@ -1010,7 +998,7 @@ $ ./build/ripwire . --verify="frobnicate(x)" **Answers:** find the markdown docs that name SYM in backticks — the doc-to-code link markdown docs (plans/designs) that name SYM in a `backtick` (doc↔code). -An @FILE:LINE seed rebinds to the innermost enclosing definition and answers, disclosing sym= (its name). unbackticked_docs=N (absent at 0): files naming SYM only outside such a span, a ceiling. +An @FILE:LINE seed rebinds to the innermost enclosing definition and answers, disclosing sym= the pre-PR family — plumbing (--affected) to mid-task report (--situ) to gate (--test-gate): **Try it** @@ -1018,9 +1006,8 @@ _Markdown docs that name SYM in a backtick (doc<->code edges)._ ``` $ ./build/ripwire . --mentions=rankGraphTeleport - - - + + @@ -1044,22 +1031,24 @@ _Test files that transitively reach the changed file._ ``` $ ./build/ripwire . --affected=src/graph.h - - - + + + + + + - - +... [5 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--exercises`, `--test-gate`, `--edit-target-file`, `--legend` +**Shaped by:** `--mentions`, `--exercises`, `--test-gate`, `--edit-target-file`, `--legend` **Caveats (stated by the binary):** @@ -1079,9 +1068,8 @@ _Which symbols a TEST FILE exercises — the reverse direction of --affected._ ``` $ ./build/ripwire . --exercises=test/regression.sh - - - ``` @@ -1106,11 +1094,11 @@ _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: f8d431396 +at: ce5c0b3fc (0 changed files — working tree is clean, nothing to analyze) ``` -**Shaped by:** `--affected`, `--test-gate`, `--legend`, `--limit` +**Shaped by:** `--mentions`, `--affected`, `--test-gate`, `--legend`, `--limit` ### `--handoff` @@ -1124,19 +1112,19 @@ _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] ``` @@ -1159,12 +1147,12 @@ _Pre-PR gate on a CLEAN tree: no obligations, exit 0._ ``` $ ./build/ripwire . --test-gate - - + untested blast radius ; exit 4 while either exists. tests_capped=/untested_capped=: 1 = cut. counts_floor=1: every count is a FLOOR, never a total. graph_ambiguous=/graph_unresolved=: resolver gauge. graph_unindexed=N: N files no grammar could read (the map header's unindexed=); their calls raise neither gauge. at=: commit+dirty+shallow. next=: the one pasteable follow-up. impacted=N: symbols that transitively call the change (changed symbols excluded). shown_tests=/shown_untested=: t rows and u rows printed, two independent counts. script_gates_unmodelled=N: test/*.sh runners in the corpus, a path count; not call-graph modelled. script_gates_registered=N: shell gates test/regression.sh registers as suite members. script_gates_mapped=N: registered gates with exact dependency evidence (literal paths or RIPWIRE_TEST_DEPS). script_gates_unresolved_dynamic=N: registered gates with no mappable deps; they may cover the change unlisted. 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). --> + ``` -**Shaped by:** `--affected`, `--quality-delta`, `--edit-target-file`, `--json`, `--limit` +**Shaped by:** `--mentions`, `--affected`, `--quality-delta`, `--edit-target-file`, `--json`, `--limit` **Caveats (stated by the binary):** @@ -1179,24 +1167,24 @@ every hit comes back with its enclosing symbol literal / regex search + enclosin **Try it** -_Regex search + enclosing symbol._ +_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._ ``` -$ ./build/ripwire . --regex='fnv1a\w+' - - - - +$ ./build/ripwire . --grep=DISCLOSE + - - +$ ./build/ripwire . --grep=DISCLOSE --grep-context=1 + - - - - - - - - +$ ./build/ripwire . --grep=DISCLOSE --and=cache + - +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: 1241 more bytes on this line] + - - - - - - - + + - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1300,21 +1288,21 @@ file requires anywhere in the same file. Second occurrence of --grep=/--regex= i _Drop every hit in a file that ALSO contains the --not literal anywhere (file scope)._ ``` -$ ./build/ripwire . --grep=DEGRADED_PATH_ALERT --not=test --grep-scope=file - - +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: 1241 more bytes on this line] + - - - - - - - + + - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1326,28 +1314,28 @@ $ ./build/ripwire . --grep=DEGRADED_PATH_ALERT --not=test --grep-scope=file **Answers:** SPAN TIERS: which tree-sitter span a hit must sit in — code (default) or any (exhaustive) SPAN TIERS: which tree-sitter span a hit must sit in to print. -code (default) serves the CODE tier when any hit is code, and otherwise comment AND string TOGETHER (tier= "comment+string"), disclosing what it held back (suppressed_comment=/suppressed_string=); a pattern living only in prose is still answered, never silently emptied. When every code hit is a use of the literal (a test/doc file, or a shell/YAML/TOML/JSON file) and source code (not one of those) holds it as a string, the string tier is served WITH code (tier="code+string"; comments stay held back). any turns tiering off entirely -- the exhaustive view. Hit files are parsed on demand under a fixed budget; tier_budget= says so when it stops, and hits it never classified are emitted, never suppressed. +code (default) serves the CODE tier when any hit is code, and otherwise comment AND string TOGETHER (tier= "comment+string"), disclosing what it held back (suppressed_comment=/suppressed_string=); a pattern living only in prose is still answered, never silently emptied. any turns tiering off entirely -- the exhaustive view. Hit files are parsed on demand under a fixed budget; tier_budget= says so when it stops, and hits it never classified are emitted, never suppressed. **Try it** _Span tiers off: the exhaustive view — the comment and string hits the default tier held back (suppressed_comment=96 / suppressed_string=29 in the plain --grep block above) now print alongside the code hits; hits= grows accordingly._ ``` -$ ./build/ripwire . --grep=DEGRADED_PATH_ALERT --grep-in=any - - - - - - - +$ ./build/ripwire . --grep=DISCLOSE --grep-in=any + - - - - +$ ./build/ripwire . --grep=DISCLOSE --handles + - + +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 @@ -1414,8 +1402,7 @@ $ ./build/ripwire . --match='(if_statement)' 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 -... [20 more line(s); run it to see the whole thing] +... [21 more line(s); run it to see the whole thing] ``` **Shaped by:** `--no-redact`, `--sarif`, `--limit` @@ -1424,7 +1411,7 @@ $ ./build/ripwire . --match='(if_statement)' **Answers:** structural search written in CODE, not in node kinds: --pattern='foo($X, ...)' structural search written in CODE, not in node kinds: --pattern='foo($X, ...)'. -$NAME binds one node (repeat it and both sites must match structurally); $_ binds nothing; ... (or $$$) is an ellipsis over siblings, matched by ONE first-match-wins probe under a hard cap -- both facts on the element. Comments are transparent, everything else is kind- and text-exact ($A + $B does not match a - b). Served: c cpp objc java csharp javascript typescript python go rust swift; ruby, bash and the data tiers are named in unsupported= instead of answered. A pattern no served grammar resolves, or that collapses to a bare token, is REFUSED -- never reported as hits=0. A qualified call (ns::foo) is no hit for a bare foo; unmatched_qualified=N counts them. +$NAME binds one node (repeat it and both sites must match structurally); $_ binds nothing; ... (or $$$) is an ellipsis over siblings, matched by ONE first-match-wins probe under a hard cap -- both facts on the element. Comments are transparent, everything else is kind- and text-exact ($A + $B does not match a - b). Served: c cpp objc java csharp javascript typescript python go rust swift; ruby, bash and the data tiers are named in unsupported= instead of answered. A pattern no served grammar resolves, or that collapses to a bare token, is REFUSED -- never reported as hits=0. **Try it** @@ -1454,19 +1441,19 @@ _Raw BM25 ranking (debug lens; --for is the real verb)._ ``` $ ./build/ripwire . --query="teleport pagerank" --top-k=5 - - - - - - - - + + + + - + - + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1488,19 +1475,18 @@ _Importance-weighted detail: FULL bodies for top-2, signatures for the rest._ ``` $ ./build/ripwire . --for="pagerank power iteration" --detail=2 - - - - - + + + + + +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 ) - -What a ranked document discloses about the power iteration that ordered it. `isPageRank == false…struct RankDisclosure - -What a rank call hands back: the vector, and the power iteration's own account of itself. Struct…struct RankedGraph - -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… -... [19 more line(s); run it to see the whole thing] + +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] + +... [20 more line(s); run it to see the whole thing] ``` **Shaped by:** `--max-tokens`, `--for`, `--signatures-only`, `--auto-bodies`, `--compress`, `--owners`, `--plan`, `--abi` @@ -1517,24 +1503,24 @@ test/showcasecapturecheck.sh (C) re-derives this range from the SAME repo every **Try it** -_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: 91.8% fewer bytes at top-10, 85.2% at top-50, 85.5% at top-100 (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._ +_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: 92.7% fewer bytes at top-10, 88.7% at top-50, 87.6% at top-100 (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._ ``` $ ./build/ripwire . --pack-signatures --top-k=10 - - - - - - - - - - - - + + + + + + + + + + - + ... [17 more line(s); run it to see the whole thing] ``` @@ -1552,7 +1538,12 @@ _Control-flow skeleton of one symbol, payload-only via the new --top-k=0._ ``` $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 -& p, float alpha = 0.85f ) + + + + +& p, float alpha = 0.85f ) { PROFILE_SCOPE_DESCRIBE( "rankGraph: PageRank (power iteration)" ); const std::vector pw = biasPrior( g, p ); @@ -1561,21 +1552,16 @@ $ ./build/ripwire . --outline=rankGraphTeleport --top-k=0 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 ); } ); -... [3 more line(s); run it to see the whole thing] +... [8 more line(s); run it to see the whole thing] ``` -**Shaped by:** `--top-k`, `--expand`, `--compress`, `--no-redact`, `--limit`, `--max-memory` +**Shaped by:** `--top-k`, `--expand`, `--compress`, `--no-redact`, `--limit` ### `--expand=A,B,...` **Answers:** print these symbols' full bodies, plus the signatures of what they call full bodies of A,B,... -Selector grammar per item (the tail after the LAST ':' decides; a tail STARTING WITH A DIGIT is a range, anything else is a name): NAME every def of that name | FILE:NAME that file's def NAME:START-END body slice | FILE:NAME:START-END selector + slice FILE:LINE:NAME paste a row's p="path:line" straight from --callers/--lint/--grep (NOT --hotspots: its p= is a BARE path — build FILE:LINE:NAME from its own p=/top_l=/top= instead, since top= is just the worst function's name) path::scope::name the id composed from a row's p= sc= n= (the map, --for, --pack-task) Scope::name the sym= spelling edit-check and grep's in= rows print — matches the name under any scope whose ::-boundary SUFFIX is Scope (Box::lid, deep::Box::lid); a wrong scope refuses, it never falls back to the bare-name union Scope.name|Scope#name the same match, dotted (Python/JS/Java) or Ruby-style; tried only when no other spelling matched (START-END is 1-based within the def's OWN body — lines="lo-hi/total" marks the slice partial; out-of-range clamps. FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. Disclosed on the root as topk_default="0" (self-describing: the change is visible without reading source). A MULTI-match name (an ambiguous bare name) or a multi-token --expand keeps the map — there IS something to disambiguate. An EXPLICIT --top-k=N (0 included) always overrides this default. Each body also carries sibs="a,b,..." sibs_total="N" [sibs_capped="1"] (the file's OTHER symbols, names only, capped at 40) and inc="x.h,..." inc_total="N" [inc_capped="1"] (the file's own #include/import targets, capped at 24) — both absent when the count is 0 (a documented zero, not a degrade), so a body no longer needs a second --outline call just to learn what else lives in its file. CHEAPEST-COMPLETE-ANSWER SERVING (no explicit --top-k, no range slice): the verb ALSO measures the (possibly map-less) bundle against the requested symbols' whole FILE(s) and emits the SMALLER, disclosed on the root as mode="bundle|whole-file" reason="the two byte counts" — on a small file the old bundle was 5.65x the file itself; on a big file the bundle saves ~26x. The whole-file form is with the file CDATA-wrapped (redacted as usual) and every requested symbol's line anchor kept. An EXPLICIT --top-k=N (including 0) opts out of BOTH the exact-name default and mode= auto-selection and keeps the classic undecorated shape; a SYM:START-END slice opts out of mode= auto-selection only (serving the whole file would invert an explicit narrowing) but still gets the exact-name top-k=0 default when it applies. When the bundle wins AND no explicit --top-k was given, the requested bodies are served BEFORE the ranked map (not buried after it), and the map's escape hatch also rides the root as note="...--top-k=0 for the payload alone..." (not stderr-only): a caller reading only stdout sees both the answer and how to drop the map on the next call. Absent whenever the bundle carries no map at all (top-k=0, exact-name default) or loses to whole-file, and whenever --top-k was explicit — the classic shape above carries no note= either. +Selector grammar per item (the tail after the LAST ':' decides; a tail STARTING WITH A DIGIT is a range, anything else is a name): NAME every def of that name | FILE:NAME that file's def NAME:START-END body slice | FILE:NAME:START-END selector + slice FILE:LINE:NAME paste a row's p="path:line" straight from --callers/--lint/--grep (NOT --hotspots: its p= is a BARE path — build FILE:LINE:NAME from its own p=/top_l=/top= instead, since top= is just the worst function's name) path::scope::name the id composed from a row's p= sc= n= (the map, --for, --pack-task) Scope::name the sym= spelling edit-check and grep's in= rows print — matches the name under any scope whose ::-boundary SUFFIX is Scope (Box::lid, deep::Box::lid); a wrong scope refuses, it never falls back to the bare-name union (START-END is 1-based within the def's OWN body — lines="lo-hi/total" marks the slice partial; out-of-range clamps. FILE matches any path substring, like --callers/--lego.) EXACT-NAME DEFAULT (one token, one unambiguous match, no explicit --top-k): the ranked map defaults to top-k=0 — you already named the exact symbol, so the ~200-row orientation map is pure overhead in front of the one body it exists to summarize. Disclosed on the root as topk_default="0" (self-describing: the change is visible without reading source). A MULTI-match name (an ambiguous bare name) or a multi-token --expand keeps the map — there IS something to disambiguate. An EXPLICIT --top-k=N (0 included) always overrides this default. Each body also carries sibs="a,b,..." sibs_total="N" [sibs_capped="1"] (the file's OTHER symbols, names only, capped at 40) and inc="x.h,..." inc_total="N" [inc_capped="1"] (the file's own #include/import targets, capped at 24) — both absent when the count is 0 (a documented zero, not a degrade), so a body no longer needs a second --outline call just to learn what else lives in its file. CHEAPEST-COMPLETE-ANSWER SERVING (no explicit --top-k, no range slice): the verb ALSO measures the (possibly map-less) bundle against the requested symbols' whole FILE(s) and emits the SMALLER, disclosed on the root as mode="bundle|whole-file" reason="the two byte counts" — on a small file the old bundle was 5.65x the file itself; on a big file the bundle saves ~26x. The whole-file form is with the file CDATA-wrapped (redacted as usual) and every requested symbol's line anchor kept. An EXPLICIT --top-k=N (including 0) opts out of BOTH the exact-name default and mode= auto-selection and keeps the classic undecorated shape; a SYM:START-END slice opts out of mode= auto-selection only (serving the whole file would invert an explicit narrowing) but still gets the exact-name top-k=0 default when it applies. When the bundle wins AND no explicit --top-k was given, the requested bodies are served BEFORE the ranked map (not buried after it), and the map's escape hatch also rides the root as note="...--top-k=0 for the payload alone..." (not stderr-only): a caller reading only stdout sees both the answer and how to drop the map on the next call. Absent whenever the bundle carries no map at all (top-k=0, exact-name default) or loses to whole-file, and whenever --top-k was explicit — the classic shape above carries no note= either. **Try it** @@ -1583,11 +1569,11 @@ _NEW since the last capture: --top-k=0 means PAYLOAD-ONLY — no ranked map ride ``` $ ./build/ripwire . --top-k=0 --expand=rankGraphTeleport - - + + +, with a BRANCH-FREE size(). Both, not one or the other. @@ -1664,18 +1653,11 @@ $ ./build/ripwire . --pack-top-n=3 --top-k=0 // parse/merge, then READ-HOT during resolve. // // Three things matter for that shape, and the layout below gets all three: -// • no per-list malloc — the N small lists that would each allocate are inline; -// • a BRANCH-FREE size() — `return sz_`, because the size lives in its own field; -// • 16 BYTES per instance — because `inl_` and `heap_` are never both live, so they share storage. ... [17 more line(s); run it to see the whole thing] ``` **Shaped by:** `--top-k`, `--token-budget`, `--for` -**Caveats (stated by the binary):** - -- pack the N top symbols' bodies [--pack-budget-bytes=B] A budget cut is stated: truncated=1 lines=1-K/T on the cut file, src_cut shown= total= capped=1. - ### `--no-redact` **Answers:** emit source and doc text verbatim, redacting nothing emit source/doc text VERBATIM, redacting nothing. @@ -1688,14 +1670,14 @@ _--no-redact: emit bodies verbatim (credential redaction is on by default)._ ``` $ ./build/ripwire . --expand=readAckRecords --top-k=0 --no-redact - - + - - - - + + + + - + + + - - - - + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1758,20 +1740,20 @@ _File->file dependency graph (god-files, cycles)._ ``` $ ./build/ripwire . --deps - - - - - + + + + + + - - - - + + + + + + - - - ... [17 more line(s); run it to see the whole thing] ``` @@ -1794,20 +1776,20 @@ _Complexity x recent git churn (maintenance pain)._ ``` $ ./build/ripwire . --hotspots - - - + - - - - - - - - - - + + + + + + + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1823,20 +1805,20 @@ _Token-normalized duplicate bodies._ ``` $ ./build/ripwire . --clones - - - - - - - + + + + + + - - - - - + + + + + + ... [17 more line(s); run it to see the whole thing] ``` @@ -1854,17 +1836,16 @@ _Per-function size ranking, largest Halstead volume/token entropy/lines first ``` $ ./build/ripwire . --biggest-first --limit=8 - - - - - - - - - - - + + + + + + + + + + ``` @@ -1888,9 +1869,8 @@ _Per function, the non-local MUTABLE state it can reach (transitively), most wri ``` $ ./build/ripwire . --nonlocal-state --limit=8 - - - @@ -1902,6 +1882,7 @@ $ ./build/ripwire . --nonlocal-state --limit=8 + ... [17 more line(s); run it to see the whole thing] ``` @@ -1923,20 +1904,20 @@ _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] ``` @@ -1954,20 +1935,20 @@ _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] ``` @@ -1991,16 +1972,15 @@ _The local-reasoning lens: to understand this symbol, how much must you know tha ``` $ ./build/ripwire . --context-ratio --limit=8 - - - - - - - - - -... [22 more line(s); run it to see the whole thing] + + + + + + + +... [23 more line(s); run it to see the whole thing] ``` **Shaped by:** `--limit` @@ -2023,21 +2003,21 @@ _Score the naming-* rules against this repo's own rename history: proxy=old/(old ``` $ ./build/ripwire . --naming-calibration - - - - + + + + - -