About pipeline
pipeline is a coding-process skill for developers and agent software that can dispatch subagents. It lowers decision overhead through separated implementation and review, isolated Git worktrees, and explicit acceptance criteria; its per-task overhead makes it unsuitable for small changes.
Key features
- 01Defines lead, executor, and reviewer roles
- 02Requires independent-context review and verification
- 03Isolates tasks with Git worktrees
- 04Structures task, progress, and review records
- 05Specifies recovery, merge, and cleanup procedures
Use cases
- 01Break down and deliver complex coding tasks
- 02Have an independent reviewer verify code and tests
- 03Manage implementation scope with acceptance criteria
- 04Resume tasks after interruption or leftover state
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ alexcdever/ pipeline --skill pipelineAfter installing, type/pipelinein Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
.gitattributesLICENSE.gitignorePROTOCOL.mdGLOSSARY.mdREADME.mdFLAKY.mdMIGRATION.mdENVIRONMENT.mdTESTING.mdRECOVERY.mdDetails
From the project README编程流程
一份纯流程契约,给两种人用:人类开发者(提要求、做裁决)和具备子代理能力的 agent 软件(照着它干活)。目的是让 agent 软件开发时更有效率——不是让它留更多的记录。更根本的目标是降低人类开发者的决策成本:能自己判断的自己判断,能自己收拾的自己收拾,人只在真正需要裁决的地方出现。
这套流程按任务收费,和改动大小无关:一个任务至少要预走一遍验收标准、派执行者与审查者各一个独立子代理、建一个 worktree、留四个文件、跑一次合并后复验,外加三次全量测试(执行者、审查者、主树各一次;push 那次通常能复用主树的)。小到 agent 几分钟能写完、自己一眼能验完的活就不适用它——成本大于收益;项目的全量测试要跑上十几分钟、又没有更快的分层办法时,同样要重新掂量这个成本。
它规定主代理、执行子代理、审查子代理怎么协作,什么算完成,什么不算。它不规定用什么模型、什么子代理机制、什么编程语言,也不附带任何脚本;Git 是唯一被指定的工具。
硬前提
宿主必须能派发具备独立上下文的子代理,并且能读写项目文件、运行命令(含项目的验收测试)、使用 Git worktree。
独立上下文的判据(它约束的是审查者):审查者能看到的东西,全部来自派发时传给它的材料、和它被指到的那棵 worktree;它读不到主代理与执行者的对话历史。审查者还必须能跑命令、能写 .pipeline/reviewer.md——只能读、不能动的审查者等于没有审查。
做不到上面这些,本技能不适用——它没有降级模式:同一个上下文既实现又审查是不允许的。没有独立审查,这套流程剩下的部分全部失去意义。
角色
- 主代理:面向用户,决定做什么、以及什么算完成。规划、写任务描述、派发子代理、核对结果、合并。自己不改产品代码(合并、跑验收命令、撤销整次合并的
git revert -m 1、git merge --abort退出半截合并、审查者留下坏现场时按「还原三件套」复位 worktree、补提交.gitignore、补写公共 gitdir 的info/exclude条目都不算改代码)。它负责把流程推到底:停不停、报不报完成,按第 11 条办。 - 执行子代理:在隔离 worktree 中实现一个任务,遵循「测试驱动」一节。不动任务描述里的验收标准;标准有问题就报回来,不自行改。
- 审查子代理:在独立上下文中亲手复验,不采信任何转述,只看代码、测试,和自己跑出来的结果——包括自己跑的那一次全量测试。它不提交任何东西,也不替执行者改代码——复验中为了动手验证而临时动过的东西留在工作区不进提交,还原按「临时文件」一节办。
(摸底和会刷屏的活都可以派一次性子代理——见「噪声」。它不是这里列的角色:不建 worktree、不写
.pipeline/、不动产品代码,回到主代理的只有一份结论。)
噪声
主代理的上下文用来做决定,不用来通读原始输出。
动手之前先预判:这一步会刷出多少原始输出。按输出性质选处理方式:
- 短输出或结果即结论:直接保留完整输出。文件读取、小范围搜索、
git status、git diff、版本号、路径和产物检查都属于这一类;输出本身是要判断的结果,不能因为它可能变长就随意隐藏——确实变长的,仍按长输出命令存日志、摘要进来、按需回查,「不隐藏」指要看到能下判断的内容,不是全文必须进上下文。 - 长输出命令:完整的标准输出和错误输出(stdout/stderr)必须保存到系统临时目录;进入当前上下文的是执行摘要和有限预览,不是完整原始输出。摘要至少包含退出码、是否超时、耗时、输出规模、临时日志路径,以及输出的头部、尾部和可机械识别的错误/失败/警告片段。摘要由执行工具或确定性脚本生成,不要求调用 LLM;宿主不支持自动摘要时,由 agent 主动写临时日志并按需读取。
- 探索性噪声:全仓搜索、翻目录、追一条跨很多文件的调用链,以及不需要主代理亲自持有原始输出的摸底,可以派一次性子代理。它只回传能直接决定下一步的结论,不回传过程或原始输出。
完整日志是可回查的事实,不是摘要的替代品。摘要不足以判断时,读取日志中的相关范围;失败时优先查错误输出(stderr)、错误/失败片段及其上下文,仍不能判断再扩大范围或重新执行更窄、机器可读的命令。
- 噪声止于一次性子代理或摘要。主代理不通读与当前决定无关的原始输出;但执行者、审查者和主代理亲自执行的关键验收命令,必须保留完整长输出到可回查的临时日志——宿主存不下时按第 5 条(
degraded与missingFields)办。 - 答案的形状在派发时就定死。摸底确定的那四件事(见第 1 步「读项目约定」)就是这个形状;答不上来的,直接说答不上来。
- 决定权不跟着派出去。一次性子代理没有用户通道,遇到需要产品决策的,把情况如实回报给主代理;由主代理按第 9 条问用户。
- 它的结论是规划材料,不是证据。凡要当判据的事实,由主代理亲手走一遍或由审查者复验;必须主代理亲手做的动作(走验收、合并、主树复验)要保留命令、退出码、执行摘要和必要的后置验证,不要求把完整输出全部放进主代理上下文。
执行层协议
宿主侧的命令执行设施向 agent 返回什么,见 PROTOCOL.md。契约只要求:长输出命令的完整标准输出和错误输出(stdout/stderr)必须可回查、退出码不能代替验收——怎么包装、字段叫什么,是执行层的事。
临时文件
临时文件放在系统临时目录里(%TEMP%、/tmp 这类),不放进项目。
- 谁写的都算。 临时文件是那种用完就该丢、本来不该留在仓库里的东西:为跑一次命令写的脚本、下载下来的数据或工具、跑测试时生成的输入、中间产物——主代理、执行者、审查者、一次性子代理,谁写的都一样。
- 放进项目就不算临时。 项目里不建
.tmp/这类目录——执行者的git add -A会把它捡进提交,主树里也会多出与任务无关的文件。 - 要留在仓库里的不在此列。 交付物、测试代码,和测试要用的夹具与样例数据,照旧进项目;
.pipeline/里那四个文件也一样,它们就该待在 worktree 里(见「.pipeline/」一节)。每个进不进提交的新文件都先在任务描述(task.md上段)里写明:进提交、执行者把它提进分支,会被第 5 步「改动是否只落在任务描述的范围里」的核对看见;不进提交,把它的路径记进task.md上段,并写进公共 gitdir 的info/exclude——即git rev-parse --git-common-dir所指的主仓.git/info/exclude(公共条目对所有 worktree 与主树同时生效;私有 gitdir 的info/excludeGit 不读,见ENVIRONMENT.md)。条目必须锚定到具体路径、且带<id>(/<id>-…或完整相对路径),裸目录与裸通配都在禁止之列——info/exclude的裸通配会让合法文件被静默跳过(见ENVIRONMENT.md)。记进task.md是清场回收这些条目的唯一依据;条目生效期间对主树同名路径同样屏蔽。归属裁决不了时,按「停下来问用户」一节处理。 - 这条管新写的文件。 已经在项目里的文件被临时改过,那是改动,不是临时文件——审查者留下的临时改动照旧留在工作区,不进提交:执行者被再次派发时(退回重做、重写描述后重派、解冲突),先把 worktree 恢复到检出在
<id>分支尖端、没有进行到一半的 git 操作(merge、rebase、cherry-pick 都算,git status会报出来)、工作区干净(tracked 改动还原,未跟踪临时文件删掉;.pipeline/与登记进info/exclude的文件被忽略、不受影响)——这三项下称「还原三件套」。审查者逐个检出测试提交后没回到尖端的、解冲突解到一半的、接基底接了一半的,都在这一步发现并复位;不然下一次git add -A会把残留扫进提交,或者提交根本落不到<id>分支上。只有同一份描述下接着中断的活干(执行者被打断、超时,或恢复后续干)不还原——那是执行者自己的在制品,按progress.md与工作区现状接着干,就留着;描述已经换过的,旧的在制品同样作废,一并还原。
用户行为
用户行为:术语见同目录 GLOSSARY.md。
- 入口可以是按钮、命令、接口、配置文件;状态变化可以是界面、数据库、文件、日志。
- 「改了三层代码」不是用户行为,「用户点了 X,Y 变了」才是。
- 重构、基建、文档:找出它的可操作入口和可观察状态变化,套进同一个形状(比如一份文档的入口可以是「人照着它做」,状态变化是「他做出来的东西变了」)。套不进去,就把它拆到能套进去为止。
任务
任务:术语见同目录 GLOSSARY.md;审查可以来回几轮。
几个行为能合成一个任务,当且仅当它们共用同一条链路——判据是「同一个可操作入口 + 同一份数据」,两条都满足才合并;「同一段代码路径」只是常见结果,不是第三条门槛,用它去拆该合并的任务就是拆错了。入口或数据不同的,各做各的。
每个用户行为有一条或多条验收标准:正常走通是一条,被拒绝、边界、异常各是另一条。
用词
这一节管的是话怎么说,不管说什么。 它约束这套流程产出的全部文字:task.md、progress.md、executor.md、reviewer.md,主代理给用户写的回报与提问,以及派发子代理时写下的说明。项目自己的代码、标识符、测试名不受影响。
为什么放在契约里:下游多半是 AI,写下来的话会被下一轮的 AI 当成「已经确立的说法」再抄一遍。只读得到这个仓库的项目,这一节是它唯一会读到的用词约定。
四条:
- 用日常的中文词。 不自造名词,不把动词压成名词(写「验证一次」,不写「执行一次验证动作」);不写「闭环、抓手、赋能、底层逻辑」这类行话——它们看着专业,实际没说什么。契约里已经定义过的词不在此列:「链路」「用户行为」「噪声」有确切含义,照常用。
- 缩写第一次出现时写全称,后面用原词。 只有一类例外:照着敲的字面量——JSON 字段名(
"cwd"、"stdout"、"stderr")、命令里的占位符(<sha>)、程序输出里原样印出来的字符串;它们改了就对不上,只能原样保留。此外一律展开:PID(进程号)、WAL(预写日志)、SHA(提交号)、cwd(当前目录)在正文里第一次出现时写中文,别拿「大家都认识」当理由跳过——判据是读者能不能看懂,不是这个词有多常见。API、URL、JSON、HTTP、Git这类行业通用词照旧。 - 项目里的东西用它本来的名字。 文件、函数、命令、错误码原样写;术语用
GLOSSARY.md的词,不用它_Avoid_那行列的。 - 编号不当名字用。 写「用户配置读取失败」,不写「AC3 失败」;第 N 条、第 N 步这种契约内部的编号照旧。
这几条不给「为了好懂就把话说含糊」开口子:确实靠原词才说得准的(merge-base、worktree、mock)保留原词,判据是「不认识它的人能不能看懂」,不是「它是不是中文」。
.pipeline/
.pipeline/ 在子工作树里(<worktree>/.pipeline/)—— 进行中的任务,状态都放在这里,一个任务一份,寿命就是那个任务的寿命:worktree 被删掉,它随之消失。<id> 是这个任务的短标识(kebab-case),同时用作 worktree 目录名和分支名。
四个文件。规则是一个文件一个写者,且每个文件回答一个别人没回答过的问题:
task.md—— 只有主代理写。上段是任务描述(目标、范围、验收标准,每条标准标注「本轮新行为」还是「预走时已成立(回归)」,入口自动化不了的另标「无自动化验收测试」),写完不再改;下段是终态,合并后才写(合并提交、主树复验结果、已合并可清场)。它回答:要做什么。摸底确认的那个主分支名、未提交改动清单、本任务分到的环境资源实例、共享数据文件的初始值或重建步骤、全量测试的命令(分了两套就两套都记)、三档各占多少(只作观察,不作阈值)、不稳定测试排除清单的路径与时限天数、以及哪几条标准没有自动化验收测试也记在上段。progress.md—— 只有执行子代理写。它回答:还剩什么。 把这次要做的拆成若干步,每步后面写它现在是什么状态——用你自己的话,不规定词表。随时覆盖,永远只反映当前。executor.md—— 只有执行子代理写。它回答:发生了什么。 宣布完成时写一次,详略得当,给主代理看。reviewer.md—— 只有审查子代理写。它回答:复验看到了什么。(落笔顺序与对照按第 5 步,半截结论按「重派前清理」删。)
四个文件都是散文:没有 schema、没有必填字段、没有机器可读区块;用词按「用词」一节办。要加第五个,先回答它解决的是哪个还没人回答的问题——两个文件回答同一个问题,就说明有一个是多余的。这个问题是改这份契约时要回答的,不是在单次任务里加文件的口子。
作废的旧结论文件,在重派前删(下称「重派前清理」):描述重写、或改派执行者(换人接手)时,删上一轮的 executor.md、reviewer.md 与 progress.md;只是重派审查者(执行者不换)时只删 reviewer.md。
恢复 = 盘点残留(下称「恢复」一条):按四个发现源盘点——git worktree list 的登记条目、.worktrees/ 下的实际目录、git branch --list 扫出的 <id> 形态分支、公共 gitdir 的 info/exclude 里带任务标识的条目(git rev-parse --git-common-dir 所指那份)。四个都干净才没有残留、正常往下走。
第四个源不能省:前三个都干净、只有 info/exclude 里压着一条旧条目,是最容易漏的那种残留——它不影响登记、目录、分支任何一项,但漏删的条目会让主树里同名的合法文件被 git add -A 永久静默忽略(不报错、不改退出码),直到某次主树复验才发现少了文件。
任何一个不干净,就去读同目录的 RECOVERY.md——{登记, 目录, 分支} 三态表(8 种组合)怎么处置、并入判法、repair 与重挂、清场变体,全在那里。这份契约不重复它们,只留三条绕不过的:
- 认不出是谁的残留,保持原样。 认得出的判据(含跨会话可用的归属证据)、以及认不出时的处置,见
RECOVERY.md。 - 判成「已并入」之前,任何东西都不删。 并入判法(含 revert 与手工撤销的识别)见
RECOVERY.md。 - 恢复成续干的任务,要计入在跑数。 串行时先把它推到清场再开新任务;并行时计入「任务间并行」的在跑数——它占着一个 worktree、一份上下文。
下面这条不属于「恢复」,但它一直在这节里,保持在这里:
没有独立的「删除 .pipeline/」这个动作——清场就是第 7 步那几步,不是只删 worktree。执行者任务中新发现一个「不进提交但必须在 worktree 里」的文件的,照「标准有问题就报回来」同款通道报回,由主代理补记到 task.md 上段、加 info/exclude,处理完回复执行者继续(告知 exclude 已生效)——非描述性记录(主分支名、未提交改动清单、不进提交路径、环境资源实例、共享数据文件初始状态、全量测试命令)允许主代理追加到上段末尾,不算改描述、不作废审查。
项目的 .gitignore 必须在创建第一个 worktree 之前就提交 /.worktrees/ 与 /.pipeline/。前者让主工作树看不见整棵子工作树。后者不是给主工作树用的(主工作树里根本没有 .pipeline/)——它是给子工作树自己用的:.gitignore 里的 / 锚定在它所在的目录,同一个文件检出进 .worktrees/<id>/ 后,/.pipeline/ 在那里重新生效;少了它,执行者的 git add -A 会把 .pipeline/ 提交进分支。
流程
总览如下图(只画骨架,规则以下文为准)。本节与「.pipeline/」一节里主代理的 git 命令,一律在主工作树根目录下执行(即当前目录是主工作树根)。
sequenceDiagram
participant U as 用户
participant M as 主代理
participant E as 执行者
participant R as 审查者
U->>M: 提要求
loop 每个任务
M->>M: 摸底 定任务 定下全量测试命令
M->>M: 预走验收
M->>E: 派发 worktree 与标准
loop 测试驱动
E->>E: 失败测试
E->>E: 最小实现
E->>E: 重构
end
E->>E: 自检 + 跑全量测试
E-->>M: 回报完成
M->>M: 核对改动范围
M->>R: 派发 不带执行者结论
R->>R: 亲手逐条复验 + 跑全量测试
alt 通过
R-->>M: 通过
M->>M: 合并 no-ff
M->>M: 主树复验 + 跑全量测试
M->>M: 清场
Note over M: push 前由项目侧检查(钩子/CI/远端规则)再跑一次
else 问题在实现
R-->>M: 不通过
M->>E: 退回重做
else 问题在描述
R-->>M: 不通过
M->>M: 重写 task.md
M->>E: 按新描述重新派执行者
M->>R: 再派审查者
end
end
M-->>U: 报完成
- 规划:先摸底,再定下一个任务(一个或几个共用同一条链路的用户行为),给它一个短标识
<id>。摸底按这个顺序走——前两步在定任务之前,后四条在定下任务与<id>之后:- 查主工作树状态:先
git status记下状态——有进行到一半的 git 操作(merge、rebase、cherry-pick 都算)的,停下来报给用户,由他整理完再继续;同时确认检出在主分支上,不在的同样停下来交用户整理——这一条要排在恢复之前,恢复里的基底与主分支名都靠它定下来。主分支是哪个也要在这里定下来并记进task.md:先看git symbolic-ref --short refs/remotes/origin/HEAD(输出形如origin/main,去掉origin/才是本地分支名),取不到就按项目惯例(main、master),但取到的名字必须是本地分支、真实存在(git rev-parse --verify refs/heads/<名字>)且与当前检出一致,都对不上就问用户——写进去的只能是核对过的本地分支名,猜出来的名字(origin/main这类远端引用)会让后面的git diff、git log、git worktree add静默算错基底(见ENVIRONMENT.md)。然后按「恢复」那四个发现源盘点未关闭的任务,任何一个有残留就先按「恢复」处理(只看登记条目会让没登记的目录和孤儿分支永远没人发现),处理完再继续规划新的;恢复成续干的任务,串行时先把它推到清场再开新任务,并行时计入「任务间并行」的在跑数、新任务照那一节的判定走。 - 读项目约定:读
AGENTS.md之类,如果有;并确定四件事——产品定义在哪、术语在哪、测试怎么跑(指全量测试,见「全量测试」一节:命令、目录、单次耗时、吃掉哪些共享资源、三档各占多少、退出码可不可信)、什么不能碰。顺带查两样东西在不在:push 前那道检查(机制不限,判据见「全量测试」一节,且要按那一节末故意让它失败一次确认它真能拦)、以及共用代码有没有被别的任务在同一条链路上用着。摸底可以派一次性子代理去做(它有自己的上下文),结论不落盘。 - 查与任务的冲突:定下任务后再查一次
git status,未提交改动(含未跟踪文件)和本任务要改的文件有重叠的,停下来交用户整理——提交、stash 还是丢弃由用户选,主代理不代做;不重叠的记下即可,这份记录随task.md上段留下,主树复验失败时还要拿出来对照。主代理自己预走验收或主树复验留下的产物不算。用户已提交进主分支的推进也不用停,那是正常的 HEAD 前移,合并时真有冲突按「合并有冲突」办。 - 清旧版残留:项目里如果还留着旧版技能的东西——根目录有
pipeline_tools/,.pipeline/已经被 Git 跟踪,或者docs/tasks/下还压着旧版任务单(带<!-- Task ID: -->标记,或写着pipeline-contract区块)——说明它是从旧版迁过来的(旧版把任务证据提交进 Git,新版不提交):先读本技能同目录的MIGRATION.md,把该清的文件和理由当面说给用户,得到许可再动(这属于第 9 条的「缺用户许可」);用户说不清理就不清,问到这里为止——但「.pipeline/已经被 Git 跟踪」这一条例外:.gitignore对已跟踪的文件无效,不清就会让git add -A把.pipeline/提交进分支,至少要git rm -r --cached取消跟踪并落进提交才能开工(只改 index 不提交,建 worktree 检出旧 HEAD 时.pipeline/仍被跟踪),这个后果要连同选项一起说给用户。 - 定下任务之后:预走之前先取快照——
git status记下已有的未提交改动、把项目会写的外部共享状态(数据文件、本地库、缓存)当前值记进主代理自己的会话记录,预走本身会改掉它们,事后再取就晚了(迁移类项目尤其明显:预走跑一次migrate,数据就从 v1 变成 v2,回不去)。然后把这份快照抄进第 3 步的task.md上段。预走时若写脏了 tracked 文件,记下是哪个文件、缺什么可重定向参数,第 3 步一并处理。 - 再亲手走一遍,看标准现在是不是已经成立——整组标准全都已成立的,说明任务已经被满足、或标准没抓到真实变化,重选任务或重写标准;「搜不到相关代码」不能当成标准尚未成立的依据。同一个用户行为下的标准不必条条都从一开始不成立:边界、拒绝、异常这类在现状下成立是正常的(写它们是为防退化),按回归测试处理——不要求先红、可与实现同一提交、不占「一次只做一条标准」的位置(不必为它单开一轮「先红→实现→重构」),测试仍须写、仍须覆盖该条。注意「回归测试」是这条标准自己的性质,不是「全量测试」——后者是项目全部测试跑一遍,见「全量测试」一节,两件事。逐条标注写进第 3 步的 task.md,否则下游只能靠猜。这一走由主代理自己留下的产物,走完就清掉、不混进下一轮对照。并行与否在这一步按「任务间并行」定,串行照第 11 条。
- 查主工作树状态:先
- 建 worktree:
.gitignore已按上一节提交那两条才建——没有先在主工作树补上并提交(文件本身有未提交改动时按第 10 条交用户整理);再确认主工作树仍检出在摸底确认的那个分支上,不在的按第 10 条交用户整理;然后由主代理从主工作树当前 HEAD 建.worktrees/<id>(git worktree add -b <id> .worktrees/<id>)。.pipeline/被 Git 忽略,不随检出出现,到第 3 步写task.md时才建立。<id>已经被一条已有分支占用:认得出是上个任务残留的(本会话或task.md里出现过这个标识),先按「恢复」处理它,新任务不挂到旧分支上继承它的提交,也不为躲开它另起一个<id>把旧任务晾在原地;认不出是谁的分支(比如用户自己的同名分支),停下来问用户,不占用也不删除。目标目录.worktrees/<id>已经存在(命令报 already exists)的,说明那是个没登记的残留目录,按「目录在、登记不在」处理。 - 写任务描述:写进
<worktree>/.pipeline/task.md,含验收标准。每条标准后标注它是「本轮新行为」还是「预走时已成立(回归)」——这个判断只有预走的主代理知道,不写下来执行者与审查者都无从执行第 4 条的例外。范围里要写明执行者将要新建的测试目录或路径前缀(如tests/、__tests__/)——不然第 5 步的白名单会把验收测试本身判成越界;按前缀匹配,不必逐个列文件。写完不再改——要改就重写上段,作废此前的审查结论,按「重派前清理」删掉上一轮三个文件(非描述性记录追加到上段见「例外与词汇」表)。 - 派执行子代理:传入 worktree 绝对路径、验收标准、摸底结论(任务描述就在该 worktree 当前目录下的
.pipeline/task.md上段),连同分给本任务的环境资源实例(端口、库名/数据文件名,见「任务间并行」)。它先按「.pipeline/」一节建progress.md,然后按测试驱动做;做完自检(按验收标准亲手走一遍,再跑一次全量测试,查后置状态),自检时记录命令、退出码、执行摘要;回报完成前确认git status干净(.pipeline/与被忽略产物除外)——自检把仓库里的数据文件写脏是正常的,那就把它还原回原值再回报,并在executor.md里记清「改了哪个文件、原值/新值」;验收测试会写脏 tracked 文件的项目,标准里就要写明数据文件可重定向(用--store <临时路径>这类参数;项目本来没这个参数的,先在标准里把它写成任务范围内的一个验收项,或按第 9 条问用户要不要加,不擅自新增)。第二条要当心:标准的验的正是「跑完之后副作用留下了什么」(例如「二次 migrate 打印already 2且不重写备份」)时,还原值会让它变成假失败——这类标准必须把前置状态一起写清(「从 v1 的 store 开始」),还原时连同前置状态一起重建,不能只把文件拷回去。全量测试不是绿的就不能报完成——它挂了就说明这一轮改坏了别处,那是本任务的事,不是「本来就坏」(本来就坏的按第 9 条问用户,不自行判定无关)。自检通过后写executor.md,再回来回报完成。 - 派审查子代理:主代理先核对执行者的回报与提交——改动是否只落在任务描述的范围里(先用
git diff <主分支>...<id>这类与当前主分支的 merge-base 比列出实际改了哪些文件,再逐个看git log --name-only <主分支>..<id>的文件清单——只看 diff 会漏掉「改了又改回去」的越界:净效果为零的文件在 diff 里根本不出现,只有提交清单暴露它;tip-vs-tip 的两点git diff <主分支> <id>不能当基准,它会漏掉主分支前移后别人的提交(见ENVIRONMENT.md)——最后逐项对照task.md上段写明的范围,范围是白名单:不在上段的文件就算越界,哪怕只改一行;两个例外:上段已写明的测试路径(第 3 步要求写进去)、以及「问题在描述」里已声明作废的旧提交所涉文件——这两类不算越界),确认 worktree 检出在<id>分支尖端、工作区干净,再传入 worktree 绝对路径、同一份任务描述(task.md上段,不含终态)、验收标准、摸底结论,并告知改动范围(改了哪些文件、哪几个提交,只列属于当前描述的)。派发材料里不带执行者的结论。 审查者亲手复验、亲手逐条标准走一遍入口,并自己跑一次全量测试;长输出命令保留完整日志并按需回查,不能只采信退出码或摘要;先把自己的结论写进.pipeline/reviewer.md,再读执行者留下的东西(progress.md与executor.md)对照。 - 合并:主代理核对证据后合并到主分支,按这个顺序走:
- 合并前:检出状态按第 2 步同款再确认一次,不在的按第 10 条交用户整理。合并成功不改清场时机——清场仍只在主树复验通过、终态写过之后(第 7 步);主树复验不通过时要留着这棵树与四个文件。
- 执行合并:合并必须产生合并提交(用
git merge --no-ff),合的是分支上的提交,审查者留在工作区的临时改动不进提交。 - 主树复验:主代理在主工作树按验收标准亲手走一遍(同第 7 条要求),含跑一次全量测试,产物按「清掉未跟踪的、还原被改过的 tracked 文件(
git checkout --)」,判据是git status回到复验前。复验前先排掉两类 git 看不见的污染:- ①本任务的 worktree 里还有活着的进程(服务、watcher、测试挂住、
cd进去的 shell)——它会抢走端口或数据文件,让复验打到 worktree 的代码上、拿到假通过。判据不是「端口能不能起」,是「那个进程的进程号还在不在」:起服务时用Start-Process -PassThru(POSIX:cmd & echo $!)把进程号记下来,停的时候按进程号停(Windows 上「端口能起」「Rename-Item能改」都不算数,理由见ENVIRONMENT.md)。进程号丢了就按第 9 条问用户,不靠间接信号赌。 - ②共享的外部状态被写过(本地数据库、
store.json这类数据文件、缓存、外部服务沙箱)——复验要用的初始状态是task.md上段记的那份(见第 1 步与第 3 步:预走之前就要取,预走本身会改掉它),复验前照它恢复;恢复必须在停进程之后,否则会静默写进一个正被别的进程读的库。没记初始值、或恢复会毁掉用户/并行任务的数据时,按第 9 条问用户,不擅自删除。 这两类都不在git status里——「主树干净」只是必要条件,不是复验环境成立的证据。
- ①本任务的 worktree 里还有活着的进程(服务、watcher、测试挂住、
- 通过之后:才把
task.md的终态段写成已合并、可清场(连同合并提交与主树复验结果;复验经用户裁决豁免的写「已合并、复验经用户裁决豁免」)。
- 清场,按这个顺序走——顺序错了会丢东西或留现场:
- 先把要用的东西取出来:按
.pipeline/task.md记的路径取出 ①info/exclude里本任务的条目 ②各文件与回报里记录的临时日志路径,抄进主代理自己的会话记录(.pipeline/马上要删,写那里等于没取;记录只能落在主代理的会话里,项目里不新建文件记它)。exclude 条目本身删完 worktree 也还在info/exclude里读得到,真正会随.pipeline/一起丢的只有日志路径与「哪些条目是本任务的」这条对应关系。 - 停掉这个任务起的进程(清单见第 6 步主树复验)。worktree 里若还跑着服务、数据库、watcher、
cd进去的 shell,worktree remove会因文件占用失败(Windows 上是Permission denied),而且失败前它已经删掉了文件与.pipeline/、也移除了登记,只留一个空目录加.git与分支——这一格极难辨认,处置见RECOVERY.md「目录在、登记不在」一条;过程细节见ENVIRONMENT.md。 - 收拾主树复验留下的现场(见第 6 步):未跟踪的产物清掉、被改过的 tracked 文件用
git checkout --还原,判据是git status回到复验前;task.md上段若记了共享数据文件的初始值,按它把外部状态也恢复回去——这一步必须在停进程之后做,否则会静默写进一个正被读的库。漏了它清场结束后主树还是脏的、撞第 10 条。 - 删掉第 ① 项那些
info/exclude条目(漏了它条目就永久残留,之后同名的合法文件会被静默忽略、git add -A再也捡不进提交)。 - 删树前先摘掉 worktree 里的链接:
git worktree remove --force的递归删除会顺着 junction / symbolic link 穿透到目标目录本身(不是只删链接),而且绕过回收站——一次清场就足以把 gitignore 的数据目录连同几 GB 数据库删光(真实事故:worktree 里为跑测试建了指向主树data/的 junction,清场时主库连同备份、staging 全被删)。列出链接(Windowscmd /c dir /AL <worktree>、POSIXfind <worktree> -maxdepth 2 -type l)逐个摘掉:Windows 用不递归的cmd /c rmdir <路径>(单斜杠,cmd //c在 git-bash 里什么都不干),POSIX 用unlink;cmd /c rmdir摘成功也可能返回 1,退出码不可信,只认读回验证——链接已不存在、主树目标目录仍在、文件大小与清点前一致。更便宜的预防是 worktree 里压根不建指向主树的链接:测试要读真实数据就直接用绝对路径。 - 再两条命令删干净:先
git worktree remove --force .worktrees/<id>删工作树,后git branch -D <id>删分支——顺序不能反:分支被 worktree 占用时branch -D会报cannot delete branch,而worktree remove不删分支、漏了它每次清场都留一条孤儿分支。--force是防御性的:worktree 里若有未登记或非忽略的残留,普通remove会被拒;丢弃.pipeline/正是清场语义。 - 最后清理临时日志——走到这一步失败日志的保留条件也已满足,一并清掉;各角色清自己写的,主代理按刚取出的路径清子代理留下的。任务没走到这一步就结束的(规划中止、摸底完发现不做),摸底阶段那些一次性子代理留下的日志,同样由主代理按回报里的路径清掉。
- 先把要用的东西取出来:按
审查不通过、合并失败、中途放弃、执行期间报回,这几种情况分别怎么办。
-
执行者执行期间报回:「标准有问题」的,核对属实按「问题在描述」办(此时还没派过审查者,不涉及「重派审查者」);核对认为标准没错的,回复执行者按原描述继续。报回「做不了/卡住」的,涉及产品取舍或任务是否还值得做的按第 9 条问用户,标准的范围与收窄由用户裁决;确属标准写错(意图明确、只是表述有误)的,才按「问题在描述」重写。
-
审查结论无效(半截、或主代理当场核对发现它判错了)→ 重派之前,由主代理把 worktree 按「临时文件」一节那三件套还原(上一任动过手、或检出测试提交留下的现场,都要收掉),只删
reviewer.md重派审查者,不必退回执行者(问题不在它的实现上,让它返工只是白跑一轮);实现侧确有缺陷的(含审查者漏判的「通过」)仍按「问题在实现」办; -
问题在实现(跑不通、做错了)→ 原地重做,重做完按「重派前清理」删掉上一轮
reviewer.md再派审查者——新审查者一开文件就不该读到上一轮的裁决; -
问题在描述(标准本身错了)→ 重写任务描述,按「重派前清理」删掉上一轮三个文件(下一轮的人不该读到作废的结论),重新派执行者与审查者;重写出来的新标准同样要先按第 1 步「定下任务之后」那一条亲手预走一遍,已经成立的照那一套办——重选即放弃该任务,按第 7 步清场。
<id>分支上为旧描述写的提交已经作废——重派时说明哪些不再算数(或让执行者先清掉与新描述无关的提交),派审查者时「哪几个提交」只列对应当前描述的。 -
合并有冲突 → 若主工作树有进行到一半的合并(
git status会报出合并进行中,或看.git/MERGE_HEAD是否存在),先git merge --abort退出中途状态(这是收尾,不是解冲突、也不算改产品代码;留着它下一轮git merge会被拒绝,还会撞上第 10 条):由执行者解(worktree 的还原按「临时文件」一节由再次派发的执行者做),让它在自己的 worktree 里把主分支并进来、解冲突、重跑验收,然后重新走第 5、6 步;重派审查者前按「重派前清理」删掉上一轮reviewer.md; -
主树复验不通过(worktree 通过但主树不通过):
- 先排污染:对照摸底记下的清单与主树
git status——复验失败是被未提交改动污染的,停下交用户整理、重跑复验,不算实现问题;git 之外那两类同样要排(worktree 里活着的进程、共享数据库/数据文件被写过,见第 6 步),同样不算实现问题。 - 确属实现问题:撤掉这次合并(见下面的撤法),保留 worktree 与四个文件,按「问题在实现」退回执行者重做——终态不写「可清场」、不清场。退回时把这次复验的失败记录一并交给执行者当线索(
reviewer.md里只有审查者在 worktree 的「通过」,没有这次失败的信息),按「重派前清理」删掉上一轮reviewer.md再派审查者。 - 标准本身写错了:先按第 9 条问用户保不保留当前结果。保留的终态只记「已合并、主树复验未通过」,同样不清场——这个任务在「任务间并行」的在跑数里占一个永久名额,直到用户另作处置(说撤就按上面的撤法撤合并、说清就按
RECOVERY.md的清场变体收工——worktree 与分支都还在时那也就是第 7 步;撤合并与清场之间若被打断,登记或目录可能已经缺了一件,那时得走变体,不能只写「执行第 7 步」。名额占满时主代理主动提示用户)。不保留才撤掉合并、再按「问题在描述」重写任务描述。 - 撤法:
git revert -m 1 <那个合并提交>这类不丢主分支已有提交的方式,不用reset。撤之前先按RECOVERY.md的 revert 判法看一眼是否已经撤过——撤过的说明撤合并这步已完成,回到本条目原路径继续(该退回的退回、该重写的照常写),不因此清场;只有用户选「放弃」时才跳过 revert 直接收工——此处「放弃」指整任务作废、那条合并留在主分支上不再处置,不是「不保留」(不保留仍要先撤合并、再按「问题在描述」重写描述接着做,见上面的「标准本身写错了」与RECOVERY.md「补验分支」第 1 步);用户要的是「把合并也撤掉」的,走git revert -m 1,那不叫放弃。revert 撞冲突(.git/REVERT_HEAD在场、工作区留冲突标记)的,git revert --abort退出中途态再按第 9 条问用户,不擅自解。 - 撤完要把分支接回去,两条路,看重做时有没有改写分支历史:
- 只是往后追加了提交(「问题在实现」的原地重做就属这种)→ 先
git revert掉那条 revert(把主线还原回分支的原基底),再git merge --no-ff <id>就干净接上,原改动与新增提交一起回主分支。「重新合并会冲突」本身不构成换基底的依据——这种情形的坑是没先 revert 掉那条 revert:不先 revert 就直接合并,被撤的原改动保持撤销状态,而新提交若没碰到那些行,git merge会静默成功、不报任何错。 - 改写了历史(「问题在描述」要求清掉作废提交就属这种,分支的起点被挪回原基底)→ revert-the-revert 之后重新合并不再干净,只能让执行者把分支接到新的基底上(rebase / cherry-pick 到当前主分支尖)。
- 只是往后追加了提交(「问题在实现」的原地重做就属这种)→ 先
- 先排污染:对照摸底记下的清单与主树
-
用户中途说不做了 → 和第 7 步清场同一个动作,只是放弃了就不必再留失败日志,这个任务的临时日志一并清理(「失败日志留到查清为止」一行的例外)。若合并已落地、主树复验未做,已合进主分支的代码不能没人处置——先按第 9 条问用户撤不撤该合并(
git revert -m 1),再清场。
主代理自己在第 5、6 步核对不通过的(改动越出任务范围、证据对不上),同样按「问题在实现」退回执行者;如果这时合并已经落地,先按「主树复验不通过」那套撤掉这次合并再退回,坏内容不能留在主分支上。核对不过但现场明显是上一任审查者留下的(半截检出、临时改动、reviewer.md 缺失),按「审查结论无效」办——主代理还原后直接重派审查者,执行者不必白跑一轮重做。
不另起 <id>,不留接替痕迹。
停下来问用户
停下来问用户,不是把问题原样丢回去。 只描述「这里需要你决定」而不给判断,等于把决策成本原样还给用户。
能停下来的只有这几种情形:第 9 条、第 10 条、「临时文件」里裁决不了的归属,以及恢复与建 worktree 时认不出身份的残留(.worktrees/ 下认不出的目录/登记条目按第 9 条问用户,保持原样、不静默处置)。停的时候都按同一个问法:
- 说清卡在哪。 哪一步、哪条规则、什么事实让流程走不下去。写当场看到的东西(命令、退出码、文件状态),不写推测。
- 给出你的建议。 你认为该怎么办、理由是什么。建议可以不止一条,但要指明你推荐哪条——没有推荐的建议,等于把选择原样推回去。
- 写成几个选项。 每个选项一句话说清它会导致什么后果,让用户不用重建你的上下文就能选。
- 永远留一个「其它」。 用户在里面写自己的判断,包括推翻你所有建议。「以上都不是」这个选项本身不构成一个可选答案——它会把选择收窄回你给的那几条。
- 选完就按它走。 用户选了哪条就是哪条:不追问、不再问一遍、不把自己没被采纳的建议再讲一遍。
宿主有选项式提问就用它——点一下就能答;没有就写成编号列表,同样带「其它」。
任务间并行
多个任务可以同时推进,任务内部照旧走「流程」那七步——并行不改变执行、审查、合并各自由谁做、按什么顺序做。默认形态仍是串行;以下判定同时成立才并行:
-
同时在跑的任务不超过三个(在跑 = worktree 还没清场)。每个在跑的任务都有交错的回报要核对、复验要亲手跑;超了,先崩的是决定质量。
-
摸底确认共享资源不互踩。 worktree 只隔离文件,不隔离端口、数据库、构建缓存这类环境资源。第一件要做的是让测试自带环境:一条测试自己建、自己清它需要的东西,不依赖共享的库、不留共享的残留、不假设执行顺序。这种测试并行时不需要分配任何实例——它自己就是隔离的。所以顺序是:先把测试改成自带环境,再给剩下的分配实例;把分配实例当成默认方案,等于放弃了从根上消除冲突的机会。判据是「它碰不碰同一个具体实例」,不是「它是什么档」。
剩下的那些必须吃共享实例的(连一个真库、占一个固定端口、读一份共享数据集),才走下面这套:判据是「每个任务能不能分到互斥的实例」,不是「两边有没有同类资源」——同类型不算冲突(每个任务都要一个端口、一个测试库),要的是同一个具体实例被两个任务同时用。所以:①先列每个任务要用的互斥资源——要列全一组,不能只列主文件:监听端口、独占的数据库/schema、锁文件、数据库的预写日志(journal 文件)、备份文件、迁移状态/版本表、进程号记录文件、外部服务账号;全量测试算一个用资源的动作(它会起服务、连库、写数据文件),它吃的那组资源必须和别的任务错开,否则两份全量测试同时跑会互踩、拿到假绿;②给每个任务分配不同实例(端口 +1、该资源组的所有路径都带
<id>——只改主文件名、留下同名的store.lock仍会互斥),把分配结果写进它task.md上段;③再求一次交集,非空就不并行;④不可分配的固定资源直接判不并行——端口或路径写死在代码里改不了、共用一个外部服务账号、只能有一个实例的守护进程,这些属于真阻塞,靠实例分配解决不了(能不能配,第 1 步摸底就要查清;配不了就把「加可重定向入口」写成任务范围内的一个显式验收项,或干脆串行)。并发安全的资源(构建缓存、只读依赖目录)列出来但不参与比对。写不下来哪一样就是「确认不了」,同样不并行。第 4 步派执行者时把分给它那组实例一起交代,执行者只能用清单里分给它的那组,要新开资源或换端口,照「标准有问题就报回来」的通道报回,不擅自挑一个。
并行之下,这几条有特殊含义:
- 摸底共享。 同一批并行任务的派发材料装同一份摸底结论,不必每个任务重摸一次。
- 派完执行者就规划下一个。 一个任务在等执行者或审查者回来,主代理就去规划下一个——串行等待正是并行要消掉的成本。
- 合并是串行闸门。 一次只合一个任务;主树复验永远针对合并时刻的 HEAD——worktree 建在哪一刻的 HEAD 不作数。后合并的任务合并时与主树冲突的,按「合并有冲突」办。
- 审查不互相借。 每个任务有自己的审查者;任务 A 的审查结论对任务 B 不作数——B 面对的代码可能已经被 A 的合并改过。
全量测试
全量测试 = 项目里全部自动化测试跑一遍。 它是防「本任务验收全绿、别处已经坏了」的唯一手段:验收测试只证明这一条标准的入口通了,证明不了共用的代码没被这一轮改坏。
第 1 步摸底就把它定下来(四件事里的「测试怎么跑」),答不上来就不是一个能干活的状态:全量测试的命令是什么、从哪个目录跑、单次耗时多少、要吃掉哪些共享资源(端口、库名/数据文件名)、退出码可不可信、以及三档各占多少。没有这个命令的项目,第一个任务就是把测试集合建出来——这是范围里的一项,不是顺带。
四个地方,各跑一次,各是绿的才算做完(第 12 条):
| 谁 | 什么时候 | 落到哪 |
|---|---|---|
| 执行子代理 | 写 executor.md 之前 | executor.md:命令、退出码、执行摘要、耗时 |
| 审查子代理 | 写 reviewer.md 之前 | reviewer.md:同上,且不采信执行者的绿 |
| 主代理 | 合并后、在主工作树 | 本任务的复验记录;没过按「主树复验不通过」办 |
| 项目侧自动化 | 含代码文件的提交被 push 到远端之前 | 挂哪个机制不限,但要满足 TESTING.md 里的判据;配不出按第 9 条问用户 |
push 这一处必须由项目侧自动化强制,不靠人记得——主代理一个人守着没用,用户手工 push、别的 agent push 都绕得过去。具体挂在哪个机制不限,但它要满足 TESTING.md 里写的那六条,并且经摸底故意让它失败一次、确认它真能拦;做不到的按第 9 条问用户,不当作已经存在。
唯一的复用:同一个提交、同一棵树、同一份命令、期间没有任何改动时,后一处可以引用前一处的绿,同时记下提交号与时刻。执行者自检、审查者复验、主树复验这三处不适用——它们各自承载不同的判断责任(实现对不对、标准验没验到、合并后还成不成立),互相引用等于把它们并成一个,第 1 条就破了。真能省下的只有「主树复验刚跑完、立刻 push」这一次。
全量测试里混着不稳定测试怎么办(同一份代码、同一条命令,有时过有时不过)。不处置的话,四个地方会频繁因为与改动无关的原因变红;处置的办法是给它一个限期修的出口,细节、判据与格式都在同目录的 FLAKY.md 模板里。要点:判据是同一提交、同一命令、同一环境连跑三次、既有绿又有红(三次全红是缺陷,退回执行者修,不是不稳定);记进项目里的排除清单(本模板的落地位置见它开头的说明:跟着测试代码走,没有测试目录才放版本库根目录),第一次真的移出时才建,不预先建空文件;路径与时限天数记进 task.md 上段;登记的同一次动作里必须同时开一项修它的任务,否则这条登记不成立;到期自动回到全量集合(今天 > 移出日期 + 时限天数),移出日期不能往后挪。这张清单不是「测试可以不管了」的口子:被排除的测试覆盖的行为在修好之前没人验,这是它的真实代价。
测试怎么分档、push 检查要满足什么、怎么确认它真能拦,见 TESTING.md。
测试驱动
一次只做一条验收标准。每条标准走这三步,走完再换下一条:
- 先写测试:给这条标准写会失败的验收测试。它必须失败,且失败的原因必须是要做的事还没做——不是环境坏了、也不是测试写错了。
- 再写最小实现:只写让这条测试通过所需的最少代码。下一条标准留给下一轮,顺手在这一步做它就越界了。
- 然后重构:在测试保持通过的前提下整理代码。确实没有可重构之处就跳过。
每一步都单独提交——顺序是可查的事实,不是自我声明。所以审查者逐个检出只加测试的提交,亲手跑一次确认它按上一条要求的方式失败(是要做的事还没做),再看实现提交的 diff——那是发现测试被偷偷弱化的地方。测试与实现写在同一个文件里的项目(#[cfg(test)] 模块、单文件组件、doctest、随测试文件放的最小桩代码),「只加测试的提交」按行为判而不是按文件名判:那个提交必须是「测试在、被测目标的行为还不在」——桩可以是空函数或直接报未实现,但不能已经把目标行为写进去。判据仍是「跑起来必须失败、且失败原因是目标行为缺失」。
验收标准
每条标准写成一个可执行的步骤序列:做什么操作 → 期待什么状态变化。不写测试文件名、不写断言实现——那些会腐烂。
服务型入口的标准要多写三件套:启动方式(命令、从哪个目录、哪个端口)、就绪探测(怎么算起好了、超时多久、超时算不算失败)、停服方式。少了这三样,执行者与审查者各自发明一套、主树复验还可能打到别人留下的进程上。验收测试要无人值守地重复跑,这三样就得是自动化的(不是「人先开个窗口,然后我 curl 一下」):测试自己起、自己等就绪、自己停。手动的三件套只够人工走一遍,进不了测试。
不是二值的标准要连度量口径一起写:「p95 从 34ms 降到 20ms」这样的标准,必须同时写明采样口径(用哪个客户端/工具、样本量、测量点、预热与否、取哪一轮)与判定阈值——口径不同基线就不同,「现在成不成立」根本判不了。客户端也要写死:同一个接口,curl 量到的是毫秒级、换成某些 HTTP 客户端库可能差两个数量级,不指定就等于没标准。样本量要与百分位匹配:算 p95 至少要有上百个样本,10 个样本的 p95 就是最大值、那是 max 不是 p95。写不出可比口径的,就把标准改写成二值形态——指定客户端种类与调用方式、但不写死文件名(那会撞「不写测试文件名」),例如「用 curl 连发 200 次,第 190 次分位的耗时 ≤ 20ms」。
- 每条标准至少一条验收测试,且它必须能从真实入口无人值守地跑,测试代码由执行者写。标准写在代码之前,所以测试可以先写。只挂在标准「正常走通」那一条上是不够的:拒绝、边界、异常各是另一条标准,各有自己的入口路径,一条从整条链路进去的测试验不了它们——每条标准都要有,不是每个任务有一条就完事。从真实入口驱动 ≠ 必须跨机、必须起浏览器:入口指的是「用户或调用方真正走的那个面」,能在一个进程里把那个面驱动起来(命令行直接调、接口直接发请求、用替身挡掉外部的库),就还是单进程档;驱动不了才升级到单机档。能单进程就留在单进程档——升一档就慢一档、不稳一档,第 12 条要跑四次的地方就越容易红。
- 这条的道理是:你在意的行为,只能靠一条自动化测试来保证——想要某个行为稳稳存在,唯一的办法就是给它写一条测试。反过来,别人的改动通过了你全部的测试、却弄坏了你在意的东西,那是你的责任:缺的是你这边的那条测试,不是对方。这条同时也解释了「全量测试为什么会红」——红不是噪音,是你漏了一条测试的信号。档位和这条是两件事:单进程档、单机档、跨机档是「它碰什么资源」,不是「它重不重要」;一条关键行为完全可以(并且应该)用单进程档来验。
- 判断权在审查者:它逐条标准亲手走一遍入口,再判断对应的测试是否真的证明了那条标准。测试全绿不算通过——最常见的假通过是看起来端到端、中间层全 mock 了(见「假通过」条)。手上有变异测试工具的项目,可以拿它补一手:工具自动改坏源码(把比较符反过来、把返回值换成常量),再跑测试——测试没失败就说明那一处根本没验到。这是可选项,不做硬要求:它依赖每种语言各自的工具(各语言生态不同,没有通用入口),审查者只在「测试看起来全绿但可疑」时用,项目没有工具就仍然靠走入口。它不属于第 12 条那四处——那不是四处必跑的东西。
- 走一遍入口 = 从真实入口驱动一遍:命令行入口就调命令,界面或服务型入口用项目自带的驱动方式,或向它对外暴露的界面、接口发起真实请求——不是调用被测代码的内部函数,也不是把验收测试再跑一遍。宿主驱动不了某个入口的:子代理没有用户通道,如实回报「无法驱动、未能复验」交主代理,不自行判成不通过——那会把能用的实现白白退回重做;主代理按第 9 条停下来问用户,经用户裁决接受未能复验的项时,该裁决同时替代审查者与主树两次复验,不因此违反第 1 条;用户不接受的,按「问题在描述」或放弃办。这个豁免只有这一种用法:只覆盖「入口驱动不了、审查者已如实回报」的单个验收项,不适用于硬前提缺失(没有独立上下文、没有子代理、没有 worktree),也不是给「整条标准免验」开口子——那些情形本技能不适用,用户同意也不行。
- 自动化不了的入口,标准要当场记成「无自动化验收测试」(例外表那一行):它不是「以后补」,而是这条标准每次都要人手工走一遍——所以要在
task.md上段逐条写明哪几条是这种,并且在预走阶段就按第 9 条交用户裁决:接受(每次手工走)还是把入口改造成可驱动(那就成了任务范围内的一项)。拖到派发执行者时才撞见,执行者就会自己发明一套弱化的验法。
证据与恢复
证据是主代理当场获得的事实:命令、退出码、超时状态、执行摘要、按需回查的长输出日志、后置验证结果和审查结论。
- 执行摘要不是语义证明。 它是低成本、确定性的执行层事实,内容同「噪声」一节;不要求为每条命令调用 LLM,也不假设它理解了命令语义。
- 完整日志可回查。 长输出命令的完整标准输出和错误输出放在系统临时目录,关键验收不能只依赖摘要或退出码。日志被系统临时目录回收读不到时,按证据缺失处理并重跑取回。
- 失败日志留到查清为止。 命令失败的完整日志要留到原因确认、修复和复验完成之后才可清理;审查和重做都靠它,当场删掉就没了(任务被用户放弃时随清场一并清理,见「例外与词汇」表)。
- 成功也要验证状态。 命令退出码为 0 后,按验收标准检查实际状态变化;测试、构建、生成和迁移等命令分别确认测试执行、产物存在、文件正确或目标状态产生。
- 临时日志不进任务状态。
.pipeline/只保留四个文件,不新增日志、摘要或 JSON 结果文件;日志路径要随结论或回报记下——清场靠它取回子代理写的日志——但不把完整日志复制进去。 - 不承担审计职能。 任务做完了,结果就在代码里;做失败了,也不必留一份失败档案。
- 不进入 Git。 它在被忽略的
.pipeline/和系统临时目录里,任务结束后不必归档。
代价说清楚:换机器、重新 clone 都会丢掉 .pipeline/;git clean -ffdx(第二个 -f 才删嵌套仓库,-fdx 会跳过)会连整个 .worktrees/ 一起删掉——那里是正在进行的工作,不是缓存。 它服务的是同一台机器上换一个 agent 软件继续干,不是异地恢复。手工删 worktree 目录而不删分支,会留下一个孤立分支和一条指向已删目录的 stale 登记(git worktree list 仍看得见、靠它找回残留)。
不可违反的规则
本节条款称「第 N 条」(全文「第 9 条」「第 10 条」等均指这里)。
- 没有独立审查就没做完。自检、自己审自己、主代理替执行者背书,都不算。
- 先写任务描述再开工;
task.md只有主代理写。描述改了,此前的审查结论一律作废,按「重派前清理」删掉上一轮三个文件(非描述性记录的追加见「例外与词汇」表)。 - 一个任务一个 worktree,固定在
.worktrees/<id>。不建第二个,不在仓库外建。 - 测试驱动的顺序不能倒。没有失败的测试,就没有实现。例外只有一类:第 1 步预走时就已成立的标准,按回归测试处理(可与实现同一提交、不要求先红),见「定下任务之后」那一条——它们不占「一次只做一条标准」的位置,审查者只核对它确实覆盖了那条标准。
- 长输出命令的完整标准输出和错误输出(stdout/stderr)必须保存在系统临时目录;进入上下文的是执行摘要,摘要不足时按需回查原始日志。宿主存不下时按「执行结果结构」标记
degraded与missingFields,并改用更窄或机器可读的命令,或换能完整保存日志的环境重跑。 - 退出码为 0 不能替代验收;必须检查验收标准要求的实际状态变化。
- 合并后必须在主工作树按验收标准亲手走一遍,含跑全量测试与验收测试、查后置状态。worktree 通过 ≠ 主树通过。
- 做完就清场(第 7 步那几步,含取出 exclude 条目与日志路径、停进程、清主树产物):
.pipeline/随 worktree 一起消失。 - 缺产品决策或用户许可、协议兼容性存疑、外部服务或权限不可得、宿主能力缺口(驱动不了某个验收入口、定不了主分支名),或流程被环境、工具故障卡住且主代理自己修不好时,停下来问用户,不擅自定案。
- 主工作树未检出在主分支(摸底确认的那个分支),或主工作树有进行到一半的 git 操作(merge、rebase、cherry-pick、revert 都算),或未提交改动(含未跟踪文件)和本任务要改的文件有重叠——或这些改动污染了主树复验:停下来交用户整理,整理完接着走被打断的那一步——还没建 worktree 的再建,该合并的再合并,复验被污染的按「主树复验不通过」重跑复验。
- 派发不是交付。子代理交回结果只说明这一步做完了:主代理接着走流程的下一步,串行与并行都一样——串行时一个任务清场后直接开始下一个任务,不交回控制权、不等一句「继续」;只在「停下来问用户」一节列的情形停,问法照那一节。但「下一个任务」必须来自用户的请求:用户一次给了几件事就依次做完;做完了、没有排队的请求,就是合法的收工点,报完成即可——这不叫交回控制权,用户没要的改动也不主动发明。
- 全量测试要在四个地方各跑一遍、各是绿的,缺一处就没做完:执行者回报完成前、审查者下结论前、主树复验时、包含代码文件的提交被 push 到远端之前。四处都必须自己跑、写进各自的结论里;不采信别人跑的结果,唯一的复用见「例外与词汇」表。push 这一处必须由项目侧自动化强制,不靠人记得——主代理一个人守着没用,用户手工 push、别的 agent push 都绕得过去。具体挂什么机制不限(钩子、CI、远端分支保护都行),但它要满足「全量测试」一节那六条,并且经摸底故意让它失败一次、确认它真能拦;做不到的按第 9 条问用户,不当作已经存在。
例外与词汇
例外只许有下表这些。 规则句里不再追加「除了…之外」的从句——要加例外,先在这张表加一行,再让规则句指向它。这是把「每打一次补丁就多一条从句」堵住的机制。
| 规则 | 例外 | 在哪展开 |
|---|---|---|
| 第 4 条:没有失败的测试就没有实现 | 预走时已成立的标准按回归测试处理(可同一提交、不要求先红、不占「一次只做一条」的位置) | 「定下任务之后」;逐条标注写进 task.md |
| 第 5 步:范围是白名单 | ①上段已写明的测试目录/前缀 ②「问题在描述」里已声明作废的旧提交所涉文件 | 第 3 步、第 5 步 |
| 第 10 条:主树有未提交改动就停下 | 主代理自己预走验收、主树复验留下的产物不算 | 第 1 步「查与任务的冲突」、第 6 步 |
| 第 1 条:没有独立审查就没做完 | 审查者报「入口驱动不了」、经用户裁决接受的单个验收项;硬前提缺失不适用 | 「验收标准」一节 |
| 还原三件套:再派执行者前必须还原 | 同一份描述下接着中断的活干(被打断、超时、恢复后续干)不还原 | 「临时文件」一节 |
| 主代理自己不改产品代码 | 收尾类动作不算改代码:合并、跑验收命令、git revert -m 1 撤整次合并、git merge --abort 退出半截合并、审查者留下坏现场时按「还原三件套」复位 worktree、补提交 .gitignore、写 info/exclude | 「角色」、「合并有冲突」、「主树复验不通过」、「审查结论无效」 |
| 第 4 条:只加测试的提交必须失败 | 测试与实现写在同一个文件里的项目,按行为判而不按文件名判(桩可以是空函数或直接报未实现,但不许已把目标行为写进去) | 「测试驱动」一节末 |
.gitignore 对已跟踪文件无效 | 旧版 .pipeline/ 已被跟踪的,至少要 git rm -r --cached 并落进提交才能开工 | 第 1 步「清旧版残留」、MIGRATION.md |
task.md 上段写完不再改(第 2 条) | 非描述性记录(主分支名、未提交改动清单、不进提交路径、环境资源实例、共享数据文件初始状态、全量测试命令、三档占比)允许追加到上段末尾——不算改描述、不作废此前的审查结论 | 「.pipeline/」一节「没有独立的『删除 .pipeline/』这个动作」那段 |
| 失败日志留到查清为止 | 任务被用户放弃时,日志随清场一并清理(保留条件已不可能满足) | 「证据与恢复」;「流程」一节之后那组异常处置里的「用户中途说不做了」 |
| 四个地方都要各跑一遍全量测试 | 单次命令预算内跑不完的项目,分快速集合与全量集合;四个地方跑全量集合,快速集合不能顶替 | 「全量测试」一节 |
| 第 12 条:push 前全量测试必须通过 | 刚在同一个提交、同一棵树上跑完全量测试、期间没有任何改动时,可直接用它,记下提交号与时刻;执行者自检、审查者复验、主树复验不适用本行 | 「全量测试」一节末 |
| 第 12 条:四处都要全绿 | 排除清单(FLAKY.md 模板)里已登记、且未过期的不稳定测试不计入红——判据是同一提交同一命令同一环境连跑三次有红有绿、同一次动作里已开修它的任务、且今天 ≤ 移出日期 + 时限天数;过期自动回全量集合,不许改移出日期往后挪 | 「全量测试」一节末、FLAKY.md |
| 每条标准至少一条验收测试 | 入口无法被自动化驱动的(界面、硬件、外部服务):该条标准记成「无自动化验收测试」,改为每次手工走一遍、逐条写进 task.md,按第 9 条经用户裁决 | 「验收标准」一节 |
| 主代理自己不改产品代码 | 摸底时故意让 push 前那道检查失败一次以确认它真能拦——只许动版本库之外的机制配置(.git/hooks/*、仓库外的 CI 设置或包装命令);动到版本库里的 CI 配置文件就不算例外,那一步按任务处理(写进 task.md 范围,由执行者做),或请用户来做 | 「全量测试」一节末 |
术语见同目录的 GLOSSARY.md;技能说明与它对同一个词的用法冲突时,以它为准——它只管词,不管规则。
加例外的方式只有一种:先在上面那张表加一行,再让规则句指向它——不在规则句里追加「除了…之外」的从句。这是把「每改一次就多一条从句、每条从句又生出新矛盾」堵住的机制。