# 前端行为验收并入 reviewWithFixLoop(v2 最终设计:per-FE + fix 循环) > 状态:可实现(ready-to-implement),含 3 项实现前置依赖。 > 上游:本设计取代 `docs/design/2026-06-02-frontend-behavior-gate.md` 的「阶段级末尾只读 halt 门」形态。 > 运行时红线(不可违反):禁用 time/random builtin(`Date.now()` / `Math.random()` / `new Date()`);顶层 `return` 是结果通道;`agent/phase/parallel/log/adjudicate/recordDecisions` 是注入全局;**后端 featureLoop 分支逐字不变**。 --- ## 0. 用户拍板的方向(不可推翻) - 行为验收**并入 per-FE reviewWithFixLoop**,与静态 code-reviewer 并列为另一个验收维度。 - 行为发现的硬问题**可 fix**(带 locator 的 must-fix),驱动 fix→重验循环,不再 halt。 - **仅前端 FE** 有此维度;后端 REQ 分支(无 UI)逐字不变。 - 接受**每个 FE 起一次(或少数几次)全栈**的代价。 本设计在守住上述方向的前提下,落实了 5 维评审里 **确凿的 blocker**:中途可构建性(头号)、起栈成本笛卡尔积爆炸、起栈不可跨子会话复用、locator 不可靠降级=放行、缺 locator 硬问题被现有 filter 静默吞、删阶段门后 report 失去绿前置锚点、测试库护栏只在 LLM 层。 --- ## 1. 关键架构决策:行为验收是 reviewer-approve 的「approve 子门」,不是每轮都起栈 这是本设计相对「种子设计」最重要的调整,一次性化解 3 个 blocker(成本笛卡尔积 / 不可复用栈 / 中途构建脆弱性被乘以 N×round)。 **原种子设计**:每个 review round 的 step2 跑一次行为验收、fix 后 step5 再跑一次 → 单 FE 最坏 `REVIEW_HARD_ROUNDS(10) × 2 = 20` 次全栈起栈,N 个 FE 串行 → N×20,墙钟在最坏路径不收敛。 **v2 决策**:把行为验收从「每个 review round 内」**解耦**为「**reviewer 即将 approve 时才触发的 approve 前置子门**」: ``` reviewWithFixLoop(FE): ┌─ 静态 review→fix 循环(与现状几乎不变;后端逐字不变) │ round 1..N: reviewer 判 → request-changes 则 filter locator must-fix → fix → reverify(功能测试) → 再 review │ └─ 当某轮 reviewer 判 approve(现 1386 分支)→ 不立即 return,先进【行为 approve 子门】: behaviorSubGate(FE): for behaviorRound = 1..BEHAVIOR_FE_MAX(=3): 跑一次 per-FE 行为验收(runBehaviorGateOnce,内含 envError attempt 重试) ├─ envError / 空覆盖 → 内部 attempt 重试;确定性失败(build-failed) → 记 coverageGap 短路,不 retry 不 halt ├─ behaviorHard(interactionFailures + sentinel textIssues)为空 → 子门 green → break └─ behaviorHard 非空: · 有 locator 的 → 合并进 fixPrompt 的 issues,跑 fix(runStage) · 软文字(i18n/literal/semantic) → adjudicate(continue 记 decisions / retry / halt),永不阻断 approve · 无 locator 的 behaviorHard → adjudicate(allowContinue:false)(retry 重判/重跑 或 halt),绝不静默丢弃、绝不 approve fix 后只重跑「本 FE 行为验收」(不必重跑功能 reverify,除非 fix 也动了功能逻辑——见 §6) 子门 BEHAVIOR_FE_MAX 轮仍未 green → throw HALT behavior-unresolved → 静态 approve ∧ 行为 green ⇒ 才 flipDocs08Checkbox + return{approved:true} → featureLoop:1344 在 return 后打 req-done(落点不动) ``` **收益**: - 每 FE 行为起栈次数从 `O(review rounds)` 降到 `O(行为 fix 轮)`,典型 **1 次**(一次过)到 **最多 BEHAVIOR_FE_MAX=3 次**。 - 静态 must-fix 反复震荡阶段**不起全栈**——只有静态已 clean、reviewer 认可后才付全栈代价,与用户「接受每 FE 起一次全栈」精确对齐(典型就是一次)。 - 中途构建脆弱性不再被 `N×round` 放大,只在每 FE 的 approve 时刻面对一次(仍需 §2 骨架占位保证可构建)。 > **为何不冲突用户决策**:用户要的是「行为验收是 reviewWithFixLoop 内的另一个验收维度、行为问题可 fix、有 fix→重验循环」。本设计完全满足:行为验收在 reviewWithFixLoop 函数内、是 approve 的合取前置、行为硬问题转 must-fix 喂 fix、fix 后重跑行为验收循环。它只是把「行为验收的触发时机」定在「reviewer approve 那一刻」而非「每个 round 顶」——这是控制流优化,不改变验收维度的存在与可 fix 性。 --- ## 2. 实现前置依赖 A(blocker):前端骨架全量路由占位阶段——保证任意时刻 app 可构建可起 **问题(已核实)**:`skeleton-gen` 只生成 docs/04 / scripts/*.mjs / tokens.css / .gitignore,**不生成任何 frontend/ 源码、router、路由占位**。frontend/ 全部源码由 coding 期 featureLoop 逐 FE 增量产出。验 FE-N 时 FE-N+1..M 的组件文件不存在 → eager import 路由表编译失败 / 共享 nav 指向未建路由 → 整 SPA 起不来,连 FE-N 页面都加载不出。per-FE 行为验收前移到「前端只建了一部分」的每一轮,直接踩头号风险。 **v2 方案(必做,不是候选)**:在 `featureLoop(frontend)` **之前**新增一个 coding 期 stage `runFrontendSkeleton(module)`,由独立子代理依据 `docs/08 §三` FE 清单 + `frontend/` router 约定一次性生成: 1. **App 外壳**(`frontend/src/App.*` + 入口 `main.*`,若不存在)。 2. **router 全量路由表**:每个 FE-NN 对应路由都声明,且**全部 lazy import**(`() => import(...)`)。未实现的 FE 路由指向一个最小占位组件 `FeStub`(如 `frontend/src/views/_stub/FeStub.vue`,渲染 `
FE-NN 占位
`)。 3. **共享布局/导航**:导航链接全部指向已声明的路由 path(不指向不存在的 path),保证任意时刻无悬空链接。 落点与时序: - 在顶层循环 `if (module.feItems.length)` 段、`phase('Frontend')` 之后、`featureLoop` 之前调用 `await runFrontendSkeleton(module)`。 - **幂等**:以 git tag `fe-skeleton-done` 或检测 router 文件存在 + 全 FE 路由已声明为 ground truth;已建则 skip(resume 安全)。子代理产出后自行 commit(沿用 commitBlock 习惯)。 - FE-N 实现时(tddPrompt),把对应路由的占位 import 替换为真组件——这要求 **tddPrompt 前端分支补一句**:「若 router 中本 FE 路由仍指向 `FeStub`,实现完成后把该路由 import 改为本 FE 真组件」(属 frontend/ 路径内,不破坏护栏)。 > **为何这是根因解**:让 router 始终 lazy + 占位齐全 → 任意时刻 `vite build` / dev server 可起、每个 FE 路由可达 → 把「中途起不来」从高频降为罕见 → per-FE 行为验收的 flake/误判面收敛、`build-failed`(依赖 B)成为罕见兜底而非常态。 > **Plan 期 vs coding 期**:放在 coding 期(而非改 skeleton-gen)的理由——FE 清单在 Plan 末尾才稳定、且骨架要落 frontend/ 源码属 coding 范畴;放 coding 期不触碰已锁定的 Plan 闸门,且能用 git tag 幂等。这是新增一个 Plan/coding 边界 stage 的代价,已被本设计接受并显式声明。 --- ## 3. 实现前置依赖 B(blocker):BEHAVIOR_GATE_SCHEMA 增 `build-failed` kind + 确定性短路控制流 **问题**:中途构建失败是**确定性编译错误**(缺组件 / import 解析失败 / 类型错),但 `envError.kind` 枚举只有 `port-conflict/stack-not-ready/seed-error/auth-failed/timeout/none`。门面对 `Cannot find module ./views/FE-07.vue` 只能误归类:归 `stack-not-ready` → retry 空转到耗尽后 HALT(编译错永不自愈);归 `interactionFailures` → 假 must-fix 污染源码。两条路都失败。 **v2 方案**: 1. `BEHAVIOR_GATE_SCHEMA.envError.kind` 枚举**新增 `build-failed`**(确定性失败语义;`route-not-buildable` 不单列,统一用 `build-failed` + detail 区分)。 2. **控制流**(在 per-FE 行为门 helper 内):`build-failed` **既不 retry 也不 halt**——记 `coverageGap`(reason 新增枚举 `build-failed-sibling-unimpl`)+ recordDecisions,**本轮行为门视为「本 FE 行为维度无法判定但非本 FE 缺陷」直接放行 approve**(因为它是「后续 FE 未实现」的预期中途态,不是 FE-N 的 bug;§2 骨架占位让这种情况罕见,一旦发生说明占位未覆盖,留证据供人工)。 3. `behaviorGatePrompt`(per-FE 版)step0/step2 **明确归因指令**:先 `build` / 起 dev server;若失败,先用 `git` / `Grep` 判断报错根因文件路径—— - 落在**非本 FE 的 frontend/ 路径**(兄弟 FE / 占位未覆盖)→ 判 `envError.kind="build-failed"`(预期中途态)。 - 落在**本 FE 路径** → 才可能是本 FE 引入的真构建 bug → 归 `interactionFailures[kind="js-error"]` 或带 locator must-fix。 > 没有这层「确定性失败短路 + 根因归属」,per-FE 行为门无法落地——这是把行为门从「全 FE 已建的安全环境」迁到「部分 FE 已建的敌对环境」的必须保障。 --- ## 4. 实现前置依赖 C(blocker):FE-NN → 路由 path 确定性映射,锁进 spec 产物 **问题**:per-FE 只验「本 FE 关联路由」,但 FE→路由关系当前只在 spec 顶部「关联原型」散文 + 子代理对 router 的 Grep 推断,**无结构化真值**。推窄→漏验(假绿);推宽→把别的未实现 FE 的死控件算到本 FE(误 must-fix)。且若 router 全量声明(依赖 A),`routesPlanned = router 全部路由` 会让覆盖率分母被未建路由污染。 **v2 方案**: 1. **deriveSpecPrompt 前端分支**强制在 spec 头部产出结构化小节(不只是散文): ``` ## 行为验收作用域(per-FE 行为门唯一断言依据) - 关联路由: [/orders, /orders/:id] - 负责控件白名单: [data-testid 约定 或 page+DOM 选择器清单] ``` 并要求 fe-feature-review(code-reviewer)校验该小节存在且与 router 配置一致(缺失 / 不一致 → request-changes)。 2. **behaviorGatePrompt 改为接收「本 FE 路由清单 + 控件白名单」入参**(per-FE 版必需): - `routesPlanned` **只数本 FE 关联路由**(不是 router 全部),未建兄弟路由既不计入分母也不计 coverageGap。 - 行为门只对白名单内控件判 must-fix;白名单外控件 / 共享控件若属其它未 approve FE → 归 `coverageGap`(reason `deep-control-not-driven` 或 `build-failed-sibling-unimpl`),**绝不**归本 FE 的 interactionFailure。 3. **空覆盖兜底保留**:`routesReached==0 || controlsEnumerated==0`(针对本 FE 路由子集)仍归 envBlocked,绝不静默判 green。 > 没有这个确定性映射,per-FE 路由作用域无法界定,覆盖率与归因全失真。 --- ## 5. 实现前置依赖 D(blocker / 安全):测试库护栏下沉到 setup-test-db.mjs 模板自身 **问题(已核实)**:`scripts-setup-test-db-template.mjs` 只校验 schema 是合法标识符(`/^[A-Za-z0-9_$]+$/`),**不判它是不是测试库**,DROP+CREATE 无条件执行。测试库命名护栏(库名须含 `test/_test/_dev/_local`)当前只在**门子代理生成的 runner 内**(LLM 级 prompt 检查)。per-FE × per-behaviorRound 反复 DROP+CREATE,任一轮子代理漏写护栏 → 对 config-vars 指向的库(可能=开发库)无条件 DROP,反复次数越多撞上漏写的概率越高。真实数据销毁风险。 **v2 方案**:把测试库命名护栏**下沉到 `setup-test-db.mjs` 模板**(确定性 JS 边界,不依赖每个子代理记得复述):在现有标识符校验后追加—— ```js // 测试库命名护栏:DROP+CREATE 只允许作用于明确的测试/本地库,防误删开发/生产库。 const ALLOW = process.env.ALLOW_NONTEST_DROP === '1' if (!ALLOW && !/(^|_)(test|dev|local)$|(^|_)test_|^test_/.test(DB_SCHEMA) && !/test|_dev|_local/.test(DB_SCHEMA)) { console.error(`[setup-test-db] 拒绝:schema=${JSON.stringify(DB_SCHEMA)} 不像测试库(须含 test/_test/_dev/_local),设 ALLOW_NONTEST_DROP=1 显式放行`) process.exit(1) } ``` (具体正则以实现为准,语义=库名须含 `test`/`_test`/`_dev`/`_local` 之一,否则 fail-closed。) - 这样不论被行为门调用多少次都安全。 - coding.mjs 行为门控制流里,对「测试库护栏触发的红」**保持不重试不仲裁直接 throw** 的硬边界语义(与现 v1 一致,见 behaviorGatePrompt step2 第 1 条)。 - 这是 skeleton-gen 模板的一次性改动,**不属于 coding.mjs 改造**,但列为本设计前置(否则反复起栈的安全暴露面不可接受)。 --- ## 6. reviewWithFixLoop 改造后的逐轮控制流(实现级) **仅 `phase==frontend` 改造;`fe=isFrontend(phase)` 现已存在(1373),后端分支逐字不变。** ### 6.1 数据流:两类 must-fix 独立来源,schema 不合并、fix 入参合并 - **review-must-fix**:reviewer 的 `REVIEW_SCHEMA.issues`,照现状 1392 的 locator filter(缺 locator 降级丢弃)。 - **behavior-hard**:行为门返回的 `BEHAVIOR_GATE_SCHEMA.interactionFailures` + `source=='sentinel'` 的 `textIssues`。 - **schema 不杂交**(采纳「schema 选型」维度的定调):行为验收**保留独立 `BEHAVIOR_GATE_SCHEMA` 返回**,不压扁进 `REVIEW_SCHEMA.issues`(否则丢失 envError / 空覆盖 / coverageGaps / source 软硬分流这三个赖以正确的区分)。**仅在喂 fix 时**把「有 locator 的 behavior-hard」降维成 `{summary, locator, severity}` 喂现有 `fixPrompt`(fix 步天然吃这形状)。即:**schema 不合并,fix 入参合并**。 ### 6.2 approve 闸(显式 AND,钉死落点) approve 出口(现 1386 `if (r.verdict==='approve')` 分支)**改为合取**: ``` reviewer.verdict==='approve' ∧ behaviorSubGate(FE) 返回 green 其中 green ≡ behaviorHard.length===0 ∧ envError∈{none, build-failed} ∧ 本FE覆盖非空(或 build-failed 短路) ``` 只有合取成立才 `flipDocs08Checkbox` + `return {approved:true}`。这保证(采纳「删阶段门」维度 blocker 的钉死): - **行为 green 是 reviewWithFixLoop 的 return 前置条件**——req-done tag 落点(featureLoop:1344)**保持不动**,语义自动升级为「静态过+行为过」。 - **无 locator 的 behavior-hard 绝不 approve**(采纳「控制流/schema」维度 blocker#1):走 adjudicate(allowContinue:false) 决定 retry(重跑行为验收/重判)还是 halt,绝不被 1392 的 locator filter 静默吞掉。 - flipDocs08Checkbox 翻转自动晚于行为 green(checkbox 纯装饰、resume 只认 req-done tag,无视觉误导)。 ### 6.3 behaviorSubGate(approve 子门)逐步 ``` async function behaviorSubGate(id, specPath, feScope): // feScope = {routes:[...], controlWhitelist:[...]}(来自 §4 spec 结构化小节) for behaviorRound in 1..BEHAVIOR_FE_MAX(=3): bg = await runBehaviorGateOnce(id, behaviorRound, feScope) // 见 §7,内含 envError attempt 重试 // 1) build-failed 短路(依赖 B):兄弟未实现 → 记 coverageGap + decisions,子门视为 green-by-skip,return passed if (bg.envError.kind === 'build-failed' && 根因在非本FE路径) { recordDecisions; return {green:true, skipped:true} } // 2) envError(其它) / 空覆盖:runBehaviorGateOnce 内部已 attempt 重试;到这里仍 blocked → adjudicate(allowContinue:false) retry/halt if (envBlocked(bg)) { adjudicate; 仍 blocked → throw HALT } // 3) 软文字:for-of 走 adjudicate;continue→recordDecisions + 加入跨轮 softPassed;sentinel→并入 behaviorHard;retry/halt 同现 processTextIssues(bg, softPassed) // softPassed 提升到 reviewWithFixLoop 顶层作用域,跨 behaviorRound 持久 // 4) behaviorHard = interactionFailures + sentinel textIssues if (behaviorHard.length === 0) return {green:true} // 5) 分流 const withLoc = behaviorHard.filter(有 locator) const noLoc = behaviorHard.filter(无 locator) if (noLoc.length) { v=adjudicate(allowContinue:false); v!=='retry' → throw HALT; else 下一轮重跑 } if (withLoc.length) { await runStage(fixPrompt(id, 'frontend', withLoc降维)) // 复用现有 fix 步 // fix 后:只重跑本 FE 行为验收(下一轮 behaviorRound);若 fix 同时改了功能逻辑,附带重跑功能 verify(见下) } throw HALT behavior-unresolved(BEHAVIOR_FE_MAX 轮仍未 green) ``` - **softPassed Set 提升到 reviewWithFixLoop 顶层作用域**(与 round 同寿命,跨 behaviorRound 持久)——直接照搬现 runBehaviorGate 的 softPassed 语义,否则文字层每轮重新消耗仲裁预算撞 ADJUDICATE_MAX。 - **行为软文字永不进 approve 闸**,只 recordDecisions(采纳「控制流/schema」维度 high#3)。 - **fix 后的功能复验**:behaviorSubGate 内的 fix 改的是 frontend/ UI 源码,可能引入功能回归。策略——fix 后先跑一次现有 `verifyPrompt` 功能 reverify(allowContinue:false,复用 runStage),红则当功能回归(与现 reverify 同级硬边界),绿后再重跑行为验收。这把「fix 引入功能回归」纳入兜底,且功能 reverify 是 scoped 组件测试(不起全栈),成本低。 ### 6.4 轮次预算与计数(二维,钉死防证据覆盖) - 静态 review/fix 仍用 `REVIEW_SOFT_ROUNDS=5` / `REVIEW_HARD_ROUNDS=10`(现状不变)。 - 行为子门独立、更小预算:**新增 `BEHAVIOR_FE_MAX=3`**(每 FE 行为 fix 轮硬上限;超限 throw HALT)。**不**复用 review 的 10 轮驱动起栈,**不**让 `REVIEW_HARD_ROUNDS × BEHAVIOR_GATE_PASS_MAX` 隐式相乘到 120 量级。 - **runBehaviorGateOnce 内部**的 envError attempt 重试用独立小预算(沿用 testGate 的 attempt 1→2 + ADJUDICATE_MAX 思路;§7)。 - **二维计数表**(采纳「控制流/schema」维度 medium#5): - `behaviorRound`:approve 子门内的行为 fix 轮(1..BEHAVIOR_FE_MAX)。 - `attempt`:单次 runBehaviorGateOnce 内的环境 race 重试序号。 - 证据文件名用复合编号:`--behavior-r-a.md`,每次起栈独立证据不互相覆盖、不丢 flake 信号。 - **单 FE 行为起栈次数硬上界** = `BEHAVIOR_FE_MAX(3) × 每轮 attempt 上限(≤2 + ADJUDICATE_MAX 内)`,量级远小于种子设计的 10×12。典型一次过 = 1 次起栈。 ### 6.5 contract 严格分离(采纳「控制流/schema」维度 medium#7) 同一 FE 循环内不同 stage 各自 contract,**绝不混用**: - fix / review / verify stage:套 `featureStageContract('frontend')`(硬护栏:命中 backend//sql//scripts/ 即越界硬停)。 - 行为验收 stage:**仍独立套 `behaviorGateContract()`**(作用域例外:允许运行 setup-test-db / 起后端 / 跑 sql 种子 / 跑 playwright;唯一可写 `.tmp/behavior-gate//...` + 证据)。 - behaviorGateContract **新增一条中途态豁免**(采纳头号维度 low#6):「本门在 per-FE 模式下运行,frontend/ 中本 FE 之外的路由/组件可能尚未实现属预期;遇到指向未建路由的链接/404/编译缺件,一律记 coverageGap 或 envError.kind=build-failed,绝不归为本 FE 的 interactionFailure。本 FE 路由清单是唯一断言作用域。」 --- ## 7. per-FE 行为验收子代理(runBehaviorGateOnce + behaviorGatePrompt per-FE 版)要点 **runBehaviorGateOnce(id, behaviorRound, feScope)**:保留现 runBehaviorGate 的失败分层(不推倒重写——采纳「删阶段门」维度 high#4),但 scope 缩到单 FE: - **复用现 enforceEnv 思路**做内部 envError attempt 重试 + 空覆盖兜底(attempt 1→2,仍异常经 adjudicate(allowContinue:false) retry/halt)。 - 返回 `BEHAVIOR_GATE_SCHEMA`(含本 FE scope);把「interaction/sentinel 仍非空」作为「本轮未过」返回给 behaviorSubGate 外层。 - **不在 runBehaviorGateOnce 内嵌 BEHAVIOR_GATE_PASS_MAX 的多次 rerun 收敛**(那是阶段级单次门的设计);交互/文字层 retry 限制为每 behaviorRound 至多 1 次重起,收敛靠外层 behaviorSubGate 推进(采纳成本维度 medium#6)。 **behaviorGatePrompt per-FE 版**(由整 app 阶段级改造): - `id` 入参从写死 `frontend-phase` 改为本 FE id;新增入参 `specPath` / `behaviorRound` / `attempt` / `feScope`。 - **起栈**:runner 自起后端+前端(项目无既有 e2e webServer/playwright.config——已核实 F1,**删除「复用既有 webServer」这条死路暗示**,避免实现者照已证伪的假设做;只走「冷起栈」,**明确写死 round 间不复用运行栈、无 HMR**,这是现运行时硬约束,采纳成本维度 blocker#2)。 - **四段时序不变**:空库→起后端等 Flyway 建 schema+健康就绪→sentinel 种子(FK 有序)→起前端 headless。测试库护栏现由模板兜底(§5),runner 仍可复述但不再是唯一防线。 - **step0/step2 build 归因**(依赖 B):先 build / 起 dev server,失败按根因路径归 `build-failed`(非本 FE) 或本 FE 真 bug。 - **step1 路由真值**:`routesPlanned` 只数 `feScope.routes`(本 FE 路由),不数 router 全部(依赖 C)。 - **枚举**:只驱动 `feScope.routes` + `feScope.controlWhitelist`;非白名单 / 共享未 approve FE 控件 → coverageGap,不归本 FE。 - **行为硬问题带源码 locator**(采纳 locator 维度 blocker#1 的拆分): - A 类(可经 route→router 配置→view 组件文件反查到**组件级文件路径**):locator = 「组件文件 + DOM 选择器 + 失败 kind + 期望端点/期望 sentinel 值 + 实际渲染值」。**fixPrompt 放宽**:locator 允许「文件 + DOM 描述」而非强制 file:line,由 fix 子代理在该组件内 Grep 定位 handler/绑定。 - B 类(连组件文件都反查不出):**不静默降级为放行**——归 coverageGap 并**计入未覆盖**,使 behaviorSubGate 不能判 green(降级≠放行)。或归 envError(stack-not-ready) 走 retry。 - **起栈强制 dev/source-map 模式**,runner 注入定位辅助(`data-testid` 约定 / Vue `__file`),把 page+selector 映射到组件文件作为契约前置。 - **binding-garbage / sentinel-mismatch**:locator 除组件文件外,附带 DOM 路径 + 绑定文本片段 + 期望 sentinel + 实际渲染值(写进 summary,不依赖 file:line),供 fix 在组件内 Grep 该绑定表达式。 - **临时件隔离 per-FE×per-behaviorRound**:`.tmp/behavior-gate//r/`(采纳「删阶段门」维度 medium#5);每轮跑前清空本子目录,runner `finally` 必须 kill 本 FE 起的全部子进程并按本 FE 端口集回收;FE 间起栈端口先探测占用 + 动态回退。每次 coding-start 首个 FE 行为验收前清一次 `.tmp/behavior-gate/` 整目录入口(去跨 resume 串味)。 - **证据**:`docs/superpowers/reviews/--behavior-r-a.md`(与 review 报告同目录);截图归档到版本管理的 assets。 - **确定性端口/pid 回收前置**(采纳安全维度 high#3):起栈前先按既知端口 + `.tmp/*.pid` 强制回收上一 attempt 残留(编排层 + runner 双保险),对反复 port-conflict 设独立硬上限直接 halt 提示人工清理,避免连环 retry 烧时间。 --- ## 8. 删除 / 改写的阶段级门引用(实现级清单) ### 8.1 删除(顶层 frontend 段) - 删 `phase('Behavior')`(1644)+ `await runBehaviorGate(module)`(1645)。 - `runBehaviorGate`(1465-1582)**改造为** per-FE 的 `runBehaviorGateOnce` + `behaviorSubGate`(被 reviewWithFixLoop 调用),**不推倒重写**——保留 enforceEnv / 空覆盖兜底 / interaction 分层 / 软文字 source 分流 / softPassed 语义,只把 scope 缩到单 FE、把「硬问题转 must-fix locator」作为新增出口。 - `meta.phases`:**彻底删除 `{ title: 'Behavior' }`**(12 行)——行为验收并入 Frontend phase 内,所有行为相关 `agent()`/`adjudicate()` 的 phase 入参从 `'Behavior'` 统一改为 `'Frontend'`(与 reviewWithFixLoop 现有 grp 一致)。不保留 'Behavior' 作 UI 分组(否则成无 `phase()` 驱动的孤儿)(采纳「删阶段门」维度 medium#3)。 ### 8.2 reportPrompt 前端分支改写(采纳「删阶段门」维度 high#2 + 控制流维度 medium#6) - **删/改 1096 绿前置**:原 Glob `frontend-phase-behavior-gate-r*.md`「最后一份非 RED」整条**删除**(阶段级文件已不再产生,留之必断链或不确定 halt)。改为:**对每个 `req-done/` tag 视为行为已过**(因 per-FE 行为 green 已是 req-done 前置,report 不必再独立校验行为绿,避免双真值)。可选加一句轻量校验:每个 FE 存在对应 `--behavior-r*-a*.md` 证据且最后一份非 RED。 - **改 1106 §⑤**:把 `frontend-phase-behavior-gate-r*.md` 汇总改为按 per-FE 证据目录 `docs/superpowers/reviews/-FE-*-behavior-r*-a*.md` 汇总 flake / 环境 race / 文字 continue。 - **改 1107 §⑧**:偏离清单的 behavior-gate coverageGaps / textIssues continue / 逐控件判定 / authState 来源,从阶段级文件改为 per-FE 证据汇总。 - 注意 testGate 的 `frontend-phase-test-gate-r*.md` 绿前置(1094)**保留不动**——testGate(全量回归)不并入 per-FE 循环。 ### 8.3 保留不变 - **阶段级 testGate(全量回归 vitest+playwright)保留**(1642-1643)——职责正交,per-FE 行为验收不替代全量回归。 - 后端 featureLoop / featureLoop backend 分支 / runMilestone / runCrossModule / 顶层 backend 段:**逐字不变**。 --- ## 9. README / SKILL 文案改动 - **README**(46-49 行):把「→ testGate(frontend) → 前端行为闸 behavior-gate(…逐路由枚举…)→ runMilestone」改为:「featureLoop(前端,FE-NN,每个 FE 在 review 循环内并入 per-FE 行为验收 approve 子门:reviewer approve 时起本 FE 全栈+sentinel 种子,枚举本 FE 路由控件/文字,硬问题转可 fix must-fix→重验,行为 green 才打 req-done) → testGate(frontend,全量回归) → runMilestone」。补一句「前端骨架占位阶段保证中途可构建」。 - **coding-start SKILL**(步骤 0 横幅,26 行):把「前端行为闸 behavior-gate(…逐路由枚举:交互失效→halt,文字不符→仲裁)」改为「前端功能循环内含 per-FE 行为验收(reviewer approve 时起本 FE 全栈验『按钮真生效/文字对』,硬问题可 fix 重验,不再是末尾独立门)」。 --- ## 10. 残留风险(接受 / 已缓解) 1. **冷起栈墙钟**:round 间不复用栈是现运行时硬约束(无跨子会话常驻进程原语)。已用「approve 子门 + BEHAVIOR_FE_MAX=3」把起栈次数压到典型 1 次/FE、最坏 3 次/FE 来控成本,而非靠不可行的栈复用。每次起栈含全量 Flyway apply(随 migration 增多单调增长),计入墙钟预算。若 N 很大仍可能数小时——这是用户已接受「每 FE 起一次全栈」的直接代价,本设计已把它从 N×20 降到 N×(1~3)。 2. **locator 可靠性**:A 类(组件文件级)映射可行;B 类不可降级放行而是计入未覆盖阻断 approve。仍可能出现 fix 在组件内 Grep 不中绑定行、多轮修不中逼近 BEHAVIOR_FE_MAX 后 halt 转人工——这是「运行时 DOM→源码」固有难度的残留,已用「附 DOM 路径+绑定片段+期望/实际值」最大化命中率、用比 review 更紧的 BEHAVIOR_FE_MAX=3 控制空转成本。 3. **骨架占位覆盖不全**:若 runFrontendSkeleton 漏建某 FE 的路由占位,验该 FE 前的某个兄弟 FE 时仍可能 build-failed;已用 build-failed 短路(不 halt、记证据)兜底,但会留覆盖盲点供人工。 4. **per-FE 库状态与阶段级 testGate 隔离**:行为门入口 DROP+CREATE 自带空库语义,跑完不为 testGate 留状态(testGate 自带 setup-test-db 重置);二者共用同一物理测试库,时序上串行无并发争用。 5. **deriveSpec 的 FE→路由结构化小节依赖 LLM 正确产出 + reviewer 校验**:若两者都失误,feScope 可能不准;已让 reviewer 把「小节存在且与 router 一致」作为 request-changes 项兜底,但非确定性。 --- ## 11. 实现顺序建议 1. 前置 D(setup-test-db 模板护栏)——独立、最安全、先做。 2. 前置 C(deriveSpecPrompt FE→路由结构化小节 + reviewer 校验)——为 feScope 入参铺路。 3. 前置 A(runFrontendSkeleton 骨架占位 stage + tddPrompt 占位替换指令)——保证中途可构建。 4. 前置 B(BEHAVIOR_GATE_SCHEMA 增 build-failed + 归因控制流)。 5. 主改造(reviewWithFixLoop 加 behaviorSubGate / runBehaviorGate→runBehaviorGateOnce per-FE / 二维计数 / softPassed 提升 / contract 分离)。 6. 删除阶段级门 + reportPrompt 改写 + meta.phases 删 Behavior + phase 入参改 Frontend。 7. README / coding-start SKILL 文案。 每步后端分支必须逐字不变(diff 校验);运行时红线(time/random builtin / 顶层 return / 注入全局)每步复核。