fix(git-merge-regen): anchor merge=os-regen on whitespace tokens (#15701) - #15748
Conversation
`reconcileAttributes` selected `.gitattributes` rows with
`/\bmerge=os-regen\b/`. `\b` sits between a word character and a non-word
one, and `-` and `.` are both non-word, so the predicate admitted rows git
routes to a DIFFERENT driver:
x merge=os-regen \b: true token: true git: os-regen
x merge=os-regen-v2 \b: TRUE token: false git: os-regen-v2
x merge=os-regen.2 \b: TRUE token: false git: os-regen.2
An attribute value is a whitespace-delimited token, so the anchor is
whitespace: `/(^|[ \t])merge=os-regen([ \t]|$)/` — the same predicate the awk
reader in `scripts/pm/os-regen-merge.sh` already carries, character for
character.
Latent, not live: no sibling driver with this prefix exists, so the live
verdict is unchanged at `agree on 18 path(s)`. It matters because
`reconcileAttributes` compares in both directions — a sibling's row would red
as `mapped to merge=os-regen but not declared in regen-artifacts.mjs`, naming
a rule the row does not break, on a path this driver does not own, and asking
for the wrong repair.
The live `.gitattributes` cannot discriminate the two spellings, so the
predicate is extracted as `routedPaths(text)` and pinned in the self-test's
own new battery `reconcileAttributeTokenAnchor` against a fixture carrying
sibling-driver rows — the same reason `reconcileOwnership` exists beside
`reconcileScripts`. Roster, floor and dispatch list updated with it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
|
ACCEPT — PR #15748 (head The change is exactly the ruling: Measured by this seat (branch blob run against the main checkout's tree; Ablation accepted from the report (mutation proven on disk, one battery red with the live Flip + arm follows when both required jobs report success on Generated by Claude Code |
|
Flipped ready + auto-merge enabled (05:57Z, Generated by Claude Code |
Fixes #15701
The defect
reconcileAttributesinscripts/git-merge-regen.mjsselected the routed.gitattributesrows with
/\bmerge=os-regen\b/.\bsits between a word character and a non-word one, and-and.are both non-word — so the word-boundary spelling admitted rows git routes to adifferent driver:
\bspellingx merge=os-regenos-regenx merge=os-regen-v2os-regen-v2— a different driverx merge=os-regen.2os-regen.2— a different driverx merge=os-regenXos-regenXLatent, not live: no sibling driver with this prefix exists, so on today's file both spellings
return the same 18 paths. It matters because
reconcileAttributescompares.gitattributesand
REGEN_ARTIFACTSin both directions — a sibling's row would red asmapped to merge=os-regen but not declared in regen-artifacts.mjs: a rule the row does not break, on apath this driver does not own, whose suggested repair (declare the path here) is the wrong one.
The change
One predicate, anchored on the whitespace that delimits an attribute token:
The filter chain moved into
routedPaths(text)— text in, paths out — for one reason: the live.gitattributescarries no sibling-driver row, so the file this reader reads cannotdiscriminate the loose spelling from the anchored one. The fixture needs the same code the live
reader runs.
reconcileAttributeTokenAnchoris the new self-test battery holding that predicate against afixture with sibling-driver rows (
merge=os-regen-v2,merge=os-regen.2,merge=os-regenX),a comment line naming the driver, a tab-delimited row, a row flanked by other attributes, and
an exactness case (
routed.length === 3, so a predicate admitting something the case listforgot to name still reds). Same reason
reconcileOwnershipexists besidereconcileScripts:the live tree is green either way. Roster entry,
SELF_TEST_BATTERY_FLOOR12 to 13, dispatchlist and the prose that counts the callees were updated with it.
Verification
Exit codes captured before any pipe; verdict lines are the gates' own.
node scripts/git-merge-regen.mjs --self-test(before, onorigin/main)✓ .gitattributes ↔ regen-artifacts.mjs agree on 18 path(s)…✓ merge driver wiring is consistent (24 path(s) deliberately excluded).node scripts/git-merge-regen.mjs --self-test(after)agree on 18 path(s), plus✓ .gitattributes row filter: 9 case(s) pinned, sibling drivers (merge=os-regen-v2, merge=os-regen.2) refusedpnpm check:merge-drivernode scripts/pm/check-governed-merges.mjs --test scripts/git-merge-regen.mjs✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.node scripts/check-changeset-fixed.mjs✓ .changeset/config.json "fixed" group is in sync with 69 public workspace packages.Ablation — the new assertion demonstrably fails
Run against the committed tree, so the restore leg has a real reference.
DRIVER_ATTRIBUTE_ROWreverted to
/\bmerge=os-regen\b/; the mutation was confirmed on disk before measuring(
anchored_decl=0 loose_decl=1,git diff --numstat=1 1 scripts/git-merge-regen.mjs), andthe restore was proven by blob hash (
57c0cc328bdf8ca561770e1f379a0f6831a85206before andafter) with
git diff HEADempty. The mutation script carried atrap ... EXIT INT TERMrestoring an absolute path.
Ablated exit 1, restored exit 0. Exactly one battery discriminates, and it is the new
one — the live reconciliation stays green under the defect, which is why a fixture was needed.
Derived gate family
node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack(exit 0) derived 29 commands from the committed change set at
99dd56749; all 29 were run.22 exit 0. The other 7 are NOT MEASURED, not failures — this container has no
node_modules, and each of them refused before running a single check:node packages/lint/scripts/check-reference-carrier-shape.mjs(exit 3) —ts-parse: PREREQUISITE NOT MET — the dependency typescript is not installednode packages/lint/scripts/check-reference-carrier-shape.mjs --self-test(exit 3) — samenode scripts/check-ci-filter-parity.mjs(exit 3) —check-ci-filter-parity: PREREQUISITE NOT MET — the dependency yaml is not installednode scripts/check-closing-keyword-parity.mjs(exit 3) — same,yamlnode scripts/check-closing-keyword-parity.mjs --self-test(exit 3) — same,yamlpnpm check:driver-memory-census(exit 3) —ts-parse: PREREQUISITE NOT MET — the dependency typescript is not installednode scripts/check-comment-mask-corpus.mjs(exit 1) —ERR_MODULE_NOT_FOUND: Cannot find package '@typescript-eslint/parser', so it never entered the gate body eitherThe union above was run at the final commit
99dd56749, which is this branch's head — nocommit landed after it. The derivation also names 3 families whose argv takes a value from the
workflow (
check-cross-package-test-inputs,check-shard-attestation,check-test-completeness)and 16 that would apply only if this card had a changeset; those are CI's, and are listed here
so the gap is named rather than implied.
Scope
scripts/pm/os-regen-merge.shis alreadytoken-anchored —
/(^|[ \t])merge=os-regen([ \t]|$)/ { print $1 }, character for characterthe same predicate. PR fix(pm): drop the phantom # pathspec from os-regen-merge.sh's .gitattributes reader #15700 landed. Nothing to do there; reported as asked.
.gitattributesandscripts/regen-artifacts.mjsare untouched.skip-changesetapplies: AGENTS.md — "that label is for a diff thatpublishes nothing from any released package." This diff is one root-level dev tool in the
private root manifest
@objectstack/spec-monorepo; no released package ships it..gitattributesrow would no longer match where\bdid (\ris neither[ \t]nor end ofstring). This repo's file is LF and git normalises on checkout; had it been otherwise the
failure would be a loud red naming missing paths, never a silent one.
🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code