Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 7 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,9 +507,12 @@ rules it enforces:
bare-name ladder, so #349's Clojure `str/join` binds an in-tree `join`. A shared fix is planned (slice 6a). Until
it lands, this rule does **not** mean a qualified call in a new language is free of wrong edges. Pin the wrong
edge as a known-gap arm (see the definition of done below), and do not add a guard for your language alone.
- **Known gap.** Tags-pass predicates (`#eq?`/`#any-of?`) do not run yet, so keyword-headed definitions
(Elixir today; a Lisp-style `defn` if one is added) keep a small keyword table in C++. That table belongs in `tags.scm` once the
mechanism lands (#355, slice 5a); it is not a pattern to extend.
- **Keyword heads are data.** The tags pass evaluates the string predicates `#eq?`, `#not-eq?`, `#any-of?` and
`#not-any-of?` (`src/infra/tspredicates.h`, #355; never `#match?`), so a keyword-headed definition keeps its keyword list in its
`tags.scm`: Elixir's `def`/`defp`/… is the `#any-of?` list on `@_keyword`, and C++ that must recognise the same head outside a match
reads that list back from the compiled query (`predicateStrings`) instead of keeping a copy. Elixir's metadata attributes
(`@doc`, `@spec`, …) are the `#not-any-of?` list on `@_attribute` the same way. `test/tagspredicatecheck.sh` holds these rules.
Clojure's head tables move the same way (#389); `macroBodyKeyword` (`ingest_relations.h`) is still C++.

#### Where language-specific code lives

Expand Down Expand Up @@ -594,7 +597,7 @@ review will credit it, but it is never a condition for merging the language.
| 2. Aliases | None. Renames reach the resolver as `RawBind` records (`ingest_cache.h`) from separate producers: Python's `@import.names` / `@import.alias` captures, read in `captureTagsFacts` (`ingest_sidecap.h`) and turned into binds by the Python-only `emitCapturedPythonImportBind` (`ingest_importcap.h`); `captureJsImportFacts`, `captureJsModuleAliases` and `captureGoImportFacts` (`ingest_jsimports.h`), C/C++ aliases in `fnPtrAliasName` (`ingest_binds.h`) and `captureTypeAlias` (`ingest_relations.h`). The readers are separate too: `addTypeAliasBases` and `buildUsingReexports` (`resolve.h`). | duplicated: one "local name → target" concept in about five shapes. | **2a** one shared binds channel (`@import.alias` / `@import.names` → one `RawBind` kind): M, *maintainers*, after the #358 import slices. **2b** after 2a, one language per PR moves its renames onto it: S each (Rust `use a as b`, **wanted**; Kotlin `import x as y`, PHP `use … as`, C# `using X = …`). |
| 3. Types | `captureTagsFacts` (`ingest_sidecap.h`) reads `@definition.*` captures for every language. | shared, but language decisions are `Lang::` branches inside the loop: 36 non-comment `Lang::` mentions, 15 of them Elixir. | **3a** #374: one constexpr per-language hook table that `captureTagsFacts` calls once per capture, one language per commit, Elixir first. M. |
| 4. Inheritance | `captureBases` (`ingest_relations.h`): base clauses for C++, TS/JS, Java, Python, Swift, C#, PHP, Kotlin and Ruby, chosen by node kind (`isBaseTypeNodeIn`). | partly. Rust `impl … for` is a separate pass; Ruby mixins read the same path through `@reference.mixin` captures since slice 4a (#400). | **4a** done (#400): Ruby `include`/`extend`/`prepend` read the shared path through `queries/ruby/tags.scm` captures, `captureRubyMixinBases` retired. **4b** the base-clause node kinds move from `isBaseTypeNodeIn` into `tags.scm` captures: M, all of the languages above. **4c** structural conformance (#386): a class or type whose methods cover a structural base's required methods (Python `typing.Protocol`, Go interfaces, possibly TS interfaces) is listed as an implementor marked `structural=1`, never as a nominal subclass, with a disclosed minimum-method threshold; step (c), one shared method-set check, per-language data for which bases are structural. M; Python and Go first (Go **wanted**). |
| 5. Callables | Definitions come from `tags.scm`, read by `captureTagsFacts`. | partly. Keyword-headed definitions keep keyword tables in C++: `elixirFunctionKeyword` and `elixirMetadataAttribute` (`ingest_elixir.h`) repeat `macroBodyKeyword` (`ingest_relations.h`), 0.82 and 0.96 similar by `--clones`. | **5a** #355: the tags pass evaluates `#eq?`/`#any-of?`/`#not-any-of?`, then Elixir's table moves into its `tags.scm`. M; Elixir, then Clojure. |
| 5. Callables | Definitions come from `tags.scm`, read by `captureTagsFacts`. | partly. The tags pass evaluates `#eq?`/`#not-eq?`/`#any-of?`/`#not-any-of?` and Elixir's definition keywords and metadata attribute names are `tags.scm` data (#355). Still C++: `macroBodyKeyword` (`ingest_relations.h`) and Clojure's head tables. | **5a** #355 landed the evaluator and Elixir's two tables; Clojure's tables next (#389). M. |
| 6. Dispatch | `buildGraph` (`graph.h`): a SCIP pin, then the canonical tier (`appendCanonicalCandidates`, `resolve.h`), bindings and receiver rules (`receiverevidence.h`), then name tiers. Per-language qualified-call rules: `ElixirResolver` (`elixir_resolve.h`), `keepRustQualifiedCandidates` and `keepStdQualifiedCandidates` (`graph.h`). | partly. Facts about each language are scattered `l == Lang::X \|\| …` predicates (`implicitReceiverLang`, `memberNeverReachesFreeFunction` and `classNameReceiverLang` in `receiverevidence.h`; `langUsesHashComment` in `quality.h`), and `--clones` reports them as one group. | **6a** the shared refusal for an unplaced module qualifier (the known gap above): S, *maintainers*, not yet scheduled. **6b** the **language-neutral qualified-call resolver**: generalise `ElixirResolver`'s module table (alias/import/require facts → `Module::name`) so that Clojure `ns/f`, Elixir and other module-qualified languages use one resolver driven by per-language data. M; Elixir byte-identical, Clojure as the first new user. **6c** those predicates become columns of one constexpr per-`Lang` table: S, all languages. |
| 7. Data-driven dispatch | One mechanism per shape: fn-pointer and callback bindings `buildFnPtrBindTables` (`graph.h`, C/C++); HTTP routes `routesVisitNode` (`ingest_sidecap.h`, a JS/TS and Python walk) into `RouteDef`/`RouteUse` (`model.h`); FFI aliases `BindingAlias` (`model.h`). | partly. Calls by a literal method name (`send(:m)`) resolve for Ruby only. | **7a** #375: one capture for a call whose method name is a literal, resolved through the ordinary receiver rules. M; Python `getattr`, JS/TS `obj["m"]`, Go `MethodByName` (**wanted**). **7b** route registration read from captures instead of the walk: M; JS/TS and Python. |
| 8. Preprocessing | `retagMacroCallReferences` (`model.h`) for macro calls. Inert regions (quote, discard, comment forms): `inInertRegion` (`ingest_inert.h`) for Elixir's quote and live holes; Clojure's `clojureInert` (`ingest_clojure.h`) is still per language. | partly. The shared inert-region walk `inInertRegion` (`ingest_inert.h`) landed with #380 (#357); Elixir's quote and live holes use it, Clojure's filter does not yet. | **8a** Clojure's quote/discard filter (`clojureInert`, `ingest_clojure.h`) moves onto `inInertRegion`: S, Clojure. Ready now that #380 and #349 are both on `main`. |
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2120,9 +2120,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published
### In the tests

<details>
<summary><b>701 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>702 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 **701 gate scripts** and is the authoritative list; <!-- gatecount -->
`test/regression.sh` names **702 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/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` | 701 gate scripts plus the determinism, cache-transparency and golden contracts. <!-- gatecount --> |
| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 702 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 @@ -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 **701 gate scripts**, all of which exist on disk. <!-- gatecount -->
naming **702 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 @@ -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 701. The <!-- gatecount -->
~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 702. 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
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 = [
["701 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
["702 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, "701", "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, "702", "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 }){
["192 long flags · 36 slides", "bash test/deckclaimcheck.sh"],
["every --flag named here exists", "bash test/deckcheck.sh"],
["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"],
["701 gate scripts", "bash test/manifestcheck.sh"], // gatecount
["702 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
14 changes: 7 additions & 7 deletions queries/cpp/tags.scm
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
; field), so the module-scope patterns above never see it: pre-fix NO class-static constant was
; indexed, not even a SCREAMING one (probed 2026-08-12). This pattern is deliberately LOOSE — it
; also matches every plain default-member-initializer (`int retries = 3;`) — because tags-pass
; predicates never run (see the cast-keyword note below) and child ORDER between `static` and
; predicates did not run before #355 (see the cast-keyword note below) and child ORDER between `static` and
; `constexpr` is not fixed in source. The whole keep decision lives in ingest.cpp
; (fieldConstantCaptureKept): keep iff the field carries BOTH a `static` storage_class_specifier
; AND a const/constexpr/constinit type_qualifier — per-instance fields (default-initialized or
Expand All @@ -172,7 +172,7 @@
; ---- instance FIELDS (ripwire addition — the member-variable round, card A3, 2026-09-02) ----
; Every per-object data member of a class/struct/union body: `int count = 0;`, `char* label;`,
; `int& ref;`, `int buf[8];`, a bitfield `unsigned x : 3;` — declared or default-initialized alike.
; Deliberately LOOSE like the class-static-constant pattern above (tags-pass predicates never run):
; Deliberately LOOSE like the class-static-constant pattern above (tags-pass predicates did not run before #355):
; it also matches `static int live;` and `static constexpr int kMax = 3;`. The keep decision is
; ingest_names.h fieldCaptureKept: a `static` storage_class_specifier DROPS the capture — a class-static
; constant keeps its @definition.constant row above (t="var"), and a mutable static member is not a
Expand Down Expand Up @@ -203,7 +203,7 @@
; ANONYMOUS token child of the declaration (its only named children are the type and the declarator),
; invisible to any named-node constraint. The whole qualifier test therefore lives in ingest.cpp
; (cudaMemorySpaceQualifierOf), the same capture-time home as isCppCastKeyword, because tags-pass
; predicates never run (see the cast-keyword note below). Policy enforced there: `__constant__`
; predicates did not run before #355 (see the cast-keyword note below). Policy enforced there: `__constant__`
; extracts case-blind (the keyword is the evidence — the Rust const_item rationale);
; `__device__`/`__managed__` are mutable device globals and stay behind the SCREAMING gate; no
; memory-space token drops. The BLAST RADIUS of these unconstrained patterns is all of Lang::Cpp
Expand Down Expand Up @@ -360,8 +360,8 @@
;
; WHY THE CAST KEYWORDS ARE NOT EXCLUDED HERE. tree-sitter-cpp parses `static_cast<T>(x)` as this exact
; shape, so this pattern also matches all four C++ cast keywords (171 sites in src/ alone). The natural fix
; — a `(#not-eq? @name "static_cast")` predicate — does NOT work: ripwire's passesPredicates is wired into
; --match/--lint only, never into the tags pass (measured: the predicate left --uses=static_cast at 165).
; — a `(#not-eq? @name "static_cast")` predicate — did NOT work before #355: the tags pass ran no predicates
; (measured: the predicate left --uses=static_cast at 165). #355 runs the string ones, so this filter can move here.
; The exclusion therefore lives at capture time in ingest.cpp (isCppCastKeyword), where it is enforceable.
(call_expression
function: (template_function
Expand Down Expand Up @@ -398,8 +398,8 @@
;
; This pattern captures the SHAPE ONLY: any identifier-call statement whose immediate next named
; sibling is a compound_statement. It deliberately over-matches — a real `logCall( "x" );` followed by
; an unrelated block inside a function body is this exact shape — because tags-pass predicates never
; run (same story as the cast keywords above). The three real gates live at capture time in
; an unrelated block inside a function body is this exact shape — because tags-pass predicates did not
; run before #355 (same story as the cast keywords above). The three real gates live at capture time in
; ingest.cpp's testMacroBlockPartsOf: the callee must be a KNOWN test macro (kTestBlockMacroNames),
; the statement must carry the error-recovery MISSING ";" (a real semicolon disqualifies), and a title
; string literal must be present in the argument list (it becomes the symbol's name; the identifier
Expand Down
Loading