Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
383c748
test(ruby): a red gate for Ruby's bare-word call, with Ruby's own loc…
andriytyurnikov Oct 1, 2026
60ba5c0
feat(ruby): a bare-word call is a call wherever Ruby's own lexical ru…
andriytyurnikov Oct 1, 2026
72e9351
test(ruby): Ruby has no declarations — red arms for an own attr_reade…
andriytyurnikov Oct 1, 2026
69d39b2
fix(ruby): Ruby has no declarations — an attr accessor or an empty ho…
andriytyurnikov Oct 1, 2026
8aa316f
docs(changelog): Ruby bare-word calls and Ruby-has-no-declarations un…
andriytyurnikov Oct 1, 2026
28bd0e6
test(ruby): rubybarecallcheck wraps its three one-line `&& ok || no` …
andriytyurnikov Oct 1, 2026
4a072cb
test(callform): Ruby row 6 — the bare paren-less call is now a call; …
andriytyurnikov Oct 1, 2026
806e3f3
refactor(graph): the decl/def collapse family is collapseFamilyOf — c…
andriytyurnikov Oct 1, 2026
45dc9b4
test(ruby): a red gate for what a bare call can reach — mixins are an…
andriytyurnikov Oct 2, 2026
9899564
test(ruby): rubyreachcheck pins the include-narrow, concern, core-roo…
andriytyurnikov Oct 2, 2026
089e077
feat(ruby): a call to self reaches what Ruby's method lookup reaches …
andriytyurnikov Oct 2, 2026
02ed7ac
docs(changelog): a Ruby call to self reaches what Ruby's method looku…
andriytyurnikov Oct 2, 2026
8d82330
test(ruby): a red gate for a typed receiver — a call on a built objec…
andriytyurnikov Oct 2, 2026
5671f34
feat(ruby): a call on a receiver the code builds answers from that cl…
andriytyurnikov Oct 2, 2026
55eb738
docs(changelog): a Ruby call on a receiver the code builds answers fr…
andriytyurnikov Oct 2, 2026
2f5a3e6
test(ruby): a red gate for a call on RSpec's own target — expect( v )…
andriytyurnikov Oct 2, 2026
dd948d5
feat(ruby): a call on RSpec's own target is RSpec's — expect( v ).to …
andriytyurnikov Oct 2, 2026
c56a916
docs(changelog): a call on RSpec's own expectation target is RSpec's,…
andriytyurnikov Oct 2, 2026
64f4b84
test(ruby): a red gate for a method named by a symbol the framework c…
andriytyurnikov Oct 2, 2026
9a451c7
feat(ruby): a method a Rails declaration names by symbol has a caller…
andriytyurnikov Oct 2, 2026
a3eb23a
docs(changelog): a method a Rails declaration names by symbol has a c…
andriytyurnikov Oct 2, 2026
eecca7c
test(ruby): rubydeclrefcheck's absent arm reports through if/else, no…
andriytyurnikov Oct 2, 2026
e8e65e1
test(ruby): a red gate for Rake task files and Jbuilder views — crawl…
andriytyurnikov Oct 2, 2026
212f11f
feat(ruby): Rake task files and Jbuilder views are Ruby — main's and …
andriytyurnikov Oct 2, 2026
c48caf0
test(ruby): rubyrspectargetcheck's red arms for RSpec's matcher chain…
andriytyurnikov Oct 2, 2026
b3085fa
feat(ruby): a call on an RSpec matcher's chain is RSpec's — receive( …
andriytyurnikov Oct 2, 2026
d17db41
docs(changelog): Rake task files and Jbuilder views are Ruby, and a c…
andriytyurnikov Oct 2, 2026
bb6ec04
test(ruby): a red gate for a call on a class — the class object's loo…
andriytyurnikov Oct 2, 2026
57253a7
test(ruby): rubyclassrecvcheck's singleton, forwarding and qualified-…
andriytyurnikov Oct 2, 2026
d24c4b0
feat(ruby): a call on a class answers from the class object's lookup …
andriytyurnikov Oct 2, 2026
23ddede
docs(changelog): a Ruby call on a class answers from the class object…
andriytyurnikov Oct 2, 2026
b21d03b
test(builtin): builtinbindcheck's header external= counts the two C.n…
andriytyurnikov Oct 2, 2026
5a29f7d
chore(versions): the shared pins re-derived on main 2720d1c5 — kParse…
andriytyurnikov Oct 4, 2026
51dfeeb
test(ruby): the inverted absence arms fail on a missing row or a fail…
andriytyurnikov Oct 4, 2026
a493ca6
test(ruby): rubyreachcheck — an out-of-reach call is refused as exter…
andriytyurnikov Oct 4, 2026
f3ab168
test(ruby): rubyclassrecvcheck floor (h) — a mixin's own singleton me…
andriytyurnikov Oct 4, 2026
0b6118a
docs(changelog): floor (h) of the class-object entry, the accessors' …
andriytyurnikov Oct 4, 2026
b86a7a9
test(ruby): rubyclassrecvcheck — include is the instances' side, exte…
andriytyurnikov Oct 4, 2026
4fb6da2
feat(ruby): a class object's lookup tells include from extend, in Rub…
andriytyurnikov Oct 4, 2026
162255e
docs(changelog): a Ruby class object's lookup tells include from exte…
andriytyurnikov Oct 4, 2026
04a6c21
test(ruby): rubyclassrecvcheck — a call to self, or on a built instan…
andriytyurnikov Oct 4, 2026
70d9aba
feat(ruby): a call to self, or on a built instance, answers from its …
andriytyurnikov Oct 4, 2026
e55e7ae
docs(changelog): a Ruby call to self, or on a built instance, answers…
andriytyurnikov Oct 4, 2026
3d04879
Merge PR #373 (Ruby calls resolve by Ruby's method lookup) into train…
joyful-ii-V-I Oct 4, 2026
3d14534
train 26a: re-pin qschemetrip, dictv, and the showcase --at seed
joyful-ii-V-I Oct 4, 2026
5abf901
fix(graph): kRubySideAcross is sized by its enums' counts, and RubyAn…
andriytyurnikov Oct 5, 2026
4063074
Merge PR #373's newer head (5abf9014: a Ruby call to self answers fro…
barefootski Oct 7, 2026
fe6b56c
train 26a: impactpartitioncheck passes its seed names to --impact une…
barefootski Oct 7, 2026
9901af2
train 26a: CodeRabbit on #378 — new Ruby gates stop when mktemp fails…
barefootski Oct 7, 2026
cd87e30
train 26a: re-pin the showcase --at seed after #373's newer head
barefootski Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
758 changes: 752 additions & 6 deletions CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2082,9 +2082,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published
### In the tests

<details>
<summary><b>664 gate scripts</b>, five contracts no unit test can hold, and the house rule: write the gate before the code it measures</summary> <!-- gatecount -->
<summary><b>671 gate scripts</b>, five contracts no unit test can hold, and the house rule: write the gate before the code it measures</summary> <!-- gatecount -->

`test/regression.sh` names **664 gate scripts** and is the authoritative list; <!-- gatecount -->
`test/regression.sh` names **671 gate scripts** and is the authoritative list; <!-- gatecount -->
`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
Expand Down
6 changes: 3 additions & 3 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:5565
$ ./build/ripwire . --at=src/graph.h:7397
<!-- ripwire at: the ENCLOSING-DEFINITION CHAIN at one FILE:LINE seed. p= the resolved file, l= the 1-based seed line, sym= the innermost enclosing definition's name (what the same seed resolves to in a selector position), chain= the row count. Rows are INDEXED definitions only, outermost first, innermost last: n= the definition's name, t= its kind tag, l= its own start line, el= its end line (1-based, inclusive). A namespace or any construct the index does not carry is NOT a row, so an outer scope can be absent rather than misnamed; a seed line inside no indexed definition is refused, never served as an empty chain. The same seed composes into any SYM selector as @FILE:LINE (callers, callees, impact, around, expand, uses, edit-check, slice, safe-delete, path, connect) and resolves to the innermost row. -->
<!-- root= on this element is the crawl root every p= below is RELATIVE to (single-root runs only; absent => p= is the path ingest itself used, unchanged). -->
<at p="src/graph.h" l="5565" sym="rankGraphTeleport" chain="1" root=".">
<s n="rankGraphTeleport" t="fn" l="5565" el="5593"/>
<at p="src/graph.h" l="7397" sym="rankGraphTeleport" chain="1" root=".">
<s n="rankGraphTeleport" t="fn" l="7397" el="7425"/>
</at>
```

Expand Down
6 changes: 3 additions & 3 deletions docs/EVALS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` | 664 gate scripts plus the determinism, cache-transparency and golden contracts. <!-- gatecount --> |
| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 671 gate scripts plus the determinism, cache-transparency and golden contracts. <!-- gatecount --> |
| **`--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)
Expand Down Expand Up @@ -5866,7 +5866,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 **664 gate scripts**, all of which exist on disk. <!-- gatecount -->
naming **671 gate scripts**, all of which exist on disk. <!-- gatecount -->

