From a05861379bc7484aafda96e93c91594020ba4f6b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 14:24:13 +0000 Subject: [PATCH 1/3] =?UTF-8?q?docs(pm-dispatch,os-dev):=20=E4=B8=B2?= =?UTF-8?q?=E8=A1=8C=E6=8E=A5=E5=8A=9B=E4=B8=80=E5=A4=9C=E6=B2=89=E6=B7=80?= =?UTF-8?q?=E7=9A=84=E5=85=AD=E6=9D=A1=E7=BC=BA=E5=8F=A3=E8=A1=A5=E8=BF=9B?= =?UTF-8?q?=20SKILL=20=E2=80=94=E2=80=94=20=E6=8E=A5=E5=8A=9B=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E3=80=81=E9=94=9A=E7=82=B9=E6=8E=AA=E8=BE=9E=E3=80=81?= =?UTF-8?q?=E8=A3=81=E5=86=B3=E4=BC=A0=E6=92=AD=E6=89=AB=E6=8F=8F=E3=80=81?= =?UTF-8?q?=E5=81=9C=E6=91=86=E7=BA=A0=E5=81=8F=E3=80=81=E9=A3=9E=E8=A1=8C?= =?UTF-8?q?=E4=B8=AD=E9=87=8D=E5=8F=A0=E3=80=81=E9=A2=84=E6=9C=9F=E7=BA=A2?= =?UTF-8?q?=E5=81=9C=E6=94=BE=20(#5441)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2026-08-04/05 夜 spec 车道以串行接力连落 10 个 PR(#5304 → #5365),六个情形是 现行 SKILL 没有覆盖、靠现场即兴的,各有实付学费。按 issue 注明的落点章节逐条插入: - 第 7 步「入队与落地」新增平行小节「串行接力」:每棒一整圈(auto-merge 由 PM 挂、 dev 永不碰,ready 与 auto-merge 顺序不可反且每棒各走一次)、相邻棒同文件交接语义 而非文本(#5318/#5319 实例)、两棒散文互锁由 PM 指派分工(#5323↔#5365、#5335)。 - 「入队与落地 A」新增锚点断言措辞:authenticity = baseRev 是 origin/main 祖先 且 keys 与该 commit 逐行一致;baseRev 允许滞后;⛔ 不得要求 baseRev == merge-base (那会教唆手改锚点,即 #4650 攻击自身)。四步序的第 3 步补上「先 commit merge」, 并引用 #5370 / #5371 两个新陷阱(仅引用,不实现)。 - 第 5 步派发词 + 第 7 步 review 新增「裁决传播 = 全仓 pin 扫描」:翻 pin 一轮翻完 且必须保留承重(#5322 裁决、#5365 的 REST 层漏翻)。 - 第 6 步 Collect 的 subagent 半边新增停摆纠偏:watcher 永不触发,中途状态即停摆 信号,第三次视为不可靠改走接手协议。生产端半边同步落到 os-dev.md 资源纪律第 6 条。 - 第 5 步 same-day churn 新增姊妹段「飞行中范围重叠」:每轮读 origin/main 时对每个 在飞 dispatch 做相交判断,相交即预警(#5322 × #5335 实例)。 - 「入队与落地 B」新增依赖 PR 的预期红停放:draft 停放 + 签名级预期红清单 + 解除 条件,新签名才是真问题(#5365 的 REST 红即由此识别)。 仅改 `.claude/` 内部 agent 协议文本,不发布任何包;六条落点之外未动任何段落。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE --- .claude/agents/os-dev.md | 11 +++ .claude/skills/pm-dispatch/SKILL.md | 136 +++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 1 deletion(-) diff --git a/.claude/agents/os-dev.md b/.claude/agents/os-dev.md index af9d8aa9c9..caeec995ee 100644 --- a/.claude/agents/os-dev.md +++ b/.claude/agents/os-dev.md @@ -80,6 +80,17 @@ build/test runs OOM it.** Binding rules: applied to every process. Record the PID of what you start and operate on that PID only (`kill $PID`, liveness via `kill -0 $PID` — a `pgrep -f` pattern can match your own watcher and never terminate). +6. **Run the whole pipeline in the FOREGROUND — never park verification on a + background watcher and stop.** Build and test are steps of this task: run + them blocking, read the real output, continue. ⛔ Never return mid-task + reasoning that "a background watcher will wake me" — a completion + notification is itself the statement that no live subtask remains, so that + wake-up **never arrives** and the task sits stalled until the PM pulls it + back by hand (four agents, 6 stalls, ~1.5–2 h lost in one night). A + completion message reading "build still in progress" or "I'll resume + when…" is not a report; it is the stall. The one long wait that IS + legitimate is `flock` queueing on the shared lock in rule 1 — that one + blocks by design, so waiting it out is the rule, not a stall. **Toolchain traps — each of these cost at least one agent a false-red lap:** diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index e7deebdd8a..a24fa992f8 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -779,6 +779,44 @@ the issue's files today, and tell the dev to verify against `origin/main` rather than any working tree (Operational notes 4) — the dev's worktree is cut from `origin/main` once and never refreshes itself. +**In-flight overlap needs intercepting too — same-day churn only covers the +dispatch instant.** The paragraph above handles "main moved before takeoff"; +main lands ~18 merges a day, so it moves **after** takeoff just as often. +#5322's agent launched at 23:17Z and #5335 merged at 00:0xZ — the same two +compilers, two of the same four cells. The PM's routine check read `git log +origin/main`, spotted the overlap and sent an immediate SendMessage warning; the +agent narrowed its scope twice and dropped its own design in favour of a minimal +diff replayed inside the other PR's structure. Rule: **every round, when you +read `git log origin/main`, intersect each newly-landed PR against every +in-flight dispatch's declared file surface** — on any intersection warn at once, +with four instructions:「合 main 后重跑测试矩阵、读对方 diff 重划边界、只补它没覆盖 +的部分、**被完全覆盖就停下回报,⛔ 不要硬造 diff**」. One round late is one rework. + +This is Operational notes 8's sister paragraph: notes 8 is the PM re-checking +main **by symptom before enqueuing its own** shared-infrastructure fix; this one +is the PM re-checking it **on behalf of someone else's in-flight agent**. Same +fact that main keeps moving, two different victims — and only the PM can see the +second one, because the flying agent has no view of `origin/main` moving under it. + +**A ruling that flips public semantics ships with a whole-repo pin sweep — say so +in the dispatch prompt.** When a maintainer ruling changes a public semantic +(#5322 reclassified the empty combinator from *refused* to the **boolean identity +element**), pins of the old position live **outside** the package being changed: +the consumer layers each hold a copy — REST envelope tests, objectql, runtime. +#5365's first lap flipped only the service-analytics layer and the copy in +`packages/rest/src/analytics-filter-refusal-envelope.test.ts` went red in CI +(`expected 200 to be 400`); a second lap cleared it. Two lines belong in the +prompt: + +- **Flip them all in one pass**:「grep 错误码 / 错误消息(如 `INVALID_FILTER`) + **全仓扫描**同语义 pin,一轮翻完,不要只改本包」; +- **A flipped pin must keep bearing load**: assert the **substance** of the new + semantics (the reduced row count, the translated message, the status code) — + not "old assertion deleted". Refusal assertions for shapes that are *genuinely* + invalid stay **verbatim**: the guarded surface may never shrink. The two lines + are one rule — drop the second and "flip the whole repo" degrades into "delete + the whole repo's assertions", which is green and worthless. + **Issue 正文是线索,不是规格 —— and the dispatch wording is what makes an honest "the premise is dead" cheap to return.** Step 1's stale-premise check is the PM's sample; the dev's verification is the real thing, so the prompt @@ -867,6 +905,30 @@ to `mode:subagent`). Per issue: do not fabricate a pending agent's result. A dev that dies or returns malformed output counts as `status: "blocked"` with its raw output attached. +**A stalled subagent is this half's most common failure, and it never +self-heals.** When a dev stops mid-task reasoning that "a background watcher will +wake me", **that watcher never fires** — a completion notification is itself the +statement that no live subtask remains. Four agents stalled 6 times across the +2026-08-04/05 night, every one recovered by hand, ~1.5–2 h lost. Three rules: + +- **State the execution posture in the dispatch/relay prompt** for any long + verification pipeline:「**前台(阻塞)同步执行全部步骤,中途不停止、不把构建/ + 测试挂到后台等唤醒**」. +- **A completion notification carrying a MID-TASK state IS the stall signal** — + "build still in progress", "I'll resume when…". SendMessage it back + immediately with that posture line attached; ⛔ do not wait out any silence + threshold (the cloud-mode ~2 h below): a threshold is for *no* answer, not for + an answer that says the agent stopped. +- **A third stall means unreliable** — re-dispatch a fresh agent onto that + branch under "Handing off an interrupted dev" below (worktree already exists, + read every existing commit first, re-run the verification in full, claim and + assignee untouched). + +The **producer-side** half of this rule lives in `.claude/agents/os-dev.md`'s +resource discipline — fixing it at the producer beats patching it at the PM +(Prime Directive #12's instinct, applied to agent protocol); these three are the +backstop, not the primary fix. + **Cloud mode:** there is no direct return channel — collect through GitHub. Arm a `send_later` check-in (~15 min); on each wake, sweep the dispatched issues for `` comments and linked PRs, then re-arm @@ -887,6 +949,13 @@ against the report's own claims: - Test evidence in the report shows the actual commands and passing output, not a bare "tests pass". - The diff plausibly satisfies the issue's acceptance criteria. +- **A ruling-implementation PR: were the old position's pins flipped repo-wide?** + Fetch the changed-file list, then grep the consumer layers (REST envelope + tests, objectql, runtime) for the ruling's error code / message and confirm no + copy of the old position survives — **and** that refusal assertions for + genuinely invalid shapes are still there (step 5's two lines). #5365 slipped + through exactly this review layer and was caught by CI instead: CI does catch + it, at the price of one extra lap. - **Did the dev verify the issue's premise?** The report's `premise_still_valid` field makes the answer explicit — a `false` there reopens triage rather than failing review. A report that falsifies the @@ -960,7 +1029,8 @@ content/docs/references/** 1. `git merge origin/main`(⛔ 禁 rebase / force-push,AGENTS.md §3) 2. `git checkout origin/main -- <上述生成物>` -3. **整体重新生成**(⛔ 绝不做文本合并) +3. **先 commit 掉这次 merge,再整体重新生成**(⛔ 绝不做文本合并;⛔ 也绝不在 MERGE + 状态下跑 `gen:schema` —— 那会静默倒退锚点,见下一段与 #5370) 4. 断言**所有兄弟单的条目都还在** —— #4878 落地时是四条 step17 条目并存 一个比「条目还在」更硬的旁证:去查**上一单的实现体**是否完好(#4878 合并后核 @@ -968,12 +1038,76 @@ content/docs/references/** 实现体才是被吞的重灾区。驱动本身另有缺陷(把绝对路径写死进最后跑过 `pnpm install` 的那个 worktree),见 #4868。 +**锚点(`authorable-surface.base.json`)的断言措辞 —— 写错会教唆 dev 手改锚点。** +#5304 把 #4650 删除闸门的基线固化成树内锚点之后,接力模板曾用**错误断言** +「baseRev == merge-base」派发,后棒实测证伪并纠正。正确措辞(2026-08-05 夜后八棒全部 +沿用,零误报): + +> 断言 `pnpm --filter @objectstack/spec check:authorable-surface` **绿**即可。锚点 +> authenticity 的定义是两件事:`baseRev` 是 `origin/main` 的**祖先**,且它记录的 keys +> 与**该 commit** 的 `authorable-surface.json` 逐行一致(`verifyCommittedSurfaceBase` +> 就查这两条)。`baseRev` **允许滞后** —— `gen:schema` 只在 keys 真的漂移时才重写它 +> (在 `main` 上 merge base 就是 HEAD,该文件**必然**落后自己的 surface 一个 PR), +> 滞后只打一行 `ℹ️`,不是错误。⛔ 禁止为了凑「相等」手改锚点文件 —— 那正是 #4650 +> 堵住的攻击本身;⛔ 不得要求 `baseRev == merge-base`,那个等式不是任何门的判据。 + +配套两个新陷阱(都已立单,派发词**引用**即可,⛔ 不要在接力单里顺手实现它们): + +- **#5370:merge 未 commit 就跑 `gen:schema`,会把锚点静默倒退回旧 merge-base。** + MERGE 状态下 HEAD 仍是合并前的分支 tip,`resolveSurfaceBase()` 解出的是分支的**旧** + 分叉点;倒退后的锚点**依然 authentic**(旧 rev 也是 origin/main 的祖先、keys 也对得 + 上),于是**全部门放行**,一次已合并的锚点推进被静默撤销。这就是四步序里「先 commit + merge,再整体重生成」的由来。 +- **#5371:`gen:schema` 的 `rmSync` 顺手抹掉 `gen:openapi` 的产物。** 之后跑 + `@objectstack/rest` 会拿到 5 条 `expected 503 to be 200` 的**假红**(`openapi.json` + 被清场,不是 rest 坏了)。派发词里直接给解法:`pnpm --filter @objectstack/spec + gen:openapi` 补回,或重跑一次完整 spec build。 + **B. 跟到 MERGED 为止,不是跟到「已入队」为止。** 「auto-merge 已挂上」不是终点, 维护者对此有过明确纠正。每轮同时读**队列分支**与 `origin/main`(Operational notes 1);红了先分签名,再在「原样重投 / 推新提交 / 重新诊断」三者里选(notes 2 与 5)。 落地之后**再核一次落地判据本身** —— 队列的合并同样走 os-regen 驱动,A 里那个静默 吞并在队列合并这一步一样能发生。 +**依赖前棒才能转绿的 PR:draft 停放 + 一份精确的预期红清单。** 串行链里后棒常常先行 +实现(#5365 的四条进一致性表依赖 #5323 的 mongodb 归约才成立)。这种 PR **停在 +draft**,PR body 写两样东西:**精确的预期红清单**(逐条列失败测试名 + 报错签名)与 +**解除条件**(「依赖 PR #N 合入」)。每个 CI-failure webhook 到达时**与该清单比对** +—— 签名匹配则静默跳过,**出现新签名才是真问题**:#5365 的 REST 层红 +(`expected 200 to be 400`)正是靠这个比对被一眼认成新问题,而不是被当成已知的等待态 +忽略过去。依赖合入后走「最后一轮同步 → 红清零 → 转 ready → 入队」。 + +这是 Operational notes 2「先认签名,再决定重投」在**故意红**上的对偶:notes 2 管的是 +flaky 的偶然红,这一条管的是自己设计出来的红 —— 两者的失效方式完全相同,**把一个没 +预料到的签名当成预料之中的**。所以清单必须写到签名级别,「几条测试会红」不够用。 + +#### 串行接力 —— 同碰生成物的多个 PR,一次只放行一个 + +第 3 步的 batch 模型假设**同批 file-disjoint 并行**;当「多个已实现的 PR 全碰 +`packages/spec` 生成物」时该假设不成立,唯一可行形态是**串行接力**:一次只放行一个, +每合并一个就向下一棒发接力指令。2026-08-04/05 夜 spec 车道以这个形态连落 10 个 PR +(#5304 → #5306 → #5308 → #5318 → #5319 → #5321 → #5314 → #5312 → #5323 → #5365), +下面三条是那一夜靠现场即兴、事后固化的纪律。 + +**1. 每一棒都是一整圈,`auto-merge` 由 PM 挂、dev 永不碰。** 单棒循环:merge main + +上面 A 的四步重建 + 全套验证 + 兄弟单断言复核 → **PM 复核回报** → 转 ready → 挂 +auto-merge / 入队。ready 与 auto-merge 的顺序不可反 —— 转回 draft 会同时掉 auto-merge +与队列成员资格(Operational notes 1),而接力循环里**每一棒都要重走这一次**,不是整条 +链只走一次。ACCEPT 那一条只写了单 PR 的流程;链上每一棒逐棒照它执行。 + +**2. 相邻两棒同碰一个文件时,交接的是语义,不是文本。** 前棒在回报里写明它对共享文件 +改动的**性质**(改名 / 提取变量 / 增补断言,**而非纯追加**),PM **原样转告**下一棒, +并要求「两个 PR 的意图**叠加**,⛔ 禁止机械取一边」。实例:#5318 与 #5319 同动 +`packages/spec/src/system/metadata-form-zod-reconciliation.test.ts`,#5319 逐行号核过 +#5318 新增的十项元素、并实测两者的交互 —— 没有 #5319 的 preprocess 修复,#5318 的新 +断言在 view 上是空转的。取一边会「各自绿、合起来错」,即 AGENTS.md §10「clean merge +不等于 working merge」落在同一份测试文件上的形态。 + +**3. 两棒散文互锁,分工由 PM 指派。** 允许前棒给后棒留占位交接(「本段由 #N 在其同步轮 +翻正 / 删除」):#5323 的族 1 段落 ↔ #5365、#5335 的「#5322 is its own ruling」pin 块 +都是这个形状。PM 必须在**两侧**的接力指令里写明谁动、谁不动 —— 否则要么两边都动 +(冲突),要么两边都不动(留下一段过期散文),而这两种结果**在 CI 上都是绿的**。 + ### 8. Escalate uncertainties to the maintainer **First, apply the escalation bar — most things that FEEL like decisions are From fc30c192298497282539deafa04e9b2ee8d23432 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 14:30:19 +0000 Subject: [PATCH 2/3] =?UTF-8?q?docs(pm-dispatch):=20=E5=81=9C=E6=91=86?= =?UTF-8?q?=E7=BA=A0=E5=81=8F=E7=AC=AC=E4=B8=89=E6=9D=A1=E7=9A=84=E4=BA=A4?= =?UTF-8?q?=E5=8F=89=E5=BC=95=E7=94=A8=E6=94=B9=E6=8C=87=E3=80=8Cstep=205?= =?UTF-8?q?=E3=80=8D=E2=80=94=E2=80=94=20=E6=8E=A5=E6=89=8B=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E5=9C=A8=E7=AC=AC=205=20=E6=AD=A5,=E4=B8=8D=E5=9C=A8?= =?UTF-8?q?=E7=AC=AC=206=20=E6=AD=A5=E4=B9=8B=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自查发现的方向错误:「Handing off an interrupted dev」小节是第 5 步的子节 (SKILL.md:839),而新增段落写在第 6 步(:923),原文写「below」会把读者指向 第 6 步之后的 Cloud mode 段。同段里对 cloud-mode ~2h 阈值的「below」是对的,保留。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE --- .claude/skills/pm-dispatch/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index a24fa992f8..7a26d961c4 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -920,7 +920,7 @@ statement that no live subtask remains. Four agents stalled 6 times across the threshold (the cloud-mode ~2 h below): a threshold is for *no* answer, not for an answer that says the agent stopped. - **A third stall means unreliable** — re-dispatch a fresh agent onto that - branch under "Handing off an interrupted dev" below (worktree already exists, + branch under "Handing off an interrupted dev" in step 5 (worktree already exists, read every existing commit first, re-run the verification in full, claim and assignee untouched). From a8cdd24a8c1b994ecf95e7ff7b676eee65588601 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 14:32:23 +0000 Subject: [PATCH 3/3] =?UTF-8?q?docs(pm-dispatch):=20=E9=A3=9E=E8=A1=8C?= =?UTF-8?q?=E4=B8=AD=E9=87=8D=E5=8F=A0=E4=B8=80=E6=AE=B5=E7=9A=84=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E6=88=B3=E6=94=B9=E7=94=A8=E5=8F=AF=E6=A0=B8=E9=AA=8C?= =?UTF-8?q?=E5=80=BC=20=E2=80=94=E2=80=94=20#5335=20=E7=9A=84=20merged=5Fa?= =?UTF-8?q?t=20=E6=98=AF=2023:49:44Z,=E4=B8=8D=E6=98=AF=2000:0xZ?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issue #5441 正文写「#5335 在 00:0xZ 合入」,核 GitHub API 的 merged_at 与 main 上 squash 提交的 committer date,两者一致给出 2026-08-04T23:49:44Z。改写成「起飞后 32 分钟合入(merged_at 2026-08-04T23:49:44Z)」,把不可核验的钟点换成可核验的 时间差 + 权威字段;起飞时刻 23:17Z 沿用 issue 的记述(无仓内产物可核,且非承重)。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE --- .claude/skills/pm-dispatch/SKILL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index 7a26d961c4..4561ad197d 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -782,8 +782,9 @@ from `origin/main` once and never refreshes itself. **In-flight overlap needs intercepting too — same-day churn only covers the dispatch instant.** The paragraph above handles "main moved before takeoff"; main lands ~18 merges a day, so it moves **after** takeoff just as often. -#5322's agent launched at 23:17Z and #5335 merged at 00:0xZ — the same two -compilers, two of the same four cells. The PM's routine check read `git log +#5322's agent launched at 23:17Z and #5335 merged 32 minutes into that flight +(`merged_at` 2026-08-04T23:49:44Z) — the same two compilers, two of the same +four cells. The PM's routine check read `git log origin/main`, spotted the overlap and sent an immediate SendMessage warning; the agent narrowed its scope twice and dropped its own design in favour of a minimal diff replayed inside the other PR's structure. Rule: **every round, when you