Skip to content

fix(git-merge-regen): anchor merge=os-regen on whitespace tokens (#15701) - #15748

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-15701-git-merge-regen-token-anchor
Sep 5, 2026
Merged

fix(git-merge-regen): anchor merge=os-regen on whitespace tokens (#15701)#15748
baozhoutao merged 1 commit into
mainfrom
claude/issue-15701-git-merge-regen-token-anchor

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #15701

The defect

reconcileAttributes in scripts/git-merge-regen.mjs selected the routed .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 word-boundary spelling admitted rows git routes to a
different driver:

row \b spelling token anchor what git says
x merge=os-regen true true os-regen
x merge=os-regen-v2 true false os-regen-v2 — a different driver
x merge=os-regen.2 true false os-regen.2 — a different driver
x merge=os-regenX false false os-regenX

Latent, not live: no sibling driver with this prefix exists, so on today's file both spellings
return the same 18 paths. It matters because reconcileAttributes compares .gitattributes
and REGEN_ARTIFACTS in both directions — a sibling's row would red as mapped to merge=os-regen but not declared in regen-artifacts.mjs: a rule the row does not break, on a
path 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:

const DRIVER_ATTRIBUTE_ROW = /(^|[ \t])merge=os-regen([ \t]|$)/;

The filter chain moved into routedPaths(text) — text in, paths out — for one reason: the live
.gitattributes carries no sibling-driver row, so the file this reader reads cannot
discriminate the loose spelling from the anchored one. The fixture needs the same code the live
reader runs.

reconcileAttributeTokenAnchor is the new self-test battery holding that predicate against a
fixture 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 list
forgot to name still reds). Same reason reconcileOwnership exists beside reconcileScripts:
the live tree is green either way. Roster entry, SELF_TEST_BATTERY_FLOOR 12 to 13, dispatch
list and the prose that counts the callees were updated with it.

Verification

Exit codes captured before any pipe; verdict lines are the gates' own.

run exit verdict
node scripts/git-merge-regen.mjs --self-test (before, on origin/main) 0 ✓ .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) 0 same live verdict, agree on 18 path(s), plus ✓ .gitattributes row filter: 9 case(s) pinned, sibling drivers (merge=os-regen-v2, merge=os-regen.2) refused
pnpm check:merge-driver 0 the same self-test, through the manifest script name
node scripts/pm/check-governed-merges.mjs --test scripts/git-merge-regen.mjs 0 ✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.
node scripts/check-changeset-fixed.mjs 0 ✓ .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_ROW
reverted 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), and
the restore was proven by blob hash (57c0cc328bdf8ca561770e1f379a0f6831a85206 before and
after) with git diff HEAD empty. The mutation script carried a trap ... EXIT INT TERM
restoring an absolute path.

✓ .gitattributes ↔ regen-artifacts.mjs agree on 18 path(s)          ← still GREEN, the card's point
✗ .gitattributes row filter /\bmerge=os-regen\b/:
  merge=os-regen-v2 does NOT route — it is a different driver
  merge=os-regen.2 does NOT route — it is a different driver
  exactly the 3 routed rows come back
...
✗ merge driver wiring is inconsistent — 1 failure(s) (cases and floor); see above.

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 installed
  • node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test (exit 3) — same
  • node scripts/check-ci-filter-parity.mjs (exit 3) — check-ci-filter-parity: PREREQUISITE NOT MET — the dependency yaml is not installed
  • node scripts/check-closing-keyword-parity.mjs (exit 3) — same, yaml
  • node scripts/check-closing-keyword-parity.mjs --self-test (exit 3) — same, yaml
  • pnpm check:driver-memory-census (exit 3) — ts-parse: PREREQUISITE NOT MET — the dependency typescript is not installed
  • node 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 either

The union above was run at the final commit 99dd56749, which is this branch's head — no
commit 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

  • Cross-check, no edit: the awk reader in scripts/pm/os-regen-merge.sh is already
    token-anchored — /(^|[ \t])merge=os-regen([ \t]|$)/ { print $1 }, character for character
    the 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.
  • The real .gitattributes and scripts/regen-artifacts.mjs are untouched.
  • No changeset, and skip-changeset applies: AGENTS.md — "that label is for a diff that
    publishes 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.
  • Not widened, deliberately: the anchor is the exact form the card prescribes, so a CRLF
    .gitattributes row would no longer match where \b did (\r is neither [ \t] nor end of
    string). 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

`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
@github-actions github-actions Bot added the size/m label Sep 5, 2026
@baozhoutao baozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 5, 2026 — with Claude
@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — PR #15748 (head 99dd56749) reviewed against the GitHub three-dot diff (1 file, +117/−13), not the report.

The change is exactly the ruling: DRIVER_ATTRIBUTE_ROW = /(^|[ \t])merge=os-regen([ \t]|$)/ replaces \bmerge=os-regen\b, the filter chain is lifted into routedPaths(text) so the fixture exercises the live reader, and the new reconcileAttributeTokenAnchor battery pins nine rows (three positive incl. tab-delimited and flanked, -v2 / .2 / X siblings refused, comment line, unrelated driver, exact count 3). SELF_TEST_BATTERY_FLOOR 12 → 13 with the roster entry. scripts/pm/os-regen-merge.sh confirmed already token-anchored and left alone — correct.

Measured by this seat (branch blob run against the main checkout's tree; .gitattributes identical on both sides): node <branch blob> --self-test → EXIT=0, ✓ .gitattributes ↔ regen-artifacts.mjs agree on 18 path(s) and ✓ .gitattributes row filter: 9 case(s) pinned, sibling drivers (merge=os-regen-v2, merge=os-regen.2) refused. check-governed-merges.mjs --test scripts/git-merge-regen.mjs✅ NOT governed. git merge-tree --write-tree origin/main <branch> → EXIT=0. No live PR of this lane touches the file.

Ablation accepted from the report (mutation proven on disk, one battery red with the live agree on 18 path(s) line still green — the reason the fixture exists; restore proven by blob hash 57c0cc328… = HEAD). NOT MEASURED here: the 7 exit-3 ts-parse/yaml gates named in the report (this checkout has the same gaps).

Flip + arm follows when both required jobs report success on 99dd56749.


Generated by Claude Code

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Flipped ready + auto-merge enabled (05:57Z, method: MERGE). Required jobs on 99dd56749: Lint & Repo Gates = success, TypeScript Type Check = success; git merge-tree --write-tree origin/main <branch> → EXIT=0 against the current main. Watched; on landing #15701 closes.


Generated by Claude Code

@baozhoutao
baozhoutao added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit 66e68ad Sep 5, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-15701-git-merge-regen-token-anchor branch September 5, 2026 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] git-merge-regen.mjs's .gitattributes reader matches merge=os-regen with a word boundary, admitting rows routed to a different driver

2 participants