@autobest-ui/agent 1.0.6 → 1.0.7

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 (27) hide show
  1. package/bin/sync-assets.test.mjs +11 -0
  2. package/bin/traceability-validators.test.mjs +209 -0
  3. package/package.json +2 -2
  4. package/plugins/autobest-delivery/README.md +5 -1
  5. package/plugins/autobest-delivery/mcp-server/src/runner.mjs +48 -0
  6. package/plugins/autobest-delivery/mcp-server/tests/runner.test.mjs +98 -0
  7. package/plugins/autobest-delivery/skills/code-audit/SKILL.md +3 -0
  8. package/plugins/autobest-delivery/skills/code-craft/SKILL.md +3 -3
  9. package/plugins/autobest-delivery/skills/delivery-loop/SKILL.md +4 -4
  10. package/plugins/autobest-delivery/skills/delivery-loop/references/delivery-contract.md +16 -1
  11. package/plugins/autobest-delivery/skills/e2e-gen-spec/SKILL.md +4 -0
  12. package/plugins/autobest-delivery/skills/e2e-ui-checker/SKILL.md +2 -0
  13. package/plugins/autobest-delivery/skills/export-report/SKILL.md +3 -0
  14. package/skills/README.md +5 -0
  15. package/skills/common/code-pr-submit/SKILL.md +7 -4
  16. package/skills/common/make-spec/SKILL.md +37 -0
  17. package/skills/common/make-spec/agents/openai.yaml +4 -0
  18. package/skills/common/make-spec/references/spec-schema.md +82 -0
  19. package/skills/common/make-spec/scripts/validate-spec-traceability.mjs +130 -0
  20. package/skills/common/review-from-docs/SKILL.md +33 -0
  21. package/skills/common/review-from-docs/agents/openai.yaml +4 -0
  22. package/skills/common/review-from-docs/references/review-result-schema.md +42 -0
  23. package/skills/common/review-from-docs/scripts/validate-review-traceability.mjs +91 -0
  24. package/skills/common/ui-prd-scope/SKILL.md +6 -3
  25. package/skills/common/ui-prd-scope/references/requirement-traceability.md +82 -0
  26. package/skills/common/ui-prd-scope/references/scope-schema.md +28 -1
  27. package/skills/common/ui-prd-scope/scripts/validate-scope-bundle.mjs +104 -0
package/skills/README.md CHANGED
@@ -12,9 +12,14 @@ npx --yes --package=@autobest-ui/agent@latest autobest-agent-sync common
12
12
 
13
13
  当前包含:
14
14
 
15
+ - `code-pr-submit`
15
16
  - `figma-ui-capture`
17
+ - `make-spec`
18
+ - `review-from-docs`
16
19
  - `ui-prd-scope`
17
20
 
21
+ 其中 `ui-prd-scope` 使用 `REQ-<页面或Scope简称>-<三位序号>` 分配稳定需求 ID;`review-from-docs` 以该编号记录质询结论;`make-spec` 生成逐项可验收的 `spec.md` 和机器可校验的映射。后续交付角色只引用编号,不重新编号。
22
+
18
23
  ## React
19
24
 
20
25
  React Skills 只应用于单个项目。进入项目根目录后运行:
@@ -40,7 +40,7 @@ to = 当前分支的父分支(创建该分支时所基于的分支)
40
40
  | `fromBranch` | 当前 Git 分支 |
41
41
  | `toBranch` | 分支创建 reflog、本地 merge-base 或 MCP 启发结果 |
42
42
  | `prTitle` | 用户输入;否则使用当前分支最新的非合并 commit 标题,并结合分支名清理 |
43
- | `prDescription` | 用户输入;否则根据远端 diff 和当前分支的 `spec.md` 自动生成 |
43
+ | `prDescription` | 用户输入;否则根据远端 diff、`spec.md`、`spec-traceability.json` 和已有 Checker 证据自动生成 |
44
44
  | `reviewers` | 仅使用用户明确提供的评审人 ID;缺失时省略 |
45
45
 
46
46
  ## 自动发现项目
@@ -99,7 +99,7 @@ sourceBranch: ${fromBranch}
99
99
  targetBranch: ${toBranch}
100
100
  ```
101
101
 
102
- 随后调用 `code-mcp-pr.read_file_at_ref` 读取当前分支根目录的 `spec.md`:
102
+ 随后调用 `code-mcp-pr.read_file_at_ref` 读取当前分支中的 `spec.md`。能够从用户输入、diff 或 spec 定位功能目录时,也读取同目录的 `spec-traceability.json` 和最新 `checker/checker-result.json`:
103
103
 
104
104
  ```yaml
105
105
  orgName: ${orgName}
@@ -109,7 +109,7 @@ refName: ${fromBranch}
109
109
  filePath: spec.md