`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`.
Expand Down Expand Up @@ -6969,7 +6969,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 664. The <!-- gatecount -->
~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 671. The <!-- gatecount -->
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`
Expand Down
14 changes: 7 additions & 7 deletions docs/captures/COMMANDS_showcase_2026-09-14.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:5565`
## `./build/ripwire . --at=src/graph.h:7397`

*Hold a LOCATION, not a name: the enclosing-definition chain at FILE:LINE (a compiler error, a diff hunk, a stack frame), outermost -> innermost.*

`````
<!-- ripwire at: the ENCLOSING-DEFINITION CHAIN at one FILE:LINE seed. p= the resolved file, l= the 1-based seed line, sym= the innermost enclosing definition's name (what the same seed resolves to in a selector position), chain= the row count. Rows are INDEXED definitions only, outermost first, innermost last: n= the definition's name, t= its kind tag, l= its own start line, el= its end line (1-based, inclusive). A namespace or any construct the index does not carry is NOT a row, so an outer scope can be absent rather than misnamed; a seed line inside no indexed definition is refused, never served as an empty chain. The same seed composes into any SYM selector as @FILE:LINE (callers, callees, impact, around, expand, uses, edit-check, slice, safe-delete, path, connect) and resolves to the innermost row. -->
<!-- root= on this element is the crawl root every p= below is RELATIVE to (single-root runs only; absent => p= is the path ingest itself used, unchanged). -->
<at p="src/graph.h" l="5565" sym="rankGraphTeleport" chain="1" root=".">
<s n="rankGraphTeleport" t="fn" l="5565" el="5593"/>
<at p="src/graph.h" l="7397" sym="rankGraphTeleport" chain="1" root=".">
<s n="rankGraphTeleport" t="fn" l="7397" el="7425"/>
</at>
`````

