Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions .claude/agents/os-dev.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,7 +126,12 @@ the farm exactly once either way. Your local pass: ① build closure first
packages' own `pnpm test` / `pnpm typecheck`, scoped by `--filter`; ③ the gate families the
dispatch prompt names, plus any you can see are implicated (a new fake engine ⇒
`check:engine-double-contract`; a new error code ⇒ `check:error-code-casing`;
`.claude/agents/**` ⇒ `check:agent-model-declared`; any edit ⇒ `check:nul-bytes`). The
`.claude/agents/**` ⇒ `check:agent-model-declared`; any edit ⇒ `check:nul-bytes`); ④ the
prompt's gate list is a **lead, not a spec** — a same-day, carefully taken list still
misses families. After the named families pass, re-derive once against your **actual**
changed paths (`node scripts/pm/dispatch-gates.mjs <changed paths>`), run any family it
surfaces that the prompt missed and your diff really touches, and name the addition in
your report — one O(1) derivation, and naming accuracy stops resting on the PM alone. The
accepted cost is an occasional extra push-fix lap; the safety half lives with the PM, who
reads the real gate-job conclusions after your report. ⛔ Not licence to skip the named
families — they are the cheap half you still owe; what you no longer owe is waiting for CI
Expand DownExpand Up@@ -164,14 +169,24 @@ silence is the expected shape, never permission:
then regenerate (mechanized: `bash scripts/pm/os-regen-merge.sh`). Sister trap:
`gen:schema`'s cleanup wipes `gen:openapi`'s output (bogus 5xx failures in rest); restore
with `pnpm --filter @objectstack/spec gen:openapi`.
- **⛔ Take a fix out with `git checkout`, a patch file or a temp commit — NEVER
`git stash`.** The worktree isolates files and HEAD, not `refs/stash`, which is one LIFO
stack shared by every worktree: two agents stashing swap entries, `pop` reports success
while restoring the *other's* changes, and a following `git add -A` commits their
half-finished work into your PR. A hook blocks the mutating forms; alternatives, all
inside your own worktree: `git checkout origin/main -- <path>`;
`git diff > /tmp/wip.patch && git checkout -- <paths>`; `git commit -am wip` then
`git reset --soft HEAD~1`.
- **⛔ Take a fix out with a temp commit or a patch file — NEVER `git stash`.** The
worktree isolates files and HEAD, not `refs/stash`, which is one LIFO stack shared by
every worktree: two agents stashing swap entries, `pop` reports success while restoring
the *other's* changes, and a following `git add -A` commits their half-finished work
into your PR. A hook blocks the mutating forms; safe alternatives, all inside your own
worktree: `git commit -am wip` then `git reset --soft HEAD~1`;
`git diff > /tmp/wip.patch && git checkout -- <paths>` then `git apply /tmp/wip.patch`.
- **Doing reverse verification ("revert the fix, watch the diagnostics")? Commit the fix
FIRST.** Committed, restoring is `git checkout <your-branch> -- <path>` — the file comes
back out of a commit that really exists. Against an **uncommitted** edit,
`git checkout origin/main -- <path>` leaves no restore point at all: the working tree is
the only copy, the stash is banned above, and discarding local modifications is a
normal, silent, exit-0 operation — the change is simply gone, and every recovery so far
depended on the change still being in the session transcript. If you ever retype a lost
change, prove identity with `git diff` against a saved patch or `git hash-object <path>`
— a matching `--stat` insertion count is **not** byte-identity — then re-run the reverse
verification from the committed state, so the red/green numbers you report are
trustworthy.
- **Rejection-class cases assert the envelope, not the throw.** Minimum assertion set: the
error's **`code` AND `status`** (the ADR-0112 envelope). `expect(...).toThrow()` alone is
not a rejection test — measured both ways it goes blind: an unfixed driver throwing a bare
Expand DownExpand Up@@ -213,7 +228,14 @@ silence is the expected shape, never permission:
not close the card** (you implemented only the actionable half; the other half sits in the
decision box or was excluded by scope; say which half you left). ⛔ Never `Fixes` a card
still in the decision box — merging silently closes it and the inbox filter only reads
open issues. Title and prose in **English** (GitHub artifacts are English per the
open issues. ⛔ **A negated closing sentence still closes the card it names**: GitHub's
closing-keyword parser matches `fix/fixes/fixed/close/closes/closed` and
`resolve/resolves/resolved` + `#<n>` and ignores any negation in front — a body sentence written to
declare a card out of scope is exactly what closes it on merge. Keep closing keywords
away from other cards' numbers; write `#<n> is not addressed here`,
`out of scope: #<n>`, or `#<n> remains open`. The PR body and the commit message are
parsed as **separate** sources — a clean commit message proves nothing about the body.
Title and prose in **English** (GitHub artifacts are English per the
maintainer ruling of 2026-08-08 in AGENTS.md; a quoted Chinese ruling stays verbatim and
untranslated — rewriting a quoted ruling is rewriting the ruling). Close the body with the
**session-URL** attribution footer (see "Byte and sanitizer discipline").
Expand Down
18 changes: 13 additions & 5 deletions .claude/hooks/guard-shared-stash.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,10 +29,16 @@
# mirroring discipline guard-main-checkout-bash.sh already documents.
#
# Alternatives — no shared state, all of these work inside your own worktree:
# 1. clean re-read git checkout origin/main -- <path>(restore: git checkout <branch> -- <path>)
# 1. temporary commit git commit -am wip (git reset --soft HEAD~1)
# 2. patch file git diff > /tmp/wip.patch && git checkout -- <paths>
# git apply /tmp/wip.patch (git apply -R to undo again)
# 3. temporary commit git commit -am wip (git reset --soft HEAD~1)
# 3. reverse verification: COMMIT THE FIX FIRST, then take it out with
# git checkout origin/main -- <path>
# git checkout <your-branch> -- <path> (restore from the commit)
# Against an UNCOMMITTED edit that same checkout silently DISCARDS the only copy at
# exit 0 — the working tree is the only place the change exists, and this hook has
# already ruled out the stash. Commit first; the restore then pulls the file back
# out of a commit that really exists (AGENTS.md carries the same rule).
# 4. a second worktree for the comparison checkout
#
# Allowed through, deliberately:
Expand DownExpand Up@@ -167,11 +173,13 @@ show up in your git status, which is why objectui#3430 swapped two agents' in-fl
changes without an error.

Use instead — no shared state, all inside your own worktree:
1. clean re-read git checkout origin/main -- <path>
git checkout <your-branch> -- <path> # put your version back
1. temporary commit git commit -am wip # git reset --soft HEAD~1
2. patch file git diff > /tmp/wip.patch && git checkout -- <paths>
git apply /tmp/wip.patch # git apply -R to undo again
3. temporary commit git commit -am wip # git reset --soft HEAD~1
3. reverse verification: COMMIT THE FIX FIRST, then
git checkout origin/main -- <path>
git checkout <your-branch> -- <path> # restore from the commit
(against an UNCOMMITTED edit that checkout silently discards the only copy, exit 0)
4. a second worktree for the comparison checkout

Already allowed, no flag needed:
Expand Down
26 changes: 13 additions & 13 deletions .claude/skills/pm-dispatch/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -90,8 +90,7 @@ UI 创建并勾 GitHub 连接器、UI 钉模型(会话内 create_trigger 的Rout
- `needs-user-decision` vs `pm:on-hold` = **决定待做 vs 已做**,挂错招来重复升级或永久沉默
;hold 没有重启条件 = 谁都无法合法退出的状态。**机会主义重启条件必须点名触发文件**(维护
者 2026-08-11 接受)——「下个碰这些文件的 PR 顺手带上」在命中那一刻没有读者;处方:hold 评论写
触发文件清单,车道座位贴设「派发前必查」段,派发卡文件面与清单相交时点名该单、顺手活列为申报
过的增项。
触发文件清单,车道座位贴设「派发前必查」段,派发卡文件面与清单相交时点名该单、顺手活列为申报过的增项。
- **`Blocked-by:` 行是机器可 grep 的反向索引**,一遍读喂三个职责:上游关单时放回被解锁的、按解
锁扇出排序选择、**在合并后的 ref 上重验每张回队卡的文件面**(⛔ 只做第一件)。**一个标签存在
,当且仅当有具名读者**,无读者的标签必烂;已议已拒(维护者2026-08-11):「被依赖」是随上游关单衰
Expand DownExpand Up@@ -136,8 +135,7 @@ UI 创建并勾 GitHub 连接器、UI 钉模型(会话内 create_trigger 的Rout
行、被打断行为的 pin 反转为拒绝 pin(不是删除)、⛔ 同 PR 不做任何 promote/回退、冲突立
成 `needs-user-decision` 卡、该 PR 不挂 auto-merge 留异议窗口。
- **spec 改动的 fixture triage 必须跑消费包测试**(A 包的改动可让 B 包的 fixture 反着断言
,spec 范围内任何 sweep 都看不见)—— 动契约面的派发令点名消费包测试清单,报告要有各消费包真实
读数。
,spec 范围内任何 sweep 都看不见)—— 动契约面的派发令点名消费包测试清单,报告要有各消费包真实读数。

## 多仓协调(五条规则)

Expand DownExpand Up@@ -323,8 +321,10 @@ UI 创建并勾 GitHub 连接器、UI 钉模型(会话内 create_trigger 的Rout
最小字段一次拿回全车道 open 集,各状态本地求交 —— 按单一 pm 状态切片的查询看不见其它状态,是结
构性盲区。候选 = open、未 assign、无`needs-user-decision`;已排队父单的 open sub-issue 自动是
候选(`pm:epic` 父单的子树除外)。**每张候选读全文 + 全部评论**(裁决落在评论区,跳过评论就是跳
过裁决;评论还可能记着一半工作已交付);**派发前做 stale-premise check**(issue 描述的是立单当天
的仓 —— `git log --oneline -20 -- <paths>` 花几分钟,不查则赔一次 agent-run)。
过裁决;评论还可能记着一半工作已交付);**派发前做 stale-premise check —— 裁决同罪**(issue 与
裁决描述的都是当时的仓:裁决是针对某个仓库状态的判断,写得权威、日期又近,恰恰更容易被当成现成
事实 —— `git log --oneline -20 -- <paths>` 之外,裁决实施卡再核被点名的动作在 `origin/main` 上
还没被做掉;花几分钟,不查则赔一次 agent 运行)。

**批次独立性。** 一批内任两单不得可能碰同一个包/registry/barrel/spec schema;拿不准就串行。**同
文件单跨轮硬串行;延后不是搁置**(被延后那一刻就把已知的坑记到该 issue 上)。**维护者明示豁免同
Expand DownExpand Up@@ -404,7 +404,8 @@ dev 侧推分支要早 —— 远程分支是在飞工作最硬的证据。**Sta
- **标准非协商条款 ⛔ 不抄进派发词** —— 已下沉进 `.claude/agents/os-dev.md`;无条件条款只能住
在角色文件(派发词与它冲突时它胜),错了就修那里,⛔ 不靠派发词临时覆盖。**清单、路径、行号在
派发那一刻从树上取**,⛔ 不从卡片/上次派发/记忆抄(取数 `node scripts/pm/dispatch-gates.mjs`
;取数的是 PM 不是 dev —— dev 只跑被点名的门禁族,全 farm 归 CI);行级断言转述前必须自己重验。
;取数的是 PM 不是 dev —— dev 只跑被点名的族,全 farm 归 CI;但点名单是线索不是规格,dev 用同
一脚本对实际改动重取一次、补跑漏点名的族并报告点名,条款在 os-dev 定义);行级断言转述前必须自己重验。
- **文件面写两句**(预期落点 + 生产者在别包时报备后按生产者侧修,⛔ 不在消费者侧打补丁
);**same-day churn 行**(当天合并 ⇒ 先核对当前 main);**在飞重叠拦截**(每轮求交,相交即发四句
警告,被完全覆盖就停下回报 ⛔ 不硬造 diff)。
Expand DownExpand Up@@ -470,14 +471,14 @@ You are the reviewer of record —— **对 GitHub 核验,不对报告的自述

- **PR 形态与范围**:draft、目标 `main`、首行 **`Fixes #<n>` 仅当合并应当关卡** ——半实施必
须 `Part of #<n>`,否则合并静默关掉决策箱里的卡(收件箱过滤只看 open);翻 ready 前亲核首行。
changed files 范围检查(⛔ 不看报告自述);tests/docs-only 走`skip-changeset` 标签;测试证据要
真实命令与输出。
**`Fixes` 卡随关单自动离开在飞视图,`Part of` 卡合并后仍开着**:MERGED 的同一动作里摘
`pm:dispatched` 换回 `pm:queue`(或按剩余物定级)+ 评论写明已交付/还剩/归谁。changed files
范围检查(⛔ 不看报告自述);tests/docs-only 走`skip-changeset` 标签;测试证据要真实命令与输出。
- **报告在草稿 PR 时点到达,CI 收敛读数只属于复核侧**(维护者 2026-08-10 裁定): gate
`in_progress` 是诚实读数;翻 ready / 挂 auto-merge / 入队前亲核门禁 job 结论,⛔ 不因「本地
绿」跳过;收敛期转红走补丁轮(续派原 dev,不是 REWORK);重量级卡可在派发令写「本单等 CI」。
- **绿色输出 ≠ 该绿证明了被测风险**:拒收用例查 `code`+`status` 断言;「N 个包全绿」问方向与时
序;裁决实施 PR 查全仓 pin 翻转 + 拒收断言仍在;收益穿过必经边界后还在吗(必要时端到端验一次)
序;裁决实施 PR 查全仓 pin 翻转 + 拒收断言仍在;收益穿过必经边界后还在吗(必要时端到端验一次)。
- **证伪是好运行**:`premise_still_valid: false` 是再分诊输入不是失败;dev 纠正 PM要当众认;验
收判据被测量推翻 = 好运行,但过程要写在 PR 正文并附实测信噪比。
- **删除与二进制**:死代码删除在 `origin/main` 亲核引用面再 ACCEPT;`+0/-0` 先疑NUL。sweep 类
Expand DownExpand Up@@ -528,8 +529,7 @@ blockers**(三仓 `target:<major>`之和,归零 = 三张板都空)。
续,⛔ 不跑到被自动压缩。四步有序:① 收工点 = 在飞归零(⛔ 不在复核中途压缩);② 收工存档 = 把只
存在于上下文里的判断 flush 到 GitHub(「不过夜」的既有义务);③ `/compact` 由维护者执行;④ 压缩
后再派下一波第一张,⛔ 不抢派。同席压缩保全会话绑定(订阅、定时器、座位贴会话 ID),换人轮换降为
班末动作;Routine 座位不适用。前提是 GitHub 恒为唯一权威 —— 它失守时压缩从丢缓存退化为丢判断
班末动作;Routine 座位不适用。前提是 GitHub 恒为唯一权威 —— 它失守时压缩从丢缓存退化为丢判断。

### 停止条件与待命

Expand Down
14 changes: 12 additions & 2 deletions .claude/skills/pm-dispatch/references/dispatch-runbook.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -101,8 +101,12 @@ SendMessage 附执行姿态句(「前台(阻塞)同步执行全部步骤,中途
- **清单、路径、行号在派发那一刻从树上取**,⛔ 不从卡片/上次派发/记忆抄(门禁清单单
班之内就会过期)。取数:`node scripts/pm/dispatch-gates.mjs <改动路径>…` 产出本卡
该跑的门禁族,填进派发令;取数的是 PM 不是 dev —— dev 只跑被点名的几族,全 farm
归 CI 跑一次,点名的准确性从此是 PM 独担的。卡片或分诊评论里的行级断言,转述进派
发令前必须自己重验一遍。
归 CI 跑一次(⛔ 不让 dev 枚举全 farm,这半边不变)。**但点名单是线索不是规格**:
当天现取、认真读过的清单照样会不完整,实测漏点名的门恰好被改动真实打红过。⇒ dev
在跑完被点名的族之后,用同一条取数命令对**实际改动路径**重取一次,发现清单漏掉、
而自己改动确实触及的族,补跑并在报告里点名(dev 侧条款住在 os-dev 定义)—— 一次
O(1) 取数,换掉「点名准确性由 PM 独担」这个单点。卡片或分诊评论里的行级断言,转
述进派发令前必须自己重验一遍。
- **文件面写两句**:「预期落点是 X;若实测表明真正的生产者在别包,报备后按生产者侧
修(落点与理由写进报告和 PR 正文),⛔ 不在消费者侧打补丁」—— 只写一个路径名的派
发令,是要求 dev 在守约与修对之间二选一。跨包常常等于跨车道,PM 侧的对价是事后补
Expand All@@ -129,6 +133,12 @@ SendMessage 附执行姿态句(「前台(阻塞)同步执行全部步骤,中途
重验后逐面抄进派发令**(⛔ 不凭记忆),并带:「每一面都必须在 PR 正文有一个结
论:已改 / 本就合规(证据)/ 明确不在范围(理由);⛔ 静默略过 —— 评审把没提到
的面读作漏掉的面」。防的不是做错,是做对了一部分然后以为做完了。
- 改动触及**已发布包**(`package.json` 的 `private` 不为 true)⇒ 「用户可见行为
改动**必须**带 `.changeset/*.md`;判据是包的发布状态,不是改动大小 —— 缺了则合
进 main 却永不发布,看起来像修好了,比不合更糟」(ACCEPT 侧对账在复核清单)。
- **测量先行卡**(卡面是一个问题,修复由测量结果**有条件授权**)⇒ 派发令写明「⛔ 测
量存在之前不写修复」—— 测量姿态让 dev 看得见卡面没列的探针,实现姿态只看得见要交
的 diff。
- **Premise-first 写明**:issue 正文是线索不是规格,先对 origin/main 验前提;
`premise_still_valid: false` + 无 PR 是合法且常常有价值的交付 —— 派发词预设
issue 为真,就把好运行变成表面抗命。
7 changes: 7 additions & 0 deletions .claude/skills/pm-dispatch/references/landing-operations.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,6 +38,13 @@
确认 **MERGED** —— 每轮同时读队列分支与 `origin/main` 两个读数。ready 与
auto-merge 的顺序不可反(转回 draft 会同时掉 auto-merge 与队列成员资格)。

**确认 MERGED 的同一动作里给 `Part of` 卡收口**:`Fixes` 卡 GitHub 代关、标签随卡
离开在飞视图;`Part of` 卡仍开着,`pm:dispatched` 不摘就把一张无 dev、无分支、无在
飞物的卡永远算在 `label:pm:dispatched is:open` 里。摘标(换回 `pm:queue` 或按剩余
物定级)+ 一条评论(已交付什么、还剩什么、剩下的归谁)与 MERGED 确认是一个动作,
⛔ 不拆成「下轮巡检再摘」。同刻顺手读一次相关卡的 `closed_by_pull_requests`,确认没
有别的卡被正文里的闭合关键词误关(事实表见平台读数)。

**落地窗口给关键 PR 挂 `subscribe_pr_activity`**(会话型座位专用;Routine 座位每
fire 新会话收不到,维持轮询):

Expand Down
14 changes: 14 additions & 0 deletions .claude/skills/pm-dispatch/references/platform-readings.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,6 +83,20 @@
接。dev 的临时目录(`.os-scratch/` 一类)是工作物不是交付物,清掉,⛔ 不进
feature PR。

## 闭合关键词解析(PR 正文写侧)

- **PR 正文里「不修某卡」的否定句会关掉那张卡**:GitHub 的闭合关键词解析器匹配
`fix/fixes/fixed/close/closes/closed/resolve/resolves/resolved` + `#N`,**不理会前
面的否定词** ——「nothing here fixes #N」在合并时照关 #N,而写这句话的动机恰恰是声
明不修;好实践(读了兄弟卡、显式划界)反而制造了失效。安全写法:把号码放在没有关
键词打头的位置 —— `#N is not addressed here` / `out of scope: #N` /
`#N remains open`。
- **PR body 与 squash commit message 是两个独立解析源**:commit message 只有
`Fixes` 首行、看起来干净,不代表 body 干净 —— 只查 commit 会漏。误关的卡以
`completed` 状态对一切「只看 open」的过滤与巡检隐身,没有任何机械守卫覆盖这条路
径(duplicate-fix-guard 比的是两个 PR 抢同一张卡,这是一个 PR 多关了一张没人抢的
卡);消费侧检查 = 合并后读 `closed_by_pull_requests`(在复核清单)。

## 断粮检测与跨墙恢复细则(5 小时用量墙)

- **检测**:`npx ccusage blocks` 在容器内可用(读本地会话记录),两个有用读数 ——
Expand Down
Loading
Loading