110
110
  ```
111
111
 
112
- 文件不存在时继续。其他读取错误保留错误事实,但只要 diff 可用就继续生成描述。diff 失败时报告 MCP 的 `errorMsg`,不要基于猜测创建 PR。
112
+ 文件不存在时继续。其他读取错误保留错误事实,但只要 diff 可用就继续生成描述。diff 失败时报告 MCP 的 `errorMsg`,不要基于猜测创建 PR。不得为了寻找产物扫描或猜测无关目录。
113
113
 
114
114
  用户未给标题时,先读取:
115
115
 
@@ -127,9 +127,12 @@ git log --no-merges -1 --format=%s -- "${fromBranch}"
127
127
 
128
128
  ## 变更详情
129
129
  - `path/to/file`:说明可由材料证明的改动。
130
+
131
+ ## 需求追踪
132
+ - `REQ-PD-001`:已实现;Checker 通过 / 失败 / 阻断 / 缺少验收证据
130
133
  ```
131
134
 
132
- 只总结可见材料,不粘贴大段 diff,不补造功能。用户提供标题或描述时保留其原意,不擅自替换。
135
+ 存在 `spec-traceability.json` 时按真实 REQ ID 列出本 PR 涉及的 active 需求。只有 Checker 结果明确覆盖且通过时才能写“Checker 通过”;没有对应证据时写“缺少验收证据”,不得从代码、Maker 结果或整体 Checker 状态推断单项通过。不存在追踪产物时省略“需求追踪”,不临时发明编号。只总结可见材料,不粘贴大段 diff,不补造功能。用户提供标题或描述时保留其原意,不擅自替换。
133
136
 
134
137
  ## 创建授权