## `./build/ripwire . --callers=@src/graph.h:5565`
## `./build/ripwire . --callers=@src/graph.h:7397`

*The same seed in a SELECTOR position: @FILE:LINE resolves to the innermost enclosing definition, then --callers runs on it.*

`````
<!-- ripwire callers/callees: the 1-hop call hierarchy read off the call graph — the callers form lists symbols that CALL of=; the callees form lists symbols of= itself calls. of= is the selector you passed, defs= how many DEFINITIONS it resolved to (rows UNION every def's neighbours), count= the DISTINCT neighbour symbols (a floor, per counts_floor=), windowed by limit= and offset=. A neighbour that is an indexed function-like #define is a macro row (t="macro", role="macro" on the XML row): the edge crosses a macro expansion, not a plain call — rows carry no role= otherwise. Rows are ordered SOURCE first, then test/bench, then docs, by path within a tier. hop_tested=/hop_untested= partition count= by the tested= lens below (1-hop, never transitive). tested="1" on a row means an indexed test transitively reaches it (never 0, omitted when it does not). BLIND SPOT the test-gate legend also names: only a CALL EDGE from an INDEXED test symbol counts here, so a shell or CLI-level test running a built binary as a SUBPROCESS is invisible to it and a repo tested that way reads all-untested. Read untested= as no in-process test reaches it, not as no test covers it. next= is the one pasteable follow-up (the uses verb on this selector: the call sites). counts_floor="1" means every count here is a FLOOR, never a total: edges are extracted from source TEXT by NAME. Missing: dynamic dispatch (virtual/interface/duck-typed), a most-vexing-parse declaration with no call expression, a function-pointer/callback bound to more than one function in scope (reassigned, table-indexed, lambda-bound, or address-taken/reference-bound), and a plain-name binding (fp=handler) whose variable type is not PROVABLY a function pointer (a same-file typedef/declarator; a HEADER typedef is missed; auto/template types are read as unpinned, so KEPT). A macro-generated call site is role="macro" only when its name uniquely names an indexed function-like #define (C-family, t="macro"); a shared name stays a plain call, an unindexed macro is no edge. 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. COUNTING UNIT differs by verb: callers, callees, edit-check, graph-query and pr-context counts are DISTINCT SYMBOLS (repeated calls from one caller, and calls to two overloads, collapse into ONE row; multiplicity survives only in the call graph's edge weight). The reach counts (impact's reaches=, pr-context's dependents=) are the size of a transitive reach SET, each symbol counted once. The uses verb counts call SITES, one row per occurrence — a larger count there for the same symbol is these units agreeing, not disagreeing. The map header's edges= is a unit again different — distinct (caller,callee) PAIRS — and that document carries neither this marker nor this clause. -->
<!-- root= on this element is the crawl root every p= below is RELATIVE to (single-root runs only; absent => p= is the path ingest itself used, unchanged). -->
<callers of="@src/graph.h:5565" defs="1" count="7" root="." hop_tested="0" hop_untested="7" graph_ambiguous="10252" graph_unresolved="12843" graph_unindexed="237" counts_floor="1" next="--uses=@src/graph.h:5565">
<callers of="@src/graph.h:7397" defs="1" count="7" root="." hop_tested="0" hop_untested="7" graph_ambiguous="10473" graph_unresolved="12903" graph_unindexed="237" counts_floor="1" next="--uses=@src/graph.h:7397">
<s t="fn" n="getIndex" p="src/mcpindex.h:1165"/>
<s t="fn" n="rankGraph" p="src/graph.h:5606"/>
<s t="fn" n="anchoredLexicalRank" p="src/graph.h:6252"/>
<s t="fn" n="rankGraph" p="src/graph.h:7438"/>
<s t="fn" n="anchoredLexicalRank" p="src/graph.h:8084"/>
<s t="fn" n="runEval" p="src/eval.h:171"/>
<s t="fn" n="churnDecayRanking" p="src/main.cpp:1380"/>
<s t="fn" n="churnRankedGraph" p="src/main.cpp:1419"/>
Expand Down
6 changes: 3 additions & 3 deletions present/deck5_ripwire_build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
["664 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
["671 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."],
Expand All @@ -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, "664", "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, "671", "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",
Expand Down Expand Up @@ -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"],
["664 gate scripts", "bash test/manifestcheck.sh"], // gatecount
["671 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/"],
Expand Down
8 changes: 5 additions & 3 deletions queries/ruby/tags.scm
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
; Ruby structure the call graph cares about:
; - class / module definitions → def nodes (containers; module is a namespace/mixin)
; - `def name` / `def self.name` method definitions → the def nodes calls resolve TO
; - method calls → the call references (edges). A bare `foo` with no receiver and no
; args parses as (identifier); `foo(..)` / `obj.foo` parses as (call). We capture the
; (call) form's method name — the reliable, unambiguous call site.
; - method calls → the call references (edges). `foo(..)` / `obj.foo` parses as (call), and
; this query captures the (call) form's method name. A bare `foo` with no receiver, args or
; parentheses parses as (identifier) — the same node a local-variable read is — and no query
; can tell the two apart: ingest_binds.h captureRubyBareCalls mints those calls by Ruby's own
; lexical local rule instead (parser version 130, test/rubybarecallcheck.sh).
; - require / require_relative → reference edges (they ARE plain (call) nodes, captured
; by the call rule below — no separate rule needed).
;
Expand Down
2 changes: 1 addition & 1 deletion src/darkflags.h
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ inline constexpr std::int32_t kTemplateText = -1;
inline constexpr std::string_view kShellExtTable[] = { ".sh", ".bash", ".zsh" };

inline constexpr std::string_view kHashCommentExtTable[] = {
".py", ".pyi", ".sh", ".bash", ".zsh", ".rb", ".pl", ".cmake", ".yml", ".yaml", ".toml", ".r", ".jl"
".py", ".pyi", ".sh", ".bash", ".zsh", ".rb", ".rake", ".jbuilder", ".pl", ".cmake", ".yml", ".yaml", ".toml", ".r", ".jl"
};

inline LineSyntax lineSyntaxFor( std::string_view path )
Expand Down
Loading
Loading