sdd-flow-kit 1.3.15 → 1.3.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/cli.js +16 -0
  2. package/dist/core/acAssertionBinding.js +180 -0
  3. package/dist/core/apiFieldContract.js +90 -0
  4. package/dist/core/apiFieldCoverage.js +335 -0
  5. package/dist/core/componentSubstance.js +172 -0
  6. package/dist/core/demoImplementationCoverage.js +244 -0
  7. package/dist/core/e2eAssertionDepth.js +237 -0
  8. package/dist/core/e2eDemoSignalClicks.js +90 -0
  9. package/dist/core/prdArtifacts.js +12 -1
  10. package/dist/core/prdCoverage.js +52 -36
  11. package/dist/core/prdSemanticDiff.js +7 -4
  12. package/dist/core/snakeCamel.js +93 -0
  13. package/dist/core/syncSourcePrd.js +181 -0
  14. package/dist/core/techDocQuality.js +91 -0
  15. package/dist/core/testAssertionAnalysis.js +213 -0
  16. package/dist/core/visualRegression.js +243 -0
  17. package/dist/core/writeNext.js +1 -1
  18. package/dist/index.js +6 -1
  19. package/dist/steps/runGate.js +49 -5
  20. package/dist/steps/runSyncSourcePrd.js +38 -0
  21. package/dist/steps/runValidate.js +4 -2
  22. package/dist/steps/setupProject.js +3 -0
  23. package/dist/steps/startFlow.js +9 -2
  24. package/package.json +2 -1
  25. package/scripts/assert-active-openspec-change.sh +114 -0
  26. package/scripts/assess-ac-assertion-binding.mjs +104 -0
  27. package/scripts/assess-api-field-contract.mjs +119 -0
  28. package/scripts/assess-api-field-coverage.mjs +88 -0
  29. package/scripts/assess-component-substance.mjs +87 -0
  30. package/scripts/assess-demo-implementation-coverage.mjs +188 -0
  31. package/scripts/assess-e2e-assertion-depth.mjs +162 -0
  32. package/scripts/assess-playwright-e2e-coverage.mjs +244 -0
  33. package/scripts/assess-tech-doc-quality.mjs +98 -0
  34. package/scripts/assess-visual-regression.mjs +92 -0
  35. package/scripts/install-git-hooks.sh +27 -0
  36. package/scripts/lib/ac-assertion-binding.mjs +119 -0
  37. package/scripts/lib/api-field-coverage.mjs +238 -0
  38. package/scripts/lib/component-substance.mjs +117 -0
  39. package/scripts/lib/demo-reference-parse.mjs +55 -0
  40. package/scripts/lib/e2e-demo-signal-clicks.mjs +43 -0
  41. package/scripts/lib/snake-camel.mjs +74 -0
  42. package/scripts/lib/test-assertion-analysis.mjs +148 -0
  43. package/scripts/lib/visual-regression.mjs +191 -0
  44. package/scripts/resolve-playwright-e2e-scope.mjs +248 -0
  45. package/src/templates/artifacts/01-adi-doc-skill-/346/214/207/345/274/225.template.md +6 -2
  46. package/src/templates/artifacts/02-/351/234/200/346/261/202/345/210/206/346/236/220/346/217/220/347/244/272/350/257/215.template.md +5 -0
  47. package/src/templates/artifacts/04-/346/212/200/346/234/257/346/226/207/346/241/243.template.md +22 -0
  48. package/src/templates/artifacts/05-/351/252/214/346/224/266/346/270/205/345/215/225.template.md +4 -3
  49. package/src/templates/artifacts/06-openspec-/346/217/220/346/241/210/350/215/211/347/250/277.template.md +2 -2
  50. package/src/templates/artifacts/07-opsx-auto-chain.template.md +2 -2
  51. package/src/templates/artifacts/08-PRD/344/270/200/350/207/264/346/200/247/345/244/215/346/237/245/346/212/245/345/221/212.template.md +2 -0
  52. package/src/templates/artifacts/design-demo-reference.template.md +24 -0
  53. package/src/templates/artifacts/tests-fixtures-readme.template.md +19 -0
  54. package/src/templates/artifacts/visual-baseline-readme.template.md +28 -0
  55. package/src/templates/prompts/02-sdd-loop-prompt.md +25 -11
  56. package/src/templates/prompts/04-tech-doc-fill-prompt.md +76 -0
  57. package/src/templates/skills/confluence-doc.py.template +64 -25
  58. package/src/templates/skills/demo-reference-mandatory-workflow.md +79 -0
  59. package/src/templates/skills/doc-skill.SKILL.md.template +13 -6
  60. package/src/templates/skills/layered-testing-strategy.md +15 -6
@@ -92,6 +92,20 @@
92
92
  - 错误态提示口径(toast/inline):
93
93
  - 权限不足提示:
94
94
 
95
+ ### 2.4 演示代码参照(UI 类必填)
96
+
97
+ > 演示可为 **Vue / React / 其他**;生产实现须达到 **相同交互与功能**。Propose 时整段迁入 `design.md` 的 `## 演示代码参照`。
98
+
99
+ - 演示代码根路径:
100
+ - 技术栈:vue | react | 其他
101
+ - 生产 UI 库:
102
+
103
+ | 演示模块 | 演示文件/组件 | 计划生产组件路径 | 关键交互信号(验收时须在代码中出现) |
104
+ |---------|--------------|-----------------|-------------------------------------|
105
+ | | | | |
106
+
107
+ 纯后端/无 UI 演示:在 design 写 `DEMO_REFERENCE_EXEMPT: <原因>`
108
+
95
109
  ## 3. 前端状态与数据模型设计
96
110
 
97
111
  ### 3.1 领域模型(TypeScript)
