@tea-agent/loop-agent 0.33.1 → 0.33.3
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.
- package/CHANGELOG.md +26 -0
- package/dist/workflows/dag/frontend-test-html-report.js +38 -23
- package/dist/workflows/dag/frontend-test-result-contract.js +77 -6
- package/dist/workflows/dag/init-hybrid.js +150 -50
- package/dist/workflows/dag/retry-policy.js +5 -0
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +1 -1
- package/docs/templates/frontend-test-dag.json +11 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/package.json +1 -1
- package/skills/analyze-product-dependencies/SKILL.md +74 -33
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +16 -11
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +20 -10
- package/skills/analyze-product-dependencies/references/example.md +9 -9
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +93 -18
- package/skills/analyze-product-dependencies/references/input-contract.md +27 -4
- package/skills/analyze-product-dependencies/references/kb-integration.md +64 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +25 -10
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +247 -54
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +122 -29
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +94 -36
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +37 -23
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +35 -43
- package/skills/analyze-product-requirements/SKILL.md +98 -43
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +8 -12
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +28 -14
- package/skills/analyze-product-requirements/references/example.md +24 -6
- package/skills/analyze-product-requirements/references/forward-test-cases.md +87 -9
- package/skills/analyze-product-requirements/references/kb-integration.md +56 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +19 -10
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +21 -12
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +45 -14
- package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +337 -29
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +38 -7
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +41 -29
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +33 -22
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +43 -24
- package/skills/playwright-cli/SKILL.md +7 -0
- package/skills/analyze-product-dependencies/agents/openai.yaml +0 -4
- package/skills/analyze-product-requirements/agents/openai.yaml +0 -4
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
|
|
9
9
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
const temp = mkdtempSync(join(tmpdir(), "dependency-
|
|
10
|
+
const temp = mkdtempSync(join(tmpdir(), "dependency-v4-validators-"));
|
|
11
|
+
process.once("exit", () => rmSync(temp, { recursive: true, force: true }));
|
|
11
12
|
const repo = join(temp, "repo");
|
|
12
13
|
const artifactDir = join(repo, "docs", "product-analysis", "refund-status");
|
|
13
14
|
mkdirSync(artifactDir, { recursive: true });
|
|
@@ -34,15 +35,11 @@ const apiPath = join(artifactDir, "api-documentation.md");
|
|
|
34
35
|
const dependencyPath = join(artifactDir, "dependency-analysis.md");
|
|
35
36
|
|
|
36
37
|
const requirement = `---
|
|
37
|
-
artifact_version: "
|
|
38
|
+
artifact_version: "4.0"
|
|
38
39
|
artifact_type: product-requirement
|
|
39
40
|
requirement_id: refund-status
|
|
40
|
-
project_root: ../../..
|
|
41
41
|
requirement_status: complete
|
|
42
42
|
analysis_scope: backend
|
|
43
|
-
source_requirement: inline
|
|
44
|
-
source_product_analysis: none
|
|
45
|
-
source_clarification: none
|
|
46
43
|
---
|
|
47
44
|
# Product Requirement
|
|
48
45
|
## 1. 需求概述
|
|
@@ -50,14 +47,10 @@ source_clarification: none
|
|
|
50
47
|
## 2. 业务目标
|
|
51
48
|
- 提供可解释的退款进度。
|
|
52
49
|
## 3. 需求范围
|
|
53
|
-
### 3.1
|
|
50
|
+
### 3.1 已确认范围
|
|
54
51
|
- 仅订单所属用户可查询。
|
|
55
52
|
### 3.2 非目标
|
|
56
53
|
- 不修改退款流程。
|
|
57
|
-
### 3.3 默认假设
|
|
58
|
-
- 无。
|
|
59
|
-
### 3.4 未决事项
|
|
60
|
-
- 无。
|
|
61
54
|
## 4. 业务规则
|
|
62
55
|
- 不得泄露其他用户订单。
|
|
63
56
|
## 5. 后端用户故事
|
|
@@ -65,7 +58,7 @@ source_clarification: none
|
|
|
65
58
|
- 系统能力:返回指定订单的退款状态
|
|
66
59
|
- 使用方:订单详情页
|
|
67
60
|
- 业务价值:让用户了解退款进度
|
|
68
|
-
- 触发方式:API
|
|
61
|
+
- 触发方式:API(Web)
|
|
69
62
|
- 验收标准:AC-BE-001
|
|
70
63
|
## 6. 后端输出规范
|
|
71
64
|
### BE-US-001 查询退款状态
|
|
@@ -87,37 +80,35 @@ Then:
|
|
|
87
80
|
- 返回 refundStatus 和 updatedAt。
|
|
88
81
|
- 查询不得修改退款记录。
|
|
89
82
|
异常场景:
|
|
90
|
-
- 无权访问时返回
|
|
83
|
+
- 无权访问时返回 code=FORBIDDEN 且不得泄露订单详情。
|
|
91
84
|
## 7. 决策追溯
|
|
92
85
|
- 需求直接来源于明确需求。
|
|
93
86
|
`;
|
|
94
87
|
|
|
95
88
|
const api = `---
|
|
96
|
-
artifact_version: "
|
|
89
|
+
artifact_version: "4.0"
|
|
97
90
|
artifact_type: api-documentation
|
|
98
91
|
requirement_id: refund-status
|
|
99
|
-
project_root: ../../..
|
|
100
92
|
api_status: complete
|
|
101
93
|
analysis_scope: backend
|
|
102
|
-
source_product_requirement: ./product-requirement.md
|
|
103
|
-
repository_root: ../../..
|
|
104
94
|
---
|
|
105
95
|
# API Documentation
|
|
106
96
|
## 1. 通用约定
|
|
107
97
|
### 1.1 Base URL
|
|
108
98
|
- /api/v1
|
|
109
|
-
### 1.2
|
|
110
|
-
- data 包裹成功响应。
|
|
111
|
-
### 1.3
|
|
99
|
+
### 1.2 成功响应结构
|
|
100
|
+
- code 标识结果,data 包裹成功响应。
|
|
101
|
+
### 1.3 公共错误响应结构
|
|
112
102
|
- code 和 message。
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
103
|
+
\`\`\`json
|
|
104
|
+
{"code":"INVALID_ARGUMENT","message":"invalid argument"}
|
|
105
|
+
\`\`\`
|
|
106
|
+
### 1.4 时间和标识符规范
|
|
116
107
|
- ISO 8601 和字符串 ID。
|
|
117
108
|
## 2. API 索引
|
|
118
|
-
| API | Method + Path | Operation ID |
|
|
119
|
-
|
|
120
|
-
| API-001 | GET /api/v1/orders/{orderId}/refund-status | getRefundStatus |
|
|
109
|
+
| API ID | Method + Path | Operation ID | 变更类型 |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| API-001 | GET /api/v1/orders/{orderId}/refund-status | getRefundStatus | 新增 |
|
|
121
112
|
## 3. API 详情
|
|
122
113
|
### API-001 查询退款状态
|
|
123
114
|
> \`GET /api/v1/orders/{orderId}/refund-status\`
|
|
@@ -134,12 +125,12 @@ repository_root: ../../..
|
|
|
134
125
|
#### 成功响应
|
|
135
126
|
- HTTP:200
|
|
136
127
|
\`\`\`json
|
|
137
|
-
{"data":{"refundStatus":"processing","updatedAt":"2026-01-01T00:00:00Z"}}
|
|
128
|
+
{"code":"SUCCESS","data":{"refundStatus":"processing","updatedAt":"2026-01-01T00:00:00Z"}}
|
|
138
129
|
\`\`\`
|
|
139
130
|
#### 错误响应
|
|
140
131
|
| 状态码 | 错误码 | 条件 |
|
|
141
132
|
|---|---|---|
|
|
142
|
-
|
|
|
133
|
+
| 200 | FORBIDDEN | 资源访问被拒绝 |
|
|
143
134
|
\`\`\`json
|
|
144
135
|
{"code":"FORBIDDEN","message":"forbidden"}
|
|
145
136
|
\`\`\`
|
|
@@ -150,14 +141,10 @@ repository_root: ../../..
|
|
|
150
141
|
`;
|
|
151
142
|
|
|
152
143
|
const dependency = `---
|
|
153
|
-
artifact_version: "
|
|
144
|
+
artifact_version: "4.0"
|
|
154
145
|
artifact_type: dependency-analysis
|
|
155
146
|
requirement_id: refund-status
|
|
156
|
-
project_root: ../../..
|
|
157
147
|
analysis_scope: backend
|
|
158
|
-
source_product_requirement: ./product-requirement.md
|
|
159
|
-
source_api_documentation: ./api-documentation.md
|
|
160
|
-
repository_root: ../../..
|
|
161
148
|
analysis_status: complete
|
|
162
149
|
blocked_on: none
|
|
163
150
|
---
|
|
@@ -165,6 +152,7 @@ blocked_on: none
|
|
|
165
152
|
## 1. 输入与代码基线
|
|
166
153
|
- 需求:./product-requirement.md
|
|
167
154
|
- 仓库:../../..
|
|
155
|
+
- 知识检索状态:executed-no-match
|
|
168
156
|
## 2. 用户故事覆盖矩阵
|
|
169
157
|
| 故事 | AC | API | 主要代码入口 | 状态 |
|
|
170
158
|
|---|---|---|---|---|
|
|
@@ -183,7 +171,7 @@ blocked_on: none
|
|
|
183
171
|
- DTO/Schema:F1 复用统一响应结构
|
|
184
172
|
- 数据依赖:F3 读取退款记录
|
|
185
173
|
- 权限依赖:F1 校验订单归属
|
|
186
|
-
- 错误/日志/审计:F1 映射
|
|
174
|
+
- 错误/日志/审计:F1 映射 FORBIDDEN 和 NOT_FOUND code
|
|
187
175
|
- 测试落点:F1、F2 的相邻测试文件
|
|
188
176
|
- 定位证据:现有订单路由调用 F2,F2 注入 F3
|
|
189
177
|
- 风险:旧退款记录可能缺少 updatedAt
|
|
@@ -191,10 +179,10 @@ blocked_on: none
|
|
|
191
179
|
## 4. API 实现映射
|
|
192
180
|
| API | Operation ID | 方法与路径 | 代码入口 |
|
|
193
181
|
|---|---|---|---|
|
|
194
|
-
| API-001 | getRefundStatus | GET /api/v1/orders/{orderId}/refund-status |
|
|
182
|
+
| API-001 | getRefundStatus | GET /api/v1/orders/{orderId}/refund-status | \`src/refund/refund.controller.ts#getRefundStatus\` |
|
|
195
183
|
## 5. 跨故事共享依赖
|
|
196
184
|
- 复用订单鉴权和统一错误结构。
|
|
197
|
-
## 6.
|
|
185
|
+
## 6. 跨故事风险与未定位项
|
|
198
186
|
- 需要确认历史数据的 updatedAt 完整性。
|
|
199
187
|
`;
|
|
200
188
|
|
|
@@ -205,48 +193,204 @@ writeFileSync(dependencyPath, dependency);
|
|
|
205
193
|
const paginationSections = `#### Query 参数
|
|
206
194
|
| 参数 | 类型 | 必填 | 允许值 | 语义 |
|
|
207
195
|
|---|---|---|---|---|
|
|
208
|
-
| pageSize | integer | 否 | 10
|
|
196
|
+
| pageSize | integer | 否 | 10 \\| 20 \\| 50 \\| 100 | 每页条数 |
|
|
209
197
|
| cursor | string | 否 | - | 下一页游标 |
|
|
210
198
|
`;
|
|
211
|
-
const paginatedApi = api
|
|
199
|
+
const paginatedApi = api
|
|
200
|
+
.replace("### 1.4 时间和标识符规范", "### 1.4 分页约定\n- pageSize 允许值为 10、20、50、100。\n### 1.5 时间和标识符规范")
|
|
201
|
+
.replace("#### 成功响应", `${paginationSections}#### 成功响应`);
|
|
212
202
|
|
|
213
203
|
let checks = 0;
|
|
214
204
|
function expectPass(name, script, args) { pass(name, run(script, args)); checks += 1; }
|
|
215
205
|
function expectFail(name, script, args, message) { fail(name, run(script, args), message); checks += 1; }
|
|
216
206
|
|
|
217
|
-
expectPass("valid
|
|
207
|
+
expectPass("valid V4 requirement input", requirementValidator, [requirementPath, "--target", "backend"]);
|
|
208
|
+
expectFail("input rejects extra frontmatter fields", requirementValidator, [variant("requirement-extra-frontmatter.md", requirement.replace("analysis_scope: backend", "analysis_scope: backend\nsource_path: /private/customer/prd.md")), "--target", "backend"], "unsupported field source_path");
|
|
209
|
+
const backendStoryStart = requirement.indexOf("### BE-US-001");
|
|
210
|
+
const backendSpecHeading = requirement.indexOf("## 6. 后端输出规范");
|
|
211
|
+
const backendSpecStart = requirement.indexOf("### BE-US-001", backendSpecHeading);
|
|
212
|
+
const backendTraceHeading = requirement.indexOf("## 7. 决策追溯");
|
|
213
|
+
const duplicateAcInput = requirement
|
|
214
|
+
.slice(0, backendSpecHeading)
|
|
215
|
+
+ requirement.slice(backendStoryStart, backendSpecHeading).replace("BE-US-001", "BE-US-002")
|
|
216
|
+
+ requirement.slice(backendSpecHeading, backendTraceHeading)
|
|
217
|
+
+ requirement.slice(backendSpecStart, backendTraceHeading).replace("BE-US-001", "BE-US-002")
|
|
218
|
+
+ requirement.slice(backendTraceHeading);
|
|
219
|
+
expectFail("input rejects duplicate AC IDs across stories", requirementValidator, [variant("requirement-duplicate-ac.md", duplicateAcInput), "--target", "backend"], "Duplicate acceptance criterion ID");
|
|
220
|
+
expectFail("plain API trigger requires surface clarification", requirementValidator, [variant("requirement-plain-api.md", requirement.replace("API(Web)", "API"))], "API must declare Web, Remote, or Web + Remote");
|
|
221
|
+
writeFileSync(requirementPath, requirement.replace("API(Web)", "API(Remote)"));
|
|
222
|
+
expectPass("Remote API trigger is accepted", requirementValidator, [requirementPath, "--target", "backend"]);
|
|
223
|
+
writeFileSync(requirementPath, requirement);
|
|
218
224
|
expectFail("reject scope expansion", requirementValidator, [requirementPath, "--target", "both"], "not included");
|
|
219
225
|
expectFail("require matching output spec", requirementValidator, [variant("requirement-no-spec.md", requirement.replace("### BE-US-001 查询退款状态\n- 输入语义", "### BE-US-002 查询退款状态\n- 输入语义"))], "Missing output specification");
|
|
220
226
|
expectFail("require matching AC", requirementValidator, [variant("requirement-bad-ac.md", requirement.replace("- 验收标准:AC-BE-001", "- 验收标准:AC-BE-002"))], "must match");
|
|
227
|
+
expectFail("API input requires permission semantics", requirementValidator, [variant("requirement-no-permission.md", requirement.replace("- 权限规则:仅订单所属用户\n", ""))], "API business contract is missing field 权限规则");
|
|
228
|
+
writeFileSync(requirementPath, requirement.replace(/Given:[\s\S]*?异常场景:\n- 无权访问时返回 code=FORBIDDEN 且不得泄露订单详情。/, "验收说明:调用后返回可观察结果。"));
|
|
229
|
+
expectPass("input validator does not recheck AC prose style", requirementValidator, [requirementPath, "--target", "backend"]);
|
|
230
|
+
writeFileSync(requirementPath, requirement);
|
|
221
231
|
|
|
222
232
|
expectPass("valid compact API documentation", apiValidator, [requirementPath, apiPath, "--target", "backend"]);
|
|
233
|
+
expectPass("api-only validation path", apiValidator, [requirementPath, apiPath, "--target", "backend"]);
|
|
234
|
+
expectFail("API rejects extra frontmatter fields", apiValidator, [requirementPath, variant("api-extra-frontmatter.md", api.replace("analysis_scope: backend", "analysis_scope: backend\nrepository_path: /private/repo")), "--target", "backend"], "unsupported field repository_path");
|
|
235
|
+
expectFail("API index rejects rows without details", apiValidator, [requirementPath, variant("api-orphan-index-row.md", api.replace("| API-001 | GET /api/v1/orders/{orderId}/refund-status | getRefundStatus | 新增 |", "| API-001 | GET /api/v1/orders/{orderId}/refund-status | getRefundStatus | 新增 |\n| API-999 | DELETE /ghost | ghostDelete | 新增 |"))], "without a matching API detail");
|
|
236
|
+
expectFail("API index Operation ID must match detail", apiValidator, [requirementPath, variant("api-index-operation-mismatch.md", api.replace("| API-001 | GET /api/v1/orders/{orderId}/refund-status | getRefundStatus | 新增 |", "| API-001 | GET /api/v1/orders/{orderId}/refund-status | wrongOperation | 新增 |"))], "Operation ID does not match API index");
|
|
237
|
+
writeFileSync(apiPath, api.replace("### 1.4 时间和标识符规范", "### 1.4 分页约定\n- 本接口不分页。\n### 1.5 时间和标识符规范"));
|
|
238
|
+
expectFail("non-paginated API rejects pagination conventions", apiValidator, [requirementPath, apiPath], "must not contain 分页约定");
|
|
239
|
+
writeFileSync(apiPath, paginatedApi.replace("### 1.4 分页约定\n- pageSize 允许值为 10、20、50、100。\n", ""));
|
|
240
|
+
expectFail("paginated API requires pagination conventions", apiValidator, [requirementPath, apiPath], "must contain exactly one 分页约定");
|
|
223
241
|
writeFileSync(apiPath, paginatedApi);
|
|
224
|
-
expectPass("valid
|
|
242
|
+
expectPass("valid paginated API documentation", apiValidator, [requirementPath, apiPath]);
|
|
243
|
+
expectFail("pagination parameter requires allowed values", apiValidator, [requirementPath, variant("api-pagination-no-values.md", paginatedApi.replace("| pageSize | integer | 否 | 10 \\| 20 \\| 50 \\| 100 | 每页条数 |", "| pageSize | integer | 否 | - | 每页条数 |"))], "must declare numeric");
|
|
244
|
+
expectFail("pagination parameter values match conventions", apiValidator, [requirementPath, variant("api-pagination-convention-mismatch.md", paginatedApi.replace("pageSize 允许值为 10、20、50、100", "pageSize 允许值为 20、50、100"))], "must match 分页约定");
|
|
245
|
+
const cursorOnlyApi = api.replace("#### 成功响应", "#### Query 参数\n| 参数 | 类型 | 必填 | 语义 |\n|---|---|---|---|\n| cursor | string | 否 | 下一页游标 |\n#### 成功响应");
|
|
246
|
+
writeFileSync(apiPath, cursorOnlyApi);
|
|
247
|
+
expectPass("cursor-only API does not require pagination conventions", apiValidator, [requirementPath, apiPath]);
|
|
248
|
+
const explicitPaginationRequirement = requirement
|
|
249
|
+
.replace("- 不得泄露其他用户订单。", "- 不得泄露其他用户订单。\n- pageSize 允许值为 5、10、20。");
|
|
250
|
+
writeFileSync(requirementPath, explicitPaginationRequirement);
|
|
251
|
+
writeFileSync(apiPath, paginatedApi);
|
|
252
|
+
expectFail("pagination values must match Product Requirement", apiValidator, [requirementPath, apiPath], "must match Product Requirement");
|
|
253
|
+
writeFileSync(requirementPath, requirement);
|
|
225
254
|
writeFileSync(apiPath, api);
|
|
226
|
-
expectFail("
|
|
227
|
-
expectFail("
|
|
228
|
-
expectFail("API
|
|
229
|
-
expectFail("API
|
|
255
|
+
expectFail("API documentation rejects story traceability", apiValidator, [requirementPath, variant("api-story-trace.md", api.replace("| API ID | Method + Path | Operation ID | 变更类型 |", "| API ID | Method + Path | Operation ID | 用户故事 | 变更类型 |").replace("|---|---|---|---|", "|---|---|---|---|---|").replace("| API-001 | GET /api/v1/orders/{orderId}/refund-status | getRefundStatus | 新增 |", "| API-001 | GET /api/v1/orders/{orderId}/refund-status | getRefundStatus | BE-US-001 | 新增 |"))], "must not contain user-story");
|
|
256
|
+
expectFail("API success JSON must parse", apiValidator, [requirementPath, variant("api-bad-json.md", api.replace("{\"code\":\"SUCCESS\",\"data\":{\"refundStatus\":\"processing\",\"updatedAt\":\"2026-01-01T00:00:00Z\"}}", "{bad json}"))], "must be valid JSON");
|
|
257
|
+
expectFail("API success must use HTTP 200", apiValidator, [requirementPath, variant("api-success-201.md", api.replace("- HTTP:200", "- HTTP:201"))], "success response must use HTTP 200");
|
|
258
|
+
expectFail("API error must use HTTP 200", apiValidator, [requirementPath, variant("api-error-403.md", api.replace("| 200 | FORBIDDEN |", "| 403 | FORBIDDEN |"))], "error responses must use HTTP 200");
|
|
259
|
+
expectFail("API success requires top-level code", apiValidator, [requirementPath, variant("api-success-no-code.md", api.replace("{\"code\":\"SUCCESS\",\"data\":", "{\"data\":"))], "success example must contain top-level code");
|
|
260
|
+
expectFail("API success and error codes differ", apiValidator, [requirementPath, variant("api-same-code.md", api.replace("\"code\":\"SUCCESS\"", "\"code\":\"FORBIDDEN\""))], "success and error codes must be different");
|
|
261
|
+
expectFail("API error example code must be listed", apiValidator, [requirementPath, variant("api-error-code-unlisted.md", api.replace("{\"code\":\"FORBIDDEN\",\"message\":\"forbidden\"}", "{\"code\":\"NOT_FOUND\",\"message\":\"not found\"}"))], "error example code must be listed");
|
|
230
262
|
expectFail("API does not repeat ID", apiValidator, [requirementPath, variant("api-repeat-id.md", api.replace("| Operation ID | getRefundStatus |", "| API ID | API-001 |\n| Operation ID | getRefundStatus |"))], "must not repeat API ID");
|
|
231
|
-
expectFail("API
|
|
263
|
+
expectFail("API omits empty optional parameter sections", apiValidator, [requirementPath, variant("api-empty-query.md", api.replace("#### 成功响应", "#### Query 参数\n\n#### 成功响应"))], "must omit empty Query 参数");
|
|
264
|
+
expectFail("API rejects authentication sections", apiValidator, [requirementPath, variant("api-auth.md", api.replace("### 1.2 成功响应结构", "### 1.2 认证方式\n- Bearer token\n### 1.3 成功响应结构"))], "must not contain authentication");
|
|
232
265
|
expectFail("API rejects reuse-check output", apiValidator, [requirementPath, variant("api-reuse-check.md", api.replace("## 4. 数据模型", "## 4. 数据模型\n### 4.1 复用检查"))], "must not contain reuse-check");
|
|
233
266
|
|
|
234
|
-
expectPass("valid
|
|
267
|
+
expectPass("valid V4 dependency analysis", dependencyValidator, [requirementPath, dependencyPath, apiPath, "--target", "backend"]);
|
|
268
|
+
expectFail("dependency rejects extra frontmatter fields", dependencyValidator, [requirementPath, variant("dependency-extra-frontmatter.md", dependency.replace("blocked_on: none", "blocked_on: none\nrepository_path: /private/repo")), apiPath, "--target", "backend"], "unsupported field repository_path");
|
|
269
|
+
const dependencyDetailStart = dependency.indexOf("### BE-US-001");
|
|
270
|
+
const dependencyDetailEnd = dependency.indexOf("## 4. API 实现映射");
|
|
271
|
+
const duplicateDependencyDetail = dependency.slice(0, dependencyDetailEnd)
|
|
272
|
+
+ dependency.slice(dependencyDetailStart, dependencyDetailEnd)
|
|
273
|
+
+ dependency.slice(dependencyDetailEnd);
|
|
274
|
+
expectFail("dependency rejects duplicate story details", dependencyValidator, [requirementPath, variant("dependency-duplicate-detail.md", duplicateDependencyDetail), apiPath, "--target", "backend"], "duplicate 后端依赖详情");
|
|
275
|
+
writeFileSync(requirementPath, requirement.replace("API(Web)", "API(Web + Remote)"));
|
|
276
|
+
expectPass("Web + Remote API trigger is accepted", requirementValidator, [requirementPath, "--target", "backend"]);
|
|
277
|
+
expectFail("Web + Remote requires two API references", dependencyValidator, [requirementPath, dependencyPath, apiPath, "--target", "backend"], "must reference at least two API IDs");
|
|
278
|
+
writeFileSync(requirementPath, requirement);
|
|
279
|
+
writeFileSync(dependencyPath, dependency.replace("- 知识检索状态:executed-no-match", "- 知识检索状态:executed-hit\n- KB-EVIDENCE-001:当前有效 Web API 规范要求统一响应外壳。"));
|
|
280
|
+
expectPass("dependency accepts executed-hit with KB evidence", dependencyValidator, [requirementPath, dependencyPath, apiPath, "--target", "backend"]);
|
|
281
|
+
writeFileSync(dependencyPath, dependency);
|
|
282
|
+
expectFail("dependency requires explicit knowledge status", dependencyValidator, [requirementPath, variant("dependency-kb-no-status.md", dependency.replace("- 知识检索状态:executed-no-match\n", "")), apiPath], "must contain exactly one knowledge status");
|
|
283
|
+
expectFail("dependency rejects not-executed", dependencyValidator, [requirementPath, variant("dependency-kb-not-executed.md", dependency.replace("executed-no-match", "not-executed")), apiPath], "must not use not-executed");
|
|
284
|
+
expectFail("dependency executed-hit requires KB evidence", dependencyValidator, [requirementPath, variant("dependency-kb-hit-no-evidence.md", dependency.replace("executed-no-match", "executed-hit")), apiPath], "must contain at least one KB-EVIDENCE");
|
|
285
|
+
expectFail("dependency KB evidence requires executed-hit", dependencyValidator, [requirementPath, variant("dependency-kb-evidence-wrong-status.md", dependency.replace("- 知识检索状态:executed-no-match", "- 知识检索状态:executed-no-match\n- KB-EVIDENCE-001:不应存在。")), apiPath], "require executed-hit");
|
|
286
|
+
expectFail("dependency unavailable requires user skip confirmation", dependencyValidator, [requirementPath, variant("dependency-kb-unavailable-no-confirmation.md", dependency.replace("executed-no-match", "unavailable")), apiPath], "用户处置:已确认跳过知识库");
|
|
287
|
+
writeFileSync(dependencyPath, dependency.replace("- 知识检索状态:executed-no-match", "- 知识检索状态:unavailable\n- 用户处置:已确认跳过知识库"));
|
|
288
|
+
expectPass("dependency unavailable accepts user skip confirmation", dependencyValidator, [requirementPath, dependencyPath, apiPath, "--target", "backend"]);
|
|
289
|
+
writeFileSync(dependencyPath, dependency);
|
|
235
290
|
expectFail("impact files are required", dependencyValidator, [requirementPath, variant("dependency-no-impact.md", dependency.replace(/- 影响文件:[\s\S]*?- 路由\/入口:/, "- 路由/入口:")), apiPath], "missing field 影响文件");
|
|
236
291
|
expectFail("impact file IDs must be declared", dependencyValidator, [requirementPath, variant("dependency-bad-file-ref.md", dependency.replace("- Service/领域逻辑:F2", "- Service/领域逻辑:F9")), apiPath], "undeclared impact file F9");
|
|
237
292
|
expectFail("dependency rejects trace summary", dependencyValidator, [requirementPath, variant("dependency-trace.md", dependency + "\n## 7. 追溯汇总\n- BE-US-001。\n"), apiPath], "must not contain 分析范围 or 追溯汇总");
|
|
238
|
-
expectFail("API mapping keeps operation", dependencyValidator, [requirementPath, variant("dependency-no-operation.md", dependency.replace("getRefundStatus", "missingOperation")), apiPath], "missing Operation ID getRefundStatus");
|
|
293
|
+
expectFail("API mapping keeps operation", dependencyValidator, [requirementPath, variant("dependency-no-operation.md", dependency.replace("| API-001 | getRefundStatus |", "| API-001 | missingOperation |")), apiPath], "missing Operation ID getRefundStatus");
|
|
294
|
+
expectFail("API mapping requires concrete code entry", dependencyValidator, [requirementPath, variant("dependency-no-code-entry.md", dependency.replace("`src/refund/refund.controller.ts#getRefundStatus`", "未定位")), apiPath], "must map to a concrete code entry");
|
|
295
|
+
expectFail("API mapping rejects story-local file ID as code entry", dependencyValidator, [requirementPath, variant("dependency-local-file-id-entry.md", dependency.replace("`src/refund/refund.controller.ts#getRefundStatus`", "F1")), apiPath], "must map to a concrete code entry");
|
|
239
296
|
expectFail("dependency AC matches requirement", dependencyValidator, [requirementPath, variant("dependency-bad-ac.md", dependency.replace("- 验收标准:AC-BE-001", "- 验收标准:AC-BE-002")), apiPath], "must match Product Requirement");
|
|
297
|
+
expectFail("dependency rejects copied API contract", dependencyValidator, [requirementPath, variant("dependency-api-contract.md", dependency.replace("## 5. 跨故事共享依赖", "#### Query 参数\n| pageSize | integer |\n## 5. 跨故事共享依赖")), apiPath], "must not copy full API contract");
|
|
298
|
+
expectFail("local risk must not be duplicated globally", dependencyValidator, [requirementPath, variant("dependency-duplicate-risk.md", dependency.replace("- 需要确认历史数据的 updatedAt 完整性。", "- 旧退款记录可能缺少 updatedAt。")), apiPath], "risk is duplicated");
|
|
299
|
+
|
|
300
|
+
const frontendRequirement = `---
|
|
301
|
+
artifact_version: "4.0"
|
|
302
|
+
artifact_type: product-requirement
|
|
303
|
+
requirement_id: refund-status
|
|
304
|
+
requirement_status: complete
|
|
305
|
+
analysis_scope: frontend
|
|
306
|
+
---
|
|
307
|
+
# Product Requirement
|
|
308
|
+
## 1. 前端用户故事
|
|
309
|
+
### FE-US-001 查看订单
|
|
310
|
+
- 验收标准:AC-FE-001
|
|
311
|
+
## 2. 前端输出规范
|
|
312
|
+
### FE-US-001 查看订单
|
|
313
|
+
- 页面路由:/account/orders
|
|
314
|
+
#### AC-FE-001 展示订单
|
|
315
|
+
- 用户可以看到订单。
|
|
316
|
+
`;
|
|
317
|
+
const frontendDependency = `---
|
|
318
|
+
artifact_version: "4.0"
|
|
319
|
+
artifact_type: dependency-analysis
|
|
320
|
+
requirement_id: refund-status
|
|
321
|
+
analysis_scope: frontend
|
|
322
|
+
analysis_status: complete
|
|
323
|
+
blocked_on: none
|
|
324
|
+
---
|
|
325
|
+
# Dependency Analysis
|
|
326
|
+
## 1. 输入与代码基线
|
|
327
|
+
- 已读取需求和仓库。
|
|
328
|
+
- 知识检索状态:executed-no-match
|
|
329
|
+
## 2. 用户故事覆盖矩阵
|
|
330
|
+
| 故事 | AC | API | 状态 |
|
|
331
|
+
|---|---|---|---|
|
|
332
|
+
| FE-US-001 | AC-FE-001 | 不适用 | confirmed |
|
|
333
|
+
## 3. 前端依赖详情
|
|
334
|
+
### FE-US-001 查看订单
|
|
335
|
+
- 验收标准:AC-FE-001
|
|
336
|
+
- API 文档引用:不适用;本故事无 HTTP API 依赖
|
|
337
|
+
- 影响文件:
|
|
338
|
+
- F1 modify \`src/pages/orders.tsx\`:展示订单
|
|
339
|
+
- 页面/路由:
|
|
340
|
+
- 目标路由:/account/orders
|
|
341
|
+
- F1 作为页面组件
|
|
342
|
+
- 组件:F1
|
|
343
|
+
- 状态:F1
|
|
344
|
+
- API client/类型:不适用;本故事无 HTTP API 依赖
|
|
345
|
+
- 状态与边界落点:F1
|
|
346
|
+
- 定位证据:F1 已注册为订单页
|
|
347
|
+
- 风险:无
|
|
348
|
+
- 置信度:high
|
|
349
|
+
## 4. API 实现映射
|
|
350
|
+
- 不适用。本次需求不涉及 HTTP API。
|
|
351
|
+
## 5. 跨故事共享依赖
|
|
352
|
+
- 无。
|
|
353
|
+
## 6. 跨故事风险与未定位项
|
|
354
|
+
- 无。
|
|
355
|
+
`;
|
|
356
|
+
writeFileSync(requirementPath, frontendRequirement);
|
|
357
|
+
writeFileSync(dependencyPath, frontendDependency);
|
|
358
|
+
expectPass("frontend input accepts required downstream contract", requirementValidator, [requirementPath, "--target", "frontend"]);
|
|
359
|
+
expectPass("frontend dependency preserves target route and N/A API reason", dependencyValidator, [requirementPath, dependencyPath, "--target", "frontend"]);
|
|
360
|
+
expectFail("frontend dependency rejects route drift", dependencyValidator, [requirementPath, variant("dependency-route-drift.md", frontendDependency.replaceAll("/account/orders", "/orders")), "--target", "frontend"], "page route must match");
|
|
361
|
+
expectFail("frontend dependency requires N/A API reason", dependencyValidator, [requirementPath, variant("dependency-api-na-reason.md", frontendDependency.replace("- API 文档引用:不适用;本故事无 HTTP API 依赖", "- API 文档引用:不适用")), "--target", "frontend"], "must list Method + Path or use 不适用");
|
|
362
|
+
const externalApiPath = join(temp, "shared-api.md");
|
|
363
|
+
writeFileSync(externalApiPath, "# Shared API\n- GET /order/list\n- POST /unrelated/create\n");
|
|
364
|
+
const frontendWithExternalApi = frontendDependency
|
|
365
|
+
.replace("| FE-US-001 | AC-FE-001 | 不适用 | confirmed |", "| FE-US-001 | AC-FE-001 | GET /order/list | confirmed |")
|
|
366
|
+
.replace("- API 文档引用:不适用;本故事无 HTTP API 依赖", "- API 文档引用:GET /order/list")
|
|
367
|
+
.replace("- 不适用。本次需求不涉及 HTTP API。", "- GET /order/list");
|
|
368
|
+
writeFileSync(dependencyPath, frontendWithExternalApi);
|
|
369
|
+
expectPass("frontend accepts arbitrary external API path and Method + Path only", dependencyValidator, [requirementPath, dependencyPath, "--api-doc", externalApiPath, "--target", "frontend"]);
|
|
370
|
+
expectFail("frontend Method + Path requires external API input", dependencyValidator, [requirementPath, dependencyPath, "--target", "frontend"], "no --api-doc input");
|
|
371
|
+
const missingExternalApiPath = join(temp, "external-api-missing.md");
|
|
372
|
+
writeFileSync(missingExternalApiPath, "# API\n- POST /other\n");
|
|
373
|
+
expectFail("frontend rejects missing external Method + Path", dependencyValidator, [requirementPath, dependencyPath, "--api-doc", missingExternalApiPath, "--target", "frontend"], "not found in external API Documentation");
|
|
374
|
+
writeFileSync(requirementPath, requirement);
|
|
375
|
+
writeFileSync(dependencyPath, dependency);
|
|
376
|
+
|
|
377
|
+
const nonApiRequirement = requirement.replace("- 触发方式:API(Web)", "- 触发方式:定时任务");
|
|
378
|
+
const nonApiDependency = dependency
|
|
379
|
+
.replace("| BE-US-001 | AC-BE-001 | API-001 | F1 | confirmed |", "| BE-US-001 | AC-BE-001 | 不适用 | F1 | confirmed |")
|
|
380
|
+
.replace("- API 文档引用:API-001", "- API 文档引用:不适用;触发方式为 定时任务")
|
|
381
|
+
.replace(/\| API-001 \| getRefundStatus \| GET \/api\/v1\/orders\/\{orderId\}\/refund-status \| [^\n]+\|/, "- 不适用。本次需求不涉及 HTTP API。");
|
|
382
|
+
writeFileSync(requirementPath, nonApiRequirement);
|
|
383
|
+
writeFileSync(dependencyPath, nonApiDependency);
|
|
384
|
+
expectPass("non-API backend uses reasoned N/A reference", dependencyValidator, [requirementPath, dependencyPath, "--target", "backend"]);
|
|
385
|
+
expectFail("non-API backend rejects unexplained N/A", dependencyValidator, [requirementPath, variant("dependency-non-api-na.md", nonApiDependency.replace("- API 文档引用:不适用;触发方式为 定时任务", "- API 文档引用:不适用")), "--target", "backend"], "must use 不适用;触发方式为");
|
|
386
|
+
writeFileSync(requirementPath, requirement);
|
|
387
|
+
writeFileSync(dependencyPath, dependency);
|
|
240
388
|
|
|
241
389
|
const blocked = `---
|
|
242
|
-
artifact_version: "
|
|
390
|
+
artifact_version: "4.0"
|
|
243
391
|
artifact_type: dependency-analysis
|
|
244
392
|
requirement_id: refund-status
|
|
245
|
-
project_root: ../../..
|
|
246
393
|
analysis_scope: backend
|
|
247
|
-
source_product_requirement: none
|
|
248
|
-
source_api_documentation: none
|
|
249
|
-
repository_root: none
|
|
250
394
|
analysis_status: blocked
|
|
251
395
|
blocked_on: requirement-missing repository-missing
|
|
252
396
|
---
|
|
@@ -262,6 +406,55 @@ blocked_on: requirement-missing repository-missing
|
|
|
262
406
|
`;
|
|
263
407
|
writeFileSync(dependencyPath, blocked);
|
|
264
408
|
expectPass("valid blocked dependency", dependencyValidator, ["none", dependencyPath, "--target", "backend"]);
|
|
409
|
+
|
|
410
|
+
const incompleteContractBlocked = `---
|
|
411
|
+
artifact_version: "4.0"
|
|
412
|
+
artifact_type: dependency-analysis
|
|
413
|
+
requirement_id: refund-status
|
|
414
|
+
analysis_scope: backend
|
|
415
|
+
analysis_status: blocked
|
|
416
|
+
blocked_on: api-business-contract-incomplete
|
|
417
|
+
---
|
|
418
|
+
# Dependency Analysis
|
|
419
|
+
## 1. 分析范围
|
|
420
|
+
- backend
|
|
421
|
+
## 2. 输入与代码基线
|
|
422
|
+
- 已读取 Product Requirement 与仓库。
|
|
423
|
+
## 3. 阻断原因
|
|
424
|
+
- API 业务契约不完整,缺少权限或安全边界。
|
|
425
|
+
## 4. 恢复条件
|
|
426
|
+
- 返回上游需求 skill 补齐 API 业务契约后重试。
|
|
427
|
+
`;
|
|
428
|
+
writeFileSync(requirementPath, requirement);
|
|
429
|
+
writeFileSync(dependencyPath, incompleteContractBlocked);
|
|
430
|
+
expectPass("blocked on api-business-contract-incomplete", dependencyValidator, [requirementPath, dependencyPath, "--target", "backend"]);
|
|
431
|
+
|
|
432
|
+
const v2NonApiRequirement = `---
|
|
433
|
+
artifact_version: "2.0"
|
|
434
|
+
artifact_type: product-requirement
|
|
435
|
+
requirement_id: refund-status
|
|
436
|
+
requirement_status: complete
|
|
437
|
+
analysis_scope: backend
|
|
438
|
+
---
|
|
439
|
+
# Product Requirement
|
|
440
|
+
## 1. 后端用户故事
|
|
441
|
+
### BE-US-001 归档通知
|
|
442
|
+
- 触发方式:定时任务
|
|
443
|
+
- 验收标准:AC-BE-001
|
|
444
|
+
#### AC-BE-001 归档完成
|
|
445
|
+
- 任务可重复执行且不重复归档。
|
|
446
|
+
`;
|
|
447
|
+
const v2ApiRequirement = v2NonApiRequirement
|
|
448
|
+
.replace("### BE-US-001 归档通知", "### BE-US-001 查询退款状态")
|
|
449
|
+
.replace("- 触发方式:定时任务", "- 触发方式:API")
|
|
450
|
+
.replace("#### AC-BE-001 归档完成\n- 任务可重复执行且不重复归档。", "#### AC-BE-001 返回退款状态\n- 用户可查询退款状态。");
|
|
451
|
+
writeFileSync(requirementPath, v2NonApiRequirement);
|
|
452
|
+
expectPass("V2 non-API requirement input remains accepted", requirementValidator, [requirementPath, "--target", "backend"]);
|
|
453
|
+
writeFileSync(requirementPath, v2ApiRequirement);
|
|
454
|
+
expectFail("V2 API requirement must upgrade to V4", requirementValidator, [requirementPath, "--target", "backend"], "must be upgraded to V4");
|
|
455
|
+
|
|
456
|
+
writeFileSync(requirementPath, requirement);
|
|
265
457
|
writeFileSync(dependencyPath, dependency);
|
|
458
|
+
writeFileSync(apiPath, api);
|
|
266
459
|
|
|
267
|
-
console.log(`Dependency
|
|
460
|
+
console.log(`Dependency V4 validator matrix passed: ${checks} checks.`);
|