Skip to content

tri skill renumber: move appended sections to the numbers the base left free - #3052

Merged
gHashTag merged 9 commits into
masterfrom
w50-skill-renumber
Sep 3, 2026
Merged

tri skill renumber: move appended sections to the numbers the base left free#3052
gHashTag merged 9 commits into
masterfrom
w50-skill-renumber

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Closes #3051

Measured

Six section-number collisions in one week on .claude/skills/ci-gates/SKILL.md. Two of them hit the same branch four hours apart: renumbered to 468/469, master then took 468, 469 and 470, and it moved again to 471/472.

The repair never varied — rebuild from origin/master, re-append with the next free numbers, assert the master prefix is byte-identical, re-run tri skill check. Nothing in it needed judgement. Only the assertion needed care, because the file is ~12,000 lines and a lost section looks exactly like a file that never had it.

What it does

Leans on the invariant the workflow already has: a section is appended, so the branch's file is the merge base's file plus a tail, and the tail is what moves.

  .claude/skills/ci-gates/SKILL.md
  merge base ee4d845f9   434 section(s) there
  origin/master highest section  470
  appended here           2

      468  ->  471
      469  ->  472

  Written. 436 section(s); no number is used twice.

--check reports the moves and writes nothing. --base and --file default to origin/master and the ci-gates skill.

It refuses rather than guesses. If the file is not the merge base plus an append — an existing section was edited, or a previous conflict was hand-resolved — it stops and says so. The whole method rests on that invariant, and a command that quietly picked a split point would be worse than the manual repair it replaces.

Evidence

cargo test -p tri --bins 541 passed, 0 failed, 0 filtered (534 before)
renum filter 7 passed, 534 filtered — 7 is the #[test] count in the module
end-to-end on the real file 468→471, 469→472; sibling §469§472; foreign §447 untouched; §4470 untouched
tri skill check after 436 sections, no number used twice

Mutation — and the third one is the point:

mutant result
drop the word boundary, so §110 becomes §4710 a_prefix_of_a_longer_number... FAILED
accept a non-append silently an_append_is_recognised_and_an_edit_is_not FAILED
take the first number from the tail instead of the base SURVIVED all six tests

That last mutant is the exact line behind the second collision, and no test could see it because the decision lived in the integration path. It moved into plan(), and the new test fails when two tails carrying different existing numbers plan differently. With that test in place the mutant is killed, 6 passed / 1 failed.

Demonstrated on itself

This change's own section was written as ## 999. and the command placed it at 471. The section also confesses a defect it created: it first said "by the rule two sections up", and a positional cross-reference is broken by the very renumbering this command performs — the section it pointed at is on an unmerged branch and will land somewhere else. Name the rule, not its address.

What it will not do

It cannot help when master wrote the same lesson, not just the same number. Numbering is the mechanical half; two people writing one lesson twice is a different problem and this tool has no opinion about it.

…ft free

Closes #3051

Six section-number collisions in one week on one file. Two of them hit the
same branch four hours apart: renumbered to 468/469, master then took 468,
469 and 470, and it moved again to 471/472.

The repair never varied -- rebuild from origin/master, re-append with the
next free numbers, assert the master prefix is byte-identical, re-run
`tri skill check` -- and nothing in it needed judgement. Only the assertion
needed care, because the file is 12,000 lines and a lost section looks
exactly like a file that never had it.

The command leans on the invariant the workflow already has: a section is
APPENDED, so the branch's file is the merge base's file plus a tail, and the
tail is what moves. When that is false it refuses and says so; it cannot
tell which lines are yours once an existing section has been edited, and a
quietly guessed split point would be worse than the manual repair.

Three mutants, three kills, and the third one is the point. Dropping the
word boundary in the reference rewriter (`§110` becoming `§4710`)
and accepting a non-append silently were both caught by the first six tests.
Taking the first number from the TAIL instead of the base -- the exact line
behind the second collision -- SURVIVED all six, because that decision lived
in the integration path. It moved into `plan()`, and the new test fails when
two tails carrying different existing numbers plan differently.

Demonstrated on itself: this change's own section was written as `## 999.`
and the command placed it at 471.
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 19:52:42 UTC

Summary

Status Count
Total Open PRs 15
PRs with Failing Checks 10
PRs with All Checks Green 5
READY 1
FAILING 10
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=9b8875f1c9d4 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

…d never glue a heading

Refs #3051

Two defects found by the command's FIRST real use, on the conflict it was
written for.

One: the byte-prefix test refused, because master had REWORDED an existing
section while the branch was open. The branch's file is then not any commit's
file plus an append -- it is an older base plus a tail with the base moved
underneath it in the middle. Falls back to identifying the tail by shared
section TITLES, and prints which method it used.

