Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .claude/skills/ci-gates/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8939,3 +8939,50 @@ check and it takes a second.
It is the same shape as the fix that did not travel from one command to its
neighbouring function, one level up: there the sibling was a function, here it
is a file. Both were invisible because the gate I ran was green.

## 356. The option you keep not picking

`tri unparsed locate` confirmed 37 answers and refuted 37 -- exactly half. That
line sat at the bottom of four consecutive iteration reports, and each time I
picked one of the other two options.

Half is not a plateau, it is a signal. The cause took twenty minutes: a file
declaring `module NAME;` -- the SEMICOLON form -- has no wrapping brace, and
`split_module` looked for "the first line that opens a brace at depth 1" and
found the first `struct`. Everything after that struct's closing brace became
the "tail", so every truncated prefix had a large orphan chunk glued onto it
and failed for the chunk's own reasons.

confirmed 37 -> 60 refuted 37 -> 14

**A number that has been stable across several reports is either finished or
avoided.** Write down which, and if it is avoided, take it next.

## 357. The base rate is what turned a hunch into a cause

The hunch -- "the tail is too long" -- was available immediately: 32 of the 37
refuted cases had a tail over 10 lines. On its own that is worth nothing; long
tails might simply be common.

```
tail > 10 lines tail = 1 line
refuted 32 4
confirmed 4 33
```

A one-line tail is a real braced module's closing brace, and it lands almost
exclusively in the confirmed column. THAT is the finding, and it costs one more
measurement than the hunch did.

**A distribution over the failing group is a hunch. The same distribution over
the passing group is a cause.**

## 358. Fourteen refusals that are correct

The 14 still refuted point at lines 5 to 11 -- the first item in the file. Those
are specs opening with `algorithm NAME {`, a construct the parser does not
implement at all, so the very first prefix fails and there is nothing to bisect.

The locator says so instead of naming that item, which is right: "the file is
unsupported from its first declaration" is not the same claim as "this item
causes the failure", and only the second one is what the command promises.
45 changes: 45 additions & 0 deletions cli/tri/src/unparsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,29 @@ mod tests {
assert!(stage_of("Error: parse error at module level near line 2") == Stage::Parse);
}

// `module NAME;` wraps nothing, so the body is everything after it. Taking
// the first braced line as a wrapper instead made the rest of the file a
// "tail" that was glued onto every truncated prefix -- 32 of 37 refuted
// answers were that, against 4 of 37 confirmed.
#[test]
fn a_semicolon_module_wraps_nothing() {
let src = [
"// header",
"module config-load;",
"struct A { x: u32 }",
"fn b() -> u32 { return 1; }",
];
let (start, end) = split_module(&src);
assert_eq!(start, 2, "body starts after the `module NAME;` line");
assert_eq!(end, 4, "and runs to the end -- there is no closing brace");
}

#[test]
fn a_braced_module_still_splits_at_its_brace() {
let src = ["module m {", " fn a() { }", "}"];
assert_eq!(split_module(&src), (1, 2));
}

// A file with no module wrapper still has a body: the whole file.
#[test]
fn split_module_handles_a_bare_file() {
Expand Down Expand Up @@ -1262,6 +1285,28 @@ fn check_text(t27c: &Path, root: &Path, text: &str) -> (bool, Option<usize>) {
fn split_module(lines: &[&str]) -> (usize, usize) {
let d = depths(lines);
let c = code_only(lines);
// `module NAME;` -- the SEMICOLON form, which wraps nothing. The scan below
// looks for the first line that opens a brace at depth 1 and calls it the
// module header; in a semicolon-form file that is the first `struct` or
// `fn`, so everything after its closing brace became the "tail" and the
// reconstruction glued a large orphan chunk onto a truncated body. Every
// prefix then failed for the chunk's own reasons.
//
// Measured before the fix, with the base rate that makes it mean something:
//
// tail > 10 lines tail = 1 line
// refuted 32 4
// confirmed 4 33
//
// A one-line tail is the closing brace of a real braced module. Anything
// longer was this.
for i in 0..lines.len() {
let t = c[i].trim();
let t = t.strip_prefix("pub ").unwrap_or(t);
if t.starts_with("module ") && t.ends_with(';') && d[i] == 0 {
return (i + 1, lines.len());
}
}
for i in 0..lines.len() {
if d[i] == 1 && c[i].contains('{') {
for j in i + 1..lines.len() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# NOW -- The half the locator was silent on was one wrong assumption (2026-08-30)

## The half the locator was silent on was one wrong assumption (Refs #2864)

- tri unparsed locate confirmed 37 and refuted 37 -- exactly half. Four iterations of options lists carried that line, and I kept picking something else.
- Cause: split_module assumed a BRACED module. Files declaring 'module NAME;' -- the semicolon form -- have no wrapper, so it took the first 'struct X {' as the header and made the rest of the file a tail. Every truncated prefix then had a large orphan chunk glued to it and failed for the chunk's own reasons.
- Found with the base rate, not without it: tail longer than 10 lines appeared in 32 of 37 REFUTED and 4 of 37 CONFIRMED; a one-line tail in 4 refuted and 33 confirmed. A one-line tail is a real module's closing brace.
- confirmed 37 -> 60, refuted 37 -> 14, all 60 still reproduce alone. The 14 that remain point at lines 5-11: their FIRST item is unsupported -- files opening with 'algorithm NAME {' -- so there is nothing to bisect and the refusal is correct.
Loading