Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
f39bef2
pathgaps: K5 rule 2 by namesake shape — file-owned needs an import (2…
joyful-ii-V-I Oct 22, 2026
f9a082d
CHANGELOG: by-name hedge rules 2b/2c (lane ind4b)
joyful-ii-V-I Oct 22, 2026
5fa806b
pathgaps: rule 2b/2c in a new NamesakeRule; NamesakeOwnerReach back t…
joyful-ii-V-I Oct 22, 2026
eae3b23
langfacts: transitiveIncludes column for K5 rule 2b (langidentitychec…
joyful-ii-V-I Oct 22, 2026
9ac4507
--lego: a base the file imports from outside the tree is no implement…
joyful-ii-V-I Oct 22, 2026
474c07e
CHANGELOG: --lego drops outside-bound base namesakes
joyful-ii-V-I Oct 22, 2026
5af40f0
--lego outside-base rule: keep a workspace crate and the tree's own p…
joyful-ii-V-I Oct 22, 2026
149b0b1
--lego outside-base rule, fix round 1: Cargo crate names, multi-root …
joyful-ii-V-I Oct 22, 2026
b806c6a
pathgaps: rule 2c binds each base name per file (proven implementors …
joyful-ii-V-I Oct 22, 2026
8395e64
pathgaps: rule 2c's proven-derivation walk in its own ProvenSubclasse…
joyful-ii-V-I Oct 22, 2026
973b906
--lego outside-base rule, fix round 2: renamed dependency keys, leadi…
joyful-ii-V-I Oct 22, 2026
dddad3e
rustOutsideUses: split the in-tree test and the crate-alias lookup in…
joyful-ii-V-I Oct 22, 2026
34d66b7
rustCrateSpelling: replace_copy, not a hand-rolled copy loop
joyful-ii-V-I Oct 22, 2026
b3c9104
cargoCrateNames: normalise names in place, no one-use copy helper
joyful-ii-V-I Oct 22, 2026
a407aeb
pathgaps: a Python base name rebound at module scope is no proven bin…
joyful-ii-V-I Oct 22, 2026
81fe9ae
graph: FC3 — a JS/TS binding written after its declaration binds no c…
joyful-ii-V-I Oct 22, 2026
5d03426
test: falseedgecheck (S) row set (no run row under Eng); qschemetrip …
joyful-ii-V-I Oct 22, 2026
f58f5ce
--lego Rust outside-base rule, fix round 3: decide outside only what …
joyful-ii-V-I Oct 22, 2026
c38a461
langfacts: writtenBindingUndecided column (JS/TS) — the FC3 rule read…
joyful-ii-V-I Oct 23, 2026
005fe82
pathgaps: a Python base name is a proven binding only when its import…
joyful-ii-V-I Oct 23, 2026
e2803f9
graph: hidesJsBinding() — collectFalseEdgeBindings keeps its complexi…
joyful-ii-V-I Oct 23, 2026
209236f
pathgaps: group the Python scope facts in one struct; split buildPyth…
joyful-ii-V-I Oct 23, 2026
65c5db5
pathgaps: Python files by extension, not a Lang:: compare (langidenti…
joyful-ii-V-I Oct 23, 2026
3e37689
graph: kJsHidingKinds table instead of a one-line predicate (quality-…
joyful-ii-V-I Oct 23, 2026
b59ae81
pathgaps: one isPythonSourcePath predicate for the Python scope facts…
joyful-ii-V-I Oct 23, 2026
6fc2cd8
pathgaps: isPythonSourcePath reads the extension once (quality-delta …
joyful-ii-V-I Oct 23, 2026
03f7267
pathgaps fix 4: out-of-chain qualifier imports, global at byte 0, mat…
joyful-ii-V-I Oct 23, 2026
7545cee
pathgaps fix 4: pythonBaseNameProven reads the qualifier from the ref…
joyful-ii-V-I Oct 23, 2026
c0697d9
pathgaps fix 4: match/type binds in capturePythonPatternDecls (small …
joyful-ii-V-I Oct 23, 2026
25f3fd0
pathgaps fix 4: capturePythonPatternDecls takes 5 parameters (quality…
joyful-ii-V-I Oct 23, 2026
b938a8c
--lego Rust outside-base rule, fix round 4: blank-line UB, cargo conf…
joyful-ii-V-I Oct 23, 2026
8d9899c
graph: FC3 fix round 1 — deep patterns, pinned declarations, member c…
joyful-ii-V-I Oct 23, 2026
e3be3f6
graph: FC3 fix round 1 — narrow B3 to pinned declarations, FC5 to the…
joyful-ii-V-I Oct 23, 2026
c4bc768
feat(hop1): one backward hop provider (slot + declined) for --path's …
joyful-ii-V-I Oct 23, 2026
fb7e836
fix(whereis,grep): honest lexical def labels; regex-shaped literal hi…
joyful-ii-V-I Oct 23, 2026
c263189
refactor(grep): spell occ= and pattern_meta= through the shared absen…
joyful-ii-V-I Oct 23, 2026
5f89826
test(hop1): hop1check gate; #11 rule requires every same-named class …
joyful-ii-V-I Oct 23, 2026
fddfb32
test(hop1): hop1check reports through the house accumulator (gateexit…
joyful-ii-V-I Oct 23, 2026
5cd062d
pathgaps fix 5: aliased member imports, dotted import root, PEP 695 t…
joyful-ii-V-I Oct 23, 2026
7f190c6
test(honestlexcheck): if/else verdicts and a write-failure-safe ok() …
joyful-ii-V-I Oct 23, 2026
4994f5a
pathgaps fix 5: capturePythonTypeParamDecls split (quality-delta comp…
joyful-ii-V-I Oct 23, 2026
f82aed6
refactor(hop1): bridge crossings as helpers; backhops.h index keyed b…
joyful-ii-V-I Oct 23, 2026
be2c76f
refactor(hop1): DeclinedCrossRule keys through graph.h jsImportKey (q…
joyful-ii-V-I Oct 23, 2026
76022e3
--lego Rust: pin a dependency key that is also an in-tree package nam…
joyful-ii-V-I Oct 23, 2026
fa8f9b0
docs(hop1): regenerate LIMITS.md / TUNING.md and the README cap count…
joyful-ii-V-I Oct 23, 2026
343696c
--lego Rust: a dependency key that is also an in-tree package name is…
joyful-ii-V-I Oct 23, 2026
3d4c6ad
fix(ci): honest-lex CI round 1 -- gate count, clean-env, one cast per…
joyful-ii-V-I Oct 23, 2026
82d8a2a
graph: FC3 fix round 2 — a member call through a written root keeps t…
joyful-ii-V-I Oct 23, 2026
8fc7a98
fix(hop1): size kBackHopViaNames by an exact enum count (enumtableche…
joyful-ii-V-I Oct 23, 2026
a02e843
pathgaps fix 6: a package that binds the name is not its submodule; a…
joyful-ii-V-I Oct 23, 2026
c604986
--lego Rust: only a path = under the root puts a dependency key in th…
joyful-ii-V-I Oct 23, 2026
70b5303
MCP quality_delta show_stale + edit_check verdict fold (card #9 MCP-F…
joyful-ii-V-I Oct 24, 2026
042f4fc
gates: MCP fold arms (staleackcheck 2m, editcheckcheck 1v-m); mcpattr…
joyful-ii-V-I Oct 24, 2026
099d4e6
gates: MCP fold arms predict the exact default document (fold only wh…
joyful-ii-V-I Oct 24, 2026
f961fa0
hop1 fix round 1: ring cycle-guard arm, slot_fns= forward width, brid…
joyful-ii-V-I Oct 24, 2026
6c5bb4c
pathgaps fix 7: a re-export chain hops only through files that bind t…
joyful-ii-V-I Oct 24, 2026
96f6189
pathgaps fix 7: markPythonDynamicBinds wraps the rebind table (buildE…
joyful-ii-V-I Oct 24, 2026
b62c6cf
fix(grep): no pattern_meta on subscripts, semver, RxJS $ and footnote…
joyful-ii-V-I Oct 24, 2026
23d03c6
--lego Rust: a module or directory name of another crate puts no head…
joyful-ii-V-I Oct 24, 2026
111655a
--lego Rust: build the crate-aware use table from the tree, not only …
joyful-ii-V-I Oct 24, 2026
4329199
test(compactlegendcheck): re-pin ripwire.grep/v1 1380 -> 1432 (measur…
joyful-ii-V-I Oct 24, 2026
52cafcb
lean2b fix round 1: gateexitcheck spelling, tools/list ceiling, MCP r…
joyful-ii-V-I Oct 24, 2026
cb9ff0b
pathgaps fix 8: a re-export chain's endpoint class is the name's mean…
joyful-ii-V-I Oct 24, 2026
e3d34bf
lean2b fix round 2: restore "defect-shape" in the MCP quality_delta d…
joyful-ii-V-I Oct 24, 2026
4fe650a
Merge lane/lean2b e3d34bf5 (lean2b MCP/CLI fold + tools/list trim)
joyful-ii-V-I Oct 24, 2026
497a864
Merge lane/fc2-lego-extparent 111655a2 (FC2 Rust lego external-parent…
joyful-ii-V-I Oct 24, 2026
b039530
train 34: fold FC2's Rust use-head compare into LangFacts::baseImport…
joyful-ii-V-I Oct 24, 2026
9d05296
Merge lane/honest-lex 4329199f (honest-lex complete=1 literal legend …
joyful-ii-V-I Oct 24, 2026
82623e9
Merge lane/ind4b-k5-tighten-k5c cb9ff0b2 (IND4b Python import-precise…
joyful-ii-V-I Oct 24, 2026
e98a53c
train 34: langidentity ratchet reads honest-lex's occ= rows
joyful-ii-V-I Oct 24, 2026
d687d66
Merge lane/hop1 f961fa00 (HOP1 slot-bridge hop rows)
joyful-ii-V-I Oct 24, 2026
e73d1b5
Merge lane/fc3-js-rebind 82d8a2ac (FC3 JS rebind false edges hedged)
joyful-ii-V-I Oct 24, 2026
b58af84
train 34: kParserVer 226 (rich 227), kCacheVersion 40, qschemetrip re…
joyful-ii-V-I Oct 24, 2026
3390863
train 34: legendcoverage (S) slot-bridge probe; falseedgecheck (S) na…
joyful-ii-V-I Oct 24, 2026
0b53bae
docs: ARCHITECTURE §6 seeds -> hops -> walks, and a CONTRIBUTING map …
joyful-ii-V-I Oct 24, 2026
c97f6f9
train 34: regenerate counts, printf_parity grep pin, versions fragment
joyful-ii-V-I Oct 24, 2026
1fa0886
train 34: showcase capture 2026-10-24 and COMMANDS.md regenerated once
joyful-ii-V-I Oct 24, 2026
cb627d6
train 34: split appendAuxFileHits out of scanUnsupportedFiles (qd gat…
joyful-ii-V-I Oct 24, 2026
2cd212e
fc2 x L6: an undecided Rust base clause opens the implementor list
joyful-ii-V-I Oct 24, 2026
8de5fa5
honest-lex: a UTF-8 byte is a word byte in pattern_meta
joyful-ii-V-I Oct 24, 2026
26f616a
honest-lex: the UTF-8 word-byte test uses static_cast
joyful-ii-V-I Oct 24, 2026
7be3c12
honestlexcheck B10: a fixed-string match on the literal pattern_meta …
joyful-ii-V-I Oct 24, 2026
daeda73
hop1check B6-B8: a negative arm needs its premise (CodeRabbit 4368689…
joyful-ii-V-I Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .github/pargates-shard-weights.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@
"headsnapcachecheck.sh": 19.9,
"historyoraclecheck.sh": 6.9,
"hookcheck.sh": 78.0,
"hop1check.sh": 30.0,
"hostilecheck.sh": 6.2,
"hotspotsincecheck.sh": 8.6,
"htmlcolorcheck.sh": 12.4,
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ If you are about to write C++ here, §3 is the authoritative style-rules checkli
- **What the tool does / which flag answers which question** → `README.md`, `docs/COMMANDS.md`, or
`./build/ripwire --help` (the binary self-documents and is always current).
- **How it is built internally** → `docs/ARCHITECTURE.md`.
- **Where code for a graph question goes** (a new input spelling, a new indirection shape, a new verb) → the
seeds → hops → walks layers, `docs/ARCHITECTURE.md` §6.
- **How its claims are measured** → `docs/EVALS.md`.

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

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

`test/regression.sh` names **709 gate scripts** and is the authoritative list; <!-- gatecount -->
`test/regression.sh` names **711 gate scripts** and is the authoritative list; <!-- gatecount -->
`python3 test/pargates.py . ./build/ripwire -j 6` runs the same set in parallel. On top of them sit the
contracts that do not fit a unit test: two runs byte-identical, warm output identical to cold, output
that pipes clean through `xmllint --noout`, a sanitizer build with `-fno-sanitize-recover=all`, and a
Expand Down Expand Up @@ -2878,7 +2878,7 @@ python3 test/pargates.py . ./build/ripwire -j 6
A new gate script must be added to `test/regression.sh` in the same change. The gate
`test/manifestcheck.sh` enforces this rule.

Another gate derives the cap inventory. The tool has 276 compile-time caps and 7 ranking parameters.
Another gate derives the cap inventory. The tool has 278 compile-time caps and 7 ranking parameters.
`docs/LIMITS.md` lists each cap, its value, and whether the file discloses a truncation when the cap
fires, and `python3 docs/limits_build.py --check` proves that list against `src/`. `docs/TUNING.md`
lists the measured cost of each cap.
Expand Down
28 changes: 28 additions & 0 deletions changelog.d/fc2-lego-external-parent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
### Fixed — `--lego` stops listing an implementor whose own file imports its base from outside the tree

`--lego` no longer lists a class as an implementor of an in-tree type when the class's own file imports its base from
outside the tree: `class Sub(ext.Base)`, `from ext import Base`, `import { Base } from 'lib'` / `lib.Base`, `use ext::Base;
impl Base for S`, Java/Kotlin `import org.ext.Base` / `extends org.ext.Base` (a package the tree does not declare). A star or
wildcard import, an import the tree cannot place, an import of the tree's own package name (Node self-reference, also on a
multi-root run; a single-root Python repo whose root is the package, by its directory or `[project] name`) and a same-file
declaration still keep the by-name row, and a clause dropped as outside adds no `implementors_floor=`.
For Rust the decision is inverted: a `use` head is decided OUTSIDE only when it is `std`/`core`/`alloc`/`proc_macro`/`test`, or
a registry or git dependency that some Cargo.toml of the tree declares (any dependency table, `[workspace.dependencies]`
included; the key and its `package =` name; a `path =` crate or a key `[patch]`/`[replace]` names is not one), also through
an alias the file itself spells (`use serde as sd;`, `use std::io::{Write as _};` moves no other head). A head that is a
crate of the tree keeps its row: a Cargo `[package]`/`[lib]` name from its tests/, examples/ and main.rs, a module of the
using file's OWN crate (a workspace crate directory of it spelled with `_` for its `-` directory; a name that is only another
member's directory or module, `crates/foo_core/` beside a registry `foo-core = "1"`, is no module of this file's crate), a dependency key with `path =` under the indexed root (only a path puts a key in the tree)
(`core2 = { package = "foo-core", path = "../core" }`, also a `[dependencies.core2]` table or a dotted key), a leading `::`,
an `extern crate foo_core as fc;` or `use foo_core as fc;` the file spells. EVERY other head — an unknown crate, an alias or
re-export this scan did not see (`pub extern crate foo_core as fc;` in lib.rs, `use fc::T` from another file), a rename only
`[patch]`, `[replace]` or `.cargo/config` names, a `path =` crate outside the indexed root — is undecided: it lists no row
and the interface carries `implementors_floor=`, never a confident `implementors="0"`. A tree with no Cargo.toml decides
nothing outside but `std` and its siblings. A key that two members of the tree mean differently (in the tree for one, a registry
crate for another, also when it spells a package name of the tree) and a key a `.cargo/config(.toml)` `[patch]` names — read in
each manifest's directory and above it, within the indexed root, so a config ABOVE the indexed root is not read — are undecided
too, as is a renamed key with no `path =` (`fc = { package = "foo-core", version = "1" }`: a registry crate, or a `[patch]`'s), an
inline table that does not close on its line, and a key one manifest writes as a registry crate in one dependency table and as a
`path =` crate in another; a Cargo file the run cannot read decides nothing outside. This does NOT
cover a C++ `ext::Base` or `using ext::Base`, a C# `using`, a Swift `import`, a PHP `use`, a JS `require` destructure or a
Rust `impl ext::Base for S` with no `use`: those rows are still listed by name. Gate: `test/legotransitivecheck.sh` (XP).
12 changes: 12 additions & 0 deletions changelog.d/fc3-js-written-binding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### Fixed — JS/TS calls through a name written after its declaration are no longer read as the declared class

`let { Eng, Foo } = require('a'); Eng = Foo; new Eng()` builds a `Foo`, so a BARE call by a name written in its scope is
unresolved (`unresolved=`), never its declared target nor a same-spelled class. A MEMBER call through such a root (`Eng.make()`,
`tank.spill()`) is not receiver-typed: the root's spelling does not name a class, so the call keeps only the by-name hedge
(`via="name"`, every candidate), with or without a declaration (`let Eng;`, an undeclared global). A write whose value keeps the
name (`Environment = require('x').Environment` over `var Environment;`) and a stated constructor (`let app; app = new Hono()`) are
unchanged. Residual, not fixed: a member call whose receiver is a typed chain the tool does not follow (kysely
`ctx.db.selectFrom('person').selectAll().limit(100)`) still falls to the by-name ladder, and the merged caller row carries the
sites of a bare imported `limit(…)` call in the same file unhedged. Refusing every member call a free function (which JS/TS allow
through an object property) changed about 9,000 census rows on six in-sample repos (hono, kysely, nunjucks, fastify, commander, koa),
so it is left to a later card. Gate: `test/falseedgecheck.sh` arm (S).
11 changes: 11 additions & 0 deletions changelog.d/honest-lex-grep-pattern-meta.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### Fixed — `--grep` says when a literal pattern reads as a regex, and prints one row per line

A literal `--grep='compose|HTTPException'` answered `hits="0" complete="1"` with `next=` the conceptual lens, while 31 hono
files hold one of the names; `--grep='CHANGELOG\.md'` found the one line spelling the backslash. The root now carries
`pattern_meta="…"` (the regex-shaped pieces: `|` between words, a backslash escape, `.*`/`.+` inside the pattern, a leading `^`
or trailing `$`, a `[^…]`/`[a-z]` class; a lone `.` as in `CHANGELOG.md` is not one) and `next=` is `--regex=<pattern>`
whatever the hit count (a basic-grep `a\|b` is offered as `a|b`; nothing is offered when `--regex` would refuse it). The literal
answer itself, and its `complete=`, are unchanged: the scan still matched the text as written. Folded in: a line holding the
pattern twice used to print as two `<hit>` rows and count twice; it is now one row with `occ="2"`, and `hits=`, `shown=` and the
window count lines, not occurrences (the unindexed list too). The MCP `grep` verb carries `pattern_meta`, the same `next` and
`occ` per row. Gate: `test/honestlexcheck.sh` (B, M arms).
16 changes: 16 additions & 0 deletions changelog.d/honest-lex-whereis-lexical-defs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
### Fixed — `--whereis` no longer labels comment, prose and string lines `kind="def"` on the lexical fallback

When the parsed index has no definition of the name (or the checkout drifted from it), HEAD rows are labelled by a lexical shape
test (`head_labels="lexical"`). That test took a declaration word anywhere earlier on the line, so `// a class published its
name`, a help string `"struct (like --lego)"`, a markdown line and every `struct T *data` local read `kind="def"`: 241 false
definition rows over 30 cells (10 words with no definition × this repo, hono, tmux). Now the word must sit directly before the
name (qualifiers, `*`/`&`, template or generic arguments, a qualification chain, a Go receiver, one ALL-CAPS export macro after
`class`/`struct` may sit between; `template<…> RetType NAME(` too); a line that opens a comment (`//`, `#`, `;`, `-- `, ` * `,
`"""`…), a name after a trailing comment opener and a name inside a `"…"`/`` `…` `` literal are never definitions; `#define NAME`
(object-like as well as function-like) is; a typedef defines only its own last declarator; `struct T` followed by a declarator,
`*`, `)` or `,` uses the tag and defines nothing; `using std::x;` and `using namespace x` define nothing (`using X = …` does), nor does Rust's `impl Trait for X`. The
root gains `head_defs="0"` when the lexical labels stand and the index defines no symbol of the name at all. Still a floor in both
directions, and named as one: prose whose declaration word sits right before the name reads as a definition, and an unusual
declarator (a wrapped signature, an enumerator, a field) reads as a reference. The parse chooser for other refs' blobs keeps the
old, looser reading as well, so no blob that used to be parsed goes unparsed. Control: 12 real definitions forced onto the lexical
path (C, C++, TS, Python, Rust, Go, Ruby) keep their `kind="def"` rows. Gate: `test/honestlexcheck.sh` (A, H arms).
19 changes: 19 additions & 0 deletions changelog.d/hop1-backward-hop-step.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Added — one backward hop step for `--path` and `--impact` (slot and declined callers)

`--callers=X` already names, one hop per call, who MAY call X without a graph edge: a `<vr via="slot">` row's
`called_by=` (a call through a struct field X is stored in) and a `<dc>` row (a declined call whose candidate list holds
X). The answers that join hops now read the same rows, from one provider (`src/backhops.h`):

- `--path`'s hedged `<bridge>` crosses a slot hop (`via="slot"`, `via="slot-name"` for a field-name-only join) while the
join names at most 16 calling functions (`slot_callers=`), and `slot_fns=` says how many functions the field holds
(not capped: a wide table such as tmux's 92-entry `cmd_entry.exec` is still crossed, and the row says how wide it is); a
chain whose every crossing is a slot may take three crossings, any other chain keeps two. On tmux, `--path=cmdq_fire_command,window_copy_cmd_cancel` now prints the
three-field-call chain (`cmd_entry.exec`, `window_mode.command`, the command table's `.f`); before, no chain.
- A `reachable="0"` path answer whose search met gaps and whose `to=` has such a hop ends with `next="--callers=<to>"`
(CLI and MCP `path_between`) instead of the gap rows' bodies.
- `--impact` (XML, `--json`, columnar count, MCP `impact`) adds `reaches_hedged=N` beside `reaches=`: the functions that
may reach the symbol through one or two calls through struct fields, with the two-hop ones listed as
`<ring d="2"><rh called_by= called_at= via= calls=/></ring>` (16 shown, no width bound on the d=1 set; `next=` pages the callers of the first unshown row's `calls=`, not every hidden row). Never counted in
`reaches=`, `by_depth=` or the radius counts.

Every crossing stays a hedge: `proven="0"`, `via=` named, its width beside it (a slot crossing's `slot_callers=` is bounded at 16, its `slot_fns=` is printed, not capped), and never in `reachable=`/`hops=`/`reaches=`.
8 changes: 8 additions & 0 deletions changelog.d/hop1-typed-receiver-not-builtin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### Fixed — a declined call on a typed in-tree receiver is no longer read as the builtin method its name spells

The bridge and the `<near_sink>` frontier skipped every declined call spelled like a builtin type's method, so hono's
`this.router.match( method, path )` (`router!: Router`, an in-tree interface with eight implementations) was dropped as
`String.prototype.match`. The skip now lifts for one field of `this`/`self` or of a parameter whose class the source
states, when every same-named class types that field as an in-tree class and the call's candidate list is complete (2
to 16). A `.match` on a string parameter, an un-annotated local, a field typed `string`, or a field another same-named
class types outside the tree stays skipped.
49 changes: 49 additions & 0 deletions changelog.d/ind4b-by-name-hedge-import-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
### Changed — by-name hedges: a free namesake needs an import, an override is a way in


- A by-name hedge (`<near_sink>` `why="name"`, a `<bridge>` `via="namesake"` crossing) to a namesake with no class (a free
function, or a JavaScript/TypeScript method, whose class the extractor does not record) now needs the caller's file to be,
or to import, the namesake's file (a direct import edge). A namesake file the search merely touched no longer counts. For
C, C++ and Objective-C the rule follows the transitive `#include` closure, but no probe of ours produced a by-name hedge
to a C-family free function: those calls are declined, not bound by name. On the 880-pair seed-18 `--path` draw over 11 in-sample repos this
withholds the 2 shown by-name sites a hand-check had rejected (koa's `delegate( proto, 'response' ).method( … )` reaching
`request.js`'s `get method`, and a bundled Bootstrap `this.init( … )` reaching nunjucks' `Parser.init`). The one accepted
site, flake8's `report()`, stays. 16 rows that already showed a value or declined site keep it and now also count a by-name way in.
- A class method namesake is now also a way in when its class derives from the class of the definition the call was bound
to, at any depth, and every step of that derivation is bound in its own file: the base name a class writes resolves,
through that file's own declaration or its import of that name (a package re-export, and for Python a `src/` layout
import that resolves by package name, count), to the next class. This is override dispatch. A base clause that names a
different same-named class (one elsewhere in the tree, or an external one: `class Sub(ext.Base)`, `from ext import Base`)
does not count, however the file's other imports reach the parent's file; neither does a base no import binds. On the
same draw this shows 6 sites, and a hand-check read all 6 as correct: textual's `driver.start_application_mode()` /
`stop_application_mode()` → `LinuxDriver`, `widget.render_line( y )` → `TextArea`, and `node.compose()` → three `App`
subclasses in examples. Like every by-name hedge, each is a hedge and never a proven edge. What stays withheld: a base
the binding does not read (a dotted `a.b.Base`), and a Python base name that has any binding other than its import in
the class statement's own scope chain (its class body or function, the enclosing functions, then the module; an
enclosing class body is skipped, as Python skips it): an assignment of any form (`Base = Other`, `Base: type = Other`),
a def, class, loop, `with` or `except` target, a parameter, a `global` or `nonlocal` (also in a def that starts the
file), a `match` capture (`case Base:`, `case P as Base`, `*Base`), a PEP 695 `type Base = …` or a class or def type
parameter (`class Sub[Base](Base)`), a second import, an import under another name (`from m import Other as Base`, which
binds m's `Other`, never `Base`), a `try:
from a import Base / except ImportError: Base = None` fallback, or a star import that could bind it (one from a module
outside the tree, or from a module that defines, imports or star-imports the name), wherever in the scope it sits and
in either order. A qualified base (`q.Base`) is also withheld when another function or class imports `q` as a module
(`import a.b as q`), since the import tables are per file; this over-hedges the same import repeated in two scopes,
and a star import from an in-tree module whose `__all__` omits the name. `from pkg import other as a` with `a.Base` now
reads `other` (the member's own name), not `a`; a dotted `import pkg.x` binds the root `pkg`, which no table names, so
`pkg.Base` is withheld (this also over-hedges `import pkg.x as pkg`). `from pkg import a` (aliased or not) with `a.Base`
reads the submodule `a` only when the package's `__init__` does not bind `a` itself (an import, def, class or assignment of
it, a star import that could bind it, or a module `__getattr__`). A re-export chain (`from pkg.x import Base`, where the
package or a middle module re-exports the name) is followed only through files that bind the name by ONE plain import,
at every hop: a hop that renames it (`from pkg.x.a import Other as Base`), reassigns it afterwards (`Base = Other`, `Base +=`,
a `for`, `with` or walrus target, a def or class of it, a `global Base` in any def), star-imports after it, or has no
record of it ends the chain, and the site is withheld. A hop file that calls `globals()` or `setattr(...)` anywhere, or
defines a module `__getattr__`, counts as unknown for every name (a dynamic write no table sees; this over-hedges any such
file, and `sys.modules[__name__].a = x`, which is not a call, stays a floor). The import must be the name's one binding;
otherwise the site is withheld, never shown. Not read, and not detectable statically: a rebinding made from outside
the file (`a.Base = Other` on an imported module).
The chain's endpoint is held to the same standard: the class it finds is the name's meaning only when its own `class`
statement is the name's one module-scope binding in that file and no star import there could bind the name, so a later
`Base = Other`, a `global Base` in a def, a second def or class of it, or a trailing `from m import *` now withholds the
site (this over-hedges a class defined in both arms of an `if`/`try`). Residual: an endpoint file's `globals()` or
`setattr(...)` write is not read there (a hop file's is), so that shape may still be shown.
12 changes: 12 additions & 0 deletions changelog.d/mcp-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### Changed — MCP `quality_delta` and `edit_check` fold their bookkeeping rows like the CLI (`show_stale`)

- MCP `quality_delta`: a regression-free answer with stale acks now drops its `sa` rows and carries `stale_by_kind` (the
stale count split by ack kind, the same string the CLI prints) and `stale_next` (`show_stale=true`), when that saves at
least a quarter of the bytes. Every count stays on the answer. The new boolean argument `show_stale=true` (the CLI
`--show-stale` twin) returns the full document unchanged. Measured on ripwire's own tree: 15,015 B to 567 B; on the eight
other sweep repos the answer has no stale acks and is byte-identical.
- MCP `edit_check`: an unchanged contract at git HEAD with no flagged caller answers the verdict and `rows_next=` (the
MCP call, `edit_check symbol=SYM limit=N`, that lists the callers), as `--edit-check` already does; `callers=` keeps the count. Median 1.72x smaller on the
repos where it applies (1.62x to 3.46x). A dirty tree, a flagged caller, `new_body` previews and an explicit `limit` or
`offset` still list every row.
- It does NOT drop a regression row, a flagged caller or any count: the folded rows are one argument away.
Loading
Loading