Part V · Engineering & Governance

19 spec-driven 开发 把设计文档变成任务队列,用 git 当分布式锁

部分落地 工作包 41 个 / 9 个 Track 协议实现 纯人工约定,无代码
§1 Mental Model 一句话心智模型

先记住这一句

pi 把一份 3437 行的设计文档同时当成三样东西:规格说明、任务看板、和并发控制机制。认领一个工作包 = 往文档里加一行「Reserved by @我」并单独提交;谁的提交先进 main 谁拿到。

这一章的状态徽章是部分落地,但含义与 第 08 章 不同:那里是「设计已写、代码未实现」;这里是协议本身完全没有代码——没有 bot、没有 workflow、没有校验器。它靠的是人和 agent 读文档、照做。

值得研究的原因也在这里:它是一套零实现成本的并发协议,而且真的在跑(git log 里有 8 次 reserve/unreserve 提交,文档里有 3 处活跃占用)。

§2 Evidence 代码实证

协议全文

packages/agent/docs/harness-v2.md §20改写自 L3172–L3182(编号保留,散文压缩)
1. 与 main 同步。一个包可认领,当且仅当:复选框为空、依赖全部已勾选、
   且没有活跃 reservation 占用它或与之重叠的主文件。
2. 在包条目正上方加一行 `**Reserved: <id> by @<user>.**`,
   单独提交,消息为 `docs(agent): reserve <id>`。
   该提交进入 main 之后,认领才成立;若有冲突的 reservation 先落地,撤掉自己的重选。
3. 从 reservation 提交开始工作。读引用的设计与主文件。
4. 循环:实现 → 为每条自己拥有的不变量写可执行断言 → 迭代到全绿。
   设计不成立时停下,去 Discord 找 Mario 谈;达成一致后改设计再回到第 1 步。
5. 跑 `npm run check`。实现的 PR 顺手删掉自己的 reservation 并勾上复选框。
   放弃则只删 reservation,不勾。
这是改写:原文是英文散文,此处压缩并保留步骤编号。逐字原文见 harness-v2.md:3174

四个设计细节

① 冲突解决用 git 的线性历史。「该提交进入 main 之后,认领才成立;若有冲突的 reservation 先落地,撤掉自己的重选。」——不需要任何服务、任何状态机。main 分支的顺序就是锁的仲裁顺序。

② 认领必须单独提交。「Land that change alone」。这样 reservation 与实现分离,认领可以在几分钟内进 main,而不必等实现写完——锁的持有窗口才不会因为 review 时间被拉长。

③ 依赖是显式的、可机读的。每个包写「Dependencies: none」或列出前置包,可认领条件包含「依赖全部已勾选」。41 个包因此构成一张 DAG,而不是一个待办列表。

④ 验收标准要求可执行断言,明确排斥冒烟测试。「Smoke tests and happy-path coverage alone are insufficient; each owned invariant must have an executable assertion.」harness-v2.md:3178

还有一张「谁拥有哪个公开方法」的表

packages/agent/docs/harness-v2.md原文 L3193–L3197(表格节录前两行)
This table is exhaustive. A package does not remove `HarnessNotImplemented` from a
method until it owns the listed semantics and tests.

| public surface | owning package |
|---|---|
| scaffold-safe `name`, `getLeafId`, record-free create, runtime settings | F0 |
| `AgentHarness.create()` restore and `suspended` inventory | R3 |
这张表把 第 08 章那 22 个抛错的方法逐个指派给了工作包。「不拥有就不许摘掉 HarnessNotImplemented」是全套协议里最硬的一条。harness-v2.md:3193

协议在 git 历史里的痕迹

git log --oneline --all | grep -E 'reserve'命令输出,非源码
4bf1bba20 docs(agent): reserve I2
102667952 docs(agent): unreserve R3
d406aa394 docs: reserve L1
9859eaa26 docs: reserve QA2
8ed232548 docs: reserve QA1
69bab864d docs(agent): reserve harness v2 R3
abac29f67 docs(agent): reserve harness R2 work package
290136253 docs(agent): reserve harness I0 and L1-L3 work packages
ea7bfdc08 docs(agent): unreserve R2 work package
afdf73661 docs(agent): reserve harness R1 work package
f909da2bf docs(agent): reserve harness R0 work package
注意 unreserve R2 与 unreserve R3——放弃是协议的正常路径,不是异常。第 5 步显式写了「放弃则只删 reservation,不勾复选框」。