135
138
 
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: make-spec
3
+ description: 根据带稳定 REQ 编号的页面 scope 和 review-result 生成可追踪的 spec.md 与 spec-traceability.json。用户要求从已质询范围生成或刷新功能规格时使用;不质询未决业务问题、不实现代码或执行验收。
4
+ ---
5
+
6
+ # 生成可追踪功能规格
7
+
8
+ 把已确认的页面需求转换为 Delivery Plugin 可执行的规格。开始前完整读取 [需求追踪契约](../ui-prd-scope/references/requirement-traceability.md) 和 [Spec Schema](references/spec-schema.md)。
9
+
10
+ ## 输入
11
+
12
+ - 页面目录中的 `scope.md` 和范围包根目录的 `scope-manifest.json`。
13
+ - `review/review-result.json`,以及它引用的 Figma 和证据。
14
+ - 可选的既有 `spec.md` 与 `spec-traceability.json`;刷新时保留稳定编号和仍适用的验收边界。
15
+
16
+ Manifest 必须声明 `traceabilitySchemaVersion: 1`。任何 `active` 需求缺少质询记录、仍为 `unresolved`,或 scope、review 的编号和状态不一致时停止,返回需要继续 `$review-from-docs` 或回写 scope 的具体编号。
17
+
18
+ ## 生成
19
+
20
+ 1. 按 manifest 顺序建立需求覆盖台账。`active` 需求进入规格正文;`deferred` 和 `removed` 进入排除清单并保留原因。
21
+ 2. 对每个 active `REQ-ID` 合并原始陈述与已确认决策,写出可观察、可判定的验收条件。不得根据相似需求补造业务规则;执行命令、路由、fixture 和定位器等仓库事实可放在执行上下文,不冒充产品预期。
22
+ 3. 将适用的 Figma 资产映射到对应 `REQ-ID`、设备、变体和语义节点。功能文字、数据内容和业务图片主体由功能验收条件描述,视觉基准只约束结构与样式。
23
+ 4. 按 Schema 写入 `spec.md` 和 `spec-traceability.json`。计算并记录 `scope.md`、`review-result.json`、`spec.md` 的 SHA-256;每个 active 编号在 `spec.md` 中有且仅有一个需求章节,在追踪 JSON 中有且仅有一个需求条目。
24
+ 5. 执行:
25
+
26
+ ```bash
27
+ node .agents/skills/make-spec/scripts/validate-spec-traceability.mjs <页面 scope 目录>
28
+ ```
29
+
30
+ 根据实际安装位置调整脚本路径。校验失败时修正规格产物,不放宽需求覆盖。
31
+
32
+ ## 完成条件
33
+
34
+ - 每个 active 需求有非空规格章节、至少一条验收条件,以及 `runtime`、`visual` 或 `manual` 验证方式。
35
+ - 每个视觉预期都引用存在的本地基准;没有视觉要求的需求明确使用功能验证,不制造截图要求。
36
+ - 追踪文件的输入哈希与当前文件一致,active、deferred、removed 集合与 manifest 一致。
37
+ - 最终答复给出 Spec 路径、active 覆盖数量、排除数量和校验结果;不修改 scope、review、业务代码或交付证据。
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "生成需求规格"
3
+ short_description: "根据 scope 和质询结论生成可追踪的功能 spec"
4
+ default_prompt: "使用 $make-spec 根据当前 scope 和 review-result 生成带需求编号的 spec.md。"
@@ -0,0 +1,82 @@
1
+ # 可追踪 Spec Schema
2
+
3
+ ## spec.md
4
+
5
+ ```markdown
6
+ # <页面族> 功能规格
7
+
8
+ ## 输入与版本
9
+
10
+ - Scope: `scope.md`
11
+ - Review: `review/review-result.json`
12
+ - Azure PR: <URL>
13
+
14
+ ## 需求覆盖
15
+
16
+ | 需求 ID | 规格章节 | 验证方式 | 状态 |
17
+ | --- | --- | --- | --- |
18
+ | `REQ-PD-001` | 商品主图 | `runtime`、`visual` | `specified` |
19
+
20
+ ## REQ-PD-001 商品主图
21
+
22
+ ### 需求与质询结论
23
+
24
+ <保留原始要求及已确认决策。>
25
+
26
+ ### 验收条件
27
+
28
+ - <可观察、可判定的结果。>
29
+
30
+ ### 视觉映射
31
+
32
+ | 设备 | 状态/变体 | 基准 | 语义节点 |
33
+ | --- | --- | --- | --- |
34
+
35
+ ## 排除需求
36
+
37
+ | 需求 ID | 状态 | 原因 |
38
+ | --- | --- | --- |
39
+ ```
40
+
41
+ 每个 active ID 使用一个二级标题;同一需求需要多项检查时保持在同一章节。需求覆盖表不得使用没有对应章节的编号。
42
+
43
+ ## spec-traceability.json
44
+
45
+ ```json
46
+ {
47
+ "schemaVersion": 1,
48
+ "scope": {
49
+ "manifestPath": "../scope-manifest.json",
50
+ "directory": "01-product-detail",
51
+ "scopePath": "scope.md",
52
+ "scopeSha256": "64位小写十六进制"
53
+ },
54
+ "review": {
55
+ "path": "review/review-result.json",
56
+ "sha256": "64位小写十六进制"
57
+ },
58
+ "spec": {
59
+ "path": "spec.md",
60
+ "sha256": "64位小写十六进制"
61
+ },
62
+ "requirements": [
63
+ {
64
+ "id": "REQ-PD-001",
65
+ "status": "specified",
66
+ "heading": "REQ-PD-001 商品主图",
67
+ "acceptanceCriteria": ["商品主图容器保持 4:3 比例"],
68
+ "verification": ["runtime", "visual"],
69
+ "figmaRefs": ["mobile-default.png#12:34"]
70
+ }
71
+ ],
72
+ "excludedRequirements": [
73
+ {
74
+ "id": "REQ-PD-003",
75
+ "status": "deferred",
76
+ "reason": "本迭代不实现"
77
+ }
78
+ ]
79
+ }
80
+ ```
81
+
82
+ `verification` 只使用 `runtime`、`visual`、`manual`。`manual` 仅用于自动化无法可靠观察的预期,并在验收时形成明确人工证据,不能用于规避可自动化检查。
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from 'node:crypto';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ const featureDir = path.resolve(process.argv[2] || '.');
8
+ const errors = [];
9
+ const requirementPattern = /^REQ-[A-Z][A-Z0-9]{1,15}-\d{3}$/;
10
+ const hashPattern = /^[a-f0-9]{64}$/;
11
+ const allowedVerification = new Set(['runtime', 'visual', 'manual']);
12
+ const fail = message => errors.push(message);
13
+
14
+ function readJson(filePath, label) {
15
+ if (!fs.existsSync(filePath)) {
16
+ fail(`缺少 ${label}:${filePath}`);
17
+ return null;
18
+ }
19
+ try {
20
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
21
+ } catch (error) {
22
+ fail(`${label} JSON 无效:${error.message}`);
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function sha256(filePath) {
28
+ return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
29
+ }
30
+
31
+ function resolveInput(relativePath, label) {
32
+ if (typeof relativePath !== 'string' || !relativePath.trim()) {
33
+ fail(`${label} 路径必须是非空字符串`);
34
+ return null;
35
+ }
36
+ const resolved = path.resolve(featureDir, relativePath);
37
+ const relative = path.relative(featureDir, resolved);
38
+ if (relative.startsWith('..') && label !== 'Manifest') {
39
+ fail(`${label} 路径必须位于页面 scope 目录内`);
40
+ }
41
+ if (!fs.existsSync(resolved)) fail(`${label} 不存在:${relativePath}`);
42
+ return resolved;
43
+ }
44
+
45
+ const tracePath = path.join(featureDir, 'spec-traceability.json');
46
+ const trace = readJson(tracePath, 'spec-traceability.json');
47
+
48
+ if (trace) {
49
+ if (trace.schemaVersion !== 1) fail('spec-traceability.schemaVersion 必须为 1');
50
+ const manifestPath = resolveInput(trace.scope?.manifestPath, 'Manifest');
51
+ const scopePath = resolveInput(trace.scope?.scopePath, 'Scope');
52
+ const reviewPath = resolveInput(trace.review?.path, 'Review');
53
+ const specPath = resolveInput(trace.spec?.path, 'Spec');
54
+ const manifest = manifestPath ? readJson(manifestPath, 'scope-manifest.json') : null;
55
+ const review = reviewPath ? readJson(reviewPath, 'review-result.json') : null;
56
+ if (manifest && manifest.traceabilitySchemaVersion !== 1) {
57
+ fail('Manifest 未启用 traceabilitySchemaVersion: 1');
58
+ }
59
+
60
+ for (const [label, filePath, expected] of [
61
+ ['Scope', scopePath, trace.scope?.scopeSha256],
62
+ ['Review', reviewPath, trace.review?.sha256],
63
+ ['Spec', specPath, trace.spec?.sha256]
64
+ ]) {
65
+ if (!hashPattern.test(expected || '')) fail(`${label} SHA-256 格式无效`);
66
+ else if (filePath && fs.existsSync(filePath) && sha256(filePath) !== expected) {
67
+ fail(`${label} SHA-256 与当前文件不一致`);
68
+ }
69
+ }
70
+
71
+ const manifestScope = manifest?.scopes?.find(item => item.directory === trace.scope?.directory);
72
+ if (!manifestScope) fail(`Manifest 未找到 scope:${trace.scope?.directory}`);
73
+ const manifestRequirements = Array.isArray(manifestScope?.requirements)
74
+ ? manifestScope.requirements
75
+ : [];
76
+ const activeIds = manifestRequirements.filter(item => item.status === 'active').map(item => item.id);
77
+ const excludedIds = manifestRequirements.filter(item => ['deferred', 'removed'].includes(item.status)).map(item => item.id);
78
+ const traceRequirements = Array.isArray(trace.requirements) ? trace.requirements : [];
79
+ const traceIds = traceRequirements.map(item => item.id);
80
+ const traceExcluded = Array.isArray(trace.excludedRequirements) ? trace.excludedRequirements : [];
81
+ const traceExcludedIds = traceExcluded.map(item => item.id);
82
+
83
+ for (const id of [...activeIds, ...excludedIds, ...traceIds, ...traceExcludedIds]) {
84
+ if (!requirementPattern.test(id || '')) fail(`需求编号格式无效:${id}`);
85
+ }
86
+ if (new Set(traceIds).size !== traceIds.length) fail('Spec 追踪包含重复 active 需求编号');
87
+ if (new Set(traceExcludedIds).size !== traceExcludedIds.length) fail('Spec 追踪包含重复排除需求编号');
88
+ for (const id of activeIds) if (!traceIds.includes(id)) fail(`Spec 缺少 active 需求:${id}`);
89
+ for (const id of traceIds) if (!activeIds.includes(id)) fail(`Spec 包含非 active 或未知需求:${id}`);
90
+ for (const id of excludedIds) if (!traceExcludedIds.includes(id)) fail(`Spec 排除清单缺少:${id}`);
91
+ for (const id of traceExcludedIds) if (!excludedIds.includes(id)) fail(`Spec 排除清单包含未知或 active 需求:${id}`);
92
+
93
+ for (const item of traceExcluded) {
94
+ const source = manifestRequirements.find(requirement => requirement.id === item.id);
95
+ if (source && item.status !== source.status) fail(`${item.id} 的排除状态与 manifest 不一致`);
96
+ if (!item.reason || typeof item.reason !== 'string') fail(`${item.id} 的排除原因不能为空`);
97
+ }
98
+
99
+ const reviewRequirements = Array.isArray(review?.requirements) ? review.requirements : [];
100
+ const reviewed = new Map(reviewRequirements.map(item => [item.id, item]));
101
+ if (reviewed.size !== reviewRequirements.length) fail('Review 包含重复需求编号');
102
+ for (const item of reviewRequirements) {
103
+ if (!manifestRequirements.some(requirement => requirement.id === item.id)) {
104
+ fail(`Review 包含未知需求:${item.id}`);
105
+ }
106
+ }
107
+ for (const id of activeIds) {
108
+ const item = reviewed.get(id);
109
+ if (item?.reviewStatus !== 'confirmed') fail(`需求尚未确认:${id}`);
110
+ if (item && item.scopeStatus !== 'active') fail(`${id} 的 Review scopeStatus 与 manifest 不一致`);
111
+ }
112
+
113
+ const specText = specPath && fs.existsSync(specPath) ? fs.readFileSync(specPath, 'utf8') : '';
114
+ for (const item of traceRequirements) {
115
+ if (item.status !== 'specified') fail(`${item.id} 的 Spec 状态必须为 specified`);
116
+ if (!Array.isArray(item.acceptanceCriteria) || item.acceptanceCriteria.length === 0 || item.acceptanceCriteria.some(value => typeof value !== 'string' || !value.trim())) {
117
+ fail(`${item.id} 必须包含非空验收条件`);
118
+ }
119
+ if (!Array.isArray(item.verification) || item.verification.length === 0 || item.verification.some(value => !allowedVerification.has(value))) {
120
+ fail(`${item.id} 的 verification 无效`);
121
+ }
122
+ if (!new RegExp(`^## ${item.id}(?:\\s|$)`, 'm').test(specText)) {
123
+ fail(`spec.md 缺少需求章节:${item.id}`);
124
+ }
125
+ }
126
+ }
127
+
128
+ for (const error of errors) console.error(`错误 ${error}`);
129
+ console.log(`Spec 追踪校验:${errors.length} 个错误`);
130
+ process.exit(errors.length === 0 ? 0 : 1);
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: review-from-docs
3
+ description: 围绕已有页面 scope、PRD、RAG、Figma 和仓库证据逐条质询需求,保留稳定 REQ 编号并生成结构化 review-result。用户要求评审、质询或澄清 scope,或需要在生成 spec 前消除需求冲突时使用;不生成最终 spec 或业务代码。
4
+ ---
5
+
6
+ # 基于文档质询需求
7
+
8
+ 以用户指定的页面 `scope.md` 为入口,把每个需求的冲突、歧义和缺失决策质询到可生成规格的状态。开始前完整读取 [需求追踪契约](../ui-prd-scope/references/requirement-traceability.md) 和 [质询结果 Schema](references/review-result-schema.md)。
9
+
10
+ ## 输入
11
+
12
+ - 必需的页面 `scope.md`,以及其范围包根目录的 `scope-manifest.json`。
13
+ - scope 声明的 PRD 来源、RAG 引用、Figma JSON/PNG 和仓库事实。
14
+ - 可选的既有 `review/review-result.json`;继续质询时保留已确认结论。
15
+
16
+ 输入未声明 `traceabilitySchemaVersion: 1`、非全局 scope 缺少需求编号,或 scope 与 manifest 的编号不一致时停止并要求先用 `$ui-prd-scope` 刷新范围包。本 Skill 不创建、修改或重排需求编号。
17
+
18
+ ## 质询
19
+
20
+ 1. 建立 manifest 中全部需求的台账,保留 `active`、`deferred` 和 `removed`。逐条核对 scope 原文、PRD、RAG、Figma 与仓库事实;证据优先级沿用 scope,不把历史 RAG 或当前实现升级为新需求。
21
+ 2. 对每个 `active` 需求检查行为、状态转换、权限、空态/错误态、响应式、UI 状态、数据前置和可观察验收结果。已有证据能唯一回答时记录证据结论;需要业务取舍时向用户提问。
22
+ 3. 每次只提出一个相互关联的决策组,明确列出 `REQ-ID`、证据冲突、缺失信息和该回答会影响的验收边界。不得用默认值替用户消解业务冲突。
23
+ 4. 用户回答后立即更新内存台账;结论必须落到对应 `REQ-ID`。用户明确暂缓或取消时记录状态和原因,但不修改 scope manifest 的生命周期状态;将需要回写 scope 的差异列入 `scopeUpdatesRequired`。
24
+ 5. 所有问题处理完毕,按 Schema 写入并复读 `review/review-result.json` 和 `review/review-result.md`。两份产物结论一致,Markdown 使用简体中文,JSON 保持机器字段。
25
+ 6. 执行 `node .agents/skills/review-from-docs/scripts/validate-review-traceability.mjs <页面 scope 目录>`;根据实际安装位置调整脚本路径。校验失败时修正质询产物,不修改或放宽 scope 编号。
26
+
27
+ ## 完成条件
28
+
29
+ - Manifest 中每个需求在结果中恰好出现一次,没有未知编号。
30
+ - 每个 `active` 需求为 `confirmed` 或 `unresolved`;只有证据或用户回答足以确定验收边界时才能标为 `confirmed`。
31
+ - `confirmed` 需求至少包含一条 `decisions`,`unresolved` 需求至少包含一条 `remainingIssues`。
32
+ - 用户要求生成可交付 Spec 时,任何 `active` 需求仍为 `unresolved` 都是明确阻断,不得伪装为默认结论。
33
+ - 最终答复列出 confirmed/unresolved/deferred/removed 数量、产物路径和需要回写 scope 的生命周期变化;不生成 `spec.md`、不修改业务代码。
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "需求文档质询"
3
+ short_description: "按需求编号质询 scope 中的冲突、歧义和缺失信息"
4
+ default_prompt: "使用 $review-from-docs 逐条质询当前 scope,并生成带需求编号的 review-result。"
@@ -0,0 +1,42 @@
1
+ # Review Result Schema
2
+
3
+ 质询结果写入页面 scope 目录下的 `review/review-result.json`:
4
+
5
+ ```json
6
+ {
7
+ "schemaVersion": 1,
8
+ "scope": {
9
+ "manifestPath": "../scope-manifest.json",
10
+ "directory": "01-product-detail",
11
+ "scopePath": "scope.md"
12
+ },
13
+ "requirements": [
14
+ {
15
+ "id": "REQ-PD-001",
16
+ "scopeStatus": "active",
17
+ "reviewStatus": "confirmed",
18
+ "questions": [
19
+ {
20
+ "question": "图片加载失败时是否保留 4:3 占位?",
21
+ "answer": "是,显示默认占位图",
22
+ "answeredBy": "user"
23
+ }
24
+ ],
25
+ "decisions": ["加载失败时保持 4:3 容器并显示默认占位图"],
26
+ "evidenceRefs": ["scope.md#REQ-PD-001", "mobile-default.json#12:34"],
27
+ "remainingIssues": []
28
+ }
29
+ ],
30
+ "scopeUpdatesRequired": []
31
+ }
32
+ ```
33
+
34
+ ## 约束
35
+
36
+ - `scope.manifestPath`、`scope.scopePath` 和所有本地产物路径相对于页面 scope 目录。
37
+ - `scopeStatus` 使用 `active`、`deferred`、`removed`。
38
+ - `reviewStatus` 使用 `confirmed`、`unresolved`、`deferred`、`removed`;后两者必须与 scope 状态相同。
39
+ - `answeredBy` 使用 `user` 或 `evidence`。证据回答必须在同一需求的 `evidenceRefs` 中可定位。
40
+ - `scopeUpdatesRequired` 只记录用户在质询中改变生命周期的需求,例如 `{ "id": "REQ-PD-003", "targetStatus": "deferred", "reason": "本迭代不实现" }`。它不是对 manifest 的静默修改授权。
41
+
42
+ `review-result.md` 按需求编号展示原始需求、证据、问题、回答、最终决策和未解决项,并在顶部汇总各状态数量。
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ const featureDir = path.resolve(process.argv[2] || '.');
7
+ const errors = [];
8
+ const requirementPattern = /^REQ-[A-Z][A-Z0-9]{1,15}-\d{3}$/;
9
+ const reviewStatuses = new Set(['confirmed', 'unresolved', 'deferred', 'removed']);
10
+ const fail = message => errors.push(message);
11
+
12
+ function readJson(filePath, label) {
13
+ if (!fs.existsSync(filePath)) {
14
+ fail(`缺少 ${label}:${filePath}`);
15
+ return null;
16
+ }
17
+ try {
18
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
19
+ } catch (error) {
20
+ fail(`${label} JSON 无效:${error.message}`);
21
+ return null;
22
+ }
23
+ }
24
+
25
+ const reviewPath = path.join(featureDir, 'review/review-result.json');
26
+ const review = readJson(reviewPath, 'review-result.json');
27
+
28
+ if (review) {
29
+ if (review.schemaVersion !== 1) fail('review-result.schemaVersion 必须为 1');
30
+ const manifestPath = path.resolve(featureDir, review.scope?.manifestPath || '');
31
+ const scopePath = path.resolve(featureDir, review.scope?.scopePath || '');
32
+ const manifest = readJson(manifestPath, 'scope-manifest.json');
33
+ if (!review.scope?.manifestPath) fail('review-result 缺少 scope.manifestPath');
34
+ if (!review.scope?.scopePath) fail('review-result 缺少 scope.scopePath');
35
+ if (!fs.existsSync(scopePath)) fail(`Scope 不存在:${review.scope?.scopePath}`);
36
+ if (manifest?.traceabilitySchemaVersion !== 1) {
37
+ fail('Manifest 未启用 traceabilitySchemaVersion: 1');
38
+ }
39
+ const manifestScope = manifest?.scopes?.find(
40
+ item => item.directory === review.scope?.directory
41
+ );
42
+ if (!manifestScope) fail(`Manifest 未找到 scope:${review.scope?.directory}`);
43
+ const expected = new Map(
44
+ (manifestScope?.requirements || []).map(item => [item.id, item])
45
+ );
46
+ const requirements = Array.isArray(review.requirements) ? review.requirements : [];
47
+ const ids = requirements.map(item => item?.id);
48
+ if (new Set(ids).size !== ids.length) fail('review-result 包含重复需求 ID');
49
+
50
+ for (const id of expected.keys()) {
51
+ if (!ids.includes(id)) fail(`review-result 缺少需求:${id}`);
52
+ }
53
+ for (const item of requirements) {
54
+ if (!requirementPattern.test(item?.id || '')) {
55
+ fail(`需求编号格式无效:${item?.id}`);
56
+ continue;
57
+ }
58
+ const source = expected.get(item.id);
59
+ if (!source) {
60
+ fail(`review-result 包含未知需求:${item.id}`);
61
+ continue;
62
+ }
63
+ if (item.scopeStatus !== source.status) {
64
+ fail(`${item.id} 的 scopeStatus 与 manifest 不一致`);
65
+ }
66
+ if (!reviewStatuses.has(item.reviewStatus)) {
67
+ fail(`${item.id} 的 reviewStatus 无效:${item.reviewStatus}`);
68
+ }
69
+ if (source.status === 'active' && !['confirmed', 'unresolved'].includes(item.reviewStatus)) {
70
+ fail(`${item.id} 是 active,reviewStatus 必须为 confirmed 或 unresolved`);
71
+ }
72
+ if (source.status !== 'active' && item.reviewStatus !== source.status) {
73
+ fail(`${item.id} 的排除状态必须保持为 ${source.status}`);
74
+ }
75
+ if (item.reviewStatus === 'confirmed' &&
76
+ (!Array.isArray(item.decisions) || item.decisions.length === 0)) {
77
+ fail(`${item.id} 已 confirmed 但缺少 decisions`);
78
+ }
79
+ if (item.reviewStatus === 'unresolved' &&
80
+ (!Array.isArray(item.remainingIssues) || item.remainingIssues.length === 0)) {
81
+ fail(`${item.id} 仍 unresolved 但缺少 remainingIssues`);
82
+ }
83
+ }
84
+ if (!Array.isArray(review.scopeUpdatesRequired)) {
85
+ fail('review-result.scopeUpdatesRequired 必须是数组');
86
+ }
87
+ }
88
+
89
+ for (const error of errors) console.error(`错误 ${error}`);
90
+ console.log(`Review 追踪校验:${errors.length} 个错误`);
91
+ process.exit(errors.length === 0 ? 0 : 1);
@@ -18,8 +18,10 @@ description: 通过 MCP 拉取 Azure DevOps PRD PR,按共享 UI 页面族拆
18
18
 
19
19
  1. 读取仓库 `AGENTS.md`,检查现有输出目录和 Figma 采集目录,保留无关文件及用户创建的文件。
20
20
  2. 使用完整 PR 链接调用 `mcp__azurepr_mcp_bridge__get_azure_pull_request`。记录 PR 元数据、所有变更 PRD 文件,以及 bridge 返回的是完整文档还是仅新增片段。
21
- 3. 写入前确定输出身份:
22
- - 创建 `scope-manifest.json`,记录 PR 链接/ID/path、RAG 平台、来源到 scope 的映射、页面族决策、每个 scope 的 RAG 覆盖、生成目录、未匹配来源和 Figma 溯源。
21
+ 3. 写入前完整读取 [references/requirement-traceability.md](references/requirement-traceability.md),确定输出身份和编号:
22
+ - 创建 `scope-manifest.json`,声明 `traceabilitySchemaVersion: 1`,记录 PR 链接/ID/path、RAG 平台、来源到 scope 的映射、页面族决策、每个 scope 的 RAG 覆盖、生成目录、未匹配来源和 Figma 溯源。
23
+ - 为每个非全局 scope 选择稳定的 2 至 16 位大写 ASCII 简称,并以 `REQ-<简称>-<三位序号>` 分配需求 ID。一个可独立确认、实现和验收的需求使用一个 ID。
24
+ - 刷新已有范围包时,先按业务含义和来源定位已有需求并复用 ID;调整顺序不改号,删除或暂缓保留原 ID 和原因,新增需求使用该 scope 从未使用过的下一序号。不得回收或静默重排编号。
23
25
  - 输出根目录已有相同 PR/path 的 manifest 时,只刷新该 manifest 所属文件。在 manifest 或报告中标记失效 scope;只有用户确认后才能删除。
24
26
  - 目录属于其他 PR,或包含没有身份信息的旧文件时,默认写入 `<output-root>/pr-<id>`;用户明确授权迁移时除外。不得静默混合两个 PR。
25
27
  4. 写入前建立来源清单。先分离全局规则和页面规则,再按共享 UI 模板分组,不能按文档或 URL 机械拆分。用户提供的页面族分类优先。否则依据布局/DOM、工作流/状态、模块所有权和数据契约记录合并或拆分理由。同一 UI 的不同 URL 作为变体;用户明确认定为一个页面族时,局部差异本身不构成拆分理由。
@@ -39,7 +41,7 @@ description: 通过 MCP 拉取 Azure DevOps PRD PR,按共享 UI 页面族拆
39
41
  - 将无歧义的匹配资产复制到页面目录,并在 manifest 记录原路径。只有用户明确要求时才移动源文件。未匹配资产保留原位。
40
42
  - 在 `scope.md` 中列出每一组本地 JSON/PNG;只总结设计证据直接支持的布局和状态,不转储原始 JSON。
41
43
  - 按 PRD 改动点标明每个资产覆盖的组件、设备和状态。只在改动点缺少必需视觉证据时记录待补采;局部组件改动不要求完整页面截图。不得把组件截图静默当作完整页面设计,也不得因它不是整页而把已覆盖的局部改动误报为缺失。
42
- 9. 创建或重写页面 scope 前,读取 [references/scope-schema.md](references/scope-schema.md)。把适用的全局规则展开到各页面文档中。每个页面 scope 都要重复 Azure PR 链接、证据类型和完整 RAG 溯源,不依赖 README、全局文件或研究笔记才能理解。RAG 事实放在它所补充的功能或状态附近并附引用;不要另建后续提示必须读取的 RAG 或问题文档。
44
+ 9. 创建或重写页面 scope 前,读取 [references/scope-schema.md](references/scope-schema.md)。把适用的全局规则展开到各页面文档中。每个页面 scope 都要包含与 manifest 一致的“需求清单”,重复 Azure PR 链接、证据类型和完整 RAG 溯源,不依赖 README、全局文件或研究笔记才能理解。RAG 事实放在它所补充的功能或状态附近并附引用;不要另建后续提示必须读取的 RAG 或问题文档。
43
45
  10. 按以下优先级处理证据:当前 PRD 明确业务规则、当前 Figma 视觉证据、RAG 历史 PRD、仓库当前实现。冲突保留在“待质询”;实现证据只描述当前约束,不能覆盖新需求。
44
46
  11. 执行校验:
45
47
 
@@ -56,6 +58,7 @@ node .agents/skills/ui-prd-scope/scripts/validate-scope-bundle.mjs <实际输出
56
58
  - 每个页面 scope 都包含需求来源、运行环境/模块、改动边界、URL、权限/前置条件、Figma 状态、带溯源的页面级 RAG 和待质询问题。
57
59
  - 每个 RAG 覆盖领域都标记为命中、未命中、不适用或由用户明确暂缓;隐含的未检索缺口视为错误。
58
60
  - 每个页面族的合并/拆分都有依据,或明确引用用户提供的分类。
61
+ - 每个非全局 scope 的简称和需求 ID 格式有效且全范围包唯一;manifest 与 `scope.md` 的编号、正文、状态和来源一致。
59
62
  - 每个本地资产链接有效,同目录 JSON 均可解析。
60
63
  - 缺少不可从仓库获得的路由/环境前置、业务数据、权限、改动点所需 Figma 状态或 RAG 证据时,明确由开发、产品、设计或后续质询补充。仓库可发现的运行事实由后续角色自行解析,不转嫁为用户实时输入。
61
64
  - 最终答复链接范围索引,列出页面族,汇总未命中和阻塞缺口,并说明未修改业务代码。
@@ -0,0 +1,82 @@
1
+ # 需求追踪契约
2
+
3
+ 本契约定义页面 scope 从 PRD 到交付证据使用的稳定需求编号。`ui-prd-scope` 创建并拥有编号;后续质询、规格、实现、验收、审计和 PR 只能引用,不得重编号。
4
+
5
+ ## 编号
6
+
7
+ 格式固定为:
8
+
9
+ ```text
10
+ REQ-<页面或Scope简称>-<三位序号>
11
+ ```
12
+
13
+ - 简称使用 2 至 16 位大写 ASCII 字母或数字,以字母开头,例如 `PD`、`PL`、`GLOBAL`。
14
+ - 完整编号匹配 `^REQ-[A-Z][A-Z0-9]{1,15}-\d{3}$`。
15
+ - 每个页面族在 `scope-manifest.json` 中声明唯一的 `requirementPrefix`。
16
+ - 序号从 `001` 递增。调整展示顺序时保留原编号;新增需求使用该前缀下从未使用过的下一个序号。
17
+ - 一个编号只描述一个可独立实现或验收的原子需求。背景、目标、会议记录和普通备注不编号。
18
+
19
+ ## 生命周期
20
+
21
+ 需求状态只使用:
22
+
23
+ | 状态 | 含义 |
24
+ | --- | --- |
25
+ | `active` | 本次必须进入质询、规格、实现和验收 |
26
+ | `deferred` | 用户明确暂缓,保留编号和原因 |
27
+ | `removed` | 用户明确取消,保留编号和原因 |
28
+
29
+ 刷新已有 scope 时先读取旧 manifest,按来源和语义复用原编号。需求被拆分时保留原编号并为新增原子需求分配新编号;需求合并展示时仍保留所有原编号。不得用内容哈希、表格行号、E2E scene、检查 ID 或 `reportGroup` 代替需求编号。
30
+
31
+ ## Manifest 结构
32
+
33
+ 声明 `traceabilitySchemaVersion: 1` 的新范围包必须在每个非全局 scope 中包含:
34
+
35
+ ```json
36
+ {
37
+ "directory": "01-product-detail",
38
+ "pageFamily": "product-detail",
39
+ "requirementPrefix": "PD",
40
+ "requirements": [
41
+ {
42
+ "id": "REQ-PD-001",
43
+ "statement": "商品主图容器保持 4:3 比例",
44
+ "status": "active",
45
+ "sourceRefs": [
46
+ {
47
+ "type": "azure-prd",
48
+ "path": "/Frontend_Web/product-detail.md",
49
+ "section": "商品图片"
50
+ }
51
+ ],
52
+ "figmaRefs": ["mobile-default.json#12:34"],
53
+ "ragRefs": ["doc_id=81"],
54
+ "statusReason": null
55
+ }
56
+ ]
57
+ }
58
+ ```
59
+
60
+ `sourceRefs` 至少包含一个一手 PRD 来源;RAG 和当前实现只能补充,不能成为新增需求的唯一来源。`deferred` 和 `removed` 必须提供非空 `statusReason`。
61
+
62
+ ## Scope 文档
63
+
64
+ 每个非全局 `scope.md` 在“改动范围”之前包含:
65
+
66
+ ```markdown
67
+ ## 需求清单
68
+
69
+ | 需求 ID | 原子需求 | 状态 | PRD 来源 | Figma | RAG |
70
+ | --- | --- | --- | --- | --- | --- |
71
+ | `REQ-PD-001` | 商品主图容器保持 4:3 比例 | `active` | `/Frontend_Web/product-detail.md` 商品图片 | `mobile-default.json#12:34` | `doc_id=81` |
72
+ ```
73
+
74
+ 正文中讨论需求、冲突和待质询项时引用同一编号。Manifest 是机器追踪源,`scope.md` 是自包含的人读入口,两者的编号、描述和状态必须一致。
75
+
76
+ ## 覆盖不变量
77
+
78
+ - 每个 `active` 需求必须在质询结果中出现且最终为 `confirmed`,之后才能生成可交付 Spec。
79
+ - 每个 `active` 需求必须在 `spec.md` 和 `spec-traceability.json` 中出现。
80
+ - 每个 Spec 需求必须由至少一个运行检查或视觉映射覆盖;同一需求可对应多个检查。
81
+ - Maker、Checker、Auditor 和 PR 描述只报告已有编号,不根据文本猜测或创建编号。
82
+ - `deferred` 和 `removed` 不进入实现与验收覆盖,但必须在质询和 Spec 的排除清单中保留。