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