From 5ab3f125265f6c960369e2bd0060cd48ee8fa916 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 1 Oct 2026 02:40:53 -0400 Subject: [PATCH 01/35] =?UTF-8?q?test:=20mapdatasectioncheck=20=E2=80=94?= =?UTF-8?q?=20data=20Sections=20must=20not=20crowd=20code=20out=20of=20the?= =?UTF-8?q?=20map=20(red)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A data file of a few hundred keys or headings indexes as SymKind::Section rows with no call edges. Their rank is their share of the teleport prior, and priorwt's x1.7 specific-name boost fires on data names, so they push called code, main and _helper out of the top-K the default map emits. The gate measures the end-to-end statistic: code rows in the emitted top-K, out of 8, on a generated fixture (test/mapdatasectionfix/gen.sh: eight Python functions plus one data file; S1 schema.rb columns, S2 markdown headings, S3 YAML keys, S4 JSON keys; long or short names; N in {20,40,200}, K in {200,16}). Surfaces: XML, --json, the --html node set, --tree (code file first), --max-tokens and MCP analyze. It also checks I1 (no in-degree-0 Section ahead of a non-Section row), Gate D (the data_sections_cut= count and a next= that pages exactly the cut Sections), the legend definitions and a Section-free control. Red on a5229aca: the primary cells (S2 long N=200, S3/S4 long N=220, K=200) show 1/8 code rows on every map surface. The pre-registration lands verbatim in docs/EVALS.md with this commit, before any fix code or arm number. Co-Authored-By: Claude Opus 5.5 --- .github/pargates-shard-weights.json | 1 + README.md | 4 +- docs/EVALS.md | 185 ++++++++++++++++++++- present/deck5_ripwire_build.js | 6 +- test/mapdatasectioncheck.sh | 242 ++++++++++++++++++++++++++++ test/mapdatasectionfix/gen.sh | 113 +++++++++++++ test/regression.sh | 2 +- 7 files changed, 544 insertions(+), 9 deletions(-) create mode 100755 test/mapdatasectioncheck.sh create mode 100755 test/mapdatasectionfix/gen.sh diff --git a/.github/pargates-shard-weights.json b/.github/pargates-shard-weights.json index 3c2f136c5..760d12e79 100644 --- a/.github/pargates-shard-weights.json +++ b/.github/pargates-shard-weights.json @@ -285,6 +285,7 @@ "luacheck.sh": 3.5, "macroedgecheck.sh": 3.6, "manifestcheck.sh": 62.9, +"mapdatasectioncheck.sh": 55.0, "mapdiffcheck.sh": 3.3, "matchcapturecheck.sh": 11.8, "matchgrammarcheck.sh": 0.6, diff --git a/README.md b/README.md index daf4382e5..b58c172e7 100644 --- a/README.md +++ b/README.md @@ -2063,9 +2063,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published ### In the tests
-658 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +659 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 **658 gate scripts** and is the authoritative list; +`test/regression.sh` names **659 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 e338af94f..b22a9c66e 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` | 658 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 659 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) @@ -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 **658 gate scripts**, all of which exist on disk. +naming **659 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`. @@ -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 658. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 659. 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` @@ -14195,3 +14195,182 @@ that `--slice=SYM:VAR` ranks lines by relevance across a whole function is super rule is proven only to order the rows it already emits, never to find the most relevant line in a function it has not narrowed down first. `src/compactlegend.h`'s `order` reading, `src/slice.h`'s v1/v2 legends, and `--help=slice` are worded to this scope. + +## Map data Sections never crowd code out of the default map (#339 F1) — PRE-REGISTERED 2026-09-30 (before any fix code or arm number) + +The registration below was frozen on 2026-09-30 (frozen text sha256 `e731a459614b32eda140c5c424beb8867ff8bbc24cdbf8ddea524cf47d38ae93`) +and lands here with the gate, `test/mapdatasectioncheck.sh` (red on `a5229aca`), before any fix code or arm number. It is +reproduced verbatim, except that its headings are demoted one level to sit under this section and three references to files +outside this repository (a design note, a decision log, a scratch directory) are replaced by neutral words. Deviations decided +after the freeze are listed with the result, never edited into this text. + +This supersedes the DRAFT, which is left unchanged. Design: the lane's design note (outside this repository). It lands verbatim in `docs/EVALS.md` with the gate commit, before any fix code or arm number exists. Line refs are at BASE `b90547fa`. + +**Owner rulings, 2026-09-30:** +- **R1:** A touches only the map's uniform prior. Every other rank consumer stays byte-identical. There is no per-verb census. +- **R2:** `--eval` decides on code-seeded commits. +- **R3:** paired, stratified bootstrap margins on three repos replace the priorwt precedent. +- **R4:** FIND terminality is a post-release readout with a revert guard. + +**Hypothesis.** Under A, every non-Section node outranks every in-degree-0 Section in the map. Then code rows survive the #339 shapes, the non-Section order is unchanged when there are no Section→non-Section edges, and nothing outside the map scope moves. + +### 0. Arms and pins + +**A.** A new entry point, `rw::rankDefaultMap(…)`, whose name is frozen for MEAS. +- It is the uniform teleport with Section entries ×`kSectionPriorMul`=0.1, followed by the unchanged `biasPrior`. +- `static_assert(kSectionPriorMul*kSpecificNameMul < kCommonNameMul*kPrivateNameMul)` re-reads the invariant (0.17 < 0.25) from BASE's constants. β is set by it; it is never fit and never swept. + +**Map scope.** The list is exhaustive: +- the plain map: main.cpp:1762 with no payload verb and the default `--rank-by` (XML, `--json`, `--html`, `--max-tokens`); +- `--tree` (verbs_report.h:3521); +- MCP rank_by pagerank (mcpverbs.h:479, the CLI-parity twin); +- MCP analyze when `changed==0` (mcpindex.h:1198). Analyze is `ix.rank`'s only reader (mcpverbs.h:438). + +Everything else keeps BASE bytes, including every payload verb's whole output. + +**Disclosure.** +- **Attribute:** `data_sections_cut="N"` on each map-scope root, where N = Sections indexed − Section rows shown. It is omitted at 0. +- **`next=`:** `next="--graph-query='kind(all,sec)' --offset=M --limit=K"`, where M = Section rows shown and K = the effective top-k. + - `kind(…,sec)` (query.h:63) is main's only kind filter. `--exclude/--include/--path/--scope` filter paths. + - graph-query is out of scope. It keeps BASE order (rank desc, id asc; verbs_navigate.h:335–342) and pages itself (`pageDisclosure`, `next_offset=`). So no page exceeds K rows at any N (#339: ≈2,000). + - Shown rows are skipped exactly when A's Section-relative order equals BASE's. That holds when no non-Section→Section edge exists, because Section ranks then scale by one factor. +- **Pre-specified fallback:** if Gate D's set equality fails anywhere, the attribute ships without `next=`. This is a §9.1 dead-end cut, counted apart. No other spelling is tried after data. + +**B (fallback).** Code-first row pick (design §2B). The rank vector is untouched. + +**C.** Rejected. + +**PLACEBO_s (s=1..20).** A, with `kind==Section` replaced by membership in R_s. +- R_s is n_sec non-Section ids drawn without replacement by `std::mt19937_64(s)` over crawl order. +- The draw is stratified on the Sections' `priorwt::weight` values, so the removed mass is within 1% of A's. Any shortfall comes from the nearest stratum and is disclosed. +- It is not degree-matched (disclosed). R_s is dumped and hashed. s is passed by env var. +- It is skipped where n_sec > 0.5 × the non-Section count. + +**Pins.** +- #339: `pr-339`@`221afc08`, used for S1 only. It is re-pinned before measuring, never after. +- Corpora: + - ripwire@`b90547fa`; + - django `03988c5a`/tree `1afcf39f`; + - webpack `a943d69c`/tree `4d708c08` (`extcorpus.lock`); + - the frozen `snapshot.*pack`. + + The clones are full-history, and HEAD and tree are verified on every run. + +**Design numbers are predictions, not data.** +- The 3/8–7/8 table, the `k=` values and "1 `sec` row in ripwire's top 200" came from the installed 0.6.5 (`built_from=3fcd515ff`). That binary is not an arm. +- #339's 6/8 and 4/8 came from its review. +- Step 1 after the freeze re-reads them on BASE (BASE+339 for S1), before any other arm runs. + +**Builds.** 9 builds, run after the 3 now running: BASE, BASE+MEAS, A, A+MEAS, B, PLACEBO+MEAS, and BASE/A/B+339 (throwaway merges, never pushed). +- Each has its own worktree and a Release build, with the same toolchain and flags. Every run uses `--no-cache`. +- The binary table records sha256, `built_from=` (plus the patch sha256) and the compiler. +- **At run time,** each wrapper logs `shasum -a 256 $BIN` immediately before the run. Output whose hash ≠ the arm's pin is rejected. +- Also sha256-pinned before first use: MEAS and PLACEBO (kept outside the repository, never pushed), the fixture generator, Gate S's argv list and MEAS's key/class list. + +### 1. Primary, gating, end-to-end (§7): code rows in the emitted top-K + +**Harness:** `RIPWIRE_BIN=$BIN test/mapdatasectioncheck.sh`, red-first, with a deterministic generator. + +**Fixture:** 8 Python functions (a call chain, an uncalled `main`, `_helper`) plus one data file, with long or short names. The data file is S1 (#339 `db/schema.rb` columns), S2 (md headings), S3 (YAML keys) or S4 (JSON keys). + +- **Primary cells (K=200):** S1 at 20/40 tables; S2/S3/S4 long at N=200/220/220. +- **Grid, gating for A:** shape × {long, short} × N∈{20,40,200} × K∈{200,16}. +- **Surfaces:** XML, `--json`, the `--html` node set, `--tree` (code file first), `--max-tokens`, MCP analyze (clean tree). +- **Statistic:** code rows shown / 8. It is exact, so no CI. +- **BASE must be red** on each primary cell. The predictions are 6, 4, 3, 3; S4 is unmeasured. A cell green on BASE is reported inert and dropped before any A/B number exists, and never replaced. +- **Pass:** 8/8 on every cell × surface. +- **Reported only:** bytes before the 1st and the 8th code row (§9.1 #8). + +### 2. Gates for A + +**Edge census, first:** Section in/out degree by edge type, per corpus and fixture. + +**I1.** At `--top-k=S`, no non-Section row follows an in-degree-0 Section row. S is the header's `symbols=`; `--top-k=0` emits no map (cli.h:34). + +**I2.** A's non-Section row sequence ≡ BASE's (`--top-k=S --json`, three corpora), with every inversion listed. +- Bound: 0 inversions between rows whose BASE scores differ by >1e-6 relative. +- If Section→non-Section edges exist, the bound is instead Kendall τ ≥ 0.99 over the top 200. + +**Gate S (R1).** It replaces the census and the draft's §3(b) margins. There is no acceptance path. +- **(i) Static.** A's diff leaves `priorwt::weight`, graph.h:4378, `biasPrior`, `rankGraphTeleport`, `rankGraph` and `diffTeleport` byte-unchanged. `rankDefaultMap`'s call sites equal the map scope. +- **(ii) Dynamic.** BASE vs A stdout and exit status are `cmp`-identical, on the three corpora and on S2/S3-long-200. MCP analyze also runs with one uncommitted code edit. The argv list is frozen before any A binary runs: + - `--eval`, `--eval-retrieval`, `run_recalleval.py --lane both`, `run_extcorpus.py`; + - `--for` (default, candidates, `--anchor`), `--expand`; + - `--map-diff` (git, no-git), `--rank-by=churn|churn-decay|authority|hub|rrf`; + - `--impact`, `--graph-query`, communities/drill/zoom, `--seams`, `--report`, `--exercises`; + - MCP analyze (dirty), MCP impact. + +**Gate D.** +- N equals the `kind(all,sec)` count= minus the Section rows shown. +- Following `next=` and then `next_offset=` until `capped="0"` yields exactly the cut Sections, each once, with every page ≤ K rows. This must hold on every §1 cell at K∈{200,16} and on the three corpora. +- The attribute is defined in the XML, JSON and compact legends. Legend coverage fails on an undefined one. + +**Byte identity.** Section-free fixtures and goldens do not change. The golden refresh is listed per file, with the reason ("`k=` rescale" or "Section rows moved"). + +### 3. Non-regression and readouts + +#### (a) `--eval` co-change (R2, R3): the `map` column decides +By R1, eval.h:325 (seeded) and graph.h:5148 (anchored) are non-map consumers. So every printed `--eval` row is byte-identical under A (Gate S), and the bar applies to the one vector A changes. + +**MEAS.** +- It is additions only, in eval.h only; for BASE it also adds a ≤3-line shim, `rankDefaultMap → rankGraph`. +- It is active only with `RIPWIRE_MEAS_EVAL_ROWS=` and writes only there. +- It writes one row per scored commit: + - the key (newest-first ordinal, sha256 of the sorted changed paths); + - the seed path and class; + - n_gold and the Section-only gold count; + - hits@5/10/20 for every printed ranker and for `map`. +- `map` = eval.h:326–331's per-file sum over `rankDefaultMap`'s vector, with the seed excluded. + +**Output-neutrality, proven before any A number:** +- `git apply --numstat` shows 0 deletions, and the patch adds no stdout/stderr write. +- Every non-339 build's `--eval` stdout and exit status `cmp`-equal BASE's on the three corpora (env set on MEAS builds). +- In each MEAS arm, the rows re-sum exactly to the dumped accumulators (`%.17g`), which reproduce every printed figure. +- Keys, seeds and classes equal BASE+MEAS's pinned list, or the run is void. + +**Code-seeded (mechanical).** The seed (eval.h:299–306: most symbols, tie → lowest id) holds ≥1 indexed symbol whose kind is neither Section nor ModuleScope. Otherwise the seed is `data` (≥1 Section) or `none`. The class is computed on BASE and is the same in every arm. + +**Deciding test, code-seeded only.** +- Commits are paired. The CI is a 95% percentile bootstrap, resampling within repo: 10,000 resamples, seed 20260930. The pooled Δ is commit-weighted. +- **A fails** if, for any k∈{5,10,20}, the pooled-Δ lower bound is < −1.0 pp or any repo's point Δ is < −2.0 pp. +- A repo with 0 code-seeded commits is not evaluable. + +**Secondary, non-deciding:** `data`/`none` seeds, and gold-kind strata. Ranking is at corpus HEAD, so absolute levels are upper bounds, but the paired Δ is valid. + +#### (b) §8 placebo (gates claims, not the ship) +- It runs on `map`. Fixtures are skipped because they have no matched set. +- A gain is claimed only if A's pooled Δ beats all 20 placebo Δs (one-sided p ≤ 1/21) and its lower bound is > 0. +- A loss inside the placebo range is generic perturbation. + +#### (c) FIND terminality (P1, R4): post-release, not a merge gate +- **Command:** `bench/substitution_report.py `, the §5 map row, per repo via `--tag`. +- **Windows:** baseline = the 21 days before the release that ships the arm; readout = the first ≥200 map rows after it. +- **Revert guard:** Δ < −5 pp with the Newcombe 95% CI upper bound < 0 → revert. +- **Power:** ≈ ±9 pp, so this is a guard, not evidence. + +### 4. Decision rule (in order; nothing re-tuned after data) +1. BASE must be red on §1, with inert cells dropped first. +2. **Ship A** iff all of these hold: + - §1 passes; + - I1 = 0 and I2 is within bound; + - Gate S passes; + - Gate D passes, or its fallback applies; + - byte identity holds; + - §3(a) passes. + + A Gate S or D failure is a wiring defect. Fix only the wiring (β and the kind test unchanged), rerun §1–§3, and log a deviation. +3. **Ship B** iff A fails only §3(a). B is judged on §1 over the surfaces it wires, on byte identity when no swap fires, and on Gate S(ii) vs BASE. +4. **A fails §1, I1 or I2:** the premise is false. Go to step 3. +5. **B also fails §1:** ship the gate and fixtures, revert the feature, and record the negative (EVALS §7). #339 F1 stays open, and C needs its own registration. +6. **§3(c) trips:** revert. + +**What would prove the design wrong:** +- a Section with in-degree > 0 inside a crowded top-K; +- an I2 breach; +- a code-seeded `map` loss beyond margin that the placebo does not reproduce. That would mean demoting data removes co-change signal, which points to B. + +**Blind spots:** doc-seeking tasks in doc-heavy repos. Payload verbs and dirty-tree MCP analyze stay BASE by R1, so they can still crowd. + +### 5. Published +- **EVALS:** "PRE-REGISTERED" (this text), then "RESULT at " with every number above, losses included, and β stated as derived. +- **CHANGELOG:** the change, `data_sections_cut=`/`next=`, the `k=` golden rescale, and the numbers. diff --git a/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index f5a6ac7b6..35ee018f6 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 = [ - ["658 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 + ["659 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, "658", "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, "659", "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 }){ ["185 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"], - ["658 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["659 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/mapdatasectioncheck.sh b/test/mapdatasectioncheck.sh new file mode 100755 index 000000000..2872e34e0 --- /dev/null +++ b/test/mapdatasectioncheck.sh @@ -0,0 +1,242 @@ +#!/usr/bin/env bash +# mapdatasectioncheck.sh — data Sections never crowd code rows out of the default map (#339 F1), and the cut is disclosed. +# +# A data Section (a markdown heading, a YAML/JSON key, a Rails schema column) is a SymKind::Section row. With no call +# edge in or out, its rank is its share of the teleport prior, and priorwt's x1.7 specific-name boost fires on data +# names like `database_url_12`. So a data file of a few hundred keys used to push called code, `main` and `_helper` +# out of the top-K the map emits. The map now ranks with rw::rankDefaultMap (Section restart mass x0.1, then the +# unchanged biasPrior). Every non-Section row then outranks every in-degree-0 Section, and the map discloses the cut +# as data_sections_cut= with a next= that pages the cut Sections. +# +# The statistic is end-to-end (docs/METHODOLOGY.md §7): code rows in the top-K the map actually emits, out of 8. +# Fixture: test/mapdatasectionfix/gen.sh — eight Python functions (a call chain under an uncalled `main`, plus an +# uncalled `_helper`) and ONE data file. The data shapes are S1 db/schema.rb columns, S2 markdown headings, S3 YAML +# keys and S4 JSON keys. S1 cells run only on a build that mints Sections from db/schema.rb (the #339 capture); +# elsewhere they are reported SKIP. +# +# Arms (the pre-registration in docs/EVALS.md "Map data Sections" names them): +# (P) §1 grid: shape x {long,short} x N in {20,40,200} x K in {200,16}, plus the primary cells S3/S4 long N=220. +# Surfaces: XML, --json, the --html node set, --tree (the code file is listed first), --max-tokens=1500, and +# MCP analyze on a clean git tree. Pass = 8/8 code rows on every cell x surface (--tree: code file first). +# (I1) at the K where every non-Section row is in, no in-degree-0 Section row is in. +# (D) Gate D: data_sections_cut = kind(all,sec) count - Section rows shown; next= then next_offset= (until +# has_more="0") pages exactly the cut Sections, each once, every page <= K rows. Run on XML and --max-tokens; +# --json, MCP analyze and MCP rank_by must carry the same disclosure as the XML at the same K. --tree carries +# data_sections_cut= with no next= (its rows are a per-file top 3, not a rank prefix). +# (L) the attribute is defined in the full XML legend, the compact legend and --help, and only where it rides. +# (Z) a Section-free fixture carries no data_sections_cut= and no next= on its root. +# MAPSEC_EXTRA_ROOTS=dir1:dir2 runs (I1) and (D) on those trees too (the measurement harness's corpora). +# Exit 0 all pass, 1 any fail, 2 setup. +set -u +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +BIN="${1:-${RIPWIRE_BIN:-$ROOT/build/ripwire}}" +[ "${BIN#/}" = "$BIN" ] && BIN="$PWD/$BIN" +GEN="$ROOT/test/mapdatasectionfix/gen.sh" +[ -x "$BIN" ] || { echo "mapdatasectioncheck: no ripwire binary at $BIN — build first"; exit 2; } +[ -f "$GEN" ] || { echo "mapdatasectioncheck: fixture generator missing: $GEN"; exit 2; } +command -v git >/dev/null 2>&1 || { echo "mapdatasectioncheck: git not on PATH"; exit 2; } +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT +fail=0 +no(){ printf ' FAIL %s\n' "$*"; fail=1; } +echo "mapdatasectioncheck: BIN=$BIN" + +# Every fixture is a committed git tree, so MCP analyze sees a CLEAN working set (changed==0, the map scope). +mkfix(){ + bash "$GEN" "$TMP/$1" "$2" "$3" "$4" || return 1 + git -C "$TMP/$1" init -q && git -C "$TMP/$1" add -A \ + && git -C "$TMP/$1" -c user.name=gate -c user.email=gate@example.invalid -c commit.gpgsign=false commit -qm fixture +} +for shape in S1 S2 S3 S4; do + for len in long short; do + for n in 20 40 200; do mkfix "$shape-$len-$n" "$shape" "$len" "$n" || { echo "mapdatasectioncheck: fixture generation failed"; exit 2; }; done + done +done +mkfix S3-long-220 S3 long 220 && mkfix S4-long-220 S4 long 220 || { echo "mapdatasectioncheck: fixture generation failed"; exit 2; } +# (Z): the code file alone. +mkdir -p "$TMP/codeonly" && bash "$GEN" "$TMP/codeonly-src" S3 short 1 >/dev/null && cp "$TMP/codeonly-src/app.py" "$TMP/codeonly/" || exit 2 + +python3 - "$BIN" "$TMP" "${MAPSEC_EXTRA_ROOTS:-}" <<'PYEOF' || no "the arms above reported a failure (or the check body could not run)" +import json, os, re, shlex, subprocess, sys +BIN, TMP, EXTRA = sys.argv[1], sys.argv[2], sys.argv[3] +CODE = {"load", "parse", "normalize", "transform", "render", "save", "_helper", "main"} +fails = [] +def ok(msg): print(" PASS " + msg) +def no(msg): print(" FAIL " + msg); fails.append(msg) + +def run(d, *args): + p = subprocess.run([BIN, d, "--no-cache", *args], capture_output=True, text=True) + return p.stdout +def mcp(d, tool, k): + msgs = [{"jsonrpc": "2.0", "id": 1, "method": "initialize"}, + {"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": tool, "arguments": {"path": d}}}] + p = subprocess.run([BIN, "--mcp", "--top-k=%d" % k], input="".join(json.dumps(m) + "\n" for m in msgs), capture_output=True, text=True) + last = [l for l in p.stdout.splitlines() if l.strip()][-1] + return json.loads(last)["result"]["content"][0]["text"] + +ATTR = re.compile(r'([A-Za-z_]+)="([^"]*)"') +def strip_comments(t): return re.sub(r"", "", t, flags=re.S) +def xml_map(t): + """root attrs + rows [(file, t, n, weight)] of an XML map document, in emission order.""" + body = strip_comments(t) + m = re.search(r"]*)?>", body) + root = dict(ATTR.findall(m.group(1) or "")) if m else {} + rows, cur = [], None + for tag in re.finditer(r"<(f|s)\s([^>]*)>", body[m.end():] if m else ""): + a = dict(ATTR.findall(tag.group(2))) + if tag.group(1) == "f": cur = a.get("p") + else: rows.append((cur, a.get("t"), a.get("n"), int(a.get("overloads", "1")))) + return root, rows +def json_map(t): + o = json.loads(t) + rows = [(f["p"], s.get("t"), s.get("n"), int(s.get("overloads", 1))) for f in o.get("r", []) for s in f.get("s", [])] + return o, rows +def code_rows(rows): return len({n for (p, t, n, w) in rows if t == "fn" and n in CODE and p == "app.py"}) +def sec_shown(rows): return sum(w for (p, t, n, w) in rows if t == "sec") +def sec_multiset(rows): return sorted((p, n) for (p, t, n, w) in rows if t == "sec" for _ in range(w)) + +def gq(d, *args): + out = run(d, *args) + m = re.search(r"]*)>", out) + a = dict(ATTR.findall(m.group(1))) if m else {} + body = strip_comments(out) + rows = [] + for s in re.finditer(r"]*)/>", body): + r = dict(ATTR.findall(s.group(1))) + rows.append((r["p"].rsplit(":", 1)[0], r["n"])) + return a, rows +def sections_all(d): + a, _ = gq(d, "--graph-query=kind(all,sec)", "--limit=1") + count = int(a.get("count", "0")) + if count == 0: return 0, [] + _, rows = gq(d, "--graph-query=kind(all,sec)", "--limit=%d" % count) + return count, sorted(rows) + +def gate_d(label, d, root, rows, K, allsec): + """Gate D on one map document: the count identity, the frozen next= spelling, and the paging walk.""" + count, full = allsec + M = sec_shown(rows) + N = count - M + got = root.get("data_sections_cut") + if N == 0: + if got is None and "next" not in root: ok("%s: no Section cut, no data_sections_cut= / next=" % label) + else: no("%s: data_sections_cut=%s next=%s on a map that cut no Section" % (label, got, root.get("next"))) + return + if got != str(N): + no("%s: data_sections_cut=%s, expected %d (= kind(all,sec) count %d - %d Section rows shown)" % (label, got, N, count, M)); return + want = "--graph-query='kind(all,sec)' --offset=%d --limit=%d" % (M, K) + if root.get("next") != want: + no("%s: next=%r, expected %r" % (label, root.get("next"), want)); return + paged, args, pages = [], shlex.split(want), 0 + while True: + a, prow = gq(d, *args) + pages += 1 + if len(prow) > K: no("%s: page %d holds %d rows > K=%d" % (label, pages, len(prow), K)); return + paged += prow + if a.get("has_more") != "1": break + if pages > count + 2: no("%s: paging did not terminate" % label); return + args = [x if not x.startswith("--offset=") else "--offset=" + a["next_offset"] for x in args] + if sorted(paged + sec_multiset(rows)) != full or len(paged) != N: + no("%s: the %d paged Sections + %d shown != the %d indexed (each once)" % (label, len(paged), M, count)); return + ok("%s: data_sections_cut=%d; next= pages exactly the cut Sections in %d page(s) of <= %d" % (label, N, pages, K)) + +def i1(label, d): + """I1: at the smallest K holding every non-Section row, every Section row in it has in-degree > 0.""" + S = int(json.loads(run(d, "--json", "--top-k=1"))["symbols"]) + _, rowsS = json_map(run(d, "--json", "--top-k=%d" % S)) + m = sum(w for (p, t, n, w) in rowsS if t != "sec") + lo, hi = m, S + while lo < hi: + mid = (lo + hi) // 2 + _, r = json_map(run(d, "--json", "--top-k=%d" % mid)) + if sum(w for (p, t, n, w) in r if t != "sec") >= m: hi = mid + else: lo = mid + 1 + _, rj = json_map(run(d, "--json", "--top-k=%d" % lo)) + a, _ = gq(d, "--graph-query=fanin(kind(all,sec),1)", "--limit=1") + nin = int(a.get("count", "0")) + _, insec = gq(d, "--graph-query=fanin(kind(all,sec),1)", "--limit=%d" % max(nin, 1)) if nin else ({}, []) + pool = sorted(insec) + bad = 0 + for x in sec_multiset(rj): + if x in pool: pool.remove(x) + else: bad += 1 + if bad == 0: ok("%s I1: the %d non-Section rows are all in by K=%d, with no in-degree-0 Section ahead of them" % (label, m, lo)) + else: no("%s I1: %d in-degree-0 Section row(s) rank ahead of the last non-Section row (K=%d, %d non-Section rows)" % (label, bad, lo, m)) + +print("== (P) code rows in the emitted top-K, (D) the disclosure, per cell ==") +cells = [(s, l, n) for s in ("S1", "S2", "S3", "S4") for l in ("long", "short") for n in (20, 40, 200)] + [("S3", "long", 220), ("S4", "long", 220)] +for (shape, ln, n) in cells: + d = os.path.join(TMP, "%s-%s-%d" % (shape, ln, n)) + allsec = sections_all(d) + if allsec[0] == 0: + print(" SKIP %s %s N=%d: this build indexes no Section in the data file" % (shape, ln, n)); continue + for K in (200, 16): + lab = "%s %s N=%d K=%d" % (shape, ln, n, K) + xml = run(d, "--top-k=%d" % K) + xroot, xrows = xml_map(xml) + jo, jrows = json_map(run(d, "--json", "--top-k=%d" % K)) + html = run(d, "--html", "--top-k=%d" % K) + hnames = {o["label"] for o in (json.loads(x) for x in re.findall(r'\{"id":\d+,"label":"[^"]*","type":"fn"[^}]*\}', html)) if o["label"] in CODE} + tree = run(d, "--tree") + tfile = re.search(r'= 8 else "-")) + for surf, v in stat.items(): + (ok if v == 8 else no)("%s %s: %d/8 code rows" % (lab, surf, v)) + (ok if tfile and tfile.group(1) == "app.py" else no)("%s tree: code file listed first (%s)" % (lab, tfile.group(1) if tfile else "none")) + gate_d(lab + " xml", d, xroot, xrows, K, allsec) + mk = int(re.search(r"shown=(\d+)", mt).group(1)) + gate_d(lab + " max-tokens", d, mroot, mrows, mk if mk else K, allsec) + for name, other in (("json", {k: str(v) for k, v in jo.items() if k in ("data_sections_cut", "next")}), ("mcp analyze", aroot), ("mcp rank_by", rroot)): + pair = (other.get("data_sections_cut"), other.get("next")) + (ok if pair == (xroot.get("data_sections_cut"), xroot.get("next")) else no)("%s %s: same data_sections_cut=/next= as the XML map %s" % (lab, name, pair)) + troot = dict(ATTR.findall(re.search(r"]*)>", strip_comments(tree)).group(1))) + tsec = len(re.findall(r'", full, flags=re.S)) else no)("the full XML legend defines data_sections_cut=") +(ok if "data_sections_cut=" in "".join(re.findall(r"", compact, flags=re.S)) else no)("the compact legend defines data_sections_cut=") +helpall = subprocess.run([BIN, "--help=all"], capture_output=True, text=True).stdout +(ok if "data_sections_cut=" in helpall else no)("--help=all defines data_sections_cut=") + +print("== (Z) a Section-free tree carries no disclosure ==") +z = run(os.path.join(TMP, "codeonly")) +(ok if "data_sections_cut" not in z and "kind(all,sec)" not in z else no)("code-only map: no data_sections_cut=, no Section next=, no legend clause") +zj = run(os.path.join(TMP, "codeonly"), "--json") +(ok if "data_sections_cut" not in zj else no)("code-only --json map: no data_sections_cut") + +for extra in [x for x in EXTRA.split(":") if x]: + print("== extra root %s ==" % extra) + allsec = sections_all(extra) + for K in (200, 16): + r, rows = xml_map(run(extra, "--top-k=%d" % K)) + gate_d("%s K=%d xml" % (os.path.basename(extra), K), extra, r, rows, K, allsec) + i1(os.path.basename(extra), extra) + +print("mapdatasectioncheck: %d failure(s)" % len(fails)) +sys.exit(1 if fails else 0) +PYEOF +if [ "$fail" -ne 0 ]; then + echo "mapdatasectioncheck: FAILURES ABOVE" + exit 1 +fi +echo "mapdatasectioncheck: all arms PASS" +exit 0 diff --git a/test/mapdatasectionfix/gen.sh b/test/mapdatasectionfix/gen.sh new file mode 100755 index 000000000..5c1c394d3 --- /dev/null +++ b/test/mapdatasectionfix/gen.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# gen.sh OUT SHAPE LEN N — the deterministic fixture generator for test/mapdatasectioncheck.sh. +# +# Writes one fixture tree into OUT (created; must not exist): eight Python functions in app.py — a call chain +# under an uncalled `main`, plus an uncalled `_helper` — and ONE data file whose N entries index as +# SymKind::Section rows: +# S1 db/schema.rb N tables, each an implicit `id`, seven typed columns and `t.timestamps` +# (Rails schema columns; Sections only on a build that carries the #339 capture) +# S2 docs/guide.md N `##` headings (plus the doc file's own whole-file Section) +# S3 config.yaml N top-level keys +# S4 data.json N top-level keys +# LEN picks the data names: `long` names are >= 8 characters and >= 2 words, so priorwt::weight gives them +# the x1.7 specific-name boost; `short` names are one short word (no boost). Code names never change. +# The output is a pure function of the arguments: no dates, no randomness, no environment reads. +set -eu +out="$1"; shape="$2"; len="$3"; n="$4" +case "$shape" in S1|S2|S3|S4) ;; *) echo "gen.sh: SHAPE must be S1|S2|S3|S4" >&2; exit 2 ;; esac +case "$len" in long|short) ;; *) echo "gen.sh: LEN must be long|short" >&2; exit 2 ;; esac +case "$n" in ''|*[!0-9]*) echo "gen.sh: N must be a positive integer" >&2; exit 2 ;; esac +[ -e "$out" ] && { echo "gen.sh: $out exists" >&2; exit 2; } +mkdir -p "$out" + +cat >"$out/app.py" <<'EOF' +def load(path): + with open(path) as handle: + return handle.read() + + +def parse(text): + return text.split() + + +def normalize(item): + return item.strip() + + +def transform(items): + return [normalize(item) for item in items] + + +def render(items): + return "\n".join(items) + + +def save(text, path): + with open(path, "w") as handle: + handle.write(text) + + +def _helper(value): + return value + + +def main(): + data = load("in.txt") + items = transform(parse(data)) + save(render(items), "out.txt") +EOF + +i=1 +case "$shape" in +S1) + mkdir -p "$out/db" + { + printf 'ActiveRecord::Schema[8.1].define(version: 2026_09_30_000001) do\n' + while [ "$i" -le "$n" ]; do + printf ' create_table "table_%d", force: :cascade do |t|\n' "$i" + for j in 1 2 3 4 5 6 7; do + if [ "$len" = long ]; then + printf ' t.string "billing_address_line_%d_%d"\n' "$i" "$j" + else + printf ' t.string "c%d"\n' "$j" + fi + done + printf ' t.timestamps\n end\n\n' + i=$(( i + 1 )) + done + printf 'end\n' + } >"$out/db/schema.rb" + ;; +S2) + mkdir -p "$out/docs" + { + printf '# Guide\n\n' + while [ "$i" -le "$n" ]; do + if [ "$len" = long ]; then printf '## Installation step %d details\n\nText.\n\n' "$i" + else printf '## s%d\n\nText.\n\n' "$i"; fi + i=$(( i + 1 )) + done + } >"$out/docs/guide.md" + ;; +S3) + { + while [ "$i" -le "$n" ]; do + if [ "$len" = long ]; then printf 'database_url_%d: value\n' "$i" + else printf 'k%d: value\n' "$i"; fi + i=$(( i + 1 )) + done + } >"$out/config.yaml" + ;; +S4) + { + printf '{\n' + while [ "$i" -le "$n" ]; do + sep=','; [ "$i" -eq "$n" ] && sep='' + if [ "$len" = long ]; then printf ' "database_url_%d": "value"%s\n' "$i" "$sep" + else printf ' "k%d": "value"%s\n' "$i" "$sep"; fi + i=$(( i + 1 )) + done + printf '}\n' + } >"$out/data.json" + ;; +esac diff --git a/test/regression.sh b/test/regression.sh index ecc896b63..4e3613dd1 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 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 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 fnliteralcheck 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 impactdepthcheck 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 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 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 rubydescribedclasscheck rubyinheritcheck 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 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 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 fnliteralcheck 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 impactdepthcheck 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 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 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 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 rubydescribedclasscheck rubyinheritcheck 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 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 37bf96f80aef61f5653ed6ac6dc303af97f7ac1f Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 1 Oct 2026 03:15:21 -0400 Subject: [PATCH 02/35] fix(edit-check): a C/C++ declaration and its definition are one contract A header prototype `int scale( int x, int factor = 2 )` and its .cpp definition were two contracts to --edit-check: the bare name was refused as ambiguous, the header handle the refusal suggested was refused again, and the definition's handle flagged every caller relying on a default (incompatible="3" on three calls that compile), because the arity test read only the definition's parameter list. A bodyless C/C++ declaration now stands for a definition when name and scope match, the definition's file is the declaration's or #includes it directly (the include proof the file:name widening already uses), linkage allows it, and the parameter-type lists match (read from each signature's source text, names and defaults stripped). Such a declaration group folds into the definition's group on the post-hoc verb (CLI and MCP), and each definition's accepted arity takes the defaults of its matching declaration (defaults_from="decl", defined in both legends). Real overloads stay separate and take defaults only from their own declaration. Anything unproven keeps the previous, stricter answer. The --dry-run/new_body preview keeps one contract per file. Gate: test/editcheckdeclcheck.sh (red on the parent: 8 failures). compactlegendcheck pins ripwire.edit-check/v1, whose fixture probe used to be refused and now answers. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 17 + README.md | 4 +- docs/COMMANDS.md | 2 +- docs/EVALS.md | 6 +- docs/captures/COMMANDS_showcase_2026-09-14.md | 2 +- present/deck5_ripwire_build.js | 6 +- src/compactlegend.h | 2 + src/editcheck.h | 443 +++++++++++++++++- src/editpreview.h | 3 +- src/mcpverbs.h | 3 +- src/verbs_quality.h | 4 +- test/compactlegendcheck.sh | 4 + test/editcheckdeclcheck.sh | 191 ++++++++ test/regression.sh | 2 +- 14 files changed, 660 insertions(+), 29 deletions(-) create mode 100644 test/editcheckdeclcheck.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 4297ac739..b058ae935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,23 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Fixed — `--edit-check` on C/C++: a declaration and its definition are one contract, and the declaration's defaults count + +A header declaring `int scale( int x, int factor = 2 )` and a `.cpp` defining it were two "contracts" to `--edit-check`: +the bare name was refused as ambiguous, and the definition's handle flagged every caller that relied on a default +(`incompatible="3"` on three calls that all compile), because the arity test read only the definition's parameter list. +A bodyless C/C++ declaration now stands for a definition when the name and scope match, the definition's file is the +declaration's or `#include`s it directly, the definition has external linkage (or shares the file), and the parameter-type +lists are equal (names and default values stripped, read from each signature's source text). Such a declaration's group +folds into the definition's, so the bare name and the header's `file:name` answer about the definition, and a call passing +between `params` minus that declaration's defaults and `params` arguments is never flagged. The root then carries +`defaults_from="decl"`, defined in the full and compact legends. Real overloads stay separate: each definition takes +defaults only from the declaration whose types match it. A declaration that cannot be tied that way (an unproven include, +different types, definitions in two files) lends nothing and keeps today's answer. The fold applies to the post-hoc verb +(CLI and MCP `edit_check`); the `--dry-run`/`new_body` preview keeps one contract per file and refuses as before. The test +fixture's own `--edit-check=distance` (declared in `geometry.h`, defined in `geometry.cpp`) now answers instead of refusing. +The session legend dictionary is `dictv=b3fca206914e7dd4 entries=751`. Gate: `test/editcheckdeclcheck.sh`. + ### Added — a memory guard on every root: zero-config, silent on normal runs, a disclosed partial answer past its line (#350, layer 3) diff --git a/README.md b/README.md index daf4382e5..b58c172e7 100644 --- a/README.md +++ b/README.md @@ -2063,9 +2063,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published ### In the tests
-658 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +659 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 **658 gate scripts** and is the authoritative list; +`test/regression.sh` names **659 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/COMMANDS.md b/docs/COMMANDS.md index 03f7ad1c1..c68b3ac3d 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -4398,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=0e543e1e6a3fe37d entries=750 +ripwire legend dictionary ripwire.dict/v1 dictv=b3fca206914e7dd4 entries=751 : 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/EVALS.md b/docs/EVALS.md index e338af94f..8391a11cf 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` | 658 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 659 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) @@ -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 **658 gate scripts**, all of which exist on disk. +naming **659 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`. @@ -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 658. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 659. 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/captures/COMMANDS_showcase_2026-09-14.md b/docs/captures/COMMANDS_showcase_2026-09-14.md index 42cad1d99..d5bfe2155 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=0e543e1e6a3fe37d entries=750 +ripwire legend dictionary ripwire.dict/v1 dictv=b3fca206914e7dd4 entries=751 : 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/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index f5a6ac7b6..35ee018f6 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 = [ - ["658 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 + ["659 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, "658", "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, "659", "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 }){ ["185 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"], - ["658 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["659 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/src/compactlegend.h b/src/compactlegend.h index 8d1ac3d5a..8f970c439 100644 --- a/src/compactlegend.h +++ b/src/compactlegend.h @@ -658,6 +658,8 @@ inline constexpr CompactCompletenessTerm kCompactAttributeReadings[] = // edit-check: src/editcheck.h (the root emit) { "defs", "defs=N: overloads at this site (same file, scope, name) folded into one contract; params compared by MAX", false, "edit-check", MapHeaderRead::No, {}, "edit-check" }, { "shown_unflagged", "shown_unflagged=N: unflagged callers on this page; flagged ones always print, total= counts unflagged only", false, "edit-check", MapHeaderRead::No, {}, "edit-check" }, + // 2026-10-01: present-only — rides only a C/C++ definition whose declaration's defaults widened its accepted arity + { "defaults_from", "defaults_from=decl: a C/C++ declaration of this definition (same types) has defaults; calls within them are never flagged", false, "edit-check", MapHeaderRead::No, {}, "edit-check" }, // exemplar: src/verbs_for.h + src/exemplar.h selectExemplar/pickWinnerOfKind { "candidates", "candidates=N: instances of kind= under the ccx ceiling the pick was ranked from", false, "exemplar", MapHeaderRead::No, {}, "exemplar" }, { "low_confidence", "low_confidence=1: weak task-to-kind match, fell back to fn; pass a kind (fn|method|class...) instead", false, "exemplar", MapHeaderRead::No, {}, "exemplar" }, diff --git a/src/editcheck.h b/src/editcheck.h index b40b3154b..ec81eb879 100644 --- a/src/editcheck.h +++ b/src/editcheck.h @@ -24,6 +24,8 @@ #include "gitstamp.h" // r26-stamp Task A: gitstamp::atAttr — the at="[+dirty]" root anchor #include "graphlegend.h" // §H4 §3.4: the shared counts_floor= marker + floor/counting-unit legend tail #include "pageview.h" // LB-G: pageWindow / effectiveRowCap / pagingDisclosure — the ONE paging vocabulary +#include "infra/ownedfile.h" // the signature-span read behind the C/C++ declaration/definition identity +#include "infra/namesplit.h" // namesplit::isIdentChar — the ONE ASCII identifier-byte test #include #include @@ -107,13 +109,294 @@ inline std::vector editCheckOverloadSet( const IngestResult& ing, const return overloadNodes; } +// ── C/C++: A DECLARATION AND THE DEFINITION IT DECLARES ARE ONE CONTRACT (2026-10-01) ──────────────────── +// The field report: `int scale( int x, int factor = 2 );` in lib.h, its definition in lib.cpp, three callers passing 1, +// 1 and 2 arguments, and a trailing `int bias = 0` added to both. Every call still compiles. The verb (1) refused the +// bare name as "2 distinct contracts" — the prototype and its definition sit in two files, and a contract was keyed +// per (file, scope) — (2) answered the definition's handle with incompatible="3", because the arity test below read +// the DEFINITION's parameter list, which carries no defaults (C++ puts them on the declaration a caller sees), and +// (3) refused the header handle the refusal itself had suggested. +// +// THE IDENTITY RULE. A bodyless C-family declaration D stands for a bodied definition F when ALL of these hold: +// * same name and same `scope` — the (scope, name) gather declToDefCandidates already uses; +// * F's file is D's file, or F's file #includes D's file directly — includeProofOfDeclFiles, the SAME positive proof +// the file:name selector widening keeps a candidate by (an unproven pair is never joined: test (I)); +// * F has external linkage, unless D sits in F's own file (keepProvenCandidates' clause 3); +// * the PARAMETER-TYPE LISTS are equal — the declared types, names and default values stripped. This is what keeps +// real overloads apart: `scale( int, int = 2 )` never lends its default to `scale( double, int )`. +// The types are read off the SOURCE TEXT of each signature span (no parameter-type fact is indexed). A list the reader +// cannot parse into exactly `params` entries — a function-pointer parameter, a macro-built list, a stale span — matches +// nothing, which is the pre-existing answer: the pair stays two contracts and no default is borrowed. +// +// A false "compatible" is worse than a false "incompatible" here (the flag is the verb's one call-site signal), so every +// rule above fails CLOSED: anything unproven keeps today's stricter reading. +// +// `EditCheckSpliced` is the pre-apply preview's one in-memory file: the merged tree's spans for that file index the +// spliced bytes, not the disk. +struct EditCheckSpliced +{ + std::string_view bytes; + std::uint32_t fileId; + bool engaged; +}; + +// The signature text of `s`: [sigStartByte, sigEndByte) for a definition (up to its body), the whole span for a +// declaration. Capped — a signature is never this long, and the parse below refuses a list it cannot close. +inline std::string editCheckSignatureText( const IngestResult& ing, const Symbol& s, const EditCheckSpliced& spliced ) +{ + constexpr std::uint32_t kSigCap = 16384; + const std::uint32_t end = isDefinitionNotDeclaration( s ) ? s.sigEndByte : s.endByte; + if( end <= s.sigStartByte ) + { + return {}; + } + const std::uint32_t len = std::min( end - s.sigStartByte, kSigCap ); + if( spliced.engaged && spliced.fileId == s.fileId ) + { + return s.sigStartByte < spliced.bytes.size() ? std::string( spliced.bytes.substr( s.sigStartByte, len ) ) : std::string(); + } + OwnedFile in = openOwnedFile( diskPath( ing, s.fileId ).c_str(), "rb" ); + if( !in || s.sigStartByte > 0x7fffffffu || std::fseek( in.file, long( s.sigStartByte ), SEEK_SET ) != 0 ) + { + return {}; + } + std::string text( len, '\0' ); + text.resize( std::fread( text.data(), 1, len, in.file ) ); + return text; +} + +// One parameter's text as tokens: identifiers (with `::` kept inside them) and single punctuation bytes, so whitespace +// never decides equality. An attribute (`[[maybe_unused]]`) is not part of the type and is skipped. +inline std::vector editCheckTokens( std::string_view param ) +{ + const auto identAt = [ & ]( std::size_t i ) + { + return namesplit::isIdentChar( param[i] ) || ( param[i] == ':' && i + 1 < param.size() && param[ i + 1 ] == ':' ); + }; + std::vector toks; + std::size_t i = 0; + while( i < param.size() ) + { + if( param.substr( i, 2 ) == "[[" ) + { + const std::size_t close = param.find( "]]", i + 2 ); + i = ( close == std::string_view::npos ) ? param.size() : close + 2; + continue; + } + if( !identAt( i ) ) + { + if( param[i] != ' ' && param[i] != '\t' && param[i] != '\n' && param[i] != '\r' ) + { + toks.emplace_back( 1, param[i] ); + } + ++i; + continue; + } + std::string tok; + while( i < param.size() && identAt( i ) ) + { + const std::size_t step = ( param[i] == ':' ) ? 2u : 1u; + tok.append( param.substr( i, step ) ); + i += step; + } + toks.push_back( std::move( tok ) ); + } + return toks; +} + +// One parameter's TYPE: its tokens minus the declarator name, which is dropped when it is the last identifier (before +// an array suffix, if any) and a type token precedes it — `int x` -> `int`, `const Foo& f` -> `const Foo &`, +// `int a[3]` -> `int [ 3 ]`. A keyword type is never mistaken for a name (`unsigned long` keeps both), and a lone +// qualifier is not a type (`const Foo` keeps `Foo`). +inline std::vector editCheckTypeTokens( std::string_view param ) +{ + static constexpr std::string_view kTypeWords[] = { "int", "long", "short", "char", "unsigned", "signed", "double", "float", "bool", + "void", "auto", "wchar_t", "char8_t", "char16_t", "char32_t" }; + static constexpr std::string_view kQualWords[] = { "const", "volatile", "struct", "class", "enum", "union", "typename", "register", + "restrict", "__restrict" }; + const auto in = []( const std::string& t, std::span set ) { return std::find( set.begin(), set.end(), t ) != set.end(); }; + + std::vector toks = editCheckTokens( param ); + const auto suffix = std::find( toks.begin(), toks.end(), std::string( "[" ) ); + const std::size_t nameAt = ( !toks.empty() && toks.back() == "]" ) ? std::size_t( suffix - toks.begin() ) : toks.size(); + if( nameAt < 2 ) + { + return toks; + } + const std::string& name = toks[ nameAt - 1 ]; + const bool typeBefore = std::any_of( toks.begin(), toks.begin() + std::ptrdiff_t( nameAt - 1 ), + [ & ]( const std::string& t ) { return !in( t, kQualWords ); } ); + if( namesplit::isIdentChar( name[0] ) && !in( name, kTypeWords ) && !in( name, kQualWords ) && typeBefore ) + { + toks.erase( toks.begin() + std::ptrdiff_t( nameAt - 1 ) ); + } + return toks; +} + +// Where the parameter list of `name` opens in a signature's text: the first '(' after a whole-word occurrence of the +// name (whitespace between allowed), or npos. +inline std::size_t editCheckParamOpen( std::string_view text, std::string_view name ) +{ + for( std::size_t at = name.empty() ? std::string_view::npos : text.find( name ); at != std::string_view::npos; at = text.find( name, at + 1 ) ) + { + const std::size_t after = text.find_first_not_of( " \t\r\n", at + name.size() ); + if( ( at == 0 || !namesplit::isIdentChar( text[ at - 1 ] ) ) && after != std::string_view::npos && text[after] == '(' ) + { + return after; + } + } + return std::string_view::npos; +} + +// The raw parameters between the '(' at `open` and its matching ')', each with whether it carries a default. Split at +// depth-0 commas; '<'/'>' nest only in a TYPE, never after its '=' (a default value may compare). Empty, with +// `closed` false, when the list never closes. +struct EditCheckRawParams +{ + std::vector text; + std::vector defaulted; + bool closed; +}; + +inline EditCheckRawParams editCheckSplitParams( std::string_view text, std::size_t open ) +{ + EditCheckRawParams raw{}; + int depth = 0, angle = 0; + bool inDefault = false; + std::size_t start = open + 1; + for( std::size_t i = open + 1; i < text.size() && !raw.closed; ++i ) + { + const char c = text[i]; + const bool atTop = depth == 0 && ( angle == 0 || inDefault ); + const bool assigns = c == '=' && depth == 0 && angle == 0 && text.substr( i - 1, 3 ).find( "==" ) == std::string_view::npos + && std::string_view( "!<>" ).find( text[ i - 1 ] ) == std::string_view::npos; + if( atTop && ( c == ')' || c == ',' ) ) + { + raw.text.push_back( text.substr( start, i - start ) ); + raw.defaulted.push_back( char( inDefault ? 1 : 0 ) ); + start = i + 1; + inDefault = false; + angle = 0; + raw.closed = ( c == ')' ); + continue; + } + depth += ( c == '(' || c == '[' || c == '{' ) ? 1 : ( c == ')' || c == ']' || c == '}' ) ? -1 : 0; + angle += ( inDefault || depth != 0 ) ? 0 : ( c == '<' ) ? 1 : ( c == '>' && angle > 0 ) ? -1 : 0; + inDefault = inDefault || assigns; + } + return raw; +} + +// The parsed parameter list of one signature: each parameter's type and how many carry a default. `ok` is false when +// the text does not hold `name( … )`, the list does not close, it is variadic, or it does not split into exactly +// `params` entries — and a list that is not ok matches nothing. +struct EditCheckParamList +{ + std::vector> types; + std::uint16_t defaulted; + bool ok; +}; + +inline EditCheckParamList editCheckParamList( std::string_view text, const Symbol& s ) +{ + EditCheckParamList res{}; + const std::size_t open = editCheckParamOpen( text, s.name ); + const EditCheckRawParams raw = ( open == std::string_view::npos ) ? EditCheckRawParams{} : editCheckSplitParams( text, open ); + if( !raw.closed ) + { + return res; + } + for( std::size_t k = 0; k < raw.text.size(); ++k ) + { + const std::string_view param = raw.defaulted[k] ? raw.text[k].substr( 0, raw.text[k].find( '=' ) ) : raw.text[k]; + if( param.find( "..." ) != std::string_view::npos ) + { + return res; // variadic: never a fixed list + } + res.defaulted += raw.defaulted[k] ? 1u : 0u; + res.types.push_back( editCheckTypeTokens( param ) ); + } + if( res.types.size() == 1 && ( res.types[0].empty() || res.types[0] == std::vector{ "void" } ) ) + { + res.types.clear(); // `()` and `(void)`: no parameter + } + res.ok = res.types.size() == s.params; + return res; +} + +// THE IDENTITY RULE above, for one (declaration, definition) pair: the declaration's default count when `decl` declares +// `def`, else nullopt. `defList` is the definition's own parsed list, read once by the caller. +inline std::optional editCheckDeclDeclares( const IngestResult& ing, NodeId decl, NodeId def, const EditCheckParamList& defList, + const EditCheckSpliced& spliced ) +{ + const Symbol& d = ing.symbols[ decl ]; + const Symbol& f = ing.symbols[ def ]; + const bool shapeFits = defList.ok && d.name == f.name && d.scope == f.scope && langCompatible( d.lang, Lang::C ) && langCompatible( f.lang, Lang::C ) + && !isDefinitionNotDeclaration( d ) && isDefinitionNotDeclaration( f ) && d.params == f.params + && ( f.internalLinkage == 0 || d.fileId == f.fileId ); + if( !shapeFits || ( d.fileId != f.fileId && includeProofOfDeclFiles( ing, { decl }, { def } )[ f.fileId ] == 0 ) ) + { + return std::nullopt; + } + const EditCheckParamList declList = editCheckParamList( editCheckSignatureText( ing, d, spliced ), d ); + if( !declList.ok || declList.types != defList.types ) + { + return std::nullopt; + } + return declList.defaulted; +} + +// Per member of the overload set, the FEWEST arguments a call may pass: `params` minus the defaults of the matching +// declaration that carries the most of them (C++ lets a redeclaration add defaults; the widest reading is the one-sided +// one). `fromDecl` is true when any member's range was widened that way — the root's defaults_from="decl". +struct EditCheckDeclDefaults +{ + std::vector minArity; // parallel to the overload set + bool fromDecl; +}; + +// One definition's minimum: only a fixed-arity C-family DEFINITION can be widened — anything else is already a +// wildcard (arityExact 0) or has no declaration to read. +inline std::uint16_t editCheckMinArity( const IngestResult& ing, NodeId def, const EditCheckSpliced& spliced ) +{ + const Symbol& f = ing.symbols[ def ]; + if( !langCompatible( f.lang, Lang::C ) || f.arityExact == 0 || !isDefinitionNotDeclaration( f ) ) + { + return f.params; + } + const EditCheckParamList defList = editCheckParamList( editCheckSignatureText( ing, f, spliced ), f ); + std::uint16_t minArity = f.params; + for( NodeId cand = 0; defList.ok && cand < ing.symbols.size(); ++cand ) + { + const std::optional defaulted = ( ing.symbols[ cand ].name == f.name ) + ? editCheckDeclDeclares( ing, cand, def, defList, spliced ) : std::nullopt; + minArity = std::min( minArity, std::uint16_t( f.params - std::min( defaulted.value_or( 0 ), f.params ) ) ); + } + return minArity; +} + +inline EditCheckDeclDefaults editCheckDeclDefaults( const IngestResult& ing, std::span overloadNodes, const EditCheckSpliced& spliced ) +{ + EditCheckDeclDefaults res{}; + res.minArity.reserve( overloadNodes.size() ); + for( NodeId ov : overloadNodes ) + { + res.minArity.push_back( editCheckMinArity( ing, ov, spliced ) ); + res.fromDecl = res.fromDecl || res.minArity.back() < ing.symbols[ ov ].params; + } + ENSURES( res.minArity.size() == overloadNodes.size() ); + return res; +} + // ── §A6a: the DISTINCT contracts one --edit-check selector matched ─────────────────────────────────────── // A contract is per definition site. --callers may honestly UNION the callers of every overload (it says so: // defs="3"); this verb may not — "did I break a contract?" answered about a definition the agent never edited // is worse than no answer, because status="unchanged" reads as reassurance. So a selector that matches more // than one definition SITE is REFUSED, and the refusal hands back the spellings that pick one. // -// The group key is (file, scope) — see editCheckOverloadSet on why canonId alone is not enough. `spelling` is +// The group key is (file, scope) — see editCheckOverloadSet on why canonId alone is not enough — with ONE exception for +// the post-hoc verb: a C/C++ declaration group folds into the definition group it provably declares (editCheckFoldDeclGroups, +// the identity rule above editCheckDeclDeclares), because a prototype and its definition are one contract. `spelling` is // what the caller should retype: `file:name` when that file holds exactly one group, else the canonical id // (both resolve through resolveAllByNameQualified). struct EditCheckGroup @@ -122,12 +405,88 @@ struct EditCheckGroup std::string spelling; // a selector that resolves to THIS group and no other }; -inline std::vector editCheckGroups( const IngestResult& ing, const Graph& g, std::span matches ) +// The (file, contract) KEYS of a match list with each key's members, in first-seen order: matches arrive in ascending +// node id (resolveAllByNameQualified walks ing.symbols in order), so members[i][0] IS group i's lowest id. +struct EditCheckGroupKeys { - // one pass, keyed by (fileId, canonId): matches arrive in ascending node id (resolveAllByNameQualified - // walks ing.symbols in order), so the first node seen for a key IS the group's lowest id. - std::vector groups; std::vector> keys; + std::vector> members; +}; + +// THE FOLD (2026-10-01, the decl/def identity rule above editCheckDeclDeclares): a group made only of C/C++ declarations +// is dropped when every one of them declares a definition in the match list AND all of those definitions sit in ONE +// other group — the declaration and its definition are then one contract, answered at the definition. A declaration +// that declares nothing in the list, or definitions in two groups (one header, a posix.cpp and a win.cpp), keeps its +// group: those are distinct contracts and the refusal still names them. +// The ONE group every declaration of group `groupIndex` declares into, or groupCount when some declaration declares +// nothing in the list or the declarations land in two groups. `defListOf` parses each definition's list once. +template +inline std::size_t editCheckDeclTargetGroup( const IngestResult& ing, const EditCheckGroupKeys& gk, std::size_t groupIndex, DefListOf& defListOf ) +{ + const std::size_t groupCount = gk.keys.size(); + std::size_t target = groupCount; + for( NodeId decl : gk.members[ groupIndex ] ) + { + bool declares = false; + for( std::size_t other = 0; other < groupCount; ++other ) + { + const bool hits = other != groupIndex && std::any_of( gk.members[ other ].begin(), gk.members[ other ].end(), [ & ]( NodeId def ) + { + return isDefinitionNotDeclaration( ing.symbols[ def ] ) && editCheckDeclDeclares( ing, decl, def, defListOf( def ), {} ).has_value(); + } ); + if( hits && target != groupCount && target != other ) + { + return groupCount; // definitions in two groups: two contracts, nothing to fold into + } + target = hits ? other : target; + declares = declares || hits; + } + if( !declares ) + { + return groupCount; + } + } + return target; +} + +inline void editCheckFoldDeclGroups( const IngestResult& ing, EditCheckGroupKeys& gk ) +{ + const std::size_t groupCount = gk.keys.size(); + if( groupCount < 2 ) + { + return; + } + std::vector> defLists; // each definition's list, parsed once + const auto defListOf = [ & ]( NodeId def ) -> const EditCheckParamList& + { + const auto at = std::find_if( defLists.begin(), defLists.end(), [ def ]( const auto& entry ) { return entry.first == def; } ); + if( at != defLists.end() ) + { + return at->second; + } + defLists.emplace_back( def, editCheckParamList( editCheckSignatureText( ing, ing.symbols[ def ], {} ), ing.symbols[ def ] ) ); + return defLists.back().second; + }; + + EditCheckGroupKeys kept; + for( std::size_t groupIndex = 0; groupIndex < groupCount; ++groupIndex ) + { + const bool declOnly = std::all_of( gk.members[ groupIndex ].begin(), gk.members[ groupIndex ].end(), [ & ]( NodeId m ) + { return langCompatible( ing.symbols[m].lang, Lang::C ) && !isDefinitionNotDeclaration( ing.symbols[m] ); } ); + if( declOnly && editCheckDeclTargetGroup( ing, gk, groupIndex, defListOf ) < groupCount ) + { + continue; // folded into the definition group it declares + } + kept.keys.push_back( gk.keys[ groupIndex ] ); + kept.members.push_back( gk.members[ groupIndex ] ); + } + ENSURES( !kept.keys.empty() ); // a folded group always names a kept one as its target + gk = std::move( kept ); +} + +inline EditCheckGroupKeys editCheckGroupKeys( const IngestResult& ing, const Graph& g, std::span matches, bool foldDecls ) +{ + EditCheckGroupKeys gk; for( NodeId m : matches ) { if( m >= ing.symbols.size() ) @@ -136,12 +495,31 @@ inline std::vector editCheckGroups( const IngestResult& ing, con } const std::uint32_t fileId = ing.symbols[m].fileId; const std::string canon( editCheckContractId( ( m < g.canonId.size() ) ? g.canonId[m] : ing.symbols[m].name, ing.symbols[m] ) ); - if( std::find( keys.begin(), keys.end(), std::make_pair( fileId, canon ) ) != keys.end() ) + const auto at = std::find( gk.keys.begin(), gk.keys.end(), std::make_pair( fileId, canon ) ); + if( at != gk.keys.end() ) { + gk.members[ std::size_t( at - gk.keys.begin() ) ].push_back( m ); continue; } - keys.emplace_back( fileId, canon ); - groups.push_back( EditCheckGroup{ m, std::string{} } ); + gk.keys.emplace_back( fileId, canon ); + gk.members.push_back( { m } ); + } + if( foldDecls ) + { + editCheckFoldDeclGroups( ing, gk ); + } + return gk; +} + +// `foldDecls` is the post-hoc verb's identity rule (editCheckFoldDeclGroups); the pre-apply preview and the other +// readers of these groups (the write verbs, --slice) keep one group per (file, scope), so their answers are unchanged. +inline std::vector editCheckGroups( const IngestResult& ing, const Graph& g, std::span matches, bool foldDecls = false ) +{ + const EditCheckGroupKeys gk = editCheckGroupKeys( ing, g, matches, foldDecls ); + std::vector groups; + for( const std::vector& members : gk.members ) + { + groups.push_back( EditCheckGroup{ members.front(), std::string{} } ); } // the spelling: file:name is the form an agent can paste from any p="file:line" row, so prefer it and fall @@ -150,9 +528,9 @@ inline std::vector editCheckGroups( const IngestResult& ing, con for( std::size_t groupIndex = 0; groupIndex < groups.size(); ++groupIndex ) { const Symbol& s = ing.symbols[ groups[ groupIndex ].lowestNode ]; - const std::string& canonOfThis = keys[ groupIndex ].second; + const std::string& canonOfThis = gk.keys[ groupIndex ].second; std::size_t groupsInFile = 0; - for( const auto& [fileId, canon] : keys ) + for( const auto& [fileId, canon] : gk.keys ) { if( fileId == s.fileId ) { @@ -431,6 +809,15 @@ inline bool editCheckImplicitReceiver( const Symbol& s ) noexcept return ( s.lang == Lang::Python || s.lang == Lang::Ruby ) && !s.scope.empty(); } +// The arity half of the incompatibility test for one member of the overload set: can a call passing `argCount` +// arguments bind it? A variadic/defaulted definition (arityExact 0) and an implicit receiver are wildcards; a C/C++ +// definition whose declaration carries defaults accepts [minArity, params] (editCheckDeclDefaults); every other +// definition's minArity IS its params, so for it this is the exact test. +inline bool editCheckArityAccepts( const Symbol& os, std::uint16_t minArity, std::uint16_t argCount ) noexcept +{ + return os.arityExact == 0 || editCheckImplicitReceiver( os ) || ( minArity <= argCount && argCount <= os.params ); +} + // ── THE CALLEE TEST, with the Elixir arity fold ────────────────────────────────────────────────────────── // "Does this call reference name the focus's contract?" For every language but Elixir it is the exact // calleeName == name test the in-edge walk already implied, byte for byte. Elixir keys a callable by `name/N`, @@ -448,9 +835,10 @@ struct EditCheckCalleeTest std::span overloadNodes; std::optional logical; // engaged for an Elixir focus only std::vector scratch; + EditCheckDeclDefaults declDefaults; // per overload: the fewest arguments a matching C/C++ declaration admits EditCheckCalleeTest( const IngestResult& input, const Symbol& focusSymbol, std::span overloads ) - : ing( input ), focus( focusSymbol ), overloadNodes( overloads ) + : ing( input ), focus( focusSymbol ), overloadNodes( overloads ), declDefaults( editCheckDeclDefaults( input, overloads, {} ) ) { if( focus.lang == Lang::Elixir ) { @@ -492,12 +880,14 @@ struct EditCheckCalleeTest inline std::vector editCheckIncompatibleFlags( const IngestResult& ing, std::span overloadNodes, EditCheckCalleeTest& callee, std::span seenCaller ) { + // a C/C++ definition whose matching declaration carries defaults accepts the RANGE [minArity, params] + // (editCheckDeclDefaults); every other definition's minArity is its params, so the test below is the exact one + ASSUME( callee.declDefaults.minArity.size() == overloadNodes.size() ); const auto provenIncompatible = [ & ]( std::uint16_t argCount ) -> bool { - for( NodeId ov : overloadNodes ) + for( std::size_t k = 0; k < overloadNodes.size(); ++k ) { - if( const Symbol& os = ing.symbols[ov]; - os.arityExact == 0 || editCheckImplicitReceiver( os ) || os.params == argCount ) + if( editCheckArityAccepts( ing.symbols[ overloadNodes[k] ], callee.declDefaults.minArity[k], argCount ) ) { return false; // a candidate could still accept it } @@ -708,6 +1098,15 @@ inline constexpr const char* kEditCheckWindowLegend = "limit=N (offset=M pages, and a page past the end reads shown_unflagged=\"0\" with has_more=\"0\"); on the root, limit=\"0\" " "means no explicit limit was given and the verb's own default page size shaped the window — never a zero-row page. "; +// defaults_from="decl" (2026-10-01): the clause that reads it, emitted only beside it. No double hyphen (G4). +inline constexpr const char* kEditCheckDefaultsFromDeclLegend = + "defaults_from=\"decl\": a C/C++ definition's own parameter list carries no default, but a DECLARATION of it does: same " + "name and scope, the same parameter types, in the definition's file or a file it #includes directly. The declaration " + "and the definition are one contract (the bare name and the declaration's file:name answer about the definition), and " + "a call passing from params minus that declaration's defaults up to params arguments is accepted, so it is never " + "flagged. A declaration that cannot be tied that way lends nothing, and its definition's callers are judged against " + "the definition's own list. "; + // The root attributes that clause defines: rule 1's noun-prefixed pair plus rule 6's paging half, composed // in ONE place so the pair and the half cannot come apart. Empty when the window is inactive, which is what // keeps an answer that fits byte-identical to what it was. @@ -826,7 +1225,9 @@ inline std::string editCheckBundleText( const IngestResult& ing, const Graph& g, // for emptiness (main.cpp's MainDispatch::notesDegraded), so a sidecar // that left EVERY line unparsed still reaches this root. Defaults false, // matching `ni`'s own nullptr default (the MCP verb passes neither today). - bool notesDegraded = false ) + bool notesDegraded = false, + // the pre-apply preview's spliced file: its spans index these bytes, not the disk + const EditCheckSpliced& spliced = {} ) { const Symbol& fsym = ing.symbols[ focus ]; // R-E (2026-08-17 harvest): same single-root condition every other verb's root= uses (sarif.h) — the ONE @@ -841,6 +1242,10 @@ inline std::string editCheckBundleText( const IngestResult& ing, const Graph& g, const std::vector overloadNodes = editCheckOverloadSet( ing, g, focus ); const EditCheckContract contract = editCheckContractVsHead( ing, g, root, maxFileBytes, excludes, focus, overloadNodes ); EditCheckCalleeTest callee( ing, fsym, overloadNodes ); + if( spliced.engaged ) + { + callee.declDefaults = editCheckDeclDefaults( ing, overloadNodes, spliced ); // the preview's spans index the spliced bytes + } const auto [ callerIds, callerIncompatible ] = editCheckCallers( ing, g, overloadNodes, callee ); // the flagged-caller COUNT, needed BEFORE the headline is written: the verdict joins it with the was/now @@ -949,6 +1354,10 @@ inline std::string editCheckBundleText( const IngestResult& ing, const Graph& g, { out += kEditCheckWindowLegend; } + if( callee.declDefaults.fromDecl ) + { + out += kEditCheckDefaultsFromDeclLegend; + } if( contract.defsUnmeasured ) { out += "defs_unmeasured=\"1\": the HEAD baseline carries no definition COUNT for this symbol (a cache defect), so that " @@ -1013,6 +1422,10 @@ inline std::string editCheckBundleText( const IngestResult& ing, const Graph& g, char callersOpen[ 64 ]; rw::formatTo( callersOpen, sizeof( callersOpen ), " callers=\"{}\" incompatible=\"{}\"", callerIds.size(), incompatibleCount ); out += callersOpen; + if( callee.declDefaults.fromDecl ) + { + out += " defaults_from=\"decl\""; // beside the incompatible= it widened; defined by the clause emitted only with it + } out += unprovenDefsAttrXml( unprovenDefs ); // H1: beside the incompatible= it qualifies; absent at zero out += declinedCallsAttrXml( declinedCalls ); // callers the resolver declined to bind that could have meant it; absent at zero // r26-stamp Task A: the HEAD baseline this contract compares against is only meaningful pinned to a diff --git a/src/editpreview.h b/src/editpreview.h index 9178905bb..229892885 100644 --- a/src/editpreview.h +++ b/src/editpreview.h @@ -458,7 +458,8 @@ inline Outcome run( const IngestResult& ing, const Graph& g, const std::string& // differently from the answer it predicts would be worth nothing (test/editpreviewcheck.sh compares // the two documents). oc.xml = editCheckBundleText( merged, mg, root, maxFileBytes, excludes, groups[0].lowestNode, ni, true, - pageLimit, pageOffset, previewUnprovenDefs ); + pageLimit, pageOffset, previewUnprovenDefs, /*notesDegraded=*/false, + EditCheckSpliced{ newBytes, fsym.fileId, true } ); // the merged spans index the spliced bytes // E3 (terminality round A, 2026-09-05): the CURRENT span an apply would replace, as the bytes are on disk, so // the Read an agent makes before an edit "to see what I am about to overwrite" is already in the preview. // Appended as the last child of the preview's own root — the post-hoc document cannot carry it (after the diff --git a/src/mcpverbs.h b/src/mcpverbs.h index 27c66da36..748a1d876 100644 --- a/src/mcpverbs.h +++ b/src/mcpverbs.h @@ -4244,7 +4244,8 @@ inline EditCheckReply editCheckText( const std::string& root, const std::string& mcprefuse::notFoundHintFor( "edit_check", "symbol" ) ) }; } - const std::vector groups = editCheckGroups( ing, g, matches ); + // the CLI's rule: the declaration/definition fold for the post-hoc answer, one group per (file, scope) for new_body + const std::vector groups = editCheckGroups( ing, g, matches, /*foldDecls=*/newBody.empty() ); if( groups.size() > 1 ) { return EditCheckReply { {}, editCheckAmbiguousMessage( symbol, groups, "symbol=", matches.size() ) }; diff --git a/src/verbs_quality.h b/src/verbs_quality.h index dfcd5de39..0d7273244 100644 --- a/src/verbs_quality.h +++ b/src/verbs_quality.h @@ -2263,7 +2263,9 @@ std::optional runEditCheck( const MainDispatch& d ) return 1; } - const std::vector groups = editCheckGroups( ing, d.g, matches ); + // the C/C++ declaration/definition fold is the post-hoc verb's (editcheck.h editCheckFoldDeclGroups); the preview + // below splices over ONE definition span, so it keeps one group per (file, scope) and refuses as before + const std::vector groups = editCheckGroups( ing, d.g, matches, /*foldDecls=*/!editPreviewRequested( cfg ) ); if( groups.size() > 1 ) { rw::emitTo( stderr, "ripwire: --edit-check: {}\n", diff --git a/test/compactlegendcheck.sh b/test/compactlegendcheck.sh index 084e862ac..2b262cd59 100755 --- a/test/compactlegendcheck.sh +++ b/test/compactlegendcheck.sh @@ -498,9 +498,13 @@ probeFor() # code_not_flow_scanned=); the gate now runs under an empty HOME of its own, so the probe measures the same bytes everywhere. # RE-PINNED 2026-09-30 (review M5): 380 -> 530 (measured 518, hermetic HOME). The probe is a bare --scan-skills, whose answer # now names the directories it walked (dirs=) and whose legend defines it (+149 B); the value is in the root, not the legend. +# PINNED 2026-10-01 (C/C++ declaration/definition fold): ripwire.edit-check/v1 610 (measured 595). The --edit-check=distance +# probe used to be REFUSED — geometry.h declares distance and geometry.cpp defines it, and the verb counted the prototype and +# its definition as two contracts — so the schema had no XML answer to pin. It now answers about the definition. # the pins follow the definitions, measured + 10 rounded up to 10. # schema pin measured PIN_TABLE=' +ripwire.edit-check/v1 610 595 ripwire.map/v1 910 892 ripwire.map-diff/v1 900 885 ripwire.pack-signatures/v1 770 759 diff --git a/test/editcheckdeclcheck.sh b/test/editcheckdeclcheck.sh new file mode 100644 index 000000000..31c76b7ee --- /dev/null +++ b/test/editcheckdeclcheck.sh @@ -0,0 +1,191 @@ +#!/usr/bin/env bash +# editcheckdeclcheck.sh — --edit-check on C/C++: a declaration and the definition it declares are ONE contract. +# +# The field report: a header declares `int scale( int x, int factor = 2 )`, the .cpp defines it, three callers pass 1, 1 +# and 2 arguments, and a trailing `int bias = 0` is added to both. Every call still compiles. The verb answered: +# R1 --edit-check=scale refused as "2 distinct contracts" (the prototype and its definition); +# R2 --edit-check=./lib.cpp:scale incompatible="3" — the defaults live on the HEADER declaration and the arity test +# read the definition's parameter list only; +# R3 --edit-check=./lib.h:scale refused again — the handle the refusal itself suggested. +# +# Arms: +# (A) R1: the bare name answers (exit 0) about the definition, no refusal. +# (B) R2: the definition's handle reports incompatible="0", flags no caller, and says where the defaults came from +# (defaults_from="decl", defined in the same document's legend). +# (C) R3: the header handle answers too, with the same contract as (B). +# (D) the field-report shape on the MCP twin (edit_check symbol=scale): incompatible="0". +# (E) CONTROL — defaults never make a wildcard: a call with MORE arguments than the parameter list is still flagged, +# and only that caller is. +# (F) E3 decoy — two real overloads, scale(int,int=2,int=3) and scale(double,double,double,double): they stay two +# definitions (defs="2"), each takes defaults only from ITS declaration (a 1-argument call is accepted by the int +# overload alone, a 5-argument call by neither). +# (G) a declaration whose parameter TYPES match no definition is not folded and lends no defaults: the 1-argument +# call against `scale( double, int )` stays flagged, and the bare name stays refused. +# (I) an unproven declaration (the definition's file does not include the header) lends no defaults. +# +# Operates on private temp git repos. Needs git and python3. +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" +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"; exit 2; } +command -v git >/dev/null 2>&1 || { echo "git required"; exit 2; } +command -v python3 >/dev/null 2>&1 || { echo "python3 required"; exit 2; } + +TMP="$( mktemp -d )"; trap 'rm -rf "$TMP"' EXIT +echo "editcheckdeclcheck: BIN=$BIN" + +commit(){ ( cd "$1" && git init -q && git config user.email t@t && git config user.name t && git add -A && git commit -qm init ) >/dev/null 2>&1 \ + || no "could not commit $1 — every arm on it would read no-baseline"; } +# ec → the document on stdout (full legend), stderr to $TMP/err, exit code to $TMP/rc +ec(){ ( cd "$1" && "$BIN" . --no-cache --edit-check="$2" --legend=full 2>"$TMP/err"; echo $? >"$TMP/rc" ); } +rc(){ cat "$TMP/rc"; } +# the root element, and one attribute off it (empty when absent) +root(){ printf '%s' "$1" | grep -oE ']*>' | head -1; } +attr(){ printf '%s' "$1" | python3 -c 'import re,sys; m=re.search(r"(?]*incompatible=\"1\"",sys.stdin.read())))'; } +legend(){ printf '%s' "$1" | python3 -c 'import re,sys; m=re.match(r"\A(?:\s*)+",sys.stdin.read(),re.S); sys.stdout.write(m.group(0) if m else "")'; } + +# ── the field-report corpus ─────────────────────────────────────────────────────────────────────────────── +F="$TMP/field"; mkdir -p "$F" +printf 'int scale( int x, int factor = 2 );\n' >"$F/lib.h" +printf '#include "lib.h"\nint scale( int x, int factor )\n{\n return x * factor;\n}\n' >"$F/lib.cpp" +printf '#include "lib.h"\nint a() { return scale( 1 ); }\nint b() { return scale( 2 ); }\nint c() { return scale( 3, 4 ); }\n' >"$F/use.cpp" +commit "$F" +printf 'int scale( int x, int factor = 2, int bias = 0 );\n' >"$F/lib.h" +printf '#include "lib.h"\nint scale( int x, int factor, int bias )\n{\n return x * factor + bias;\n}\n' >"$F/lib.cpp" + +echo "=== (A) R1: the bare name is one contract, not two ===" +OA="$( ec "$F" scale )"; RCA="$( rc )"; RA="$( root "$OA" )" +if [ "$RCA" = 0 ] && [ -n "$RA" ]; then + ok "(A) --edit-check=scale answers (exit 0) instead of refusing a prototype and its definition as two contracts" + case "$( attr "$RA" p )" in + lib.cpp:2) ok "(A) the answer is about the definition (p=\"lib.cpp:2\")" ;; + *) no "(A) p=\"$( attr "$RA" p )\", expected the definition lib.cpp:2" ;; + esac + [ "$( attr "$RA" incompatible )" = 0 ] \ + && ok "(A) incompatible=\"0\": every call still binds through the header's defaults" \ + || no "(A) incompatible=\"$( attr "$RA" incompatible )\", expected 0 — flags: $( flagged "$OA" )" +else + no "(A) --edit-check=scale exited $RCA: $( cat "$TMP/err" )" +fi + +echo "=== (B) R2: the definition's handle reads the defaults off its declaration ===" +OB="$( ec "$F" ./lib.cpp:scale )"; RB="$( root "$OB" )" +if [ "$( rc )" = 0 ] && [ -n "$RB" ]; then + [ "$( attr "$RB" incompatible )" = 0 ] && [ -z "$( flagged "$OB" )" ] \ + && ok "(B) incompatible=\"0\" and no caller row flagged (callers=\"$( attr "$RB" callers )\")" \ + || no "(B) incompatible=\"$( attr "$RB" incompatible )\" flags=[$( flagged "$OB" )] — the defaults on lib.h admit all three calls" + [ "$( attr "$RB" callers )" = 3 ] && ok "(B) premise: callers=\"3\" (the arm is about three real call sites)" \ + || no "(B) premise broken: callers=\"$( attr "$RB" callers )\", expected 3" + [ "$( attr "$RB" status )" = contract-change ] && [ "$( attr "$RB" params_now )" = 3 ] \ + && ok "(B) the edit is still reported: status=\"contract-change\" params_was=\"$( attr "$RB" params_was )\" params_now=\"3\"" \ + || no "(B) status=\"$( attr "$RB" status )\" params_now=\"$( attr "$RB" params_now )\" — the widened list must still read as a contract change" + [ "$( attr "$RB" defaults_from )" = decl ] \ + && ok "(B) defaults_from=\"decl\" names where the arity range came from" \ + || no "(B) defaults_from=\"$( attr "$RB" defaults_from )\", expected decl" + case "$( legend "$OB" )" in + *'defaults_from='*) ok "(B) the legend defines defaults_from=" ;; + *) no "(B) defaults_from= is emitted but not defined in the document's legend" ;; + esac +else + no "(B) --edit-check=./lib.cpp:scale exited $( rc ): $( cat "$TMP/err" )" +fi + +echo "=== (C) R3: the header's handle is accepted and answers the same contract ===" +OC="$( ec "$F" ./lib.h:scale )"; RC3="$( root "$OC" )" +if [ "$( rc )" = 0 ] && [ -n "$RC3" ]; then + ok "(C) --edit-check=./lib.h:scale answers (exit 0)" + for k in p status incompatible callers defs; do + [ "$( attr "$RC3" "$k" )" = "$( attr "$RB" "$k" )" ] \ + || no "(C) $k=\"$( attr "$RC3" "$k" )\" differs from the definition handle's \"$( attr "$RB" "$k" )\"" + done + ok "(C) p/status/incompatible/callers/defs agree with --edit-check=./lib.cpp:scale" +else + no "(C) --edit-check=./lib.h:scale exited $( rc ): $( cat "$TMP/err" )" +fi + +echo "=== (D) the MCP twin answers the field-report shape the same way ===" +python3 - "$F" <<'PY' | "$BIN" --mcp >"$TMP/mcp.rpc" 2>/dev/null +import json, sys +print( json.dumps( { "jsonrpc": "2.0", "id": 1, "method": "initialize" } ) ) +print( json.dumps( { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "edit_check", "arguments": { "path": sys.argv[1], "symbol": "scale", "legend": "full" } } } ) ) +PY +OD="$( python3 -c ' +import json,sys +lines=[l for l in open(sys.argv[1]).read().splitlines() if l.strip()] +r=json.loads(lines[-1]) if lines else {} +sys.stdout.write("__ERROR__:"+str(r.get("error",{}).get("message","no response")) if "result" not in r else r["result"]["content"][0]["text"])' "$TMP/mcp.rpc" 2>/dev/null )" +RD="$( root "$OD" )" +[ -n "$RD" ] && [ "$( attr "$RD" incompatible )" = 0 ] && [ "$( attr "$RD" defaults_from )" = decl ] \ + && ok "(D) MCP edit_check symbol=scale: incompatible=\"0\" defaults_from=\"decl\"" \ + || no "(D) MCP edit_check symbol=scale: ${OD:0:300}" + +echo "=== (E) CONTROL: defaults never make a wildcard ===" +E="$TMP/over"; mkdir -p "$E"; cp "$F"/lib.h "$F"/lib.cpp "$F"/use.cpp "$E/" +printf 'int d() { return scale( 1, 2, 3, 4 ); }\n' >>"$E/use.cpp" +commit "$E" +OE="$( ec "$E" ./lib.cpp:scale )"; RE="$( root "$OE" )" +[ "$( attr "$RE" incompatible )" = 1 ] && [ "$( flagged "$OE" )" = d ] \ + && ok "(E) the 4-argument call to a 3-parameter scale is flagged, and only it (incompatible=\"1\", d)" \ + || no "(E) incompatible=\"$( attr "$RE" incompatible )\" flags=[$( flagged "$OE" )], expected 1 and [d]" + +echo "=== (F) E3 decoy: real overloads stay two definitions, each with its own declaration's defaults ===" +D="$TMP/decoy"; mkdir -p "$D" +printf 'int scale( int x, int f = 2, int g = 3 );\ndouble scale( double x, double y, double z, double w );\n' >"$D/lib.h" +printf '#include "lib.h"\nint scale( int x, int f, int g ) { return x + f + g; }\ndouble scale( double x, double y, double z, double w ) { return x + y + z + w; }\n' >"$D/lib.cpp" +printf '#include "lib.h"\nint a1() { return scale( 1 ); }\nint a3() { return scale( 1, 2, 3 ); }\ndouble a4() { return scale( 1.0, 2.0, 3.0, 4.0 ); }\nint a5() { return scale( 1, 2, 3, 4, 5 ); }\n' >"$D/use.cpp" +commit "$D" +OF="$( ec "$D" scale )"; RF="$( root "$OF" )" +if [ "$( rc )" = 0 ] && [ -n "$RF" ]; then + [ "$( attr "$RF" defs )" = 2 ] && [ "$( printf '%s' "$OF" | grep -oE ']*params="[0-9]+"' | grep -oE 'params="[0-9]+"' | tr '\n' ' ' )" = 'params="3" params="4" ' ] \ + && ok "(F) defs=\"2\": the two overloads stay two definitions (params 3 and 4), the declarations fold beside them" \ + || no "(F) defs=\"$( attr "$RF" defs )\" def rows: $( printf '%s' "$OF" | grep -oE ']*>' | tr '\n' ' ' )" + [ "$( flagged "$OF" )" = a5 ] \ + && ok "(F) only the 5-argument call is flagged; the 1-argument call binds the int overload through ITS defaults" \ + || no "(F) flags=[$( flagged "$OF" )], expected [a5] — a1 flagged means the int overload's defaults went to the double one" +else + no "(F) --edit-check=scale on two overloads exited $( rc ): $( cat "$TMP/err" )" +fi + +echo "=== (G) a declaration whose parameter TYPES match no definition lends nothing ===" +G="$TMP/mismatch"; mkdir -p "$G" +printf 'int scale( int x, int f = 2 );\n' >"$G/lib.h" +printf '#include "lib.h"\nint scale( double x, int f ) { return int( x ) * f; }\n' >"$G/lib.cpp" +printf '#include "lib.h"\nint a1() { return scale( 1 ); }\n' >"$G/use.cpp" +commit "$G" +OG="$( ec "$G" ./lib.cpp:scale )"; RG="$( root "$OG" )" +[ "$( flagged "$OG" )" = a1 ] && [ -z "$( attr "$RG" defaults_from )" ] \ + && ok "(G) scale( double, int ) takes no defaults from scale( int, int = 2 ): a1 stays flagged, no defaults_from=" \ + || no "(G) flags=[$( flagged "$OG" )] defaults_from=\"$( attr "$RG" defaults_from )\" — a different parameter-type list is a different contract" +ec "$G" scale >/dev/null +[ "$( rc )" = 1 ] && grep -q 'Qualify one contract' "$TMP/err" \ + && ok "(G) the bare name stays refused: the declaration and the definition are two contracts here" \ + || no "(G) --edit-check=scale exited $( rc ), expected the ambiguity refusal: $( cat "$TMP/err" )" + +echo "=== (I) an UNPROVEN declaration (no #include of it) lends no defaults ===" +U="$TMP/unproven"; mkdir -p "$U" +printf 'int scale( int x, int f = 2 );\n' >"$U/lib.h" +printf 'int scale( int x, int f ) { return x * f; }\n' >"$U/lib.cpp" +printf '#include "lib.h"\nint a1() { return scale( 1 ); }\n' >"$U/use.cpp" +commit "$U" +OU="$( ec "$U" ./lib.cpp:scale )" +[ "$( flagged "$OU" )" = a1 ] && [ -z "$( attr "$( root "$OU" )" defaults_from )" ] \ + && ok "(I) lib.cpp does not include lib.h: no defaults are borrowed, a1 stays flagged (the safe direction)" \ + || no "(I) flags=[$( flagged "$OU" )] defaults_from=\"$( attr "$( root "$OU" )" defaults_from )\"" + +echo "=== determinism + well-formedness ===" +O2="$( ec "$F" ./lib.cpp:scale )" +if [ "$O2" = "$OB" ]; then ok "GUARD: two runs byte-identical"; else no "GUARD: two runs differ"; fi +if command -v xmllint >/dev/null 2>&1; then + if printf '%s' "$OB" | xmllint --noout - 2>/dev/null; then ok "GUARD: XML well-formed"; else no "GUARD: XML malformed"; fi +fi + +[ "$fail" = 0 ] && echo "ALL PASS" || echo "FAILURES ABOVE" +exit "$fail" diff --git a/test/regression.sh b/test/regression.sh index ecc896b63..1b1e3de78 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 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 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 fnliteralcheck 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 impactdepthcheck 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 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 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 rubydescribedclasscheck rubyinheritcheck 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 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 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 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 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 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 impactdepthcheck 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 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 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 rubydescribedclasscheck rubyinheritcheck 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 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 76b54afeb0c853fdbaf74b87a68b870dc4bdf296 Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Thu, 1 Oct 2026 03:19:46 -0400 Subject: [PATCH 03/35] fix(edit-check): every handle the ambiguity refusal prints round-trips The "Qualify one contract:" list offered `./lib.h:scale` for a header declaration, but the file:name tier widens a header declaration to the definitions it stands for, so pasting the handle back was refused with the same list. Each shown handle is now re-resolved and re-grouped (with the caller's own fold rule) and must land on exactly its contract; otherwise the canonical id, then the @FILE:LINE seed is offered. Gate: test/editcheckdeclcheck.sh arm (H) (red on the parent: 3 failures). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 9 +++++++++ src/editcheck.h | 35 +++++++++++++++++++++++++++++++- test/editcheckdeclcheck.sh | 41 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b058ae935..ef547ce58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,15 @@ not published here — see `docs/EVALS.md` for the instruments behind the headli ## [Unreleased] +### Fixed — every handle an `--edit-check` ambiguity refusal prints is accepted when pasted back + +The refusal's "Qualify one contract:" list offered `file:name` for a header declaration, and the `file:name` tier widens a +header's declaration to the definitions it stands for, so `./lib.h:scale` was refused again with the same list. Each +handle shown is now re-resolved before it is printed and must land on exactly its own contract; otherwise the canonical +id, then the `@FILE:LINE` seed (which names one place, one symbol) is offered. The same check runs for the `--dry-run` +preview's refusal and for every other verb that prints these handles; a handle that already round-tripped is unchanged. +Gate: `test/editcheckdeclcheck.sh` arm (H). + ### Fixed — `--edit-check` on C/C++: a declaration and its definition are one contract, and the declaration's defaults count A header declaring `int scale( int x, int factor = 2 )` and a `.cpp` defining it were two "contracts" to `--edit-check`: diff --git a/src/editcheck.h b/src/editcheck.h index ec81eb879..9affef80b 100644 --- a/src/editcheck.h +++ b/src/editcheck.h @@ -511,6 +511,34 @@ inline EditCheckGroupKeys editCheckGroupKeys( const IngestResult& ing, const Gra return gk; } +// E2 (2026-10-01): every spelling the refusal prints must be ACCEPTED when it is pasted back. `file:name` failed that +// for a header whose declaration the file:name tier widens to its definitions (#63): `./lib.h:scale` re-resolved to +// the declaration AND the definition and was refused again. So each shown spelling is re-resolved here and must land on +// exactly this group; otherwise the canonical id, then the `@FILE:LINE` seed (one place, one symbol) is offered. +constexpr std::size_t kEditCheckSpellingsShown = 6; + +inline void editCheckRoundTripSpelling( const IngestResult& ing, const Graph& g, EditCheckGroup& group, const std::string& canon, bool foldDecls ) +{ + const Symbol& s = ing.symbols[ group.lowestNode ]; + std::vector tries{ group.spelling }; + if( canon != s.name && canon != group.spelling ) + { + tries.push_back( canon ); + } + tries.push_back( "@" + ing.files[ s.fileId ] + ":" + std::to_string( s.line ) ); + for( const std::string& spelling : tries ) + { + const std::vector again = resolveAllByNameQualified( ing, spelling ); + const EditCheckGroupKeys gk = editCheckGroupKeys( ing, g, again, foldDecls ); + if( gk.keys.size() == 1 && gk.members[0].front() == group.lowestNode ) + { + group.spelling = spelling; + return; + } + } + // no spelling round-trips (a line holding two definitions): the original stays — it still names the right file +} + // `foldDecls` is the post-hoc verb's identity rule (editCheckFoldDeclGroups); the pre-apply preview and the other // readers of these groups (the write verbs, --slice) keep one group per (file, scope), so their answers are unchanged. inline std::vector editCheckGroups( const IngestResult& ing, const Graph& g, std::span matches, bool foldDecls = false ) @@ -541,6 +569,11 @@ inline std::vector editCheckGroups( const IngestResult& ing, con const bool fileIsEnough = ( groupsInFile == 1 ) || ( canonOfThis == s.name ); groups[ groupIndex ].spelling = fileIsEnough ? ing.files[ s.fileId ] + ":" + s.name : canonOfThis; } + // only a refusal prints spellings, and only the first kEditCheckSpellingsShown of them + for( std::size_t groupIndex = 0; groups.size() > 1 && groupIndex < std::min( groups.size(), kEditCheckSpellingsShown ); ++groupIndex ) + { + editCheckRoundTripSpelling( ing, g, groups[ groupIndex ], gk.keys[ groupIndex ].second, foldDecls ); + } return groups; } @@ -558,7 +591,7 @@ inline std::vector editCheckGroups( const IngestResult& ing, con // about a sibling verb's output, which is the worst kind of wrong — an agent can act on it without re-running // anything. Each number now carries the noun it actually is. definitionCount >= groups.size() always // (collapsing distinct matches into a group can never invent one), which is asserted rather than assumed. -constexpr std::size_t kEditCheckSpellingsShown = 6; +// (kEditCheckSpellingsShown, the cap, is defined beside editCheckRoundTripSpelling, which verifies exactly that many.) inline std::string editCheckAmbiguousMessage( std::string_view spec, std::span groups, std::string_view exampleForm, std::size_t definitionCount ) diff --git a/test/editcheckdeclcheck.sh b/test/editcheckdeclcheck.sh index 31c76b7ee..0bcffc91c 100644 --- a/test/editcheckdeclcheck.sh +++ b/test/editcheckdeclcheck.sh @@ -21,6 +21,8 @@ # overload alone, a 5-argument call by neither). # (G) a declaration whose parameter TYPES match no definition is not folded and lends no defaults: the 1-argument # call against `scale( double, int )` stays flagged, and the bare name stays refused. +# (H) E2 — every handle printed after "Qualify one contract:" is accepted on a rerun, on (G) and on a two-platform +# corpus (one declaration, two .cpp definitions, which is two contracts and must stay refused). # (I) an unproven declaration (the definition's file does not include the header) lends no defaults. # # Operates on private temp git repos. Needs git and python3. @@ -169,6 +171,45 @@ ec "$G" scale >/dev/null && ok "(G) the bare name stays refused: the declaration and the definition are two contracts here" \ || no "(G) --edit-check=scale exited $( rc ), expected the ambiguity refusal: $( cat "$TMP/err" )" +echo "=== (H) E2: every handle the refusal prints is accepted on a rerun ===" +P="$TMP/platform"; mkdir -p "$P" +printf 'int scale( int x, int f = 2 );\n' >"$P/lib.h" +printf '#include "lib.h"\nint scale( int x, int f ) { return x * f; }\n' >"$P/posix.cpp" +printf '#include "lib.h"\nint scale( int x, int f ) { return x + f; }\n' >"$P/win.cpp" +printf '#include "lib.h"\nint a1() { return scale( 1 ); }\n' >"$P/use.cpp" +commit "$P" +roundTrip(){ # roundTrip