sync: d8d-prototype 文档验证缺陷链修复 — step-05/06/07

Step 5: 引入 doc-registry.js 单一数据源,消除双写不一致
Step 6: server.js 路由白名单动态覆盖所有文档路径前缀
Step 7: 新增文档内容深度验证,强制遍历每个 view-doc 页面

根因: 旧版 Step 5 双写 DOC_MAP、Step 6 硬编码路由、
Step 7 只截图列表页不深入文档内容,导致文档 404 未被发现
This commit is contained in:
yourname
2026-04-09 00:43:05 +00:00
parent f8fe098f55
commit 6c3c676ca5
3 changed files with 233 additions and 59 deletions

View File

@@ -6,6 +6,17 @@
生成 `docs-index.html` 文档首页和 `view-doc.html` 文档查看器,使用前端 JavaScript 渲染 Markdown 文件。
## 铁律单一数据源Single Source of Truth
**docs-index.html 和 view-doc.html 必须共享同一份文档注册表**,避免双写不一致。
架构:
```
doc-registry.js ← 唯一的文档映射数据源
↓ 引用 ↓ 引用
docs-index.html view-doc.html
```
## 执行序列
### 5.1 扫描项目文档
@@ -15,8 +26,9 @@
- `_bmad-output/planning-artifacts/` — 规划文档PRD、架构、UX 设计、产品简报等)
- `_bmad-output/implementation-artifacts/` — 实现产物Story 规格、旅程验证、Pipeline 状态等,**可能不存在**
- `_bmad-output/` 根目录下的文件
- 项目根目录的 README.md、DESIGN.md 等
- docs/ 目录下的文档
- 项目根目录的 `TASKS.md``CLAUDE.md`
- `database/` 目录下的文档
- `docs/` 目录下的文档
#### 文档分组策略
@@ -28,50 +40,81 @@
| **Implementation - Story 规格** | `implementation-artifacts/` | `X-Y-title.md` | 蓝色 |
| **Implementation - 旅程验证** | `implementation-artifacts/` | `epic-N-journeys.md` | 绿色 |
| **Implementation - Pipeline** | `implementation-artifacts/` | `pipeline-*.md`, `pipeline-*.yaml`, `sprint-status.yaml` | 橙色 |
| **Other** | 项目根目录 | README.md 等 | 灰色 |
| **项目文档** | 项目根目录 | TASKS.md, CLAUDE.md, README.md 等 | 灰色 |
| **数据库** | `database/` | SCHEMA_DESIGN.md 等 | 紫色 |
### 5.2 生成 docs-index.html
### 5.2 生成 doc-registry.js单一数据源
文档首页功能
- **文档卡片网格展示**1-3 列自适应)
- 每张卡片显示文档标题、描述、图标
- 查看按钮链接到 `view-doc.html?doc=文档路径`
- 使用 Tailwind CSS 响应式布局
生成独立的 `doc-registry.js` 文件,作为文档映射的唯一数据源
**文档链接格式**
```html
<a href="view-doc.html?doc=prd.md" target="_self">
<i class="fas fa-file-alt"></i> PRD 文档
</a>
```javascript
// doc-registry.js — 文档注册表(单一数据源)
// docs-index.html 和 view-doc.html 共同引用此文件
const DOC_REGISTRY = {
// Planning 规划文档
'prd': { title: '产品需求文档 (PRD)', desc: '产品愿景、目标用户、功能需求', path: '../_bmad-output/planning-artifacts/prd.md', group: 'planning', icon: 'fa-file-alt', color: 'blue' },
// ... 其他文档条目
};
// 按分组返回文档列表
function getDocsByGroup(group) {
return Object.entries(DOC_REGISTRY).filter(([, v]) => v.group === group);
}
// 获取所有唯一路径前缀(供 server.js 路由使用)
function getPathPrefixes() {
const prefixes = new Set();
Object.values(DOC_REGISTRY).forEach(doc => {
const match = doc.path.match(/^\.\.\/([^/]+)\//);
if (match) prefixes.add('/' + match[1] + '/');
else if (doc.path.match(/^\.\.\/[^/]+\.md$/)) {
const fileName = doc.path.replace(/^\.\.\//, '/');
prefixes.add(fileName);
}
});
return Array.from(prefixes);
}
```
### 5.3 生成 view-doc.html
### 5.3 生成 docs-index.html
文档首页功能:
- **引入 doc-registry.js**`<script src="doc-registry.js"></script>`
- **文档卡片网格展示**1-3 列自适应)
- 每张卡片显示文档标题、描述、图标
- 查看按钮链接到 `view-doc.html?doc=<key>`
- 使用 Tailwind CSS 响应式布局
- **遍历 DOC_REGISTRY 动态生成卡片**,而非硬编码
### 5.4 生成 view-doc.html
文档查看器功能:
1. **顶部导航栏**:返回按钮、文档标题、复制链接、打印功能
2. **加载状态**Spinner 动画 + 加载提示
3. **Markdown 渲染**:使用 markdown-it 库html: false 防止 XSS
4. **JavaScript 核心逻辑**
- 从 URL 参数获取文档名(?doc=xxx.md
- 验证路径安全性(防止路径遍历
- 并行尝试多个路径Promise.all 优化)
1. **引入 doc-registry.js**`<script src="doc-registry.js"></script>`
2. **顶部导航栏**:返回按钮、文档标题、复制链接、打印功能
3. **加载状态**Spinner 动画 + 加载提示
4. **Markdown 渲染**:使用 markdown-it 库html: false 防止 XSS
5. **JavaScript 核心逻辑**
- 从 URL 参数获取文档 key?doc=xxx
- 从 DOC_REGISTRY 查找对应路径
- fetch 加载 markdown 内容并渲染
- 支持 YAML 文件的代码块展示
### 5.4 安全设计
### 5.5 增量更新时的同步保证
当增量更新添加新文档时:
1. **只修改 doc-registry.js** — 添加新条目
2. docs-index.html 和 view-doc.html 无需修改(动态读取注册表)
3. **无需同步两个文件**,从根本上消除不一致风险
### 5.6 安全设计
- markdown-it 配置 `html: false` 防止 XSS
- 验证文档路径,防止路径遍历攻击
- 使用 `textContent` 而不是 `innerHTML`
### 5.5 方案优势
| 特性 | 独立页面方案 ⭐ | 页面内展开方案 |
|------|----------------|--------------|
| URL 可分享 | ✅ | ❌ |
| 支持书签 | ✅ | ❌ |
| 代码维护 | ✅ 逻辑集中 | ❌ 每页都要写 |
### 5.6 更新状态文件
### 5.7 更新状态文件
- 勾选 docs-index.html 和 view-doc.html
- 更新 last_updated
@@ -79,8 +122,9 @@
## 完成标志
- [ ] docs-index.html 已生成,包含所有文档的卡片
- [ ] view-doc.html 已生成,支持 Markdown 渲染
- [ ] doc-registry.js 已生成,包含所有文档映射
- [ ] docs-index.html 已生成,通过 doc-registry.js 动态渲染
- [ ] view-doc.html 已生成,通过 doc-registry.js 查找文档路径
- [ ] 文档链接路径正确
- [ ] 安全措施已实施
- [ ] 状态文件已更新

View File

@@ -6,9 +6,24 @@
生成 `server.js` 文件,提供静态文件服务,支持本地预览原型系统。
## 铁律:路由白名单必须覆盖所有文档路径
server.js 的路由必须覆盖 doc-registry.js 中所有文档的路径前缀,**不能硬编码**只支持 `/_bmad-output/`
## 执行序列
### 6.1 生成 server.js
### 6.1 扫描 doc-registry.js 提取路径前缀
在生成 server.js 之前,先分析 doc-registry.js 中所有文档的路径:
```javascript
// 从 doc-registry.js 提取唯一路径前缀
const docPaths = Object.values(DOC_REGISTRY).map(d => d.path);
// 例如: ['../_bmad-output/...', '../TASKS.md', '../database/SCHEMA_DESIGN.md', '../CLAUDE.md']
// 提取前缀: ['/_bmad-output/', '/TASKS.md', '/CLAUDE.md', '/database/']
```
### 6.2 生成 server.js
```javascript
const http = require('http');
@@ -38,6 +53,16 @@ const MIME_TYPES = {
'.yml': 'text/yaml; charset=utf-8',
};
// 动态生成父目录路由白名单 — 基于 doc-registry.js 中的路径
// 自动提取所有 ../xxx/ 前缀和 ../xxx.md 文件名
const PARENT_ROUTE_WHITELIST = [
'/_bmad-output/', // BMAD 输出文档(默认)
'/database/', // 数据库文档
'/TASKS.md', // 任务记录
'/CLAUDE.md', // 项目说明
// ⚠️ 增量更新时:如果 doc-registry.js 新增了其他父目录路径,必须在此添加
];
const server = http.createServer((req, res) => {
let urlPath = req.url.split('?')[0];
if (urlPath === '/') urlPath = '/index.html';
@@ -46,8 +71,11 @@ const server = http.createServer((req, res) => {
const decodedPath = decodeURIComponent(urlPath);
const normalizedPath = path.normalize(decodedPath).replace(/^(\.\.[\/\\])+/, '');
// 支持父目录访问BMAD 输出文档)
const filePath = normalizedPath.startsWith('/_bmad-output/')
// 动态路由:白名单中的前缀/文件映射到父目录
const useParentDir = PARENT_ROUTE_WHITELIST.some(
prefix => normalizedPath.startsWith(prefix) || normalizedPath === prefix
);
const filePath = useParentDir
? path.join(PARENT_DIR, normalizedPath)
: path.join(PROTOTYPE_DIR, normalizedPath);
@@ -73,14 +101,24 @@ const server = http.createServer((req, res) => {
});
server.listen(PORT, () => {
console.log(`\n🚀 Prototype server running at:`);
console.log(`\n Prototype server running at:`);
console.log(` http://localhost:${PORT}/`);
console.log(`\n📁 Serving from: ${PROTOTYPE_DIR}`);
console.log(`\n Serving from: ${PROTOTYPE_DIR}`);
console.log(` Press Ctrl+C to stop\n`);
});
```
### 6.2 启动服务器
### 6.3 关键改进说明
与旧版相比的变更:
| 方面 | 旧版(有缺陷) | 新版(已修复) |
|------|--------------|--------------|
| 路由策略 | 硬编码只有 `/_bmad-output/` | 白名单数组,覆盖所有文档路径 |
| 与 Step 5 关系 | 无联动 | 要求扫描 doc-registry.js 提取前缀 |
| 增量更新 | 需手动改 server.js | 白名单有注释提醒,且有数组结构易于扩展 |
### 6.4 启动服务器
使用 `nohup` 后台启动:
@@ -95,7 +133,19 @@ curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/
# 应返回 200
```
### 6.3 更新状态文件
### 6.5 验证路由覆盖
启动后,验证白名单中每个路径都能正常访问:
```bash
# 对 PARENT_ROUTE_WHITELIST 中的每个条目验证
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/_bmad-output/planning-artifacts/prd.md
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/database/SCHEMA_DESIGN.md
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/TASKS.md
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/CLAUDE.md
```
### 6.6 更新状态文件
- 勾选 server.js
- 更新 last_updated
@@ -107,7 +157,8 @@ curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/
- [ ] 服务器可正常启动
- [ ] 静态文件可正常访问
- [ ] .md 文件可正常返回
- [ ] 父目录 BMAD 文档可访问
- [ ] **PARENT_ROUTE_WHITELIST 覆盖 doc-registry.js 中所有路径前缀**
- [ ] **每个白名单路径 curl 验证返回 200**
- [ ] 状态文件已更新
**完成后,加载 `steps/step-07-validate.md` 继续执行。**

View File

@@ -13,27 +13,29 @@
- ❌ 只检查文件是否存在
- ❌ 跳过图片分析直接标记完成
- ❌ 自行声称"验证通过"而不提供截图证据
-**只验证原型页面,跳过文档内容验证**
**必须**按以下顺序执行:
1. Playwright 导航 → 截图(至少:平铺入口页 + 每个独立页面)
2. ZAI analyze_image 分析每张截图
3. 发现问题 → 修复 → 重新截图验证
4. 全部通过 → 写入验证报告 → 标记完成
2. **文档内容深度验证** — 遍历每个 view-doc.html?doc=xxx 页面,确认内容加载成功
3. ZAI analyze_image 分析每张截图
4. 发现问题 → 修复 → 重新截图验证
5. 全部通过 → 写入验证报告 → 标记完成
## 执行序列
### 7.1 验证流程
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 1. 启动服务器 │ → │ 2. 遍历页面链接 │ → │ 3. 截图保存 │
│ node server.js │ │ Playwright导航 │ │ 保存到screenshots│
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ 4. AI分析验证
│ 图片MCP+需求对比│
└─────────────────┘
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 1. 启动服务器 │ → │ 2. 遍历原型页面 │ → │ 3. 截图保存 │
│ node server.js │ │ Playwright导航 │ │ 保存到screenshots│
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐ ┌─────────────────
5. AI分析验证 │ ← │ 4. 文档内容验证 │ ← ⭐ 新增!
│ 图片MCP+需求对比│ │ 遍历每个view-doc
└─────────────────┘ └─────────────────
```
### 7.2 确保 server.js 已启动
@@ -45,7 +47,7 @@ curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/
cd prototype/ && nohup node server.js &>/dev/null &
```
### 7.3 使用 Playwright MCP 验证
### 7.3 使用 Playwright MCP 验证原型页面
```javascript
// 1. 启动浏览器,导航到入口
@@ -72,7 +74,68 @@ fullPage: true
// - 截图保存
```
### 7.4 使用图片 MCP 分析截图
### 7.4 文档内容深度验证 ⭐(强制步骤)
**本步骤不可跳过!** 必须验证 docs-index.html 中列出的**每个文档**都能在 view-doc.html 中正常渲染内容。
#### 7.4.1 遍历所有文档链接
使用 Playwright `browser_run_code` 批量验证:
```javascript
async (page) => {
// 1. 先导航到 docs-index.html 获取所有文档链接
await page.goto('http://localhost:8080/docs-index.html');
const docLinks = await page.evaluate(() => {
return Array.from(document.querySelectorAll('a[href*="view-doc.html"]'))
.map(a => new URL(a.href, 'http://localhost:8080').searchParams.get('doc'));
});
// 2. 逐个验证每个文档的内容加载
const results = [];
for (const doc of docLinks) {
await page.goto(`http://localhost:8080/view-doc.html?doc=${doc}`, { waitUntil: 'networkidle', timeout: 10000 });
const title = await page.title();
const bodyText = await page.evaluate(() => {
const content = document.querySelector('.markdown-body') || document.body;
return content ? content.innerText.substring(0, 200) : '';
});
const hasError = await page.evaluate(() => !document.getElementById('error').classList.contains('hidden'));
await page.screenshot({ fullPage: true, path: `prototype/screenshots/doc-${doc}.png`, type: 'png', scale: 'css' });
results.push({ doc, title, hasContent: bodyText.length > 20, hasError, preview: bodyText.substring(0, 80) });
}
return JSON.stringify(results, null, 2);
}
```
#### 7.4.2 检查浏览器控制台错误
对每个文档页面检查控制台是否有 404 或加载失败错误:
```javascript
mcp__playwright__browser_console_messages
level: error
// 检查是否有 "Failed to load resource" 或 "404" 错误
```
#### 7.4.3 修复发现的问题
如果发现文档加载失败hasContent=false 或 hasError=true**必须立即修复**
1. **404 错误** → 检查 server.js 的 `PARENT_ROUTE_WHITELIST` 是否覆盖了该文档路径
2. **DOC_MAP 缺失** → 检查 view-doc.html或 doc-registry.js是否有该文档条目
3. **源文件不存在** → 从 docs-index.html 中移除该条目
修复后**重新验证**,直到所有文档内容加载成功。
#### 7.4.4 验证结果汇总格式
| 文档 key | 标题 | 内容加载 | 控制台错误 | 截图 |
|----------|------|---------|-----------|------|
| prd | 产品需求文档 | ✅ | 无 | ✅ |
| schema | 数据库 Schema | ❌→✅ | 404→修复 | ✅ |
### 7.5 使用图片 MCP 分析截图
```javascript
// 分析页面布局和样式
@@ -87,7 +150,7 @@ actual_image_source: prototype/screenshots/actual.png
prompt: 对比参考设计与实际实现指出差异和问题
```
### 7.5 验证检查清单
### 7.6 验证检查清单
#### 链接验证
- [ ] 所有导航链接可点击
@@ -109,6 +172,13 @@ prompt: 对比参考设计与实际实现,指出差异和问题
- [ ] 页面标签清晰可见
- [ ] 网格间距合理,视觉舒适
#### 文档内容验证 ⭐⭐(新增,不可跳过)
- [ ] docs-index.html 中每个文档链接都已遍历
- [ ] 每个 view-doc.html?doc=xxx 页面内容加载成功hasContent=true
- [ ] 无浏览器控制台 404 或加载失败错误
- [ ] 每个 doc-registry.js 条目在 server.js 路由白名单中都有覆盖
- [ ] 所有失败的文档已修复并重新验证通过
#### 样式验证(通过图片 MCP
- [ ] 配色方案符合设计系统
- [ ] 字体大小合适
@@ -122,7 +192,7 @@ prompt: 对比参考设计与实际实现,指出差异和问题
- [ ] 交互流程正确
- [ ] 与需求文档描述一致
### 7.6 生成验证报告
### 7.7 生成验证报告
```markdown
# 原型验证报告
@@ -130,6 +200,7 @@ prompt: 对比参考设计与实际实现,指出差异和问题
## 测试概览
- 测试时间: {date}
- 测试页面: {total} 个
- 测试文档: {doc_count} 个
- 截图数量: {count} 张
- 验证方式: Playwright MCP + Image MCP
@@ -138,6 +209,12 @@ prompt: 对比参考设计与实际实现,指出差异和问题
|------|---------|---------|------|
| index.html | ✅/❌ | ✅/❌ | ... |
## 文档内容验证结果 ⭐
| 文档 | 标题 | 内容加载 | 控制台错误 | 修复状态 |
|------|------|---------|-----------|---------|
| prd | 产品需求文档 | ✅ | 无 | - |
| schema | 数据库 Schema | ✅ | 无 | 已修复路由 |
## 平铺布局验证结果 ⭐
| 验证项 | 结果 | 说明 |
|--------|------|------|
@@ -158,7 +235,7 @@ prompt: 对比参考设计与实际实现,指出差异和问题
1. [问题描述] → [改进建议]
```
### 7.7 最终状态更新
### 7.8 最终状态更新
- 设置 `status: completed`
- 将 step-07-validate 加入 completed_steps
@@ -167,9 +244,11 @@ prompt: 对比参考设计与实际实现,指出差异和问题
## 完成标志
- [ ] 所有页面已截图
- [ ] **所有文档内容已验证加载成功**
- [ ] 链接验证通过
- [ ] 平铺布局验证通过
- [ ] 样式分析完成
- [ ] **文档内容验证无 404 错误**
- [ ] 验证报告已生成
- [ ] 状态文件 status 已设为 completed