交付流水线规则
该项目在 GSD(参见 docs/gsd_multilevel_delivery_pipeline.md)之上运行一个按工单处理的交付流水线。计划同时扮演工单角色;这些规则确保流水线的确定性层能够机器消费这些计划。
语言。 所有工件——计划、前置元数据、提交信息、代码、PR 正文——均使用英语编写。当直接与用户对话时,使用用户的语言回复;英语用于工件,用户的语言用于对话。
给计划编写者(编写 PLAN.md)
-
始终包含完整的前置元数据:
phase、plan、title、type、wave、depends_on: []、files_modified: []、requirements: []。空的requirements是阻塞项——GSD 的计划检查器和 Gate 2(validate-graph)都会拒绝它;请引用 ROADMAP 的 requirement ID,或者当计划是从外部导入时的外部跟踪器 ID。wave是供人类读者参考的文档:依赖图是权威的,验证器会重新计算真实的依赖深度,并在你的值与它不一致时发出警告。请保持真实,而非装饰。 -
交付块(只增不减,绝不替换 GSD 字段):
delivery: ticket: T-<phase>-<plan> risk: low|medium|high # high 必须设置 human_checkpoint: true human_checkpoint: false repo: owner/name # 仅当工作位于另一个仓库时才使用repo对于任何不修改此仓库的工单都是必需的。 如果省略,流水线会针对错误的仓库跟踪该工单:它的 PR 可能在隔壁仓库已经变绿并合并,而看板却永远显示pending,并且每个依赖它的工单都会被一个永远不会推进的工单阻塞。此时files_modified中的路径是相对于那个仓库的——绝不能使用../other-repo/x.ts,因为任何工作树都无法访问(Gate 2 会警告,状态同步会将工单搁置)。跨仓库的依赖不会级联:父工单必须先合并,因此应将契约侧(类型/工具名)切片到自己的工单中,而不是让消费者等待整个功能完成。 -
不要发明分支名。 规范的分支名是
ticket/<ID>-<slug>,其中 slug 是经过清理的工单标题(小写、音译、非字母数字字符折叠为单个连字符、不超过 40 个字符)。省略delivery.branch——图验证器会生成它;显式指定的值将按照相同规则进行验证。 -
files_modified是一个契约,而不是猜测——列出计划接触的每个路径。空的files_modified会导致 Gate 2 失败(这是使“依赖无序的工单永远不会冲突”变得可检查的条件,也是执行器的范围)。依赖无序的计划如果路径重叠也会导致 Gate 2 失败;请通过添加依赖或重新切片来解决,绝不要通过扩大通配符来规避(匹配所有内容的裸通配符会被标记)。 -
在计划编写新代码之前,先命名已经完成此功能的现有代码。 每个计划的 Context 读取必须要么指向该工单扩展的现有实现(
path:line——它基于的转换器、排序器、中间件、助手、端点),要么记录未找到时的检查过程:你搜索了什么行为,以及你查看了哪两三个可能的位置。按行为搜索,而不是按你打算给新代码起的名字——重复的代码几乎总是以其他名字存在,而这正是它能在审查中存活的原因。“我没有看到”不等于“我找过”;只有后者才是读者可以信任的计划。这是流水线中最便宜的门控:一个未被注意到的重复会等到集成者整整一个阶段之后才发现,那时其他工单已经构建在它上面了,而到那时移除它本身就是一个新工单。与 §4 不同,这无法从前置元数据中机器检查——Gate 2 永远不会看到它,漂移检查只对重新评估的工单进行兜底,因此将其写下来是控制手段。 -
验证命令应限定在工单范围内,而非项目范围。 编写最狭窄的命令,使其实际覆盖
files_modified——具体的测试文件或过滤器、类型检查和 lint 覆盖所触及的路径。裸的npm test/make test/ 整个端到端套件不是这里的验证命令:它属于 CI,每次推送时在专为它构建的硬件上运行一次。执行器在每次尝试时都会运行这些命令,而修复角色在每轮修复中也会重新运行它们,因此一个未限定范围的命令不仅仅慢一次——它是整个流水线的节拍速度,乘以每个工单在自己的工作树中并行运行的次数。一个命令要可用,必须满足两个属性:它必须能在没有外部服务的裸工作树中运行,并且它必须仅因本工单的错误而失败。无法限定范围的命令(需要实时数据库、浏览器、部署环境的套件)是 CI 专属检查——在“测试策略”下说明,并将其从“验证命令”中排除,而不是交给执行器一个它无法运行的东西。 -
depends_on驱动级联,而不仅仅是排序。 在史诗堆叠交付(默认)下,根工单(空的depends_on)向阶段史诗分支发起 PR;依赖工单则进行级联——它向主要父工单的分支发起 PR而无需等待合并,因此流程永远不会阻塞。精确声明依赖关系:虚假的依赖会使流程串行化,缺少依赖则会使执行器面对不完整的基础。验证器会推导出史诗(epic/<phase-dir>)、主要父工单和pr_base;多个同阶段父工单(钻石形)会收到警告——实际情况请尽量线性化。将依赖保持在一个阶段内。 跨阶段依赖无法级联——没有共享分支可供堆叠——因此依赖工单将保持阻塞,直到父工单的整个阶段都已合并到默认分支。验证器会对每一个发出警告;请重新切片。 -
ID 是规范的:
T-<2位阶段>-<2位计划>。请写T-01-02,不要写T-1-2——验证器两者都能规范化,但分支、PR 标题和 Jira 标签在其他所有地方都进行字符串比较。 -
值后面不要跟
#注释。 将注释放在单独一行。以前files_modified: [a.ts] # note会将注释折叠到最后一个路径中;解析器现在已能正确去除它,但 Gate 2 仍然拒绝任何包含#的值,因为这几乎总是泄漏。 -
Gate 2 是机械的:只有当图验证器以退出码 0 结束时它才通过。Jira/GitHub 问题、ROLLOUT.md 或散文式摘要绝不能替代实体化的 PLAN 文件。
-
Jira 是投影,不是事实来源。 默认情况下(除非
pipeline.jira.enabled: false或未连接 Jira MCP),分解步骤会在 Gate 2 之后(绝不提前,也绝不替代)将验证通过的图导出到 Jira(每个阶段一个 Epic,每个工单一个 issue),全部使用英语。项目会自动解析并缓存;Jira 失败不得阻止分解;PLAN 文件仍然是规范的。
给执行器(实现一个工单)
- 保持在
files_modified范围内。 范围外的更改属于另一个工单——应升级处理,而不是静默扩大范围。 - 在编写新代码之前先阅读现有实现。 无论计划在 §5 中命名了什么,以及流水线从漂移检查中交给你的任何
reuse_candidates:先阅读每一个,并扩展它,而不是在它旁边添加一个平行层。如果一个候选确实不适用,请在证据中说明原因——这是合法结果,静默忽略则不是。重用绝不意味着你可以离开files_modified:如果在它之上构建会让你超出范围,那就是范围外的升级(§1),而不是更大的差异。 - 原子提交,前缀为工单 ID:
feat(T-01-02): …。 - 在本地运行计划的验证命令直至通过后再声明完成;没有命令输出就绝不能声称验证通过。运行那些命令——不要通过将范围扩大到整个套件来“确保全面”(§6):CI 拥有完整的运行权,而你在此启动的套件会阻塞你自己的工作树以及共享同一台机器的每个其他执行器。如果计划的命令已损坏或明显不覆盖更改,请在计划中修复或缩小它们,并在证据中说明;静默地用
npm test替换不是修复。 - 工作树和分支来自流水线(
tickets.json)——不要临时创建分支或工作树。你的 PR 基分支同样由流水线解析(delivery-state.json→base:对于根工单是史诗分支,对于依赖工单是父工单的分支)——在史诗堆叠模式下,绝不要直接向 main/master 发起 PR。 - 停在提交处。 不要
push,不要创建拉取请求,不要接触审查者。编排器在发布任何内容之前会机械地验证工作树(git log <base>..HEAD)——正是这个检查能捕获“代理报告成功但实际未做任何更改”的情况,并且只有将发布权与被检查的代理分开才能奏效。请交回你的验证证据和一个 PR 正文。