sync: 新增 BMAD 开发流程重构 - 目录结构 modes/phases/rules + 新增自动修复引擎 + H5 不兼容组件替换方案
This commit is contained in:
611
.claude/skills/d8d-dev/auto-fix-engine.md
Normal file
611
.claude/skills/d8d-dev/auto-fix-engine.md
Normal 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/无错误文字)| Bug(UX 缺陷)| AUTO-FIX | 用户不知道为什么操作无效 → 添加 Toast 或内联错误提示 |
|
||||
| 14 | 即时使用数据(验证码等)仅通过 Toast 显示,无 console.log 或自动填充,下一步骤无法获取 | Bug(数据丢失风险)| AUTO-FIX | sendCode() 用 Toast 显示验证码但无 console.log,流程卡死 |
|
||||
| 15 | UI 直接输出英文枚举值(如 APPROVED/REJECTED/PENDING/PAID/CANCELLED 等)而非本地化中文标签 | Bug(UI 本地化缺失)| 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)
|
||||
- ⛔ 无任何提示 → Bug(UX 缺陷:校验失败但用户看不到原因)→ 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(代码 Bug,max 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 次)
|
||||
|
||||
**适用**: 操作被前端表单校验拦截(#12),Toast 提示了具体原因
|
||||
|
||||
**流程**:
|
||||
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 |
|
||||
329
.claude/skills/d8d-dev/e2e-testing.md
Normal file
329
.claude/skills/d8d-dev/e2e-testing.md
Normal 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 → evaluate(JSON 验证) |
|
||||
|
||||
### 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 强制)
|
||||
```
|
||||
|
||||
**强制规则**:
|
||||
- 步骤 0(resize)不能省略,即使"上一个 Story 刚 resize 过"
|
||||
- 步骤 3-5(交互)在页面有表单/按钮时**绝对不能省略**
|
||||
- 步骤 7(截图)是强制证据,不是可选装饰
|
||||
- 步骤 8(视觉验证)**不调用等于验证未完成**(⛔ 详细规则和标准提示词见 ./visual-verification.md)
|
||||
- 步骤 9(API 目标检测)**前端 Story 必须执行**,检测页面 JS 发起的 API 请求是否指向 localhost 而非外网域名
|
||||
|
||||
### API 调用目标检测(步骤 9 详解)
|
||||
|
||||
**目的**:检测前端页面的 JavaScript 发出的 API 请求是否指向正确的服务器地址。如果前端代码 fallback 到 `http://localhost:3001`,在 Playwright 测试环境下可以成功(因为 Playwright 运行在 localhost),但真实外网用户会失败。
|
||||
|
||||
**检测方法**:
|
||||
|
||||
```
|
||||
在步骤 8(analyze_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-FIX(Bug 类型)**:
|
||||
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 节**。
|
||||
|
||||
在步骤 8(analyze_image)之后、步骤 9(network_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`) |
|
||||
| 短信通知 | 输出到控制台,验证通知记录入库 |
|
||||
|
||||
299
.claude/skills/d8d-dev/iron-laws.md
Normal file
299
.claude/skills/d8d-dev/iron-laws.md
Normal 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_evaluate(JS 检测) | 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 模式"作为跳过原型的理由
|
||||
|
||||
453
.claude/skills/d8d-dev/journey-authenticity-rules.md
Normal file
453
.claude/skills/d8d-dev/journey-authenticity-rules.md
Normal 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 输入账号密码 → 点击"登录"按钮`
|
||||
|
||||
### 修复
|
||||
|
||||
将凭据从用户步骤移到前置条件或技术验证步骤。
|
||||
|
||||
---
|
||||
|
||||
## 规则 R5:API 标注与用户行为对应
|
||||
|
||||
### 规则
|
||||
|
||||
用户操作名称 → 技术验证中的 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 也无错误 → Bug(UX 缺陷:校验拦截但用户看不到原因)
|
||||
│ → 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 覆盖度评估
|
||||
→ 验证模式:阻断进入旅程执行
|
||||
```
|
||||
754
.claude/skills/d8d-dev/journey-mode.md
Normal file
754
.claude/skills/d8d-dev/journey-mode.md
Normal 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 内容 → 继续下一步骤
|
||||
└── ⛔ 无任何提示 → Bug(UX 缺陷:校验失败但用户看不到原因)→ 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)
|
||||
- 跨轮次累计的视觉问题 ≥ 1(emoji_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` 的旅程创建任务并启动验证
|
||||
- 支持从上次中断处恢复执行
|
||||
410
.claude/skills/d8d-dev/phase-00-setup.md
Normal file
410
.claude/skills/d8d-dev/phase-00-setup.md
Normal 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` 有已知 bug(locations 字段解析失败),用 `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} → 仅 localhost,NPM 无法转发
|
||||
b. 修复方法(按框架):
|
||||
- Vite(uni-app/mini-program):vite.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),必须先尝试 HTTP(Step 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. 代码中对外暴露的 URL(presigned 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: {外网域名或 localhost,SETUP 阶段探测后填入}
|
||||
- ADMIN_URL: {外网域名或 localhost,SETUP 阶段探测后填入}
|
||||
- MINI_URL: {外网域名或 localhost,SETUP 阶段探测后填入}
|
||||
- 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` 并填充基础信息
|
||||
|
||||
54
.claude/skills/d8d-dev/phase-01-plan.md
Normal file
54
.claude/skills/d8d-dev/phase-01-plan.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Phase 1: PLAN(规划)
|
||||
|
||||
### Phase 1: PLAN(规划)
|
||||
|
||||
1. **解析史诗文件**
|
||||
- 读取 `{planning_artifacts}/*epic*.md`
|
||||
- 提取所有 Epic:编号、标题、优先级、依赖关系
|
||||
- 提取所有 Story:Epic 编号、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 6(EPIC-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`
|
||||
|
||||
31
.claude/skills/d8d-dev/phase-02-create-spec.md
Normal file
31
.claude/skills/d8d-dev/phase-02-create-spec.md
Normal 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`
|
||||
|
||||
97
.claude/skills/d8d-dev/phase-03-implement.md
Normal file
97
.claude/skills/d8d-dev/phase-03-implement.md
Normal 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` 都能启动
|
||||
|
||||
181
.claude/skills/d8d-dev/phase-04-test.md
Normal file
181
.claude/skills/d8d-dev/phase-04-test.md
Normal 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-dev(intent: "修复 {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 节
|
||||
|
||||
160
.claude/skills/d8d-dev/phase-05-review.md
Normal file
160
.claude/skills/d8d-dev/phase-05-review.md
Normal 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-status:Story 状态 → `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-DONE(Epic 完成)
|
||||
|
||||
每个 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
|
||||
|
||||
@@ -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 章节
|
||||
- ⛔ 禁止标记为"环境限制"跳过
|
||||
|
||||
579
.claude/skills/d8d-dev/validation-mode.md
Normal file
579
.claude/skills/d8d-dev/validation-mode.md
Normal 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、phase(api/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_image(AI 视觉验证项目设计系统) │
|
||||
│ 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 必须通过才能继续)
|
||||
|
||||
**在步骤 6(analyze_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 检查 1(localhost fallback)
|
||||
├── icon_solution_compliant === true ← 步骤 3.5 检查 2(图标方案合规,小程序/移动端)
|
||||
├── source_code_no_emoji_icons === true ← 步骤 3.5 检查 3(Emoji 充当图标)
|
||||
├── 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 为完成。必须定位漏写的文件并修复后再继续。
|
||||
|
||||
617
.claude/skills/d8d-dev/visual-diff-mode-pencil.md
Normal file
617
.claude/skills/d8d-dev/visual-diff-mode-pencil.md
Normal 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` 获取:
|
||||
- 实际应用 URL(API_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}.png(N 为修复轮次)
|
||||
F5. 重新对比(重复 1.C + 1.D)
|
||||
F6. GATE-CHECK → 通过? → RECORD → NEXT PAGE
|
||||
未通过? → 重试 (回到 F1,max 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 交互时 |
|
||||
651
.claude/skills/d8d-dev/visual-diff-mode.md
Normal file
651
.claude/skills/d8d-dev/visual-diff-mode.md
Normal 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` 获取:
|
||||
- 实际应用 URL(API_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. 样式 class:Tailwind 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 次,仍失败则标记为需手动修复
|
||||
362
.claude/skills/d8d-dev/visual-verification.md
Normal file
362
.claude/skills/d8d-dev/visual-verification.md
Normal 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 URL(403)或修复路径(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=1,V2 检测到 count=1,V3 检测到 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 → 跨轮次基础=0(V5标记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 修复阶段"**(必须在关卡内立即执行)
|
||||
@@ -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 测试序列和视口规则。
|
||||
|
||||
## 实战经验
|
||||
|
||||
|
||||
@@ -114,10 +114,9 @@
|
||||
|
||||
```html
|
||||
<style>
|
||||
/* phone-frame: iPhone 12+ 逻辑分辨率 390×844,border-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;
|
||||
|
||||
Reference in New Issue
Block a user