diff --git a/.claude/skills/ci-gates/SKILL.md b/.claude/skills/ci-gates/SKILL.md index c2cd744c9..0aacbe04d 100644 --- a/.claude/skills/ci-gates/SKILL.md +++ b/.claude/skills/ci-gates/SKILL.md @@ -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. diff --git a/cli/tri/src/unparsed.rs b/cli/tri/src/unparsed.rs index 36b40ad6d..f5db505ad 100644 --- a/cli/tri/src/unparsed.rs +++ b/cli/tri/src/unparsed.rs @@ -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() { @@ -1262,6 +1285,28 @@ fn check_text(t27c: &Path, root: &Path, text: &str) -> (bool, Option) { 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() { diff --git a/docs/now/2026-08-30-the-half-the-locator-was-silent-on-was-one-wrong-assumption.md b/docs/now/2026-08-30-the-half-the-locator-was-silent-on-was-one-wrong-assumption.md new file mode 100644 index 000000000..d77c596f6 --- /dev/null +++ b/docs/now/2026-08-30-the-half-the-locator-was-silent-on-was-one-wrong-assumption.md @@ -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.