Skip to content

docs(os-regen): refuse the =false spelling, record the silent incoming-side drop, and scope os-regen-merge.sh to main - #18648

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-18343-os-regen-residual-readings
Sep 17, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-18343-os-regen-residual-readings

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #18343

Three residual readings on the merge=os-regen driver, carried over from the #15815 thread. One driver, one PR — the card forbids splitting them, and the verification below was re-run together on a8f9e52fd7a67db44d5653aadf27dd396e386a16.

Two landing files only: AGENTS.md (§11 driver block) and scripts/pm/os-regen-merge.sh. The driver itself (.gitattributes, scripts/git-merge-regen.mjs) is untouched — reading 3's answer turned out to be "state it", so it demanded no line there.


Reading 1 — the =false spelling is now refused by literal spelling

The card's finding: both authoritative texts refused only the empty string, so a reader grepping =false found nothing in either. Fixed in both, folded into the existing refusal — no line added in AGENTS.md.

AGENTS.md :606–:608, before:

     with `-c merge.os-regen.driver=`, which does not disable the driver but leaves git
     failing to run it and reporting a conflict for every routed path, including ones that
     text-merge cleanly.

after:

     with `-c merge.os-regen.driver=` or `-c merge.os-regen.driver=false`, neither of
     which disables the driver: git runs the configured program, it fails, and every
     routed path is reported conflicted, including ones that text-merge cleanly.

scripts/pm/os-regen-merge.sh :143, before:

# ⛔ NOT `git -c merge.os-regen.driver= merge-tree --write-tree BASE HEAD`. The
# empty string does not DISABLE the driver — git still tries to RUN it, fails, and
# marks the path conflicted, so that spelling reports a conflict for EVERY routed
# path including ones whose text merges perfectly.

after — both spellings named, with the reason the literal matters:

# ⛔ NOT `git -c merge.os-regen.driver= merge-tree --write-tree BASE HEAD`, and
# ⛔ NOT `git -c merge.os-regen.driver=false merge-tree …` either. NEITHER spelling
# DISABLES the driver — git still tries to RUN the configured program (nothing, or
# `false`), it fails, and the path is marked conflicted, so BOTH report a conflict
# for EVERY routed path including ones whose text merges perfectly. Both are spelled
# out here because readers grep the LITERAL: `=false` is the spelling actually in
# circulation, and "same family as the empty string" is a thing no grep finds.

⛔ Not taken on the family argument. The card forbids resting on "mechanically the same family", so =false was measured on its own, this tree, git 2.43.0, two pairs over a 200-line routed blob (packages/spec/spec-changes.json):

pair driver ON -c …driver= -c …driver=false bare shared clone
both sides edit the SAME line (really conflicts) exit 0 ✗ exit 1 ✓ exit 1 ✓ exit 1 ✓
edits ~190 lines apart (text-merges cleanly) exit 0 ✓ exit 1 ✗ exit 1 ✗ exit 0 ✓

