From 48a4f071c65c6a638f82dccd7398bc15d350bd2c Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Tue, 22 Sep 2026 06:39:15 -0400 Subject: [PATCH 01/11] =?UTF-8?q?test(slice):=20red-first=20arm=20?= =?UTF-8?q?=E2=80=94=20--slice=20rows=20emit=20in=20a=20declared=20order?= =?UTF-8?q?=20the=20root=20states?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit slicecheck (order): mix:x must emit 4,2,5 (def-use coverage desc, then line) with order="defuse" on the root, defined in both legend tiers and --help, and a flow run must keep the seed rows in that order. Red on origin/main 15a20855 (all six assertions): rows come out 2,4,5 in source order, unstated. PRE-REGISTERED (2026-09-22, written before any src/ change and before any number below was computed): Rule under test: R1 def-use coverage. A --slice=SYM:VAR row's score is the number of distinct sliceable locals with an occurrence on that line; rows emit score-descending, then line ascending, then binding line ascending (file is constant: one definition). Zero fitted parameters. Measurement: bench/slice/run_slice_linerecall.py from lane/research-arise-slice (LocBench V1 test, py single-function rows), plus one scratch arm that reads the EMITTED order: per (scored instance, inventory variable) whose v1 rows hold a gold line, Recall@{1,3,5,10,20} and MRR of the l= values in emission order against gold & rows, versus a uniform random permutation of the same rows (200 shuffles, seed 20260920, own RNG). Population: every inventory variable (unseeded); the gold-touched subset is reported beside it, never instead of it. Decision: ADOPT R1 ordering iff the new binary's emitted-order MRR beats the random control's MRR on the all-inventory population. Otherwise stop ranking: emit in source order, and state that order in the header as a presentation order, not a ranking. Co-Authored-By: Claude Opus 5 --- test/slicecheck.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/slicecheck.sh b/test/slicecheck.sh index 700656c7c..e8bb37aa6 100644 --- a/test/slicecheck.sh +++ b/test/slicecheck.sh @@ -32,6 +32,8 @@ # (13) JS/TS destructuring binders are sliceable locals whose def is the pattern line: object, # array, renamed (`y: yy`), defaulted (`z = 3`, its right side a read), rest, a destructured # parameter, `for (const { k } of xs)`, and `({ x } = o)` as an assign +# (order) VAR-mode rows emit in the declared order (def-use coverage desc, then line) and the root says +# order="defuse" — the legend (both tiers) and --help define it; a flow run's seed rows keep that order # (14) Python `global X` / `nonlocal X` row k="scope" t="global"|"nonlocal" — a scope declaration, # neither read nor write — and introduce the name in the inventory with that role # @@ -489,5 +491,35 @@ done && no "(rd-bound) the arming hook is advertised in --help — it is a gate's hook, not a user surface (G5)" \ || ok "(rd-bound) the arming hook appears in no --help text (G5)" +# ── (order) --slice=SYM:VAR rows emit in the DECLARED order, and the root states it ───────────────────────────── +# Measured (LocBench py, 478 (instance, variable) pairs): source order pinpoints a gold line WORSE than a random +# shuffle of the same rows (MRR 0.525 vs 0.602), def-use coverage beats it (0.628). So the rows rank by coverage — +# how many distinct sliceable locals share the line — descending, then line, then binding line, and the root says +# order="defuse" so the reader never mistakes a ranking for source order (or the reverse). Fixture: l=4 names FOUR +# locals, l=2 and l=5 name one each; source order is 2,4,5, the declared order is 4,2,5. A flow run keeps the same +# seed rows in the same order (the flow rows keep their own stated (d=, l=, v=) order). +ORD="$WORK/order"; mkdir -p "$ORD" +printf 'def mix(a):\n x = 1\n y = 2\n z = x + y + a\n return x\n' >"$ORD/m.py" +"$BIN" "$ORD" --slice=mix:x --no-cache >"$ORD/o.xml" 2>/dev/null +"$BIN" "$ORD" --slice=mix:x --slice-flow=back --no-cache >"$ORD/f.xml" 2>/dev/null +OROOT="$( grep -o ']*>' "$ORD/o.xml" )" +OLINES="$( grep -o ']*-->' "$ORD/o.xml" | grep -q 'order=defuse\|order="defuse"' \ + && ok "(order) order= is defined in the same document's legend" || no "(order) order= rides with no legend definition" +"$BIN" "$ORD" --slice=mix:x --legend=compact --no-cache 2>/dev/null | grep -o '' | grep -q 'order=defuse' \ + && ok "(order) the compact legend defines order= too" || no "(order) the compact legend does not define order=" +[ "$FLINES" = "4,2,5," ] \ + && ok "(order) a flow run's seed rows keep the same declared order" \ + || no "(order) a flow run's seed rows emit '$FLINES', expected 4,2,5" +"$BIN" --help=all 2>&1 | grep -q 'order="defuse"' \ + && ok "(order) --help documents order=\"defuse\"" || no "(order) --help does not document order=\"defuse\"" + [ "$fail" = 0 ] && printf 'ALL PASS\n' || printf 'FAILURES ABOVE\n' exit "$fail" From 113853402cf3d1e418f755ff085364121c0beab5 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Tue, 22 Sep 2026 06:47:24 -0400 Subject: [PATCH 02/11] fix(slice): --slice=SYM:VAR rows rank by def-use coverage, and the root says so (order="defuse") MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The seed rows came out in source order with nothing on the root naming it. Measured on the lane/research-arise-slice harness (LocBench V1 test, py single-function rows, 173 scored instances, 478 instance x variable pairs holding a gold line), source order pinpoints a gold line WORSE than a uniform shuffle of the same rows: MRR 0.525 vs 0.602, @1 0.198 vs 0.268. Rows now emit by def-use coverage (distinct sliceable-local NAMES with an occurrence on the line — unbound identifiers such as a called builtin do not count), descending, then line, then binding line, then fold index: a total order, zero fitted parameters. Emitted order after: MRR 0.628, @1 0.285, @3 0.743, @5 0.847 (random 0.602 / 0.268 / 0.676 / 0.827); @10/@20 tie the shuffle (0.937 vs 0.939, 0.983 vs 0.983). The pre-registered rule (first commit of this lane: adopt iff emitted-order MRR beats the random control) is met. In-sample, legitimately: nothing was fitted. The emitted order equals the harness's own R1 over the same rows on 478/478 pairs. The root carries order="defuse" in VAR mode (flow runs too: the seed rows keep this order, the flow rows keep their stated (d=, l=, v=) order); the full legend, the compact slice legend, the default compact layer's slice readings and --help define it. The inventory () order is unchanged. Co-Authored-By: Claude Opus 5 --- src/cli.h | 4 ++- src/compactlegend.h | 1 + src/slice.h | 78 ++++++++++++++++++++++++++++++++++++++++++--- test/slicecheck.sh | 7 ++-- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/cli.h b/src/cli.h index 327a61c55..c263a58e0 100644 --- a/src/cli.h +++ b/src/cli.h @@ -1817,7 +1817,9 @@ inline constexpr char kHelpHead[] = " --slice=SYM[:VAR] trace one variable's definitions and uses inside one function\n" " NAME-BASED intra-procedural def-use slice of variable VAR inside the ONE uniquely-resolved\n" " definition SYM (statement-level def-use edges as a queryable primitive — the ARISE result,\n" - " arXiv:2605.03117). One row per line touching VAR, source order: k=def|use|both|\n" + " arXiv:2605.03117). One row per line touching VAR, ranked as the root's order=\"defuse\"\n" + " states: def-use coverage (distinct sliceable locals on the line) descending, then line —\n" + " source order ranked a fix's lines below a random shuffle, this above it. k=def|use|both|\n" " scope = a Python global/nonlocal statement, neither read nor write; t=param|decl|\n" " assign|call-arg|read|global|nonlocal = the strongest role on the line; CDATA = the trimmed source\n" " line; defs=/uses= count occurrences. JS/TS destructuring binders (`const {a, b} = o`,\n" diff --git a/src/compactlegend.h b/src/compactlegend.h index d2576f0f7..d294b1e0d 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -1017,6 +1017,7 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = // 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" }, + { "order", "order=defuse: s rows ranked by def-use coverage (distinct sliceable locals on the line) desc, then line; not source order", false, "slice", MapHeaderRead::No, {}, "slice" }, // slice.h sliceDefUseRowOrder { "counts", "counts=as-classified: defs=/uses=/vars=/steps= count what the name classifier rowed; neither floors nor totals", false, "slice", MapHeaderRead::No, {}, "slice" }, // flags: src/darkflags.h (the root emit) { "gates", "gates=/dark_gates=: gate rows (never cut) / those whose default keeps the guarded code out of the build", false, "flags", MapHeaderRead::No, {}, "flags" }, // also defines dark_gates= diff --git a/src/slice.h b/src/slice.h index cf85f7a7c..a0020e01c 100644 --- a/src/slice.h +++ b/src/slice.h @@ -3055,7 +3055,9 @@ inline std::string sliceLegendText( const SliceEmitOpts& opts ) "counts=as-classified — defs/uses/vars/steps count what the classifier rowed, neither floors nor totals. " ": k=def|use|both|scope, t=param|decl|assign|call-arg|read|global|nonlocal, b=declaration line a " "shadowed name binds to (0=unbound), pp=1 build-dependent preprocessor region, rd=lines of the defs reaching a use row " - "(-=none) per reach=cfg (flow-sensitive; C-family, Python) | linear (source order; JS/TS, Go, Java, Rust). Inventory " + "(-=none) per reach=cfg (flow-sensitive; C-family, Python) | linear (source order; JS/TS, Go, Java, Rust). " + "order=defuse: seed rows ranked by def-use coverage (distinct sliceable locals on the line) desc, then line — not " + "source order. Inventory " ", vars=count. " "bindings=shadow count; preproc_rows=lines dropped under #if 0; seed/var_from/seed_vars/seed=1 = line-seed disclosure. " "Flow rows add v=variable d=depth f=from-line; steps=flow rows, depth=bound, flow_truncated=1 bounded not complete, " @@ -3069,7 +3071,10 @@ inline std::string sliceLegendText( const SliceEmitOpts& opts ) { out = "' "$ORD/o.xml" | grep -q 'order=defuse\|order="defuse"' \ +# the legend is everything before the root (a legend spells rows, so a [^>]* comment match would stop short) +grep -q 'order="defuse"' "$ORD/o.xml" && sed 's//dev/null | grep -o '' | grep -q 'order=defuse' \ +"$BIN" "$ORD" --slice=mix:x --legend=compact --no-cache 2>/dev/null | sed 's//dev/null | sed 's/ Date: Tue, 22 Sep 2026 07:02:32 -0400 Subject: [PATCH 03/11] fix(slice): fit the order= legend clause to its budget, and re-derive what the new reading moved - The full legend's order="defuse" clause is cut to one line: sliceflowcheck (30b) holds the v1 legend to 4,608 B and the longer clause took it to 4,808 B. - sliceflowsenscheck's sentinel re-derives the SOURCE-order reaching rule from the tool's own rows; it walked them in emission order, which is no longer line order. It now sorts by line first. Every rd= value is still checked row by row, unchanged. - The new compact reading adds one dictionary entry: the showcase capture's recorded --legend-dict figure is corrected in place (dictv=fd7a59c7c536eb85 entries=704, 68,575 B on 705 lines), and docs/COMMANDS.md is regenerated from it and from the new --help. - printf_parity.manifest re-pins help_all: the --slice help entry moved on purpose. Co-Authored-By: Claude Opus 5 --- docs/COMMANDS.md | 4 ++-- docs/captures/COMMANDS_showcase_2026-09-14.md | 4 ++-- src/slice.h | 8 +++----- test/printf_parity.manifest | 2 +- test/sliceflowsenscheck.sh | 2 +- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 8e491b610..5fc99a8e9 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -3243,7 +3243,7 @@ $ ./build/ripwire . --safe-delete=DoesNotExist **Answers:** trace one variable's definitions and uses inside one function NAME-BASED intra-procedural def-use slice of variable VAR inside the ONE uniquely-resolved definition SYM (statement-level def-use edges as a queryable primitive — the ARISE result, arXiv:2605.03117). -One row per line touching VAR, source order: k=def|use|both| scope = a Python global/nonlocal statement, neither read nor write; t=param|decl| assign|call-arg|read|global|nonlocal = the strongest role on the line; CDATA = the trimmed source line; defs=/uses= count occurrences. JS/TS destructuring binders (`const {a, b} = o`, `[x] = arr`, destructured parameters) are locals whose def is the pattern line. A write hidden behind a call — receiver mutation, a by-reference/out-parameter, a function-like macro — is a use, never a def (stated in the legend). Bare --slice=SYM lists the sliceable locals ( rows) so a caller can pick VAR. LIMITS in the legend, not implied: no alias analysis. REACHING DEFINITIONS are FLOW-SENSITIVE inside the definition for C-family and Python (root reach="cfg": a def is killed by the next unconditional def on every path, defs join at if/elif/else, switch, loop back-edge, try/finally, for/while-else, match, #ifdef merges) and source-order for JS/TS/Go/Java/Rust (reach="linear", nothing joins); every use row carries rd= (the lines of the defs that reach it, "-" = none). The unit is the STATEMENT (uses read the entering state, defs apply after); a nested lambda/def body, ?:, short-circuit fold into their statement, goto is untracked, global/nonlocal is tracked like a local — each disclosed in the legend. Block scopes ARE separated: a name declared twice in the definition is two variables, each row of a shadowed name carries b= (the declaration line it binds to), the root bindings=, the inventory one per binding. SYM matching several definition sites REFUSES (exit 1) listing the file:name spellings that pick one, like --edit-check. Served: C/C++/ObjC (+CUDA/Metal), Python, JS/TS, Go, Java, Rust — other indexed languages refuse loudly (never an empty success). Single-root only. PREPROCESSOR (C-family): a `#if 0` body and the `#else` of `#if 1` are DEAD — dropped, the line count disclosed as preproc_rows=; every other conditional region (`#ifdef X`, `#ifndef X`, `#if defined(X)`, `#if EXPR`) is build-dependent and cannot be decided without the build's macro set, so its rows are KEPT and flagged pp="1", and a pp def never hides the unconditional def before it in a flow (both are reaching). LINE-SEEDED: --at=FILE:LINE beside --slice (or --slice=@FILE:LINE) is the ARISE (file, line[, variable]) seed — the definition sliced is the innermost one enclosing the line (a seed narrows an otherwise-ambiguous SYM; a seed enclosed by none of SYM's definitions refuses naming both). A seed line naming exactly ONE sliceable local pre-picks it (disclosed: seed= var_from="seed"); zero or several serve the inventory with seed_vars= and the candidate rows marked seed="1", never a guess. A plain identifier spec beside --at reads as the seed's VARIABLE (--slice=VAR --at=src/f.cpp:12). SINCE: --since=REV|DATE beside --slice=SYM:VAR adds a child carrying the DEPENDENCE diff of that variable against the committed tree at REV — one row per added or removed STATEMENT of the variable, one row per added or removed def-use edge. The unit is the STATEMENT and the key is the ROLE, never the line and never the text, so a re-wrap, a comment edit, an insertion above the definition, and a rename of an unrelated local all come back EMPTY. Empty means no def-use edge of that variable moved, never that the commit changed nothing — git diff answers the second question. status= names each way the symbol can be absent at REV, and comparable="0" says outright that no comparison was made and the emptiness is not evidence. Refused on the bare inventory: a dependence diff needs a seed variable. +One row per line touching VAR, ranked as the root's order="defuse" states: def-use coverage (distinct sliceable locals on the line) descending, then line — source order ranked a fix's lines below a random shuffle, this above it. k=def|use|both| scope = a Python global/nonlocal statement, neither read nor write; t=param|decl| assign|call-arg|read|global|nonlocal = the strongest role on the line; CDATA = the trimmed source line; defs=/uses= count occurrences. JS/TS destructuring binders (`const {a, b} = o`, `[x] = arr`, destructured parameters) are locals whose def is the pattern line. A write hidden behind a call — receiver mutation, a by-reference/out-parameter, a function-like macro — is a use, never a def (stated in the legend). Bare --slice=SYM lists the sliceable locals ( rows) so a caller can pick VAR. LIMITS in the legend, not implied: no alias analysis. REACHING DEFINITIONS are FLOW-SENSITIVE inside the definition for C-family and Python (root reach="cfg": a def is killed by the next unconditional def on every path, defs join at if/elif/else, switch, loop back-edge, try/finally, for/while-else, match, #ifdef merges) and source-order for JS/TS/Go/Java/Rust (reach="linear", nothing joins); every use row carries rd= (the lines of the defs that reach it, "-" = none). The unit is the STATEMENT (uses read the entering state, defs apply after); a nested lambda/def body, ?:, short-circuit fold into their statement, goto is untracked, global/nonlocal is tracked like a local — each disclosed in the legend. Block scopes ARE separated: a name declared twice in the definition is two variables, each row of a shadowed name carries b= (the declaration line it binds to), the root bindings=, the inventory one per binding. SYM matching several definition sites REFUSES (exit 1) listing the file:name spellings that pick one, like --edit-check. Served: C/C++/ObjC (+CUDA/Metal), Python, JS/TS, Go, Java, Rust — other indexed languages refuse loudly (never an empty success). Single-root only. PREPROCESSOR (C-family): a `#if 0` body and the `#else` of `#if 1` are DEAD — dropped, the line count disclosed as preproc_rows=; every other conditional region (`#ifdef X`, `#ifndef X`, `#if defined(X)`, `#if EXPR`) is build-dependent and cannot be decided without the build's macro set, so its rows are KEPT and flagged pp="1", and a pp def never hides the unconditional def before it in a flow (both are reaching). LINE-SEEDED: --at=FILE:LINE beside --slice (or --slice=@FILE:LINE) is the ARISE (file, line[, variable]) seed — the definition sliced is the innermost one enclosing the line (a seed narrows an otherwise-ambiguous SYM; a seed enclosed by none of SYM's definitions refuses naming both). A seed line naming exactly ONE sliceable local pre-picks it (disclosed: seed= var_from="seed"); zero or several serve the inventory with seed_vars= and the candidate rows marked seed="1", never a guess. A plain identifier spec beside --at reads as the seed's VARIABLE (--slice=VAR --at=src/f.cpp:12). SINCE: --since=REV|DATE beside --slice=SYM:VAR adds a child carrying the DEPENDENCE diff of that variable against the committed tree at REV — one row per added or removed STATEMENT of the variable, one row per added or removed def-use edge. The unit is the STATEMENT and the key is the ROLE, never the line and never the text, so a re-wrap, a comment edit, an insertion above the definition, and a rename of an unrelated local all come back EMPTY. Empty means no def-use edge of that variable moved, never that the commit changed nothing — git diff answers the second question. status= names each way the symbol can be absent at REV, and comparable="0" says outright that no comparison was made and the emptiness is not evidence. Refused on the bare inventory: a dependence diff needs a seed variable. **Try it** @@ -4386,7 +4386,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=de80b7c7b3ea84b5 entries=703 +ripwire legend dictionary ripwire.dict/v1 dictv=fd7a59c7c536eb85 entries=704 : 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=) diff --git a/docs/captures/COMMANDS_showcase_2026-09-14.md b/docs/captures/COMMANDS_showcase_2026-09-14.md index 599301eb4..861a5c8f4 100644 --- a/docs/captures/COMMANDS_showcase_2026-09-14.md +++ b/docs/captures/COMMANDS_showcase_2026-09-14.md @@ -5351,7 +5351,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=de80b7c7b3ea84b5 entries=703 +ripwire legend dictionary ripwire.dict/v1 dictv=fd7a59c7c536eb85 entries=704 : 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=) @@ -5381,7 +5381,7 @@ ripwire.impact/v1 : transitive blast radius of of=: reach s 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, ripwire.at/v1 : enclosing-definition chain at p=:l=: sym= innermost, chain= outermost-first, spans -… [674 more display lines; full output is 68453 bytes on 704 raw line(s)] +… [675 more display lines; full output is 68575 bytes on 705 raw line(s)] ````` ## `./build/ripwire . --lint --lint-select=cache-` diff --git a/src/slice.h b/src/slice.h index a0020e01c..bb26d49e3 100644 --- a/src/slice.h +++ b/src/slice.h @@ -6,7 +6,7 @@ // MOTIVATION. ARISE (arXiv:2605.03117) measured statement-level definition-use edges exposed as a // queryable agent primitive at +17pp Function Recall@1 on SWE-bench Lite. ripwire's graph stops at // symbol granularity; this is the bounded v1 of that primitive: one definition, one variable, its -// def/use statement rows in source order. +// def/use statement rows, emitted in the order="defuse" ranking (sliceDefUseRowOrder). // // HONESTY CONTRACT (all four limits are stated in the emitted legend, never implied): // • NAME-BASED — occurrences are identifier-name matches inside the definition's span. No alias @@ -3071,10 +3071,8 @@ inline std::string sliceLegendText( const SliceEmitOpts& opts ) { out = " sites) and found no other occurrence. Added slicecheck.sh arm (10d): --legend=full well-formedness for plain and --slice-flow=both, proven red against both this lane's pre-fix binary and the reviewer's own 15a20855 build. Re-derived: sliceflowcheck (30b) legend budget 4600/4608 (v1) and 1388/1400 (flow addendum, more headroom than before); printf_parity.manifest re-pins help_all (only label moved, UPDATE_GOLDEN_EXPECT confirmed); docs/COMMANDS.md regenerated; the showcase capture's --legend-dict figure corrected in place to the new dictv (entries unchanged at 704), the same in-place-correction shape c02f8f2d already used for this exact line. Co-Authored-By: Claude Sonnet 5 --- docs/COMMANDS.md | 2 +- docs/EVALS.md | 78 +++++++++++++++++++ docs/captures/COMMANDS_showcase_2026-09-14.md | 2 +- src/cli.h | 4 +- src/compactlegend.h | 2 +- src/slice.h | 18 +++-- test/printf_parity.manifest | 2 +- test/slicecheck.sh | 14 +++- 8 files changed, 107 insertions(+), 15 deletions(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 5fc99a8e9..1fa8b8a38 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -3243,7 +3243,7 @@ $ ./build/ripwire . --safe-delete=DoesNotExist **Answers:** trace one variable's definitions and uses inside one function NAME-BASED intra-procedural def-use slice of variable VAR inside the ONE uniquely-resolved definition SYM (statement-level def-use edges as a queryable primitive — the ARISE result, arXiv:2605.03117). -One row per line touching VAR, ranked as the root's order="defuse" states: def-use coverage (distinct sliceable locals on the line) descending, then line — source order ranked a fix's lines below a random shuffle, this above it. k=def|use|both| scope = a Python global/nonlocal statement, neither read nor write; t=param|decl| assign|call-arg|read|global|nonlocal = the strongest role on the line; CDATA = the trimmed source line; defs=/uses= count occurrences. JS/TS destructuring binders (`const {a, b} = o`, `[x] = arr`, destructured parameters) are locals whose def is the pattern line. A write hidden behind a call — receiver mutation, a by-reference/out-parameter, a function-like macro — is a use, never a def (stated in the legend). Bare --slice=SYM lists the sliceable locals ( rows) so a caller can pick VAR. LIMITS in the legend, not implied: no alias analysis. REACHING DEFINITIONS are FLOW-SENSITIVE inside the definition for C-family and Python (root reach="cfg": a def is killed by the next unconditional def on every path, defs join at if/elif/else, switch, loop back-edge, try/finally, for/while-else, match, #ifdef merges) and source-order for JS/TS/Go/Java/Rust (reach="linear", nothing joins); every use row carries rd= (the lines of the defs that reach it, "-" = none). The unit is the STATEMENT (uses read the entering state, defs apply after); a nested lambda/def body, ?:, short-circuit fold into their statement, goto is untracked, global/nonlocal is tracked like a local — each disclosed in the legend. Block scopes ARE separated: a name declared twice in the definition is two variables, each row of a shadowed name carries b= (the declaration line it binds to), the root bindings=, the inventory one per binding. SYM matching several definition sites REFUSES (exit 1) listing the file:name spellings that pick one, like --edit-check. Served: C/C++/ObjC (+CUDA/Metal), Python, JS/TS, Go, Java, Rust — other indexed languages refuse loudly (never an empty success). Single-root only. PREPROCESSOR (C-family): a `#if 0` body and the `#else` of `#if 1` are DEAD — dropped, the line count disclosed as preproc_rows=; every other conditional region (`#ifdef X`, `#ifndef X`, `#if defined(X)`, `#if EXPR`) is build-dependent and cannot be decided without the build's macro set, so its rows are KEPT and flagged pp="1", and a pp def never hides the unconditional def before it in a flow (both are reaching). LINE-SEEDED: --at=FILE:LINE beside --slice (or --slice=@FILE:LINE) is the ARISE (file, line[, variable]) seed — the definition sliced is the innermost one enclosing the line (a seed narrows an otherwise-ambiguous SYM; a seed enclosed by none of SYM's definitions refuses naming both). A seed line naming exactly ONE sliceable local pre-picks it (disclosed: seed= var_from="seed"); zero or several serve the inventory with seed_vars= and the candidate rows marked seed="1", never a guess. A plain identifier spec beside --at reads as the seed's VARIABLE (--slice=VAR --at=src/f.cpp:12). SINCE: --since=REV|DATE beside --slice=SYM:VAR adds a child carrying the DEPENDENCE diff of that variable against the committed tree at REV — one row per added or removed STATEMENT of the variable, one row per added or removed def-use edge. The unit is the STATEMENT and the key is the ROLE, never the line and never the text, so a re-wrap, a comment edit, an insertion above the definition, and a rename of an unrelated local all come back EMPTY. Empty means no def-use edge of that variable moved, never that the commit changed nothing — git diff answers the second question. status= names each way the symbol can be absent at REV, and comparable="0" says outright that no comparison was made and the emptiness is not evidence. Refused on the bare inventory: a dependence diff needs a seed variable. +One row per line touching VAR, ranked as the root's order="defuse" states: def-use coverage (distinct local names on the line) descending, then line — measured (docs/EVALS.md): puts a gold line first more often than a random shuffle. k=def|use|both| scope = a Python global/nonlocal statement, neither read nor write; t=param|decl| assign|call-arg|read|global|nonlocal = the strongest role on the line; CDATA = the trimmed source line; defs=/uses= count occurrences. JS/TS destructuring binders (`const {a, b} = o`, `[x] = arr`, destructured parameters) are locals whose def is the pattern line. A write hidden behind a call — receiver mutation, a by-reference/out-parameter, a function-like macro — is a use, never a def (stated in the legend). Bare --slice=SYM lists the sliceable locals ( rows) so a caller can pick VAR. LIMITS in the legend, not implied: no alias analysis. REACHING DEFINITIONS are FLOW-SENSITIVE inside the definition for C-family and Python (root reach="cfg": a def is killed by the next unconditional def on every path, defs join at if/elif/else, switch, loop back-edge, try/finally, for/while-else, match, #ifdef merges) and source-order for JS/TS/Go/Java/Rust (reach="linear", nothing joins); every use row carries rd= (the lines of the defs that reach it, "-" = none). The unit is the STATEMENT (uses read the entering state, defs apply after); a nested lambda/def body, ?:, short-circuit fold into their statement, goto is untracked, global/nonlocal is tracked like a local — each disclosed in the legend. Block scopes ARE separated: a name declared twice in the definition is two variables, each row of a shadowed name carries b= (the declaration line it binds to), the root bindings=, the inventory one per binding. SYM matching several definition sites REFUSES (exit 1) listing the file:name spellings that pick one, like --edit-check. Served: C/C++/ObjC (+CUDA/Metal), Python, JS/TS, Go, Java, Rust — other indexed languages refuse loudly (never an empty success). Single-root only. PREPROCESSOR (C-family): a `#if 0` body and the `#else` of `#if 1` are DEAD — dropped, the line count disclosed as preproc_rows=; every other conditional region (`#ifdef X`, `#ifndef X`, `#if defined(X)`, `#if EXPR`) is build-dependent and cannot be decided without the build's macro set, so its rows are KEPT and flagged pp="1", and a pp def never hides the unconditional def before it in a flow (both are reaching). LINE-SEEDED: --at=FILE:LINE beside --slice (or --slice=@FILE:LINE) is the ARISE (file, line[, variable]) seed — the definition sliced is the innermost one enclosing the line (a seed narrows an otherwise-ambiguous SYM; a seed enclosed by none of SYM's definitions refuses naming both). A seed line naming exactly ONE sliceable local pre-picks it (disclosed: seed= var_from="seed"); zero or several serve the inventory with seed_vars= and the candidate rows marked seed="1", never a guess. A plain identifier spec beside --at reads as the seed's VARIABLE (--slice=VAR --at=src/f.cpp:12). SINCE: --since=REV|DATE beside --slice=SYM:VAR adds a child carrying the DEPENDENCE diff of that variable against the committed tree at REV — one row per added or removed STATEMENT of the variable, one row per added or removed def-use edge. The unit is the STATEMENT and the key is the ROLE, never the line and never the text, so a re-wrap, a comment edit, an insertion above the definition, and a rename of an unrelated local all come back EMPTY. Empty means no def-use edge of that variable moved, never that the commit changed nothing — git diff answers the second question. status= names each way the symbol can be absent at REV, and comparable="0" says outright that no comparison was made and the emptiness is not evidence. Refused on the bare inventory: a dependence diff needs a seed variable. **Try it** diff --git a/docs/EVALS.md b/docs/EVALS.md index 97eff9a02..9afec5047 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -13868,3 +13868,81 @@ The detected root comes from the first transcript whose first line names it, whi scans until one matches instead of reading only the first file: 113 of the 628 do not name the absolute root on their first line — that is the complement of the 515 above, and it is not a claim that those 113 print no banner, only that the root is not in it. + +## `--slice=SYM:VAR` def-use row order — PRE-REGISTERED 2026-09-22 (before any src/ change and before any number below was computed) + +`--slice=SYM:VAR` seed rows emitted in SOURCE order (the file's own line order), unstated on the root. The +red-first gate arm (commit `48a4f071`) registered a rule and a decision procedure before any implementation +code or any number existed: + +> **Rule under test: R1 def-use coverage.** A `--slice=SYM:VAR` row's score is the number of distinct +> sliceable locals with an occurrence on that line; rows emit score-descending, then line ascending, then +> binding line ascending (file is constant: one definition). Zero fitted parameters. +> +> **Measurement:** `bench/slice/run_slice_linerecall.py` from `lane/research-arise-slice` (LocBench V1 test, +> Python single-function rows), plus one scratch arm that reads the EMITTED order: per (scored instance, +> inventory variable) whose v1 rows hold a gold line, Recall@{1,3,5,10,20} and MRR of the `l=` values in +> emission order against gold & rows, versus a uniform random permutation of the same rows (200 shuffles, +> seed 20260920, own RNG). Population: every inventory variable (unseeded); the gold-touched subset is +> reported beside it, never instead of it. +> +> **Decision:** ADOPT R1 ordering iff the new binary's emitted-order MRR beats the random control's MRR on +> the all-inventory population. Otherwise stop ranking: emit in source order, and state that order in the +> header as a presentation order, not a ranking. + +Neither the harness (`bench/slice/run_slice_linerecall.py`) nor the scratch arm that reads emission order is +committed to this tree — both live on the unmerged `lane/research-arise-slice`, and the scratch arm is a +43-line diff over that harness. The numbers below are reproduced from that harness's output and from an +independent re-run against both binaries (base `15a20855` and this lane), not from a script this tree ships; +`docs/research/slice-line-recall.md`, cited by an earlier draft of this feature, does not exist on `main` or +on this lane and is not the record of this claim — this section is. + +## `--slice=SYM:VAR` def-use row order — MEASURED 2026-09-22 against the band above: **ADOPTED — the emitted-order MRR beats the random control** + +**Population, stated precisely (correcting the scratch arm's own label):** 478 (scored instance, inventory +variable) pairs, from 173 LocBench V1 Python instances, **whose v1 rows hold a gold line** — not "every +inventory variable, unseeded" as the scratch arm's `all_inventory` label implied. A pair with no gold line +among its rows scores 0 under every candidate order, so the ADOPT/DON'T-ADOPT decision is unaffected either +way; only the population's name was overstated. + +| arm | @1 | @3 | @5 | @10 | @20 | MRR | +| --- | --- | --- | --- | --- | --- | --- | +| source order (base `15a20855`, the "before") | 0.198 | — | — | — | — | 0.525 | +| random control (200 shuffles, seed 20260920, sequential stream) | 0.268 | 0.676 | 0.827 | 0.939 | 0.983 | 0.602 | +| def-use coverage (this lane, emitted order, the "after") | 0.285 | 0.743 | 0.847 | 0.937 | 0.983 | 0.628 | + +@3/@5/@10/@20 were not separately reported for source order at registration time — only MRR and @1 were +measured for that arm; the table states that gap rather than filling it in. + +The registered decision is the MRR row: **0.628 > 0.602 > 0.525** — def-use coverage beats the random +control, which beats source order. ADOPTED per the pre-registered rule. + +**The control's spread, so the margin has a scale.** Across the control's own 200 shuffles, the mean MRR's +standard deviation is 0.0117 (the registered sequential RNG stream) / 0.0116 (an independent per-pair RNG, +cross-check only); the emitted-order MRR exceeds 199 of those 200 shuffle means (197/200 under the +independent RNG) — roughly +2.2σ. A paired bootstrap of (emitted MRR − that pair's own control mean) over +the 478 pairs gives Δ = +0.026, 95% CI [0.004, 0.049] — excludes zero, narrowly. + +**The gain is concentrated at @1/@3, honestly split per pair.** Per pair against its own control mean: +better on 182, WORSE on 227, tied on 69 — def-use coverage loses the per-pair comparison more often than it +wins. The population-level win lives in where gold lands when the rule is right: @1/@3 favor def-use +clearly, @10/@20 tie the shuffle (0.937 vs 0.939, 0.983 vs 0.983 — the shuffle is marginally ahead at @10). +On average 43% of a pair's rows share the top def-use-coverage score, so line order (the tiebreak) still +does real work inside that band. + +**In-sample, honestly.** Zero fitted parameters — the rule is a fixed count-and-sort, not tuned against this +population — but the population itself is the LocBench V1 Python set the rule was measured on; there is no +held-out split. **Python only.** The coverage count follows the `--slice` inventory exactly (as registered), +and that inventory is uneven across languages the tool serves — Python lambda/JS arrow params are not +inventory locals while Python nested-def/Rust closure params are, Python `self` counts as a param local +while Rust `self` does not, Java/Python attribute identifiers share a name with same-spelled locals while +C++/JS field/property identifiers do not. None of this was measured for C/C++, JS/TS, Go, Java, C#, Ruby, +Rust, or Swift; the `order="defuse"` ranking ships for every served language on the strength of the Python +measurement alone, stated here rather than left implicit. + +**Falsifiable claim, restated to match what the table shows:** *"Ranking `--slice=SYM:VAR` rows by def-use +coverage puts a gold line first (rank 1) more often than a random shuffle of the same rows, on LocBench V1 +Python."* That is an @1 claim (0.285 vs 0.268), not a claim that the rule outranks a shuffle on every pair +or at every depth — @10/@20 tie, and the per-pair split has more losses than wins. `--help` and +`src/slice.h`'s `sliceDefUseRowOrder` comment are worded to this claim, not to the broader "ranks above it" +a first draft of this feature shipped. diff --git a/docs/captures/COMMANDS_showcase_2026-09-14.md b/docs/captures/COMMANDS_showcase_2026-09-14.md index 861a5c8f4..564af1a71 100644 --- a/docs/captures/COMMANDS_showcase_2026-09-14.md +++ b/docs/captures/COMMANDS_showcase_2026-09-14.md @@ -5351,7 +5351,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=fd7a59c7c536eb85 entries=704 +ripwire legend dictionary ripwire.dict/v1 dictv=2d9cec7852fd809b entries=704 : 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=) diff --git a/src/cli.h b/src/cli.h index c263a58e0..45c5afc86 100644 --- a/src/cli.h +++ b/src/cli.h @@ -1818,8 +1818,8 @@ inline constexpr char kHelpHead[] = " NAME-BASED intra-procedural def-use slice of variable VAR inside the ONE uniquely-resolved\n" " definition SYM (statement-level def-use edges as a queryable primitive — the ARISE result,\n" " arXiv:2605.03117). One row per line touching VAR, ranked as the root's order=\"defuse\"\n" - " states: def-use coverage (distinct sliceable locals on the line) descending, then line —\n" - " source order ranked a fix's lines below a random shuffle, this above it. k=def|use|both|\n" + " states: def-use coverage (distinct local names on the line) descending, then line — measured\n" + " (docs/EVALS.md): puts a gold line first more often than a random shuffle. k=def|use|both|\n" " scope = a Python global/nonlocal statement, neither read nor write; t=param|decl|\n" " assign|call-arg|read|global|nonlocal = the strongest role on the line; CDATA = the trimmed source\n" " line; defs=/uses= count occurrences. JS/TS destructuring binders (`const {a, b} = o`,\n" diff --git a/src/compactlegend.h b/src/compactlegend.h index d294b1e0d..235d0c3d3 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -1017,7 +1017,7 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = // 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" }, - { "order", "order=defuse: s rows ranked by def-use coverage (distinct sliceable locals on the line) desc, then line; not source order", false, "slice", MapHeaderRead::No, {}, "slice" }, // slice.h sliceDefUseRowOrder + { "order", "order=defuse: seed s rows (no v=) ranked by def-use coverage (distinct local names on the line) desc, then line; not source order — flow s rows (v=) keep their (d=,l=,v=) order", false, "slice", MapHeaderRead::No, {}, "slice" }, // slice.h sliceDefUseRowOrder { "counts", "counts=as-classified: defs=/uses=/vars=/steps= count what the name classifier rowed; neither floors nor totals", false, "slice", MapHeaderRead::No, {}, "slice" }, // flags: src/darkflags.h (the root emit) { "gates", "gates=/dark_gates=: gate rows (never cut) / those whose default keeps the guarded code out of the build", false, "flags", MapHeaderRead::No, {}, "flags" }, // also defines dark_gates= diff --git a/src/slice.h b/src/slice.h index bb26d49e3..53f211ab9 100644 --- a/src/slice.h +++ b/src/slice.h @@ -3056,12 +3056,12 @@ inline std::string sliceLegendText( const SliceEmitOpts& opts ) ": k=def|use|both|scope, t=param|decl|assign|call-arg|read|global|nonlocal, b=declaration line a " "shadowed name binds to (0=unbound), pp=1 build-dependent preprocessor region, rd=lines of the defs reaching a use row " "(-=none) per reach=cfg (flow-sensitive; C-family, Python) | linear (source order; JS/TS, Go, Java, Rust). " - "order=defuse: seed rows ranked by def-use coverage (distinct sliceable locals on the line) desc, then line — not " + "order=defuse: seed rows ranked by def-use coverage (distinct local names on the line) desc, then line — not " "source order. Inventory " ", vars=count. " "bindings=shadow count; preproc_rows=lines dropped under #if 0; seed/var_from/seed_vars/seed=1 = line-seed disclosure. " "Flow rows add v=variable d=depth f=from-line; steps=flow rows, depth=bound, flow_truncated=1 bounded not complete, " - "flow_redundant=1 (both=, unseeded only) reaches no line the flat inventory does not — seed via --at=FILE:LINE for real reach. " + "flow_redundant=1 (both=, unseeded only) reaches no line the flat inventory does not — seed via at=FILE:LINE for real reach. " "Limits: a write hidden behind a call (receiver mutation, by-ref/out-param, macro) rows as a use; no alias analysis; " "the statement is the unit (nested bodies/?:/short-circuit fold, goto untracked); no control dependence; block " "scopes separated; C-family #if 0 dropped, other #if kept+flagged. Full legend: omit " @@ -3071,7 +3071,7 @@ inline std::string sliceLegendText( const SliceEmitOpts& opts ) { out = ""; + "flow_redundant=\"1\" (unseeded both=): adds no line beyond the flat inventory; seed at= for real gain. -->"; } } // H1: the residue clause, in BOTH dialects and as its own comment — opened `