Commit b5e09b2
fix(devx): teach the os-regen pre-commit hook the deferred merge (#8205)
* fix(devx): teach the os-regen pre-commit hook the deferred merge (#8047)
Two in-repo authorities contradicted each other on main. `scripts/pm/os-regen-merge.sh`
— the sanctioned landing sequence for a branch touching `merge=os-regen` paths —
commits the merge BEFORE regenerating, deliberately: the driver exits 0 while
silently dropping one side, so only a separate regeneration commit on a known-good
base lets a reviewer read "what main brought" apart from "what the change produces".
The `os-regen` pre-commit hook refused exactly that commit. The way out people
learned (measured on PR #7851) was to skip the entire pre-commit hook, which trades
one false positive for a blanket bypass. Maintainer ruling 2026-08-12: the hook moves.
The merge commit is now DEFERRED, not passed: `pre-commit` records
`deferred-at <head> <merge-head>` in the existing marker and lets the commit through.
Two properties keep that a split rather than an escape hatch.
One commit deep, by construction. A deferral is entered only while MERGE_HEAD exists,
a second merge attempted while one is outstanding is refused, and every non-merge
commit after it is refused by the ordinary staleness check — so nothing can land
between the merge and its discharge. "The immediately following commit" is enforced by
there being no other commit it could be.
Collected where collection becomes possible. At the moment the merge commit is
created, the commit that discharges it does not exist, so pre-commit can only record.
The two events that can follow are the next commit (this same check) and the push, so
`.githooks/pre-push` — new, registered by the existing `core.hooksPath=.githooks`,
77ms and a marker stat on every push that did not defer — refuses a push that still
owes one. An undischarged deferral cannot leave the machine.
Prose reconciled across all three authorities that disagreed: the script header (the
authority), AGENTS.md §11, and what the hook prints. The hook's refusal now names
`scripts/pm/os-regen-merge.sh` instead of advertising a bypass.
Tests: `--self-test` grows a decision table over all five states plus a fixture that
builds throwaway git repos and replays the two-commit sequence — behaviour about a
PAIR of commits, which no single-tree assertion can express. Gates are redirected at
the fixture's own package.json so the replay costs no spec build; a mistake there
fails safe (a directory without those scripts reads as stale). The pre-push hook's
index mode is asserted too — git ignores a non-executable hook in silence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q983j8EGKjUs8r14n76TeH
* fix(devx): drop the issue-ID citation from the AGENTS.md deferral bullet
`check:pm-skill-id-lint` refuses issue numbers in AGENTS.md operative text:
a lesson has to be self-contained (failure mode + discipline + boundary) and
a ruling keeps its date, so the number carries no information a reader needs.
The bullet already states the mechanism in full; the citation becomes the
ruling's date. Gate green locally, self-test included.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q983j8EGKjUs8r14n76TeH
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 814feb8 commit b5e09b2
5 files changed
Lines changed: 422 additions & 22 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
8 | 20 | | |
9 | 21 | | |
10 | 22 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
364 | 364 | | |
365 | 365 | | |
366 | 366 | | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
367 | 379 | | |
368 | 380 | | |
369 | 381 | | |
| |||
0 commit comments