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
24 changes: 12 additions & 12 deletions .claude/agents/os-dev.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,13 +66,13 @@ JSON,所以终报消息就是 JSON 本身,别无其它。

## 资源纪律 —— 并行 agents 共享同一个容器

1. **重活串行 —— 共享验证锁是具名约定。** 每容器一把锁,`/tmp/os-heavy-verify.lock`,包
住每次 build/test:`flock -E 99 -w 540 /tmp/os-heavy-verify.lock -c '<command>'`。纪
律:**释放归 `flock`**(fd 持有,命令进程树退出即释放 —— 永不手搓 lockfile);只包
**命令本身**,不包你的阅读与判断;保留 **`-E 99`**,让排队超时与测试失败可区分。
`-w` 要压在一次前台调用之内(本 harness 单次调用上限 10 分钟),循环重试获取 —— 盲等
不能比承载它的调用活得久,为逃上限把它丢后台正是规则 7 要止的停摆。排队是常态,不是
挂死
1. **重活串行 —— 共享验证锁只有一个入口。** 每次 build/test 都从这里走:
`bash scripts/pm/os-verify-lock.sh -c '<command>'`(或 `-- <argv>`)。⛔ 永不手搓
`flock`/lockfile —— 手写的 `-w` 正是「守约者饿死、越界者通吃」的来源(实测:同一容器
五个等待者、三个越界 6 倍;越界者只凭「一直在场」就 12/12 赢走交接)。入口点保证:等待
预算钉死在一次前台调用之内(更长的表达不出来)· 按到达顺序授予 · 99 仍专指「没排到」·
收尾打印本次持锁时长,过长自己喊出来。只包**命令本身**,不包你的阅读与判断;结论读它
印的 VERDICT 行,不读裸 `$?`。排队是常态,不是挂死
2. **压住堆**:重命令前缀 `NODE_OPTIONS=--max-old-space-size=4096`(要抬需给理由)。
3. **定向,不扫全**:只 build/test 受影响的包(`pnpm --filter <pkg> …`),vitest
`--maxWorkers=2`,turbo `--concurrency=2`。
Expand All@@ -85,13 +85,13 @@ JSON,所以终报消息就是 JSON 本身,别无其它。
对那个 PID 操作。
6. **整条流水线在前台跑。** build 与 test 都是本任务的步骤:阻塞运行、读真实输出、继续。
⛔ 永不把验证挂在后台 watcher 上然后停轮(禁令与两种合法终态见「干净收尾」)。唯一合
法的长等待是规则 1 的 `flock` 排队——主动、在轮内(规则 7),从不是停轮的理由。
法的长等待是规则 1 的锁排队——主动、在轮内(规则 7),从不是停轮的理由。
7. **排队不是停摆 —— 在轮内主动等。** 持锁的是你不拥有的进程,它的完成不会以任何方式唤
醒你:⛔ 永不为「等锁」结束一轮(实测:这么做的每个 agent 都无通知地停摆,赔进一轮探
活)。循环:限时获取 ⇒ 退出码 99 时把间隔花在无锁工作上(写测试、changeset、PR
文、包内 `typecheck`)⇒ 再获取。**排队 ~20 分钟无进展 ⇒ 先看这次检查能否收窄到不必持锁
(收窄要申报,见「干净收尾」);收窄不了就停下报 `blocked` 并点名持锁者**:`fuser -v /tmp/os-heavy-verify.lock`(或 `lsof`)打印其 PID 与命令 —— 一动不动
的持锁者本身就是真发现。报告它;沉默是唯一错误答案。
活)。循环:拿到 99 就把间隔花在无锁工作上(写测试、changeset、PR 正文、包内
`typecheck`)⇒ 再跑一次。**排队 ~20 分钟无进展 ⇒ 先看这次检查能否收窄到不必持锁(收窄
要申报,见「干净收尾」);收窄不了就停下报 `blocked` 并点名持锁者** —— `os-verify-lock.sh
--status` 打印持锁者、已持时长与队列;一动不动的持锁者本身就是真发现。沉默是唯一错误答案。

## Toolchain traps(每条都至少让一个 agent 白跑一轮)

Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -343,6 +343,28 @@ jobs:
- name: Release-rehearsal clone preflight self-test
run: node scripts/pm/release-rehearsal-clone.mjs --self-test

# Verify-lock entry-point self-test (#9661). `scripts/pm/os-verify-lock.sh`
# is the ONE way an agent takes the container's shared heavy-verify lock,
# and it is the enforcement of a rule that used to live only in prose: the
# acquisition budget os-dev.md declares is now unrepresentable above its
# cap, grants are ordered by a ticket file, and every run reports how long
# it held. Measured before it existed, in one container: five live waiters,
# three of them 6x over the declared cap, and the compliant ones were the
# ones not verifying — a convention whose violators win is not a convention.
#
# Nothing in CI *uses* the lock (GitHub runners are one job per machine),
# so this step exists to keep the mechanism honest rather than to serialise
# anything: 29 cases over the budget clamp, ticket liveness (dead pid,
# REUSED pid, over-age), fd hygiene, exit-code passthrough, and real
# multi-process contention — queue timeout, legacy free-hand coexistence in
# both directions, and three staggered waiters acquiring in arrival order.
# It runs entirely on a private lock under a temp dir; it never touches
# /tmp/os-heavy-verify.lock, so a runner and an agent container behave the
# same. Unconditional and un-`if:`-ed, like the self-tests above — an
# exemption is what a self-test must not have. No network, ~15 s.
- name: Verify-lock entry-point self-test
run: bash scripts/pm/os-verify-lock.sh --self-test

# Docs/skills authoring guard (#2035 / ADR-0059): TS code blocks in
# Markdown/MDX are not type-checked or ESLinted, so skills/ and
# content/docs/ can drift back to teaching the bare `: Page = {}` literal
Expand Down
Loading
Loading