# Coding 并行化实施附录(设计综合裁决) > **本附录优先级高于计划正文《2026-06-12-parallel-orchestration.md》,冲突以附录为准。** > > 来源:预置修正点 D1–D5 + 三个镜头评审的全部 findings,逐条裁决后综合(裁决标准:正确性 > 简单性 > 性能;坚持 fail-open to serial——任何调度/隔离环节失败一律降级串行,**绝不**因并行机制新增 halt 点)。关键事实均已对照 workflows/coding.mjs 与 skills/plan/skeleton-gen/templates/ 实测核验(含 `node --check` 基线恒红的复现)。 > > 裁决总览:接受 28 条 findings(含跨镜头重复条目合并)+ D1–D5 全部确认(其中 D2/D3/D4/D5 带修正);拒绝 1 条(lane 测试库 moduleId 命名,见 § 十)。 --- ## 一、总开关与入口(修正 D4) ### 补 1:coding-start 只开模块级并行,features 留闸 - **改什么**:`ARGS.parallel` 缺省 → 全串行(D4 前半,确认不变)。coding-start 步骤 4 只传 `parallel: { modules: true, maxWidth: 3 }`,**不传** `features` / `featureMaxWidth`(即维持计划行 12 的 `features: false` 缺省)。`features: true` 的开启动作写成 Task 14 的验收 checkbox——评估通过后才改 coding-start 的 SKILL.md(一行改动)。 - **为什么**:D4 原文让入口从第一天就传 `features: true`,Task 12-13 一旦合入该 flag 立即生效,绕过计划自设的两道闸(Phase F 题注「A–E 稳定 ≥1 轮」、Task 14「收益 < 20% 冻结」);届时排查问题的人不会想到入口早已埋了开关。 - **落点**:Task 10(coding-start args 与横幅)、Task 14(新增"开启 features 开关"验收 checkbox)。 ## 二、Phase F 范围(确认 D1) ### 补 2:feature 级并行仅限后端 REQ,移除 ERP_PORT_OFFSET - **改什么**:D1 照案通过——Phase F 收窄为后端 REQ;Task 13 中 ERP_PORT_OFFSET 相关内容(含模板支持)整体移除;FE 的 feature 级并行收益由 Phase E spec 预生成承担。 - **为什么**:评审核验成立——后端 REQ 链的 tdd/verify 不起栈、不占固定端口(Spring 测试随机端口);FE feature 的 e2e 要打穿 vite/playwright/后端三层端口配置,复杂度/收益比不成立。 - **落点**:Phase F 题注、Task 13。 ## 三、波次前置串行段(修正 D2:补 lane 占用感知) ### 补 3:主根复位 + prune 不够,必须加 lane 占用感知 - **改什么**:D2 的「主根 HEAD 回默认分支 + 树净 + `git worktree prune`」确认保留,但补三点修正: - (a) **lane 占用感知微步骤**(prune 之外另加):`git -C worktree list --porcelain` 枚举 `-lanes/` 下持有 `module-*` 分支的残留 lane。本次要跑的模块若其分支被某残留 lane 占用:lane 树脏 → 先对**该 lane** 跑 recoverDirtyWorktreePromptM(branch=`module-`)(in-scope 残留 commit 进模块分支,工作不丢);恢复成功或本就净 → `git worktree remove` 该 lane;恢复失败(越界残留)→ 该模块按现行 `branchSetup-dirty-worktree` 同类语义结构化 halt(**等价前移**,非新增 halt 类——串行路径走到 runBranchSetup 也会在同一种脏树上 halt),波次其余模块照常调度。 - (b) 占用感知前置跑在**每个波次**(含宽 1)开跑前——宽 1 回退路径正是被残留 lane 卡死的高发场景:halt → lane 保留取证 → resume 后剩余模块独占波次(宽 1)→ 主根 legacy 路径 `git checkout module-` 被残留 lane 拒绝(git 实测 fatal: already used by worktree)→ 仲裁三轮全败 → 硬 halt。即"降级回串行"这条兜底路径本身会被残留 lane 毒化。 - (c) 主根脏树恢复的目标分支传**当前 HEAD 分支**而非默认分支:HEAD 停在 `module-` 且脏 → recoverDirtyWorktree(branch=`module-`) 就地 commit-in-scope 后再 checkout 默认分支(传默认分支会被 coding.mjs:1144 的分支护栏按 dirty-on-wrong-branch 拒绝,造成本可避免的降级/halt);HEAD 已在**默认分支**且脏 → 不在前置段处理(绝不为并行机制新增"对默认分支自动 commit"),本波次降级 maxWidth=1 交给 legacy 路径现有恢复/halt 语义。 - `git worktree prune` 仅保留用于清理"目录已删"的管理残留——它对目录完好的 lane(恰是 Task 6 Step 4 刻意保留的取证态)毫无作用,不要依赖它。 - **为什么**:git 实测双向阻塞——分支被主根 checkout 时 `worktree add` 报 fatal(D2 动机,成立);反向残留 lane 持分支时主根 checkout 同样 fatal(D2 未覆盖)。 - **落点**:Task 9 Step 3(前置串行段微步骤,扩展为含宽 1 波次)、Task 6 Step 4(halt 保留 lane 的注释补一句"resume 时由波次前置占用感知清理")。 ## 四、migration 版本段(Task 7) ### 补 4:maxV 改全局并集,laneIdx 保留 - **改什么**:maxMigrationVersionPromptM 的输入从"主根 `ls sql/migrations/V*.sql`"改为**全局并集**:主根文件 ∪ `git for-each-ref refs/heads/module-*` 的每个分支跑 `git ls-tree -r --name-only -- sql/migrations`,取全体最大版本号(纯 git 只读、确定性、零时间/随机源)。`vBase(maxV, laneIdx)` 的 laneIdx 保留不改,并在注释写明:并集口径下"halt-resume 后 laneIdx 漂移"**无害**——每个新波次的段基址恒高于一切已知版本(含未合并分支已占用的段),同段绝不二次发放,只产生无害的段空洞。 - **为什么**:halt 波次收敛 + 保留 lane 意味着 halted 模块的 V 文件已 commit 在未合并的 `module-` 分支上,对主根 ls 不可见;下一波或下次 resume 会把同段重复发放给别的模块,两分支各写 V(文件名不同,git 合并静默通过),Flyway 在"同版本号多文件"上直接报错——正是计划行 23 点名的事故,且发生在两次运行之间,波次内防线救不了。评审另案的"docs02Idx 替代 laneIdx""段账本文件"两个子方案不采纳:并集口径已消除全部撞号路径,再加稳定键/账本是多余复杂度(正确性等同,简单性更差)。 - **落点**:Task 7 Step 1(prompt 改并集)、Step 2(注释写明漂移无害的依据)。 ### 补 5:Flyway out-of-order 约定 - **改什么**:docs-04 骨架模板锁定段加一条后端约定「Flyway 配置 `out-of-order: true`」;Task 7 Step 3 注释与 README 阶段 B 同步写明:并行版本段下版本号**只保唯一、不保连续、不保合并序**;独立模块 migration 互不引用正是同波次准入条件,乱序 apply 安全。 - **为什么**:计划行 202「段间空洞无影响」只对**每次重建**的库成立。同波次两模块按完成序 merge,高版本段可能先落地默认分支;任何 inter-merge 窗口被 migrate 过、之后不重建的持久库(docs-04 § 3.5 鼓励的人工验收/演示库正落在该窗口),下一次 migrate 会因低版本号 pending 在 validate 上硬失败,报错形似模块 bug。 - **落点**:Task 7 Step 3、Task 8(docs-04 模板同批改动)、Task 10(README)。 ## 五、测试 DB lane 化(修正 D5:三件套 + fail-closed 兜底,Task 8) ### 补 6:env 必须打穿到 Spring 进程,探测两点联查 - **改什么**(D5 修正后确认,三件套): - (a) **新骨架约定**:docs-04 skeleton 模板锁定段 + tddPrompt 写明——后端 `application*.yml`(含测试 profile)JDBC URL 的 schema 段必须写成 Spring 占位 `${ERP_TEST_DB_SCHEMA:}`(env 缺省回退原 schema,**串行行为一字不变**;gradle test fork 的 JVM 与 bootRun 默认继承 env,env 即天然贯通,无需 SPRING_DATASOURCE_URL 拼装)。 - (b) **探测两点联查**(替换 Task 8 Step 3 的单 grep):grep 目标项目 `scripts/` 含 `ERP_TEST_DB_SCHEMA` **且** grep `backend/src/**/application*.{yml,properties}` 含 `${ERP_TEST_DB_SCHEMA` 占位;任一不满足 → 本波次降级 maxWidth=1(log 写明原因 + 建议重跑 skeleton 升级,**不 halt**)。 - (c) lane 注入维持"成品字符串"纪律(Task 8 Step 2 原案):`ERP_TEST_DB_SCHEMA=` 前缀由波次执行器读 config-vars.yaml 拼好渲染进 contract,绝不让子代理自己拼;明示**含派发给嵌套子会话的命令**。 - **为什么**:实测模板现状——schema 引用只在 setup-test-db / seed-demo-data 两个 Node 脚本;gradle 测试与 bootRun 的 datasource 由 coding 期生成的 application*.yml 写死(全仓无任何 application 模板或 spring.datasource 占位约定),env 传进去无人消费。按计划只改三个模板,Task 8 Step 3 的探测**必然假阳性放行**:setup-test-db 建了 lane 库、两条 lane 的 gradle 测试仍同连共享库互相清库——fail-open 的降级判据必须可信,否则降级形同虚设。评审中"test.mjs run() 注入 SPRING_DATASOURCE_URL + 探测 grep 注入代码特征"的子方案不采纳:tdd 内循环由嵌套子会话直跑 gradle、不经 test.mjs,该注入覆盖不到主要路径;占位约定让 env 在所有 fork 路径天然贯通,更简单且覆盖完整。存量项目(无占位)按 (b) 老老实实降级串行,不另设兜底注入。 - **落点**:Task 8 Step 1/2/3、tddPrompt(与 Task 7 Step 3 同文件顺带)、docs-04 模板。 ### 补 7:lane schema 的 fail-closed 兜底 marker - **改什么**:波次执行器建好 lane worktree 后,在 lane 根写确定性标记文件 `.tmp/erp-lane-db`(一行 lane schema 名;`.tmp/` 已在 gitignore 模板忽略,绝不会被 recoverDirtyWorktree 判 in-scope 自动 commit)。三个脚本模板取值顺序改为:`process.env.ERP_TEST_DB_SCHEMA || || db.schema`——脚本按自身所在仓库根定位 marker,lane 副本天然 lane 作用域;主根无此文件,串行行为一字不变。lib 模板测试补 marker 优先级 case。明令**禁止**用"改 lane 副本 config-vars.yaml 的 database.schema"兜底(该文件随 git 提交,lane 树一脏会被自动 commit 并随 milestone 合回默认分支,把真 schema 名搞坏)。 - **为什么**:lane 注入是纯 prompt-only,tdd 主会话还会把命令转述给二级子会话(提示词链两跳),任何一跳丢前缀,setup-test-db 就对共享 schema DROP+CREATE——症状是兄弟 lane 随机红测,烧 testGate attempt 与仲裁预算甚至假 halt。最具毁灭性的操作(DROP)需要确定性兜底,不能只靠提示词。 - **落点**:Task 8 Step 1(三模板 + lib 测试)、Task 9 Step 3(建 lane 后写 marker 的微步骤)。 ### 补 8:lane 库命名统一序号制(修正 D3) - **改什么**:模块级 lane 库维持 `_lane`(Task 8 Step 2 原案不变);Phase F 的 feature 级测试库由 D3 的 `__f` 改为 **`_lane_f`**(serial 主根跑 feature 并行时 N 取 0)。D3 的"每条 feature 链独立测试库"结论本身确认。 - **为什么**:moduleId 进 schema 名引入两个不必要问题——64 字符上限需要专门截断规则;截断后长前缀相同的 moduleId 撞名会重新引入互相清库,且只在特定 id 组合下出现、极难排查。另 module id 允许 `-`(/^[A-Za-z0-9_-]+$/),落进 MySQL 标识符/JDBC URL 还要引号/转义处理。序号制后缀固定 ≤ 10 字符、唯一性由「波次内 lane 序号唯一 × 模块内批序号唯一」确定性保证,与模块级口径统一。 - **落点**:D3 修正、Task 12-13(Phase F)。 ## 六、起栈类 stage 全局互斥(新增;Task 8/9) ### 补 9:stackLock——Seed 与 testGate 整段互斥 - **改什么**:新增与 withMainRootLock 同构的全局 **stackLock**(promise 链实现,确定性、无时间/随机源)。runModule 内把 **Seed stage 整段**与 **testGate 整段**(test.mjs 第 5 步 e2e 的 Playwright globalSetup 同样按固定端口冷起后端,e2e 段不可单拆则整个 testGate 包锁)各自 `await withStackLock(...)` 包进锁内——同一时刻全仓只有一条链在起栈/占端口。seedGenPrompt 与 gatePrompt 的 lane 注入同步写明:「端口探测/残留 pid 回收**只允许**在持有起栈互斥的本 stage 内执行」。Task 9 Step 3 注释写明取舍:「冷起栈资源(固定端口 + 进程树)全局唯一,相关 stage 串行化而非 lane 化」。 - **为什么**:Seed 冷起 gradle bootRun 用 config-vars.yaml 固定端口,且 seedGenPrompt:645 明确指示"起栈前按既知端口回收上一次残留 pid"——≥2 宽波次两条链并发进 Seed 时,后到者会把兄弟 lane 正在验证的后端当残留 **kill 掉**(主动互杀,每个 ≥2 宽波次必然踩中的确定性故障,不是小概率 race)。不打穿端口配置(与 D1 的复杂度论证一致),改互斥:Seed/testGate 占模块总时长比例小,串行化损失远低于打穿三层端口配置;并行收益主体(tdd/review/fix,不起栈)完整保留。tdd 内循环(Spring 测试随机端口、不起栈)不进锁。 - **落点**:Task 9 Step 1(锁定义并列处)、Task 9 Step 3(注释)、Task 8 Step 2(seedGenPrompt/gatePrompt 注入文案)。 ### 补 10:seedStageContract 同步 lane 注入(Seed 全链用 lane schema) - **改什么**:Task 8 Step 2 的 lane 注入范围从"仅 featureStageContract"改为「**所有会触达 DB 的 contract**」:featureStageContract 与 **seedStageContract** 追加同一条成品硬约束(`c.dbSchema` 渲染)——Seed 全链(setup-test-db / gradle bootRun / seed-demo-data / mysql COUNT 对账)在 lane 模式一律带 `ERP_TEST_DB_SCHEMA=` 前缀(bootRun 经补 6 的占位约定吃到 lane schema;COUNT 对账连 lane schema 查询)。Task 8 验证清单加一条 grep 审计:凡 prompt 文本含 `setup-test-db.mjs` / `seed-demo-data.mjs` 字样的 builder,必须含 lane 注入分支、或确属主根专属(行为门只在 frontend-phase 终波宽 1 主根跑,天然豁免)。 - **为什么**:Seed 是计划自己定义的"第三类 stage"(coding.mjs:580-590 注释明示不套 featureStageContract),按 Task 8 Step 2 字面注入覆盖不到它,而它恰是 DROP+CREATE/起栈/灌种子的大户——并行 lane 中按计划字面会直接 DROP 共享库。统一 lane schema 也与补 7 的 marker 兜底自洽(marker 把 lane 内一切脚本确定性指向 lane 库;若 Seed 按"锁内默认库"跑会与 marker 互相打架)——评审中"锁内段保持默认 schema、不注入 lane env"的替代子方案因此不采纳。 - **落点**:Task 8 Step 2(含验证清单新增 grep 审计)。 ## 七、执行器与穿线细节(Task 1/2/9) ### 补 11:microStepContract 显式 root 入参,仲裁在 lane 上取证 - **改什么**:microStepContract 改为 `microStepContract(root)` 显式入参;主根专属 prompts(detectDefaultBranchPromptM、milestone 微步骤等)传 ROOT;runStage/runAction 的 opts 增加 `root`(缺省 ROOT),透传 adjudicate → adjudicatePromptM,使 lane stage 失败触发的仲裁子代理在 `c.root` 上取证。Task 1 Step 2 的两份名单相应改写,消除"microStepContract 列入穿线名单(追加 c、调用方必传),而其主根专属调用方又列入保持名单(无 c 可传)"的自相冲突。 - **为什么**:lane 内 runStage/runAction 失败的仲裁按原计划钉死主根(`git -C ROOT`)——主根工作树在默认分支(或被兄弟 milestone 切换中),看不到 lane 分支上刚 commit 的 spec/plan/源码,retry/halt 裁决与 guidance 会基于错误树面作出;且名单按字面执行直接报错。 - **落点**:Task 1 Step 2/3。 ### 补 12:行为门 recordDecisions 直调收口 + 顶层 decisions 口径 - **改什么**:Task 1 Step 3 给 runBehaviorGate/runBehaviorGateOnce 穿线 c 的同时,coding.mjs:1927/:1935/:1953/:1973/:1988 共 5 处 recordDecisions 直调改为 `recordDecisions(site, list, c.decisions)`。顶层 return 的 `decisions` 改为「results 各 r.decisions 拼接 ∪ 全局 sink 残量」;loop-end 的汇总口径写明 =「flush 失败模块的 r.decisions ∪ 全局残量」(同一条决策绝不既丢失又重复)。 - **为什么**:Task 2 删除 flushedCount/decStart 水位线后,这 5 处直调仍落全局数组——frontend-phase 模块的 per-module RESUME flush 改读 r.decisions 后会漏掉全部行为门决策;顶层 return 也只剩残量,记账悬空。 - **落点**:Task 1 Step 3、Task 2 Step 1-2。 ### 补 13:锁非重入纪律(withMainRootLock 与 stackLock 同适用) - **改什么**:两把锁的定义注释钉死:「**promise 链非重入**:只允许 runModule 顶层逐段获取(`await withMainRootLock(() => runMilestone(m))`、`await withMainRootLock(() => recordResume(...))`),被包函数及其调用链内**绝不**再调同一把锁;两把锁之间也**绝不**嵌套获取」。loop-end 的两处 recordResume(:2235/:2246,波次循环已结束、本无竞态)统一也走锁——保持单一纪律避免遗忘。 - **为什么**:promise 链锁嵌套获取即互等死锁,且静默无诊断(Workflow 直接挂死);计划文字「runMilestone + recordResume 包进锁」没有说明获取层级,实现者若 runModule 包一层 + recordResume 函数体自包一层即触发。 - **落点**:Task 9 Step 1。 ### 补 14:Task 9 Step 2 伪代码修正 - **改什么**:remaining 过滤改为按波次成员集:`const waveIds = new Set(wave.map(m => m.id)); remaining = remaining.filter(m => !waveIds.has(m.id))`(**绝不**以 rs 为键反查——runtime-null 项没有 r.module,按 r.module 匹配会把故障模块留在 remaining,下轮 deps 不满足触发"图卡死降级全序"误重跑)。parallel() 调用处加注释:「结果数组与 thunk 数组按位次对齐(runtime 保证);null 仅表示 runtime 级故障(runModule 永不 throw)」。 - **落点**:Task 9 Step 2。 ### 补 15:lane 模式 milestone 前先收口 lane 树 - **改什么**:runModule 在调 runMilestone **之前**(仍在 lane 上下文)先对 `c.root` 跑 worktreeClean + recoverDirtyWorktree(branch=`module-`):in-scope 残留 commit 进模块分支、out-of-scope → halt(两个 prompt 已在 Task 1 穿线清单内,只补一个调用点)。runMilestone 自身(主根专属)的 step 1 wt-clean 保留——只管 merge 的机械安全。两个检查各管一棵树。 - **为什么**:串行模式 milestone 的 wt-clean 同时守住「merge 机械安全」与「被测树 == 被合并树」两件事;lane 模式下它只看主根——lane 里 tdd/fix 留下的未提交文件参与了 testGate(test.mjs 跑在工作树上)却不进 merge,默认分支拿到的是"没测过的子集",正确性缺口无人兜底(残留只会让 removeLaneWorktree 的 bestEffort 删除失败留 log)。 - **落点**:Task 2 Step 2(runModule 链内调用点)、Task 9 Step 1(与互斥段的相对位置:收口在 lane 上下文,先于取锁)。 ## 八、调度与种子语义(Task 4/7) ### 补 16:调度器新增"共享表"边规则 - **改什么**:schedulerPrompt 增加一条边规则:「两个模块的演示种子会触达**同一张表**(按 docs/03 的表归属 / docs/08 各模块 `路径:` 字段判定共同表)→ 必须加 fk 或 seed 边(宁串勿并),evidence 写共同表名」。 - **为什么**:演示种子 1000–9999 主键区间是**按表**隔离的;两个无依赖边的并行模块若都向同一共享表插种子,各自 lane 互看不见对方文件,PK 都从 1000 起取——合并后 seed-demo-data 逐文件 apply 撞主键(data-error),拖到行为门/e2e 才爆、归因困难。共享表本身就是不可并行信号,符合"宁串勿并"偏置。 - **落点**:Task 4 Step 2。 ### 补 17:seed NN 约定显式化(允许并行同号) - **改什么**:seedGenPrompt 在 `c.vBase > 0`(并行 lane)时条件渲染一条附注:「并行 lane 间**允许同 NN**:NN 仅表观排序,(NN, module_id) 才是唯一键;同波次模块无 seed 依赖边、互不引用,注入按完整文件名字典序应用(seed-demo-data 现状已满足)」。不做 NN 偏移预分配(良性撞号,最小代价方案)。 - **为什么**:两个并行模块从同一基点各算 NN=max+1 必产同号文件(文件名含 module id,git 无冲突、`_demo_seed_history` 按文件名记账仍确定性工作),但静默违反 prompt 自己声明的"NN=max+1 唯一"字面约定——豁免理由必须落进 prompt/注释,否则实现者各自发挥。 - **落点**:Task 7(seedGenPrompt 同文件顺带改动)。 ## 九、验证清单(全计划) ### 补 18:语法门替换(基线 node --check 恒红) - **改什么**:实测基线 `node --check workflows/coding.mjs` 恒红(node v26:`export const meta` 判 ESM → 顶层 return 报 `Illegal return statement`,exit=1)。新增确定性包裹检查脚本(如 `lib/check-workflow-syntax.mjs`:读入文件 → 剥行首 `export ` → 包进 `async function __wf(args, agent, parallel, phase, log) { ... }` → 写 `.tmp/` 临时文件 → `node --check`,透传退出码)。全计划验证清单(计划 :69/:104/:330 等)的 `node --check workflows/coding.mjs` 全部替换为该脚本。 - **为什么**:恒红的唯一确定性门会让实现者习惯性忽略红灯(掩盖本次并行化引入的真语法错),或误以为自己改坏了文件。 - **落点**:全计划验证清单、各 Task 验证步。 ## 十、裁决记录(拒绝与改判) **拒绝(1 条):** - 「lane 测试库按波次序号命名(`_lane`)与 halt 取证语义冲突,且与 D3 的 moduleId 命名法不一致」——**拒绝**。理由:(1) 波次严格串行执行,`_lane` 不存在并发撞名;halt 后整个运行即停(波次边界 break),lane 库被同号覆盖只发生在**用户主动重跑之后**,取证窗口由用户掌控,且主要取证物是保留的 lane worktree(种子/迁移/证据文件),库内数据本就"不保证存活、保证可复现"(seedGenPrompt 既有哲学);(2) moduleId 命名引入截断规则、截断撞名与 `-` 字符的 MySQL 标识符/JDBC 引号问题(见补 8),是更大的正确性风险面;(3) 命名口径已由补 8 统一为序号制,不存在"两套规则"。正确性等同下取更简单方案。 **改判说明(条目接受、其个别子建议被替代):** - lens「Seed 阶段完全不在并行隔离设计内」的"锁内段保持默认 schema/默认端口、不注入 lane env"子方案不采纳——与补 7 的 marker 兜底互相打架(marker 会把 lane 内 setup-test-db 确定性指向 lane 库);统一 lane schema(补 10)。stackLock 主张照案采纳(补 9)。 - lens「migration 版本段按 laneIdx 分配不稳定」的"docs02Idx 替代 laneIdx"与"段账本文件"子建议不采纳——补 4 的全局并集 maxV 已使 laneIdx 漂移无害,再加稳定键/账本是多余复杂度。 - lens「Task 8 Step 3 探测判据假阳性」的"test.mjs run() 拼 SPRING_DATASOURCE_URL 注入 + 探测 grep 注入代码特征"子方案不采纳——tdd 内循环不经 test.mjs,覆盖不全;改用占位约定 + 两点联查(补 6)。 - 三条「附录文件不存在」meta findings:接受,以本附录正式落盘为解决;其中对 D1 成立、D5 方向成立的核验结论已并入补 2/补 6。 --- ## 实施偏差记录 (后续实现代理在此逐条登记与计划/附录字面不符的取舍:条目格式建议「Task 编号 / 偏差内容 / 原因」。) - **Task 1 Step 2 / 穿线名单外的 4 个函数一并穿线 c**:`seedStageContract`、`behaviorReverifyPrompt`、`currentBranchPromptM`、`createFeSkeletonTagPromptM` 不在计划字面名单,但都含 `${ROOT}` 且属模块作用域(Seed 链 lane 化见补 10;后两者由穿线名单内的 runBranchSetup / runFrontendSkeleton 调用)。不穿线则 Step 4 的 grep 审计(残留必须全部落白名单)必然失败,故按审计要求穿线。 - **Task 1 Step 2 / commitBlock 的 c 接在带默认值的 tail 之后**:签名为 `commitBlock(addPath, msg, tail = '…', c)`;两处用缺省 tail 的调用点显式传 `undefined` 占位(`undefined` 触发参数默认值,`null` 不会)。保持"c 为末位参数"的约定不破例。 - **Task 3 / `if (!c.lane)` 实现为 `if (c.lane == null)`**:Task 9 伪代码的 lane 序号从 0 起(`lane: i`),`!c.lane` 会把 lane 0 误判为主根模式(竞态改全局 phase + 误走主根分支)。Phase A 串行下二者行为完全等价(lane 恒 null)。 - **Task 2 Step 2 / 补 15 的 lane 收口调用点已加进 runModule,但以 `if (c.lane != null)` 门控**:补 15 落点指定 Task 2 Step 2,但其语义只对 lane 模式有意义;串行模式下 runMilestone step 1 的 wt-clean 已覆盖同一棵树(主根),无门控会平添一次重复子代理调用、违背"纯重构行为不变"。Phase D 启用 lane 后该段自动生效。 - **Task 2 Step 1 / runAction 的 opts.dec 仅接受不消费**:ACTION_RESULT_SCHEMA 无 decisions 字段,runAction 内无 recordDecisions 可透传;按计划字面收下 dec(调用点签名与 runStage 统一),函数内注释说明现状。 - **Task 1/2 验证步 / `node --check workflows/coding.mjs` 替换为 `node lib/check-workflow-syntax.mjs`**:按补 18 执行(基线恒红已复现:node v26 对 `export const meta`+顶层 return 报 Illegal return statement)。包裹检查脚本本次新建(lib/check-workflow-syntax.mjs),`.tmp/` 已加入仓库 .gitignore。 - **Task 2 Step 3 / 真实项目 resume 对照未执行,checkbox 留空**:非交互子代理环境无真实 ERP 项目与 Workflow 运行时可跑。已用 stub agent/phase/log 对包裹后的脚本做行为冒烟替代验证:① 全 done 项目 → Router 全 skip → 返回 `{results:[],pending:[],decisions:[]}`、phase 仅 'Router';② 单 todo 模块首个微步骤 agent 返回 null → runModule 结构化返回 `{module,status:'halted',reason,decisions}`(永不 throw)、串行 phase 序列 Router,Router,Milestone、halt 后 RESUME 条目照写。真实 resume 对照留待 Phase A 收尾的人工跑批。 - **Task 2 Step 2 / RESUME flush 成败记账新增模块级 `resumeFlushed` Set**:计划的 runModule 返回形状(`{module,status,reason?,decisions}`)没有 flush 成败位,而补 12 的 loop-end 口径("flush 失败模块的 r.decisions ∪ 全局残量")需要它;用顶层 Set 记账而不扩 runModule 返回形状。附带效果:旧水位线下"模块 A flush 失败、后继模块 B flush 成功会把 A 的决策一并视为已 flush"的漏记缺陷被修复——这正是补 12 口径的要求(绝不既丢失又重复)。 - **Task 4 Step 2 / docs/02 全序的 order 边以"紧邻前驱链"形态默认输出**:计划只说"默认全保留为 order 边"未定边形态;对待跑序逐对全连接是 O(n²) 边爆炸且让松开几乎不可操作。prompt 写成:默认每个待跑模块对其 docs/02 紧邻前驱出一条 order 边(链式边传递地保留全序);松开某条链边必须对该模块与**全部前序待跑模块**逐对核查"查无 fk/api/seed 证据 ∧ docs/06 无顺序声明",任一对命中证据即对该对补对应 kind 边——语义与计划字面等价、不弱化"保留是默认"。 - **Task 4 Step 3 / scheduleViolation 增加"重复模块项"检查**:计划字面只列未知 id / 自依赖 / 环;但 deps 数组同 id 出现两项时 annotateDeps 的 Map 会取后值静默丢边,与"宁串勿并"相悖。重复项按同一违例路径处置(adjudicate retry → 仍违例则降级全序),不新增 halt 点。 - **Task 4 / runScheduler 在 todo 后端模块 ≤1 时跳过 scheduler 子代理**:此时模块间不存在任何可判边(dependsOn 只能指向 done 模块,恒满足),直接按空边集标注(`annotateDeps(todo, [], allBackendIds)`,frontend-phase 硬规则照常生效),与"空边集通过校验"完全等价,省一次取证子代理调用。 - **Task 5 / `maxWidth` 以顶层 const 提前在 Phase B 定义**:计划把 `const maxWidth = Math.max(1, ARGS?.parallel?.maxWidth ?? 3)` 写在 Task 9 Step 2 伪代码里,但 `nextWave(remaining, doneSet)` 两参签名钉死(Task 9 调用形即两参),宽度上限只能闭包取得。Task 9 实现时直接复用本 const,勿重复定义。 - **Task 4/5 验证 / 纯函数冒烟以 `.tmp/` 临时脚本替代**:coding.mjs 无法 node --test;除注释内联自证外,已从 coding.mjs 原文正则抽出 serialChainDeps / scheduleViolation / annotateDeps / nextWave 跑 22 个确定性 case(菱形/链式/全独立/环降级 + 全部违例分支 + frontend-phase 硬规则)全绿,脚本即跑即删不入库。 - **Task 8 Step 1 / scripts-test-template 不做 `DB_SCHEMA = env || db.schema` 字面改动,改为 marker→env 回填**:该模板实测不读 config-vars.yaml、自身不连库(D5 核验:schema 只被 setup-test-db / seed-demo-data 两脚本与后端 application*.yml 占位消费);按补 6/补 7 语义落为「env 缺省时读 `.tmp/erp-lane-db` 回填 `process.env.ERP_TEST_DB_SCHEMA`」,让 gradle test fork 与 e2e globalSetup→bootRun 经 env 继承吃到 lane schema(占位 `${ERP_TEST_DB_SCHEMA:<缺省 schema>}` 消费)。另:三模板在 marker 生效时各打一行留痕 log(env 前缀在命令行上自明,marker 是不可见兜底路径,指错库排查需要痕迹)——仅覆盖路径新增输出,缺省路径行为一字不变。 - **Task 8 Step 1 / docs-04 模板锁定段顺带落盘补 5 的 `out-of-order: true` 约定**:补 5 落点写明该 docs-04 改动「Task 8 同批」;与补 6 的 application*.yml 占位约定写进同一锁定块(§1.1 后),避免悬空给后续 Task 各自发挥。 - **Task 8 Step 1 / 新建 `lib/test-template.test.mjs`**:计划 Files 只列 setup-test-db 测试「+ 兄弟测试」,test 模板此前无测试文件;为断言 env/marker 真正贯通到全部子进程(D5 要害,5 个闸门步骤逐一探针)新建。占位 `{{...}}` 以探针脚本相对路径填充(模拟 skeleton-gen Plan 期填充;占位嵌在模板 JS 单引号字符串内,填充值不能带引号)。 - **Task 8 Step 1 验证 / `node --test lib/` 目录形式在 node v26 报 MODULE_NOT_FOUND,改跑等价 glob `node --test 'lib/*.test.mjs'`**:111 测试全绿(含本次新增 env 覆盖 / marker 优先级 / 贯通 case)。 - **Task 6 Step 2 / createLaneWorktreePromptM 增加第三参 defaultBranch**:计划字面签名两参,但 prompt 文本的「不存在 → `worktree add -b <默认分支>`」需要默认分支真值;由调用方传 detectDefaultBranchPromptM 的结果(runBranchSetup lane 分叉已如此接线),不让子代理自行探测 main/master——保持单一真值来源与确定性。 - **Task 6 Step 4 / halt 的 RESUME lane 路径以 halted reason 前缀实现**:runModule 返回形状(`{module,status,reason?,decisions}`)Phase A 已钉死不扩;catch 分支在 `c.lane != null` 时给 reason 前缀 `[lane 保留取证: ] `,RESUME halt 条目与终端 `⛔` 日志(都渲染 reason)即自然携带 lane 路径。lane 路径本身可由 `laneRoot(module.id)` 确定性重算,Task 10 重构图状 pending 记账时可改为结构化字段。 - **Task 8 Step 2 / lane 注入条件取 `c.dbSchema` 非空而非计划字面的 `c.lane != null`**:注入内容必须渲染成品 schema 名,`c.dbSchema` 为空时渲染只会产出坏约束(`ERP_TEST_DB_SCHEMA=` 空值前缀);Phase D lane 模式两字段同时填充(lane 必有 dbSchema,建 lane 的前置是探测通过),语义等价且对"误建 lane 未填 schema"的异常态更安全。seedStageContract 同步注入(补 10);behaviorGate*/frontendSkeleton 含 setup-test-db/seed-demo-data 字样但属 frontend-phase 终波宽 1 主根专属,按补 10 豁免口径在注释中登记。 - **Task 8 Step 2-3 / 波次执行器侧接线留给 Task 9,Phase C 先落原语**:`readDbSchemaPromptM()`(config-vars.yaml 读 schema 基底,FIELD_VALUE_SCHEMA)、`laneDbSchema(base, laneIdx)`(补 8 序号制 `_lane` 拼接)、`laneDbSupportPromptM()`(补 6b 两点联查,新增 LANE_DB_PROBE_SCHEMA:`{scriptsEnv, ymlPlaceholder, detail?}` 两布尔独立返回、合取判定与降级 maxWidth=1 留 JS 编排层)均已定义;Task 8 Step 3 checkbox 留空——其字面含波次执行器的降级动作,属 Task 9 Step 3 职责。 - **Task 7/8(顺带)/ 补 9 的 seedGenPrompt、gatePrompt 起栈互斥注入文案已随 lane 注入落盘**:文案描述编排层语义(「本 stage 整段在全局起栈互斥内执行,端口探测/残留 pid 回收只允许在持有互斥的本 stage 内做」),stackLock 本体按落点留 Task 9 Step 1;串行(c.dbSchema 空)不渲染,行为不变。补 17 的 seed NN 同号豁免按落点以 `c.vBase > 0` 条件渲染进 seedGenPrompt。 - **Phase C 验证 / 条件渲染冒烟以 `.tmp/` 临时脚本替代**(同 Task 4/5 先例):抽取 laneRoot/vBase/laneDbSchema 与 8 个受改 builder 跑确定性断言(vBase 5 组边界值、lane/串行双态渲染、`${ERP_TEST_DB_SCHEMA:...}` 占位字面量未被模板插值、worktree 三分支命令形)全绿,脚本即跑即删不入库;`node lib/check-workflow-syntax.mjs` 通过。 - **Task 9 Step 1 / 两把锁以 `makeChainLock()` 工厂统一生成**:计划伪代码为 `let mainRootLock` + 独立函数;补 9 写明 stackLock 与 withMainRootLock 同构,故以工厂去重。语义逐点等价:链式 then 排队、fn 抛错 catch 复位尾指针、调用方拿到的 run 保留原始 rejection(halt 不被锁吞)。补 13 非重入纪律全文写在工厂定义处注释。 - **Task 9 Step 2 / remaining 过滤以 `consumed` 集实现(补 14 的成员集口径 + 降级裁员细化)**:补 14 的 waveIds 按"nextWave 产出的整波"过滤,但 Step 3 的"本波次降级 maxWidth=1"会把波内未跑成员裁掉——它们必须留在 remaining 等下波,照搬整波 waveIds 会把未跑模块静默吞掉。consumed = 占用感知前移 halt 的成员 ∪ 实际执行的 wave 成员;键仍取自波次成员、绝不以 rs 反查(补 14 本意完整保留)。 - **Task 9 Step 2 / runtime-null 槽位 reason 带 lane 取证前缀并抢救 decisions**:计划字面 `reason:'runtime-null'`;实现为 `[lane 保留取证: ] runtime-null`(与 runModule catch 同口径,Task 10 的 RESUME halt 条目渲染 reason 即自带 lane 路径)。lane ctx 预构造在 thunk 外(`ctxs[i]`),null 槽位仍能回收已登记的 c.decisions 进补 12 的 loop-end 口径。parallel() 只在 ≥2 宽 lane 波次使用,前缀恒有意义。 - **Task 9 Step 2 / 串行闸(useParallel=false)不调 runScheduler**:计划行 12 要求缺省"走现行串行路径一行不差",而 runScheduler 会引入 Schedule phase 与一次取证子代理。串行直接 `serialChainDeps(todo)`(nextWave 在链式图上每轮恰出 1 个),执行语义与现行逐模块循环一致。唯一新增:占用感知微步骤按补 3b 字面("每个波次含宽 1")在串行波次也跑——宽 1 回退被残留 lane 毒化正是补 3b 的动机;无残留 lane 时为单次只读枚举、零状态变更。 - **Task 9 Step 3 / 降级路径回收本次已建 lane**:计划只说"任一失败 → 降级宽 1";若建到一半失败即降级,已建 lane 持有的 module-* 分支会让降级后的宽 1 主根 checkout 直接 fatal(降级路径自我毒化,且本波占用感知已跑过救不回)。prepareParallelWave 失败时对本次已建 lane 逐个 bestEffort 移除(刚建的树必净,移除幂等安全)再返回 null。 - **Task 9 Step 3 / 补 3a 的"残留 lane 移除失败"并入同一前移 halt 路径**:补 3a 只规定"恢复失败 → halt";恢复成功/本就净但 `git worktree remove` 失败时分支依旧被占,该模块走任何路径都会 fatal→仲裁→halt,故同按 branchSetup-dirty-worktree 口径前移结构化 halt(reason 带 lane 路径),不新增 halt 类。 - **Task 9 Step 3 / 占用感知枚举自身失败 fail-open 跳过**:补 3 未规定枚举微步骤(listLaneWorktrees)失败的处置;按"绝不因并行机制新增 halt 点"原则 log 后跳过本波感知——残留 lane 若真挡路,由 runBranchSetup 现行 fatal→仲裁→halt 语义兜底(等价不前移)。 - **Phase D 验证 / 行为冒烟以 `.tmp/` 临时脚本替代**(同前序先例):stub agent/parallel/phase/log 对包裹后脚本跑 27 个确定性断言全绿——①2 宽波次全 done(marker/lane 清理齐全)②同波次一 done 一 halted + 波次边界收敛 + pending blockedBy ③串行闸不调 scheduler/不建 lane/走主根 checkout ④runtime-null 槽位按位次记 halted ⑤lane DB 探测不支持 → 降级宽 1 后逐波推进全 done ⑥净残留 lane 收口复跑 / 脏残留恢复失败前移 halt 且兄弟照常 ⑦双链带 REQ 并行下 gate/seed 持起栈互斥零交错。脚本即跑即删不入库;`node lib/check-workflow-syntax.mjs` 通过。Task 10 的 coding-start SKILL.md 与 README 两个 checkbox 不在本次(仅 coding.mjs 部分)范围。 - **Task 11 / spec 预生成启用闸 `useSpecPrefetch = ARGS?.parallel?.features === true || ARGS?.parallel?.modules === true`**:计划未给字面闸;按"spec 预生成无物理冲突风险(batch 只写盘不 commit、文件名含 id 不相撞),可与模块并行开关解耦"自主决策——两开关任一开启即启用,前后端 featureLoop 同享。缺省(`parallel` 缺省 / `modules:false` 逃生口且未开 features)全关,维持与 master 串行等价。它**不是** Phase F 的 feature 级并行(tdd/fix 链仍严格串行,那才归 features 闸全权管辖、受 Task 14 收益闸约束)。 - **Task 11 / `deriveSpecPrompt(id, phase, c, { batch = false } = {})` 是"c 为末位参数"约定的唯一特例**:计划 Step 2 字面签名即 `deriveSpecPrompt(id, phase, c, { batch: true })`(计划字面优先于 Phase A 接口备忘的穿线约定);opts 带默认值,现有 3 参调用点无须改动。特例已在函数头注释登记,后续阶段勿仿。 - **Task 11 / 统一 commit 微步骤以 bestEffortAction 跑,失败不 halt 而是清空 specById 全量回退主链串行重跑**:计划只说"一个微步骤统一 add+commit"未定失败处置;按附录总则"绝不因并行机制新增 halt 点"——预生成写盘内容留在树上,主链 spec 重跑按 prompt 既有"已存在同 id spec 复用日期前缀/就地 Edit"规则复用产物(resume 语义同源),commit 仍失败由现行 commitBlock 的 halt 语义兜底(halt 留在现行路径,不新增类别)。微步骤自身幂等:`git diff --cached --quiet` 无 staged 变更即视为成功,绝不空 commit 报错。 - **Task 11 / 预生成加 `items.length >= 2` 与 `todo.length >= 2` 两道阈值**:计划未提阈值;单 feature 时预生成无并行收益、纯多一次统一 commit 微步骤(多烧一次子代理),直接走现行串行路径。2 项中 1 项已 done 时(todo=1)批量 tag check 结果仍被主链消费(doneById 缓存),仅 spec 不进 batch。 - **Task 11 / 批量 tag check 直用 agent() 而非 agentR,null/失败槽位不预生成**:agentR 的 throw 在 parallel() 内会被折成 null 丢失诊断;批量查失败的项留给主链 agentR 逐项重查(其 halt 语义与现行串行路径逐字一致),且不为完成态未知的项白烧 spec 子代理。 - **Phase E 验证 / 行为冒烟以 `.tmp/` 临时脚本替代**(同前序先例):抽出 featureLoop / deriveSpecPrompt / commitBlock 原文配 stub 跑 21 个确定性断言全绿——①预生成全成功(tag check 宽 3 + spec 宽 2、主链零 spec 重跑、统一 commit 含全部成功项、doneById 缓存零重查、plan/req-done 照常)②batch 单项失败落 null 回退主链串行(统一 commit 只含成功项)③统一 commit 失败 specById 清空全回退④闸关全串行(零 parallel() 调用、agentR 逐项)⑤单项不预生成⑥todo 单项不 batch 但缓存照常消费⑦deriveSpecPrompt 双态渲染(batch 无任何 commit 命令、非 batch commitBlock 原样)。脚本即跑即删不入库;`node lib/check-workflow-syntax.mjs` 通过、禁用 builtin grep 零命中。 - **Task 12 / `planFactsPromptM(planPath, c)` 带末位 c**:计划字面签名单参;plan 文件 commit 在模块分支上(lane 模式主根树面可能看不到),按 Phase A 穿线约定走 `microStepContract(c.root)`,c 为末位参数。 - **Task 12/13 / feature 测试库命名按补 8 落为 `_lane_f`,无截断规则**:D3 原案(计划/任务摘要中的"确定性截断的 `__f`")被补 8 明文改判;实现取 `c.dbSchema`(模块 lane 模式已是 `_lane`)直接缀 `_f`,串行主根 N 取 0(`laneDbSchema(base, 0)`),i = 批内槽位序号。 - **Task 13 / reviewWithFixLoop 增加尾参 opts `{ flipDocs08 = true }`**:"c 为末位参数"约定的第二个特例(先例 deriveSpecPrompt 的 `{batch}`,同因:带默认值的 opts 不动既有调用点)。动机:docs/08 相邻 REQ 行在并行 feature 分支上各自勾选,批后合并大概率同 hunk 冲突——冲突即触发"丢弃分支 + 串行重跑全链",cosmetic 副作用不配这个代价;feature lane 内跳过勾选,批后收口在模块分支统一补落(仍在 approve 之后、tag 之前,语义不变)。 - **Task 13 / feature 并行前置补 lane 测试库支持探测(计划 Task 12/13 未列)**:串行主根模式跑 feature 并行前按补 6b 两点联查(laneDbSupportPromptM)+ readDbSchemaPromptM 读基底,任一不满足 → 本模块全部批降级串行(log,不 halt)——并行 feature 链的 tdd 内循环测试若同连共享库会互相清库,降级判据与模块级同源。模块 lane 模式 `c.dbSchema` 非空视为波次前置已探测通过,免重查。 - **Task 13 / mergeFeatureBranchPromptM 冲突时自动 `git merge --abort`**:与 milestone 合并"冲突留树给人工"刻意相反——此处计划钉死了自动恢复路径(丢弃分支 → 回模块分支串行重跑),abort 恢复干净模块分支是该路径的前置,树面没有人工要救的东西(注释已写明取舍)。合并经 agentR:null 走既有 NULL_AGENT halt(计划只豁免"冲突不 adjudicate",不豁免 runtime 故障;null 时合并是否落地不可知,绝不盲目丢弃分支)。 - **Task 13 / 批内链失败的处置(计划未规定)按"批边界收敛"落地**:兄弟链全部跑完、成功者完成合并 + 打 tag 落地后,统一抛 `HALT feature-batch`(失败链的 feature lane 整树保留取证,reason 带 `[feature-lane 保留取证: ]` 前缀);同批合并冲突成员的串行重跑此时搁置(模块即将 halt 不再起新链,resume 由 req-done tag 缺失兜底自动重跑)。链失败是真实 stage halt(串行链同样会停),不属 fail-open 豁免之列。 - **Task 13 / 准入分批为"保 docs/02 原序的贪心连续分批"**:计划未定分批算法。无 facts / 改 schema 的 feature 自成串行项并截断当前批,**绝不跨越它重排**(后续 feature 可能依赖它的 schema/代码产出);批内成员放弃相对顺序(两两不相交 + 无 schema 改动正是顺序无关的准入前提);lane 建失败的退串成员在批后按序补跑。 - **Task 13 / 模块分支名取 `currentBranchPromptM(c)` 的 HEAD**:featureLoop 不接收 module 形参(Phase A 签名钉死 `featureLoop(items, phase, c)`),feature 分支的基分支与合并目标取 c.root 当前 HEAD(runBranchSetup 已确保在 `module-` 上),不另起约定;读取失败 → 全部批降级串行(fail-open)。 - **Task 13 / feature lane 路径约定 `<模块工作树根>-feats/`、feature ctx 共享模块 decisions**:路径由 feature id 确定性导出(模块 lane 模式下落在 `-lanes/` 前缀内会被 listLaneWorktrees 枚举,但其分支为 feat/*,占用感知只按 module-* 匹配,互不干扰)。feature ctx 的 decisions 直接引用 `c.decisions`(单线程事件循环 push 原子),RESUME flush 与补 12 口径零改动;vBase 透传仅保字段语义一致(准入已排除 schema 改动,tdd 不取号)。 - **Task 13 / 执行序改变仅在 features 闸开启时生效**:plan 全集先行(先串行跑完全部 spec+plan 再提取 facts 分批)只在 `useFeatureParallel ∧ phase==='backend' ∧ items.length>=2` 生效;闸关 / FE phase / 单 feature 维持现行逐 feature 交错链。spec/plan/tdd/verify/review/tag 调用点抽为 featureLoop 内共用闭包(isDone/ensureSpec/runPlanStage/runImplChain/tagDone),并行批与串行链 site/label/allowContinue/dec/root 口径同源一字不差。 - **Phase F 验证 / 行为冒烟以 `.tmp/` 临时脚本替代**(同前序先例):stub agent/parallel/phase/log 对包裹后脚本跑 30 个确定性断言全绿——①并行批 happy path(plan 全集先行、改 schema 项不进批、feature lane 注入 `erp_lane0_f0/_f1`、合并→勾 docs/08→tag 收口有序、lane 内跳过勾选、清理 lane+删分支)②合并冲突丢弃重跑(tdd 二跑落模块树、重跑通过才打 tag)③lane DB 不支持全批降级串行(探测惰性缓存单次)④批内链失败批边界收敛 halt(兄弟先合并落地、失败 lane 保留取证、不起后续串行项)⑤闸关串行等价(零 facts/零 feature 微步骤、逐 feature 交错)。脚本即跑即删不入库;`node lib/check-workflow-syntax.mjs` 通过、禁用 builtin grep 零命中、`${ROOT}` 审计残留全部落主根专属白名单。 ### 终审遗留修复(F1–F11) - **F1 修复**:scheduleViolation 增加 deps 完整性校验——todo 后端模块必须在 deps 恰好逐一覆盖,缺项即违例(处置同其它违例:adjudicate retry → 降级全序,不新增 halt 点);annotateDeps 的 `|| []` 保留为防御并注明完整性校验后理论不可达(仅 0/1 模块免调度的空边集路径会走到,语义恰为"无依赖")。 - **F2 修复**:featureStageContract 的 lane 注入段(c.dbSchema 非空分支)追加「lane 内禁全量测试入口」硬约束——tdd/verify/fix 只允许目标化测试命令(gradle 按类/按模块过滤、vitest 过滤模式),禁止 `node scripts/test.mjs` / `pnpm e2e:ci` 等含 e2e/冷起全栈的全量入口(固定端口起栈击杀兄弟 lane),全量回归只发生在 test-gate(编排层起栈互斥保护)。 - **F3 修复**:runScheduler 自 scheduler agent 调用起整体 try/catch,catch 路径 log 后降级 serialChainDeps——fail-open 不再只依赖 agent() 永不 reject 的契约,runtime 抛错不会让 Workflow 顶层崩溃。 - **F4 修复**:phase('Schedule') 移到「todoBackendIds ≤ 1 早退」分支之后——todo 为空 / 仅 1 个后端模块时完全不调 scheduler 也不切 Schedule phase,resume 全 done 项目的 UI 终态不再从 Router 漂移。 - **F5 修复**:schedulerPrompt 共享表边规则(规则 5)补方向约定——按 docs/02 § 二顺序**后者依赖前者**的单向边,明令禁止双向各输出一条(成环白白损失并行度)。 - **F6 修复**:createLaneWorktreePromptM / createFeatureWorktreePromptM 第 1 步改精确匹配语义——按 `git worktree list --porcelain` 行解析 `worktree ` 字段**全等于** path 才算已注册,明令禁止子串包含判断(id 互为前缀的误匹配)。 - **F7 修复**:microStepContract 的「所有 git 命令必须用 git -C 」补例外说明——除非步骤明示了其它 `-C` 目标(lane / feature worktree 路径,以步骤原文为准),消除与 lane prompt 内 `git -C ` 步骤的字面冲突。 - **F8 修复**:新增顶层 `laneEnvCache`(null=未探测 / {fail} / {baseSchema})缓存 lane 库 schema 基底 + lane DB 支持度两点联查——首个 ≥2 宽波次探测后复用,成功失败都缓存;agentR 抛错(瞬时 runtime 故障非环境事实)不缓存留下波重探;maxV 不入缓存每波重读。featureBatchRun 侧已有 featPrep 惰性缓存,不在本项范围。 - **F9 修复**:laneOccupancySweep 的枚举步改直调 agent() 判空——null 安静 log 后 fail-open 跳过本波感知,不再经 agentR(其 null 路径先打 ⛔ HALT 再被 catch 吞掉,稀释「⛔=真 halt」约定);模块级恢复/移除仍走 agentR(那是真结构化 halt)。 - **F10 修复**:currentBranchPromptM 输出段 `BRANCH_NAME_SCHEMA` 笔误改为实际下发的 `DEFAULT_BRANCH_SCHEMA`。 - **F11 修复**:spec 预生成 runStage 的 dec 改每项临时 sink(specDecsById),仅当该项 specById 成果被主链 ensureSpec **实际消费**时并入 c.decisions(delete 防二次并入,不重复打 log);thunk 失败落 null 与统一提交失败清空 specById 时连 sink 一起丢弃——主链重跑会重新登记,同一 stage 决策不再记两遍。