2026-06-02-frontend-behavior-in-review-loop.md 31.9 KB

前端行为验收并入 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,渲染 <div data-fe-stub="FE-NN">FE-NN 占位</div>)。
  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确定性前置校验后才 green-by-skip 放行(既不 retry 也不 halt);不满足前置 = 「脏」build-failed → 过 adjudicate(allowContinue:false) retry/halt,绝不静默放行。前置(评审加固,见下「评审加固」):(a) 必须有 rootCausePath;(b) 不得同时携带交互/sentinel 硬问题。干净放行时记 coverageGap(reason build-failed-sibling-unimpl)+ recordDecisions(「后续 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-drivenbuild-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 边界,不依赖每个子代理记得复述):在现有标识符校验后追加——

// 测试库命名护栏: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 的硬边界语义现已落地为确定性机制(评审加固):门子代理在 envError.detail 以固定标记 TESTDB_GUARD_MARK[TESTDB-GUARD])开头,runBehaviorGateOnce 拿到首个结果即 behaviorTestDbGuardTripped 命中 → 立即 throw HALT,绝不进入 attempt 重试 / adjudicate(此前仅 step2 prompt 承诺、JS 无兑现,护栏红会误入 stack-not-ready 通用重试路径空转约 5 次)。
  • 这是 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覆盖非空
            ∧ 无未解释漏达路由(routesReached + 路由级 coverageGap ≥ routesPlanned) (或干净 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,内含 testdb-guard 直接 halt + envError attempt 重试
    // 1) build-failed:经前置校验后短路(依赖 B)。脏(无 rootCausePath / 携带交互|sentinel 硬问题) → adjudicate(allowContinue:false),绝不静默放行
    if (bg.envError.kind === 'build-failed') {
      if (dirty(bg)) { v=adjudicate(allowContinue:false); v!=='retry' → throw HALT; else 下一轮重跑 }
      else { recordDecisions; return {green:true, skipped:true} }   // 干净:兄弟未实现,green-by-skip
    }
    // 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 持久
    // 3.6) 覆盖率对账(确定性兜底):未被路由级 coverageGap 解释的漏达路由(routesReached<routesPlanned) → adjudicate(allowContinue:false)
    if (planned>0 && planned-reached-routeGapCount > 0) { v=adjudicate(allowContinue:false); v!=='retry' → throw HALT; else 下一轮重跑 }
    // 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 重试序号。
    • 证据文件名用复合编号:<date>-<FE>-behavior-r<behaviorRound>-a<attempt>.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/<FE>/... + 证据)。
  • 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/<FE>/r<behaviorRound>/(采纳「删阶段门」维度 medium#5);每轮跑前清空本子目录,runner finally 必须 kill 本 FE 起的全部子进程并按本 FE 端口集回收;FE 间起栈端口先探测占用 + 动态回退。每次 coding-start 首个 FE 行为验收前清一次 .tmp/behavior-gate/ 整目录入口(去跨 resume 串味)。
  • 证据docs/superpowers/reviews/<date>-<FE>-behavior-r<behaviorRound>-a<attempt>.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/<FE> tag 视为行为已过(因 per-FE 行为 green 已是 req-done 前置,report 不必再独立校验行为绿,避免双真值)。可选加一句轻量校验:每个 FE 存在对应 <date>-<FE>-behavior-r*-a*.md 证据且最后一份非 RED。
  • 改 1106 §⑤:把 frontend-phase-behavior-gate-r*.md 汇总改为按 per-FE 证据目录 docs/superpowers/reviews/<date>-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 / 注入全局)每步复核。


12. 评审加固(实现后多代理评审产出,已落地)

实现后用多代理评审(6 维 + 逐发现对抗复核)核验本设计的逻辑与目标达成。控制流(approve 合取不变量、fix 轮边界、无 stale-green、残留清除)与运行时红线(确定性 builtin / 顶层 return / 16 schema 合法)全部通过。目标维度(「每控件可用 / 每文字正确」)判为 mostly-achieves,确认了若干 escapability 缺口,其中确定性、低风险的三项已加固:

  1. build-failed 短路加确定性前置(头号 must-fix)behaviorSubGate 在 green-by-skip 前校验 (a) rootCausePath 非空、(b) 无交互/sentinel 硬问题搭车;任一不满足 → adjudicate(allowContinue:false) retry/halt,绝不凭未校验的 LLM 归因静默放行。骨架(lazy router + FeStub)令合法的兄弟未实现 build-failed 极罕见,故一个 build-failed 更可能是本 FE 真共享代码回归——这是此前 comment §107-108 声称 load-bearing 却无 JS 兜底的边界。
  2. 覆盖率对账兜底(§3.6):空覆盖此前只兜 ==0;新增 routesReached < routesPlanned 且缺口未被「路由级 coverageGap」解释时 → adjudicate(allowContinue:false),堵住「部分覆盖假绿」(只数路由级 reason,过计只抑制本门、绝不误 halt)。
  3. 测试库护栏直接 halt 落地:用 TESTDB_GUARD_MARK 标记 + behaviorTestDbGuardTripped 兑现 step2「不重试不仲裁直接 halt」承诺(详见 §5)。

未改(确认为可接受的设计取舍,留作后续):白名单/路由作用域自证(非从 live router/DOM 独立枚举)、非数据文字按 source 软分流、disabled 控件仅提交类有 should-work 复核——均为有意取舍,记于此供后续权衡。