Cell for cell what the empty string does — a false POSITIVE on row 2, where the driver-free bare shared clone (GitHub's actual condition) correctly says exit 0. One difference worth recording, and it is in the worse direction: the empty string at least prints error: cannot run : No such file or directory; =false prints nothing at all. The measured column is recorded in the script header beside the 2026-08 table.

Verification re-run (the card's own instrument, with its firing control):

                                   origin/main   this branch
'driver=false'  in AGENTS.md                 0       1
'driver=false'  in os-regen-merge.sh         0       1
control 'merge.os-regen.driver=' in both     1/1     2/2    ⇐ the instrument is firing

Reading 2 — the silent incoming-side drop, REPRODUCED first

The card's acceptance is explicit: ⛔ reproduce before designing anything. Done first, in a scratch repo with this clone's real driver registered, on the routed path packages/spec/spec-changes.json, both sides editing it, plus one ordinary hand-written file edited on the incoming side as a firing control.

Direction A — the loss is real, and invisible:

git merge, driver ON                        exit 0
incoming side's token in the merged file    0 occurrences   ⇐ THE LOSS
our side's token in the merged file         1
control: incoming prose in the plain file   1               ⇐ the merge itself worked

read against the merge commit it produced:
git show --stat / git log -1 --stat         names the routed path 0 times
git diff / git diff HEAD                    0 lines
git status --porcelain                      0 lines
control: first-parent diffstat              names the control file 1 time
git diff --stat HEAD^2 HEAD                 names the routed path 1 time

Direction B — instruments that DO shout, on the same merge:

$GIT_DIR/os-regen-pending                   exists, names the path       1
driver-free bare-shared-clone merge-tree    exit 1, names the path       3
after regenerating, git diff --cached       names the path               1

The mechanism, which is what makes it a rule and not an anecdote: the merged result IS our side, so it differs from the first parent in nothing — which is exactly why a merge commit's combined diffstat omits it and git diff reads clean. "The tree is clean" is the symptom, not the all-clear.

⚠️ One refinement on the card's wording, reported because it is a reading: the drop is not silent at merge time — the driver prints a per-path ⟳ notice on stderr naming the regeneration command. What is silent is everything after: the commit, its diffstat, and every ordinary post-merge read. A transient console line is not a check.

AGENTS.md :584–:593, the change (in place, no line added):

before: ... on purpose: the driver exits 0 while silently dropping one side, so only a
        separate regeneration commit on a known-good base lets a reviewer read ...

after:  ... on purpose: the driver exits 0 while silently dropping one side, and no
        diffstat or `git diff` shows the drop, so only a separate regeneration commit
        on a known-good base lets a reviewer read ...

The script gets the full measurement as a new header section (## What "SILENTLY" above means, measured — and why no post-merge check sees it), which also names the four places the drop IS visible, each of which has to be looked at on purpose. And the one per-path notice the script already prints where a drop happened gained the second half:

   ⚠ TAKING main's side of PATH (both sides changed it)
     — the os-regen driver merged it with exit 0 and silently kept one side.
+    MEASURED: nothing downstream reports that drop — it is absent from the
+    merge commit's diffstat and `git diff`/`git status` read clean over it.
       Step 4's regeneration re-derives the generated content on top. ...

Reading 3 — stacked branches: STATED, not fixed. Here is why

What I did: stated the limitation in the script's header, in its --help, and in the run output before step 1 touches anything. Why not a --base flag: because AGENTS.md already rules the form out, and building tooling for it would contradict a standing decision:

⛔ Off main is literal — a stacked series, each PR branched off the one below, is NOT a supported form. … A multi-card change uses a trunk branch … ⛔ No gate or merge-policy change is made for it.
— AGENTS.md, Multi-agent discipline §2

The cost reading the card left unmeasured, now measured: 19 occurrences of origin/main, of which 11 are executable sites (fetch, merge-base, the refusal message, git diff for main_edited, step 1's git merge, step 2's git restore --source=, step 3's commit message, step 4's printed grep target and three echoes) plus 2 self-test assertions that pin the literal string git restore --source=origin/main at the source. So it is not the "two or three sites" half of the card's guess — but the cost is not the reason to decline; §2 is.

The sharper half of reading 3, which the card's usage-text evidence does not show: on a stacked branch this helper does not refuse. git merge-base HEAD origin/main still answers — with the trunk's fork point — so it runs, merges origin/main instead of the parent branch, and step 2 takes main's side of generated artifacts the parent branch owns. A silently wrong answer, not an error, in the one situation the script exists to make safe. That is now the first thing its header says.

--help after (new paragraph; the card's ⛔ "not something you discover by invoking it"):

Run INSIDE the feature branch's worktree, on a clean tree.

⛔ The base is ALWAYS `origin/main`; there is no `--base` and none is planned. A
branch stacked on another feature branch is OUT OF SCOPE: this script would merge
`origin/main` into it and take main's side of the parent branch's generated
artifacts. AGENTS.md Multi-agent discipline §2 rules that form out and prescribes a
TRUNK branch instead — land the trunk onto `main` and run this script there.

And at run time, printed before the fetch, so it is not discovered afterwards either:

→ base: origin/main — always; there is no --base (see --help). A branch stacked
        on another feature branch is OUT OF SCOPE here: this would merge origin/main
        into it and take main's side of the parent branch's generated artifacts.

AGENTS.md carries its half in the one place it names the helper — is the in-repo authority for landing one **onto main** — rather than restating §2, which this file already carries once.


AGENTS.md payment declaration — in place, line-neutral

1099 → 1099. No line added, so nothing was bought and nothing is owed. ⛔ No re-wrap paid for anything: the block's local wrap (~90 chars, well under the 120-byte per-line budget) is unchanged, and the corollary bullet's first six lines are byte-identical to main — only :606–:608 moved, three lines replaced by three.

Both additions fit because their paragraphs' last lines were short. Where that was not quite enough, four word-level compressions inside the MERGE bullet paid the difference. Every one preserves its fact; listed so it can be checked without reading the diff:

before after fact removed
for landing one of these branches for landing one onto \main`` none — gains reading 3's base
records that merge as a deferral and then holds you to it records it as a deferral and holds you to it none — "that merge"→"it", drops "then"
the immediately following commit must discharge it the next commit must discharge it none
defer on top of an outstanding one … rather than this one defer onto an outstanding one … not this one none

Ratchet output on this head (pnpm check:pm-skill-ratchet, exit 0):

✓ check-skill-line-ratchet: AGENTS.md: widest table row is 768 bytes (pin 768; headroom 0).
✓ check-skill-line-ratchet: AGENTS.md is 1099 lines (ceiling 1099; headroom 0).

scripts/pm/os-regen-merge.sh is not line-ratcheted: 1453 → 1521 lines, the header's existing style kept.


Verification

bash scripts/pm/os-regen-merge.sh --self-test on this head: exit 0, 79 cases pass. ⛔ No existing criterion was relaxed and no self-test case was deleted — the two source-scan pins that police step 2's spelling still read exactly 1 and 0 against the head:

grep -c 'git restore --source=origin/main'  (non-comment lines)  -> 1   (pinned 1)
grep -c 'git checkout origin/main --'       (non-comment lines)  -> 0   (pinned 0)

bash -n exit 0. Control-character self-scan over both files: no hits (grep -naP exit 1).

Repo-wide pnpm lint narrowed to the touched paths, with the narrowing proven rather than asserted: ① the covered population read from eslint.config.mjs itself is **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} — no config block matches .md or .sh; ② --format json over both paths reports 2 files, 0 errors, exit 0; ③ the diff therefore contains zero files of any extension eslint configures, so it cannot move an untouched file's verdict in either direction.

Derived gate list with exit codes

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack from this worktree (⛔ no hand-fed path list — the tool took its own change set vs merge base 32be735e5): 29 families, 29 run, all exit 0.

node scripts/check-ci-filter-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-governed-prose :: exit 0
pnpm check:pm-skill-id-lint :: exit 0
pnpm check:pm-skill-ratchet :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:required-contexts :: exit 0
pnpm check:watch-hint-literal :: exit 0

Reconciled: --ran with exit codes recorded ⇒ 29 derived famil(ies) accounted for — 29 run, 0 NOT-MEASURED (a DERIVED zero — all 29 recorded an exit code and none of them is 3).

node scripts/pm/check-governed-merges.mjs --test AGENTS.md scripts/pm/os-regen-merge.sh ⇒ exit 3, GOVERNED (AGENTS.md ×1 — the repo-root agent instruction file). Draft, four-piece; ⛔ no seat flips it ready, queues it or arms auto-merge — the maintainer's approval lands it.

skip-changeset: nothing here sits under any package's files[] — AGENTS.md is a repo-root instruction file and scripts/pm/** is not published.


Acceptance notes

Noted, not filed: the script's --self-test has no battery roster or verdict handshake, which AGENTS.md's "Writing a --self-test" section now prescribes for new ones. It is a pre-existing shape, not something this PR introduces, and rewiring it would be a second change riding a rules PR. Who takes it: the next PR that adds a case to this self-test.


维护者速读(草稿)

改了什么 —— 一张卡,三条关于 os-regen 合并驱动的残留读数,落在两个文件上。① 把 driver=false 这个拼法按字面写进两处权威文本的拒绝句里(此前只拒空串,拿 =false 去搜的人两处都搜不到)。② 把"真合并会静默丢掉 incoming 一侧、而且合并后的常规检查一个字都不会说"这件事,先独立复现再记录下来。③ os-regen-merge.sh 不接 base:明写"栈式分支不支持"并指出该怎么办,没有去加 --base。

为什么改 —— 这三条的共同要害是读者按字面去搜、按常规去看,什么都看不到。①是搜不到:本仓刚栽过一次同样的跟头(报告写 section-7、树上是 §7,复核者读出一个假零)。②是看不到:合并结果就是我们这一侧,所以合并提交的 diffstat 里它压根不出现,git diff 读过去干干净净 —— 危险的不是丢内容,是丢了还伪装成干净合并。③是调用了才发现:而最需要它的场合(栈式分支)恰恰是它会悄悄给出错误答案的场合,不是报错。

风险与代价(含回滚) —— 风险低。两个文件都是给人看的文本,没有运行时行为改变;脚本侧新增的是注释、--help 文案、一行运行时提示,加一条已有通知的下半句。AGENTS.md 1099 → 1099,行数零净增,没有动天花板、没有靠折行筹行;为塞下新内容做的四处措辞压缩逐条列在上面的表里,每一条都不丢事实。脚本自检 79 例全过,两条钉住 step 2 写法的源码断言读数不变(1 和 0)。回滚就是 revert 这一个 commit,不牵动任何其它面。

③ 为什么只写不修 —— AGENTS.md 多 agent 纪律 §2 已经裁定栈式分支不是受支持的形态,并逐字写明「⛔ No gate or merge-policy change is made for it」。给这个助手加 --base 就是在为那条裁决拒绝的形态造工具。代价也量了(11 处可执行站点 + 2 条钉字面的自检断言),但代价不是理由,§2 才是。若您认为该推翻 §2 这一条,那是另一张卡、另一个决定。

席位意见 ——

你要做的 —— 读一遍上面「AGENTS.md payment declaration」里的四行压缩对照表(那是本 PR 唯一改写了既有规则措辞的地方),认可即批准合并;这是受管面,需要您亲自点这一下。


⚠️ Angle-bracket placeholders in the quoted excerpts above are respelled as bare words (BASE HEAD, PATH) — GitHub's body sanitizer eats short angle-bracket fragments, fenced ones included. The tree's own text uses the angle-bracket spelling.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu


Generated by Claude Code

…side drop, and scope the helper to main

Three residual readings on the os-regen merge driver, one driver, one PR.

1. `-c merge.os-regen.driver=false` is now refused by literal spelling beside
   the empty-string one in both authoritative texts. Re-measured: it answers
   exit 1 on a pair that text-merges cleanly, cell for cell what the empty
   string does.

2. The silent incoming-side drop is reproduced and recorded: the merge result
   IS our side, so the merge commit's diffstat, `git diff` and `git status`
   all read clean over it. The four places it IS visible are named.

3. `os-regen-merge.sh` takes no base and hardcodes origin/main; a branch
   stacked on another feature branch is stated out of scope in the header,
   the usage text and the run output, with AGENTS.md's trunk-branch remedy.

AGENTS.md is line-neutral at 1099/1099.

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

维护者速读(终稿)

skills 席 session_01Gqi43smmqjJ5sUrhfoPeKu,2026-09-17T12:33Z。对照本席自己读的 diff 校正 dev 草稿;席位意见已填。

改了什么 — 两个文件,把 #15815 线程上遗留的三条 os-regen 驱动读数接进权威文本。AGENTS.md 只动 MERGE 那一条 bullet 及其下一条(+12 / −12,1099 → 1099 行不变,原地重排,不动 CEILINGS 行):-c merge.os-regen.driver= 的拒绝句旁边并列写上 =false 这个字面拼法,并补一句「diffstat 与 git diff 都看不见那次丢失」。scripts/pm/os-regen-merge.sh 只改注释、--help 和 echo(+77 / −9):头注新增两节 —— 「base 恒为 origin/main,栈式分支超出范围」和「静默丢失的实测(两个方向)」;=false 并入已有的拒绝行和测量表;运行时在动树之前先打印 base 说明;步骤 2 的逐路径提示补上「下游没有任何东西会报这次丢失」。⛔ 驱动本身一行未动,⛔ 自检用例一条未删(--self-test 在 head 与 main 上都 exit 0)。

为什么改 — 三条读数是同一个驱动的三个面:① 读者按字面 grep,=false 在两处权威文本里都查不到(而 spec 席实测过它,还在栈上告诉了每个 dev);② 驱动开启的真合并会静默丢掉 incoming 一侧,且合并后的常规检查一个字都不说 —— dev 先复现再落笔,方向 A 量出丢失、方向 B 量出四个能喊出来的地方;③ 助手不接 base,栈式分支上会静默给出错误答案而不是拒绝。

风险与代价(含回滚) — 治理面,只改文本与提示;不动驱动、不动关卡、不放宽任何判据。③ 选的是「明写不支持并指路」而非加 --base:依据是 AGENTS.md :465–469 已裁「栈式系列不是受支持的形态,⛔ 不为它改任何关卡或合并策略」,给助手加第二个 base 就是在为被裁掉的形态造工具。回滚 = revert 一个提交,零副作用。

席位意见 — 荐合。三条都按卡上的验收原样落:字面拒绝、先复现后记录、明写不支持并指路;行数零余量下原地 1099 行不变,没有买行也没有欠行。

你要做的(一个动作) — 认可 ③「不加 --base,栈式明写超出范围」即 Approve 本 PR(或直接人工合并);席位随后按裁定 C 落地。若你想让助手接 base,Request changes,本席另起一张并同时提 §2 的修订。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 17, 2026 14:37
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit ae9545c Sep 17, 2026
44 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-18343-os-regen-residual-readings branch September 17, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants