sync: 新增 BMAD 开发流程重构 - 目录结构 modes/phases/rules + 新增自动修复引擎 + H5 不兼容组件替换方案

This commit is contained in:
Claude Code
2026-04-21 05:44:50 +00:00
parent 009d2a2c5b
commit 6b1cb8535e
18 changed files with 5594 additions and 136 deletions

View File

@@ -0,0 +1,611 @@
# 统一修复引擎Auto-Fix Engine
> **⛔ 所有模式共享**:完整模式、回归验证模式、旅程验证模式均引用此文件。任何模式不得另起炉灶重新定义修复机制。
---
## 1. 步骤完成判定标准(⛔ 每个 step 执行后强制判定)
**PASS**: `browser_snapshot` 显示的内容 **完全包含** 旅程/验证定义中该步骤"用户看到 X"的预期状态。
- 例: 预期"看到已登录状态(头像、昵称)" → snapshot 中有实际昵称文字(非占位符、非"加载中..." → PASS
**FAIL**: 以下任一 = FAIL
1. snapshot 内容与预期不符(如预期"已登录"但看到"加载中..."
2. `browser_click` / `browser_type` 无法找到目标元素(元素不存在或不可交互)
3. `browser_wait_for` 超时(页面未按预期响应)
4. snapshot 显示错误状态错误提示、空白页、404、500
5. 数据断言不通过(操作前后数据状态无变化或变化不符预期)
**⛔ 禁止判定为 PASS 的情况**:
- `"加载中..."` / `"请稍候"` / 空白页
- 元素存在但内容为空 / 占位符
- `"暂未开放"` / `"开发中"` / `"功能开发中"` 提示
- 不做 snapshot 直接假设操作成功
-`browser_evaluate` 绕过不可交互的元素后声称"操作完成"
---
## 2. FAIL 分类标准(⛔ 所有模式共享)
| # | FAIL 特征 | 分类 | 修复类型 | 示例 |
|---|-----------|------|---------|------|
| 1 | API 返回 404 且路由文件不存在 | 未实现 | AUTO-IMPLEMENT | 路由缺失 |
| 2 | API 返回 500 / 错误状态码 | Bug | AUTO-FIX | 空 body 返回 500 |
| 3 | 前端组件/按钮未渲染 | 未实现 | AUTO-IMPLEMENT | 登录入口按钮缺失 |
| 4 | 页面跳转到错误位置 | Bug | AUTO-FIX | 导航逻辑错误 |
| 5 | 页面停留在非功能态("加载中"/空白/错误态/有内容但零交互元素) | Bug | AUTO-FIX | hydration 失败 / 渲染 Bug / fetchProfile 空 catch / 响应结构不匹配 |
| 6 | 可交互元素无法通过 Playwright 操作 | Bug | AUTO-FIX | uni-input 编译后 browser_type 失败 |
| 7 | 功能提示"暂未开放"/"开发中"/"暂未实现" | 未实现 | AUTO-IMPLEMENT | 手机号登录只有占位符 |
| 8 | 测试数据过期/状态不对 | 数据问题 | AUTO-DATA-FIX | 收益冻结期不可提现 |
| 9 | 已有数据阻止测试(重复评价等) | 数据问题 | AUTO-DATA-FIX | 删除旧评价或换测试用户 |
| 10 | 正常业务规则正确拒绝无效操作 | 可接受 | 仅记录 | 最低提现金额限制 |
| 11 | 旅程定义或源码中发现 Mock 登录且手机号登录未实现 | 未实现 | AUTO-IMPLEMENT | Mock 登录不是真实用户流程,必须实现手机号+验证码登录 |
| 12 | 操作被前端表单校验拦截Toast/提示阻止提交)| 环境限制/校验 | ENV-ADAPT | 按 Toast 内容分流缺少必填→补充填写文件上传→browser_file_upload环境限制→代码添加 dev 替代 |
| 13 | 表单校验失败但页面无任何提示(无 Toast/无错误文字)| BugUX 缺陷)| AUTO-FIX | 用户不知道为什么操作无效 → 添加 Toast 或内联错误提示 |
| 14 | 即时使用数据(验证码等)仅通过 Toast 显示,无 console.log 或自动填充,下一步骤无法获取 | Bug数据丢失风险| AUTO-FIX | sendCode() 用 Toast 显示验证码但无 console.log流程卡死 |
| 15 | UI 直接输出英文枚举值(如 APPROVED/REJECTED/PENDING/PAID/CANCELLED 等)而非本地化中文标签 | BugUI 本地化缺失)| AUTO-FIX | 任何前端页面(管理后台/小程序/H5状态字段显示英文枚举而非中文 |
| 16 | 约束声明与代码行为不一致Constraint Mismatch: 旅程描述/UI 文案说"可选/可跳过/选填"但代码实际必填,或 UI 标记"选填"但接口必填 | 约束不一致 | JOURNEY-FIX / AUTO-FIX | 旅程说"可跳过上传证件"但代码 `if (!idCardFront)` Toast "请上传所有证件照片" |
| 17 | 前端代码 CSS/style 含 `url("data:image/svg+xml")``url('data:image/svg+xml')`SVG data URI 充当图标/背景/装饰) | Bug(平台不兼容) | AUTO-IMPLEMENT | 微信小程序不支持 SVG data URI。⛔ 必须按 platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss将所有 SVG data URI 替换为 Iconify 类名。**禁止用另一种 SVG data URI 替代原来的** |
| 18 | 页面同时有系统导航栏pages.json navigationBarTitleText 非 custom和自定义 fixed header导致双层标题栏 | Bug(双重导航栏) | AUTO-FIX | 截图中顶部出现两层标题(系统导航+自定义头部),参见 ./reference/platform-uni-app.md §3 |
| 19 | Tab 页面下拉刷新方式不一致scroll-view refresher / onPullDownRefresh / 无 混用),或使用列表级 scroll-view refresher | Bug(UX 不一致) | AUTO-FIX | 首页用 scroll-view refresher其他 Tab 无刷新,参见 ./reference/platform-uni-app.md §7 |
| 20 | API route handler 无 try-catch 包裹,导致未处理异常直接返回原始 500 且无日志 | Bug(健壮性) | AUTO-FIX | Admin 提现审批 PUT 返回 500 但状态仍更新(异常被吞) |
| 21 | uni-app 页面使用 `onMounted` 获取数据,但该页面是 `uni.navigateBack()` 的返回目标,导致回退时数据不刷新 | Bug(数据陈旧) | AUTO-FIX | 提现后返回收益页余额未刷新onMounted 不再触发) |
| **99** | **以上都不匹配的任何 FAIL** | **默认: Bug** | **AUTO-FIX** | **兜底规则,确保无遗漏** |
| **V1** | 页面主题/配色与原型不一致(深色→浅色、主色调偏差) | 视觉差异 | VISUAL-FIX | 原型深色主题但实际浅色;按钮颜色不一致 |
| **V2** | 页面布局结构与原型不同(顶部导航↔侧边栏、网格↔列表、卡片↔表格) | 视觉差异 | VISUAL-FIX | 原型顶部水平导航但实际左侧边栏;原型卡片网格但实际列表 |
| **V3** | 品牌标识与原型不同Logo 图标、标题文字、副标题) | 视觉差异 | VISUAL-FIX | 原型闪电图标+"VividSpark"但实际 "V" 字母+"Welcome back" |
| **V4** | 原型存在的 UI 组件在实际页面中缺失Remember me、FAQ、过滤器、标签页等 | 视觉差异 | VISUAL-FIX | 原型有 FAQ 区域但实际缺失;原型有 Remember me 但实际无 |
| **V5** | 按钮/输入框/卡片样式与原型不一致padding、圆角、边框、字号等 | 视觉差异 | VISUAL-FIX | 原型圆角 16px 但实际 8px原型按钮 padding 16/32 但实际 12/24 |
### "可接受"严格限定(⛔ 仅以下 2 种,其余一律必须修复)
1. **系统按设计正确拒绝了无效操作**(如未选协议点登录 → "请先同意用户协议"),且不影响后续步骤
2. **业务规则正确阻止了当前数据状态下的操作**(如余额不足不可提现),且该规则不应为测试目的修改
**除以上 2 种外,任何 FAIL 不得标记为"可接受"。功能缺失、代码错误、交互故障均必须修复。**
### ⛔ "已知问题不豁免"规则(⛔ 强制 — 跨轮次生效)
**核心原则**: "已知" ≠ "已修复"。任何在之前的验证轮次中被发现但未实际修复的问题,在后续轮次再次检测到时,**必须**立即触发修复,不得以任何理由跳过。
**判定流程**:
```
检测到 FAIL → 查分类表确定修复类型
读取 pipeline-state.yaml 前一次验证结果
该问题是否在上一轮已被记录pending_fixes / verification_detail 中存在)?
├── 是(跨轮次未修复)→ ⛔ 提升优先级,立即执行修复(不得再次"仅记录"
└── 否(新问题)→ 按正常分类触发修复
```
**⛔ 禁止行为**:
- ❌ 以"已知/遗留/历史/V3遗留"为由跳过修复
- ❌ 只记录到 `pending_fixes` 而不执行实际修复动作
- ❌ 将修复推迟到"旅程完成后"或"下次验证"
- ❌ 标记"已记录"作为修复动作("已记录"不是修复)
- ❌ 以"非当前 EPIC/旅程范围"为由跳过 pending_fixes 中的问题(范围外 ≠ 豁免)
**跨轮次问题升级规则**:
- 第 1 轮检测到 → 正常修复流程
- 第 2 轮仍未修复 → **强制修复**(不得延迟,不得降级为 WARN
- 第 3+ 轮仍未修复 → **阻断当前旅程**(标记 FAIL + 输出修复失败报告)
### 关键判断流程
- API 404 → `grep` 检查路由文件是否存在 → 存在 = Bug(#2) → 不存在 = 未实现(#1)
- 页面显示"暂未开放"/"开发中"/"暂未实现" → 一律未实现(#7) → AUTO-IMPLEMENT
- 元素可见但不可操作(`browser_type` 失败) → Bug(#6) → ⛔ **先诊断**`browser_evaluate` 检查 DOM/CSS → 按诊断结果分流修复(禁止直接用 `browser_run_code` 绕过)
- 页面卡在"加载中..."/空白 → Bug(#5) → AUTO-FIX调查根因空 catch、响应结构不匹配、状态管理错误
- 页面有静态内容但零交互元素(有文字但无 button/input/a/form`browser_evaluate` 执行 FAIL #5 诊断 → 按诊断结果分流(见 FAIL #5 诊断子步骤)
- 操作后页面无变化(无跳转/无反馈)→ browser_snapshot 检查 Toast → 按 Toast 内容分流(#12) → ENV-ADAPT
- 有 Toast/错误提示 → 读取内容 → 按 #12 分流(补充填写 / browser_file_upload / ENV-ADAPT
- ⛔ 无任何提示 → BugUX 缺陷:校验失败但用户看不到原因)→ AUTO-FIX添加 Toast/错误提示)
- 即时使用数据(验证码)仅通过 Toast 显示且源码无 console.log → Bug(#14) → AUTO-FIX添加 console.log + 自动填充)
- UI 显示英文枚举值APPROVED/REJECTED/PENDING/PAID/CANCELLED 等)→ Bug(#15) → AUTO-FIX添加中文映射函数适用于所有前端管理后台 statusLabel / 小程序 computed 映射)
- 前端代码含 `data:image/svg+xml`SVG data URI`grep -rn "data:image/svg+xml" app/mini-program/src/` 扫描所有 `.vue` 文件 → 发现 → 平台不兼容(#17) → AUTO-IMPLEMENT⛔ 必须按 platform-uni-app.md §1 完整安装 Iconify + 替换所有 SVG data URI**禁止用另一种 SVG data URI 替代原来的**
- Tab 页面下拉刷新方式不一致scroll-view refresher / onPullDownRefresh / 无 混用)→ Bug(#19) → AUTO-FIX参见 ./reference/platform-uni-app.md §7 统一为 onPullDownRefresh
- 无法归类的 FAIL → 默认 Bug(#99) → AUTO-FIX
- API 返回 500 + `Read` 源码发现 handler 无 try-catch → Bug(#20) → AUTO-FIX添加 try-catch 包裹 + console.error + JSON 500 响应)
- `uni.navigateBack()` 返回后 `browser_snapshot` 数据未刷新 + 源码用 `onMounted``onShow` → Bug(#21) → AUTO-FIX替换 `onMounted``onShow`
---
## 3. 真实用户完成规则(⛔ 旅程验证模式强制)
**核心原则**: 旅程中的每个用户步骤,必须是一个**仅使用鼠标/键盘/触摸屏的真实用户**能够完成的方式。
**具体规则**:
- `browser_type` 在可见 input 上失败 → Bug(#6) → AUTO-FIX
- `browser_click` 在可见按钮上失败 → Bug(#6) → AUTO-FIX
- 禁止用 `browser_evaluate` / `browser_run_code` 绕过不可交互的元素
- "需要 evaluate 才能操作" 本身就是 Bug → AUTO-FIX
**`browser_run_code` 仅允许场景**:
- 检测布局溢出等非用户交互操作
- 读取 localStorage / cookie 等数据状态验证
- 复杂的拖拽操作(`browser_drag` 不可用时)
**⛔ 如果 `browser_type` 在可见 input 上失败**:
1. 标记该步骤 FAIL
2. 分类: Bug#6 前端交互问题)
3.**诊断**: `browser_evaluate` 检查 DOM 结构和计算样式(见下方 FAIL #6 诊断子步骤)
4. 根据诊断结果选择修复策略CSS Bug / 可见性 Bug / 事件 Bug / 框架编译 Bug
5. 触发 AUTO-FIX: 修复具体根因
6. 修复后重试 `browser_type`
7. 仍失败 → 标记 journey-step-fail继续下一步骤
**⛔ FAIL #5 诊断子步骤(页面有内容但零交互元素时强制执行)**:
**触发条件**: `browser_snapshot` 显示页面有静态内容(标题/文字),但没有 button、input、a、form 等可交互元素。
1. `browser_evaluate` 检查页面状态:
```javascript
() => {
const interactive = document.querySelectorAll('button, input, a, form, [role="button"], [role="link"]');
const hasContent = document.body.innerText.trim().length > 0;
// 检测框架挂载状态
const hasNextRoot = !!document.getElementById('__next');
const hasReactRoot = !!document.querySelector('[data-reactroot]');
const hasVueApp = !!document.querySelector('[data-v-app]') || !!document.querySelector('#app')?.__vue_app__;
const frameworkMounted = hasNextRoot || hasReactRoot || hasVueApp;
// 检测 JS 是否加载
const scriptTags = document.querySelectorAll('script[src]').length;
return {
interactiveCount: interactive.length,
hasContent,
frameworkMounted,
hasNextRoot, hasReactRoot, hasVueApp,
scriptTags,
bodyHTMLLength: document.body.innerHTML.length,
bodyText: document.body.innerText.substring(0, 300)
};
}
```
2. 根据诊断结果分流:
- `interactiveCount === 0 && frameworkMounted === true` → **框架已挂载,当前页面源码就是无交互的展示页**
→ `grep/Read` 检查当前页面源码:源码无交互组件 → **功能缺失** → AUTO-IMPLEMENT添加 redirect/导航逻辑)
→ 源码有交互组件但未渲染 → **渲染 Bug(#5)** → AUTO-FIX
- `interactiveCount === 0 && frameworkMounted === false && scriptTags > 0` → **JS 加载但框架未挂载** → **Hydration 失败 Bug(#5)** → AUTO-FIX检查控制台错误、检查构建产物
- `interactiveCount === 0 && !hasContent` → **空白页** → Bug(#5) → AUTO-FIX检查加载逻辑
3. 修复后重测:
- AUTO-IMPLEMENT 添加 redirect 后 → `browser_navigate` 重试 → 应自动跳转到功能页 → `browser_snapshot` 确认有交互元素
- AUTO-FIX 修复 hydration/渲染后 → `browser_navigate` 重试 → `browser_snapshot` 确认交互元素已渲染
**⛔ FAIL #6 诊断子步骤(`browser_type`/`browser_click` 失败时强制执行)**:
> uni-app/小程序编译特性诊断(如 uni-input height:0px详见 **./reference/platform-uni-app.md 第 3.1 节**。
1. `browser_evaluate` 检查目标元素的 DOM 结构和计算样式:
```javascript
(el) => {
const tag = el.tagName?.toLowerCase();
const computed = getComputedStyle(el);
const info = {
tag, height: computed.height, width: computed.width,
opacity: computed.opacity, display: computed.display,
innerInputs: []
};
el.querySelectorAll('input').forEach(i => {
const c = getComputedStyle(i);
info.innerInputs.push({
height: c.height, width: c.width,
opacity: c.opacity, pointerEvents: c.pointerEvents
});
});
return info;
}
```
2. 根据诊断结果分流:
- `innerInputs[0].height === '0px'` → **框架编译 CSS Bug** → AUTO-FIX: 给 input 或包裹容器添加显式 `height`
- `opacity === '0'` 或 `pointerEvents === 'none'` → **可见性 Bug** → AUTO-FIX: 修复 CSS 可见性
- 无 `innerInputs`(非框架编译)→ **原生交互 Bug** → AUTO-FIX: 修复事件绑定或 DOM 结构
- 诊断结果正常但 `browser_type` 仍失败 → **Playwright 适配** → 允许 `browser_run_code` + `force: true`(仅限此场景,且须记录适配原因)
---
## 4. 修复流程
### AUTO-FIX代码 Bugmax 2 次)
**适用**: API 错误、逻辑错误、配置错误、渲染异常、交互故障、空 catch 块、响应结构不匹配
**流程**:
1. 读取失败原因,定位源文件和行号(`grep` / `Read`
2. ⛔ 如果是 FAIL #6交互失败→ 先执行诊断子步骤(见上方),按诊断结果选择修复策略
3. `Edit` 最小改动修复
4. `Bash` 重启受影响服务(如有必要)
5. 重测失败步骤 → PASS 继续 / FAIL 重试
**示例**:
- 退款 API 无 body 返回 500 → `Edit` 添加 body 解析容错
- `fetchProfile` 空 catch 吞噬错误 → `Edit` 添加错误日志 + null 处理
- API 响应结构与前端期望不匹配 → `Edit` 修正数据提取逻辑
- input 元素不可交互 → `browser_evaluate` 诊断 → 按根因修复(见 FAIL #6 诊断子步骤)
- uni-app input `height: 0px` → `Edit` 给 `.input-field` 添加显式 `height: 44px`
- 管理后台首页缺少 redirect → 源码检查发现 page.tsx 是纯展示 → AUTO-IMPLEMENT: 添加 `redirect("/login")`
- React hydration 失败 → `browser_evaluate` 确认框架未挂载 → AUTO-FIX: 检查构建产物和 `use client` 指令
- 任何前端页面状态字段显示英文枚举 → `Edit` 添加中文映射函数(管理后台: statusLabel / 小程序: computed 属性)→ 替换直接输出为映射函数调用
### AUTO-IMPLEMENT功能缺失max 2 次)
**适用**: 路由缺失、组件未渲染、功能占位符("暂未开放")、后端接口未实现
**流程**:
1. 读取 `phase-03-implement.md` 对应方案(如存在)
2. `Write` 新文件 / `Edit` 补充实现
3. `Bash` 安装依赖(如需)+ 重启服务
4. 重测失败步骤 → PASS 继续 / FAIL 重试
**示例**:
- 个人页无登录入口 → `Edit` 添加登录按钮
- 手机号登录"暂未开放" → `AUTO-IMPLEMENT` 实现完整手机号+验证码登录流程
- 后端路由缺失 → `Write` 新 `route.ts` + `handler`
- 图标方案未实施 → 按方案安装 Tailwind + Iconify
**Emoji → Iconify 替换方案(检测到 emoji 充当功能图标时使用 ⛔ 强制)**:
⛔ **完整安装配置、使用方式、禁止方案列表见 ./reference/platform-uni-app.md 第 1 节。禁止自行发明替代方案。**
适用条件: icon/btn 元素内含 emoji 字符(如 🔍🔔💬🪪💳📷🏠📱)
执行步骤:
1. **Read fully and follow: ./reference/platform-uni-app.md 第 1 节**(⛔ 不可跳过 — 含 Admin/Mini-program 两条路径完整安装步骤)
2. **替换源码**: 将所有 emoji 字符替换为 Iconify 类名(如 🔍 → `class="i-lucide-search"`
3. **Bash** 安装依赖 + 重新构建
4. **重测** 验证图标渲染正常
⛔ **禁止的替代方案**(完整列表见 ./reference/platform-uni-app.md 第 1 节"禁止的替代方案"
**手机号+验证码登录实现方案(#11 触发时使用)**:
当旅程验证发现 Mock 登录(非真实用户流程)且手机号登录未实现时,按以下方案 AUTO-IMPLEMENT
1. **后端: POST /api/auth/send-code**
- 接收 `{phone}`
- `NODE_ENV=development`: 生成 6 位随机验证码,存 Redis `sms:${phone}` TTL 300s**直接返回** `{code: "123456"}` — 这是标准开发实践,不是 Mock
- `NODE_ENV=production`: 调用短信服务商 API 发送验证码,返回 `{success: true}`
2. **后端: POST /api/auth/login** — 增强已有接口
- 接收 `{phone, code}`
- 从 Redis 取出验证码比对
- 匹配 → 查找/创建用户 → 返回 JWT复用已有登录逻辑
3. **前端: 启用 login/index.vue 已有的手机号表单**HTML 已存在,仅需实现 JS
- `sendCode()`: 调用 `POST /api/auth/send-code`,开发环境将返回的 code 自动填入输入框
- `handlePhoneLogin()`: 调用 `POST /api/auth/login {phone, code}`,成功后跳转首页
4. **清理**: 删除 Mock 登录按钮(`v-if="isDev" class="mock-btn"`)和 `handleMockLogin()` 函数
### AUTO-FIX #14: 即时使用数据持久化max 1 次)
**适用**: FAIL #14 — 即时使用数据(验证码等)仅通过 Toast 显示,无 console.log下一步骤无法获取
**为什么这是 Bug**: 下一步骤需要的数据(如验证码)仅通过 Toast 传递3秒后消失。Playwright 无法回溯,流程卡死。
注意:仅适用于**下一步骤立即需要**的数据。订单号、支付结果等后续页面自然显示的不在此列。
**流程**:
1. 读取失败页面源码,定位 `uni.showToast` 传递即时使用数据的位置
2. 确认数据变量名(如 `res.code`
3. 在 `uni.showToast` 调用**前**添加 `console.log('[DEV] 验证码:', res.code)`
4. 对验证码场景:添加自动填充 `if (res.code) { code.value = res.code }`
5. Toast 保留作为辅助反馈,不删除
6. 重测失败步骤 → PASS 继续 / FAIL 标记 journey-step-fail
**修复模板**:
```javascript
// 修复前:
const msg = res.code ? `验证码已发送:${res.code}(开发环境)` : "验证码已发送";
uni.showToast({ title: msg, icon: "none" });
// 修复后:
console.log('[DEV] 验证码:', res.code);
if (res.code) { code.value = res.code; }
uni.showToast({ title: "验证码已发送", icon: "success" });
```
### AUTO-FIX #18: 双重导航栏修复max 1 次)
**适用**: FAIL #18 — 页面在 pages.json 有 `navigationBarTitleText`(非 custom且页面源码有 `position:fixed` 的自定义 header
**为什么这是 Bug**: 系统导航栏与自定义头部同时显示,用户看到两层标题(如"首页"+"NEON PULSE"浪费屏幕空间且视觉混乱。uni-app 小程序项目应统一使用 `navigationStyle: "custom"`。
**流程**:
1. `Read` pages.json → 找到该页面的 style 配置
2. `Edit` pages.json → 将该页面 style 改为 `{ "navigationStyle": "custom" }`(移除或保留 navigationBarTitleText 均可custom 模式下不显示)
3. `Read` 页面源码 → 找到自定义 header 的 CSS通常为 `position: fixed; top: 0` 的元素)
4. `Edit` 页面源码 CSS:
- header 添加 `padding-top: env(safe-area-inset-top, 20px)`
- header height 改为 `calc(原高度 + env(safe-area-inset-top, 20px))`
- content margin-top 与新 header height 一致(含 safe-area
5. 重测 → 截图确认只有一层标题
**修复示例**:
```css
/* 修复前(首页 header 与系统导航栏重叠): */
.header { position: fixed; top: 0; left: 0; right: 0; height: 64px; }
.content { margin-top: 64px; }
/* 修复后(统一使用自定义导航栏): */
.header {
position: fixed; top: 0; left: 0; right: 0;
height: calc(64px + env(safe-area-inset-top, 20px));
padding-top: env(safe-area-inset-top, 20px);
}
.content {
margin-top: calc(64px + env(safe-area-inset-top, 20px));
}
```
```jsonc
// pages.json 修复:
// 修复前:
{ "path": "pages/home/index", "style": { "navigationBarTitleText": "首页" } }
// 修复后:
{ "path": "pages/home/index", "style": { "navigationStyle": "custom" } }
```
### AUTO-FIX #19: 下拉刷新方式统一max 1 次)
**适用**: FAIL #19 — Tab 页面下拉刷新方式不一致scroll-view refresher / onPullDownRefresh / 无 混用)
**⛔ 详细修复步骤见 ./reference/platform-uni-app.md §7**
**流程**:
1. `Edit` pages.json → globalStyle 添加 `"enablePullDownRefresh": true`
2. 对 LIST_LEVEL 页面scroll-view refresher
- 移除 scroll-view 的 `refresher-enabled` / `:refresher-triggered` / `@refresherrefresh`
- 移除 `refreshing` ref 和 `onRefresh` 函数
- 添加 `onPullDownRefresh` 生命周期from `@dcloudio/uni-app`
- 数据加载 `finally` 中调用 `uni.stopPullDownRefresh()`
3. 对 NONE 页面:添加 `onPullDownRefresh` + 对应数据刷新逻辑 + `uni.stopPullDownRefresh()`
4. 重测 → 所有 Tab 页面整页下拉刷新效果一致
### AUTO-FIX #20: API route try-catch 包裹max 1 次)
**适用**: API endpoint 返回 500`Read` 源码发现 handler 函数无 try-catch 包裹
**为什么这是 Bug**: 未处理异常直接返回原始 500 且无日志,无法诊断根因。常见的二次操作(如审批后创建通知)失败会吞噬前面的成功状态。
**检测方法**: API 返回 500 时,`Read` 对应 route.ts 源码,检查 `export async function POST/PUT/DELETE/GET` 内是否有 try-catch
**修复流程**:
1. `Read` 失败的 route.ts 文件
2. 确认 handler 无 try-catch 包裹
3. 在 requireAdmin/鉴权检查之后,将剩余逻辑包裹在 try-catch 中
4. catch 块:`console.error("Xxx error:", error)` + `NextResponse.json({error: "处理失败"}, {status: 500})`
5. 参考模式:同项目中已有 try-catch 的 route如 admin auth login
**修复模板**:
```typescript
// 修复前:
export async function PUT(req, { params }) {
const result = requireAdmin(req);
if ("error" in result) return result.error;
const body = await req.json(); // ← 无 try-catch异常直接变 500
// ... 业务逻辑 ...
}
// 修复后:
export async function PUT(req, { params }) {
const result = requireAdmin(req);
if ("error" in result) return result.error;
try {
const body = await req.json();
// ... 业务逻辑不变 ...
} catch (error) {
console.error("Xxx error:", error);
return NextResponse.json({ error: "处理失败" }, { status: 500 });
}
}
```
### AUTO-FIX #21: uni-app 页面 onMounted→onShow 替换max 1 次)
**适用**: 旅程中 `uni.navigateBack()` 返回目标页面后,页面数据未刷新
**为什么这是 Bug**: `onMounted` 仅在组件首次创建时触发。当用户从子页面 `navigateBack()` 返回时,父页面组件仍在导航栈中未被销毁,`onMounted` 不会再次触发。uni-app 的 `onShow` 在每次页面显示时都会触发,是正确的数据刷新时机。
**触发条件**(三条件全部满足):
- 条件 A: 旅程中使用 `uni.navigateBack()` 返回到该页面
- 条件 B: 返回后 `browser_snapshot` 显示数据未更新(如余额、列表数量未变化)
- 条件 C: `Read` 页面源码确认数据获取在 `onMounted` 而非 `onShow` 中
**修复流程**:
1. `Read` 页面 `.vue` 文件
2. 修改 import将 `import { ref, onMounted } from "vue"` 中 `onMounted` 移除(若 vue 导入不再需要 onMounted
3. 添加 `import { onShow } from "@dcloudio/uni-app"`(新 import 行)
4. 将 `onMounted(() => { fetchXxx() })` 替换为 `onShow(() => { fetchXxx() })`
5. 参考模式:项目中已有正确使用 `onShow` 的页面(如 profile/index.vue
**修复模板**:
```typescript
// 修复前:
import { ref, onMounted } from "vue";
// ...
onMounted(() => { fetchEarnings(); });
// 修复后:
import { ref } from "vue";
import { onShow } from "@dcloudio/uni-app";
// ...
onShow(() => { fetchEarnings(); });
```
### AUTO-FIX/JOURNEY-FIX #16: 约束一致性修复max 1 次)
**适用**: FAIL #16 — 旅程描述/UI 文案说"可选"但代码实际必填,或 UI 标记"选填"但接口必填
**检测触发条件**(双条件同时满足):
- 条件 1声明方说"可选"(旅程描述含"可跳过/可选/选填/skip/optional/使用占位图" 或 页面文本含"(选填)/(可选)"
- 条件 2代码实际"必填"Toast 含"请上传/请填写/请选择/不能为空" 或 源码有 `if (!field)` 验证)
**为什么这是问题**: 用户被"可选"承诺误导,但操作时被强制校验拦截。真实用户体验受损。
**流程**:
1. `Read` 对应页面源码 → 定位必填验证逻辑(`if (!field)` / `required` / `validate`
2. 判断正确设计意图:
- PRD/UX 规范说可选 → AUTO-FIX 代码(移除必填验证或改为可选提交)
- PRD/UX 规范说必填 → JOURNEY-FIX 旅程定义(更新描述为必填)+ AUTO-FIX UI 文案(移除"选填"标记)
- 无明确 PRD 依据 → JOURNEY-FIX旅程定义改为反映代码行为+ 记录 `constraint_mismatch` 到 pending_fixes
3. 修复后重测该步骤
**代码修复示例**(设计意图=可选时):
```javascript
// 修复前(强制必填):
if (!form.idCardFront || !form.idCardBack || !form.idCardHolding) {
uni.showToast({ title: "请上传所有证件照片", icon: "none" });
return;
}
// 修复后(改为可选):
// 移除强制校验,允许不上传照片直接提交
```
**旅程修复示例**(设计意图=必填时):
```markdown
// 修复前:
9. 用户第二步上传身份证照片(可跳过或使用占位图)→ 点击"下一步"
// 修复后:
9. 用户第二步上传身份证照片(必填:身份证正面、反面、手持照片)→ 点击"下一步"
```
### AUTO-DATA-FIX数据状态max 1 次)
**适用**: 测试数据过期、冻结期收益不可用、关联数据缺失
**流程**:
1. `Bash` 直接操作数据库或 API 修复数据状态
2. 重测失败步骤
**示例**:
- 收益冻结期不可提现 → `Bash`: `psql UPDATE earnings SET status='AVAILABLE'`
- 组局状态不正确 → API: `PUT /api/admin/events/[id]` 修正状态
- 测试用户数据缺失 → API: `POST` 创建缺失的关联记录
### ENV-ADAPT环境限制max 1 次)
**适用**: 操作被前端表单校验拦截(#12Toast 提示了具体原因
**流程**:
1. `browser_snapshot` 读取页面 Toast/错误提示内容
2. 根据 Toast 内容分流:
- "请输入/请填写/不能为空" → 找到对应字段 → `browser_type` 补充填写 → 重试操作
- "请上传/请选择图片/请选择文件" → `browser_click` 触发文件选择 → `browser_file_upload` 上传测试文件 → 重试操作
- "暂未开放/开发中" → 分类为 AUTO-IMPLEMENT → 走 auto-implement 流程
3. 重试仍失败 → 标记 journey-step-fail + Toast 内容
**⛔ 重要:表单校验失败但页面无任何提示 → Bug(#13) → AUTO-FIX添加 Toast/错误提示)**
**示例**:
- 局头申请要求上传证件照 → Toast "请上传所有证件照片" → `browser_file_upload` 上传测试图片 → 重试提交
- 注册表单缺少必填项 → Toast "请输入手机号" → `browser_type` 填写手机号 → 重试提交
- 表单提交无反应且无 Toast → Bug(#13) → AUTO-FIX: 添加 Toast 提示
### 修复失败处理
- 重试达到上限仍 FAIL → 标记该步骤 `journey-step-fail`
- 记录: `{journey, step, fail_reason, fix_type, fix_attempted, fix_result}`
- 继续执行下一个步骤(不阻断整条旅程)
- 旅程完成后统计: `steps_passed / steps_total` → 决定整体 PASS/FAIL
---
## 5. GATE-CHECK⛔ 修复完成后、记录阶段之前执行)
```
GATE-CHECK:
├── 所有交互使用真实用户操作(无 browser_run_code 绕过 input/button ← 真实用户完成规则
├── browser_take_screenshot 已执行 ← 证据检查
├── ⛔ analyze_image 已执行(所有截图都有对应 AI 视觉分析结果) ← 视觉验证检查
│ 验证pipeline-state 中 analyze_image_executed === true
│ 未执行 → 不得标记完成,必须回退执行视觉验证(见 ./visual-verification.md
├── browser_network_requests 已执行 ← 网络检查
├── failed_steps 数量 = 0 ← 断言检查
├── 即时使用数据有 console.log 或自动填充(无 #14 违规) ← 数据可追溯检查
├── auto_fix 对所有 FAIL 已触发 ← 修复检查
├── ⛔ verification_detail 中每个 FAIL 项都有对应的 auto_fix 记录 ← FAIL-修复交叉验证
│ 逐一比对 verification_detail 与 auto_fix 记录:
│ english_enum_check.found = FOUND → auto_fix 必须包含 #15 修复记录
│ icons_check.emoji_as_icons = FOUND → auto_fix 必须包含 Iconify 替换修复记录
│ icons_check.tabbar_icons_visible = FAIL → auto_fix 必须包含 TabBar 修复记录
│ 缺少对应记录 → 不得标记完成,必须回退执行修复
└── ⛔ 视觉验证 FAIL 的修复在内联关卡已执行(非推迟到步骤 4 ← 内联修复检查
结果判定:
├── 全部 PASS → 继续记录阶段status: pass
├── FAIL + 修复成功 → 继续记录阶段status: pass含修复记录
└── FAIL + 修复失败/未触发 → status: fail⛔ 不得记录为 pass
```
### ⛔ 严重违规行为
- 检测到 FAIL 但没有执行分类判定
- 分类判定为 Bug/未实现但没有触发修复
- 修复失败但没有记录 `auto_fix_result`
- `status: pass` 但 `failed_steps > 0` 且未全部修复成功
- 用 `browser_evaluate` 绕过不可交互的 input/button 后标记步骤 PASS
- 把"加载中..."/"暂未开放"判定为 PASS
- 任何 FAIL 标记为"可接受"但不符合上述严格限定的 2 种情况
- **用 SVG data URI / CSS background-image 替代 Iconify 图标方案**(微信小程序不支持 SVG data URI必须用 @egoist/tailwindcss-icons + Iconify
---
## 5.5 步骤级状态更新(⛔ AUTO-FIX/AUTO-IMPLEMENT 完成后强制执行)
**修复完成后,必须同步更新步骤级跟踪文件,确保修复证据完整可追溯。**
```
AUTO-FIX/AUTO-IMPLEMENT 执行后:
1. 更新 verification-checklist.yaml:
- 找到当前 step → status 改为 "fixed"(修复后待重测)或 "pass"(重测通过后)
- 如果重测通过 → status 改为 "pass"
2. 更新 pipeline-state.yaml step_results:
- 在当前步骤的 auto_fixes 数组中追加修复记录:
```yaml
auto_fixes:
- fix_id: "FIX-{NNN}"
issue_id: "ISSUE-{NNN}" # 关联触发修复的问题
fix_type: "AUTO-FIX|AUTO-IMPLEMENT|AUTO-DATA-FIX"
description: "{修复描述}"
files_changed: [ "{文件路径}" ]
retry_count: {N}
result: "pass|fail"
fixed_at: "{ISO timestamp}"
screenshot_after_fix: "{修复后重测截图路径}"
```
3. 更新步骤级 GATE:
- 重新检查 G5: all_issues_resolved
- 修复通过 → step_gate 改为 "pass"
- 修复失败且重试耗尽 → step_gate 改为 "fail",步骤标记 failed
4. 如重测需要重新截图和 analyze_image:
- browser_take_screenshot → 保存为新截图(原截图保留)
- analyze_image → 更新 analyze_image_result
- 更新 step_results 的 screenshot_path 和 analyze_image_result
```
**⛔ 禁止行为**:
- ❌ 修复后不更新 verification-checklist.yaml 的 step status
- ❌ 修复后不追加 auto_fix 记录到 step_results
- ❌ 修复后不重新检查步骤级 GATE
- ❌ 修复失败但 step_gate 仍标记为 "pass"
---
## 6. 各模式引用方式
以下文件必须引用本引擎,删除各自的重复定义:
| 文件 | 引用位置 |
|------|---------|
| `journey-mode.md` | 执行模板步骤 4修复阶段→ `Read fully and follow: ./auto-fix-engine.md` |
| `validation-mode.md` | AUTO-FIX/AUTO-IMPLEMENT 分支 → `Read fully and follow: ./auto-fix-engine.md` |
| `phase-04-test.md` | Phase 4.5 AUTO-FIX 循环 → 引用第 4 节修复流程 |
| `phase-05-review.md` | 质量关卡 FAIL 处理 → 引用第 2 节分类标准 |
| `visual-diff-mode.md` | Phase VD-3 逐页修复 → VISUAL-FIX (V1-V5) 分类引用 |
| `e2e-testing.md` | 步骤 9.5 判定规则 → 引用第 2 节分类标准 |
| `iron-laws.md` | 检测即阻断规则 → 引用第 2 节分类标准 + 第 5 节 GATE-CHECK |

View File

@@ -0,0 +1,329 @@
# E2E 测试
> 参见 ./auto-fix-engine.md 了解统一修复引擎FAIL分类标准 + 修复流程 + GATE-CHECK
## E2E 测试覆盖矩阵
| 目标 | 启动方式 | Playwright MCP 测试方法 |
|------|---------|----------------------|
| 管理后台Next.js | `pm2 start ecosystem.config.js` → admin 进程 | browser_navigate → snapshot → click/type → verify |
| 小程序用户端uni-app | `pm2 start ecosystem.config.js` → mini-h5 进程 | browser_navigate → snapshot → click/type → verify |
| API 路由 | `pm2 start ecosystem.config.js` → api 进程 | browser_navigate → evaluateJSON 验证) |
### Playwright MCP 测试序列模式
**此序列为完整模式,所有步骤都是强制的**(除非页面确实没有表单/按钮)。
```
0. browser_resize → 设置正确视口(管理后台 1440x900 / 小程序 375x812
1. browser_navigate → 目标 URL
2. browser_snapshot → 验证页面/组件加载(确认非 404/空白)
3. browser_type → 填写表单(页面有表单则必须填写,不能跳过)
4. browser_click → 提交/操作按钮(页面有按钮则必须点击,不能跳过)
5. browser_wait_for → 等待响应/数据加载timeout: 5s
6. browser_snapshot → 验证操作结果(数据已创建/更新/删除)
7. browser_take_screenshot → 保存截图到 tests/e2e/screenshots/story-x-y.png强制
8. analyze_image → AI 视觉验证截图(强制,见下方"增强 analyze_image 提示词"
8.5. browser_evaluate → 布局溢出检测(小程序 Story 强制,见下方"步骤 8.5"
9. browser_network_requests → API 调用目标检测(步骤 8 之后,前端 Story 强制)
```
**强制规则**
- 步骤 0resize不能省略即使"上一个 Story 刚 resize 过"
- 步骤 3-5交互在页面有表单/按钮时**绝对不能省略**
- 步骤 7截图是强制证据不是可选装饰
- 步骤 8视觉验证**不调用等于验证未完成**(⛔ 详细规则和标准提示词见 ./visual-verification.md
- 步骤 9API 目标检测)**前端 Story 必须执行**,检测页面 JS 发起的 API 请求是否指向 localhost 而非外网域名
### API 调用目标检测(步骤 9 详解)
**目的**:检测前端页面的 JavaScript 发出的 API 请求是否指向正确的服务器地址。如果前端代码 fallback 到 `http://localhost:3001`,在 Playwright 测试环境下可以成功(因为 Playwright 运行在 localhost但真实外网用户会失败。
**检测方法**
```
在步骤 8analyze_image之后执行
browser_network_requests:
filter: "/api/"
requestHeaders: false
requestBody: false
static: false
```
**判定规则**
1. 读取返回的网络请求列表中所有请求的 URL
2. 检查是否有任何 URL 包含 `localhost``127.0.0.1`
3. 结合 `pipeline-context.md` 中的 `URL_MODE` 判断:
- `URL_MODE` 为 "外网域名" 且发现 localhost API 调用 → **FAIL**
- `URL_MODE` 为 "localhost(fallback)" 且发现 localhost API 调用 → **PASS**(符合预期)
**结果记录**
```yaml
verification_detail:
api_target_verified: true/false
api_target_violations: ["http://localhost:3001/api/xxx"] # 仅失败时
```
**失败处理**
1. 检查 `.env.local` 是否存在:`cat app/mini-program/.env.local` / `cat app/admin/.env.local`
2. 检查文件内容是否包含正确的外网域名
3. 如果文件正确但仍失败 → 重启服务 `pm2 restart admin && pm2 restart mini-h5`,清缓存后重试
**注意**
- 此检测必须在页面有 API 交互后执行(先完成 browser_click 等操作触发 API 调用)
- 如果页面刚打开还没有 API 请求,需要先触发一个会产生 API 调用的操作
- 此检测是对 phase-00-setup.md 步骤 4.7 的运行时验证
### 步骤 9.5: 源码三重检查(所有前端 Story 强制)
**目的**:检测前端源码中的三类问题 — localhost fallback、图标方案未合规、emoji 充当图标。
**适用范围**: app/ 下所有前端代码admin、mini-program 等),不仅限于 admin。
**检测方法**(对所有涉及前端页面的 Story 强制执行):
```bash
# 检查 1: localhost fallback 值
LOCALHOST_FALLBACK=$(grep -rn "localhost:[0-9]" app/*/src/ --include="*.ts" --include="*.tsx" --include="*.vue" | grep -v node_modules)
LOCALHOST_COUNT=$(echo "$LOCALHOST_FALLBACK" | grep -c ":" || echo 0)
echo "localhost_fallback=$LOCALHOST_COUNT"
[ "$LOCALHOST_COUNT" -gt 0 ] && echo "LOCALHOST_FILES:" && echo "$LOCALHOST_FALLBACK"
# 检查 2: 内联 API 声明(非集中式模块)
INLINE_FILES=$(grep -rn "const.*API_BASE\s*=" app/*/src/ --include="*.ts" --include="*.tsx" --include="*.vue" | grep -v node_modules | grep -v "lib/api\|config/api\|lib/config")
INLINE_COUNT=$(echo "$INLINE_FILES" | grep -c ":" || echo 0)
echo "inline_api=$INLINE_COUNT"
[ "$INLINE_COUNT" -gt 0 ] && echo "INLINE_FILES:" && echo "$INLINE_FILES"
# 检查 3: 图标方案合规 + Emoji 充当图标
# 完整检测命令见 ./reference/platform-uni-app.md 第 6 节
```
**判定规则**
| 条件 | 分类 | 结果 | 动作 |
|------|------|------|------|
| `LOCALHOST_COUNT > 0` | Bug | **FAIL** | AUTO-FIX: 替换为外网域名 |
| `INLINE_COUNT > 0` 且集中模块存在 | Bug | **FAIL** | AUTO-FIX: 替换为 import |
| `INLINE_COUNT > 0` 且无集中模块 | 未实现 | **FAIL** | AUTO-IMPLEMENT: 创建集中模块 |
| `tabbar_iconpath_empty=true` | 未实现 | **FAIL** | AUTO-IMPLEMENT: 实现自定义 TabBar |
| `EMOJI_COUNT > 0` | 未实现 | **FAIL** | AUTO-IMPLEMENT: Iconify 方案替换 |
| 全部为 0 | — | **PASS** | — |
**AUTO-FIXBug 类型)**
1. localhost fallback → 从 pipeline-context.md 读取外网域名Edit 替换
2. 内联 API集中模块存在→ Edit 替换为 import 引用
3. 重启前端服务 → 重新执行步骤 9.5 验证
**AUTO-IMPLEMENT未实现类型**
按照 `./reference/platform-uni-app.md` 第 1 节(图标方案)和第 2 节TabBar 方案)执行完整实现:
1. 安装依赖Tailwind + Iconify + weapp-tailwindcss
2. 创建配置文件tailwind.config.js, vite.config.ts
3. 创建组件(自定义 TabBar
4. 修改引用(替换 emoji、更新 pages.json
5. 重启服务 → 重新执行步骤 9.5 验证
> 完整的 AUTO-FIX/IMPLEMENT/DATA-FIX 分类标准和修复流程见 ./auto-fix-engine.md
### 增强 analyze_image 提示词(步骤 8 详解)
**目的**:当前 analyze_image 只检查项目设计系统颜色,需增加多个维度,每个维度必须明确 PASS/FAIL。
**提示词模板**
```
分析此截图,按以下维度逐一给出 PASS/FAIL 判定:
1. **设计规范**(从项目 _bmad-output/04-ui-ux-design.md 提取):
- {执行时Read _bmad-output/04-ui-ux-design.md → 提取设计系统规则 → 填入}
- 回退:文件不存在 → grep 项目 CSS :root 变量
→ PASS/FAIL + 具体问题
2. **图标/图片完整性**
- TabBar 图标是否全部可见(非空白/缺失)
- 是否有 broken image加载失败的图片占位符
- 是否有 emoji 充当功能图标(如 🏠🏠📱 等)
- 可缺失的图片(如头像)是否有 CSS 渐变色占位符
- TabBar 图标:如果有底部导航栏,检查图标是实际图片/SVG 还是纯文本标签?
如果只有文字(如"首页/广场/消息/我的")而没有对应的图标 → FAIL
检查方法:图标区域是否有 <img> 或 <svg> 或 icon class还是只有 <text>
→ PASS/FAIL + 具体问题
3. **功能元素完整性**
- 预期的按钮/表单/列表是否全部渲染
- 按钮文案是否可读(非乱码/空白)
- 列表项是否有数据(非空列表且无"暂无数据"提示)
→ PASS/FAIL + 具体问题
4. **布局质量**(⛔ 逐一检查以下每一项,不得跳过):
a. 溢出检测:内容在视口范围内,无水平溢出或裁剪
b. 文字可读:文字不被截断,不显示省略号,对比度足够
c. 重叠检测:元素无重叠(标题栏与自定义头部不碰撞,导航栏不遮盖内容)
d. 间距合理性:无异常大间距(如导航栏与内容之间的过大空白),无紧贴元素(相邻交互元素应有明确间距)
e. 元素变形检测:按钮/输入框/卡片未被挤压变形(宽高比异常),文字未被截断
f. 对齐检测:同行元素水平对齐,同列元素垂直对齐
g. TabBar底部 TabBar 完整可见(非 TabBar 页面此项 PASS
→ PASS/FAIL + 具体问题
总结:列出所有 FAIL 项,如全部 PASS 则输出"全部 PASS"。
```
**结果记录**
```yaml
verification_detail:
design_check: { design_compliance: PASS/FAIL, design_spec_source: "04-ui-ux-design.md", details: "..." }
icons_check: { tabbar_icons_visible: PASS/FAIL, broken_images: PASS/FAIL, emoji_as_icons: PASS/FAIL }
functional_check: { elements_rendered: PASS/FAIL, text_readable: PASS/FAIL }
layout_check: { overflow: PASS/FAIL, spacing_anomaly: NONE/FOUND, element_deformation: NONE/FOUND, alignment: PASS/FAIL, tabbar_visible: PASS/FAIL }
```
### 步骤 8.5: 布局溢出检测(小程序强制)
**目的**:小程序页面在真实移动设备上经常出现宽度/高度溢出视口、底部 TabBar 被遮挡的问题。此步骤通过 JavaScript 检测这类布局问题。
**检测方法**
> 完整的检测 JS 代码和 TabBar CSS 选择器见 **./reference/platform-uni-app.md 第 5.2 节**。
> 防溢出规则见 **./reference/platform-uni-app.md 第 5.1 节**。
在步骤 8analyze_image之后、步骤 9network_requests之前执行。
**判定规则**
| 检查项 | FAIL 条件 | 说明 |
|--------|----------|------|
| 水平溢出 | `horizontal_overflow === true` | 页面宽度超出视口,会导致横向滚动条 |
| TabBar 不可见 | `tabbar_found && !tabbar_visible` | TabBar 存在但被遮挡,用户需要滚动才能看到 |
**⛔ 阻断规则(检测到 FAIL 时必须修复后才能继续)**
- 水平溢出: `horizontal_overflow === true` → Story 标记 FAIL进入 AUTO-FIX
- TabBar 不可见: `tabbar_found && !tabbar_visible` → Story 标记 FAIL进入 AUTO-FIX
- 修复方向:
1. 检查页面根元素是否有超过 `100vw` 的宽度设置
2. 检查固定定位元素的 `bottom` 值是否考虑了 TabBar 高度
3. 检查可滚动内容高度是否减去了 TabBar + Header 高度
4. 修复后重新执行步骤 8.5 验证
**结果记录**
```yaml
verification_detail:
layout_check:
horizontal_overflow: true/false
document_width: 375
viewport_width: 375
tabbar_found: true/false
tabbar_visible: true/false/null # null = 页面无 TabBar
```
### 用户旅程测试Epic 级集成验证)
**⛔ 工具选择强制规则**:执行旅程测试时,每个用户步骤必须遵循上述 Playwright MCP 测试序列(步骤 0-9。**禁止使用 curl 替代 browser_click / browser_type / browser_snapshot / browser_navigate**。curl 仅用于数据状态的前后对比验证(补充,非替代用户交互)。详见 `journey-mode.md` 的"工具选择守卫"部分。
**核心理念**:技能文档只定义"如何做用户旅程测试"的方法论和步骤,**不定死具体旅程**。具体旅程由 AI 根据项目自身的 PRD/需求文档/Epic+Story 定义自主识别。
### ⛔ 用户旅程真实性原则(强制遵守)
**用户旅程 ≠ 单元测试。必须模拟真实用户从自然入口开始的行为路径。**
**规则 1起始页必须是自然入口**
- 小程序:起始页 = 用户打开网址看到的第一个页面(参见 ./reference/platform-uni-app.md §4 入口页面)
- 管理后台:起始页 = 根 URL `/`(通常重定向到登录或 Dashboard
-`browser_navigate("/pages/login/index")` — 用户不会直接输入登录页 URL
-`browser_navigate("http://mini-xxx.dev.d8d.fun/")` → 用户看到首页 → 自然触发登录流程
**规则 2操作路径必须可发现**
- 每一步必须是用户"看得见、点得到"的
- ❌ "用户进入局头申请页"(怎么进去的?没写)
- ✅ "用户点'我的'Tab → 看到'申请成为局头'按钮 → 点击进入申请页"
**规则 3前置条件必须明确且可验证**
- 每个旅程必须写明操作前的系统/用户状态
- ❌ "用户提交申请"
- ✅ "前置:用户已登录(有 token且 organizer=null无局头身份"
**规则 4数据断言必须具体**
- ❌ "登录成功返回 JWT token"(太宽泛)
- ✅ "新用户登录后返回的 userId ≠ 已有用户的 userId"
- ❌ "申请提交成功"
- ✅ "申请前 organizer=null申请后 organizer.status=PENDING"
**规则 5跨越页面必须连续导航**
- 页面跳转必须通过用户操作触发(点击/提交),不得直接 `browser_navigate`
-`browser_navigate("/pages/organizer-apply/index")`
- ✅ 用户点击"申请成为局头"按钮 → 页面跳转到申请页
**违反以上任何规则的旅程测试视为无效,不得标记为 pass。**
**旅程来源(兼容模式)**
- **优先**:读取 Phase-01 预定义的 `{implementation_artifacts}/epic-N-journeys.md` 文件(验证模式由 Phase 0V 步骤 4.5 预定义/恢复)
- **回退**:如果旅程文件不存在,从 PRD/需求文档 + 已实现源码自主识别跨页面业务流程
- 验证模式的 Epic 旅程测试遵循此完整模式流程
**执行时机**:每个 Epic 所有 Story 验证完成后Phase 6: EPIC-DONE 中触发)。
**步骤定义**
1. **读取旅程定义**
- **首选**:读取 `{implementation_artifacts}/epic-N-journeys.md`Phase-01 或 Phase 0V 步骤 4.5 预定义)
- **回退**:读取 `_bmad-output/` 下的 PRD 文件、Epic/Story 定义文件,从需求文档自主提取跨页面业务流程
- 关注:用户故事描述、业务流程图、页面跳转逻辑
2. **识别测试旅程**
- 从 PRD 中提取涉及当前 Epic 功能的用户场景
- 旅程定义格式:`起始页 → 操作步骤1 → 页面2 → 操作步骤2 → ... → 预期最终状态`
- 每个旅程必须跨越至少 2 个页面
- 每个旅程必须包含至少 1 个数据状态变化(创建/更新/删除)
3. **执行旅程测试**
```
对每个旅程:
browser_resize → 正确视口
browser_navigate → 起始页
browser_snapshot → 验证起始状态
(循环)
browser_type / browser_click → 执行操作
browser_wait_for → 等待页面跳转/数据更新
browser_snapshot → 验证中间状态
(结束循环)
browser_take_screenshot → 最终状态截图
browser_evaluate → 验证最终数据状态(如 API 返回值、localStorage 等)
```
4. **验证标准**
- 每步页面渲染正常(无空白/404/错误提示)
- 每个数据操作有正确响应(创建成功/更新成功/状态变化)
- 页面跳转正确(到达预期页面)
- 最终状态符合 PRD 预期
5. **记录结果**
```yaml
user_journeys:
epic_N:
journey_1:
name: "{从 PRD 提取的旅程名称}"
steps: ["页面A → 操作X → 页面B → 操作Y → 结果"]
status: pass/fail
screenshot: "tests/e2e/screenshots/journey-epicN-1.png"
details: "简短描述"
```
**旅程来源优先级**
1. 项目 PRD 文档中的用户流程/场景描述(最高优先级)
2. Epic/Story 规格文件中的验收标准组合
3. 已实现的页面路由和导航逻辑
**最低覆盖要求**:每个 Epic 至少覆盖 1 条涉及该 Epic 功能的用户旅程。
**与 Story 级验证的区别**
- Story 级验证:测试单个功能点(如"创建按钮可点击"
- 旅程级验证:测试完整业务流程(如"登录 → 浏览 → 创建 → 发布 → 验证列表更新"
| 服务 | Mock 方式 |
|------|----------|
| 微信 OAuth | Mock API 返回测试 OpenID |
| 微信支付 | 沙箱环境 + Mock 回调 |
| 企业付款到零钱 | Mock 转账 API验证请求结构 |
| 对象存储 | **容器内置 MinIO**`localhost:9000`,兼容 S3 API |
| PostgreSQL | **容器内置 PG 17**`localhost:5432`,用户 `postgres` |
| Redis | **容器内置 Redis 7**`localhost:6379` |
| 短信通知 | 输出到控制台,验证通知记录入库 |

View File

@@ -0,0 +1,299 @@
# 铁律
> 参见 ./auto-fix-engine.md 了解统一修复引擎FAIL分类标准 + 修复流程 + GATE-CHECK
## 铁律Epic 间零暂停自动继续
**这是最重要的执行规则,必须严格遵守:**
```
Epic N 完成(所有 Story done + 分类提交 + push
立即开始 Epic N+1无任何暂停、无状态报告、无用户确认
Epic N+1 完成
立即开始 Epic N+2
... 直到所有 Epic 完成
```
**规则:**
1. **Epic 间不停**Epic 完成后立即进入下一个 Epic不输出进度汇总
2. **用户未取消即继续**:只要用户没有发送停止/取消消息,就一直自动执行
3. **进度信息内联**:每完成一个 Story用一行 `✅ Story X-Y done` 标记,不展开说明
4. **只在结束时汇报**:所有 Epic 完成后才输出最终 pipeline-report
5. **错误不中断**:单个 Story 失败记录到 error_log跳过继续下一个
**违反此规则的行为:**
- ❌ Epic 完成后问用户"是否继续?"
- ❌ Epic 完成后输出长篇进度汇总然后停下
- ❌ 等待用户确认才开始下一个 Epic
- ❌ 在 Epic 之间做多余的验证或状态汇报
## 铁律二:禁止跳过 Story 级质量关卡
**与铁律一Epic 间不停)同样重要。铁律一说的是速度,铁律二说的是质量。**
```
❌ 错误做法(批量跳步):
把 39 个 Story 合并为 3 批 → 批量写 API → 批量写页面 → 批量提交
结果:跳过所有 Spec/Test/Review留下未发现的 bug
✅ 正确做法(逐 Story 流水线):
Story 1: CREATE-SPEC → IMPLEMENT → TEST → REVIEW → MARK-DONE
Story 2: CREATE-SPEC → IMPLEMENT → TEST → REVIEW → MARK-DONE
...
Story 39: CREATE-SPEC → IMPLEMENT → TEST → REVIEW → MARK-DONE
```
**每个 Story 必须经过的关卡(一个都不能少)**
| 关卡 | 作用 | 跳过后果 |
|------|------|---------|
| CREATE-SPEC | 分析依赖、边界条件、验收标准 | 遗漏非显而易见的需求细节 |
| TEST | 单元测试(Jest) + E2E 测试(Playwright MCP) 验证实现正确性 | bug 滞留到后期,修复成本 10x缺少 E2E = 前端/API 行为未验证 |
| REVIEW | 并行审查子代理(盲猎人/边界猎人/验收审计) | 类型错误、关系不匹配等问题逃逸 |
| AUTO-FIX | 自动修复失败测试 | 失去流水线核心自愈能力 |
**允许的效率优化(不跳步骤)**
- 同 Epic 内连续 Story 的 Spec 可以精简,但不能跳过
- IMPLEMENT 阶段可以在一次 Agent 调用中创建多个文件,但必须按 Story 粒度跟踪状态
- Epic 内所有 Story 完成后统一分类提交(按类型分 commit而不是每个 Story 单独提交
**违反此规则的行为**
- ❌ 把多个 Story 合并为一批直接实现
- ❌ 跳过 CREATE-SPEC 直接写代码
- ❌ 跳过 TEST 直接提交
- ❌ 跳过 REVIEW 直接标记 done
- ❌ 用"效率"作为借口绕过质量关卡
- ❌ 创建 Epic 级 Task 或用一次 Agent 调用批量处理多个 Story
## 铁律三:禁止 Agent 批量处理多 Story
**铁律一说速度,铁律二说质量,铁律三说粒度。**
```
❌ 错误做法Agent 批量合并):
一次 Agent 调用验证 Epic 2-4 的所有 API
一次 Bash curl 测试多个 Story 的端点
一次 Playwright 会话验证多个页面
Agent prompt 列出 5 个 Story 的验收标准
✅ 正确做法(逐 Story 执行):
Agent 调用 1: 只处理 Story 2-1
Agent 调用 2: 只处理 Story 2-2
...
每个 Agent = 一个 Story = 一个 TaskCreate
```
**规则**
| 规则 | 说明 |
|------|------|
| 规则 1 | 一次 Agent 调用只处理一个 Story |
| 规则 2 | 禁止并行 Agent 分别验证不同 Epic 的 Story |
| 规则 3 | 39 个 Story = 39 个 TaskCreate禁止合并 |
**违反行为(全部禁止)**
- ❌ 用一次 Bash curl 测试多个 Story 的 API 端点
- ❌ Playwright 一次会话验证多个页面(跨 Story
- ❌ Agent prompt 列出多个 Story 的验收标准
- ❌ 创建 `subject: "Epic 2: 组局管理"` 这样的粗粒度 Task
**唯一并行例外**:同 Epic 内无依赖的 Story 可并行实现,但**每个 Agent 仍只处理一个 Story**。
## 铁律四:验证模式禁止表层验证
**铁律一说速度,铁律二说质量,铁律三说粒度,铁律四说深度。**
表层验证 = 只看页面"能不能打开",不验证"能不能用"。这是验证模式最常见的失败模式。
```
❌ 表层验证(禁止):
browser_navigate → browser_snapshot → "OK 渲染正确" → 下一个
结果:表单提交不了、按钮没绑定事件、样式与设计稿不一致,全部漏掉
✅ 深层验证(必须):
browser_resize → browser_navigate → browser_snapshot
→ browser_type → browser_click → browser_wait_for → browser_snapshot
→ browser_take_screenshot → analyze_image
→ "表单提交成功、数据已更新、视觉符合项目设计系统" → 下一个
```
### 按Story类型的验证要求矩阵
**根据 sprint-status.yaml 的 assignee 字段判断 Story 类型,确定验证要求**
| Story 类型 | assignee | 渲染验证 | 交互验证 | 视觉验证(analyze_image) | 截图存档 | API curl |
|------|---------|---------|---------|---------|---------|---------|
| **纯后端** | `backend` | — | — | — | — | **必须** |
| **API+前端** | `frontend+backend` | **必须** | **必须** | **必须** | **必须** | **必须** |
| **API+管理后台** | `backend+admin` | **必须** | **必须** | **必须** | **必须** | **必须** |
| **纯前端(小程序)** | `frontend` | **必须** | **必须** | **必须** | **必须** | 可跳过 |
| **纯管理后台** | `admin` | **必须** | **必须** | **必须** | **必须** | 可跳过 |
| **纯配置/Schema** | — | — | — | — | — | — |
**判断规则**
- assignee 含 `backend` → 需要测试 API
- assignee 含 `frontend``admin` → 需要测试前端页面
- 两者都有 → API + 前端都需要测试
- 纯配置/Schema Story → 仅验证配置正确性,记录 `e2e_skipped_reason`
### 六维验证检查清单(每个前端 Story 必须全部通过)
| 维度 | 验证方法 | 通过标准 | 常见遗漏 |
|------|---------|---------|---------|
| **渲染验证** | browser_snapshot | 组件树包含预期元素 | 只做了这一步就跳过 |
| **交互验证** | browser_type + browser_click | 表单可填写、按钮可点击、操作有结果响应 | 管理后台不填表单、小程序不测试流程 |
| **视觉验证** | analyze_image对每步截图 | 项目设计系统(04-ui-ux-design.md) + 图标/图片加载完整 + 无 emoji 占位符 + 无 broken image | 只在最后做一次,中间步骤视觉问题被遗漏 |
| **截图存档** | browser_take_screenshot每步 | 每步保存到 tests/e2e/screenshots/(含步骤编号) | 只在最后保存一张截图 |
| **API 目标验证** | browser_network_requests | 前端 JS 发起的 API 请求不指向 localhost外网域名模式下 | 完全不检测,导致外网用户无法使用 |
| **布局溢出验证** | browser_evaluateJS 检测) | scrollWidth <= viewportWidth、TabBar 可见(小程序页面) | 完全不检测布局溢出 |
### Epic 0 专用验证检查清单
**Epic 0项目初始化的验证不涉及前端页面使用以下独立检查项**
| 检查项 | 验证命令 | 通过标准 |
|--------|---------|---------|
| 数据库连接 | `psql -U postgres -c "SELECT 1"` | 返回结果 |
| 数据库表创建 | `psql -U postgres -d barparty -c "\dt"` | 17 张表存在 |
| 数据库迁移 | `cd api && npx prisma migrate status` | 所有迁移已应用 |
| Redis 连接 | `redis-cli ping` | PONG |
| 种子数据 | `psql -U postgres -d barparty -c "SELECT count(*) FROM \"Admin\""` | 3 条 admin 记录 |
| pm2 进程 | `pm2 list` | api/admin/mini-h5 均 online |
| API 健康检查 | `curl -sf http://localhost:3001/api/health` | 返回 200 |
| Admin 页面 | `curl -sf http://localhost:3002` | 返回 HTML |
| Mini-H5 页面 | `curl -sf http://localhost:5173` | 返回 HTML |
### 管理后台交互验证(强制操作列表)
**每个管理后台页面必须至少完成以下交互**(如果页面包含对应元素):
1. **表单页面**:填写所有必填字段 → 点击提交 → 等待成功消息 → 验证数据已创建/更新
2. **列表页面**:验证数据行存在 → 点击筛选标签 → 验证筛选生效 → 翻页 → 验证分页
3. **详情页面**:从列表点击进入 → 验证详情字段完整 → 点击编辑 → 修改并保存
### 小程序交互验证(强制操作列表)
**每个小程序页面必须至少完成以下交互**(如果页面包含对应元素):
1. **表单页面**:填写字段 → 点击提交 → 等待响应 → 验证成功提示
2. **列表页面**:验证列表项渲染 → 点击进入详情 → 验证详情页
3. **流程类页面**:必须走完完整流程(如登录 → 首页 → 详情 → 报名 → 订单确认)
### 视口切换规则(每个前端 Story 强制执行)
```
验证管理后台页面之前:
browser_resize → width: 1440, height: 900
(即使上一个 Story 也是管理后台,也要确认当前视口正确)
验证小程序页面之前:
browser_resize → width: 390, height: 844, deviceScaleFactor: 2
(即使上一个 Story 也是小程序,也要确认当前视口正确)
- 390x844 = 现代 iPhone (12/13/14/15) 标准视口
- deviceScaleFactor: 2 → 像素密度 2x匹配 Pencil 设计稿导出 scale=2
- 旧款 iPhone 是 375x812现代设计稿普遍使用 390 宽度
```
### 禁止行为列表
- ❌ 只执行 browser_navigate + browser_snapshot 就标记验证通过
- ❌ 不填写表单就说"管理后台页面验证通过"
- ❌ 不点击按钮就说"交互功能正常"
- ❌ 不调用 analyze_image 就说"视觉验证通过"
- ❌ 不保存截图就继续下一个 Story
- ❌ 只在最后阶段截图和 analyze_image中间步骤无视觉证据
- ❌ 不执行 browser_network_requests 检测就说"API 调用正确"
- ❌ 前端页面 JS 调用 localhost:3001 但仍标记 Story 为 done
- ❌ 混用视口(管理后台用 375 宽度或小程序用 1440 宽度)
### 检测即阻断规则(⛔ 铁律四补充)
**任何检测到的问题必须阻断 Story 完成,不允许"记录后继续"**
| 检测项 | FAIL 条件 | 分类 | 修复动作 |
|--------|----------|------|---------|
| 布局溢出 | `horizontal_overflow === true` | Bug | AUTO-FIX: CSS 修复 |
| TabBar 图标缺失 | 只有文本无图标 | 未实现 | AUTO-IMPLEMENT: 实现自定义 TabBar参见 ./reference/platform-uni-app.md §2 |
| API 内联声明 | 任何前端代码有独立 `const API_BASE =` | Bug/未实现 | 集中模块存在→AUTO-FIX否则→AUTO-IMPLEMENT |
| API 目标错误 | 外网模式下发现 localhost | Bug | AUTO-FIX: 替换 fallback 值 |
| 源码 localhost fallback | grep 发现 `localhost:[port]` | Bug | AUTO-FIX: 替换为外网域名 |
| TabBar iconPath 为空 | `pages.json` iconPath 是空字符串 | 未实现 | AUTO-IMPLEMENT: 自定义 TabBar + Iconify参见 ./reference/platform-uni-app.md §1+§2 |
| Emoji 充当功能图标 | icon/btn 元素内含 emoji 字符 | 未实现 | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss小程序禁止 SVG data URI 等不支持的替代方案** |
| SVG data URI 充当图标/装饰 | `grep -rn "data:image/svg+xml" app/mini-program/src/` 在 CSS/style 中发现 `url("data:image/svg+xml...")``url('data:image/svg+xml...')` | Bug(平台不兼容) | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss/vite将所有 SVG data URI 替换为 Iconify 类名,⛔ 禁止用另一种 SVG data URI 替代原来的** |
| Tailwind/Iconify 未配置 | mini-program 无 tailwindcss-icons | 未实现 | AUTO-IMPLEMENT: 完整安装配置(参见 ./reference/platform-uni-app.md §1 |
| Phase 衔接跳步 | Phase 0V 未完成就进入 Story 验证 | 阻断 | 不得开始 Story 验证直到 Phase 0V COMPLETE GATE 通过 |
| 三写不一致 | pipeline-state 和 pipeline-context 的 done 计数不匹配 | 阻断 | 不得标记 Story 完成,必须定位漏写的文件 |
| 用户旅程未执行 | `pipeline-state` user_journeys 为空 | 阻断 | 不得标记 complete必须回退执行 |
| 未实现功能占位符 | 前端代码含"暂未开放"/"功能开发中"/"暂未实现"等占位文本 | 未实现 | AUTO-IMPLEMENT: 实现该功能 |
| 旅程步骤使用 curl 替代 Playwright | 旅程中用户操作步骤(点击/填写/导航/查看页面)使用了 curl 而非 browser_click/type/navigate/snapshot | 阻断 | 旅程标记 FAIL必须使用 Playwright 重做 |
| 旅程 FAIL 未触发修复 | FAIL 被记录但未执行 AUTO-FIX/AUTO-IMPLEMENT/AUTO-DATA-FIX 分类判定和修复流程 | 阻断 | 必须执行分类判定并触发对应修复流程 |
| 即时使用数据仅 Toast 传递 | 验证码等下一步骤需要的数据仅通过 Toast 显示且源码无 console.log 且无自动填充 | Bug | AUTO-FIX(#14): 添加 console.log + 自动填充 |
| 框架编译 input 不可交互 | `browser_type` 失败后 `browser_evaluate` 诊断发现内部 input `height: 0px`(如 uni-app `uni-input` 在 flex 容器中无显式 height | Bug | AUTO-FIX(#6): 修复 CSS 添加显式 height/width |
| 页面有内容但零交互元素 | `browser_evaluate` 确认 `interactiveCount===0``hasContent=true`(用户打开页面看到标题但无任何 button/input/a/form 可操作) | Bug(#5) 或 未实现(#3) | 诊断分流:框架已挂载+源码无交互→AUTO-IMPLEMENT(添加 redirect/导航)框架未挂载→AUTO-FIX(hydration) |
| UI 硬编码英文枚举值 | `browser_evaluate` 检测页面可见文本含 APPROVED/REJECTED/PENDING/PAID/CANCELLED/REFUNDED/DRAFT/ONGOING/COMPLETED 等英文大写枚举(适用于所有前端页面,不限于管理后台) | Bug(#15) | AUTO-FIX: 添加中文映射函数(管理后台 statusLabel / 小程序 computed |
| 约束声明与代码行为不一致 | 旅程描述含"可选/可跳过/选填"或页面 UI 含"(选填)/(可选)"标记但代码实际有必填验证Toast 含"请上传/请填写/不能为空"或源码 `if (!field)` 拦截) | 约束不一致(#16) | JOURNEY-FIX: 修复旅程定义 / AUTO-FIX: 修复代码或 UI 文案,使声明与行为一致 |
| analyze_image 视觉验证未执行 | pipeline-state 无 `analyze_image_called: true` 或 verification_detail.design_check 缺失 | 阻断 | 必须回退执行 analyze_image 后才能标记完成(见 ./visual-verification.md |
| 双重导航栏 | 页面在 pages.json 中有 `navigationBarTitleText`(非 custom且页面源码含 `position:fixed` 的自定义 header 元素,导致系统导航栏与自定义头部同时显示(截图中可见两层标题) | Bug(#18) | AUTO-FIX: pages.json 该页面添加 `navigationStyle: "custom"` + 页面 header CSS 添加 safe-area padding参见 ./reference/platform-uni-app.md §3 |
| API route 无 try-catch | API 返回 500 + `Read` 源码发现 handler 函数无 try-catch 包裹(异常直接返回原始 500 且无日志) | Bug(#20) | AUTO-FIX: 添加 try-catch 包裹 + console.error + JSON 500 响应(参见 ./auto-fix-engine.md #20 |
| 页面 navigateBack 后数据陈旧 | `uni.navigateBack()` 返回目标页后 `browser_snapshot` 数据未更新 + 源码用 `onMounted``onShow` | Bug(#21) | AUTO-FIX: 替换 `onMounted``onShow`from `@dcloudio/uni-app`,参见 ./auto-fix-engine.md #21 |
> 完整的 FAIL 分类标准(含兜底规则 #99和修复流程见 ./auto-fix-engine.md 第 2-4 节
**违反此规则的行为**
- ❌ 检测到溢出后写"需注意"然后标记 done
- ❌ analyze_image 报告 FAIL 但 Story 仍标记 verified
- ❌ 跳过用户旅程测试直接标记 Epic 完成
- ❌ 发现 localhost fallback 但标记"已通过 env 覆盖"然后 done
- ❌ 发现 emoji 当图标但写"视觉可接受"然后 done
-**将视觉问题标记为任何非检测即阻断表中定义的分类**(模式性禁止)
- 禁止的分类包括但不限于:"已知遗留项"、"技术债"、"后续优化"、"暂时保留"、"非本次引入"
- 唯一允许的分类检测即阻断表中的分类Bug/未实现/阻断)
-**将修复从 AUTO-FIX/AUTO-IMPLEMENT 降级为"仅记录"或"WARN"**
-**推迟视觉验证修复到"旅程完成后"或"步骤 4 修复阶段"**(必须在关卡内立即执行)
- ❌ 发现 pages.json iconPath 为空但标记"TabBar 可见"然后 done
- ❌ 页面有静态标题但无交互元素(用户无法操作)时标记"渲染通过"
- ❌ 遇到零交互元素但不诊断原因直接判定为 hydration Bug可能是缺少 redirect
- ❌ 任何前端页面直接显示英文枚举值(如 APPROVED/REJECTED/PENDING但标记"显示正常"
- ❌ 旅程说"可跳过/可选/选填"但代码必填验证拦截后仅静默补填继续(不标记 Constraint Mismatch
- ❌ 页面 UI 含"(选填)/(可选)"标记但后端/前端实际必填(不标记 UX Mismatch
- ❌ API 返回 500 但未检查源码 try-catch 就标记"环境问题"或"已知问题"
- ❌ 页面 navigateBack 后数据未刷新但标记"已通过 API 验证"(必须检查 onShow
## 铁律六:页面变更必须同步原型
**铁律一说速度,铁律二说质量,铁律三说粒度,铁律四说深度,铁律五说自愈,铁律六说原型同步。**
任何涉及新 UI 页面或页面修改的 Story完成后必须同步更新原型系统。原型是 UI 的唯一参考来源,代码实现与原型不同步 = 规格与实现不一致。
```
❌ 错误做法(实现后不管原型):
Story 7-4 实现了批量任务面板 UI → 原型仍只有 Epic 1-6 的页面
结果:新页面在原型中无法预览,文档中心缺少新页面卡片
✅ 正确做法(实现后同步原型):
Story 7-4 实现了批量任务面板 UI
→ 原型系统更新:添加批量任务面板页面
→ 文档中心更新docs-index.html 添加新文档卡片
→ 统一入口更新index.html 更新页数和快速导航
```
**判断规则**
| Story 类型 | 原型同步 | 文档中心更新 | 入口页更新 |
|------------|---------|------------|-----------|
| 新 UI 页面 | **必须** | **必须** | **必须** |
| 修改现有 UI 布局/组件 | **必须** | 可选 | 可选 |
| 纯后端/API | 跳过 | 可选 | 跳过 |
| 纯配置/Schema | 跳过 | 跳过 | 跳过 |
**违反此规则的行为**
- ❌ 实现了新 UI 页面但原型中无对应页面
- ❌ d8d-iterate feature 类型涉及新页面但跳过 Phase 3
- ❌ 原型页面更新了但文档中心未更新
- ❌ 统一入口页 (index.html) 页数未更新或快速导航缺少新页面
- ❌ 用"遵循现有 UI 模式"作为跳过原型的理由

View File

@@ -0,0 +1,453 @@
# 旅程真实性规则(⛔ 统一规则集 — 定义和验证共享)
> **Read fully and follow** when referenced by journey-mode.md.
>
> 被以下流程引用:
> - 子模式 1 步骤 3.2(旅程生成后自检)
> - 子模式 2 步骤 1.5(执行前校验与自动修复)
> - 其他任何需要校验旅程定义的流程
---
## 规则 R1用户步骤禁止引用开发专用元素
### 检测方法
1. 扫描旅程文件的"用户视角步骤"部分
2. 匹配禁止模式:
| 禁止模式 | 匹配规则 | 违反示例 |
|---------|---------|---------|
| Mock 开发桩 | 包含 "Mock 登录" "Mock 支付" "Mock 按钮" "[开发]" | ❌ "用户点击 Mock 登录按钮" |
| 测试固定数据 | "13900001111" "test-user" "13900000000" 在用户步骤中 | ❌ "用户输入手机号 13900001111" |
| 环境判断代码 | "v-if" "isDev" "DEV" "NODE_ENV" 在用户步骤中 | ❌ "如果 isDev 则点击 Mock 按钮" |
**通过标准**:用户视角步骤中 0 条匹配。
### 修复策略
```
遇到"Mock 登录" →
读取 login/index.vue 源码:
├── 手机号+验证码已实现 → 替换为"用户输入手机号 → 获取验证码 → 输入验证码 → 点击登录"
│ 技术验证注明"⚠️ 开发环境POST /api/auth/send-code 直接返回验证码(非 Mock是标准开发实践"
└── 手机号+验证码未实现 → 触发 AUTO-IMPLEMENT:
1. 后端: POST /api/auth/send-code {phone} → 开发环境返回 {code}, 生产环境发送短信
2. 后端: POST /api/auth/login {phone, code} → 验证码校验 + JWT
3. 前端: 启用 login/index.vue 已有的手机号表单 + 实现 sendCode/handlePhoneLogin
4. 删除 Mock 登录按钮及相关代码
遇到"Mock 支付" →
替换为"用户确认支付"(后端沙箱/Mock 对用户透明)
技术验证步骤可注明"开发环境支付自动成功"
遇到测试固定数据 →
替换为泛化描述:"用户输入手机号"(具体号码移到技术验证步骤或前置条件)
```
---
## 规则 R2功能可用性必须与源码一致
### 检测方法
1. 对旅程中涉及的关键功能,读取对应页面/组件源码
2. 检查功能是否标记为"暂未开放" / "开发中" / "暂未实现"
```bash
# 自动检测命令
grep -rn "暂未开放\|开发中\|暂未实现\|功能暂未" app/mini-program/src/pages/ app/admin/src/app/
```
### 判定
- 功能已实现 → 正常写入旅程 ✅
- 功能标记"暂未开放" → 旅程中不能作为用户步骤 ❌
- 修复:移除该步骤,或标注为"前置条件:需先实现该功能"
- 在验证模式中触发 AUTO-IMPLEMENT
---
## 规则 R3环境适配 — 旅程必须适配当前测试环境
### 检测方法
```bash
# 检测条件编译
grep -rn "#ifdef H5\|#ifdef MP-WEIXIN\|#ifdef APP-PLUS" app/mini-program/src/pages/
# 检测环境判断
grep -rn "import.meta.env.DEV\|process.env.NODE_ENV\|isDev" app/mini-program/src/pages/
```
### 环境适配决策树
```
读取 pipeline-context.md → 确定测试环境
对每个旅程步骤检查:该操作在当前环境是否可用?
├── 可用 → 正常步骤
├── 不可用但有替代方案 → 改用替代方案(标注)
└── 不可用且无替代 → 标注"⚠️ 环境限制" + 使用可用的开发桩
```
### 环境适配表(动态生成 — AI 读取源码后填入)
执行校验时AI 读取相关源码后自动生成此表:
| 用户操作 | 微信小程序 | H5 | 当前环境适配 |
|---------|-----------|-----|------------|
| 微信一键登录 | ✅ 可用 | ❌ Toast 提示不可用 | H5: 不可用(已知,不影响旅程) |
| 手机号+验证码 | ✅ 可用 | ✅ 可用(开发环境验证码直接返回) | 正常 — 旅程验证的标准登录方式 |
| 微信支付 | ✅ 可用 | ❌ N/A | H5: 开发环境自动成功 |
| Admin 登录 | N/A | ✅ 可用 | 正常 |
---
## 规则 R4凭据隔离
### 规则
- ✅ 前置条件:`Admin 账号可用(凭据见 pipeline-context.md`
- ✅ 技术验证步骤:`POST /api/admin/auth/login body={username, password}`(凭据值可出现)
- ❌ 用户视角步骤:`Admin 输入用户名 superadmin、密码 admin123`
- ✅ 用户视角步骤:`Admin 输入账号密码 → 点击"登录"按钮`
### 修复
将凭据从用户步骤移到前置条件或技术验证步骤。
---
## 规则 R5API 标注与用户行为对应
### 规则
用户操作名称 → 技术验证中的 API 名称必须一致:
| 用户视角操作 | 正确的技术验证标注 | 错误标注 |
|-------------|------------------|---------|
| 用户点击"微信一键登录" | `POST /api/auth/login {code}` | "Mock 登录 API" |
| 用户确认支付 | `POST /api/orders/[id]/pay` | "Mock 支付 API" |
| 用户申请退款 | `POST /api/orders/[id]/refund` | — |
技术验证步骤可注明"⚠️ 开发环境说明:后端自动 Mock 微信 OAuth 返回",但不能改变 API 名称或标注为 "Mock"。
---
## 规则 R6用户操作可行性兜底原则
> **⛔ 通用规则,不需要为每种操作类型单独定义子规则。**
### 核心原则
旅程中的每个用户操作如果执行失败Playwright 无法完成),必须判断:
-**Bug**(真实用户也无法完成)→ AUTO-FIX
- 还是 **环境限制**(真实用户可以完成但 Playwright 不能)→ 找 Playwright 替代方案
### 判定流程(通用兜底)
```
用户操作执行失败browser_type/click/file_upload 等)
browser_snapshot 检查页面 → 验证真实用户是否也会失败
├── 有 Toast/错误提示 → 读提示内容 → 按提示处理
├── 无任何提示 + 操作无反应 → 验证真实用户体验:
│ ├── 真实用户也无法完成 → Bug → AUTO-FIX
│ └── 真实用户可以完成 → Playwright 适配(优先用 Playwright 工具)
└── 需要特定环境能力 → 环境限制 → ENV-ADAPT
```
### 表单校验提示可见性规则
**⛔ 表单校验失败时,错误提示必须持久可见(不能仅用 Toast**
理由Toast 在几秒内消失Playwright snapshot 时可能已消失,无法读取失败原因。
更重要的是:对真实用户来说,持久可见的内联错误提示也是更好的用户体验。
**修复策略(校验失败但看不到提示 → Bug → AUTO-FIX**
```
校验失败 → browser_snapshot 检查:
├── 看到内联错误文字 → 读取 → 按提示处理
├── 看到 Toast → 读取 → 按提示处理(⚠️ Toast 会消失,应同时修复为内联提示)
├── 无任何提示 + browser_console_messages 有错误 → Bug提示仅 console 无 UI
│ → AUTO-FIX: 将 Toast 改为内联错误提示(显示在对应字段旁)
├── 无任何提示 + console 也无错误 → BugUX 缺陷:校验拦截但用户看不到原因)
│ → AUTO-FIX: 添加 console.error + 内联错误提示
└── 校验通过但操作仍无反应 → 走通用判定流程
```
**AUTO-FIX 修复标准**
- 表单校验错误应显示在**对应字段下方/旁边**(内联提示),不用 Toast
- 同时在 `console.error` 中打印校验失败原因(方便 Playwright 读取)
- Toast 可作为**辅助**保留,但不能是唯一的错误提示方式
### 场景 B: 即时使用数据仅通过 Toast 传递
操作成功后产生的数据,如果**下一步骤立即需要使用**(如验证码需输入到表单),
且该数据**仅通过 Toast 传递**(无 console.log、无自动填充、无页面持久化
则 Toast 消失后旅程执行无法获取该数据 → 流程卡死。
**三条件判定(必须同时满足)**
| 条件 | 说明 |
|------|------|
| 1. 下一步骤需要该数据 | 验证码、临时 token 等即时使用数据 |
| 2. 仅通过 Toast 传递 | 无 console.log / 无自动填充 / 无页面持久化 |
| 3. Toast 消失后无法回溯 | 后续页面不显示、API 不返回历史记录 |
**不适用场景(以下不是问题)**
- 订单号、支付结果 → 后续页面自然显示,不需要从 Toast 提取
- 创建成功提示 → ID 在后续页面可查
- 普通操作反馈("已收藏"、"已取消")→ 不涉及后续步骤需要的数据
**检测方法**
```
1. 源码扫描:检查 uni.showToast 是否传递了下一步骤需要的数据
grep -rn "uni\.showToast" app/ | grep -i "code\|验证码\|token"
2. console.log 检查:确认是否有对应的 console 记录
grep -rn "console\.log" app/ | grep -i "code\|验证码"
3. 自动填充检查:确认数据是否被写入 ref/reactive
```
**修复策略AUTO-FIX**
```
发现即时使用数据仅通过 Toast 传递且无 console.log:
1. 添加 console.log开发环境:
console.log('[DEV] 验证码:', res.code)
2. 自动填充(验证码场景):
code.value = res.code || ''
3. Toast 保留作为辅助,不作为唯一数据载体
修复示例sendCode:
// 修复前:
uni.showToast({ title: `验证码已发送:${res.code}`, icon: "none" })
// 修复后:
console.log('[DEV] 验证码:', res.code)
if (res.code) { code.value = res.code }
uni.showToast({ title: "验证码已发送", icon: "success" })
```
### 通过标准
- 操作失败时必须先 `browser_snapshot` 确认页面状态
- 表单校验错误必须有持久可见的 UI 提示(非仅 Toast
- 即时使用数据必须有 console.log 或自动填充,不能仅依赖 Toast
- 框架编译导致的元素不可交互必须通过 `browser_evaluate` 诊断 CSS 根因
- 不为每种操作类型单独定义规则,用此通用流程兜底
### 场景 C: 框架编译导致元素不可交互
`browser_type``browser_click` 失败时,如果 `browser_evaluate` 诊断发现:
- 元素是框架编译产物(如 `<uni-input>` 包裹原生 `<input>`
- 内部原生元素的 CSS 尺寸为 0`height: 0px` / `width: 0px`
- 这是框架编译 CSS 计算问题,不是事件绑定或 DOM 缺失
**判定**: Bug影响真实用户→ AUTO-FIX修复 CSS不是 Playwright 适配)
**诊断流程**:
1. `browser_type` 失败 → `browser_evaluate` 检查元素 DOM 结构和计算样式
2. 发现 `innerInputs[0].height === '0px'` → 框架编译 CSS Bug
3. 读取源码,找到对应 CSS通常是 flex 容器中 input 无显式 height
**修复策略**:
1. 读取源码,找到框架组件对应的 CSS
2. 给内部 input 或包裹容器添加显式 `height`/`width`
3. 重测 `browser_type`(不用 `browser_run_code` 绕过)
4. 修复后 `browser_type` 通过 → PASS / 仍失败 → 标记 journey-step-fail
**典型场景**: uni-app `<input>` 在 flex 容器中使用 `flex: 1` 且无显式 `height`,编译后内部 `<input>` 计算为 `height: 0px`
---
## 规则 R7约束声明必须与代码实际行为一致
> **⛔ 通用规则,检测旅程描述和页面 UI 文案中的约束声明与代码实际验证逻辑的不一致。**
### 为什么需要 R7
当旅程定义或页面 UI 告诉用户"某步骤/字段可选",但代码实际强制必填时:
- 旅程验证会"静默补填继续",不标记为问题
- 真实用户会被意外的校验错误困惑("明明说可选的?"
- 这是比功能缺失更隐蔽的问题:**用户被误导了**
### R7-A旅程步骤约束 vs 源码验证逻辑
```
检测方法:
1. 扫描旅程"用户视角步骤"中的约束关键词:
- 可选/可跳过/选填/optional/skip/跳过/使用占位图
- 必填/必须/required/不可为空
2. 对含约束描述的步骤,读取对应页面源码的提交/下一步函数:
- 搜索 if (!field) / required / validate
- 搜索 Toast/提示中的"请上传/请填写/请选择/不能为空"
3. 比对旅程约束 vs 代码实际验证
```
### R7-B页面 UI 文案 vs 代码/接口实际约束
```
检测方法:
1. browser_snapshot / analyze_image 后,扫描页面文本中的约束标识:
(选填)/(可选)/(必填)/ * /required
2. 对含约束标记的字段,读取对应源码:
- 前端 JS表单提交函数中该字段的验证逻辑
- 后端 API接口对该字段是否 required
3. 比对 UI 文案 vs 前后端实际约束
自动触发时机:
- 旅程验证执行时 browser_snapshot 显示含约束标识的字段标签
- analyze_image 检测到页面含"选填/可选"文本
```
### 判定表
| 声明方 | 声明内容 | 代码实际 | 判定 | 分类 |
|-------|---------|---------|------|------|
| 旅程描述 | 可选/可跳过 | 必填验证存在 | ❌ 不一致 | Constraint Mismatch |
| 旅程描述 | 必填/必须 | 无验证 | ⚠️ 遗漏验证 | Bug / Journey Update |
| 页面 UI | (选填) | 前端/后端必填 | ❌ Bug | UX Mismatch |
| 页面 UI | *(必填标记) | 无验证 | ⚠️ 遗漏验证 | Bug |
| 旅程描述 | 未提及约束 | 有验证 | ⚠️ 旅程遗漏 | Journey Update |
### 修复策略
```
检测到 Constraint Mismatch / UX Mismatch:
判断正确设计意图:
├── PRD/UX 说可选 → 旅程/UI 正确 → AUTO-FIX 代码(移除必填或改为可选)
├── PRD/UX 说必填 → 代码正确 → AUTO-FIX 旅程定义或 UI 文案
└── 无法判断 → 标记 WARN + 记录到 pending_fixes
同时修复旅程定义使其准确反映代码行为
执行时按代码实际行为执行
```
### 执行时约束检测(⛔ 强制)
旅程验证执行过程中,每个用户步骤首次操作被拦截时:
```
操作被 Toast 拦截(含"请上传/请填写/请选择/不能为空"
检查触发条件(双条件):
条件 A: 旅程描述含"可跳过/可选/选填/skip/optional"
条件 B: browser_snapshot 页面含"(选填)/(可选)"文本
A 或 B 任一满足 + Toast 拦截 → Constraint Mismatch (FAIL #16)
→ 不得静默补填继续,必须标记并记录
→ 读取源码确认 → 按设计意图修复
A 和 B 均不满足 → 普通 FAIL #12 (ENV-ADAPT) → 按提示补填继续
```
### 通过标准
- 旅程中含约束关键词的步骤,与源码验证逻辑一致
- 页面上含"选填/可选/必填"标记的字段,与前后端验证一致
- 不一致项必须有修复动作,禁止仅记录不修复
---
## 规则 R8页面/视图覆盖深度(⛔ 强制)
### 核心原则
应用的每个独立视图/页面必须有足够的旅程覆盖深度。
"路过式"覆盖(仅 1 步提及)不等于真正的功能验证。
**为什么需要 R8**: FR 级覆盖检查是二值判断("提到就算覆盖"),无法发现:
- 一个组件有 10 个交互元素,但旅程只提及了 1 个
- 一个主视图被其他旅程的 1 步"路过"覆盖,但筛选器联动、分页、导出等核心交互完全未验证
### 最低覆盖标准(⛔ 100% 强制)
| 组件交互元素数 | 最低覆盖要求 | 未满足时 |
|--------------|-------------|---------|
| 1-2 个 | 至少 1 步涉及 | 自动补旅程步骤 |
| 3-5 个 | 至少 1 条旅程含 ≥2 步专门操作该视图 | 自动补旅程步骤 |
| ≥6 个 | 必须有 1 条专门旅程 | 自动补专门旅程 |
| **任何** | **每个交互元素必须被 ≥1 步覆盖** | **未覆盖 → 自动补** |
### 交互元素识别方法
```
1. 从 app-client.tsx 或路由配置提取所有独立视图:
grep -n "currentView\|view ===\|render.*View\|component" src/components/features/app-client.tsx
2. 对每个视图组件,统计用户交互元素:
grep -c "onClick\|handleClick\|onChange\|handleSelect\|onSubmit" src/components/features/{view-component}.tsx
3. 具体分类:
- 按钮: onClick / handleClick / button
- 输入框: input / textarea / type="text"
- 选择器: select / dropdown / combobox / Select
- 分页: page / pagination / nextPage / setPage
- 导出: export / download / csv / excel
- 弹窗: dialog / modal / open / close
```
### 检测方法
```
1. 提取视图列表: 读取 app-client.tsx识别 currentView 的所有可能值
2. 对每个视图组件:
a. 读取源码,统计交互元素数量
b. 读取所有旅程文件,统计该组件被覆盖的步骤数和覆盖深度
c. 逐元素判定(⛔ 每个交互元素单独判定):
- 该元素被 ≥1 条旅程步骤覆盖 → ✅ PASS
- 该元素未被任何旅程步骤覆盖 → ❌ FAIL → 自动补旅程
3. 输出组件覆盖矩阵
```
### 修复策略(⛔ 强制自动补 — 不允许仅 WARN
```
检测到任何交互元素未被覆盖:
1. 分析组件的所有交互元素 → 列出未覆盖清单
2. 对每个未覆盖元素:
a. 确定最佳补充策略:
- 已有同视图专门旅程 → 在该旅程中追加步骤
- 无专门旅程 → 新建一条旅程(以该视图为主场景)
b. 生成旅程步骤:
- 用户视角: 自然导航到该元素 + 操作 + 预期结果
- 技术验证: browser 操作 + API 断言
- 验收标准: 元素可交互 + 效果正确
c. 遵循 R1-R8 规则校验
d. 写入对应 Epic 旅程文件
3. 更新覆盖度报告
4. 重新校验max 3 次)
5. 仍失败 → 输出详细未覆盖报告
```
### 通过标准
- 每个独立视图/页面的**每个交互元素**都被至少 1 条旅程的步骤覆盖(⛔ 100%
- 交互元素 ≥ 6 的视图必须有 1 条专门旅程
- 不允许"仅路过 1 步"覆盖有 ≥ 3 个交互元素的组件
- ⛔ 不允许任何交互元素仅被标记 WARN 而不补充 — 必须自动补旅程
---
## 自动校验执行流程(定义和验证通用)
```
步骤 1逐规则校验R1 → R2 → R3 → R4 → R5 → R7 → R8
步骤 2汇总结果
├── 全部 PASS → 返回校验通过
└── 有违规 → 自动修复旅程文件 → 重新校验max 3 次)
步骤 3修复后仍失败3 次上限)
→ 输出失败报告,逐项列出无法自动修复的项
→ 定义模式:阻断进入 FR 覆盖度评估
→ 验证模式:阻断进入旅程执行
```

View File

@@ -0,0 +1,754 @@
# 旅程模式
> 专用用户旅程定义与验证模式。与验证模式并行,聚焦"真实用户行为"而非"功能点存在性"。
## 为什么需要旅程模式
**用户旅程 ≠ 单元测试 ≠ Story 级验证**
| 维度 | Story 验证 | 用户旅程 |
|------|-----------|---------|
| 目标 | 验证功能点存在且正确 | 验证用户能完成目标 |
| 入口 | 直接 navigate 到目标页面 | 从用户自然入口开始 |
| 范围 | 单个功能点 | 跨页面完整流程 |
| 断言 | API 返回 200 | 数据状态变化符合预期 |
| 失败模式 | "功能不存在" | "用户找不到入口/流程断了" |
Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这个盲区。
## 旅程模式入口
用户选择"旅程模式"后,询问子模式:
1. **旅程定义** — 为所有 Epic 生成/重写 `epic-N-journeys.md`
2. **旅程验证** — 按真实用户行为执行每个 Epic 的旅程
## 子模式 1旅程定义
### 流程
```
1. 读取 PRD + Epic/Story 定义 + sprint-status.yaml
2. 读取入口配置:
- 小程序: 参见 ./reference/platform-uni-app.md §4入口页面 = pages.json 第一个 page
- 管理后台: app/admin/src/app/ 路由结构(根 URL = 自然入口)
3. 为每个 Epic 生成/重写 epic-N-journeys.md
- 严格遵循旅程真实性规则(⛔ 见 ./journey-authenticity-rules.md
- 使用增强版旅程格式
3.2 生成后自检(⛔ 强制)
**Read fully and follow: ./journey-authenticity-rules.md**
- 读取刚生成的旅程文件 + 对应页面源码
- 按规则 R1-R5 逐条校验
- 违规 → 自动修复 → 重新校验max 3x
3.5 覆盖度评估FR 追踪矩阵交叉检查):
a. 读取 `03-epics-and-stories.md` 的 "FR 覆盖追踪矩阵" 表格
b. 读取所有 `epic-N-journeys.md` 中每条旅程涉及的技术验证步骤
c. 逐一比对:每个 FR 是否被至少 1 条旅程的验收标准或技术验证步骤覆盖
d. 计算 FR 覆盖率 = 已覆盖 FR 数 / 总 FR 数
e. 覆盖度门控:
- 100% → ✅ PASS
- 80-99% → ⚠️ WARN列出未覆盖 FR建议补充旅程
- <80% → ❌ FAIL必须补充旅程后才能进入验证子模式
f. 输出覆盖度报告到 `_bmad-output/implementation-artifacts/journey-coverage-report.md`
3.6 组件级覆盖校验(⛔ 强制 — FR 检查通过后必须执行):
⛔ 目的FR 级检查是二值判断("提到就算覆盖"),无法发现"1 步路过式覆盖"的问题。
此步骤深入到组件交互元素粒度,确保每个用户可操作的元素都被旅程验证。
a. 识别应用的所有主要视图/页面:
- 从 app-client.tsx 或路由配置提取视图列表(如 batch-list / batch-dashboard / batch-results
- 从 src/app/ 目录结构提取独立页面路由
b. 对每个主要视图,读取对应组件源码,提取所有用户交互元素:
- 按钮: onClick / handleClick / button 元素
- 输入框: input / textarea / type="text"
- 下拉选择: select / dropdown / combobox / Select 组件
- 筛选器/过滤器: filter / 筛选
- 分页控件: page / pagination / nextPage / setPage
- 导出/下载: export / download / csv / excel
- 对话框/弹窗: dialog / modal / open / close
c. 比对交互元素 vs 所有旅程的技术验证步骤:
- 每个交互元素必须被至少 1 条旅程的某个步骤覆盖
- 计算每个组件的覆盖深度 = 被旅程步骤覆盖的交互元素数 / 总交互元素数
d. 覆盖深度门控(⛔ 100% 覆盖强制 — 任何缺口必须自动补旅程):
| 组件交互元素数 | 最低覆盖要求 | 不满足时 |
|--------------|-------------|---------|
| 1-2 个 | 至少 1 步涉及 | ❌ FAIL → 自动补旅程 |
| 3-5 个 | 至少 1 条旅程含 ≥2 步专门操作该视图 | ❌ FAIL → 自动补旅程 |
| ≥6 个 | 必须有 1 条专门旅程(以该视图为主场景) | ❌ FAIL → 自动补旅程 |
| 任何数量 | 每个交互元素必须被覆盖 | 未覆盖元素 → 自动补到已有旅程或新建旅程步骤 |
e. 输出组件覆盖矩阵到覆盖度报告:
| 组件 | 交互元素数 | 被覆盖数 | 覆盖深度 | 旅程来源 | 状态 |
|------|-----------|---------|---------|---------|------|
f. 门控汇总(⛔ 100% 强制 — 无 WARN只有 PASS 或自动补):
- 所有组件所有交互元素 100% 覆盖 → ✅ PASS
- 有任何交互元素未被覆盖 → ❌ FAIL → 自动补充旅程步骤(见下方 g
g. 自动补旅程流程(⛔ 强制 — FAIL 后必须执行):
1. 列出所有未覆盖的交互元素(组件名 + 元素名 + 元素类型)
2. 判断补充策略:
a. 已有同视图的专门旅程 → 在该旅程中追加步骤覆盖缺失元素
b. 无同视图专门旅程 → 新建一条旅程,以该视图为主场景
3. 生成旅程步骤内容:
- 用户视角步骤: 从自然导航入口到操作该元素的完整路径
- 技术验证步骤: browser_click/type/select + 验证 API 调用
- 验收标准: 元素可交互且产生预期效果
4. 遵循旅程真实性规则 R1-R8⛔ 必须通过校验)
5. 写入对应 Epic 的旅程文件
6. 更新覆盖度报告的组件覆盖矩阵
7. 重新执行步骤 3.6.a-f 校验max 3 次)
8. 3 次后仍有未覆盖元素 → ❌ FAIL + 输出详细报告
4. 输出: "已生成/重写 N 个 Epic 旅程文件 + FR 覆盖率 X% + 组件覆盖率 Y%"
```
### ⛔ 用户旅程真实性原则
**必须严格遵守,违反 = 旅程无效 = 不许进入验证**
详细检测方法和自动修复策略见 **./journey-authenticity-rules.md**(统一规则集 R1-R5
**规则 1起始页必须是自然入口**
- 小程序:起始页 = 用户打开网址后看到的第一个页面(参见 ./reference/platform-uni-app.md §4 入口页面)
- 管理后台:起始页 = 根 URL `/`(通常重定向到登录或 Dashboard
-`browser_navigate("/pages/login/index")` — 用户不会直接输入登录页 URL
-`browser_navigate("http://mini-xxx.dev.d8d.fun/")` — 用户打开网址看到首页
**规则 2操作路径必须可发现**
- 每一步操作必须是用户"看得见、点得到"的
- 必须描述用户**看到什么 → 做什么操作**
- ❌ "用户进入局头申请页"(怎么进去的?没写)
- ✅ "用户点'我的'Tab → 看到'申请成为局头'按钮 → 点击进入申请页"
**规则 3前置条件必须明确且可验证**
- 每个旅程必须写明"操作前系统/用户应处于什么状态"
- ❌ "用户提交申请"(什么用户?什么状态?)
- ✅ "前置:用户已登录(有 token且 organizer=null无局头身份"
**规则 4数据断言必须具体**
- 每个断言必须是可量化、可比较的
- ❌ "登录成功返回 JWT token"(太宽泛,任何返回都算通过)
- ✅ "用户 A 登录后返回的 userId ≠ 用户 B 登录后的 userId"
- ❌ "申请提交成功"(只检查 HTTP 200
- ✅ "申请前 organizer=null申请后 organizer.status=PENDING"
**规则 5跨越页面必须连续导航**
- 页面跳转必须通过用户操作触发(点击/提交),不得 `browser_navigate` 直接跳转
-`browser_navigate("/pages/organizer-apply/index")`
- ✅ 用户点击"申请成为局头"按钮 → 页面跳转到申请页
### 增强版旅程定义格式
```markdown
# Epic N 用户旅程
## 旅程 1: {名称}
- **起始页**: {自然入口 URL}
- **前置条件**: {用户状态、系统状态、数据状态}
- **跨越页面**: {涉及的页面列表}
### 用户视角步骤
1. 用户打开 {URL} → 看到 {首页/落地页}
2. 用户点击 {元素} → 跳转到 {页面}
3. 用户在 {页面} 看到 {内容} → 点击/填写 {操作}
4. ...
5. 用户最终看到 {结果}
### 技术验证步骤
1. {验证页面渲染}(无空白/404/错误)
2. {验证 API 调用}(具体的请求和响应断言)
3. {验证数据状态变化}(操作前 vs 操作后)
4. ...
### 数据状态变化
| 阶段 | 状态 |
|------|------|
| 操作前 | {状态快照} |
| {步骤N}后 | {状态变化} |
| 最终 | {预期最终状态} |
### 验收标准
- [ ] {具体可量化的断言1}
- [ ] {具体可量化的断言2}
- [ ] {全程使用自然导航路径}
```
### 旅程来源优先级
1. **PRD 用户场景**(最高优先级) — PRD 中描述的用户故事和使用场景
2. **Epic/Story 验收标准组合** — 多个 Story 的验收标准串联成完整流程
3. **已实现的路由/导航逻辑** — 从代码中提取的页面跳转关系
### 最低覆盖要求
- 每个非基础设施 Epic 至少 1 条旅程
- 纯基础设施 Epic如 Epic 0可豁免
- 每条旅程必须跨越至少 2 个页面
- 每条旅程必须包含至少 1 个数据状态变化
---
## 子模式 2旅程验证
### 流程
```
1. 读取所有 epic-N-journeys.md
1.5 执行前校验与自动修复(⛔ 强制)
**Read fully and follow: ./journey-authenticity-rules.md**
- 按统一规则 R1-R5 + R7 校验所有旅程文件
- R7 约束一致性校验:对含"可选/可跳过/选填"关键词的步骤grep 对应源码验证逻辑
- 不一致 → 自动修复旅程定义(使其准确反映代码)→ 重新校验
- 违规 → 自动修复旅程文件 → 重新校验max 3x
- 环境适配:读取 pipeline-context.md 确定当前环境,按 R3 适配
1.8 生成步骤级验证清单(⛔ 强制 — 写入 verification-checklist.yaml
- 解析每个旅程的「技术验证步骤」→ 为每个步骤生成 step 条目
- 提取验收标准 → 生成 acceptance_criteria 条目
- 写入/追加到 verification-checklist.yaml 的 journeys 部分:
```yaml
journeys:
epic_N:
journey_M:
name: "{旅程名称}"
steps:
- id: "jN-M-s{S}"
action: "{操作描述}"
expected: "{预期结果}"
screenshot_required: true
screenshot_path: "tests/e2e/screenshots/journey-epicN-M-stepS.png"
analyze_image_required: true
status: "pending"
acceptance_criteria:
- criterion: "{验收标准}"
status: "pending"
```
- 如 verification-checklist.yaml 已存在 → 跳过已 pass 的步骤,从 pending 步骤恢复
2. 询问: 验证全部 Epic 还是指定 Epic?(确定验证范围)
1.9 创建旅程任务清单(⛔ 强制 — 根据范围筛选,每条旅程一个 Task
- 为每条待验证旅程**创建独立的 Task 任务**,使用 `TaskCreate` 工具:
- 每个任务记录Epic 编号、旅程名称、旅程文件路径、前置条件
- 便于独立追踪每条旅程的验证进度
- 避免单个 agent 上下文过大导致污染或遗漏
任务格式示例:
- **主题**: `验证旅程 {旅程名称} (Epic {N})`
- **描述**:
```
执行用户旅程验证:
- Epic: {N}
- 旅程: {旅程名称}
- 文件: {旅程文件路径}
- 前置条件: {前置条件描述}
严格按照 journey-mode.md 执行模板执行完整验证流程。
```
1.10 启动逐个任务验证(每条旅程独立 agent
- 对**每个任务**启动一个**独立的 agent**,按照后续「执行模板」执行验证
- 独立 agent 避免上下文污染,执行更精准
⛔ **强制规则:禁止在主 agent 中一次性串行执行多条旅程**
3. 汇总所有任务结果 → 生成旅程验证报告
4. 更新 pipeline-state.yaml 最终状态
```
---
## 旅程验证核心原则
**核心原则:每条旅程独立 agent 执行,禁止一次性串行执行所有旅程。**
**最佳实践:每个旅程启动一个独立 agent分批并行执行避免上下文污染和 Playwright MCP 配额超限,执行更精准。**
所有旅程验证任务创建完成后,由独立 agent 按照后续「执行铁律」和「执行模板」依次执行单条旅程的完整验证。
---
## 执行模板(单条旅程验证)
```
前置已创建独立任务任务包含Epic 编号、旅程名称、旅程文件路径、前置条件
```
### ⛔ 执行铁律
**规则 E0定义先行校验⛔ 最高优先级)**
- 执行旅程前必须先校验旅程定义(步骤 1.5
- 校验 → 自动修复 → 重新校验,全自动化无需人工确认
- 校验通过后的旅程定义即为最终执行依据
**规则 E1必须从自然入口开始**
```
✅ 正确:
browser_navigate → "http://mini-xxx.dev.d8d.fun/" (首页)
browser_snapshot → 看到首页内容
browser_click → 点"我的"Tab
browser_snapshot → 看到登录入口
browser_click → 点登录按钮
❌ 错误:
browser_navigate → "http://mini-xxx.dev.d8d.fun/#/pages/login/index" (直接跳登录页)
```
**规则 E2不得跳过任何步骤**
- 旅程定义了 9 步,就必须执行 9 步
- 不得因为"上一步已经验证过"而跳过
**规则 E3断言失败触发自动修复**
- 任何验收标准不满足 → 记录 FAIL + 原因
- FAIL 后立即触发分类分流(详见执行模板步骤 3.5:
- Bug代码有但行为错→ AUTO-FIX (max 2x)
- 未实现(功能缺失)→ AUTO-IMPLEMENT (max 2x)
- 数据问题(状态不对)→ AUTO-DATA-FIX (max 1x)
- 环境限制(表单校验拦截)→ ENV-ADAPT (max 1x)
- UX 缺陷(校验失败但无提示)→ AUTO-FIX (max 2x)
- 修复后重测: PASS → 继续下一步骤 / FAIL → 记录 journey-step-fail继续下一步骤
- 旅程完成后: steps_failed=0 → PASS / steps_failed>0 → FAIL (含修复报告)
**规则 E3.5:操作无反应必须读页面提示(⛔ 强制)**
- 任何操作未产生预期效果(无跳转/无变化/无反馈)时,**必须先 `browser_snapshot` 检查页面是否有错误提示**
- 禁止不看页面就猜测失败原因
- 禁止跳过错误提示直接调用 API 绕过
- Toast 提示是用户的真实反馈,旅程验证必须尊重
**按 Toast 内容分流**:
```
操作无变化 → browser_snapshot 检查页面:
├── 有 Toast/错误提示 → 读取内容 → 按提示分流处理:
│ ├── "请输入/请填写/不能为空" → 找到对应字段 → browser_type 补充填写 → 重试操作
│ ├── "请上传/请选择图片/请选择文件" → browser_click 触发文件选择 → browser_file_upload 上传测试文件 → 重试操作
│ ├── "暂未开放/开发中" → 分类为 AUTO-IMPLEMENT → 走 auto-fix 流程
│ └── 其他业务校验 → 记录 FAIL + Toast 内容 → 继续下一步骤
└── ⛔ 无任何提示 → BugUX 缺陷:校验失败但用户看不到原因)→ AUTO-FIX添加 Toast/错误提示)
```
**规则 E3.6:即时使用数据必须可回溯(⛔ 强制)**
操作成功后产生的即时使用数据(验证码等),如果仅通过 Toast 传递,下一步骤无法获取 → 流程卡死。
这不是所有 Toast 都是问题,仅当三条件同时满足时才触发修复。
**回退链(按优先级尝试)**:
```
触发即时使用数据操作后:
1. browser_snapshot → 如果 Toast 包含关键数据 → 立即记录
Toast 已消失)
2. browser_console_messages(level="info") → 查找 console.log 记录
↓ (无 console.log → FAIL #14 → AUTO-FIX: 添加 console.log)
↓ (有 console.log → 从中提取数据)
console 也无有用数据)
3. browser_network_requests(static=false, requestBody=false, requestHeaders=false) → 从 API 响应提取
network 也未捕获)
4. 回退: curl/API 直接查询(如重新请求 send-code
```
注意:**订单号、支付结果**等后续页面自然显示的数据不属于"即时使用数据"范畴 — 旅程不需要从 Toast 中提取这些数据。
此回退链仅在 Toast 消失后且下一步骤需要数据时使用。
**⛔ 如果以上回退链全部失败且源码无 console.log → FAIL #14 → AUTO-FIX**
(详见 auto-fix-engine.md AUTO-FIX #14)
**规则 E4: 数据断言必须使用操作前后的实际值**
```
✅ 正确:
操作前: curl GET /api/organizer/me → 404 (无organizer)
操作后: curl GET /api/organizer/me → {status: "PENDING"}
断言: 操作前 ≠ 操作后, status = "PENDING" → PASS
❌ 错误:
操作后: curl GET /api/organizer/me → 200 OK
断言: "有返回就算通过" → 这是 Story 级验证,不是旅程验证
```
### ⛔ 工具选择守卫Tool Selection Guard
**核心原则:用户步骤 = Playwright 工具,数据验证 = Playwright + curl 补充**
旅程验证的本质是**模拟真实用户通过浏览器操作页面**。任何用户可见的操作(点击、填写、导航、查看)都必须通过 Playwright MCP 工具执行。curl 仅用于数据状态的补充验证,绝不替代用户交互。
#### 工具映射表
| 步骤类型 | 必须使用的工具 | 禁止使用的工具 | 说明 |
|---------|-------------|-------------|------|
| 打开页面 | browser_navigate | curl/其他 | 用户看到的是页面,不是 API 响应 |
| 点击按钮/Tab/链接 | browser_click | curl/其他 | 用户点击的是 UI 元素 |
| 填写表单 | browser_type⛔ 仅此一个,失败 = Bug → AUTO-FIX | curl POST, browser_run_code | browser_type 失败 = 前端交互 Bug禁止用 run_code 绕过 |
| 文件上传 | browser_click触发选择器+ browser_file_upload上传文件| curl POST | 文件选择器由 browser_click 触发browser_file_upload 完成上传 |
| 验证页面内容 | browser_snapshot | curl GET | 用户看到的是渲染后的页面 |
| 等待响应/跳转 | browser_wait_for | — | 等待页面状态变化 |
| 验证数据状态 | browser_evaluate 或 curl均可 | — | 数据断言允许 curl 补充 |
| 保存证据 | browser_take_screenshot | — | ⛔ 每个用户步骤完成后必须截图。filename 必须以 "tests/e2e/screenshots/" 开头且以 ".png" 结尾 |
| 检查网络请求 | browser_network_requests | — | 每条旅程至少 1 次检查 |
#### 禁止行为(⛔ 检测即阻断)
- ❌ 用户步骤中使用 curl 替代 browser_click / browser_type / browser_navigate
- ❌ 用 `curl GET /api/xxx` 替代 browser_snapshot 验证页面内容
- ❌ 用 `curl POST /api/xxx` 替代 browser_click 提交表单
- ❌ 操作无反应时不检查页面提示就猜测原因
- ❌ 表单校验阻止时直接用 API 绕过(必须先尝试通过 UI 解决browser_snapshot 读提示 → 按提示补充填写/browser_file_upload
- ❌ 跳过任何用户步骤的 browser_take_screenshot⛔ 每个用户步骤完成后必须立即截图,不是只在旅程结束时)
- ❌ browser_take_screenshot 的 filename 不以 "tests/e2e/screenshots/" 开头或不以 ".png" 结尾
- ❌ 跳过 browser_network_requests 检查
- ❌ 整条旅程只用 curl 而不用任何 Playwright 工具
**违反任一项 → 旅程标记 FAIL不得记录为 pass。必须使用 Playwright 重做。**
#### 执行前自检清单
执行任何旅程之前AI 必须逐项确认:
1. ✅ 起始页使用 browser_navigate非 curl
2. ✅ 每个用户点击使用 browser_click非 curl
3. ✅ 每个表单填写使用 browser_type⛔ 仅此一个,失败 = Bug → AUTO-FIX禁止用 browser_run_code 绕过)
4. ✅ 页面内容验证使用 browser_snapshot非 curl GET
5. ✅ 每个用户步骤完成后立即 browser_take_screenshot⛔ 不是只在最后截一张)
6. ✅ browser_take_screenshot 的 filename 格式为 "tests/e2e/screenshots/journey-epic-{N}-{M}-step{S}.png"
7. ✅ 旅程结束时 browser_network_requests
### ⛔ 真实用户完成规则Real User Completion Rule
**Read fully and follow: ./auto-fix-engine.md 第 3 节"真实用户完成规则"**
- 核心原则browser_type/click 失败 = Bug → AUTO-FIX禁止 evaluate/run_code 绕过
- browser_type 失败后必须执行 FAIL #6 诊断子步骤(含完整修复流程)
### 执行模板(每条旅程 — 规定式,不得偏离)
**执行前必须对照检查清单**(违反 = 错误,必须纠正后继续):
- [ ] `browser_snapshot` = DOM 结构快照(找元素用),**不是**视觉验证
- [ ] `browser_take_screenshot` = PNG 截图保存(用于视觉验证),**每个步骤必须**
- [ ] 截图文件名**必须**包含 `.png` 扩展名(`journey-epic-N-M-step-S.png`
- [ ] 截图**必须**保存到 `tests/e2e/screenshots/` 目录,不能保存到当前工作目录
- [ ] **每个截图文件必须是真正的 PNG 图片**`file <path>` 检测输出必须包含 "PNG image data"
- [ ] **强制:每截一张图立刻检查**`browser_take_screenshot` 完成后**立刻**执行 `file` 检测格式,失败立刻重截
- [ ] 每个用户步骤完成 → `browser_take_screenshot` + `file` 格式验证 + `analyze_image` → 视觉验证关卡
- [ ] 旅程最终完成 → `browser_take_screenshot` 最终截图 + `browser_network_requests` 资源检查
- [ ] 违反任意一条 → 中断补全,不能继续
```
对每条旅程:
1. 准备阶段:
- browser_resize → 正确视口(小程序 375x812 / 管理后台 1440x900
- 读取旅程定义的前置条件
- curl/API 验证前置条件(✅ 允许 curl
- 前置条件不满足 → FAIL
2. 执行阶段(按用户视角步骤逐一执行 — ⛔ 只能用 Playwright:
- browser_navigate → 起始页(自然入口)(⛔ 禁止用 curl 替代)
- browser_snapshot → 验证起始页面(⛔ 禁止用 curl GET 替代)
- ⛔ **零交互检查**: 如果 snapshot 显示页面有文字但无任何 button/input/a/form
- 标记步骤 FAIL用户无法从此页面继续操作
- `browser_evaluate` 执行 FAIL #5 诊断(检查 interactiveCount / frameworkMounted — 见 auto-fix-engine.md
- 按诊断结果分流 → AUTO-FIX 或 AUTO-IMPLEMENT
- ⛔ **起始页视觉验证**: browser_take_screenshot → filename: "tests/e2e/screenshots/journey-epic-{N}-{M}-step0.png", type: "png" + analyze_image
- 循环每个用户步骤(步骤编号 S = 1, 2, 3...:
a. browser_snapshot → 找到目标元素
b. browser_click / browser_type / browser_run_code → 执行操作(⛔ 禁止用 curl 替代)
c. browser_wait_for → 等待响应/跳转
d. browser_snapshot → 验证操作结果(⛔ 禁止用 curl GET 替代)
e. ⛔ **视觉验证关卡**(⛔ 强制 — Read fully and follow: ./visual-verification.md
此关卡为独立步骤,不是子步骤。每个用户步骤完成后必须通过此关卡才能继续。
1. browser_take_screenshot → filename: "tests/e2e/screenshots/journey-epic-{N}-{M}-step{S}.png", type: "png"
2. analyze_image → 使用 visual-verification.md 中的标准提示词
3. 结果记录到 pipeline-state.yaml 的 verification_detail含 analyze_image_executed: true
4. 视觉验证 FAIL → 按分流规则处理(见 auto-fix-engine.md + iron-laws.md 检测即阻断表)
5. ⛔ **步骤级跟踪**(每个用户步骤完成后强制执行):
a. 更新 verification-checklist.yaml 对应 step 的 status
b. 追加 step_results 到 pipeline-state.yaml 的 user_journeys:
```yaml
- step_id: "j{N}-{M}-s{S}"
action: "{操作描述}"
status: "pass|fail|fixed|skipped"
screenshot_path: "tests/e2e/screenshots/journey-epic{N}-{M}-step{S}.png"
analyze_image_result: { ... }
issues_detected: [ ... ] # 检测到的问题
auto_fixes: [ ... ] # 自动修复记录
step_gate: "pass|fail|blocked"
gate_checks:
- check: "screenshot_consistency"
result: "pass|fail"
- check: "analyze_image_executed"
result: "pass|fail"
- check: "checklist_synced"
result: "pass|fail"
- check: "state_persisted"
result: "pass|fail"
- check: "all_issues_resolved"
result: "pass|fail"
executed_at: "{ISO timestamp}"
note: "{备注}"
```
c. 步骤级 GATE 检查6 项,任一 FAIL → 阻断):
├── G1: screenshot_consistency — screenshot_path 文件存在
├── G2: analyze_image_executed — analyze_image_result 非空
├── G3: checklist_synced — verification-checklist.yaml step status 已更新
├── G4: state_persisted — pipeline-state.yaml step_results 已追加
├── G5: all_issues_resolved — 所有 issues_detected 有对应 resolved auto_fix
├── G6: file_format_validation — `file screenshot.png` 输出检测为 "PNG image data"
│ ⛔ **强制规则:每截一张图立即检查** → `browser_take_screenshot` 完成后**立刻**执行 `file` 命令检测格式
│ - 检测失败 → **立即重新截图**,不得累积到最后批量修复
│ - 检测通过 → 才允许继续下一步骤
│ - 目的:防止出现"扩展名 .png 但内容是 YAML 文本"的错误
4.5. ⛔ **源码 SVG data URI 扫描**(视觉验证关卡内 — 每次 snapshot 后强制执行):
- Bash: `grep -rn "data:image/svg+xml" app/mini-program/src/`(扫描小程序源码目录)
- 发现匹配 → svg_data_uri_check.found = FOUND → 记录文件列表
- → 按检测即阻断表 "SVG data URI 充当图标/装饰" 条目处理
- → AUTO-IMPLEMENT: 完整安装 Iconify + 替换所有 SVG data URI参见 ./reference/platform-uni-app.md §1
5. ⛔ **视觉验证修复必须内联执行**(不得推迟到步骤 4
- 视觉 FAIL 后,必须在当前关卡内**立即**执行 AUTO-FIX/AUTO-IMPLEMENT
- 修复后重测:重新 browser_take_screenshot + analyze_image 确认问题已修复
- 修复失败 → 标记 journey-step-fail → 继续下一步骤
- 步骤 4修复阶段仅处理功能断言 FAIL不处理视觉验证 FAIL
f. 如涉及数据变化 → browser_evaluate 或 curl 验证数据(✅ 允许 curl 补充)
6. ⛔ **旅程步骤原型/Pencil 视觉对比验证**(⛔ 强制 — 针对操作时产生的动态状态):
目的: 捕获并验证操作时才会出现的视觉状态hover、选中、弹窗、展开、加载中、错误提示等这些状态在静态原型对比中无法覆盖
触发条件(满足任一即执行):
- 当前步骤操作后出现了弹窗/Modal/对话框
- 下拉菜单/Select/日期选择器展开
- 按钮/卡片/列表项处于 Hover/Active 状态
- 表单字段获得 Focus/有验证错误提示
- 页面显示 Loading/Skeleton/进度条
- 列表项被选中/高亮/勾选
- 页面切换/跳转后的首屏渲染完成
- 出现了错误提示/Toast/Notification
- 折叠面板展开/收起状态变化
执行流程:
a. 等待状态稳定(动画/过渡完成):
- browser_wait_for → time=1-2s等待动画完成
b. 捕获实际状态截图:
- browser_take_screenshot → 保存为 journey-epic{N}-{M}-step{S}-state.png
c. 搜索匹配的设计帧(双模式支持):
**Pencil MCP 模式(优先)**:
- 读取 pipeline-context.md 获取设计文件路径(如 VividSpark1.2.pen
- mcp__pencil__batch_get → 搜索与当前状态匹配的帧
- 搜索关键词: "{Page}-{State}" 如 "Dashboard-Modal", "Login-Error", "Upload-Loading"
- 或按 frame 类型和名称模式匹配
- 如找到匹配帧 → mcp__pencil__export_nodes 导出设计截图
**HTML 原型模式(回退)**:
- 检查 prototype/pages/{group}/{page}-{state}.html 是否存在
- 如存在 → 启动原型服务 → Playwright 截图
设计帧未找到处理:
- 标记 prototype_found: false
- 记录 actual_screenshot 路径
- 不阻断旅程,但记录到 pipeline-report.md 建议补充设计帧
d. 执行视觉对比(如找到设计帧):
- ui_diff_check:
- expected_image_source: 设计帧截图(或原型截图)
- actual_image_source: 当前状态截图 (journey-epic{N}-{M}-step{S}-state.png)
- prompt: "对比设计原型和实际实现。这是用户操作后出现的动态交互状态如弹窗、hover、选中、加载中、错误提示等
重点关注:
1) 组件布局和结构是否一致(弹窗位置、按钮排列等)
2) 颜色/样式是否匹配(背景色、边框、阴影等)
3) 文字内容是否正确(标题、提示信息、按钮文字等)
4) 交互状态是否准确hover 效果、选中状态、focus 样式等)
5) 图标和装饰元素是否一致
注意:忽略动态数据(如用户名、时间戳等),只关注 UI 结构和样式。
按严重程度列出所有视觉差异:"
e. 差异分级与处理:
记录到 pipeline-state.yaml:
```yaml
journey_state_visual_diff:
epic_6:
journey_1:
step_3:
state_type: "modal_open" # 状态类型: modal_open/hover/select_focus/loading/error/etc
prototype_found: true # 是否找到对应设计帧
prototype_source: "VividSpark1.2.pen:frame-456" # 设计来源
prototype_frame_id: "frame-456"
diff_check_executed: true
severity: "moderate" # critical/moderate/minor/none
differences:
- type: "color"
severity: "minor"
description: "弹窗背景色不一致:设计 #1A1A1A实际 #222222"
location: "弹窗背景"
- type: "spacing"
severity: "moderate"
description: "按钮间距偏差 8px设计 16px实际 24px"
location: "底部按钮组"
- type: "text"
severity: "critical"
description: "按钮文字错误:设计'确认',实际'提交'"
location: "主按钮"
screenshot_expected: "tests/e2e/screenshots/journey-epic6-1-step3-design.png" # 设计帧截图
screenshot_actual: "tests/e2e/screenshots/journey-epic6-1-step3-state.png" # 实际状态截图
auto_fix_triggered: true # 是否触发自动修复
auto_fix_type: "AUTO-FIX" # 修复类型
auto_fix_result: "PASS" # 修复结果
```
差异处理规则:
- 🔴 严重差异(布局错误、关键组件缺失、文字错误)→ 立即触发 AUTO-FIX
- 🟡 中等差异(颜色、间距偏差)→ 记录到 pipeline-report.md建议修复
- 🟢 轻微差异(微小像素偏移)→ 记录但不阻断旅程
- ✅ 无差异 → 标记状态正常
f. 自动修复与重测(如触发):
- AUTO-FIX 触发后:
1. 定位相关源码文件(根据差异类型确定)
2. 执行修复(如调整 CSS、修改组件样式等
3. 重新截图: browser_take_screenshot
4. 重新对比: ui_diff_check
5. 修复成功 → 标记 auto_fix_result: "PASS"
6. 修复失败 → 标记 auto_fix_result: "FAIL",记录原因
6. ⛔ **源码 SVG data URI 扫描**(视觉验证关卡内 — 每次 snapshot 后强制执行):
g. ⛔ **约束一致性检查**(每个用户步骤首次执行时 — R7 执行时强制检测):
触发条件(操作被拦截 + 声明方说"可选":
- 操作后 Toast 出现且含"请上传/请填写/请选择/不能为空/不能为空"
- 且满足以下任一:
a. 旅程定义中该步骤描述含"可跳过/可选/选填/optional/skip/跳过"
b. browser_snapshot 页面文本含"(选填)/(可选)"标记
满足 → Constraint Mismatch (FAIL #16):
- ❌ 不得静默补填继续
- 记录到 pipeline-state.yaml 的 constraint_mismatches
- 读取源码确认验证逻辑 → 按设计意图修复代码或旅程定义
- 修复后重测该步骤
不满足(仅 Toast 拦截但无"可选"声明)→ 普通 FAIL #12 → 按提示补填继续
3. 证据阶段(⛔ 强制,不得跳过):
- browser_take_screenshot → filename: "tests/e2e/screenshots/journey-epic-{N}-{M}-final.png", type: "png"
- browser_network_requests(static=false) → 检查无 localhost API 调用
- ⛔ **资源加载状态检查**(新增 — 每条旅程必须执行):
- `browser_network_requests(static=true, filter="\\.(jpg|jpeg|png|gif|webp|svg|mp4|webm|mov)$")`
- 过滤图片/视频资源请求,检查 HTTP 状态码
- 任何 403/404 → 标记 `resource_status_check.found = FOUND` + 触发修复
- 常见 403 原因MinIO 私有 bucket 未用 presigned URL、防盗链、CORS
- 常见 404 原因:资源路径错误、文件未上传
- 记录到 pipeline-state.yaml 的 `verification_detail.resource_status_check`
4. 修复阶段(⛔ 以下任一条件满足时必须执行,不得跳过):
**Read fully and follow: ./auto-fix-engine.md**
(包含: PASS/FAIL 判定标准 → FAIL 分类表 → 真实用户完成规则 → AUTO-FIX/IMPLEMENT/DATA-FIX 流程 → GATE-CHECK
**触发条件(⛔ 明确化 — 以下任一即触发)**:
- `steps_failed > 0`(功能断言失败)
- `constraint_mismatches` 非空R7 约束不一致检测到 — 即使 steps_passed == steps_executed
- 跨轮次累计的视觉问题 ≥ 1emoji_as_icons / english_enum / broken_images 等 — 检测到即修复)
- 前一次验证的 `pending_fixes` 中有未修复项(⛔ 即使是其他 EPIC 范围的问题,也必须在本轮修复 — 参见 auto-fix-engine.md "已知问题不豁免"规则)
- `verification_detail` 中任一检查项为 FOUND 且非设计系统主观视觉评价WARN
**⛔ 禁止的"记录但不修复"模式**:
- ❌ 将问题添加到 `pending_fixes` 但不执行实际修复动作
- ❌ 标记"已记录"/"已知"作为修复结果("已记录"不是修复)
- ❌ 以"不影响本次旅程流程"为由跳过修复
- ❌ 以"V3遗留/历史问题"为由降级为 WARN
- ❌ 以"非当前 EPIC/旅程范围"为由跳过 pending_fixes 中的问题(范围外 ≠ 豁免)
5. 记录阶段:
- 更新 pipeline-state.yaml含截图路径、coverage_frs、auto_fix 记录)
- 更新 pipeline-state.yaml 的 user_journeys
- 输出: "旅程 {name}: {PASS/FAIL} — {摘要}"
6. 分类提交推送阶段(⛔ 每条旅程完成后强制执行):
- git status → 确认变更范围
- 按以下分类顺序逐类提交(无变更则跳过):
| 顺序 | 类型 | 文件范围 | Commit Message 格式 |
|------|------|---------|-------------------|
| 1⃣ | 基础设施/配置 | `package.json`、`vite.config.*`、`tailwind.config.*`、`postcss.config.*`、`tsconfig.json` | `chore: {描述}` |
| 2⃣ | 后端代码 | `api/src/` | `feat(api): {描述}` |
| 3⃣ | 小程序代码 | `app/mini-program/src/` | `feat(mini): {描述}` |
| 4⃣ | 管理后台代码 | `app/admin/src/` | `feat(admin): {描述}` |
| 5⃣ | 测试产物 | `tests/e2e/screenshots/`、`tests/e2e/fixtures/` | `test: journey screenshots` |
| 6⃣ | 流水线状态 | `_bmad-output/implementation-artifacts/pipeline-state.yaml`、`_bmad-output/implementation-artifacts/pipeline-report.md` | `ci(pipeline): update journey verification status` |
- 提交前 `git diff --stat` 确认变更范围
- 排除 `.env`、`node_modules/`、`.next/`、`dist`
- 所有分类提交完成后: `git push origin main`
- 推送失败 → 记录到 error_log不中断
```
### 结果记录格式
```yaml
user_journeys:
epic_N:
journey_1:
name: "{旅程名称}"
status: pass/fail
steps_executed: 9
steps_passed: 8
steps_failed: 1
failed_step: "步骤 4: 新用户登录后 userId 应与已有用户不同"
failed_reason: "userId=01467a51 与已知用户相同,所有手机号返回同一用户"
screenshot: "tests/e2e/screenshots/journey-epic1-1.png"
# --- 自动修复记录(新增)---
auto_fix_attempted: true
auto_fix_type: "AUTO-FIX" # AUTO-FIX / AUTO-IMPLEMENT / AUTO-DATA-FIX
auto_fix_file: "app/mini-program/src/App.vue"
auto_fix_change: "Added userStore.restoreTokens() call in onLaunch"
auto_fix_retry_count: 1
auto_fix_result: "PASS" # PASS / FAIL
# --- FR 覆盖追踪(新增)---
coverage_frs: ["FR1", "FR2", "FR4", "FR5", "FR6"]
# ===== 新增: 步骤级结果 =====
step_results: [] # 已在执行过程中逐步追加(格式见执行模板步骤 2e
total_issues_detected: 0 # 所有步骤检测到的问题总数
total_auto_fixes: 0 # 所有步骤的自动修复总数
all_issues_resolved: true # 所有问题是否全部修复
details: "完整执行日志"
```
---
## 与回归验证模式的关系
| 维度 | 回归验证模式 | 旅程验证模式 |
|------|------------|------------|
| 粒度 | 逐 Story | 跨 Story/Epic |
| 目标 | 验证功能点存在且正确 | 验证用户能完成目标 |
| 类比 | 单元测试 | 集成测试/E2E |
| 入口 | 直接 navigate 到目标页面 | 从自然入口开始 |
- 两种模式**独立运行**,互不依赖
-**推荐顺序:先回归验证再旅程验证**。功能不存在时测旅程无意义(用户点不到不存在的东西)
- 回归验证全部 PASS 后,建议进入旅程验证模式
## 续跑逻辑
- 读取 `pipeline-state.yaml``user_journeys` 部分
-`pass` 的旅程跳过
- 从第一个非 `pass` 的旅程继续
- **步骤级恢复**(⛔ 增强):
- 读取 `verification-checklist.yaml` → 找到当前旅程中第一个 `status: pending` 的 step
- 读取 `pipeline-state.yaml``step_results` → 确认最后一个已完成步骤
- 从下一个步骤继续执行(跳过已完成的步骤,不重复截图/分析)
- 如无 pending step → 该旅程验证已完成,继续下一个旅程
### 任务级续跑增强(独立任务模式)
如果任务清单已创建:
- 读取所有任务状态,跳过 `status: completed` 的旅程
- 仅对 `status: pending``status: in_progress` 的旅程创建任务并启动验证
- 支持从上次中断处恢复执行

View File

@@ -0,0 +1,410 @@
# Phase 0: SETUP初始化
> 参见 ./iron-laws.md 了解铁律五(上下文压缩后自愈)的详细说明。
## SETUP 步骤
1. **加载配置**
- 读取 `{project-root}/_bmad/bmm/config.yaml`
- 解析:`project_name``planning_artifacts``implementation_artifacts` 路径
- 读取 `{project-root}/CLAUDE.md` 获取项目上下文
2. **发现规划文档**
- 扫描 `{planning_artifacts}/` 目录:
- `*epic*.md` — 史诗与故事
- `*prd*.md``*requirements*.md` — PRD
- `*architecture*.md` — 技术架构
- `*ux*.md``*ui*.md` — UX 设计
- 缺少任一文档时输出警告,不中断
3. **发现原型页面**
- 扫描 `{project-root}/prototype-v2/` 目录(上游 `d8d-prototype` 的产出物):
- `pages/mobile/*.html` — 小程序用户端原型页面
- `pages/admin/*.html` — 管理后台原型页面
- 构建原型映射表Story → 原型文件路径),供 CREATE-SPEC 和 IMPLEMENT 阶段使用:
```
小程序原型映射:
login → login.html
organizer-apply → organizer-apply.html
home → home.html
create-event → create-event.html
event-search → event-search.html
bar-detail → bar-detail.html
square → square.html
order-confirm → order-confirm.html
payment-result → payment-result.html
order-list → order-list.html
my-earnings → my-earnings.html
withdraw → withdraw.html
messages → messages.html
profile → profile.html
管理后台原型映射:
login → login.html
dashboard → dashboard.html
organizer-review → organizer-review.html
event-management → event-management.html
order-management → order-management.html
withdrawal-review → withdrawal-review.html
venue-management → venue-management.html
user-management → user-management.html
```
- 缺少原型目录时输出警告,不中断(无原型时纯依赖 UX 文档)
4. **安装并配置 pm2**
- 检查 pm2 是否已安装:`pm2 --version`
- 未安装则全局安装:`npm install -g pm2`
- 生成 ecosystem 配置文件 `{project-root}/ecosystem.config.js`
```js
module.exports = {
apps: [
{ name: 'api', script: 'npx next dev --port 3001', cwd: './api', watch: false },
{ name: 'admin', script: 'npx next dev --port 3002', cwd: './app/admin', watch: false },
{ name: 'mini-h5', script: 'npx vite --port 5173', cwd: './app/mini-program', watch: false },
]
}
```
- 标记Epic 0 完成后此文件才可生效(项目目录需先存在)
- ⛔ **HMR 降级策略(备选)**:配置了 `allowedDevOrigins` 后,外网域名的 HMR 通常可以正常工作。仅在以下情况下降级到生产模式:
- 配置了 `allowedDevOrigins` 后 HMR 仍失败
- 需要最大化测试稳定性(生产模式无 HMR 干扰)
降级步骤(`next start` 替代 `next dev`
```bash
npx next build
pm2 stop app && pm2 delete app
pm2 start "npx next start --port 3001" --name app
```
详见 `./reference/platform-nextjs.md` §7.1
4.5. **外网域名发现与连通性验证NPM MCP**
- project-slug 推导规则取项目根目录名basename of pwd追加 `-group` 后缀
- 示例:目录 `/mnt/code/231-251-template-6` → slug = `231-251-template-6-group`
- ⛔ 禁止从项目描述或 PRD 内容猜测 slug
- 通过 NPM MCP 工具查询已配置的代理主机:
- 使用 `get_host_report` 快速确认代理数量
- ⚠️ `list_proxy_hosts` 有已知 buglocations 字段解析失败),用 `get_proxy_host` 逐个查询更可靠
- 验证三个核心服务的外网域名都存在且 enabled=true
```
API: http://api-custom-{project-slug}.dev.d8d.fun → 127.0.0.1:3001
Admin: http://admin-custom-{project-slug}.dev.d8d.fun → 127.0.0.1:3002
Mini-H5: http://mini-custom-{project-slug}.dev.d8d.fun → 127.0.0.1:5173
```
- 缺失的域名通过 `create_proxy_host` 创建(⛔ 参数要求见 `reference/platform-nextjs.md` §7 或 `reference/platform-uni-app.md`
- `block_exploits: false`(默认 true 阻断 WebSocket
- SSL 由 K8S ingress 处理,不需要在 NPM 中配置
- ⛔ 对每个 proxy host无论已有还是新建必须验证并修复此步骤不可跳过
- `get_proxy_host` 检查 `block_exploits: false` 且 `allow_websocket_upgrade: true`
- 如不满足 → `update_proxy_host` 修正MCP 工具不直接支持 allow_websocket_upgrade 时,用 NPM API`PUT /api/nginx/proxy-hosts/{id}` 传 `{"allow_websocket_upgrade": true}`
- 将外网域名写入 `pipeline-context.md` 的"外网域名"部分
- **E2E 测试优先使用外网域名**localhost 作为最后 fallback
4.6. **外网域名连通性探测URL 解析三步法)**
**对三个核心服务逐一执行以下三步探测,不允许跳步**
```
Step 1: curl -sf --max-time 5 https://{外网域名}
↳ 200 → 使用 HTTPS记录到 pipeline-context.md 的"当前使用的 URL"
↳ 失败 → Step 2
Step 2: curl -sf --max-time 5 http://{外网域名}
↳ 200 → 使用 HTTP记录到 pipeline-context.md
↳ 502/503 → Step 2.5 诊断修复
↳ 其他失败 → Step 3
Step 2.5: 诊断 502 并修复(⚠️ 禁止直接 fallback 到 localhost
当某个服务返回 502 但 localhost 可用时,这是配置问题,必须修复:
a. 检查服务监听地址netstat -tlnp | grep {port}
- 期望: 0.0.0.0:{port} 或 ::::{port}(所有接口)
- 如果是 ::1:{port} 或 127.0.0.1:{port} → 仅 localhostNPM 无法转发
b. 修复方法(按框架):
- Viteuni-app/mini-programvite.config.ts 添加 server: { host: "0.0.0.0" }
- Next.js默认绑定所有接口通常不需要修改
c. 修复后重启服务pm2 restart {name}),重新测试 Step 2
d. 如果修复后仍失败 → 检查 NPM proxy host 配置是否正确forward_host/forward_port
Step 3: curl -sf --max-time 5 http://localhost:{port}
↳ 200 → 使用 localhost记录 fallback_reason
↳ 失败 → 服务未启动,需要先启动服务
```
**关键规则**
- **禁止在 Step 1 失败后直接跳到 localhost**Step 3必须先尝试 HTTPStep 2
- **禁止在部分服务 502 时整体 fallback**,应该逐一修复每个服务的连通性
- **502 意味着 NPM 可达但上游不可达**,这是可修复的配置问题,不是网络问题
- 所有探测结果写入 `pipeline-context.md` 的"当前使用的 URL"部分
4.7. **前端环境变量注入(消除 localhost fallback**
在步骤 4.6 探测到 API 外网域名后,**必须**将域名注入到前端应用的环境变量中,
确保前端页面的 API 调用指向正确的外网地址而非 localhost。
**为什么需要这一步**
- 小程序前端 `import.meta.env.VITE_API_BASE || "http://localhost:3001"` —— 环境变量未设置时 fallback 到 localhost
- 管理后台 `process.env.NEXT_PUBLIC_API_BASE || "http://localhost:3001"` —— 同样 fallback 到 localhost
- E2E 测试通过外网域名打开页面,但页面内部 JS 发起 fetch 请求时使用硬编码的 localhost
- Playwright 运行在 localhost 环境所以测试通过,但真实外网用户无法连接 localhost
**执行步骤**
a. 创建小程序环境变量文件:
```
写入文件 {project-root}/app/mini-program/.env.local
VITE_API_BASE={API_URL步骤 4.6 探测结果)}
```
b. 创建管理后台环境变量文件:
```
写入文件 {project-root}/app/admin/.env.local
NEXT_PUBLIC_API_BASE={API_URL步骤 4.6 探测结果)}
```
c. 重启前端服务使环境变量生效Vite/Next.js 在启动时读取 .env.local运行中创建不会自动生效
```bash
pm2 restart admin
pm2 restart mini-h5
```
d. 验证环境变量已生效(等待服务 ready 后):
```bash
# 验证文件存在且内容正确
cat app/mini-program/.env.local
cat app/admin/.env.local
# 验证前端 bundle 中不再包含 localhost:3001
curl -s {MINI_URL} | grep -o "localhost:3001" | head -1
# 期望:无输出
curl -s {ADMIN_URL} | grep -o "localhost:3001" | head -1
# 期望:无输出
```
e. 如果 curl grep 仍然发现 localhost:3001
- 确认 .env.local 文件内容正确
- 确认 pm2 已重启:`pm2 logs mini-h5 --lines 5` 和 `pm2 logs admin --lines 5`
- Vite 可能需要清缓存:删除 `app/mini-program/node_modules/.vite` 后重启
- Next.js 可能需要清缓存:删除 `app/admin/.next` 后重启
**注意事项**
- 如果使用 localhost fallback步骤 4.6 的 Step 3 结果),.env.local 值写 `http://localhost:3001`
- 此步骤创建的 .env.local 不应提交到 git应在 .gitignore 中排除)
- 更新 pipeline-context.md 记录注入状态
4.8. **Next.js allowedDevOrigins 配置(⛔ 外网域名访问时强制)**
当通过外网域名(如 `*.dev.d8d.fun`)访问 Next.js 开发服务器时,必须在 `next.config.ts` 中配置 `allowedDevOrigins`,否则会出现 "Invalid Host header" 错误。
**配置示例**
```typescript
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
allowedDevOrigins: [
"*.dev.d8d.fun",
"localhost",
],
};
export default nextConfig;
```
**配置后重启服务**
```bash
pm2 restart vividspark
```
详见 `./reference/platform-nextjs.md` §10
4.9. **MinIO 外网地址注入(⛔ 强制)**
检测容器环境变量 `MINIO_HOST`,将外网地址注入到 `.env` 中的 `S3_PUBLIC_URL`
a. 检测 MINIO_HOST
```bash
echo $MINIO_HOST
```
b. 探测 HTTPS 是否可用:
```bash
curl -sf --max-time 5 https://$MINIO_HOST/minio/health/live && echo "HTTPS" || echo "NO-HTTPS"
```
c. 注入到 `.env`(⚠️ 注意 K8S ingress 会将 HTTP 升级为 HTTPS必须使用 HTTPS
```
S3_PUBLIC_URL="https://{MINIO_HOST}"
```
d. 代码中对外暴露的 URLpresigned URL、封面图 URL必须使用 `S3_PUBLIC_URL`
e. 内部连接S3Client endpoint保持 `S3_ENDPOINT`localhost:9000
f. 详见 `reference/platform-nextjs.md` §8
5. **初始化管道状态**
- 创建 `{implementation_artifacts}/pipeline-state.yaml`
```yaml
pipeline_id: dev-pipeline-{timestamp}
status: setup
current_epic: null
current_story: null
total_epics: 0
total_stories: 0
stories_completed: 0
stories_failed: 0
epic_order: []
story_results: {}
error_log: []
```
- 每个 Story 完成后,`story_results` 条目应包含以下详细结构:
```yaml
"X-Y-title":
status: done
verified: true
verification_detail:
api_tested: true/false/skipped
api_endpoints_tested: ["POST /api/xxx", "GET /api/xxx"]
e2e_tested: true/false/skipped
screenshot_saved: true/false
screenshot_path: "tests/e2e/screenshots/story-x-y.png"
analyze_image_called: true/false
interactions_completed: ["填写表单", "点击提交", "验证数据已创建"]
# 新增字段
api_centralized: true/false # 前端 API 调用是否使用集中式模块
layout_check:
horizontal_overflow: true/false # 水平溢出检测
tabbar_visible: true/false/null # TabBar 可见性
document_width: 375
viewport_width: 375
icons_check:
tabbar_icons_visible: true/false # TabBar 图标完整
broken_images: true/false # 是否有 broken image
emoji_as_icons: true/false # 是否用 emoji 做图标
dual_write_verified: true # 三写一致性验证
note: "简短验证摘要"
```
6. **检查已有进度**
- 如果 `pipeline-state.yaml` 已存在 → 续跑模式,跳过已完成的 Story
- 如果 `sprint-status.yaml` 已存在 → 解析已完成的 Story
7. **状态文件同步检查**
- 如果两个状态文件都存在,必须验证一致性:
- `pipeline-state.stories_completed` == `sprint-status` 中 `done` 状态的 Story 数量
- 不一致时以 `pipeline-state.yaml` 为准,同步更新 `sprint-status.yaml`
- 同步后输出一行:`📋 状态同步pipeline-state X done ↔ sprint-status Y done`
7.5. **平台检测与自动配置(⛔ 强制)**
- 检测项目类型,加载对应平台方案集:
```bash
# 检测 Next.js 项目
[ -f "next.config.ts" ] || [ -f "next.config.js" ] && echo "PLATFORM=nextjs"
# 检测 uni-app 项目
[ -f "app/mini-program/src/pages.json" ] && echo "PLATFORM=uniapp"
```
- **Next.js 平台**:按 `./reference/platform-nextjs.md` 逐项检测
- NextAuth trustHost 配置(第 1 节)
- Prisma 7 adapter 配置(第 2 节)
- 检测不通过 → 自动修复并记录到 `pipeline-context.md` 修复记录
- **uni-app 平台**:按 `./reference/platform-uni-app.md` 逐项检测(已有流程不变)
---
## 铁律五:上下文压缩后的自愈能力
**AI 必须假设上下文随时可能被压缩。每个 Story 完成后,关键运行时状态必须已写入磁盘文件。**
```
上下文压缩发生
读取 pipeline-context.md恢复凭据/端口/路由/进度)
读取 pipeline-state.yaml恢复 Story 级验证状态)
TaskList恢复 Task 进度)
继续执行未完成的 Story
```
### 上下文恢复文件pipeline-context.md
**位置**`{implementation_artifacts}/pipeline-context.md`
**写入时机**(⛔ 强制,与 pipeline-state.yaml 同步更新):
- SETUP 完成后(写入基础设施信息)
- 每个 Story 完成后(⛔ 必须更新进度追踪表,与 pipeline-state.yaml 同步)
- AUTO-FIX 后(记录修复内容)
- 发现新路由/凭据/配置时(补充更新)
**三写一致性要求**sprint-status.yaml + pipeline-state.yaml + pipeline-context.md 三个文件的状态必须在同一个 Agent turn 中原子更新,缺一不可。
**模板**
```markdown
# Pipeline Context上下文恢复文件
# ⚠️ 此文件用于上下文压缩后恢复状态AI 自动维护,不需要人工编辑
## 基础设施
- API: localhost:3001 / https://api-custom-{project-slug}.dev.d8d.fun
- Admin: localhost:3002 / https://admin-custom-{project-slug}.dev.d8d.fun
- Mini-H5: localhost:5173 / https://mini-custom-{project-slug}.dev.d8d.fun
- PostgreSQL: localhost:5432, user=postgres, db=barparty
- Redis: localhost:6379
- pm2 进程: api, admin, mini-h5
## 当前使用的 URL 步骤 1.1 和所有 E2E 测试必须使用此部分)
- API_URL: {外网域名或 localhostSETUP 阶段探测后填入}
- ADMIN_URL: {外网域名或 localhostSETUP 阶段探测后填入}
- MINI_URL: {外网域名或 localhostSETUP 阶段探测后填入}
- URL_MODE: "外网域名" / "localhost(fallback)"
- fallback_reason: {如果使用 localhost记录原因如 "外网域名 curl 超时 5s"}
## 外网域名SSL 由 K8S ingress 处理)
- API: https://api-custom-{project-slug}.dev.d8d.fun
- Admin: https://admin-custom-{project-slug}.dev.d8d.fun
- Mini-H5: https://mini-custom-{project-slug}.dev.d8d.fun
## 前端环境变量注入状态
- mini-program .env.local: {已创建/未创建} — VITE_API_BASE={值}
- admin .env.local: {已创建/未创建} — NEXT_PUBLIC_API_BASE={值}
- bundle 验证: {已通过 curl grep 验证 / 未验证}
## 凭据
- Admin 登录: username=superadmin, password=admin123 不是"admin"
- JWT Secret: 来自 api/.env JWT_SECRET
- 获取 Organizer JWT: POST /api/auth/login {phone, code} → token 含 role:"organizer" + organizerId
- 获取 Admin JWT: POST /api/auth/admin/login {username, password} → token 含 role:"admin"
## 已发现的关键路由
- POST /api/auth/login — 用户登录
- POST /api/auth/admin/login — 管理员登录
- POST /api/events — 创建组局
- GET /api/events — 组局列表
- ...(自动补充)
## 进度追踪
| Story | 状态 | 验证摘要 |
|-------|------|---------|
| 0-1 | ✅ done | 17 tables, migrations OK |
| 1-1 | ✅ done | JWT+用户信息 |
| ... | ... | ... |
## 修复记录
- pay-route: userId FK → organizer.userId (P2003 fix)
- vitest: separate config for mini-program
```
### Phase 0 SETUP 新增步骤 8创建 pipeline-context.md
在步骤 7状态文件同步检查之后
- 使用上述模板创建 `{implementation_artifacts}/pipeline-context.md`
- 填充已发现的基础设施信息
- 后续每个阶段发现新信息时更新此文件
### 每个 Story 验证步骤 0恢复上下文
开始验证每个 Story 前(在 TaskCreate 之前):
- 读取 `pipeline-context.md` 获取凭据、端口、路由、进度
- 如果文件不存在 → 说明 SETUP 未完成或文件丢失,执行最小化恢复:
1. `pm2 list` 确认服务状态
2. 读取 `pipeline-state.yaml` 确认进度
3. 创建 `pipeline-context.md` 并填充基础信息

View File

@@ -0,0 +1,54 @@
# Phase 1: PLAN规划
### Phase 1: PLAN规划
1. **解析史诗文件**
- 读取 `{planning_artifacts}/*epic*.md`
- 提取所有 Epic编号、标题、优先级、依赖关系
- 提取所有 StoryEpic 编号、Story 编号、标题、验收标准
2. **构建依赖图**
- Epic 间依赖从文档中显式声明
- Story 间依赖在同一 Epic 内按编号顺序隐含
- 跨 Epic 依赖从 Epic 依赖关系推导
3. **拓扑排序**
- 根据依赖关系计算执行顺序
- 本项目执行顺序:
```
E0 → E1 → E2 → E3 → E4 → E5 → E6 → E7 → E8 → E9
```
- 每个 Epic 内 Story 按编号顺序执行
4. **预定义 Epic 用户旅程**
- 读取 PRD 和 Epic 定义文件,为每个 Epic 提取跨页面业务流程
- 创建 `{implementation_artifacts}/epic-N-journeys.md`(每个 Epic 一个文件)
- 旅程定义格式:
```markdown
# Epic N 用户旅程
## 旅程 1: {从 PRD 提取的旅程名称}
- 起始页: {页面路径}
- 步骤: {操作序列}
- 跨越页面: {涉及的页面列表}
- 数据状态变化: {什么数据被创建/更新/删除}
- 验收标准: {可观察的预期结果}
```
- 每个旅程必须跨越至少 2 个页面,包含至少 1 个数据状态变化
- 每个 Epic 至少定义 1 条旅程(纯基础设施 Epic 如 Epic 0 可豁免)
- 旅程来源优先级PRD 用户场景 > Epic/Story 验收标准组合 > 已实现的路由逻辑
- **这些旅程将在 Phase 6EPIC-DONE和验证模式中被严格执行**
**⛔ EPIC 旅程门**:必须为每个非基础设施 Epic 预定义至少 1 条旅程。
如果 PRD 和 Story 定义中均无法提取有效旅程 → 标记 Epic 为 `journey-blocked`
Phase 05 执行时回退到兼容模式(从 PRD 实时提取)。
但**不得跳过旅程测试直接标记 Epic done**。
5. **生成/更新 Sprint Status**
- 调用 `bmad-sprint-planning` 技能逻辑
- 创建 `{implementation_artifacts}/sprint-status.yaml`
6. **标记 Epic 0 特殊处理**
- Story 0.1(项目初始化)是绿地项目引导
- 标记为使用 `bmad-quick-dev` 而非标准 `bmad-dev-story`

View File

@@ -0,0 +1,31 @@
# Phase 2: CREATE-SPEC创建故事规格
### Phase 2: CREATE-SPEC创建故事规格
为当前 Story 生成详细规格文件:
1. **调用 `bmad-create-story` 技能**
- 输入:当前 Story 的 Epic 和编号
- 读取PRD、架构、UX、史诗文档作为上下文
- **读取关联的原型页面**(从 SETUP 阶段的原型映射表中匹配)
- 使用 Playwright MCP 打开原型 HTML 文件(`browser_navigate``file:///...`
- 截图原型页面(`browser_take_screenshot`)作为 UI 参考图
- 读取原型 HTML 源码提取:布局结构、组件层级、交互元素、文案内容
- 将原型截图和结构分析注入 Story 规格文件
- 输出:`{implementation_artifacts}/{epic_num}-{story_num}-{title}.md`
- 更新 sprint-status`backlog``ready-for-dev`
2. **Epic 0 特殊注入**
- Story 0.1 额外注入以下上下文:
- 完整 Prisma Schema来自架构文档
- Docker Compose 配置PostgreSQL + Redis
- pnpm workspace 初始化命令
- uni-app 项目脚手架(`npx degit dcloudio/uni-preset-vue#vite-ts`
- Next.js 项目脚手架(`npx create-next-app@latest`
- 项目设计令牌 CSS 变量(按 04-ui-ux-design.md 规范)
- 初始管理员种子数据
3. **更新管道状态**
- `current_story` 设为当前 Story
- `story_results[story_key].status` 设为 `spec-created`

View File

@@ -0,0 +1,97 @@
# Phase 3: IMPLEMENT实现
### Phase 3: IMPLEMENT实现
#### 3A: 标准实现Epic 1+
1. **前置检查**
- 数据库连接:`psql -U postgres -h localhost -c "SELECT 1"` (容器内置 PG 17
- Redis 连接:`redis-cli ping` (容器内置 Redis 7
- 数据库迁移状态:`cd api && npx prisma migrate status`(多应用)或 `npx prisma migrate status`(单 Next.js 应用)
- Dev server 状态:`pm2 list` 检查服务是否 `online`
- 未运行则启动并等待 ready
- 失败时自动修复:创建数据库、运行迁移、重启 pm2 服务
- **Next.js 平台检查**:按 `./reference/platform-nextjs.md` 逐项检测NextAuth trustHost、Prisma 7 adapter 等)
- **uni-app 平台检查**:按 `./reference/platform-uni-app.md` 逐项检测(图标、导航栏等)
2. **加载原型参考**
- 从 Story 规格文件中读取关联的原型文件路径
- 使用 Playwright MCP 截图原型页面(`browser_navigate → file:///.../prototype-v2/pages/.../*.html``browser_take_screenshot`
- 阅读原型 HTML 源码,提取:
- 页面布局结构flex/grid、区域划分
- 组件层级和样式类
- 表单字段、按钮、列表项等交互元素
- 文案内容和占位文本
- **实现时以原型为 UI 的主要参考**UX 文档作为补充
3. **容器环境服务(已内置,无需 Docker Compose**
| 服务 | 地址 | 说明 |
|------|------|------|
| PostgreSQL 17 | `localhost:5432`, 用户 `postgres` | 主数据库,`.env``DATABASE_URL` |
| Redis 7 | `localhost:6379` | 缓存/队列,`.env``REDIS_URL` |
| MinIO | `localhost:9000`, `MINIO_HOST` 环境变量 | 对象存储(替代阿里云 OSS |
2. **调用 `bmad-dev-story` 技能**
- 输入Story 规格文件路径
- 执行:按 Task 逐个实现,遵循 red-green-refactor 循环
- 更新 sprint-status`ready-for-dev``in-progress``review`
4. **API 集中式调用检查(⛔ 强制)**
**问题背景**:前端页面经常自行声明 API 基础 URL fallback`const API = process.env.XXX || "http://localhost:3001"`),导致外网用户无法访问。所有 API 调用必须通过集中式模块。
**规则**
- ❌ 禁止 admin 页面自行声明 `const API = process.env.NEXT_PUBLIC_API_BASE || "http://localhost:3001"`
- ❌ 禁止 mini-program 页面自行声明 `const API_BASE = import.meta.env.VITE_API_BASE || "http://localhost:3001"`
- ✅ 必须使用集中式 `api.ts` / `api module`(如 `app/admin/src/lib/api.ts``app/mini-program/src/utils/api.ts`
- ✅ 集中式模块内部读取环境变量,其他模块通过 import 使用
**验证命令**
```bash
# Admin 页面 — 检查是否有独立的 API 声明
grep -rn "NEXT_PUBLIC_API_BASE.*localhost" app/admin/src/app/ || echo "PASS: 无独立 API 声明"
grep -rn "const API.*=" app/admin/src/app/ | grep -v "import" || echo "PASS: 无独立 API 常量"
# Mini-program 页面 — 检查是否有独立的 API 声明
grep -rn "VITE_API_BASE.*localhost" app/mini-program/src/pages/ || echo "PASS: 无独立 API 声明"
grep -rn "const.*API_BASE.*=" app/mini-program/src/pages/ | grep -v "import" || echo "PASS: 无独立 API 常量"
```
5. **图标方案****Read fully and follow: ./reference/platform-uni-app.md 第 1 节(⛔ 强制)**
- 含 Admin (Next.js) 和 Mini-program (uni-app) 两条路径的完整安装配置
- 含使用方式、TabBar 图标方案、禁止方案列表
- **所有前端项目必须使用 Iconify 图标方案,禁止 emoji 充当功能图标**
6. **布局防溢出规则****参见 ./reference/platform-uni-app.md 第 5 节(⛔ 强制,小程序页面尤其重要)**
- 含防溢出规则、TabBar 遮挡处理、布局溢出检测 JS 代码
#### 3B: Epic 0 特殊实现
1. **使用 `bmad-quick-dev` 技能**
- 原因:绿地项目,无已有代码结构,无测试框架
- **容器环境服务已内置,无需安装 PostgreSQL/Redis/MinIO**
- 执行项目初始化全流程:
```
1. pnpm workspace 配置(根 + 子项目 package.json
2. uni-app 项目初始化 (app/mini-program/)
3. Next.js 管理后台 (app/admin/)
4. Next.js API 后端 (api/)
5. Prisma schema + 首次迁移(连接容器内置 PG
6. .env 配置(指向容器内置服务)
7. 基础配置文件 (TypeScript, Tailwind, ESLint)
8. 项目设计令牌全局 CSS按 04-ui-ux-design.md 规范)
```
2. **容器环境连接信息**
- PostgreSQL: `postgresql://postgres@localhost:5432/barparty`
- Redis: `redis://localhost:6379`
- MinIO: `localhost:9000``MINIO_HOST` 环境变量)
3. **验证初始化成功**
- `pnpm install` 在所有子目录成功
- `psql -U postgres -c "CREATE DATABASE barparty"` 创建数据库
- `cd api && npx prisma migrate dev` 成功运行
- `redis-cli ping` 返回 PONG
- `pnpm dev:api` 和 `pnpm dev:admin` 都能启动

View File

@@ -0,0 +1,181 @@
# Phase 4: TEST测试
> 参见 ./auto-fix-engine.md 了解统一修复引擎FAIL分类标准 + 修复流程 + GATE-CHECK
> 参见 ./e2e-testing.md 了解 Playwright MCP 测试序列详情。
### Phase 4: TEST测试
#### 4.0 开发服务器预检(前置门控)
**每轮测试前必须执行**,确保 E2E 测试目标可达:
1. **检查 pm2 服务状态**
```bash
pm2 list
```
确认 api (port 3001)、admin (port 3002)、mini-h5 (port 5173) 均为 `online`
2. **未运行则启动**
```bash
pm2 start ecosystem.config.js
```
3. **等待健康检查通过**
```bash
# API必须
curl -sf http://localhost:3001/api/health || (sleep 3 && curl -sf http://localhost:3001/api/health)
# Admin前端 Story 需要)
curl -sf http://localhost:3002 > /dev/null
# Mini-program H5小程序 Story 需要)
curl -sf http://localhost:5173 > /dev/null
```
4. **启动失败处理**
- 查看 `pm2 logs {name} --lines 20` 诊断错误
- 端口占用:`pm2 stop all && pm2 delete all && pm2 start ecosystem.config.js`
- 3 次重试仍失败 → 记录 error_log跳过 E2E 测试但**不得标记 Story 为 done**
#### 4.1 单元/集成测试
- 执行 `pnpm test`
- 捕获结果:通过/失败数、具体失败信息
#### 4.1.5 API 调用集中化验证(⛔ 强制前端检查)
**在 E2E 测试前执行,验证前端代码的 API 调用方式正确**
```bash
# Admin 页面检查 — 是否有独立的 API 声明(应使用 api.ts
grep -rn "NEXT_PUBLIC_API_BASE.*localhost\|const API.*=.*process.env\|const API.*=.*localhost" app/admin/src/app/ --include="*.tsx" --include="*.ts"
# 期望无输出PASS
# 有输出 → 发现独立声明,需要 AUTO-FIX 修复为使用 api.ts
# Mini-program 页面检查 — 是否有独立的 API 声明
grep -rn "VITE_API_BASE.*localhost\|const.*API_BASE.*=.*import.meta.env\|const.*API_BASE.*=.*localhost" app/mini-program/src/pages/ --include="*.vue" --include="*.ts"
# 期望无输出PASS
# 有输出 → 发现独立声明,需要 AUTO-FIX 修复
```
**AUTO-FIX 流程**
1. 发现独立 API 声明 → 定位该页面文件
2. 查看集中式 API 模块(`app/admin/src/lib/api.ts` 或 `app/mini-program/src/utils/api.ts`
3. 如果集中式模块存在 → 替换独立声明为 import 集中式模块
4. 如果集中式模块不存在 → 创建集中式模块,再替换
5. 重启前端服务 `pm2 restart admin` / `pm2 restart mini-h5`
6. 重新执行 4.1.5 验证
**结果记录**
```yaml
verification_detail:
api_centralized: true/false
api_centralized_violations: ["app/admin/src/app/login/page.tsx:12"] # 仅失败时
```
#### 4.2 E2E 测试Playwright MCP
> **⛔ 强制关卡**:涉及前端页面或 API 路由的 Story **必须**执行 E2E 测试。仅纯数据库迁移、纯 Prisma Schema 变更、纯配置文件变更等无运行时行为的 Story 可跳过,但必须在 pipeline-state 中记录 `e2e_skipped_reason`。
> **📋 验证要求矩阵**:不同类型的 Story 有不同的验证要求,详见下方[按 Story 类型的验证要求矩阵](#按-story-类型的验证要求矩阵)。
**所有前端均使用 Playwright MCP 进行端到端测试**
> **🌐 外网域名优先**E2E 测试应优先使用外网域名(从 `pipeline-context.md` 获取覆盖完整网络链路K8S ingress → NPM → localhost。如果外网域名不可达curl 超时),回退到 localhost 但需在 pipeline-context.md 记录 warning。
##### 管理后台Next.js测试流程
```
browser_navigate → {ADMIN_URL}/dashboard/xxx 从 pipeline-context.md 读取,禁止硬编码 localhost
browser_snapshot → 验证页面加载
browser_type → 填写表单字段
browser_click → 提交/操作按钮
browser_wait_for → 等待成功消息或数据加载
browser_snapshot → 验证结果状态
browser_take_screenshot → 捕获证据截图
browser_network_requests → API 调用目标检测(⚠️ 强制,验证 API 不指向 localhost
```
##### 小程序用户端uni-app H5 模式)测试流程
通过 `pnpm dev:h5` 将 uni-app 编译为 H5 Web 版本,使用 Playwright MCP 进行完整 E2E 测试:
```
browser_navigate → {MINI_URL}/#/pages/xxx/index 从 pipeline-context.md 读取,禁止硬编码 localhost
browser_snapshot → 验证页面渲染
browser_click → 按钮交互(登录、报名、支付等)
browser_type → 输入框填写
browser_fill_form → 批量表单填写
browser_snapshot → 验证交互结果
browser_take_screenshot → 捕获界面截图
browser_network_requests → API 调用目标检测(⚠️ 强制,验证 API 不指向 localhost
```
适用场景:
- 用户注册/登录流程
- 活动列表浏览与搜索
- 活动详情查看与报名
- 订单创建与支付流程
- 局头发布组局流程
- 收益查看与提现申请
- 评价提交
##### API 路由测试
```
browser_navigate → {API_URL}/api/xxx 从 pipeline-context.md 读取,禁止硬编码 localhost
browser_evaluate → 验证 JSON 响应结构和状态码
```
#### 4.3 调用 `bmad-qa-generate-e2e-tests`(流水线模式)
- **非交互模式**:不询问用户测试什么,直接根据 Story 规格文件自动确定测试范围
- Story 规格中包含 API 路由 → 自动生成 API 测试
- Story 规格中包含前端页面 → 自动生成 Playwright MCP E2E 测试
- 纯后端/内部 Story如 Prisma 迁移、中间件)→ 跳过 E2E在 pipeline-state 记录 `e2e_skipped_reason`
- 测试文件生成到 `tests/e2e/` 目录
- **生成后立即执行**:通过 Playwright MCP 运行测试序列验证
#### 4.4 测试结果判定(两级门控)
**Gate 1: 单元/集成测试pnpm test**
- 全部通过 → 进入 Gate 2
- 有失败 → 进入 AUTO-FIX 循环
**Gate 2: E2E 测试Playwright MCP**
- 前端 Story必须通过页面可加载、交互可完成
- 后端 Story必须通过API 返回正确 JSON 结构)
- 纯 Schema/配置 Story跳过需记录 `e2e_skipped_reason`
- 有失败 → 进入 AUTO-FIX 循环
**两级均通过** → 进入 Phase 5: REVIEW
**任一级失败** → 进入 Phase 4.5: AUTO-FIX
### Phase 4.5: AUTO-FIX自动修复循环
```
测试失败
分析错误(编译错误 / 断言失败 / 运行时错误 / E2E 选择器问题 / dev server 未就绪)
如果是 dev server 问题 → pm2 restart {name} → 等待 ready → 重测
如果是代码问题 → 调用 bmad-quick-devintent: "修复 {story_key} 的失败测试")→ 重测
重新执行测试
┌──── 通过? ────┐
│ │
Yes No
│ │
↓ ↓
REVIEW attempt < 3?
│ │
Yes No
│ │
↓ ↓
重试 LOG FAILURE
标记 story = failed
继续下一个 Story
```
> 完整的 AUTO-FIX/IMPLEMENT/DATA-FIX 分类标准和修复流程见 ./auto-fix-engine.md 第 2-4 节

View File

@@ -0,0 +1,160 @@
# Phase 5-7: REVIEW + EPIC-DONE + COMPLETE
> 参见 ./auto-fix-engine.md 了解统一修复引擎FAIL分类标准 + 修复流程 + GATE-CHECK
> 参见 ./iron-laws.md 了解质量关卡规则。
### Phase 5: REVIEW代码审查
1. **调用 `bmad-code-review` 技能**
- 收集未提交的代码变更git diff
- 启动并行审查子代理Blind Hunter、Edge Case Hunter、Acceptance Auditor
- 输出分级发现Critical / High / Medium / Low
2. **评估审查结果**
- **无 Critical 或 High** → 标记 Story 为 `done`
- **有 Critical 或 High**
- 调用 `bmad-quick-dev` 自动修复
- 修复后重跑测试
- 仍失败则记录到 error_log继续下一个 Story
3. **更新状态(三写,必须同步)**
- sprint-statusStory 状态 → `done`
- pipeline-state`stories_completed` +1`story_results` 添加验证详情
- pipeline-context.md更新进度追踪表中对应 Story 行为 ✅ done
- **三个文件必须在同一个 Agent turn 中原子更新**,不能只更新其中一两个
- 更新后验证一致性:
```bash
# 检查 pipeline-state 和 pipeline-context 中的状态是否一致
grep -c "status: done" _bmad-output/implementation-artifacts/pipeline-state.yaml
grep -c "✅ done" _bmad-output/implementation-artifacts/pipeline-context.md
# 两个数字应该相同
```
- 或标记为 `review-blocked`
4. **分类提交 Story 变更**
按变更类型分别提交,保持 git 历史清晰可追溯:
| 提交顺序 | 类型 | 文件范围 | Commit Message 格式 |
|---------|------|---------|-------------------|
| 1⃣ | 基础设施 | `package.json`、`prisma/`、`docker-compose.yml`、`tsconfig.json`、`tailwind.config.*` | `chore: {描述}` |
| 2⃣ | 后端代码 | `api/src/` 目录下的路由、服务、中间件 | `feat(api): {story_key} - {描述}` |
| 3⃣ | 小程序代码 | `app/mini-program/src/` 目录下的页面、组件、Store | `feat(mini): {story_key} - {描述}` |
| 4⃣ | 管理后台代码 | `app/admin/src/` 目录下的页面、组件、API | `feat(admin): {story_key} - {描述}` |
| 5⃣ | 测试代码 | `tests/`、`**/*.test.ts`、`**/*.spec.ts` | `test: {story_key} - {描述}` |
| 6⃣ | 流水线状态 | `pipeline-state.yaml`、`sprint-status.yaml` | `ci(pipeline): update {story_key} status` |
**规则**
- 如果某类型无变更则跳过该提交
- **不自动推送**(由 Epic 完成时统一推送)
- 提交前用 `git diff --stat` 确认变更范围
- 排除 `.env`、`node_modules/`、`.next/`、`dist/`
### Phase 6: EPIC-DONEEpic 完成)
每个 Epic 的所有 Story 完成后:
1. **验证所有 Story 状态**
- 检查 sprint-status 中该 Epic 所有 Story 均为 `done`
2. **运行用户旅程测试Epic 级集成验证)**
- **读取 Phase 1 预定义的旅程产物文件** `{implementation_artifacts}/epic-N-journeys.md`
- 如果旅程文件不存在 → 回退读取项目 PRD/需求文档自主识别(兼容模式):
- 完整模式:由 Phase-01 步骤 4 预定义,此处直接读取
- 验证模式:由 Phase 0V 步骤 4.5 预定义/恢复,此处直接读取
- 回退:从 PRD + Epic 定义文件 + 已实现源码自主识别跨页面业务流程
- 使用 Playwright MCP **严格按预定义旅程步骤执行** 跨页面完整业务流程测试
- 测试方法论详见 `e2e-testing.md` 的"用户旅程测试"章节
- 旅程必须跨越至少 2 个页面,包含至少 1 个数据状态变化
- 旅程测试结果记录到 pipeline-state.yaml 的 `user_journeys` 部分
- **⛔ 旅程全部通过才可标记 Epic done否则走 AUTO-FIX → 重测旅程max 2x**
3. **更新 Sprint Status**
- Epic 状态设为 `done`
4. **Git 推送 Epic 里程碑**
- 确认所有 Story 变更已分类提交(检查 `git status` 是否干净)
- 如有遗漏文件,按分类规则补提
- `git push origin main`
- 推送失败时记录到 error_log不中断流水线
### Phase 7: COMPLETE完成
**⛔ 阻断保护:进入 Phase 7 前必须通过以下检查**
```bash
# 检查: 用户旅程不能为空
JOURNEY_COUNT=$(grep -c "status: pass\|status: fail" _bmad-output/implementation-artifacts/pipeline-state.yaml 2>/dev/null || echo 0)
echo "journey_results=$JOURNEY_COUNT"
```
| 检查条件 | 阻断行为 |
|---------|---------|
| `JOURNEY_COUNT === 0` 且存在非基础设施 Epic | **⛔ 阻断** — 回退执行缺失的旅程,不得标记 complete |
| `JOURNEY_COUNT > 0` 但有 `status: fail` | 记录 done-with-issues附上失败旅程列表 |
| 所有旅程 `status: pass` | 正常进入 complete |
**阻断时的恢复动作**
1. 读取 Phase 1 预定义的 `epic-N-journeys.md` 文件(完整模式)或 Phase 0V 步骤 4.5 预定义的文件(验证模式)
2. 如文件不存在,从 PRD + Epic 定义文件自主提取旅程(兼容模式)
3. 对缺失的 Epic 执行旅程测试
4. 将结果写入 pipeline-state.yaml 的 user_journeys
5. 重新执行阻断检查
---
1. **生成最终报告** `{implementation_artifacts}/pipeline-report.md`
- Story 统计:尝试 / 完成 / 失败
- 每个 Epic 摘要
- **用户旅程执行摘要**(每个 Epic 的旅程通过/失败状态)
- 创建文件清单
- 测试覆盖率摘要
- 未解决问题清单
- 数据库迁移历史
2. **更新管道状态**
- `status: complete`(或 `done-with-issues`
3. **Git 最终提交推送**
- 提交所有剩余变更pipeline-report.md、pipeline-state.yaml 等)
- `git push origin main`
- 确保所有 Epic 的代码都已推送到远程
4. **输出摘要**
- 完成状态
- 报告位置
- 建议下一步(部署、手动测试、生产配置)
## 质量关卡定义
**以下关卡在验证模式中为强制性阻断机制,检测不通过时 Story/Epic 不得标记为完成。**
### 关卡 1: 布局完整性
- 检测: `browser_evaluate` → `horizontal_overflow`
- 通过: `horizontal_overflow === false`
- 阻断: 不通过不得继续,必须进入 AUTO-FIX
### 关卡 2: 视觉完整性
- 检测: `analyze_image` → `icons_check`
- 通过: 无 broken image、无 emoji 充当图标、TabBar 有实际图标文件
- 阻断: 不通过不得继续,必须进入 AUTO-FIX
### 关卡 3: 代码架构合规(仅 admin Story
- 检测: `grep` 内联 API 声明(`const API =`
- 通过: 0 个文件使用内联声明,全部使用 `@/lib/api.ts`
- 阻断: 不通过不得继续,必须进入 AUTO-FIX
### 关卡 4: 用户旅程
- 检测: Epic 级跨页面业务流程测试Playwright MCP
- 通过: 所有旅程 PASS
- 阻断: Epic 不得标记 done可标记 `done-with-issues`
### 关卡 5: 原型同步(⚠️ 铁律六d8d-iterate 编排下生效)
- 检测: 检查本次 Epic 是否有新 UI 页面,原型中是否已对应
- 通过: 无新 UI 页面,或所有新 UI 页面在原型中已有对应文件
- 阻断: 仅在 d8d-iterate 编排模式下生效d8d-dev 独立运行时记录为 WARN
- 修复: 调用 d8d-prototype --incremental 更新原型
> 完整的 FAIL 分类标准、GATE-CHECK 和修复流程见 ./auto-fix-engine.md

View File

@@ -1,7 +1,7 @@
# Uni-app 微信小程序平台参考
> **⛔ 主流程文件统一引用此文件**:所有 uni-app/微信小程序的平台特定内容集中于此。
> 主流程文件auto-fix/engine / iron-laws / journey-mode / visual-verification / validation-mode / e2e-testing / phase-03-implement中不再内联平台特定方案改为引用本文件对应章节。
> 主流程文件auto-fix-engine / iron-laws / journey-mode / visual-verification / validation-mode / e2e-testing / phase-03-implement中不再内联平台特定方案改为引用本文件对应章节。
---
@@ -397,131 +397,3 @@ onPullDownRefresh(() => {
uni.stopPullDownRefresh(); // ← 新增
}
```
---
## 8. H5 不兼容组件替换方案(⛔ 强制)
### 问题
uni-app 原生组件(`<checkbox>`/`<radio>`/`<switch>`/`<slider>`/`<picker>` 等)在 H5 环境下可能出现:
- 不渲染DOM 中存在但宽高为 0
- 不可交互(点击无响应)
- 样式异常(默认浏览器样式覆盖 Neon Pulse 设计)
微信小程序正常但 H5 不正常的组件,**不得标记为"环境限制"跳过**。必须用跨平台兼容的纯 view + CSS 方案替换。
### ⛔ 核心原则
- **禁止**使用 uni-app 原生表单组件(`<checkbox>`/`<radio>`/`<switch>`/`<slider>`/`<picker>`
- **必须**使用纯 `<view>` + CSS + `@tap` 实现等效功能
- 这样做:小程序和 H5 都能正常渲染,无需任何平台判断
### 不兼容组件清单
| uni-app 原生组件 | H5 问题 | 替换方案 |
|------------------|---------|---------|
| `<checkbox>` | 不渲染或样式异常 | view + Iconify check 图标 |
| `<radio>` | 不渲染或样式异常 | view + CSS 圆点 |
| `<switch>` | 样式与设计系统冲突 | view + CSS 滑动开关 |
| `<slider>` | 样式不可控 | view + CSS 进度条 + @touchmove |
| `<picker>` | 原生弹窗与 H5 不兼容 | 自定义弹窗组件 |
### 检测方法(⛔ 旅程验证步骤级强制)
```bash
# 扫描所有 .vue 文件中的原生表单组件
grep -rn '<checkbox\|<radio\|<switch\|<slider\|<picker' app/mini-program/src/ --include="*.vue"
# 发现任何匹配 → 触发 AUTO-FIX #23
```
### 替换模板
#### checkbox → view + Iconify check
```vue
<!-- 修复前: -->
<checkbox :checked="agreed" @tap="agreed = !agreed" color="#ffb3b5" />
<!-- 修复后: -->
<view class="custom-checkbox" :class="{ checked: agreed }" @tap="agreed = !agreed">
<view v-if="agreed" class="i-lucide-check check-icon"></view>
</view>
```
```css
.custom-checkbox {
width: 18px; height: 18px; min-width: 18px;
border-radius: 4px;
border: 1.5px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.05);
display: flex; align-items: center; justify-content: center;
transition: all 0.2s ease;
}
.custom-checkbox.checked {
background: linear-gradient(135deg, #ffb3b5, #ff5167);
border-color: #ff5167;
}
.check-icon { width: 12px; height: 12px; color: #000; }
```
#### radio → view + CSS 圆点
```vue
<!-- 修复前: -->
<radio :checked="selected === 'A'" @tap="selected = 'A'" color="#ffb3b5" />
<!-- 修复后: -->
<view class="custom-radio" :class="{ checked: selected === 'A' }" @tap="selected = 'A'">
<view v-if="selected === 'A'" class="radio-dot"></view>
</view>
```
```css
.custom-radio {
width: 18px; height: 18px; min-width: 18px;
border-radius: 50%;
border: 1.5px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.05);
display: flex; align-items: center; justify-content: center;
transition: all 0.2s ease;
}
.custom-radio.checked { border-color: #ff5167; }
.radio-dot {
width: 10px; height: 10px; border-radius: 50%;
background: linear-gradient(135deg, #ffb3b5, #ff5167);
}
```
#### switch → view + CSS 滑动
```vue
<!-- 修复前: -->
<switch :checked="enabled" @change="enabled = !enabled" color="#ffb3b5" />
<!-- 修复后: -->
<view class="custom-switch" :class="{ on: enabled }" @tap="enabled = !enabled">
<view class="switch-thumb"></view>
</view>
```
```css
.custom-switch {
width: 44px; height: 24px; border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
position: relative; transition: background 0.2s;
}
.custom-switch.on { background: linear-gradient(135deg, #ffb3b5, #ff5167); }
.switch-thumb {
width: 20px; height: 20px; border-radius: 50%;
background: #fff; position: absolute; top: 2px; left: 2px;
transition: transform 0.2s;
}
.custom-switch.on .switch-thumb { transform: translateX(20px); }
```
### 关联修复规则
- 触发 FAIL 分类: ../rules/auto-fix/classification.md #23
- 修复模板: ../rules/auto-fix/workflows.md AUTO-FIX #23 章节
- ⛔ 禁止标记为"环境限制"跳过

View File

@@ -0,0 +1,579 @@
# 验证模式
> 参见 ./e2e-testing.md 了解 Playwright MCP 测试序列详情。
> 参见 ./iron-laws.md 了解四维验证检查清单。
> 参见 ./auto-fix-engine.md 了解统一修复引擎FAIL分类标准 + 修复流程 + GATE-CHECK
## 续跑逻辑
恢复执行时:
1. 读取 `pipeline-state.yaml`
2. 定位 `current_epic``current_story`
3. 根据 `story_results[story_key].status` 判断恢复点:
- `spec-created` → 从 IMPLEMENT 恢复
- `implemented` → 从 TEST 恢复
- `tested-fail` → 从 AUTO-FIX 恢复
4. 跳过 sprint-status 中已标记 `done` 的 Story
5. **步骤级恢复**(⛔ 增强 — 优先于 Story 级恢复):
- 读取 `verification-checklist.yaml` → 找到当前 Story 中第一个 `status: pending` 的 step
- 读取 `pipeline-state.yaml``step_results` → 确认最后一个已完成步骤
- 从下一个步骤继续执行(跳过已完成的步骤,不重复截图/分析)
- 如无 pending step → 该 Story 验证已完成,继续下一个 Story
## 验证模式续跑
**适用场景**:验证模式被中断(上下文压缩、会话断开、用户暂停),需要恢复执行。
### 恢复流程
1. **用户重新调用 `/d8d-dev` 并选择验证模式**
2. **按顺序读取恢复信息**
```
pipeline-context.md → 凭据/端口/路由/进度
pipeline-state.yaml → 每个 Story 的 verified 状态和 verification_detail
sprint-status.yaml → Story 标题和 execution_order
TaskList → 已有 Task 状态(无 Task 则执行 Phase 0V 批量创建)
pm2 list → 确认服务运行
```
3. **定位恢复点**
- 找到 `pipeline-state.yaml` 中第一个 `verified: false` 或无 `verified` 字段的 Story
- 或找到 `TaskList` 中第一个 `status: pending` 的 Task
- 从该 Story 继续验证
4. **输出恢复信息**
```
🔄 恢复验证模式:已完成 M/K Story从 Story X-Y 继续
```
5. **服务预检**`pm2 list` 确认三个服务 online否则启动
### 续跑时的注意事项
- 已 `verified: true` 的 Story 跳过,不重复验证
- 如果 `pipeline-context.md` 存在,从中读取凭据和路由,不需要重新发现
- 如果 TaskList 为空,执行 Phase 0V 批量创建(但已有 verified=true 的直接标记 completed
- 恢复后的第一个 Story 仍然需要执行完整的 8 步验证序列
## 验证模式逻辑
**适用场景**:代码已实现(首次开发或之前的流水线已完成),需要逐 Story 回归验证。
### Phase 0V: 批量任务创建(验证模式前置步骤)
**在开始逐 Story 验证之前,一次性创建所有 Task而非逐个创建。** 这解决了上下文压缩后无法看到全局进度的问题。
1. **读取 Story 规格文档**
- 扫描 `{implementation_artifacts}/` 下的 Story 规格文件(如 `2-1-create-event.md`
- 从每个规格文件提取:
- **Acceptance Criteria**:验收标准
- **Validation Tasks**V1/V2/V3具体验证检查点
- **Proto Reference**:关联的原型页面
- 如果某个 Story 没有规格文件,从 sprint-status.yaml 获取标题和 assignee
2. **批量 TaskCreate × N**N = Story 总数)
- 每个 Task 的 description 从对应规格文档提取:
```
subject: "Verify Story 2-1: 创建组局"
description: |
验收标准(来自 2-1-create-event.md
V1 API: POST /api/events 201/400/401/403 测试
V2 前端: 表单字段/标签切换/人数计数器/价格输入/提交
V3 集成: DB 记录创建/organizerId 关联
Story 类型: API+前端 (assignee: frontend+backend)
```
3. **生成步骤级验证清单**(⛔ 强制 — 写入 verification-checklist.yaml
- 为每个待验证 Story 从 V1/V2/V3 检查点生成操作步骤条目
- 每个步骤包含id、phaseapi/e2e/source-check、action、expected、screenshot_required、screenshot_path、analyze_image_required、status初始 pending
- 为每个 Story 生成 gate_check 条目9 项检查,初始 pending
- 写入 `_bmad-output/implementation-artifacts/verification-checklist.yaml`
```yaml
generated_at: "{ISO timestamp}"
mode: "validation"
stories:
"X-Y-title":
title: "{Story 标题}"
assignee: "{assignee}"
steps:
- id: "X-Y-s{N}"
phase: "api|e2e|source-check"
action: "{操作描述}"
expected: "{预期结果}"
screenshot_required: true/false
screenshot_path: "tests/e2e/screenshots/story-X-Y-step{N}.png"
analyze_image_required: true/false
status: "pending"
gate_check:
- check: "{检查项名称}"
status: "pending"
```
4. **标记已完成的 Task**
- 已 `verified: true` 的 Story → 立即 `TaskUpdate: completed`
- 同步将 verification-checklist.yaml 中对应 Story 的所有 steps 标记为 `skipped`
- 输出: `已创建 N 个验证 Task其中 M 个已完成跳过K 个待验证`
5. **恢复时检查 TaskList 和清单文件**
- 如果 TaskList 已有 Task续跑场景→ 跳过批量创建,直接找 pending Task 继续
- 如果 verification-checklist.yaml 已存在 → 跳过清单生成,从第一个 status=pending 的 step 继续执行
### Phase 0V COMPLETE GATE
⛔ Phase 0V 以下条件全部满足后,才能进入 Story 验证循环:
1. 所有 Task 已创建(每个未验证 Story 各有 1 个 Task
2. 三个服务 URL 已验证可用
3. pipeline-context.md 已读取且凭据有效
任一未满足 → **停在 Phase 0V**,修复后再继续。不得跳过此门进入 Story 验证。
### 验证模式状态机
```
Phase 0V: 批量创建 Task + 读取 Story 规格文档
读取 pipeline-context.md + pipeline-state.yaml + sprint-status.yaml
构建验证队列(所有 Story按 execution_order
逐 Story 独立执行(一个完成才能开始下一个):
┌───────────────────────────────────────────────────┐
│ PER-STORY VERIFICATION每个 Story 独立执行) │
│ │
│ 0. 读取 pipeline-context.md 恢复上下文 │
│ 1. TaskUpdate → in_progress │
│ 2. 读取 Story 规格文档提取 Validation Tasks │
│ 3. VERIFY-API → curl 测试(按 V1 检查点) │
│ 4. VERIFY-E2E → Playwright MCP按 V2 检查点) │
│ ├── browser_resize设置正确视口
│ ├── browser_navigate + browser_snapshot │
│ ├── browser_type + browser_click交互操作
│ ├── browser_wait_for + browser_snapshot验证
│ ├── browser_take_screenshot保存证据
│ └── analyze_imageAI 视觉验证项目设计系统) │
│ 4.5. GATE-CHECK⛔ 必须全部通过才能继续) │
│ 检查项: │
│ - layout_check.horizontal_overflow === false │
│ - icons_check.broken_images === false │
│ - icons_check.emoji_as_icons === false │
│ - api_target_verified === true外网模式下
│ 任一 FAIL → 走 AUTO-FIX 路径 │
│ 5. MARK-VERIFIED → pipeline-state + pipeline-context│
└──────┬────────────────────────────────────────────┘
│ 失败
┌──────────────────┐ ┌───────────────────────────────────┐
│ AUTO-FIX │ │ AUTO-IMPLEMENT │
│ (Bug修复, max 3x)│ │ (未实现功能, max 2x) │
│ Edit 改几行代码 │ │ 读取 phase-03 方案 → 完整实现 │
│ → 重测 │ │ Bash+Write+Edit → 重启 → 重测 │
└──────────────────┘ └───────────────────────────────────┘
↓(所有 Story verified 后)
继续下一个 Epic
> 跨 Epic 的用户旅程验证已独立为「旅程验证模式」,不在此模式中执行。
> 回归验证模式只负责逐 Story 功能点验证。完成所有 Story 后建议进入旅程验证模式。
> AUTO-FIX/AUTO-IMPLEMENT/AUTO-DATA-FIX 的完整分类标准和修复流程见 ./auto-fix-engine.md
```
### 与完整模式的区别
- **不执行** CREATE-SPEC、IMPLEMENT、REVIEW 阶段
- **只执行** TEST单元测试 + E2E+ AUTO-FIX
- 每个验证通过的 Story 标记 `verified: true`
### 验证模式的 Task 粒度规则
**铁律三同样适用于验证模式**。每个 Story 必须创建独立的验证 Task独立执行完毕后才能开始下一个。
| 规则 | 说明 |
|------|------|
| 35 个 Story = 35 个 TaskCreate | 每个 Task 对应一个 Story禁止合并 |
| 每个 Task 独立执行完毕后才能创建下一个 | 禁止批量 |
| 禁止一个 Bash 命令测试多个 Story 的 API | 每条 curl 只测一个端点 |
| 禁止一次 Playwright 会话验证多个 Story | 每个 Story 独立 session |
### 验证模式的执行模板(每个 Story
**每个 Story 的验证必须按以下模板逐步执行,不允许省略任何步骤**
#### 步骤 1: 创建 Task
```
TaskCreate: subject: "Verify Story X-Y: {title}"
TaskUpdate: status: in_progress
```
#### 步骤 1.1: 解析 URL每个 Story 必须执行)
从 `pipeline-context.md` 的"## 当前使用的 URL"部分读取本次验证使用的 URL。
**如果 pipeline-context.md 已有"当前使用的 URL"且三个 URL 都可用** → 直接使用,跳过重新探测。
**如果 pipeline-context.md 没有此部分或某个 URL 不可用** → 执行 URL 解析三步法:
```
对每个服务API / Admin / Mini-H5
Step 1: curl -sf --max-time 5 https://{外网域名}
↳ 200 → 使用 HTTPS
↳ 失败 → Step 2
Step 2: curl -sf --max-time 5 http://{外网域名}
↳ 200 → 使用 HTTP
↳ 502 → Step 2.5(诊断修复,见 phase-00-setup.md 4.6
↳ 失败 → Step 3
Step 3: curl -sf --max-time 5 http://localhost:{port}
↳ 200 → 使用 localhost记录 fallback_reason
↳ 失败 → 服务未启动
```
**⛔ 关键规则**
- **禁止跳过 HTTP 直接 fallback 到 localhost**
- **502 是可修复的配置问题**(通常是 Vite 监听地址不对),必须先修复再继续
- 禁止在步骤 3/4 中硬编码 localhost必须使用解析出的 URL
```
**三个基础 URL 解析后供后续步骤使用**
- `API_URL`:用于步骤 3 的 curl 测试
- `ADMIN_URL`:用于步骤 4 管理后台的 browser_navigate
- `MINI_URL`:用于步骤 4 小程序的 browser_navigate
#### 步骤 1.5: API 路由发现(仅首次验证时)
**在验证第一个 Story 之前**执行一次路由发现,后续 Story 从 `pipeline-context.md` 查找端点:
```bash
find api/src/app/api -name "route.ts" | sort
```
将发现的完整路由列表写入 `pipeline-context.md` 的"已发现的关键路由"部分。后续每个 Story 验证时直接从 pipeline-context.md 查找对应端点,避免盲目测试导致 404。
#### 步骤 2: 读取 Story 规格文档
- 读取 `{implementation_artifacts}/{epic_num}-{story_num}-{title}.md`(如果存在)
- 从中提取 **Validation Tasks** 作为具体检查项:
- **V1: API Route Validation** → 作为步骤 3 API 验证的检查清单
- **V2: Frontend Page Validation** → 作为步骤 4 E2E 验证的检查清单
- **V3: Integration Validation** → 作为步骤 3/4 的补充检查
- 如果规格文件不存在,从 sprint-status.yaml 的 assignee 字段判断 Story 类型:
- `backend` → 纯后端
- `frontend` → 纯前端
- `frontend+backend` → API+前端
- `admin` → 管理后台
- `backend+admin` → API+管理后台
#### 步骤 3: API 验证Story 涉及 API 路由时)
- **一个 Bash 命令只测试当前 Story 的 API 端点**
- 按 Story 规格文档的 V1 Validation Tasks 逐项验证
- 验证 HTTP status code + JSON 响应结构
- 纯前端 Story无新 API可跳过但需在 Task description 中记录 `api_skipped: true`
#### 步骤 3.5: 源码三重检查Story 涉及任何前端代码时强制执行)
**适用范围**: app/ 下所有前端源码admin、mini-program、web 等),排除 node_modules。
**对任何涉及前端页面的 Story 强制执行,不仅限于 admin。**
**检查 1: API 声明 + localhost fallback通用**
```bash
# 搜索所有前端代码中的 localhost fallback 值
LOCALHOST_FALLBACK=$(grep -rn "localhost:[0-9]" app/*/src/ --include="*.ts" --include="*.tsx" --include="*.vue" | grep -v node_modules)
LOCALHOST_COUNT=$(echo "$LOCALHOST_FALLBACK" | grep -c ":" || echo 0)
# 搜索非集中式的内联 API 声明(排除已集中化的模块文件)
INLINE_FILES=$(grep -rn "const.*API_BASE\s*=" app/*/src/ --include="*.ts" --include="*.tsx" --include="*.vue" | grep -v node_modules | grep -v "lib/api\|config/api\|lib/config")
INLINE_COUNT=$(echo "$INLINE_FILES" | grep -c ":" || echo 0)
echo "localhost_fallback=$LOCALHOST_COUNT inline_api=$INLINE_COUNT"
[ "$LOCALHOST_COUNT" -gt 0 ] && echo "LOCALHOST_FILES:" && echo "$LOCALHOST_FALLBACK"
[ "$INLINE_COUNT" -gt 0 ] && echo "INLINE_FILES:" && echo "$INLINE_FILES"
```
**判定**
- `LOCALHOST_COUNT > 0` → **FAIL (Bug)** → AUTO-FIX
- `INLINE_COUNT > 0` → **FAIL** → 集中模块存在则 AUTO-FIX不存在则 AUTO-IMPLEMENT
- 全部为 0 → **PASS**
**检查 2: 图标方案合规(小程序/移动端 Story 强制)**
> 完整检测命令和判定规则见 **./reference/platform-uni-app.md 第 6 节**。
**判定**
- `tabbar_iconpath_empty=true` → **FAIL (未实现)** → AUTO-IMPLEMENT
- mini-program 缺 Tailwind/Iconify → **WARN (未实现)** → AUTO-IMPLEMENT
**检查 3: Emoji 充当功能图标(所有前端强制)**
> 完整检测命令和判定规则见 **./reference/platform-uni-app.md 第 6 节**。
**判定**
- `EMOJI_COUNT > 0` → **FAIL (未实现)** → AUTO-IMPLEMENT
- `EMOJI_COUNT === 0` → **PASS**
**AUTO-FIX 动作Bug 类型)**
1. localhost fallback → 从 pipeline-context.md 读取正确域名Edit 替换
2. 内联 API 声明(集中模块已存在)→ Edit 替换为 import 引用
3. 替换后重新执行对应检查
**AUTO-IMPLEMENT 动作(未实现类型)**
1. 读取 `phase-03-implement.md` 对应方案章节(如第 5 节图标方案)
2. 按方案逐步执行安装依赖Bash→ 创建文件Write→ 修改引用Edit→ 重启服务Bash
3. 重测验证
**⛔ AUTO-FIX/IMPLEMENT 失败阻断**:源码三重检查 AUTO-FIX/AUTO-IMPLEMENT 重试 3 次后仍 FAIL → Story 标记为 `failed`,记录到 `error_log`,继续下一个 Story。不得反复重试或跳过。
> 完整的 AUTO-FIX/IMPLEMENT/DATA-FIX 流程和重试上限见 ./auto-fix-engine.md 第 4 节
**检查 4: 未实现功能占位符(所有前端强制)**
```bash
# 搜索前端代码中的"暂未开放"等占位符文本
PLACEHOLDER=$(grep -rn "暂未开放\|功能.*开发中\|TODO.*实现\|not.*implemented\|暂未实现\|功能未开放" \
app/*/src/ --include="*.vue" --include="*.tsx" --include="*.ts" | grep -v node_modules)
PLACEHOLDER_COUNT=$(echo "$PLACEHOLDER" | grep -c ":" || echo 0)
echo "placeholder_functions=$PLACEHOLDER_COUNT"
[ "$PLACEHOLDER_COUNT" -gt 0 ] && echo "PLACEHOLDER_FILES:" && echo "$PLACEHOLDER"
```
**判定**
- `PLACEHOLDER_COUNT > 0` → **FAIL (未实现)** → AUTO-IMPLEMENT: 实现该功能
- `PLACEHOLDER_COUNT === 0` → **PASS**
**检查 5: 零交互元素检测Story 涉及前端页面时浏览器端检查)**
在 E2E 验证步骤(步骤 4`browser_navigate` 到目标页面后,`browser_snapshot` 如显示有内容但无交互元素button/input/a/form执行
```javascript
// browser_evaluate 检查
() => {
const interactive = document.querySelectorAll('button, input, a, form, [role="button"], [role="link"]');
const hasContent = document.body.innerText.trim().length > 0;
const hasNextRoot = !!document.getElementById('__next');
const hasReactRoot = !!document.querySelector('[data-reactroot]');
const hasVueApp = !!document.querySelector('[data-v-app]');
return { interactiveCount: interactive.length, hasContent, frameworkMounted: hasNextRoot || hasReactRoot || hasVueApp };
}
```
**判定**
- `interactiveCount === 0` 且 `hasContent === true` → **FAIL** → 触发 FAIL #5 诊断子步骤(见 auto-fix-engine.md
- `interactiveCount > 0` → **PASS** → 继续正常 E2E 流程
**检查 6: UI 硬编码英文枚举值(所有前端 Story 强制)**
在 E2E 验证步骤(步骤 4`browser_navigate` 到任何前端页面后,执行:
```javascript
// browser_evaluate 检查页面可见文本是否包含英文枚举值
() => {
const text = document.body.innerText;
const enumPatterns = /\b(APPROVED|REJECTED|PENDING|PAID|CANCELLED|REFUNDED|DRAFT|ONGOING|COMPLETED|AVAILABLE|FROZEN|WITHDRAWN)\b/g;
const matches = text.match(enumPatterns);
return { hasEnglishEnum: matches !== null, matches: matches || [] };
}
```
**判定**
- `hasEnglishEnum === true` → **FAIL (#15)** → AUTO-FIX添加中文映射函数
- `hasEnglishEnum === false` → **PASS**
#### 步骤 4: E2E 交互验证Story 涉及前端页面时)
- **必须使用完整 8 步 Playwright 测试序列**(见下方"Playwright MCP 测试序列模式"
- 按 Story 规格文档的 V2 Validation Tasks 逐项验证
- **禁止只执行 navigate + snapshot 就跳过** —— 这是验证模式最常见的失败模式
- 管理后台:必须填写表单、点击按钮、验证提交结果
- 小程序:必须模拟用户交互流程(登录/浏览/报名等)
- 切换前端目标时必须 `browser_resize` 到正确视口
- **⛔ 视觉验证关卡**(⛔ 强制 — Read fully and follow: ./visual-verification.md
- 每个关键操作后必须通过视觉验证关卡(独立步骤,不可跳过)
- 关卡 = browser_take_screenshot + analyze_image使用 visual-verification.md 标准提示词)
- 关卡未通过 → 按分流规则处理,不得进入下一个操作
**⛔ 步骤级跟踪(每个 E2E 操作步骤完成后强制执行)**
每个操作步骤完成后,必须执行以下 5 项操作(与视觉验证关卡配合执行):
```
步骤级跟踪流程(每个 E2E 操作后):
1. 截图保存 → browser_take_screenshot
2. 视觉分析 → analyze_image如 analyze_image_required=true
3. 更新清单 → Edit verification-checklist.yaml 对应 step 的 status
4. 记录结果 → 追加 step_results 到 pipeline-state.yaml:
```yaml
- step_id: "X-Y-s{N}"
phase: "e2e"
action: "{操作描述}"
status: "pass|fail|fixed|skipped"
screenshot_path: "tests/e2e/screenshots/story-X-Y-step{N}.png"
analyze_image_result: { ... } # 如有
issues_detected: [ ... ] # 检测到的问题(如有)
auto_fixes: [ ... ] # 自动修复记录(如有)
step_gate: "pass|fail|blocked" # 步骤级门禁结果
gate_checks: # 5 项检查结果
- check: "screenshot_consistency"
result: "pass|fail"
- check: "analyze_image_executed"
result: "pass|fail"
- check: "checklist_synced"
result: "pass|fail"
- check: "state_persisted"
result: "pass|fail"
- check: "all_issues_resolved"
result: "pass|fail"
executed_at: "{ISO timestamp}"
note: "{备注}"
```
5. 步骤级 GATE → 检查 5 项条件:
├── G1: screenshot_consistency — 如步骤要求截图screenshot_path 文件存在
├── G2: analyze_image_executed — 如步骤要求分析analyze_image_result 非空
├── G3: checklist_synced — verification-checklist.yaml 中 step status 已更新
├── G4: state_persisted — pipeline-state.yaml step_results 已追加
└── G5: all_issues_resolved — issues_detected 中所有问题有对应 auto_fix 且 result=pass
任一 FAIL → 阻断,不得继续下一步骤
```
**检测到问题时的记录格式**issues_detected:
```yaml
issues_detected:
- issue_id: "ISSUE-{NNN}"
type: "{问题类型}" # layout_overflow | english_enum | emoji_icons | broken_images | svg_data_uri | dual_nav | placeholder | api_target | constraint_mismatch
severity: "{严重程度}" # bug | unimplemented | data_issue | env_limit
description: "{问题描述}"
detected_by: "{检测方式}" # browser_evaluate | analyze_image | source_scan | curl
detected_at: "{ISO timestamp}"
```
**自动修复记录格式**auto_fixes:
```yaml
auto_fixes:
- fix_id: "FIX-{NNN}"
issue_id: "ISSUE-{NNN}" # 关联的问题
fix_type: "AUTO-FIX|AUTO-IMPLEMENT|AUTO-DATA-FIX"
description: "{修复描述}"
files_changed: [ "{文件路径}" ]
retry_count: {N} # 重试次数
result: "pass|fail" # 修复结果
fixed_at: "{ISO timestamp}"
screenshot_after_fix: "{修复后截图路径}" # 修复后重测截图
```
#### 步骤 5: 最终截图保存Story 涉及前端页面时)
- browser_take_screenshot → filename: "tests/e2e/screenshots/story-{x}-{y}.png", type: "png"
- 管理后台和小程序分别截图时使用不同后缀:
- `story-x-y-admin.png`(管理后台页面)
- `story-x-y-mini.png`(小程序页面)
- 此步骤保留作为最终证据归档(中间步骤截图已在步骤 4 中完成)
#### 步骤 6: AI 视觉验证Story 涉及前端页面时)
- **⛔ 详细规则和标准提示词见 ./visual-verification.md**
- 对最终截图强制调用 `analyze_image`
- **不调用 analyze_image 等于未完成该 Story 的验证**GATE-CHECK 会验证 `analyze_image_called: true`
- 注:中间步骤的视觉验证已在步骤 4 逐步完成(视觉验证关卡),此步骤是最终确认
#### 步骤 6.5: GATE-CHECK⛔ 所有前端 Story 必须通过才能继续)
**在步骤 6analyze_image之后、步骤 7标记完成之前执行。**
检查以下条件,**任一 FAIL → 不得执行步骤 7必须进入 AUTO-FIX**
```
GATE-CHECK 检查项:
├── layout_check.horizontal_overflow === false ← 步骤 8.5 的溢出检测结果
├── icons_check.broken_images === false ← 步骤 8 的视觉验证结果
├── icons_check.emoji_as_icons === false ← 步骤 8 的视觉验证结果
├── api_target_verified === true ← 步骤 9 的 API 目标检测结果(外网模式下)
├── code_architecture_pass === true ← 步骤 3.5 的源码三重检查(所有前端 Story
├── source_code_no_localhost === true ← 步骤 3.5 检查 1localhost fallback
├── icon_solution_compliant === true ← 步骤 3.5 检查 2图标方案合规小程序/移动端)
├── source_code_no_emoji_icons === true ← 步骤 3.5 检查 3Emoji 充当图标)
├── no_placeholder_functions === true ← 步骤 3.5 检查 4"暂未开放"等未实现占位符)
├── SG1: all_steps_gated === true ← 所有 step_results 的 step_gate 均为 "pass"
├── SG2: no_unresolved_issues === true ← 所有 steps 的 issues_detected 都有对应 resolved 的 auto_fix
└── SG3: checklist_complete === true ← verification-checklist.yaml 中所有 steps 的 status 均非 "pending"
任一 FAIL → 分类分流:
Bug代码有但值错误→ AUTO-FIX
未实现(功能根本不存在)→ AUTO-IMPLEMENT
全部 PASS → 继续步骤 7
```
**⛔ 跳过此步骤直接执行步骤 7 是严重违规行为。**
**SG1-SG3 详细判定**
```
SG1 (all_steps_gated):
读取 step_results → 所有条目的 step_gate === "pass"
任一 step_gate === "fail" 或 "blocked" → SG1 FAIL
SG2 (no_unresolved_issues):
遍历所有 step_results 的 issues_detected:
对每个 issue → 在 auto_fixes 中找到 issue_id 匹配的修复记录 → result === "pass"
无匹配修复或 result !== "pass" → SG2 FAIL
SG3 (checklist_complete):
读取 verification-checklist.yaml 当前 Story 的 steps:
所有条目的 status !== "pending"
任一 status === "pending" → SG3 FAIL
```
#### 步骤 7: 标记完成(三个写入操作,缺一不可)
**7a. TaskUpdate**
```
TaskUpdate: status: completed
```
**7b. 更新 pipeline-state.yaml**
```yaml
story_results:
"X-Y-title":
status: done
verified: true
verification_detail:
api_tested: true/false/skipped
api_endpoints_tested: ["POST /api/xxx", "GET /api/xxx"]
e2e_tested: true/false/skipped
screenshot_saved: true/false
screenshot_path: "tests/e2e/screenshots/story-x-y.png"
analyze_image_called: true/false
url_used: "外网域名" / "localhost(fallback)"
api_centralized: true/false
layout_check:
horizontal_overflow: true/false
tabbar_visible: true/false/null
document_width: 375
viewport_width: 375
icons_check:
tabbar_icons_visible: true/false
broken_images: true/false
emoji_as_icons: true/false
dual_write_verified: true
# ===== 新增: 步骤级结果 =====
step_results: [] # 已在步骤 3/4 执行过程中逐步追加
# ===== 新增: Story 级问题汇总 =====
total_issues_detected: 0 # 所有步骤检测到的问题总数
total_auto_fixes: 0 # 所有步骤的自动修复总数
all_issues_resolved: true # 所有问题是否全部修复
note: "简短验证摘要"
```
**7c. 更新 pipeline-context.md 进度追踪表**
```
Edit pipeline-context.md 的"## 进度追踪"部分:
- 找到当前 Story 的行
- 将状态改为 ✅ done
- 填写验证摘要
```
**⛔ 步骤 7c 是强制操作,跳过等于上下文压缩后无法恢复进度。**
**7d. 三写一致性验证**
```bash
# 验证 pipeline-state 和 pipeline-context 中的完成数一致
STATE_COUNT=$(grep -c "status: done" _bmad-output/implementation-artifacts/pipeline-state.yaml)
CONTEXT_COUNT=$(grep -c "✅ done" _bmad-output/implementation-artifacts/pipeline-context.md)
echo "pipeline-state: $STATE_COUNT done, pipeline-context: $CONTEXT_COUNT done"
# 如果不一致 → 说明漏写了某个文件,必须修复
```
**⛔ 步骤 7a/7b/7c/7d 四个操作缺一不可缺少任何一个Story 不得标记为完成。**
**⛔ 三写一致性阻断**`STATE_COUNT ≠ CONTEXT_COUNT` 时,不得标记 Story 为完成。必须定位漏写的文件并修复后再继续。

View File

@@ -0,0 +1,617 @@
# 原型对比验证模式 - Pencil MCP 直接对比版
> **定位**:使用 Pencil MCP 工具直接从 `.pen` 设计文件提取截图,与实际页面实现做逐页视觉对比。
>
> **与 HTML 原型版对比**
> | 维度 | Pencil MCP 直接版 | HTML 原型版 |
> |------|------------------|-------------|
> | 来源 | `.pen` 设计源文件 | `prototype/pages/*.html` |
> | 保真度 | 100% 设计稿像素级 | 可能有 HTML/CSS 转换损耗 |
> | 依赖 | Pencil MCP Server | Node.js + Express |
> | 启动 | 无需额外服务 | 需启动 `prototype/server.js` |
---
## 前置:快速入口检测
当用户调用时携带两个图片标记 `[Image #1] [Image #2]`,且两张图片已经在对话中提供时:
- **约定**[Image #1] = 实际截图 (`*-actual.png`), [Image #2] = 设计稿 (`*-design.png`)
- 跳过PVD-0 整个准备阶段(不需要从 .pen 导出,不需要 Playwright 截实际图)
- 直接进入:`Phase PVD-1: 逐页对比循环`**1.C 结构校验 + AI 视觉对比** 后续步骤
- 同样执行:差异分级 → 门禁检查 → 如需修复进入 AUTO-FIX 循环 → 结果记录 → 汇总报告
这样可以复用现有完整的对比、分级、修复逻辑,只跳过图片生成步骤。
**语法支持**
- `/d8d-dev 对比 [Image #1] [Image #2]` → 明确对比
- `/d8d-dev [Image #1] [Image #2]` → 简洁形式,同样支持
---
## 前置条件
```
检查清单(任一不满足 → 报错并终止):
├── .pen 设计文件存在(如 VividSpark1.2.pen
├── Pencil MCP Server 可用(`pencil` 工具集)
├── pipeline-context.md 存在(含实际应用 URL 和凭据)
├── pm2 服务运行中(实际应用可访问)
└── 至少有一个 Epic 的 Story 已 verified
```
---
## 核心流程
```
Phase PVD-0: 准备
├── 读取 .pen 文件 → 使用 Pencil MCP 发现设计页面
├── 设计帧名称标准化 → 提取清晰名称用于路由匹配
├── 构建页面映射表(设计帧 ↔ 实际路由)
├── 交叉验证补全 → 用 pages.json 确保不漏页
└── 创建截图输出目录
Phase PVD-1: 逐页对比循环
对每个映射页面:
├── A. Pencil MCP 导出设计帧截图
├── B. Playwright 截取实际页面
├── C. 结构校验(.pen节点树+ AI 视觉对比
├── D. 差异分级 + 强制执行计数
└── E. PAGE GATE-CHECK 门禁检查
├── 🟢 通过 → G. 记录 → NEXT PAGE
└── 🔴/🟡 未通过 → F. AUTO-FIX → 重截 → 重比 → 重新 GATE-CHECK
G. 记录结果到 pipeline-state.yaml
```
---
## Phase PVD-0: 准备
### 0.1 使用 Pencil MCP 发现设计页面
```
工具: mcp__pencil__batch_get
参数:
- filePath: "VividSpark1.2.pen"
- patterns: [{"type": "frame"}] # 查找所有帧/页面
- searchDepth: 2
```
从返回结果中提取所有设计帧:
- 帧名称(如 "Login", "Dashboard"
- 帧 ID如 "node-123", "frame-456"
- 帧尺寸width, height
分类为 frontend 和 admin 组:
- 帧名称含 "admin"、"Admin" → admin 组
- 其他 → frontend 组
### 0.2 读取实际应用信息
同 HTML 原型版,读取 `_bmad-output/implementation-artifacts/pipeline-context.md` 获取:
- 实际应用 URLAPI_URL, ADMIN_URL
- 认证凭据
- 已实现的路由
### 0.3 设计帧名称标准化
**输入**: 原始设计帧列表(从 Pencil MCP 获取)
**输出**: 标准化后的列表,用于路由匹配
```
名称标准化算法:
对每个设计帧:
1. 取 node.name 原始名称
2. 移除开头的数字和点: ^[\d]+\.\s* → 删除
3. 移除常见后缀: \s*(Screen|Page|Frame|[-_]\d+)$ → 删除
4. 转为小写 → 得到标准化名称
5. 示例:
- "1. Login Screen" → "login"
- "12. Withdraw" → "withdraw"
- "06-pay-result" → "pay-result"
- "Home" → "home"
```
### 0.4 构建页面映射表
**自动映射规则**
```
对每个(标准化后)设计帧:
1. 用标准化名称在实际路由中查找匹配:
- 标准化名=login → /pages/login/index 或 /login
- 标准化名=withdraw → /pages/withdraw/index 或 /withdraw
- 其他 → /pages/{标准化名}/index 或 /{标准化名}
2. 验证实际路由可访问curl 检查非 404
3. 不可访问的页面标记为 skipped
```
**映射输出格式**(记录到 pipeline-state.yaml
```yaml
visual_diff_pencil_mapping:
- design_frame: "Login"
design_frame_id: "node-123"
actual_route: "/pages/login/index"
group: "frontend"
status: "active" # active / skipped / error
- design_frame: "Dashboard"
design_frame_id: "node-456"
actual_route: "/"
group: "frontend"
status: "active"
```
### 0.5 交叉验证补全(确保不漏页)
```
原理:代码中存在的页面一定要对比,不能因为自动映射失败就漏掉。
1. 读取 app/mini-program/src/pages.json
2. 提取所有 pages 和 subpackages 中的页面路径
3. 对每个页面路径:
- 提取页面名 = 路径倒数第二段("pages/withdraw/index" → "withdraw"
- 检查当前映射表中是否已有该页面
- 没有 → 在 Pencil MCP 所有设计帧中搜索名称包含该页面名
- 找到匹配的设计帧 → 自动添加到映射表status = active
- 没找到 → 记录 warning 到报告:"代码存在页面 {page} 但设计中找不到对应帧"
4. 最终映射表写回 pipeline-state.yaml
```
这样可以确保**代码中有的页面一定会被对比**,不会因为名称提取问题漏掉。
### 0.6 创建截图输出目录
```bash
mkdir -p tests/e2e/screenshots/visual-diff-pencil
```
### 0.7 创建任务清单
为每个 `status: active` 的映射页面**创建独立的 Task 任务**,使用 `TaskCreate` 工具:
- 每个任务记录页面名称、设计帧ID、实际页面路径
- 便于独立追踪每个页面的验证进度
- 避免单个 agent 上下文过大导致污染或遗漏
### 分批并行策略
**Anthropic API 配额限制**: 单次 5 小时窗口有请求次数限额,一次性启动全部 agent 容易触发 `429 AccountQuotaExceeded`
**推荐执行策略**:
- 每次启动 **5 个页面** 验证
- 等待全部完成后再启动下一批
- 如果遇到 429 配额超限,**提示用户更换 API key 或等待配额重置** 后继续(重置时间在错误提示中有说明)
### 0.8 启动逐个任务验证
对**每个任务**启动一个**独立的 agent**,按照 Phase PVD-1 流程执行验证。独立 agent 避免上下文污染,执行更精准。
---
## Phase PVD-1: 逐页门禁对比循环
**核心原则:逐页执行,每页必须通过门禁才能继续下一页。禁止全量截图后批量对比。**
**最佳实践:每个页面启动一个独立 agent分批并行执行避免上下文污染和 Playwright MCP 配额超限,执行更精准。**
对映射表中每个 `status: active` 的页面,**分批启动**独立 agent 依次执行以下完整循环(每个页面由独立 agent 处理):
```
PVD-1 PER-PAGE LOOP:
A. 导出设计截图
B. 截取实际页面
C. 结构校验 + AI 视觉对比
D. 差异分级
E. PAGE GATE-CHECK ⛔
├── 🟢 通过 → G. 记录 → NEXT PAGE
└── 🔴/🟡 未通过 → F. AUTO-FIX → 重截 → 重比 → 重新 GATE-CHECK
G. 记录结果到 pipeline-state.yaml
```
### 1.A Pencil MCP 导出设计帧截图
```
工具: mcp__pencil__export_nodes
参数:
- filePath: "ui.pen"
- nodeIds: ["{当前页面的帧 ID}"] # 每次只导出 1 个
- outputDir: "tests/e2e/screenshots/visual-diff-pencil"
- format: "png"
- scale: 2 # 2x 高清
```
重命名为可读名称:
- `tests/e2e/screenshots/visual-diff-pencil/{name}-design.png`
### 1.B 截取实际页面截图
使用 Playwright
```
1. browser_resize → 375x812移动端或 1440x900管理后台
2. 处理认证(如需要)→ 见下文
3. browser_navigate → {实际 URL}{route}
4. browser_wait_for → time=3
5. browser_take_screenshot(fullPage=false)
→ tests/e2e/screenshots/visual-diff-pencil/{name}-actual.png
```
### 认证处理(需要登录的页面)
大多数功能页面需要登录才能访问,未登录会自动跳转到登录页。正确流程:
1. **生成测试 token**(需要组织者权限的页面必须):
项目已提供预先生成 token 的脚本,直接运行:
```bash
node tests/generate-test-token.cjs
```
输出一个已认证组织者身份的 JWT token复制备用。
> 脚本会使用 `api/.env` 中的 `JWT_SECRET` 生成签名,确保与后端验证一致。
2. 如果已有测试 token在 `browser_navigate` **之前** 注入 token 到 `localStorage`:
```javascript
await page.evaluate((token, userInfo) => {
localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(userInfo));
}, token, userInfo);
```
其中 `userInfo` 是:
```javascript
{
id: "test-organizer-user-id",
nickname: "Test Organizer",
role: "organizer",
organizer: { id: "test-organizer-id", status: "APPROVED" }
}
```
3. 再导航到目标 URL
4. 如果需要组织者权限,确保测试账号已通过局头认证(状态 `APPROVED`
### 常见问题排查
| 问题现象 | 可能原因 | 解决方案 |
|------|------|---------|
| 截图黑屏 / 内容空白 | 缺少必要 URL 参数(如 `eventId`),页面无法获取数据 | 从 API 获取有效测试 ID访问时带上查询参数 `?id=xxx` |
| 始终显示登录页(即使应该已登录) | 缺少 token 或 token 无效 | 重新登录获取有效 token 注入 `localStorage` |
| API 返回 401 即使有 token | **服务端环境变量 `JWT_SECRET` 与本地不匹配** | 修改 `api/.env` 后执行 `pm2 restart api` 重启加载 |
| 页面白屏 / JavaScript 报错 | API 返回 `null` 时代码直接访问属性 | 添加可选链操作符 `?.` 做防御性编程 |
### 1.C 结构校验 + AI 视觉对比
**分两步:先做结构校验,再做视觉对比**
```
步骤:
1. 从 Pencil MCP batch_get 获取当前设计帧的完整节点树
2. **递归提取所有层级命名节点**(任意层级只要 name 属性存在的节点)→ 得到"必须存在的组件列表"
**递归算法**:
function collectNamedNodes(node, path, resultList):
if node.name != null && node.name != "":
resultList.push({path: path + node.name, node: node})
for each child in node.children:
collectNamedNodes(child, path + node.name + ".", resultList);
**强制要求**: **任何层级**只要节点有 name 属性,都必须提取并检查。**不允许只检查顶层不检查深层**。
结果包含所有层级topBar, topBar.topLeft, topBar.topRight, tabSection, tabSection.tabLeft, barCard1, barCard1.ratingBadge, barCard1.activitySnippet 等等(深度嵌套没问题,全部提取)。
示例: topBar.topLeft / tabSection.tabLeft / availableCard / amountInput / ...
3. **检查全局配置关联**: 如果设计帧包含 name=bottomNav/BottomNav 且是 ref 组件
- 读取 app/mini-program/src/pages.json → 获取当前 tabBar 配置
- 对比设计中 bottomNav.children.length vs 实际 tabBar.list.length
- 对比每个 item 文字描述是否匹配(如果可匹配)
- 数量不匹配 → critical_count += 1底部导航结构错误是严重差异
4. 读取实际页面源码app/mini-program/src/pages/{page}/index.vue双向检查每个必需组件:
- **必需组件检查**: 按路径节点整个区块缺失 → 直接判定为 🔴 严重差异 → critical_count += 1
**强制规则**: 无论层级多深,**只要设计节点有 name 就必须存在**,缺失必须计数
- **文字样式一致性检查**: **任意设计节点 type=text 且有 name** → 检查 fill 颜色 + fontSize + fontWeight 与实际CSS不符 → 🟡 中等差异 → moderate_count += 1
- **布局对齐检查**: 设计节点声明 `justifyContent: space_between` → 检查实际CSS是否正确设置 `display: flex; justify-content: space-between;` → 如果右侧元素未靠右对齐 → 🟡 中等差异 → moderate_count += 1
5. 结构校验通过后,使用 Read 工具分别查看设计截图和实际截图AI 综合分析视觉差异:
- **必需组件检查**: 节点整个区块缺失 → 直接判定为 🔴 严重差异 → critical_count += 1
- **多余组件检查**: 实际页面存在设计稿中没有的顶层区块 → 🟡 中等差异 → moderate_count += 1
- **组件类型一致性检查**: 设计是 `icon_font` 但实现用纯文本字符 → 🟡 中等差异 → moderate_count += 1
6. 结构校验通过后,使用 Read 工具分别查看设计截图和实际截图AI 综合分析视觉差异:
- 布局结构(网格/弹性/定位、对齐方式)
- 组件排列(导航栏、卡片、按钮、图标)
- 颜色Neon Pulse 规范:#000000 背景、#ffb3b5 粉色、#e8b3ff 紫色、#00dce5 青色)
- 间距(内边距、外边距、元素间距)
- 字体(大小、粗细、颜色、对齐方式)
- 图标(是否存在、形状是否正确、是否 emoji 替代)
- 设计规范(是否违反 Neon Pulse 原则:如存在不必要的 1px 边框)
```
**优势**:
- 利用设计文件本身的结构信息,比纯视觉分析更可靠
- 确保不会漏检"整个区块缺失"(如支付结果页订单信息卡片整个不见了)
- 帮助 AI 聚焦到每个具体组件,差异分析更准确
- **递归检查所有层级** → 确保不会漏检顶层存在但内部子区块缺失(如 topBar 存在但 topLeft 缺失)
- **自动检查全局 tabBar 配置** → 设计稿显示的 bottomNav 结构必须与实际配置一致
- **所有文字节点都检查** → 正文/meta/操作计数等颜色不符不会漏检
### 可复用组件检查(通用规则)
设计稿 (.pen) 中标记 `reusable: true` 的组件为**全局可复用组件**,必须抽取为 Vue 全局组件在 `app/mini-program/src/components/` 目录,所有页面使用 import 导入,禁止在每个页面重复手写完整实现。
**检查规则**:
1. 先通过 Pencil MCP `batch_get` 扫描 `.pen` 中所有标记 `reusable: true` 的节点 → 获取全局可复用组件列表
2. 对每个需要使用该组件的页面:
- 如果页面**完整手写实现了可复用组件代码**(未 import 使用全局组件)→ 🟡 中等差异 `moderate_count += 1`
- 如果使用了全局组件但样式(背景色/尺寸)不符合设计规范 → 🟡 中等差异 `moderate_count += 1`
- 正确使用了全局组件且样式符合规范 → 不计数
**当前已识别可复用组件**:
- `NavigationBarPrimary` (一级页面通用导航栏) → 要求:背景 `#000000` 不透明
- `NavigationBarWithBack` (二级页面带返回导航栏) → 要求:背景 `#000000cc` 半透明 + `backdrop-filter: blur(20px)`
**修复要求**:发现重复手写实现必须抽取为全局组件,并更新所有页面使用。
### 图标检查特别规则(@egoist/tailwindcss-icons
**设计要求**:设计节点 `type=icon_font` 必须用 tailwindcss-icons 实现,禁止使用 emoji 或纯文本占位。
**正确 HTML 结构:**
```vue
<!-- ✅ 正确:容器嵌套独立图标元素 -->
<view class="action-icon pink">
<view class="i-lucide-flame"></view>
</view>
<!-- ❌ 错误:图标类直接放在容器 -->
<view class="action-icon pink i-lucide-flame"></view>
```
**正确 CSS 规则:**
```css
/* 必须添加确保SVG继承颜色 */
.action-icon > view svg {
width: 100%;
height: 100%;
fill: currentColor;
}
```
**图标命名规则:**
项目 tailwind.config.js 已启用三个图标集合:
```js
collections: getIconCollections(["mdi", "lucide", "material-symbols"]);
```
| 图标库 | 类名前缀 | 适用场景 |
|---------|----------|----------|
| Material Symbols | `i-material-symbols-*` | 直接使用设计中的原始名称,不需要转换 |
| Material Design Icons | `i-mdi-*` | 常用 material 图标 |
| Lucide | `i-lucide-*` | 设计中 Material Symbols 不存在时使用 |
**Material Symbols → Lucide 名称映射表**(当设计使用 Material Symbols 但需要用 Lucide 替代时参考):
| Material 名称 | Lucide 对应名称 | 示例场景 |
|---------------|-----------------|------------|
| `local_fire_department` | `flame` | 热门推荐/火焰 |
| `near_me` | `navigation` | 附近/定位 |
| `military_tech` | `trophy` | 排行/奖杯 |
| `wine_bar` | `wine` | 酒吧/酒杯 |
| `location_on` | `map-pin` | 位置 |
| `star` | `star` | 评分/星星 |
| `chevron_right` | `chevron-right` | 向右箭头 |
**差异定级规则:**
- 结构错误(图标类在容器上)→ 🟡 中等差异 `moderate_count += 1`
- 缺少 `fill: currentColor` → 🟡 中等差异 `moderate_count += 1`
- 图标名称不匹配(按上表)→ 🟡 中等差异 `moderate_count += 1`
- **使用 emoji 替代图标** → 🔴 严重差异 `critical_count += 1` **(微信小程序不兼容emoji强制修复)**
### 布局对齐检查
对于设计节点使用 `justifyContent: space_between` 的水平排列容器:
- 检查实际CSS是否正确设置 `display: flex; justify-content: space-between; align-items: flex-start;`
- 如果左侧元素挤在一起,右侧元素没有靠右对齐 → 🟡 中等差异 `moderate_count += 1`
对于设计节点声明的对齐方式:
- 实际实现对齐方式不符 → 🟡 中等差异 `moderate_count += 1`
### 防御性编程提醒
当 API 数据可能返回 `null` 时(未登录/加载失败),确保代码使用**可选链操作符 `?.`**访问属性避免渲染崩溃:
```vue
<!-- 正确 -->
<template>{{ earnings?.totalAmount }}</template>
<!-- 错误 -> 页面渲染崩溃 -->
<template>{{ earnings.totalAmount }}</template>
```
如果发现源码缺少可选链,在 AUTO-FIX 阶段应当添加。
### 1.D 差异分级
| 级别 | 标准 | 示例 |
|------|------|------|
| 🔴 严重 | 布局结构不同、**任意层级**关键组件缺失、bottomNav 配置与设计不匹配 (Tab数量/文字) | 原型有两列布局实际一列;整个设计区块在源码中缺失 |
| 🟡 中等 | 颜色/间距偏差、图标不一致、文本对齐方式不符、圆角尺寸差异、多余顶层区块、违反Neon Pulse设计规范、组件类型不匹配、**任意命名文字节点颜色/字体大小不符** | 卡片圆角不一致图标形状与设计不符设计左对齐实际居中设计稿没有的多余提示文字存在不必要的1px边框违反无边界线原则设计是icon-font实际用纯文本 |
| 🟢 轻微 | 微小像素差异 | 1-2px 偏移 |
### 强制执行清单(必须严格遵守,不得跳过)
⛔ **禁止以"不影响功能"为由降级差异分级**
⛔ **禁止故意不计数**来让页面"看起来更好看"
| 情况 | 级别 | 是否计数 |
|------|------|----------|
| 布局结构不同、**任意层级**关键组件缺失、bottomNav 配置与设计不匹配 (Tab数量/文字) | 🔴 严重 | `critical_count += 1` |
| 颜色/间距偏差、图标不一致、文本对齐方式不符、圆角尺寸差异、多余顶层区块、违反Neon Pulse设计规范、组件类型不匹配、**任意命名文字节点颜色/字体大小不符** | 🟡 中等 | `moderate_count += 1` |
| 微小像素差异 (1-2px 偏移) | 🟢 轻微 | `minor_count += 1` |
**门禁规则再强调**
- 通过条件: `critical_count == 0 AND moderate_count <= 3`
- 即使有 1-3 个中等差异,仍然可以 PASS → **不需要故意不计数**
- 只有当 `critical_count > 0 OR moderate_count > 3` 才需要修复
**正确定级示例**
| 案例 | 定级 | 结果 |
|------|------|------|
| 设计微信气泡图标,实际菜单图标(形状不对) | 🟡 中等 → `moderate_count += 1` | critical 0, moderate 1 → **仍然 PASS** (1 ≤ 3) |
| 设计 recentWithdrawals 区块存在,实际源码无此区块 | 🔴 严重 → `critical_count += 1` | 需要修复 |
### 1.E PAGE GATE-CHECK ⛔(每页门禁)
**每页对比完成后必须执行门禁检查。不通过则阻断,进入修复循环。**
```
GATE-CHECK 规则:
├── 🔴 critical_count > 0 → FIX REQUIRED阻断
├── 🟡 moderate_count > 3 → FIX REQUIRED阻断
└── 🟢 匹配或轻微差异 → PASS放行
通过条件: critical_count == 0 AND moderate_count <= 3
```
### 1.F AUTO-FIX LOOP每页修复循环max 3x
门禁未通过时自动触发修复。修复后重新截图、重新对比、重新门禁。
```
FIX LOOP (最多 3 轮):
F1. 分析差异描述 → 定位需要修改的代码文件
- 移动端页面: app/mini-program/src/pages/{page}/index.vue
- 管理后台页面: app/admin/src/app/dashboard/{page}/page.tsx
- 全局样式: app/mini-program/src/App.vue 或 app/admin/src/app/globals.css
F2. 修改代码CSS/组件/布局/颜色)
- 使用 Edit 工具修改对应文件
- 参照设计稿的 Neon Pulse 设计规范
- **图标问题修复优先顺序**:
1. 修正结构 → 将图标类从容器移出,嵌套独立 `<view>`
2. 修正名称 → 按 Material → Lucide 映射表修改正确名称
3. 添加CSS → 确保 `svg { fill: currentColor; }` 让颜色正确继承
F3. 重启服务(如修改了后端或需要刷新构建)
- Bash: pm2 restart mini-h5移动端or pm2 restart admin管理后台
F4. 重截实际页面
- Playwright: browser_navigate → browser_wait_for → browser_take_screenshot
- 保存为 {name}-actual-v{N}.pngN 为修复轮次)
F5. 重新对比(重复 1.C + 1.D
F6. GATE-CHECK → 通过? → RECORD → NEXT PAGE
未通过? → 重试 (回到 F1max 3x)
3 轮修复后仍未通过:
→ 记录为 "fix-failed",记录剩余差异描述
→ 继续下一页(不阻塞整体流程)
```
### 1.G 记录结果
每页完成后立即更新 `pipeline-state.yaml`
```yaml
visual_diff_pencil:
status: in_progress
pages:
"{page_name}":
design_frame: "{frame_id}"
actual_route: "{route}"
diff_level: "match|minor|moderate|critical"
critical_count: N
moderate_count: N
minor_count: N
auto_fix_attempted: true/false
auto_fix_rounds: N
auto_fix_files: ["file1.vue", "file2.tsx"]
auto_fix_result: "PASS (N critical fixed)" | "fix-failed (remaining: ...)"
gate_check: "pass|fix-failed"
```
---
## Phase PVD-2: 汇总报告
### 2.1 生成报告
所有页面完成 PVD-1 循环后,生成 `tests/e2e/screenshots/visual-diff-pencil/visual-diff-pencil-report.md`
```markdown
# Visual Diff Report (Pencil MCP Direct)
生成时间: {timestamp}
对比页面数: {total_pages}
数据来源: ui.pen (Pencil MCP 直接导出)
模式: 逐页门禁对比(含 AUTO-FIX 循环)
## Summary
| 级别 | 数量 |
|------|------|
| 🔴 严重 | {critical_count} |
| 🟡 中等 | {moderate_count} |
| 🟢 轻微 | {minor_count} |
| ✅ 匹配 | {match_count} |
## 修复统计
| 指标 | 数值 |
|------|------|
| 触发 AUTO-FIX 页面数 | {fix_attempted_count} |
| 修复成功页面数 | {fix_pass_count} |
| 修复失败页面数 | {fix_failed_count} |
## 详细对比结果
### {page_name} — {gate_check_result}
- 设计截图: `{name}-design.png`
- 实际截图: `{name}-actual.png`
- 差异级别: {diff_level}
- AUTO-FIX: {auto_fix_result}
- 差异描述...
...
```
### 2.2 更新 pipeline-state.yaml
```yaml
visual_diff_pencil:
status: completed
total_pages: {total}
compared_pages: {compared}
critical_count: {total_critical}
moderate_count: {total_moderate}
minor_count: {total_minor}
match_count: {total_match}
fix_attempted_count: {fix_attempted}
fix_pass_count: {fix_pass}
fix_failed_count: {fix_failed}
pages:
"{page_name}": { ... } # 来自 1.G 的逐页记录
report_path: "tests/e2e/screenshots/visual-diff-pencil/visual-diff-pencil-report.md"
timestamp: "{ISO timestamp}"
```
---
## 复查阶段(必须)
> **第一轮 PVD-1 验证完成成后,必须启动一轮专门的复查阶段**
原因:
- 初始验证时,很多需要登录/参数的页面因未登录/缺少参数导致截图不正确,但代码对齐检查已通过
- 必须逐个页面确认**截图内容实际正确**
- 只有**所有页面截图都正确**,整个验证任务才算完成
操作:
1. 为每个 `status: active` 页面创建独立的**复查 Task**
2. 逐个检查:截图文件存在 → 内容显示正确目标页面 → 布局与设计对齐
3. 如果不正确修复问题注入token/添加URL参数/修复代码bug→ 重新截图
---
## 附录:与 HTML 原型版对比速查
| 步骤 | Pencil MCP 直接版 | HTML 原型版 |
|------|------------------|-------------|
| 原型来源 | `.pen` 文件 | `prototype/pages/*.html` |
| 截图工具 | `mcp__pencil__export_nodes` | Playwright 访问 `localhost:8080` |
| 保真度 | 100% 设计稿 | 可能有转换损耗 |
| 启动依赖 | 无 | 需启动 `prototype/server.js` |
| 门禁机制 | ✅ 逐页 GATE-CHECK + AUTO-FIX | N/A |
| 修复循环 | ✅ max 3x per page | N/A |
| 推荐场景 | 有 `.pen` 文件时首选 | 无 `.pen` 或需 HTML 交互时 |

View File

@@ -0,0 +1,651 @@
# 原型对比验证模式Visual Diff Mode
> **定位**:在回归验证和旅程验证之后,将实际页面实现与原型做逐页视觉对比,发现布局偏差、组件遗漏、交互方式差异等视觉问题。
>
> **两种对比方式**(任选其一):
> | 方式 | 来源 | 适用场景 |
> |------|------|---------|
> | **Pencil MCP 直接版**(推荐) | `.pen` 设计源文件 | 有 `.pen` 文件时首选100% 设计稿保真度 |
> | **HTML 原型版** | `prototype/pages/*.html` | 无 `.pen` 文件或需要 HTML 交互时 |
>
> Pencil MCP 直接版文档:**[visual-diff-mode-pencil.md](./visual-diff-mode-pencil.md)**
>
> **与其他模式的关系**
> - 回归验证模式 → 检查功能点存在且正确(类比单元测试)
> - 旅程验证模式 → 检查用户流程完整性(类比 E2E 测试)
> - **原型对比验证模式** → 检查视觉还原度(类比视觉回归测试)
>
> **触发时机**
> - 旅程验证模式完成后建议进入
> - 手动触发:`/d8d-dev visual-diff` 或 `/d8d-dev visual-diff epic-3`
> - 流水线自动触发:每个 Epic 完成后(可配置)
---
## 前置条件
```
检查清单(任一不满足 → 报错并终止):
├── prototype/ 目录存在且含 pages/ 子目录
├── prototype/server.js 存在(原型预览服务)
├── pipeline-context.md 存在(含实际应用 URL 和凭据)
├── pm2 服务运行中(实际应用可访问)
└── 至少有一个 Epic 的 Story 已 verified
```
---
## 核心流程
```
Phase VD-0: 准备
├── 发现原型页面 → 建立页面清单
├── 读取 pipeline-context.md → 获取实际应用 URL
├── 启动原型服务 → node prototype/server.js (port 8080)
└── 构建页面映射表(原型页面 ↔ 实际路由)
Phase VD-1: 逐页对比循环
对每个映射页面:
├── A. 截取原型页面截图
├── B. 截取实际页面截图
├── C. UI 差异对比(三层对比法)
└── D. 差异分级 + 记录
Phase VD-2: 汇总报告
├── 生成 visual-diff-report.md
└── 输出差异统计和修复建议
```
---
## Phase VD-0: 准备
### 0.1 发现原型页面
```bash
# 列出所有原型页面
find prototype/pages -name "*.html" | sort
```
从输出中提取页面清单,分类为 frontend 和 admin。
### 0.2 读取实际应用信息
读取 `_bmad-output/implementation-artifacts/pipeline-context.md` 获取:
- 实际应用 URLAPI_URL, ADMIN_URL
- 认证凭据
- 已实现的路由
### 0.3 启动原型服务
```bash
# 检查 8080 端口是否已被占用
lsof -i :8080 || true
# 启动原型服务(后台运行)
cd prototype && node server.js &
# 验证服务可用
curl -sf --max-time 5 http://localhost:8080/ && echo "OK"
```
如果 8080 端口被占用,使用其他端口并记录。
### 0.4 构建页面映射表
**自动映射规则**
```
对每个原型页面 prototype/pages/{group}/{name}.html:
1. 提取 group (app/admin) 和 name
2. 在实际路由中查找匹配:
- name=login → /login 或 /admin/login
- name=dashboard → / 或 /dashboard
- name=register → /register
- 其他 → /{name} 或 /admin/{name}
3. 验证实际路由可访问curl 检查非 404
4. 不可访问的页面标记为 skipped
```
**映射输出格式**(记录到 pipeline-state.yaml
```yaml
visual_diff_mapping:
- prototype: "pages/app/login.html"
actual_route: "/login"
group: "frontend"
status: "active" # active / skipped / error
- prototype: "pages/app/dashboard.html"
actual_route: "/"
group: "frontend"
status: "active"
```
---
## Phase VD-1: 逐页对比循环
对映射表中每个 `status: active` 的页面执行以下流程。
### 1.0 创建截图目录
```bash
mkdir -p tests/e2e/screenshots/visual-diff
```
### 1.A 截取原型页面截图
```
1. browser_navigate → http://localhost:8080/pages/{group}/{name}.html
2. browser_resize → 1440x900 (桌面端) 或 375x812 (移动端)
3. browser_wait_for → time=2 (等待页面渲染)
4. browser_take_screenshot(fullPage=false)
→ tests/e2e/screenshots/visual-diff/{name}-prototype-viewport.png
5. browser_take_screenshot(fullPage=true)
→ tests/e2e/screenshots/visual-diff/{name}-prototype-full.png
```
### 1.B 截取实际页面截图
```
1. browser_navigate → {实际 URL}{route}
2. browser_resize → 同上视口(必须与原型一致)
3. 处理认证:
- 如果页面需要登录 → 从 pipeline-context.md 读取凭据
- browser_navigate → 登录页 → 填写凭据 → 提交 → 等待跳转
- 如果已登录(直接访问无跳转)→ 继续
4. browser_wait_for → time=3 (等待数据加载)
5. browser_take_screenshot(fullPage=false)
→ tests/e2e/screenshots/visual-diff/{name}-actual-viewport.png
6. browser_take_screenshot(fullPage=true)
→ tests/e2e/screenshots/visual-diff/{name}-actual-full.png
```
### 1.C 三层对比法
**判断页面长度**
```javascript
// browser_evaluate
() => {
return {
pageHeight: document.documentElement.scrollHeight,
viewportHeight: window.innerHeight,
isLongPage: document.documentElement.scrollHeight > window.innerHeight * 1.5
};
}
```
**如果 isLongPage === false短页面**
```
直接对比首屏:
ui_diff_check(
expected_image_source = "{name}-prototype-viewport.png",
actual_image_source = "{name}-actual-viewport.png",
prompt = "对比这两张截图:第一张是设计原型,第二张是实际实现。列出所有视觉差异,包括布局结构、组件排列、颜色、间距、字体、图标等。按严重程度排序。"
)
```
**如果 isLongPage === true长页面**
执行三层对比法:
#### 层 1首屏对比viewport 截图)
```
ui_diff_check(
expected_image_source = "{name}-prototype-viewport.png",
actual_image_source = "{name}-actual-viewport.png",
prompt = "对比这两张首屏截图:第一张是设计原型,第二张是实际实现。首屏是最重要的视觉区域。列出所有差异,按严重程度排序。"
)
```
#### 层 2全页面对比fullPage 截图)
```
ui_diff_check(
expected_image_source = "{name}-prototype-full.png",
actual_image_source = "{name}-actual-full.png",
prompt = "对比这两张完整页面截图:第一张是设计原型,第二张是实际实现。注意页面可能很长,重点关注各区域的布局和组件差异。"
)
```
#### 层 3差异区域精确定位仅当层 1 或层 2 发现差异时执行)
```
对原型和实际页面分别执行分段截图:
1. 计算分段数量 = ceil(pageHeight / viewportHeight)
2. 对每一段:
a. browser_evaluate → window.scrollTo(0, segmentIndex * viewportHeight * 0.8)
// 20% 重叠避免切割区域遗漏
b. browser_wait_for → time=1
c. browser_take_screenshot(fullPage=false)
→ {name}-prototype-section-{N}.png
→ {name}-actual-section-{N}.png
3. 逐段 ui_diff_check 对比
```
**原型页面也需要 scroll**:原型是静态 HTML需要同样执行 scroll + 截图。
### 1.D 差异分级
每页对比完成后,将差异分级:
| 级别 | 标准 | 示例 |
|------|------|------|
| 🔴 严重 | 布局结构不同、关键组件缺失、交互方式不同 | 原型有两列布局实际一列、缺少侧边栏、用标签页代替下拉菜单 |
| 🟡 中等 | 颜色/间距偏差、图标不一致、文字样式差异、非核心组件缺失 | 卡片圆角不一致、间距差 8px+、图标风格不同 |
| 🟢 轻微 | 微小像素差异、动画效果差异、可接受的响应式适配差异 | 1-2px 偏移、hover 效果不同、字体渲染差异 |
### 1.E 即时写入 Context关键
**每完成一个页面的 ui_diff_check 后,必须立即将结果写入 `pipeline-context.md`,防止上下文压缩导致结果丢失。**
```
流程:
1. ui_diff_check 返回差异结果
2. 立即编辑 pipeline-context.md在 "原型对比验证结果" 部分更新该页面的差异摘要
3. 格式:#### N/11 {页面名} — 严重程度 🔴/🟡/🟢/✅
4. 列出关键差异(精简为 3-7 条)
5. 继续下一个页面
```
**为什么必须即时写入?**
- 原型对比流程涉及 11 个页面的截图+对比,对话上下文可能在中途被压缩
- 如果不在每页完成后即时持久化,已完成的对比结果会丢失
- `pipeline-context.md` 是上下文压缩后的恢复锚点
**context 中的记录格式示例**
```markdown
## 原型对比验证结果 (Visual Diff)
### 全局问题(所有页面共通)
- 🔴 问题描述...
### 逐页结果
#### 1/11 Login — 严重 🔴
- 原型: pages/app/login.html → 实际: /login
- 🔴 差异1
- 🔴 差异2
- 🟡 差异3
#### 2/11 Register — 严重 🔴
- ...
```
**差异记录格式pipeline-state.yaml**
```yaml
visual_diff_results:
dashboard:
prototype_page: "pages/app/dashboard.html"
actual_route: "/"
severity: moderate # critical / moderate / minor / match
viewport_compared: true
fullpage_compared: true
sections_compared: 3
differences:
- severity: critical
location: "首屏左侧"
description: "侧边栏缺失,原型有固定侧边栏导航"
section: 1
- severity: moderate
location: "首屏中部"
description: "上传区域卡片圆角:原型 16px实际 8px"
section: 1
- severity: minor
location: "第2段"
description: "卡片间距略宽:原型 16px实际 24px"
section: 2
```
---
## Phase VD-2: 汇总报告
### 2.1 生成报告
生成 `tests/e2e/screenshots/visual-diff/visual-diff-report.md`
```markdown
# Visual Diff Report
生成时间: {timestamp}
对比页面数: {total_pages}
## Summary
| 级别 | 数量 |
|------|------|
| 🔴 严重 | {critical_count} |
| 🟡 中等 | {moderate_count} |
| 🟢 轻微 | {minor_count} |
| ✅ 匹配 | {match_count} |
## 严重差异(需修复)
### 🔴 {page_name}
- 原型截图: `visual-diff/{name}-prototype-viewport.png`
- 实际截图: `visual-diff/{name}-actual-viewport.png`
- 差异:
1. {具体差异描述}
2. {具体差异描述}
- 修复建议: {auto-fix 建议}
## 中等差异(建议修复)
### 🟡 {page_name}
...
## 轻微差异(可接受)
### 🟢 {page_name}
...
## 完全匹配
### ✅ {page_name}
```
### 2.2 更新 pipeline-state.yaml
```yaml
visual_diff:
status: completed # completed / partial / failed
total_pages: 11
compared_pages: 10
skipped_pages: 1
critical_count: 0
moderate_count: 3
minor_count: 5
match_count: 2
report_path: "tests/e2e/screenshots/visual-diff/visual-diff-report.md"
timestamp: "2026-04-08T10:30:00Z"
```
### 2.3 输出结论
```
═══════════════════════════════════════════
Visual Diff Report 完成
═══════════════════════════════════════════
总页面: 10 对比, 1 跳过
🔴 严重: 0 → 无需修复
🟡 中等: 3 → 建议修复
🟢 轻微: 5 → 可接受
✅ 匹配: 2
报告: tests/e2e/screenshots/visual-diff/visual-diff-report.md
═══════════════════════════════════════════
```
### 2.4 修复决策
```
VD-2 报告完成后:
├── 🔴 严重差异 > 0 → 自动进入 Phase VD-3 全量修复
└── ✅ 无严重差异 → 流程结束
```
---
## Phase VD-3: 全量自动修复
> **定位**:在 Phase VD-2 发现严重差异后,自动修复所有视觉差异,使实际页面还原原型设计。以原型 HTML 为 Source of Truth重写 UI 层。
>
> **核心原则**
> - 先全局后逐页:全局修复(主题、导航、品牌)先做,避免逐页修复被全局问题干扰
> - 保留后端逻辑:只改 UI 层React 组件 + CSS不动 API 路由和数据库
> - 以原型为基准:直接从原型 HTML 提取设计 token 和组件结构
> - 修复后必须验证:重新截图 + ui_diff_check 确认还原度
> - 全程零确认、全自动
### 3.1 全局修复(先修复影响所有页面的共性问题)
从 pipeline-context.md 的"原型对比验证结果"中提取全局问题,按优先级修复:
```
全局问题按优先级:
P0: 主题系统(深色/浅色主题 — 影响 app 组页面)
P0: 品牌标识(图标 + 标题 — 影响所有页面)
P1: 管理后台导航结构(顶部水平导航 — 影响 admin 组页面)
P1: 通用组件样式(按钮 padding/圆角、输入框样式)
```
#### 3.1.1 主题修复
```
1. 读取原型 HTML如 pages/app/login.html提取
- 页面背景色(如 #000000
- 卡片/容器背景色(如 #1E1E1E
- 文字颜色(如 #FFFFFF
- 主色调(如 #FF8400
- 边框颜色(如 #333333
2. 修改全局样式文件:
- globals.css / tailwind.config.ts
- 定义 CSS 变量或 Tailwind theme extend
3. 修改布局组件:
- 背景色 class 对齐
- 确保所有 app 组页面使用新主题
```
#### 3.1.2 品牌标识修复
```
1. 从原型提取品牌元素:
- 图标(如 FontAwesome fa-bolt 闪电图标)
- 标题文字(如 "VividSpark"
- 副标题文字(如页面特有描述)
2. 定位品牌组件文件,替换:
- Logo 组件(图标 + 文字)
- 页面标题区域
```
#### 3.1.3 管理后台导航修复
```
1. 从原型 admin 页面提取导航结构:
- 位置:顶部水平导航栏
- 菜单项:用户管理、内容审核、平台配置、数据分析、系统设置
- 右侧:管理员邮箱 + 登出按钮
2. 修改 admin 布局组件:
- 从左侧边栏 → 顶部水平导航
- 调整所有 admin 页面布局适配
```
#### 3.1.4 全局修复完成后即时写入 context
```
编辑 pipeline-context.md在"原型对比验证结果"下添加:
### 全局修复记录
- ✅/⚠️ 主题修复:{结果摘要}
- ✅/⚠️ 品牌标识修复:{结果摘要}
- ✅/⚠️ 导航结构修复:{结果摘要}
```
### 3.2 逐页修复循环
对 Phase VD-2 报告中每个有差异的页面,执行以下流程:
```
对每个有差异的页面(按严重程度排序,🔴 优先):
├── Step A: 解析原型 HTML
├── Step B: 定位并重写实际源码
├── Step C: 重新截图验证
└── 即时写入 context每页完成后
```
#### Step A: 解析原型 HTML
```
读取 prototype/pages/{group}/{name}.html提取
1. 设计 Token
- 颜色:背景色、卡片色、文字色、主色调、边框色
- 字体font-family、font-size、font-weight
- 间距padding、margin、gap
- 圆角border-radius
- 阴影box-shadow
2. 组件列表:
- 表单字段(类型、标签、占位符、图标)
- 按钮(文字、颜色、大小、圆角)
- 卡片(布局、内容结构)
- 表格(列定义、操作按钮)
- 特殊组件(标签页、进度条、图表区域)
3. 文案:
- 页面标题、副标题
- 占位符文字
- 按钮文字
- 链接文字
4. 布局结构:
- grid/flex 布局
- 列数/行数
- 组件排列顺序
```
#### Step B: 定位并重写实际源码
```
1. 路由 → 源码文件映射:
/login → src/app/(auth)/login/page.tsx
/register → src/app/(auth)/register/page.tsx
/dashboard → src/app/(creator)/dashboard/page.tsx或 src/app/page.tsx
/publish → src/app/(creator)/publish/page.tsx
/pricing → src/app/(creator)/pricing/page.tsx
/settings → src/app/(creator)/settings/page.tsx
/admin/login → src/app/admin/login/page.tsx
/admin/users → src/app/admin/users/page.tsx
/admin/moderation → src/app/admin/moderation/page.tsx
/admin/analytics → src/app/admin/analytics/page.tsx
/admin/platforms → src/app/admin/platforms/page.tsx
/admin/settings → src/app/admin/settings/page.tsx
通用组件:
侧边栏 → src/components/layout/sidebar.tsx
头部导航 → src/components/layout/header.tsx或 admin-header.tsx
2. 以原型为 Source of Truth对齐
a. 组件结构:补全缺失组件(如 Remember me、FAQ、标签页、过滤器
b. 布局结构:对齐网格列数、卡片 vs 列表等
c. 样式 classTailwind class 对齐原型视觉
d. 文案:品牌标题、占位符、按钮文字
e. 数据展示:如原型用图表,引入 chart 库并实现
3. 关键约束:
- 保留后端逻辑不变API 调用、数据处理)
- 只修改 JSX 结构和 Tailwind class
- 新增的 UI 组件使用项目已有的设计系统组件
```
#### Step C: 重新截图验证
```
1. 重跑 Phase VD-1 的截图流程:
a. 截取修复后的实际页面截图
→ tests/e2e/screenshots/visual-diff/{name}-fixed-viewport.png
b. ui_diff_check 对比(修复后 vs 原型)
2. 结果判定:
- PASS无 🔴/🟡)→ 标记 ✅ fixed
- FAIL → 调整修复策略,重试(最多 2 次)
- 仍 FAIL → 标记 ⚠️ needs-manual-fix记录原因
3. 每页修复完成后即时写入 pipeline-context.md
#### N/11 {页面名} — ✅ fixed / ⚠️ needs-manual-fix
- 修复内容: {具体修复了什么}
- 验证结果: {ui_diff_check 摘要}
```
### 3.3 修复后二次对比(全量验证)
```
全局 + 逐页修复完成后:
1. 对所有 11 页重跑 Phase VD-1 截图 + ui_diff_check
2. 生成修复前后对比:
- 修复前: visual-diff/{name}-actual-viewport.png
- 修复后: visual-diff/{name}-fixed-viewport.png
3. 更新 visual-diff-report.md添加修复结果列
4. 更新 pipeline-state.yaml visual_diff 部分
```
### 3.4 最终报告
```yaml
visual_diff:
status: completed # completed / partial / failed
total_pages: 11
compared_pages: 11
fixed_pages: 8 # 新增
manual_fix_needed: 3 # 新增
critical_count: 0 # 修复后的数值
moderate_count: 1
minor_count: 5
match_count: 5
report_path: "tests/e2e/screenshots/visual-diff/visual-diff-report.md"
fix_report_path: "tests/e2e/screenshots/visual-diff/visual-diff-fix-report.md"
timestamp: "2026-04-08T12:00:00Z"
```
输出结论:
```
═══════════════════════════════════════════════
Visual Diff Report + 自动修复 完成
═══════════════════════════════════════════════
总页面: 11 对比, 0 跳过
✅ 已修复: 8
⚠️ 需手动修复: 3见报告
修复前: 🔴 11 🟡 0 🟢 0 ✅ 0
修复后: 🔴 0 🟡 1 🟢 5 ✅ 5
报告: tests/e2e/screenshots/visual-diff/visual-diff-report.md
修复报告: tests/e2e/screenshots/visual-diff/visual-diff-fix-report.md
═══════════════════════════════════════════════
```
---
## 视口配置
| 目标 | 宽度 | 高度 | 用途 |
|------|------|------|------|
| 桌面端(管理后台) | 1440 | 900 | admin 组页面 |
| 桌面端(前端) | 1440 | 900 | app 组页面(如有移动端原型则用移动视口) |
| 移动端 | 375 | 812 | 移动端原型(如有 mobile 目录) |
---
## 错误处理
| 错误场景 | 处理方式 |
|---------|---------|
| 原型服务启动失败 | 检查端口占用,尝试其他端口,报告错误 |
| 实际页面无法访问 | 标记该页面为 error继续下一个 |
| 实际页面需要认证且登录失败 | 标记该页面为 error记录认证错误 |
| ui_diff_check 超时 | 降级为仅 analyze_image 语义分析 |
| 截图文件过大 | fullPage 截图限制最大 8000px 高度 |
| 原型页面无对应实际路由 | 标记为 skipped记录在报告中 |
---
## 注意事项
- **视口必须一致**:原型和实际页面使用完全相同的视口尺寸截图
- **数据状态差异**:原型是静态设计,实际页面有真实数据。对比时忽略因数据内容不同导致的差异(如文字内容、图片内容),只关注布局和组件差异
- **认证页面**login/register 页面不需要登录即可访问,直接截图对比
- **原型静态特性**:原型没有动画和过渡效果,实际页面可能有,这些差异标记为 🟢 轻微
- **自动修复**Phase VD-2 发现严重差异后自动进入 Phase VD-3 全量修复,全程零确认
- **保留后端逻辑**:修复只改 UI 层React 组件 + CSS不动 API 路由和数据库
- **重试上限**:每页最多修复重试 2 次,仍失败则标记为需手动修复

View File

@@ -0,0 +1,362 @@
# 视觉验证共享模块Visual Verification Module
> **⛔ 所有模式共享**旅程验证模式、回归验证模式、E2E 测试、Phase 4 测试均引用此文件。
>
> **被引用位置**
> - `journey-mode.md` 执行模板步骤 2e
> - `validation-mode.md` Story 验证流程
> - `e2e-testing.md` 步骤 8
> - `phase-04-test.md` 测试流程
---
## 核心原则
**截图 ≠ 视觉验证**`browser_take_screenshot` 只保存像素证据,`analyze_image` 才是检查设计合规性的动作。两者必须配对执行。
**任何模式在完成前端页面验证时,必须对每个截图执行 `analyze_image` AI 视觉分析。**
---
## 三层防护机制
### 第一层:关卡机制(结构保障)
视觉验证是**独立关卡**,不是循环内的子步骤。每个用户步骤/验证步骤完成后,必须经过视觉验证关卡才能继续。
```
每个步骤的执行流程:
a. browser_snapshot → 找到目标元素
b. browser_click / browser_type → 执行操作
c. browser_wait_for → 等待响应
d. browser_snapshot → 验证操作结果
⛔ 视觉验证关卡(独立,不可跳过):
1. browser_take_screenshot → 保存截图
2. analyze_image → AI 视觉分析(使用下方标准提示词)
3. 分析结果记录到 verification_detail
4. 发现问题 → 按分流规则处理
5. 关卡未通过 → 不得进入下一个用户步骤
```
### 第二层GATE-CHECK 验证(闭环保障)
`auto-fix-engine.md` GATE-CHECK 增加:
```
├── ⛔ analyze_image 已执行(所有截图都有对应视觉分析结果)
│ 验证方法pipeline-state.yaml 中 analyze_image_executed === true
│ 未执行 → 不得标记完成,必须回退执行视觉验证
```
### 第三层:执行后自检清单(审计保障)
每个 Story/旅程验证完成后强制执行:
```
执行后自检清单(⛔ 强制):
1. ✅ browser_take_screenshot 保存了截图文件(路径含步骤编号)
2. ✅ analyze_image 对截图执行了视觉分析(结果已记录)
3. ✅ verification_detail.design_check 存在且值为 PASS 或 FAIL不能为 null
4. ✅ verification_detail.icons_check.emoji_as_icons 已检查
5. ✅ 发现 FAIL 后执行了分类判定和修复流程
任一为"否" → Story 不得标记为 verified/done。
```
---
## analyze_image 标准提示词
每个截图的 AI 视觉分析使用以下提示词:
```
详细分析此移动端/桌面端页面截图,检查以下维度:
1. **设计规范**(从项目 _bmad-output/04-ui-ux-design.md 提取):
- {执行时Read _bmad-output/04-ui-ux-design.md → 提取 🎨 设计系统节的色彩/卡片/边框/渐变/阴影规则 → 填入具体值}
- 回退:文件不存在 → grep 项目 CSS :root 变量 → 仍无 → 仅检查下方 2-4 维度
2. **图标/图片完整性**
- TabBar 图标可见且有实际图标(非纯文本)
- 功能图标使用图标库(非 emoji
- 无 broken image加载失败的图片占位符
- 无 emoji 充当功能图标
2.5. **资源加载状态检查**(⛔ 强制 — 每次视觉验证关卡必须执行):
- `browser_network_requests(static=true, filter="\\.(jpg|jpeg|png|gif|webp|svg|mp4|webm)$")`
- 检查所有图片/视频资源的 HTTP 状态码
- 状态码 403 → 私有存储未签名(如 MinIO bucket 需 presigned URL
- 状态码 404 → 资源不存在(路径错误或文件未上传)
- 状态码 5xx → 服务端错误
→ 发现非 200 状态码 → 标记 `resource_status_check.found = FOUND` → 按分流规则处理
3. **英文枚举检测**
- 页面可见文本中无英文大写枚举值(如 APPROVED/REJECTED/PENDING/PAID 等)
3.5. **SVG data URI 源码扫描**(⛔ 强制 — 每次视觉验证关卡必须执行):
- `grep -rn "data:image/svg+xml" app/mini-program/src/`(或当前小程序源码目录)
- 发现任何匹配 → 标记 svg_data_uri_check.found = FOUND → 记录文件列表
- → 按检测即阻断表 "SVG data URI 充当图标/装饰" 条目处理AUTO-INSTALL Iconify
3.6. **双重导航栏源码扫描**(⛔ 强制 — 每次视觉验证关卡必须执行):
- 步骤 1: 解析 pages.json提取每个页面的 `navigationBarTitleText` 和 `navigationStyle`
:
- 步骤 2: 对每个 DUAL_NAV_RISK 页面,检查源码是否有 `position:fixed` 的自定义 header
](step 2)
- 发现匹配 → 标记 dual_nav_check.found = FOUND → 记录文件列表
- → 按检测即阻断表 "双重导航栏" 条目处理AUTO-FIX #18参见 ./reference/platform-uni-app.md §3
-
4. **布局质量**(⛔ 逐一检查以下每一项,不得跳过): ```bash
cat app/mini-program/src/pages.json | python3 -c "
import sys, json
cfg = json.load(sys.stdin)
pages = cfg.get('pages', [])
global_style = cfg.get('globalStyle', {})
for p in pages:
path = p.get('path', '')
style = p.get('style', {})
nav_title = style.get('navigationBarTitleText', global_style.get('navigationBarTitleText', ''))
nav_custom = style.get('navigationStyle', '') == 'custom'
if nav_title and not nav_custom:
print(f'DUAL_NAV_RISK: {path} title={nav_title} custom={nav_custom}')
"
```
- 步骤 2: 对每个 DUAL_NAV_RISK 页面,检查源码是否有 `position:fixed` 的自定义 header
```bash
grep -l "position.*fixed" app/mini-program/src/pages/{page-path}/*.vue
# 或
grep -l "class=\"header" app/mini-program/src/pages/{page-path}/*.vue
```
- 发现匹配 → 标记 dual_nav_check.found = FOUND → 记录文件列表
- → 按检测即阻断表 "双重导航栏" 条目处理AUTO-FIX #18参见 ./reference/platform-uni-app.md §3
⛔ **源码扫描优先规则**适用于所有源码扫描检测项dual_nav、svg_data_uri、emoji 等):
- 当源码扫描检测到 FOUND → **必须标记为 FOUND**,按对应修复流程处理
- analyze_image 只能**确认**问题,不能**否认**源码扫描结果
- 当源码扫描说有问题但 analyze_image 说没问题 → **以源码扫描为准**,执行修复
- 理由源码扫描是确定性检测grep 文件内容analyze_image 是概率性检测AI 视觉分析可能误判)
- ❌ 禁止以 analyze_image 结果覆盖源码扫描的 FOUND 结论
3.7. **下拉刷新一致性源码扫描**(⛔ 强制 — 每次视觉验证关卡必须执行,仅 uni-app 项目):
- 步骤 1: 从 pages.json 提取 tabBar 页面路径列表
- 步骤 2: 对每个 tabBar 页面源码检查刷新方式:
- `refresher-enabled` → LIST_LEVEL
- `onPullDownRefresh` → PAGE_LEVEL
- 无上述代码 → NONE
- 步骤 3: tabBar 页面间刷新方式不一致(混用 LIST_LEVEL/PAGE_LEVEL/NONE或存在 LIST_LEVEL
→ 标记 refresh_check.found = INCONSISTENT → 记录各页面刷新方式
- → 按检测即阻断表 "下拉刷新方式不一致" 条目处理AUTO-FIX #19参见 ./reference/platform-uni-app.md §7
4. **布局质量**(⛔ 逐一检查以下每一项,不得跳过):
a. 溢出检测:无水平溢出,内容不超出视口
b. 文字可读:对比度足够,文字不被截断
c. 重叠检测:元素无重叠(标题栏与自定义头部不碰撞,导航栏不遮盖内容)
d. 间距合理性:无异常大间距(如导航栏与内容之间的过大空白),无紧贴元素(相邻交互元素应有明确间距,不能挤在一起)
e. 元素变形检测:按钮/输入框/卡片未被挤压变形(宽高比异常),文字未被截断显示省略号
f. 对齐检测:同行元素水平对齐,同列元素垂直对齐
g. TabBar可见且完整非 TabBar 页面此项 PASS
h. 导航冗余检测(⛔ 强制):页面顶部不应同时存在两层标题栏。
- 判断标准:如果页面最顶部有标准系统导航栏(显示页面标题如"首页""我的""广场"等),
紧接着下方又有另一行品牌名/logo/标题(如"NEON PULSE""极音派对"),标记为 FAIL双重导航栏
- 只允许一种:要么系统导航栏(有 navigationBarTitleText要么自定义头部品牌名/logo
- ❌ 示例 FAIL顶部显示"首页"(系统) + 下方紧接"NEON PULSE"(自定义) = 双重导航栏
- ✅ 示例 PASS只有"NEON PULSE"(自定义系统栏已设custom) 或只有"首页"(系统栏无自定义header)
列出所有发现的问题,按严重程度排序。
```
---
## 视觉检查结果记录格式
每步视觉验证结果必须记录到 `pipeline-state.yaml`
```yaml
verification_detail:
analyze_image_executed: true # ⛔ GATE-CHECK 验证此字段
design_check:
design_spec_source: "04-ui-ux-design.md" # 或 "css-fallback" 或 "none"
design_compliance: PASS/FAIL/WARN
details: "具体描述"
icons_check:
tabbar_icons_visible: PASS/FAIL
emoji_as_icons: NONE/FOUND # NONE=无emoji, FOUND=发现emoji
broken_images: NONE/FOUND
details: "具体描述"
english_enum_check:
found: NONE/FOUND
details: "具体枚举值列表"
svg_data_uri_check:
source_scan_executed: true # ⛔ 源码扫描必须执行
found: NONE/FOUND
files: [] # FOUND 时列出文件路径
details: "具体描述"
dual_nav_check:
source_scan_executed: true # ⛔ 源码扫描必须执行
found: NONE/FOUND
files: [] # FOUND 时列出文件路径
details: "具体描述"
refresh_check: # ⛔ uni-app 项目必须执行
source_scan_executed: true
found: CONSISTENT/INCONSISTENT
pages: # 各 tabBar 页面刷新方式
home: PAGE_LEVEL/LIST_LEVEL/NONE
square: PAGE_LEVEL/LIST_LEVEL/NONE
messages: PAGE_LEVEL/LIST_LEVEL/NONE
profile: PAGE_LEVEL/LIST_LEVEL/NONE
details: "具体描述"
layout_check:
horizontal_overflow: false # a. 溢出检测
text_readable: true # b. 文字可读
element_overlap: false # c. 重叠检测
spacing_anomaly: NONE/FOUND # d. 间距合理性(异常大间距/紧贴元素)
element_deformation: NONE/FOUND # e. 元素变形(按钮/输入框被挤压)
alignment_issues: NONE/FOUND # f. 对齐问题
details: "具体描述"
resource_status_check: # ⛔ 新增 — 资源加载状态
executed: true/false
found: NONE/FOUND
failed_resources: # FOUND 时列出失败资源
- url: "https://..."
status: 403/404
type: "image/jpeg"
details: "描述"
```
**⛔ 步骤级关联**:上述 verification_detail 中发现的每个问题,必须同时记录到对应步骤的 `step_results[].issues_detected` 中(含 `step_id` 关联),确保问题可追溯到具体操作步骤:
```yaml
# step_results 中的关联记录示例
step_results:
- step_id: "2-1-s2"
# ... 其他字段 ...
issues_detected:
- issue_id: "ISSUE-001"
type: "layout_overflow"
severity: "bug"
description: "水平溢出: document_width=420 > viewport=375"
detected_by: "browser_evaluate"
detected_at: "{ISO timestamp}"
# ⛔ 关联: 此问题同时记录在 verification_detail.layout_check.horizontal_overflow = true
analyze_image_result:
# ⛔ 以下字段与 verification_detail 同步(同一来源)
design_compliance: "PASS" # → verification_detail.design_check.design_compliance
icons_check: { emoji_as_icons: "NONE", broken_images: "NONE" }
layout_check: { horizontal_overflow: true } # → verification_detail.layout_check.horizontal_overflow
```
**跨步骤累计检测规则增强**:累计检测按 `step_id` 追踪,每个步骤的检测结果独立记录但跨步骤累计:
```
跨步骤累计:
step 1: emoji_as_icons = NONE → 累计 0
step 2: emoji_as_icons = FOUND → 累计 1 → ⛔ 触发修复step_id: "2-1-s2"
修复后 step 2 重测: emoji_as_icons = NONE → 累计重置为 0
step 3: emoji_as_icons = NONE → 累计 0 → 不触发
```
---
## 视觉检查问题分流规则
**⛔ 强制声明**:除"设计系统主观视觉评价"(改进建议/WARN下表所有问题的修复动作均为 **强制**
不得以任何理由跳过、推迟或重新分类。以下表达均属于**无效分类**,禁止使用:
"已知遗留项"、"技术债"、"后续优化"、"暂时保留"、"视觉可接受"、"非本次旅程引入"、"记录但推迟修复"。
任何未在下表中定义的分类均为无效分类,必须按原分类执行修复。
`analyze_image` 发现问题后的分流路径:
| 发现的问题 | 分类 | 修复动作 | 铁律依据 |
|-----------|------|---------|---------|
| TabBar 图标缺失(只有文本无图标) | 未实现 | AUTO-IMPLEMENT: 自定义 TabBar + Iconify参见 ./reference/platform-uni-app.md §1+§2 | iron-laws L223, L227 |
| Emoji 充当功能图标 | 未实现 | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons小程序需 weapp-tailwindcss/vite禁止 SVG data URI** | iron-laws L228 |
| Tailwind/Iconify 未配置 | 未实现 | AUTO-IMPLEMENT: 完整安装配置(参见 ./reference/platform-uni-app.md §1 | iron-laws L229 |
| SVG data URI 充当图标/装饰 | Bug(平台不兼容 #17) | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss/vite** | iron-laws L223 |
| 英文枚举值显示 | Bug(#15) | AUTO-FIX: 添加中文映射函数 | iron-laws L239 |
| Broken image | Bug | AUTO-FIX: 修复图片路径/占位符 | — |
| 图片/视频资源 403/404 | Bug | AUTO-FIX: 使用 presigned URL403或修复路径404 | — |
| 布局溢出 | Bug | AUTO-FIX: CSS 修复 | iron-laws L222 |
| 元素变形/挤压(按钮/输入框/卡片宽高比异常、被挤压、文字截断显示省略号) | Bug | AUTO-FIX: 修复 CSS flex/width/padding/gap | — |
| 间距异常(导航栏与内容之间过大空白、相邻交互元素紧贴无间距、异常大空白区域) | Bug | AUTO-FIX: 修复 margin/padding/gap | — |
| 设计系统客观指标不合规(背景色/主色调/卡片样式/边框规则/按钮渐变等可从源码验证项) | Bug | AUTO-FIX: 修复 CSS/class参照 _bmad-output/04-ui-ux-design.md 设计规范 | — |
| 设计系统主观视觉评价不合规(发光效果强度/动画质感等无法从源码客观验证的维度) | 改进建议 | WARN | — |
> **注意**:视觉检查发现的问题分流后,走 `auto-fix-engine.md` 标准修复流程AUTO-FIX/AUTO-IMPLEMENT不在此文件定义修复细节。
### 跨步骤累计检测规则(⛔ 强制)
同一类型的问题在多个步骤被 `analyze_image` 检测到时,必须累计计数:
```
累计检测流程:
每个 analyze_image 执行后:
- 检查本次发现的视觉问题类型
- 与前序步骤的 verification_detail 合并累计
- 同一类型累计 FOUND ≥ 1 → 必须在当前关卡立即触发修复,不得推迟(视觉问题确定性高,不需多次确认)
- 如果修复在步骤 N 完成,步骤 N+1 起不再累计该类型(已修复)
```
禁止行为:
- ❌ 将同一类型问题在每个步骤独立记录为 FOUND 但从不触发修复
- ❌ 发现第 1 次标记"记录",发现第 2 次仍标记"记录",发现第 N 次仍不修复
### 跨验证轮次累计检测规则(⛔ 强制 — 修复"每轮 count=1 永远不触发"
**问题**: 同一类型问题在 V1 检测到 count=1V2 检测到 count=1V3 检测到 count=1… 每轮都不够阈值 ≥ 2永远不触发修复。
**解决**: 累计计数必须跨验证轮次V1→V2→V3→V4…累加。
```
跨轮次累计流程:
验证开始前:
1. 读取 pipeline-state.yaml 中前一次验证的 verification_detail
2. 提取每个视觉问题类型的检测状态:
- emoji_as_icons: FOUND → 跨轮次累计基础 +1
- english_enum_check: FOUND → 跨轮次累计基础 +1
- broken_images: FOUND → 跨轮次累计基础 +1
- tabbar_icons_visible: FAIL → 跨轮次累计基础 +1
3. 将跨轮次基础值设为本次验证的累计起点
(注意:基础值来自"前一次仍为 FOUND 的项"
每步 analyze_image 执行后:
- 本次 FOUND + 跨轮次基础值 = 实际累计值
- 实际累计值 ≥ 1 → ⛔ 立即触发修复(视觉问题确定性高,不需多次确认)
- 修复完成后,后续步骤不再累计该类型
```
**判定示例**:
```
V3: emoji_as_icons=FOUND → 记录到 pending_fixes未修复
V4 开始前: 读取 V3 → emoji_as_icons 跨轮次基础=1
V4 第一步 analyze_image: emoji FOUND → 1+1=2 → ⛔ 立即触发 AUTO-IMPLEMENT
V3: english_enum_check=FOUND → 记录到 pending_fixes未修复
V4 开始前: 读取 V3 → english_enum 跨轮次基础=1
V4 第一步 analyze_image: english FOUND → 1+1=2 → ⛔ 立即触发 AUTO-FIX
V5: 局头认证页 👤 emoji FOUND → 跨轮次基础=0V5标记FIXED但实际仅部分替换→ 0+1=1 → ⛔ 立即触发 AUTO-IMPLEMENT
```
**⛔ 禁止行为**:
- ❌ 只看本次验证的 count忽略前一次验证的 FOUND 记录
- ❌ 以"V3遗留/已知"为由重置累计计数为 0
- ❌ 跨轮次基础值 + 本次 FOUND ≥ 1 但仍标记"记录"不触发修复
---
## 禁止行为(⛔)
- ❌ 截图但不执行 analyze_image 就标记"视觉验证通过"
- ❌ analyze_image 报告 FAIL 但 Story 仍标记 done
- ❌ 发现 emoji 当图标但写"视觉可接受"然后 done
- ❌ 发现 TabBar iconPath 为空但标记"TabBar 可见"然后 done
- ❌ 只在最后做一次 analyze_image中间步骤跳过
- ❌ analyze_image 使用过于宽松的提示词(如"看起来正常吗?"
-**将视觉问题标记为任何非分流规则表中定义的分类**(模式性禁止 — 覆盖所有语义变体)
- 禁止使用的分类包括但不限于:"已知遗留项"、"技术债"、"后续优化"、"暂时保留"、"非本次引入"
- 唯一允许的分类:分流规则表中的 7 种未实现×3、Bug×3、改进建议×1
-**将修复动作从 AUTO-FIX/AUTO-IMPLEMENT 降级为"仅记录"或"WARN"**(除设计系统主观视觉评价外)
-**推迟视觉验证修复到"旅程完成后"或"步骤 4 修复阶段"**(必须在关卡内立即执行)

View File

@@ -208,11 +208,11 @@ Read fully and follow: ./modes/journey/journey-index.md
## 质量关卡
> 参见 ./rules/iron-laws.md 了解五大铁律和验证检查清单。
> 参见 ./iron-laws.md 了解五大铁律和验证检查清单。
## E2E 测试
> 参见 ./rules/e2e-testing.md 了解 Playwright MCP 测试序列和视口规则。
> 参见 ./e2e-testing.md 了解 Playwright MCP 测试序列和视口规则。
## 实战经验

View File

@@ -114,10 +114,9 @@
```html
<style>
/* phone-frame: iPhone 12+ 逻辑分辨率 390×844border-box 下 414×868 = 390×844 内容区 + 12px×2 边框 */
.phone-frame {
width: 414px;
height: 868px;
width: 375px;
height: 812px;
border: 12px solid #1a1a1a;
border-radius: 40px;
overflow: hidden;
@@ -128,10 +127,9 @@
height: 100%;
border: none;
}
/* grid 列宽 minmax(440px) 容纳 414px phone-frame + 容器内边距 */
.prototype-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
grid-template-columns: repeat(auto-fill, 375px);
gap: 24px;
padding: 24px;
justify-content: center;