Two, and this one it shipped: a tail found by title starts AT its first `## `
heading with no leading newline, so concatenating it onto a trimmed base glued
the heading to the base's last line and it stopped being a heading. 436
sections became 435. The count caught it; reading the file did not.

Fixed with an explicit joiner and a pre-write guard: the rebuilt file must
have exactly base + tail sections or nothing is written. Mutating the joiner
back kills the new test.

Also: when the numbers already follow the base but the base region is stale,
the file is now rebuilt rather than left alone -- that is the other half of
the same repair, and it prints what it discards (any edit to a section the
base already had) before doing it.

544 -> 547 tests.
@gHashTag

gHashTag commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

First real use, two defects, one of them shipped

I pointed this command at the conflict it was written for — PR #3044, the seventh collision on this file — and it found two things.

It refused, correctly. Master had reworded an existing section while that branch was open, so the branch's file was not any commit's file plus an append: it was an older base plus a tail, with the base moved underneath it in the middle. The byte-prefix test cannot see a tail there, and refusing was the right answer. Added a fallback that identifies the tail by shared section titles — a section whose title is in the base is the base's, however its body was reworded — and the output now says which method it used.

Then it shipped a defect, and the count caught it. A tail found by title starts at its first ## heading with no leading newline. Concatenating that onto a trimmed base glued the heading to the base's last line:

however few minutes earlier it was.## 471. A lesson written down four times, ...

It stopped being a heading. 436 sections became 435. I noticed because I asserted origin/master count + 2, not because I read the file — the glued line is one character different from correct and sits at line 12,386 of 12,475.

Fixed two ways, because one of them is a guard and the other is the bug:

  • an explicit joiner, with a unit test that fails when the heading is not at the start of a line (mutating the joiner back: FAILED, 10 passed / 1 failed);
  • a pre-write check: the rebuilt file must have exactly base + tail sections or nothing is written at all. The write moved after the check, so a future variant of this cannot reach the disk.

Also added: when the numbers already follow the base but the base region is stale, the file is now rebuilt instead of left alone — that is the other half of the same repair — and it prints what it discards (any edit you made to a section the base already had) before doing it.

PR #3044 is repaired and pushed: 436 sections, master prefix asserted byte-identical, my two sections at 471 and 472.

Correction to the last commit message: it says "544 -> 547 tests". The real figure is 544 -> 545. cargo test -p tri --bins reads 545 passed, 0 failed, 0 filtered. I am not force-pushing to fix a commit message; the number is corrected here and in the PR body, and the squash message will carry the right one.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 20:00:38 UTC

Summary

Status Count
Total Open PRs 15
PRs with Failing Checks 11
PRs with All Checks Green 4
READY 0
FAILING 11
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=9b8875f1c9d4 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

gHashTag added a commit that referenced this pull request Sep 3, 2026
Refs #3042

Eighth collision. master took 471 and 472 in #3049 -- and not only the numbers:
that PR's own 472 is "Cannot check is not absent", the same class this branch's
sections are about. Numbers moved by the command from #3052; the subject overlap
is noted rather than resolved by renaming.
…r than master

Refs #3051

Eighth collision, and the first one BETWEEN two of my own open branches rather
than with master: #3053 and this one both claimed 471. Resolved with the
command's own --base flag pointed at the sibling branch instead of master, which
is what that flag is for and had not been used in anger before.

Note for the tool: it can only see one base at a time, so N open branches still
need N-1 sequential renumbers in a chosen order. It does not discover siblings.
Refs #3051

Correcting the previous commit on this branch, which claimed to renumber to
475 and did not. The command REFUSED -- "no merge base with
origin/w48b-tri-fmt", because `git fetch origin <branch>` updates FETCH_HEAD
and not refs/remotes/origin/<branch> unless the remote's refspec covers it --
and I piped its output through `sed -n '2,14p'`, which swallowed the error
line. My own check then printed "prefix identical: True" against an EMPTY base
string, which is vacuous and read as confirmation. The section stayed at 471,
colliding with master, and was pushed that way.

Two guards would each have caught it: read the command's exit code rather than
its first fourteen lines, and assert the base is non-empty before comparing
against it. Both are in the check I now run after every renumber.

Then the substantive finding. With the ref fetched properly, `--base
origin/w48b-tri-fmt` numbered correctly around the sibling -- and rebuilt my
file ON the sibling, so this branch carried the sibling's two sections and
would have merged them under this PR. `--base` is for a different base BRANCH,
not for a peer. Number every branch against the shared base; whoever merges
second conflicts and renumbers, which is now one command.