快照时文档里有 3 处活跃占用,两处是标准格式,一处是历史特例——J 轨的所有权标记附了一段散文,解释这项工作在计划被拆成 J0–J6 之前就开始了。harness-v2.md:3260

范围也写死了

「工作限于 packages/agent、packages/session-backends/sqlite-node、packages/telemetry,以及 packages/ai 中的 telemetry 请求选项面。其它包的源码禁止触碰。」harness-v2.md:3169

在一个允许多个 agent 并行开工的仓库里,包级的文件所有权边界是防止互相踩踏的第一道防线——比 第 17 章 的 git 纪律更靠前。

§3 Design 设计动机与权衡

为什么不用 GitHub Issues

Issues 天然就是任务队列,还自带认领(assignee)。pi 没用,原因推得出来但未见公开论述:

代价是把一个高频变更的看板塞进了一个应该稳定的规格文件——下面这条。

一份文件承担两种寿命

「系统应该长什么样」应该稳定;「谁在做哪一块」每天都在变。合在一起的直接后果是:一份 3437 行的文件成为高冲突热点,而且它的 git 历史里混杂着两类完全不同的变更——设计修订和进度更新。

「reservation 单独提交」缓解了冲突(改动小、进 main 快),但没有消除根因。J 轨那段解释历史的散文就是症状:当看板结构本身演化时(计划被拆成 J0–J6),你需要在规格文件里写一段散文解释历史。

「协议无实现」是优点还是缺陷

优点是显然的:零构建成本、零维护成本、任何能读 markdown 和用 git 的参与者(人或 agent)都能立刻加入。

缺点同样显然:没有任何东西阻止两个人同时勾同一个框,也没有任何东西检测「reservation 挂了三个月没人动」。整个机制的执行力等同于参与者的自觉——与 第 17 章 的 AGENTS.md 是同一种赌注。

在当前规模(3 处活跃占用、少数几个 track 同时开工)这个赌注赢面很大。它的失效条件不是「有人作弊」,而是「参与人数上升到没人能记住谁在做什么」。

这套东西真正新颖的地方

把设计文档当任务队列不新鲜(Kubernetes 的 KEP、Rust 的 RFC 都类似)。pi 这套的特别之处是它是为「参与者可能是 agent」设计的:

最后一条最关键:协议把「什么时候必须找人」写进了流程,而不是指望 agent 自己意识到该求助。

§4 Limits 边界与失效条件

锚点之后本章描述的是锚点 ac4ac9e 上的 harness-v2.md。上游先由 85a2060(2026-08-11)把它并入 harness.md、重新编号工作包,再由 7fd478a(2026-10-01)把 harness.md 连同整套 harness 删除。认领协议随这份规范一起终止了:从锚点到 v1.2.0,提交历史里再没有 docs(…): reserve / unreserve 形式的提交(锚点之前有 8 次,见下文)。本章的「部分落地」徽章按锚点判定,未改。见 第 08 章追记。

检查过并确认:8 次 reserve/unreserve 提交中有 2 次是 unreserve,说明放弃路径被真实使用过;且 unreserve R2(ea7bfdc08)与后来的 reserve harness v2 R3(69bab864d)分属不同工作包,不构成同一包的反复占用。

§5 Takeaway 拿走什么
§ Self-check 自测

场景你想认领工作包 H0,但它依赖的 R3 复选框还是空的。协议允许你开工吗?

看答案

C。第 1 步:「a package is claimable only when its checkbox is empty, every dependency is checked, and no active reservation owns the package or overlapping primary files」——三个条件全要满足。harness-v2.md:3173

场景你做到一半发现设计有问题。协议要求你怎么办?

看答案

B。原文:「If the design does not hold, stop and consult Mario on Discord. After agreement, update the design and package description, then return to step 1.」注意「return to step 1」——设计改了要重新走认领流程。harness-v2.md:3180

基于 earendil-works/pi @ ac4ac9e(2026-08-07 快照)。
仓库 commit 速度约 428/月,行号引用仅对该 SHA 有效。