@tea-agent/loop-agent 0.33.0 → 0.33.2

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 (49) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/worker/console/operator-surface-health.js +1 -0
  3. package/dist/worker/console/static/assets/index-CnUXAqxG.css +1 -0
  4. package/dist/worker/console/static/assets/{index-3R-GT3a_.js → index-PzYzcuFG.js} +17 -17
  5. package/dist/worker/console/static/index.html +3 -2
  6. package/dist/worker/console/static-src/app/useRecoveryConsole.js +3 -2
  7. package/dist/worker/observe/routes.js +3 -8
  8. package/dist/worker/observe/static/app.js +11 -0
  9. package/dist/worker/observe/static/index.html +11 -43
  10. package/dist/worker/observe/static/kpi.js +2 -24
  11. package/dist/worker/observe/static/operator-chrome.css +480 -0
  12. package/dist/worker/observe/static/operator-chrome.d.ts +82 -0
  13. package/dist/worker/observe/static/operator-chrome.js +554 -0
  14. package/dist/worker/observe/static/shell-chrome.js +1 -11
  15. package/dist/worker/observe/static/styles.css +62 -269
  16. package/dist/worker/observe/static/views/dashboard.js +52 -4
  17. package/dist/workflows/dag/retry-policy.js +5 -0
  18. package/package.json +1 -1
  19. package/skills/analyze-product-dependencies/SKILL.md +74 -33
  20. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +16 -11
  21. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +20 -10
  22. package/skills/analyze-product-dependencies/references/example.md +9 -9
  23. package/skills/analyze-product-dependencies/references/forward-test-cases.md +93 -18
  24. package/skills/analyze-product-dependencies/references/input-contract.md +27 -4
  25. package/skills/analyze-product-dependencies/references/kb-integration.md +64 -0
  26. package/skills/analyze-product-dependencies/references/scouting-rules.md +25 -10
  27. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +247 -54
  28. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +122 -29
  29. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +94 -36
  30. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +37 -23
  31. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +35 -43
  32. package/skills/analyze-product-requirements/SKILL.md +98 -43
  33. package/skills/analyze-product-requirements/references/acceptance-criteria.md +8 -12
  34. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +28 -14
  35. package/skills/analyze-product-requirements/references/example.md +24 -6
  36. package/skills/analyze-product-requirements/references/forward-test-cases.md +87 -9
  37. package/skills/analyze-product-requirements/references/kb-integration.md +56 -0
  38. package/skills/analyze-product-requirements/references/product-analysis-schema.md +19 -10
  39. package/skills/analyze-product-requirements/references/product-requirement-schema.md +21 -12
  40. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +45 -14
  41. package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +35 -0
  42. package/skills/analyze-product-requirements/scripts/test-validators.mjs +337 -29
  43. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +38 -7
  44. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +41 -29
  45. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +33 -22
  46. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +43 -24
  47. package/dist/worker/console/static/assets/index-B0EQt_yq.css +0 -1
  48. package/skills/analyze-product-dependencies/agents/openai.yaml +0 -4
  49. 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-v3-validators-"));
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: "3.0"
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
- - 无权访问时返回 403 且不得泄露订单详情。
83
+ - 无权访问时返回 code=FORBIDDEN 且不得泄露订单详情。
91
84
  ## 7. 决策追溯
92
85
  - 需求直接来源于明确需求。
93
86
  `;
94
87
 
95
88
  const api = `---
96
- artifact_version: "3.0"
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
- ### 1.4 分页约定
114
- - 本接口不分页。
115
- ### 1.5 时间和标识符规范
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 | 用户故事 | AC | 变更 |
119
- |---|---|---|---|---|---|
120
- | API-001 | GET /api/v1/orders/{orderId}/refund-status | getRefundStatus | BE-US-001 | AC-BE-001 | 新增 |
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
- | 403 | FORBIDDEN | 资源访问被拒绝 |
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: "3.0"
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 映射 403404
174
+ - 错误/日志/审计:F1 映射 FORBIDDENNOT_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 | F1 |
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 \| 20 \| 50 \| 100 | 每页条数 |
196
+ | pageSize | integer | 否 | 10 \\| 20 \\| 50 \\| 100 | 每页条数 |
209
197
  | cursor | string | 否 | - | 下一页游标 |
210
198
  `;
211
- const paginatedApi = api.replace("#### 成功响应", `${paginationSections}#### 成功响应`);
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 V3 requirement input", requirementValidator, [requirementPath, "--target", "backend"]);
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 optional page sizes", apiValidator, [requirementPath, apiPath]);
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("pagination requires all page sizes", apiValidator, [requirementPath, variant("api-pagination-incomplete.md", paginatedApi.replace("10 | 20 | 50 | 100", "10 | 20 | 50"))], "exactly 10, 20, 50, and 100");
227
- expectFail("pagination page size must be optional", apiValidator, [requirementPath, variant("api-pagination-required.md", paginatedApi.replace("| pageSize | integer | 否 |", "| pageSize | integer | 是 |"))], "must be optional");
228
- expectFail("API index covers story", apiValidator, [requirementPath, variant("api-no-story.md", api.replace("BE-US-001 | AC-BE-001", "BE-US-999 | AC-BE-001"))], "does not cover");
229
- expectFail("API success JSON must parse", apiValidator, [requirementPath, variant("api-bad-json.md", api.replace("{\"data\":{\"refundStatus\":\"processing\",\"updatedAt\":\"2026-01-01T00:00:00Z\"}}", "{bad json}"))], "must be valid JSON");
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 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");
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 V3 dependency analysis", dependencyValidator, [requirementPath, dependencyPath, apiPath, "--target", "backend"]);
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: "3.0"
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 V3 validator matrix passed: ${checks} checks.`);
460
+ console.log(`Dependency V4 validator matrix passed: ${checks} checks.`);