Commit 2337cc32d3fc831021cedd5780384c6f98f3c5b2
1 parent
d4b9e53d
fix(coding): 行为门 runner 的 status 必须推导得出,禁止带默认值声明
实跑暴露:行为门子代理生成的一次性 run.mjs 把 status 初始化为 'red' 后再未 赋值,恒红且不带任何分类信号。本轮靠子代理自己察觉才没误判,但 r1~r3 同样 失真——任何依赖该字段做判定的地方都会恒判红,且会撞上 runBehaviorGate 的 「red 无分类原因」守卫,被判 HALT behavior-red-unclassified。 根因在插件侧:behaviorGatePrompt 只说 runner 要「透传结构化结果」,从未规定 status 怎么算,生成方式完全放任。 契约补四条: - status 必须由信号数组末尾一次性推导,判据与「输出」节逐字一致 - 变量初值用 null 或直接末尾 const,禁止 let status = 'red'/'green' 这类带 默认值的声明(恒红比恒绿更隐蔽,看起来像门在严格把关) - runner 输出前自检:red 必须有至少一条分类信号能解释;自检不过则非零退出, 不要输出无法解释的 red - 子代理返回的 status 必须等于 runner 自检后的值,不得手工改判;不一致是 runner 的 bug,修 runner 重跑 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Showing
1 changed file
with
8 additions
and
0 deletions
workflows/coding.mjs
| ... | ... | @@ -997,6 +997,14 @@ function behaviorGatePrompt(feItems, behaviorRound, attempt, c) { |
| 997 | 997 | ? `本次是本阶段首轮首次 → 先删除整个 \`${c.root}/.tmp/behavior-gate/frontend-phase/\` 目录(清掉历史残留 runner/种子),再新建本轮子目录 \`${tmpDir}/\`。` |
| 998 | 998 | : `本次 behaviorRound=${behaviorRound} → 仅删除/清空本轮子目录 \`${tmpDir}/\`(幂等,不动其它 round 的临时残留),再新建。`}用确定性、跨平台方式删除(如 \`fs.rmSync(path, { recursive:true, force:true })\` 后 \`fs.mkdirSync(path, { recursive:true })\`),**仅限上述受控路径**,绝不删 \`.tmp/behavior-gate/\` 之外的任何路径。`, |
| 999 | 999 | `- 你在 \`${tmpDir}/\` 写一个一次性 runner(如 \`run.mjs\`),用 spawn 起进程树、轮询就绪、\`finally\` 中 **kill 本门起的全部子进程**并透传结构化结果。**绝不**让前台 gradle bootRun / vite 挂死会话——它们永不退出,必须 spawn 到后台进程树 + 轮询健康端点 + 跑完 teardown。`, |
| 1000 | + // runner 的 status 必须是**算出来的**。曾实际发生:生成的 run.mjs 把 status 初始化为 'red' | |
| 1001 | + // 后再未赋值,恒红且不带任何分类信号——若子代理照单全收,上层 runBehaviorGate 的 | |
| 1002 | + // 「red 无分类原因」守卫会把它判成 HALT behavior-red-unclassified。故把推导规则写死在契约里。 | |
| 1003 | + `- **runner 的 \`status\` 必须由信号数组末尾推导,绝不允许"初始化成某个字面量再指望后续覆盖"**:`, | |
| 1004 | + ` - 先收集 \`interactionFailures\` / \`textIssues\` / \`styleIssues\` / \`coverageGaps\` / \`envError\` 与覆盖计数;**最后**一次性计算 \`status\`,判据与下方「输出」节的定义逐字一致。`, | |
| 1005 | + ` - 变量初值用 \`null\`(或干脆不声明,直接在末尾 \`const status = ...\`)。**绝不**写 \`let status = 'red'\` / \`= 'green'\` 这种带默认值的声明——那样一旦推导分支漏掉赋值,默认值会静默流到输出,而且恒红比恒绿更隐蔽(看起来像"门在严格把关")。`, | |
| 1006 | + ` - runner 输出前**自检**:\`status\` 必须 ∈ {green,red};若为 \`red\`,必须至少有一条 \`interactionFailures\` / \`textIssues\` / \`styleIssues\` / \`coverageGaps\` 或一个非 \`none\` 的 \`envError\` 能解释它。自检不过 → runner 以非零码退出并打印诊断,**不要**输出一个无法解释的 red(上层守卫会把它当未分类红灯,走仲裁甚至 halt)。`, | |
| 1007 | + ` - 你返回给上层的 \`status\` 必须**等于 runner 自检通过后算出的那个值**,不要另行改判;runner 结果与你的观察不一致时,那是 runner 的 bug,修 runner 重跑,而不是在返回值里手工覆盖。`, | |
| 1000 | 1008 | `- **确定性端口/pid 回收前置**:起栈前先按既知端口 + \`${tmpDir}/*.pid\` 强制回收上一 attempt 残留(编排层 + runner 双保险);端口先探测占用,占用则回收或退到动态空闲端口 + 把 baseURL 注入下游。`, |
| 1001 | 1009 | `- \`${c.root}/.tmp/behavior-gate/\`(含子目录)已被仓库 \`.gitignore\` 忽略,是唯一临时写区;跑完即弃,只提交证据报告 + assets。`, |
| 1002 | 1010 | '', | ... | ... |