Recorded in the module doc and in the flag's own help text, where the next
person meets it.

Section is 473 against origin/master. 553 tests.
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 20:20:15 UTC

Summary

Status Count
Total Open PRs 16
PRs with Failing Checks 10
PRs with All Checks Green 6
READY 1
FAILING 10
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=9b8875f1c9d4 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

1 similar comment
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 20:24:50 UTC

Summary

Status Count
Total Open PRs 15
PRs with Failing Checks 10
PRs with All Checks Green 5
READY 0
FAILING 10
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=9b8875f1c9d4 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

Refs #3051

The `--base origin/<sibling>` trap was left as a paragraph in the doc, and a
paragraph is what this command's own section says is not enough. `--first N`
is the thing the paragraph was standing in for: both branches number against
the SHARED base, and the second one starts higher. A number, not a different
base -- pointing --base at a peer rebuilds your file on it and carries its
sections into your PR.

A start at or below the base's highest is refused. Starting there collides
with the very file being numbered against, which is the one thing this command
exists to prevent, and the refusal names the floor so the caller can pick.

Two mutants, two kills, each by its own test: accepting any --first kills the
refusal test alone; ignoring --first and always deriving kills both. 555 tests.
@gHashTag

gHashTag commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

Added --first N, because the trap deserved a flag and not a paragraph

The --base origin/<sibling> mistake was left documented. This PR's own section says a rule written down and broken anyway earns an executable, so leaving it as prose was the wrong shape for exactly the reason the section states.

Both branches number against the shared base; the second one starts higher:

  origin/master highest section  472
  --first                 480   (asked for, not derived)

      473  ->  480

A start at or below the base's highest is refused, and the refusal names the floor:

Error: --first 100 is not above the base's highest section (472).
Starting there collides with the file being numbered against.
mutant result
accept any --first (drop the collision guard) a_requested_start_that_would_collide_is_refused FAILED (1 passed / 1 failed)
ignore --first and always derive both tests FAILED (0 passed / 2 failed)
restored 2 passed

cargo test -p tri --bins555 passed, 0 failed, 0 filtered.

Also: auto-merge is now on for this PR. Five of my branches were chasing a master that moves every few minutes, and the sync-on-BEHIND loop was restarting ~35 checks each time — a treadmill, not progress. --auto lets GitHub merge each when its own checks settle.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 20:34:55 UTC

Summary

Status Count
Total Open PRs 16
PRs with Failing Checks 9
PRs with All Checks Green 7
READY 0
FAILING 9
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=9b8875f1c9d4 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

Refs #3051

Ninth collision, and the first use of --first in anger. #3053 is open with two
sections that will land at 476/477, so this one starts at 478 -- numbered
against the SHARED base, which is the whole point of the flag: --base pointed
at that sibling would have rebuilt this file on it and carried its sections
into this PR.
Refs #3051

Tenth collision. Also a correction to the previous commit's reasoning: I said a
multi-ref `git fetch origin master w48b-tri-fmt` had left origin/master stale.
It had not -- the parent of that merge is 6c54ab3, which was the tip at the
time, and #3056 landed afterwards. Master moved between the merge and the
check. The w48b-tri-fmt ref failure earlier in the session WAS real; conflating
the two was my error.
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 21:48:22 UTC

Summary

Status Count
Total Open PRs 14
PRs with Failing Checks 10
PRs with All Checks Green 4
READY 3
FAILING 10
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=9b8875f1c9d4 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

Refs #3051

The previous commit's own check printed `OK=False` -- 442 sections where 440
were expected -- and I pushed anyway. The guard fired and I did not read it,
which is the whole failure; the corruption is downstream of that.

What it did: after resolving the conflict with `git checkout HEAD -- <file>`,
the tail no longer stood in the append relation the command needs, and the
renumber moved a slice that included master's own 475 and 478. The file ended
with three duplicated titles.

Rebuilt as origin/master plus my one section, asserted: 440 sections, master
prefix byte-identical, zero duplicate titles.

The lesson is not about the tool. A count that disagrees with its expectation
is a stop, not a line of output to scroll past -- which is what every section
in this file about population counts says, in the file this commit edits.
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-03 21:49:30 UTC

Summary

Status Count
Total Open PRs 14
PRs with Failing Checks 10
PRs with All Checks Green 4
READY 3
FAILING 10
PENDING 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=9b8875f1c9d4 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag
gHashTag merged commit 519d189 into master Sep 3, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Six section-number collisions in a week on one SKILL.md, and the repair never varied

1 participant