@@ -144,6 +158,14 @@ type ExampleEntity = {
144
158
 
145
159
  ### 4.3 响应数据映射(API -> UI)
146
160
 
161
+ #### 4.3.1 响应字段映射表(门禁 SSOT,对接后端须逐字段登记)
162
+
163
+ | 后端字段 | 前端字段 | 展示位置 | 兜底值 |
164
+ | --- | --- | --- | --- |
165
+ | `subject_id` | `subjectId` | 列表第1列 | `-` |
166
+ | `subject_name` | `subjectName` | 列表项目名称 | `-` |
167
+ | (填写) | (填写) | (填写) | `-` |
168
+
147
169
  - 展示字段组装规则:
148
170
  - 兜底值处理:
149
171
  - 时间/金额/百分比格式化规则:
@@ -11,10 +11,10 @@
11
11
  ## 填写规则
12
12
 
13
13
  1. **ID**:`AC-01`、`AC-02`… 全局唯一。
14
- 2. **验收标准**:必须可观测(DOM 顺序、完整 toast/tooltip 文案、E2E 路径、截图对比等)。
15
- 3. **测试类型**:`unit` | `component` | `e2e` | `visual` | `manual`(可组合,如 `unit+e2e`)。
14
+ 2. **验收标准**:必须可观测(DOM 顺序、完整 toast/tooltip 文案、E2E 路径、**截图基线对比**等);含 PRD 原型图时须写 `source/.../image_N.png` 或 `visual-baseline/xxx.png` 与 `diff≤2%`。
15
+ 3. **测试类型**:`unit` | `component` | `e2e` | `visual` | `manual`(可组合,如 `unit+e2e+visual`)。**PRD source 含 `.png` 原型图时,至少 1 条 P0/P1 `visual` AC**。
16
16
  4. **优先级**:`P0`(交付阻断)/ `P1` / `P2`。
17
- 5. **映射**:propose 后在 `openspec/changes/<change>/tasks.md` 或 `spec.md` 中引用 `[AC-XX]`。
17
+ 5. **映射**:propose 后在 `openspec/changes/<change>/tasks.md` 或 `spec.md` 中引用 `[AC-XX]`;**tasks 须有「演示模块迁移」段,与 design 映射表一行一任务**。
18
18
  6. **Intentional 偏差**:与 PRD 有意不一致时,必须同步记入 `03-待确认问题清单.md` 并在下文登记。
19
19
 
20
20
  ## 验收项
@@ -23,6 +23,7 @@
23
23
  |----|---------|-------------------|---------|--------|-------------------|---------|
24
24
  | AC-01 | PRD §(填写) | (填写可观测断言,toast/tooltip 须一字不差) | unit | P0 | 待映射 | 待验收 |
25
25
  | AC-02 | PRD §(填写) | (填写) | e2e | P1 | 待映射 | 待验收 |
26
+ | AC-02-V | PRD §(填写) | 列表页与基线 `source/.../image_1.png` 布局一致(diff≤2%) | visual+e2e | P1 | e2e/*.spec.ts | 待验收 |
26
27
 
27
28
  ## Intentional 偏差登记
28
29
 
@@ -8,7 +8,7 @@
8
8
  - [ ] `03-待确认问题清单.md` 已标注 **矛盾点池状态:已闭合**
9
9
  - [ ] `05-验收清单.md` 已标注 **定义状态:已定义**,且 P0 项齐全
10
10
  - [ ] `01-需求分析报告.md`、`02-改动点清单.md`、`04-技术文档草稿.md` 已生成(04 须引用 AC-ID)
11
- - [ ] `gate ac-ready` 通过(P0 已映射到 spec/tasks
11
+ - [ ] `gate ac-ready` 通过(P0 已映射到 spec/tasks,且 **design 含演示模块映射表**)
12
12
  - [ ] 用户已确认可进入提案阶段(阶段 B 闭环)
13
13
  - [ ] `autoChainOpsx` 为 true(默认)→ 与用户确认后**同一回合**自动 propose,无需再等「继续」
14
14
 
@@ -18,7 +18,7 @@
18
18
  - **验收 SSOT**:`05-验收清单.md`(**必须**)
19
19
  - 改动清单:`02-改动点清单.md`
20
20
  - 技术方案:`04-技术文档草稿.md`(引用 AC-ID)
21
- - UI/交互参考:(填写用户提供的路径,例如原型页面或设计稿目录)
21
+ - UI/交互参考:(**必填**)演示代码根路径 + 技术栈(vue/react);propose 时写入 `design.md` → `## 演示代码参照` 映射表(见 `design-demo-reference.template.md`)
22
22
 
23
23
  ## 执行命令(在 AI 工具中)
24
24
 
@@ -38,8 +38,8 @@
38
38
  ## UI 路径解析(按优先级)
39
39
 
40
40
  1. 用户本条消息给出的路径
41
- 2. `source/PRD.md` 中的「原型图片目录」行(如 `docs/adi-v232/`)
42
- 3. `docs/<product>-v<version无点>/` 或 `docs/<product>-<version>/` 目录(只读扫描)
41
+ 2. `source/PRD.md` 中的「原型图片目录」行(如 `source/image/` 或 `docs/adi-v232/image/`)
42
+ 3. `source/image/` 或 `docs/<product>-v<version无点>/image/`(只读扫描)
43
43
 
44
44
  缺失 UI 时:**仍继续** propose,在 `proposal.md` / `design.md` 注明「UI 待补」。
45
45
 
@@ -66,6 +66,8 @@
66
66
  - 「先沿用旧版,后续再改」— 兜底措辞
67
67
  - 「组件里应该有处理」— 无路径/行号/断言
68
68
  - 「基本满足」— 结论不在三选一内
69
+ - `src/views/foo/index.vue:1` — **仅文件首行,无具体组件/交互证据**
70
+ - `tests/.../prd-atoms-coverage.test.ts` — **注释登记 PRD-ID,非真实实现**
69
71
 
70
72
  ## 逐条对比(PRD 原子 × 代码)
71
73
 
@@ -0,0 +1,24 @@
1
+ ## 演示代码参照
2
+
3
+ > **UI 类 change 必填**。演示可为 Vue/React;生产实现须达到相同交互与功能,允许换 UI 库。
4
+ > 纯后端/无 UI 时在 change 文档首行写:`DEMO_REFERENCE_EXEMPT: <原因>`
5
+
6
+ - **演示代码根路径**:`(填写绝对或相对路径,如 /path/to/demo 或 docs/demo-v234)`
7
+ - **技术栈**:`vue` | `react` | `其他`
8
+ - **生产 UI 库**:`(如 Naive UI / Ant Design)`
9
+ - **迁移说明**:(可选)演示 Element Plus → 生产 Naive UI,布局与交互 SSOT 仍以演示为准
10
+
11
+ ### 演示模块 → 生产组件映射(门禁 SSOT)
12
+
13
+ | 演示模块 | 演示文件/组件 | 生产组件路径 | 关键交互信号(须在生产代码出现) | E2E 点击绑定(getByText/click) |
14
+ |---------|--------------|-------------|--------------------------------|--------------------------------|
15
+ | (示例)列表页 | FinancialOverview.vue | src/views/finance/index.vue | 请输入项目ID或名称、拉取欢聚最新数据 | getByPlaceholder('请输入项目ID或名称') |
16
+ | (示例)详情-BD预估 | ProjectDetail.vue | src/views/finance/components/BdEstimateSection.vue | 编辑预估、BD预计开票日期 | getByRole('button', { name: '编辑预估' }).click() |
17
+ | (示例)详情-红票 | ProjectDetail.vue | src/views/finance/components/RedInvoiceSection.vue | 红票及其他、添加记录 | getByText('添加记录').click() |
18
+ | (填写) | | | |
19
+
20
+ ### 禁止的实现方式
21
+
22
+ - 禁止仅用单个 `*DetailDrawer.vue` 只读 `info-grid` 代替演示中的多区块/可编辑/弹窗。
23
+ - 禁止 tasks 只写「实现 DetailDrawer」而不拆演示模块。
24
+ - 禁止 08 报告证据仅写 `index.vue:1`。
@@ -0,0 +1,19 @@
1
+ # API Fixture 样例(v1.3.20 门禁)
2
+
3
+ 将**联调/抓包**得到的真实后端 JSON 放在此目录(保持 **snake_case** 键名)。
4
+
5
+ ## 命名建议
6
+
7
+ - `finance-subject-page.json` — 列表分页响应
8
+ - `finance-subject-info.json` — 详情 subject-info
9
+ - `finance-subject-detail.json` — 详情 subject-detail
10
+
11
+ ## 要求
12
+
13
+ 1. 须含 `_` 分隔的后端字段(如 `subject_id`),禁止手写全 camelCase 空对象
14
+ 2. 配套单测:`tests/**/caseTransform.test.ts` 加载本目录 JSON,`snakeToCamelDeep` 后 `expect` 关键 camelCase 字段
15
+ 3. 04 技术文档 §4.3.1 字段映射表须与 fixture 字段一致
16
+
17
+ ## 门禁
18
+
19
+ `assess-api-field-coverage.mjs` 在 deliver 阶段校验。
@@ -0,0 +1,28 @@
1
+ # visual-baseline(L4 视觉回归基线)
2
+
3
+ > 由 `sdd-flow-kit` 在 PRD run 脚手架时创建。与 `source/` 下 PRD 原型图配合使用。
4
+
5
+ ## 用途
6
+
7
+ - 存放 **裁剪后的 UI 基线截图**(可选;也可直接引用 `source/**/image_N.png`)
8
+ - `05-验收清单.md` 中 `visual` 类型 AC 须在「验收标准」写明基线路径,例如:
9
+ - `列表页与基线 source/adi-v234/image_1.png 布局一致(diff≤2%)`
10
+ - `详情抽屉与 visual-baseline/finance-detail.png 一致(diff≤2%)`
11
+
12
+ ## E2E 要求(v1.3.21 门禁)
13
+
14
+ ```typescript
15
+ await expect(page).toHaveScreenshot("finance-report-list.png", {
16
+ maxDiffPixelRatio: 0.02,
17
+ });
18
+ ```
19
+
20
+ - 须与 `[AC-xx]` 同处一个 test 块
21
+ - **禁止**仅用 `page.screenshot()` 代替 `toHaveScreenshot`
22
+ - Playwright 快照目录默认:`e2e/finance/*.spec.ts-snapshots/` 或与基线名一致
23
+
24
+ ## 门禁
25
+
26
+ `assess-visual-regression.mjs` 在 deliver 阶段校验。
27
+
28
+ 豁免:`tasks.md` 写 `VISUAL_REGRESSION_EXEMPT: <原因>` 或 `OPSX_SKIP_VISUAL_REGRESSION=1`
@@ -46,7 +46,7 @@
46
46
  |------|------|
47
47
  | `01` | 业务概述、范围、风险(**不承担**完整验收) |
48
48
  | `05` | **唯一验收 SSOT**(从 `source/PRD.md` 逐条抽 AC) |
49
- | `04` | 技术方案(引用 AC-ID) |
49
+ | `04` | **可开发级**前端技术方案(**保留模板全部 11 章**,引用 AC-ID) |
50
50
  | `02` | 代码改动计划表 |
51
51
 
52
52
  #### 3. 文档模板要求
@@ -73,6 +73,19 @@
73
73
  * **格式**:表格形式(仅在“提问模式”下生成或更新)。
74
74
  * **字段**:问题类型 (断点/起点缺失/推测缺口) | 问题描述 | 代码位置 (如有) | 建议/待确认项。
75
75
 
76
+ **📄 04-技术文档草稿.md(与 `04-技术文档.template.md` 同结构,强制)**
77
+
78
+ > **必读**:同 run 目录下 `04-技术文档生成指引.md`。
79
+ > **禁止**用 bullet 摘要替换整份文件;**禁止**删减 `##` / `###` 章节标题。
80
+
81
+ * **填写方式**:Read 现有 `04-技术文档草稿.md`(脚手架已写入完整模板)→ **在原文件内逐节替换占位**,不得新建简化版。
82
+ * **输入**:`source/PRD.md`、`source/接口文档.md`、用户演示代码路径(Vue/React)、`05-验收清单.md`、仓库代码扫描结果。
83
+ * **必备章节**(标题须保留):文档元信息 → §1 改造范围(含 1.1–1.4)→ §2 页面与交互(含 **2.4 演示代码参照与映射表**)→ §3 状态模型 → §4 接口(含 **4.1 接口清单表**、4.3 字段映射/normalize)→ §5 表单校验 → §6 边界 → §7 **文件级**改造清单 → §8 风险表 → §9 发布回滚 → §10 测试验收 → §11 待确认。
84
+ * **篇幅**:填完后通常 **≥150 行**;§4.1 至少 5 条接口;UI 类 §2.4 至少 3 行演示映射。
85
+ * **AC 标注**:`[AC-XX]` 分散在各章节,勿只在文首堆叠。
86
+ * **详情/抽屉类**:须按演示代码拆子模块写入 §2.2 / §2.4 / §7(如 BD预估编辑、红票及其他、可编辑金额),**禁止**仅写单个 `*DetailDrawer`。
87
+ * **gate**:`docs-closed` 会校验 `tech-doc-structure`,不合格不得 propose。
88
+
76
89
  ---
77
90
 
78
91
  ### 🚦 核心指令 (Execution Logic)
@@ -100,19 +113,20 @@
100
113
 
101
114
  **任务(阶段 B 文档产出;`full-chain` 模式下不得在本路径结束回合)**:
102
115
  1. 更新 `03-待确认问题清单.md`,标注“**矛盾点池状态:已闭合**”,写入用户确认结论。
103
- 2. **从 `source/PRD.md` 生成 `05-验收清单.md`**(标注「定义状态:已定义」),再生成 `04-技术文档草稿.md`(引用 AC-ID)。
104
- 3. 生成最终版 `01-需求分析报告.md` `02-改动点清单.md`(清单中的路径是**计划**,不得在本回合修改这些文件)。
105
- 4. 确认 `02-改动点清单.md` 中仅包含代码文件改动,不涉及 `openspec/spec/` 内容。
106
- 5. **禁止** 在阶段 B 内修改 `src/` 等业务代码。
107
- 6. 更新 `.session-state.json`:`status: ready_for_propose`;若用户口令含执行模式,同步 `executionMode` / `strictCommands`。
108
- 7. 执行 `gate docs-closed` `phase advance --to docs-done`(exit 0 才继续)。
109
- 8. **若 `autoChainOpsx !== false` `executionMode !== staged-confirm`(默认 full-chain)**:**不得结束回合**,同一回合继续读 `07-opsx-自动串联指引.md`,执行 C→D→E 直至 `deliver` exit 0;**不要**要求用户再说「继续」。
110
- 9. 仅当 `autoChainOpsx === false` 时输出:`05/01/02/03/04 已更新。回复「继续」进入提案。`
111
- 10. **`executionMode=staged-confirm` 时**(含兼容口令「严格SOP,不可替代」):
116
+ 2. **从 `source/PRD.md` 生成 `05-验收清单.md`**(标注「定义状态:已定义」)。
117
+ 3. **填写 `04-技术文档草稿.md`**:先 Read 已有模板文件 + `04-技术文档生成指引.md`,**保留全部章节标题**,逐节填入 PRD/接口/演示/代码扫描内容;**禁止**重写为摘要版。
118
+ 4. 生成最终版 `01-需求分析报告.md` `02-改动点清单.md`(清单中的路径是**计划**,不得在本回合修改这些文件)。
119
+ 5. 确认 `02-改动点清单.md` 中仅包含代码文件改动,不涉及 `openspec/spec/` 内容。
120
+ 6. **禁止** 在阶段 B 内修改 `src/` 等业务代码。
121
+ 7. 更新 `.session-state.json`:`status: ready_for_propose`;若用户口令含执行模式,同步 `executionMode` / `strictCommands`。
122
+ 8. 执行 `gate docs-closed` `phase advance --to docs-done`(exit 0 才继续;失败则继续补全 04 直至 `tech-doc-structure` 通过)。
123
+ 9. **若 `autoChainOpsx !== false` `executionMode !== staged-confirm`(默认 full-chain)**:**不得结束回合**,同一回合继续读 `07-opsx-自动串联指引.md`,执行 C→D→E 直至 `deliver` exit 0;**不要**要求用户再说「继续」。
124
+ 10. 仅当 `autoChainOpsx === false` 时输出:`05/01/02/03/04 已更新。回复「继续」进入提案。`
125
+ 11. **`executionMode=staged-confirm` 时**(含兼容口令「严格SOP,不可替代」):
112
126
  - 每阶段执行前先输出:`本次计划命令清单(逐条)`
113
127
  - 每阶段执行后输出:`门禁结果 + 证据文件路径 + 下一步命令`
114
128
  - 等待用户确认后再进入下一阶段
115
- 11. **`strictCommands=true` 时**:禁止用等价命令替代 NEXT.md 中的 gate/phase 链路(可与 full-chain 组合,不强制每阶段暂停)
129
+ 12. **`strictCommands=true` 时**:禁止用等价命令替代 NEXT.md 中的 gate/phase 链路(可与 full-chain 组合,不强制每阶段暂停)
116
130
 
117
131
  ---
118
132
 
@@ -0,0 +1,76 @@
1
+ # 04-技术文档填写规范(阶段 B 强制)
2
+
3
+ > **SSOT 模板**:同目录 run 下已由脚手架生成的 `04-技术文档草稿.md`(来自 `04-技术文档.template.md`)。
4
+ > **禁止**用几行 bullet + `[AC-xx]` 替换整份文件;**必须保留模板全部章节标题**,仅在各节内填入真实内容。
5
+
6
+ ---
7
+
8
+ ## 填写方式(必须遵守)
9
+
10
+ 1. **先 Read** 当前 `04-技术文档草稿.md`(已是完整模板,约 11 章)。
11
+ 2. **在原文件上逐节替换**占位内容(`(填写)`、空表格行、示例行),**不得删除**任何 `##` / `###` 标题。
12
+ 3. 输入来源(按优先级):
13
+ - `source/PRD.md`(业务规则、交互、文案)
14
+ - `source/接口文档.md`(§4 接口清单、字段映射)
15
+ - 用户提供的**演示代码**路径(Vue/React 均可 → §2.4 映射表)
16
+ - `05-验收清单.md`(在各章标注 `[AC-XX]`,勿只在文首堆叠)
17
+ - 仓库现有代码(§1 现状、§7 改造清单)
18
+ 4. 填完后自检:行数建议 **≥150**;§4.1 至少 **5 行**接口表;§2.4 至少 **3 行**演示映射(UI 类需求)。
19
+ 5. 运行 `npx sdd-flow-kit gate --expect docs-closed`;若 `tech-doc-structure` 失败,继续补全缺失章节。
20
+
21
+ ---
22
+
23
+ ## 各章最低填写要求
24
+
25
+ ### 文档元信息
26
+ - 需求名称、版本、业务域、分支、关联 PRD/接口/演示路径/自测
27
+
28
+ ### §1 改造范围
29
+ - **1.1** 量化成功标准;**1.2** 列出全部页面/路由/弹窗;**1.3** Out of Scope(含 mock、后续迭代)
30
+ - 每条 In Scope 能力标注 `[AC-XX]`
31
+
32
+ ### §2 页面与交互设计
33
+ - **2.1** 权限/场景矩阵(有权限/无权限/只读等)
34
+ - **2.2** 至少 2 条用户路径(列表、详情、导入、编辑…)写清 Step 与成功/失败 toast **完整文案**
35
+ - **2.3** loading、空态、防重复提交
36
+ - **2.4 演示代码参照(UI 必填)**:根路径、技术栈、**映射表**(演示模块 → 计划生产组件 → 关键交互信号)
37
+
38
+ ### §3 状态与数据模型
39
+ - 核心 TypeScript 类型(列表行、详情 VO、表单草稿)
40
+ - remote / ui / 派生状态划分;详情打开/关闭清理
41
+
42
+ ### §4 接口与数据交互
43
+ - **4.1 表格**:接口路径 | Method | 用途 | 请求字段 | 响应字段 | 异常处理
44
+ - **4.3** 写明 normalize 位置 + **4.3.1 字段映射表**(| 后端字段 | 前端字段 |);`tests/fixtures/<module>/*.json` 放联调抓包样例
45
+ - 提交 payload 示例(JSON)
46
+
47
+ ### §5 表单与校验
48
+ - 关键弹窗/编辑字段校验矩阵(必填、格式、错误文案)
49
+
50
+ ### §2.5 视觉基线(PRD 含原型图时必填)
51
+
52
+ - 基线图路径:`source/.../image_N.png` 或 `visual-baseline/xxx.png`
53
+ - 在 `05-验收清单.md` 增加 `visual` 类型 AC,写明 `diff≤2%`
54
+ - E2E 使用 `toHaveScreenshot`,禁止仅 `page.screenshot`
55
+
56
+ - 边界场景、文件级改造清单(**禁止**只写一个大组件名)、风险表、发布检查、测试要点与 05 AC 对应、待确认项(无则写「无」)
57
+
58
+ ---
59
+
60
+ ## 禁止的反模式(gate 会失败)
61
+
62
+ | 反模式 | 示例 |
63
+ |--------|------|
64
+ | 摘要替换 | 仅 7 条 `## N. xxx [AC-xx]` 列表 |
65
+ | 删章节 | 缺少 §3/§5/§6/§8/§9/§11 |
66
+ | 无接口表 | §4 只有一句话「封装 api/modules」 |
67
+ | 无演示映射 | UI 需求但 §2.4 为空 |
68
+ | 详情一笔带过 | 「FinanceDetailDrawer」不提子模块(BD预估、红票、可编辑金额) |
69
+
70
+ ---
71
+
72
+ ## 与下游衔接
73
+
74
+ - Propose 时:`design.md` **复制** §2.4 映射表 + §4 接口决策
75
+ - `tasks.md`:**一行映射表 → 至少一条**「演示模块迁移」任务
76
+ - 实现:**禁止**用单文件只读骨架代替映射表中的多个生产组件
@@ -5,6 +5,7 @@ import json
5
5
  import sys
6
6
  import os
7
7
  import re
8
+ import shutil
8
9
  from pathlib import Path
9
10
  from urllib.parse import quote, urlencode, urljoin, urlparse, unquote
10
11
  from urllib.request import Request, urlopen
@@ -66,6 +67,10 @@ def version_slug(prefix: str, version: str) -> str:
66
67
  return prefix.lower() + "-v" + version.replace(".", "")
67
68
 
68
69
 
70
+ # 图片统一放在版本目录下的 image/ 子目录,与 PRD Markdown 同级
71
+ IMAGE_SUBDIR = "image"
72
+
73
+
69
74
  def dedupe_keywords(items: list[str]) -> list[str]:
70
75
  """去重并保持顺序。"""
71
76
  seen: set[str] = set()
@@ -564,10 +569,11 @@ def download_images_http(
564
569
  token: str,
565
570
  content_html: str,
566
571
  version_dir: Path,
567
- slug: str,
568
572
  request_get=None,
569
573
  ) -> str:
570
- """下载正文图片并改写相对路径;request_get 可注入 Playwright page.request.get。"""
574
+ """下载正文图片到版本目录下的 image/ 并改写相对路径;request_get 可注入 Playwright page.request.get。"""
575
+ image_dir = version_dir / IMAGE_SUBDIR
576
+ image_dir.mkdir(parents=True, exist_ok=True)
571
577
  img_urls = re.findall(r'<img[^>]*src="([^"]+)"', content_html)
572
578
  for i, img_url in enumerate(img_urls):
573
579
  original_url = img_url
@@ -583,7 +589,7 @@ def download_images_http(
583
589
  parsed = urlparse(img_url)
584
590
  ext = os.path.splitext(parsed.path)[1] or ".png"
585
591
  filename = f"image_{i + 1}{ext}"
586
- filepath = version_dir / filename
592
+ filepath = image_dir / filename
587
593
  try:
588
594
  if request_get:
589
595
  resp = request_get(img_url)
@@ -600,7 +606,7 @@ def download_images_http(
600
606
  if ok and body:
601
607
  filepath.write_bytes(body)
602
608
  content_html = content_html.replace(
603
- f'src="{original_url}"', f'src="{slug}/{filename}"'
609
+ f'src="{original_url}"', f'src="{IMAGE_SUBDIR}/{filename}"'
604
610
  )
605
611
  except Exception:
606
612
  pass
@@ -608,7 +614,6 @@ def download_images_http(
608
614
 
609
615
 
610
616
  def write_prd_markdown(
611
- docs_root: Path,
612
617
  title: str,
613
618
  matched_keyword: str,
614
619
  target_url: str,
@@ -617,12 +622,12 @@ def write_prd_markdown(
617
622
  base_url: str,
618
623
  token: str,
619
624
  version_dir: Path,
620
- slug: str,
621
625
  request_get=None,
622
626
  ) -> Path:
623
- """将 HTML 转 Markdown 并写入 docs 目录。"""
627
+ """将 HTML 转 Markdown 并写入版本目录(与 image/ 同级)。"""
628
+ version_dir.mkdir(parents=True, exist_ok=True)
624
629
  html = clean_confluence_storage_html(content_html)
625
- html = download_images_http(base_url, token, html, version_dir, slug, request_get)
630
+ html = download_images_http(base_url, token, html, version_dir, request_get)
626
631
  content_md = parse_html_to_markdown(html)
627
632
  md_content = f"""# {title}
628
633
 
@@ -636,19 +641,57 @@ def write_prd_markdown(
636
641
  md_filename = sanitize_filename(title) + ".md"
637
642
  if not re.search(rf"{prefix}[-_]?V", md_filename, flags=re.IGNORECASE):
638
643
  md_filename = f"{matched_keyword}-{md_filename}"
639
- md_path = docs_root / md_filename
644
+ md_path = version_dir / md_filename
640
645
  md_path.write_text(md_content, encoding="utf-8")
641
646
  return md_path
642
647
 
643
648
 
649
+ def ensure_prd_image_dir_metadata(content: str) -> str:
650
+ """在 PRD 头部写入原型图片相对路径,供 OPSX UI 解析。"""
651
+ if "原型图片目录" in content:
652
+ return content
653
+ lines = content.split("\n")
654
+ title_idx = next(
655
+ (i for i, line in enumerate(lines) if line.strip().startswith("# ")),
656
+ -1,
657
+ )
658
+ insert_at = title_idx + 1 if title_idx >= 0 else 0
659
+ lines.insert(insert_at, "")
660
+ lines.insert(insert_at + 1, f"> 原型图片目录: {IMAGE_SUBDIR}/")
661
+ return "\n".join(lines)
662
+
663
+
664
+ def sync_to_openspec_source(md_path: Path, version_dir: Path) -> Path | None:
665
+ """将 PRD 与 image/ 同步到 openspec source/(需设置环境变量 SDD_SYNC_SOURCE_DIR)。"""
666
+ raw = env("SDD_SYNC_SOURCE_DIR")
667
+ if not raw:
668
+ return None
669
+ source_dir = Path(raw).expanduser()
670
+ if not source_dir.is_absolute():
671
+ source_dir = (Path.cwd() / source_dir).resolve()
672
+ source_dir.mkdir(parents=True, exist_ok=True)
673
+ content = ensure_prd_image_dir_metadata(md_path.read_text(encoding="utf-8"))
674
+ dest_md = source_dir / "PRD.md"
675
+ dest_md.write_text(content, encoding="utf-8")
676
+ src_image = version_dir / IMAGE_SUBDIR
677
+ dest_image = source_dir / IMAGE_SUBDIR
678
+ if src_image.is_dir():
679
+ if dest_image.exists():
680
+ shutil.rmtree(dest_image)
681
+ shutil.copytree(src_image, dest_image)
682
+ print(f"📋 已同步到 openspec source: {dest_md}")
683
+ print(f"🖼️ 已同步图片目录: {dest_image}")
684
+ else:
685
+ print(f"📋 已同步到 openspec source: {dest_md}")
686
+ return dest_md
687
+
688
+
644
689
  def try_rest_download(
645
690
  base_url: str,
646
691
  prefix: str,
647
692
  version: str,
648
693
  keywords: list[str],
649
- docs_root: Path,
650
694
  version_dir: Path,
651
- slug: str,
652
695
  ) -> bool:
653
696
  """REST API 主路径:Bearer Token 拉取 PRD,不依赖 Playwright 登录。"""
654
697
  token = get_api_token()
@@ -664,7 +707,6 @@ def try_rest_download(
664
707
  print(f"✅ [REST] 命中: {matched_keyword} | {title[:80]}")
665
708
  page = rest_fetch_page(base_url, token, page_id)
666
709
  md_path = write_prd_markdown(
667
- docs_root,
668
710
  page["title"],
669
711
  matched_keyword,
670
712
  target_url,
@@ -673,13 +715,13 @@ def try_rest_download(
673
715
  base_url,
674
716
  token,
675
717
  version_dir,
676
- slug,
677
718
  )
678
719
  if md_path.stat().st_size < 80:
679
720
  raise RuntimeError("生成的 Markdown 过短")
721
+ sync_to_openspec_source(md_path, version_dir)
680
722
  print("\n✅ 获取完成(REST API)!")
681
723
  print(f"📄 主文档: {md_path}")
682
- print(f"🖼️ 图片目录: {version_dir}")
724
+ print(f"🖼️ 图片目录: {version_dir / IMAGE_SUBDIR}")
683
725
  return True
684
726
  except Exception as e:
685
727
  print(f" REST 拉取失败: {e}")
@@ -916,8 +958,9 @@ def main():
916
958
 
917
959
  print(f"\n📥 开始获取 {prefix} {keywords[0]} 需求文档")
918
960
  print(f"🔍 将依次尝试: {', '.join(keywords)}")
919
- print(f"📁 输出目录: {docs_root}")
920
- print(f"🖼️ 图片目录: {version_dir}")
961
+ print(f"📁 输出根目录: {docs_root}")
962
+ print(f"📁 版本目录: {version_dir}")
963
+ print(f"🖼️ 图片目录: {version_dir / IMAGE_SUBDIR}")
921
964
 
922
965
  headed = env_bool("CONFLUENCE_HEADED") or env("CONFLUENCE_HEADLESS", "1").lower() in (
923
966
  "0",
@@ -944,9 +987,7 @@ def main():
944
987
  prefix=prefix,
945
988
  version=version,
946
989
  keywords=keywords,
947
- docs_root=docs_root,
948
990
  version_dir=version_dir,
949
- slug=slug,
950
991
  headless=headless,
951
992
  slow_mo=slow_mo,
952
993
  )
@@ -958,7 +999,7 @@ def main():
958
999
 
959
1000
  if use_rest:
960
1001
  print("\n🌐 Playwright 未成功,回退 REST API(Bearer Token)...")
961
- if try_rest_download(base_url, prefix, version, keywords, docs_root, version_dir, slug):
1002
+ if try_rest_download(base_url, prefix, version, keywords, version_dir):
962
1003
  return
963
1004
 
964
1005
  print("\n❌ PRD 拉取失败:Playwright 与 REST 均未成功。")
@@ -978,9 +1019,7 @@ def _download_via_playwright(
978
1019
  prefix: str,
979
1020
  version: str,
980
1021
  keywords: list[str],
981
- docs_root: Path,
982
1022
  version_dir: Path,
983
- slug: str,
984
1023
  headless: bool,
985
1024
  slow_mo: int,
986
1025
  ) -> bool:
@@ -1017,7 +1056,7 @@ def _download_via_playwright(
1017
1056
  )
1018
1057
 
1019
1058
  if not target_url or not matched_keyword:
1020
- debug_path = docs_root / "confluence-search-debug.png"
1059
+ debug_path = version_dir / "confluence-search-debug.png"
1021
1060
  try:
1022
1061
  page.screenshot(path=str(debug_path), full_page=True)
1023
1062
  print(f" 已保存调试截图: {debug_path}")
@@ -1075,7 +1114,6 @@ def _download_via_playwright(
1075
1114
 
1076
1115
  token = get_api_token()
1077
1116
  md_path = write_prd_markdown(
1078
- docs_root,
1079
1117
  title,
1080
1118
  matched_keyword,
1081
1119
  target_url,
@@ -1084,13 +1122,14 @@ def _download_via_playwright(
1084
1122
  base_url,
1085
1123
  token,
1086
1124
  version_dir,
1087
- slug,
1088
1125
  request_get=page.request.get,
1089
1126
  )
1090
1127
 
1128
+ sync_to_openspec_source(md_path, version_dir)
1129
+
1091
1130
  print("\n✅ 获取完成(Playwright)!")
1092
1131
  print(f"📄 主文档: {md_path}")
1093
- print(f"🖼️ 图片目录: {version_dir}")
1132
+ print(f"🖼️ 图片目录: {version_dir / IMAGE_SUBDIR}")
1094
1133
  return True
1095
1134
  except Exception as e:
1096
1135
  print(f"\n❌ Playwright 获取失败: {e}")
@@ -0,0 +1,79 @@
1
+ # 演示代码强制迁移工作流(SDD / OpenSpec 通用)
2
+
3
+ > 适用于 **Vue / React / 其他** 演示仓库。验收标准不是「技术栈一致」,而是 **交互与功能与演示代码等价**。
4
+
5
+ ## 原则
6
+
7
+ 1. **演示代码是 UI/交互 SSOT**(高于 PRD 文字摘要、高于 AI 自拟骨架)。
8
+ 2. **禁止** 用「一个 FinanceDetailDrawer 读接口」代替「按演示拆分的多子模块」。
9
+ 3. **禁止** 在 08 报告用 `index.vue:1` 或注释登记冒充实现。
10
+ 4. 演示可为 React、生产为 Vue(或反之)——须在映射表注明技术栈,迁移的是 **模块边界 + 交互信号**,不是复制粘贴 DOM 库。
11
+
12
+ ## 阶段 B — 04 技术文档
13
+
14
+ 在 `04-技术文档草稿.md` 增加 **§2.4 演示代码参照**(propose 时迁入 `design.md`):
15
+
16
+ ```markdown
17
+ ## 2.4 演示代码参照
18
+
19
+ - 演示代码根路径:`/path/to/demo` 或相对路径
20
+ - 技术栈:vue | react | 其他
21
+ - 说明:(可选)Element Plus 演示 → 生产 Naive UI
22
+
23
+ | 演示模块 | 演示文件/组件 | 计划生产组件路径 | 关键交互信号 |
24
+ |---------|--------------|-----------------|-------------|
25
+ | BD预估 | ProjectDetail.vue §BD预估 | src/views/.../BdEstimateSection.vue | 编辑预估、BD预计开票日期、距今天数 |
26
+ | 红票及其他 | ProjectDetail.vue §红票 | src/views/.../RedInvoiceSection.vue | 红票及其他、添加记录、红票 |
27
+ ```
28
+
29
+ 纯后端 / 无 UI 演示时,在 `design.md` 首行写:`DEMO_REFERENCE_EXEMPT: <原因>`
30
+
31
+ ## 阶段 C — Propose
32
+
33
+ `design.md` **必须**含 `## 演示代码参照` 与上表(可从 04 复制并细化路径)。
34
+
35
+ `tasks.md` **必须**含:
36
+
37
+ ```markdown
38
+ ## 演示模块迁移(强制,对齐 design 映射表)
39
+
40
+ ### Red
41
+ - [ ] D.1 为每个映射行编写失败测试(组件存在 / 交互文案 / API mock)
42
+ ### Green
43
+ - [ ] D.2 按演示结构实现生产组件(禁止合并为单文件只读骨架)
44
+ ```
45
+
46
+ **门禁**:`gate propose-ready` / `gate ac-ready` 执行 `checkDemoReferenceArtifacts`。
47
+
48
+ ## 阶段 D — Apply
49
+
50
+ 实现顺序:
51
+
52
+ 1. 读演示文件对应段落(无关技术栈)
53
+ 2. 拆分为生产 `components/*.vue`(或 tsx)
54
+ 3. 接已有 API(`config/api/modules`)
55
+ 4. 每完成一行映射,勾选 tasks 并跑该行单测
56
+
57
+ ## 阶段 E — Validate / Deliver
58
+
59
+ - `assess-demo-implementation-coverage.mjs`:映射表中的生产文件存在,且含「关键交互信号」
60
+ - `assess-component-substance.mjs`(v1.3.19):组件非空壳(≥40 有效行、template 含控件、API/emit);Section/Modal 须被父组件 import
61
+ - `assess-ac-assertion-binding.mjs`(v1.3.19):P0 AC 的 05 验收标准字面量须出现在 test expect 中;禁止纯 manual P0
62
+ - `assess-e2e-assertion-depth.mjs`:E2E 须数据/交互断言;**可点击信号**须在 spec 中有 click/getByText(v1.3.19)
63
+ - 08 报告证据须指向 **具体组件文件 + 行号 + 交互文案**
64
+
65
+ ## 05 验收清单
66
+
67
+ 详情类 AC 验收标准须写清演示模块,例如:
68
+
69
+ - `详情 Tab「财务明细」含金额与返点卡片、财务链路、红票及其他(对齐演示 ProjectDetail.vue)`
70
+ - `BD预估区含编辑预估按钮,保存 toast「BD预估已保存」`
71
+
72
+ ## 豁免
73
+
74
+ | 场景 | 写法 |
75
+ |------|------|
76
+ | 纯 API/配置 | `DEMO_REFERENCE_EXEMPT: 无 UI` |
77
+ | 演示待补 | 禁止进入 impl;须先补演示或用户书面确认 |
78
+
79
+ 环境变量(仅紧急):`OPSX_SKIP_DEMO_REFERENCE=1`
@@ -2,7 +2,7 @@
2
2
  name: {{SKILL_NAME}}
3
3
  description: |
4
4
  当用户说「获取 {{PRODUCT}} Vx.y.z 需求文档」「下载 {{PRODUCT}}-Vx.y.z PRD」「查询 {{PRODUCT}} x.y.z 版本需求」时触发。
5
- 自动从 Confluence 搜索对应版本 PRD 页面;`技术文档`、`后端`、`详细设计` 类页面会被拒绝,避免误下载为 PRD;下载图片并生成 Markdown 到本仓库 `docs/`。
5
+ 自动从 Confluence 搜索对应版本 PRD 页面;`技术文档`、`后端`、`详细设计` 类页面会被拒绝,避免误下载为 PRD;下载图片并生成 Markdown 到本仓库 `docs/<版本目录>/`。
6
6
  ---
7
7
 
8
8
  # {{PRODUCT}} 需求文档获取(docs/{{SKILL_DIR}})
@@ -80,14 +80,21 @@ CONFLUENCE_PAGE_URL="https://confluence.huan.tv/spaces/yyzcpt/pages/109609740/..
80
80
 
81
81
  ## 输出结构
82
82
 
83
+ 每个版本独占一个目录(目录名由产品前缀 + 版本号自动生成,如 `adi-v234`):
84
+
83
85
  ```
84
- <repo>/docs/
85
- ├── {{PRODUCT_PREFIX}}-V1.2.3-xxx.md
86
- └── {{PRODUCT_PREFIX_LOWER}}-v123/
87
- ├── image_1.png
88
- └── image_2.png
86
+ <docs根目录>/
87
+ └── {{PRODUCT_PREFIX_LOWER}}-v123/ # 版本目录(相对脚本所在 docs/ 或 DOC_DOWNLOAD_DIR)
88
+ ├── {{PRODUCT_PREFIX}}_V1.2.3-xxx.md # PRD 主文档
89
+ └── image/ # 正文图片
90
+ ├── image_1.png
91
+ └── image_2.png
89
92
  ```
90
93
 
94
+ - **docs 根目录**:默认为脚本上级目录(`docs/` 或 monorepo 下的 `frontend/docs/`);可用环境变量 `DOC_DOWNLOAD_DIR` 覆盖为任意绝对/相对路径。
95
+ - **版本目录**:`<docs根目录>/<prefix>-v<版本号去点>`,便于多版本并存、按目录定位 PRD。
96
+ - **openspec 同步**:在 SDD run 中执行脚本时设置 `SDD_SYNC_SOURCE_DIR`(由 `01-获取需求文档指引.md` 自动注入),下载成功后自动写入 `source/PRD.md` 与 `source/image/`;亦可 `npx sdd-flow-kit sync-source-prd --run-id <runId>`。
97
+
91
98
  ## 注意事项
92
99
 
93
100
  {{CONFLUENCE_SPACE_NOTE}}