@tea-agent/loop-agent 0.25.5 → 0.26.0

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 (173) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +1020 -986
  3. package/bin/loop-agent.js +21 -21
  4. package/dist/commands/cursor-prompt.js +6 -6
  5. package/dist/commands/init.js +68 -129
  6. package/dist/commands/loop-benchmark.js +11 -11
  7. package/dist/commands/pi-reuse-benchmark.js +16 -16
  8. package/dist/executors/dag-pi-executor.js +26 -20
  9. package/dist/executors/model-routing.js +34 -18
  10. package/dist/executors/shell-executor.js +1 -1
  11. package/dist/governance/manifest-types.js +33 -5
  12. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  13. package/dist/task/task-demand-routing.js +3 -1
  14. package/dist/worker/console/chat/model-resolver.js +15 -3
  15. package/dist/worker/observe/static/constants.js +3 -2
  16. package/dist/worker/observe/static/copy.js +67 -67
  17. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  18. package/dist/worker/observe/static/dag-layout.js +83 -83
  19. package/dist/worker/observe/static/dag-model.js +1 -0
  20. package/dist/worker/observe/static/dom.js +220 -220
  21. package/dist/worker/observe/static/relations.js +133 -133
  22. package/dist/worker/observe/static/router.js +93 -93
  23. package/dist/worker/observe/static/run-processing.js +148 -148
  24. package/dist/worker/observe/static/styles.css +182 -42
  25. package/dist/worker/observe/static/views/batch.js +227 -227
  26. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  27. package/dist/worker/observe/static/views/failures.js +143 -143
  28. package/dist/worker/observe/static/views/feature.js +492 -492
  29. package/dist/worker/observe/static/views/run.js +453 -453
  30. package/dist/worker/observe/static/views/shell.js +7 -7
  31. package/dist/worker/observe/static/views/timeline.js +163 -163
  32. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +362 -45
  33. package/dist/workflows/dag/backend-test-case-manifest.js +20 -0
  34. package/dist/workflows/dag/backend-test-markdown-workflow.js +17 -0
  35. package/dist/workflows/dag/canvas-observer.js +275 -275
  36. package/dist/workflows/dag/init-hybrid.js +18 -15
  37. package/dist/workflows/dag/lifecycle.js +40 -30
  38. package/dist/workflows/dag/node-execution.js +13 -0
  39. package/dist/workflows/dag/types.js +59 -19
  40. package/docs/init-surface.manifest.json +3 -1
  41. package/docs/skills/README.md +7 -7
  42. package/docs/templates/README.md +1 -0
  43. package/docs/templates/adr.md +60 -60
  44. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  45. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  46. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  47. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  48. package/docs/templates/agent-dag-report.schema.json +473 -473
  49. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  50. package/docs/templates/backend-test-dag.json +9 -9
  51. package/docs/templates/backend-test-result.schema.json +99 -99
  52. package/docs/templates/feature-spec.md +53 -53
  53. package/docs/templates/frontend-design-contract.md +42 -42
  54. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
  55. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
  56. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
  57. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
  58. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
  59. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
  60. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
  61. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
  62. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
  63. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
  64. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
  65. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
  66. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
  67. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
  68. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
  69. package/docs/templates/frontend-eval/metrics.md +138 -138
  70. package/docs/templates/frontend-eval/smoke-targets.md +53 -53
  71. package/docs/templates/frontend-task-constraints.md +35 -35
  72. package/docs/templates/frontend-task-requirement.md +70 -70
  73. package/docs/templates/harness.schema.json +29 -7
  74. package/docs/templates/init-evolution-review.md +35 -35
  75. package/docs/templates/init-managed-agents.md +137 -0
  76. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  77. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  78. package/docs/templates/knowledge-sync-dag.json +178 -178
  79. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  80. package/docs/templates/product-line/closeout.yaml +9 -9
  81. package/docs/templates/product-line/design.md +13 -13
  82. package/docs/templates/product-line/links.md +10 -10
  83. package/docs/templates/product-line/requirement.md +17 -17
  84. package/docs/templates/product-line/test-plan.md +7 -7
  85. package/docs/templates/production-readiness-checklist.md +57 -57
  86. package/docs/templates/project-start-checklist.md +9 -9
  87. package/docs/templates/qa-report.md +48 -48
  88. package/docs/templates/sprint-contract.md +29 -29
  89. package/docs/templates/worker-dogfood-evidence.md +80 -80
  90. package/docs/templates/worker-dogfood-setup.md +68 -68
  91. package/harness.json +1 -2
  92. package/package.json +1 -1
  93. package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
  94. package/scripts/kb-graph-incremental-prepare.mjs +386 -386
  95. package/scripts/kb-graph-materialize.mjs +105 -105
  96. package/scripts/kb-graph-promote.mjs +164 -164
  97. package/scripts/kb-query.mjs +554 -554
  98. package/skills/ai-engineering-context/SKILL.md +48 -48
  99. package/skills/analyze-product-dependencies/SKILL.md +67 -67
  100. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -4
  101. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -30
  102. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -28
  103. package/skills/analyze-product-dependencies/references/example.md +76 -76
  104. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -35
  105. package/skills/analyze-product-dependencies/references/input-contract.md +11 -11
  106. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -61
  107. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -267
  108. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -101
  109. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -142
  110. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -76
  111. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -146
  112. package/skills/analyze-product-requirements/SKILL.md +90 -90
  113. package/skills/analyze-product-requirements/agents/openai.yaml +4 -4
  114. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -91
  115. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -56
  116. package/skills/analyze-product-requirements/references/example.md +86 -86
  117. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -66
  118. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -32
  119. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -33
  120. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -35
  121. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -193
  122. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -69
  123. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -97
  124. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -98
  125. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -156
  126. package/skills/browser-tools/browser-content.js +103 -103
  127. package/skills/browser-tools/browser-cookies.js +35 -35
  128. package/skills/browser-tools/browser-eval.js +53 -53
  129. package/skills/browser-tools/browser-hn-scraper.js +108 -108
  130. package/skills/browser-tools/browser-nav.js +44 -44
  131. package/skills/browser-tools/browser-pick.js +162 -162
  132. package/skills/browser-tools/browser-screenshot.js +34 -34
  133. package/skills/browser-tools/browser-start.js +86 -86
  134. package/skills/browser-tools/package-lock.json +2556 -2556
  135. package/skills/browser-tools/package.json +19 -19
  136. package/skills/code-review-core/SKILL.md +20 -20
  137. package/skills/codebase-scout/SKILL.md +19 -19
  138. package/skills/grill-me/SKILL.md +10 -10
  139. package/skills/loop-agent/references/README.md +67 -67
  140. package/skills/loop-agent/references/command-reference.md +5 -4
  141. package/skills/loop-agent/references/docs-converge.md +126 -126
  142. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  143. package/skills/loop-agent/references/learned/README.md +21 -21
  144. package/skills/loop-agent/references/long-running-loop.md +57 -57
  145. package/skills/loop-agent/references/model-routing.md +2 -0
  146. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  147. package/skills/loop-agent/references/pi-prompt.md +23 -23
  148. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  149. package/skills/playwright-cli/SKILL.md +420 -420
  150. package/skills/playwright-cli/references/element-attributes.md +23 -23
  151. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  152. package/skills/playwright-cli/references/request-mocking.md +87 -87
  153. package/skills/playwright-cli/references/running-code.md +241 -241
  154. package/skills/playwright-cli/references/session-management.md +225 -225
  155. package/skills/playwright-cli/references/storage-state.md +275 -275
  156. package/skills/playwright-cli/references/test-generation.md +433 -433
  157. package/skills/playwright-cli/references/tracing.md +139 -139
  158. package/skills/playwright-cli/references/video-recording.md +143 -143
  159. package/skills/requesting-code-review/SKILL.md +101 -101
  160. package/skills/requesting-code-review/code-reviewer.md +168 -168
  161. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  162. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  163. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  164. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  165. package/skills/systematic-debugging/find-polluter.sh +63 -63
  166. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  167. package/skills/systematic-debugging/test-academic.md +14 -14
  168. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  169. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  170. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  171. package/skills/using-git-worktrees/SKILL.md +215 -215
  172. package/skills/verification-before-completion/SKILL.md +154 -154
  173. package/skills/webapp-testing/SKILL.md +19 -19
@@ -1,193 +1,193 @@
1
- #!/usr/bin/env node
2
-
3
- import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { dirname, join } from "node:path";
6
- import { fileURLToPath } from "node:url";
7
- import { spawnSync } from "node:child_process";
8
-
9
- const scriptDir = dirname(fileURLToPath(import.meta.url));
10
- const temp = mkdtempSync(join(tmpdir(), "product-requirement-v3-validators-"));
11
- const projectRoot = join(temp, "project");
12
- const artifactDir = join(projectRoot, "docs", "product-analysis", "nickname");
13
- mkdirSync(artifactDir, { recursive: true });
14
- const analysisValidator = join(scriptDir, "validate-product-analysis.mjs");
15
- const clarificationValidator = join(scriptDir, "validate-requirement-clarification.mjs");
16
- const requirementValidator = join(scriptDir, "validate-product-requirement.mjs");
17
-
18
- const run = (script, args) => spawnSync(process.execPath, [script, ...args], { encoding: "utf8" });
19
- function pass(name, result) {
20
- if (result.status !== 0) throw new Error(`${name} should pass:\n${result.stderr}${result.stdout}`);
21
- }
22
- function fail(name, result, message) {
23
- if (result.status === 0) throw new Error(`${name} should fail.`);
24
- if (message && !`${result.stderr}${result.stdout}`.includes(message)) throw new Error(`${name} should mention ${message}:\n${result.stderr}${result.stdout}`);
25
- }
26
- function variant(name, text) {
27
- const path = join(artifactDir, name);
28
- writeFileSync(path, text);
29
- return path;
30
- }
31
-
32
- const analysisPath = join(artifactDir, "product-analysis.md");
33
- const clarificationPath = join(artifactDir, "requirement-clarification.md");
34
- const requirementPath = join(artifactDir, "product-requirement.md");
35
-
36
- const analysis = `---
37
- artifact_version: "3.0"
38
- artifact_type: product-analysis
39
- requirement_id: nickname
40
- project_root: ../../..
41
- analysis_scope: frontend
42
- analysis_status: no-clarification-required
43
- source_requirement: inline
44
- repository_root: none
45
- ---
46
- # Product Analysis
47
- ## 1. 原始需求
48
- 登录用户可以修改昵称,长度为 2–20 个字符。
49
- ## 2. 需求概述
50
- 在个人资料页维护展示昵称。
51
- ## 3. 业务目标
52
- - 允许用户维护展示名称。
53
- ## 4. 需求分析
54
- ### 4.1 明确需求
55
- - 用户只能修改自己的昵称。
56
- ### 4.2 推断需求
57
- - 无未经确认的推断。
58
- ### 4.3 待确认问题
59
- - 无。范围、权限和结果已经明确。
60
- ### 4.4 初步非目标
61
- - 不修改头像。
62
- ## 5. 外部事实
63
- ### 5.1 知识库事实
64
- - 未集成知识库。
65
- ### 5.2 代码库事实
66
- - 未提供代码仓库。
67
- ## 6. 初步前端用户故事
68
- ### FE-US-001 修改昵称
69
- - 角色:已登录用户
70
- - 目标:修改自己的展示昵称
71
- - 价值:保持个人资料准确
72
- - 入口:个人资料页
73
- - 验收关注点:合法值可保存;非法值不可提交;失败时保留输入
74
- ## 7. 初步前端输出规范
75
- ### FE-US-001 修改昵称
76
- - 页面/组件:个人资料页、昵称编辑表单
77
- - 展示内容:当前昵称、字符计数、校验提示
78
- - 交互动作:编辑、保存、取消、重试
79
- - UI 状态:normal、dirty、loading、success、error、disabled
80
- - 表单校验:昵称长度为 2–20 个字符
81
- - 权限可见性:仅当前登录用户
82
- - 边界处理:失败时保留输入,不得静默失败
83
- `;
84
-
85
- const clarification = `---
86
- artifact_version: "3.0"
87
- artifact_type: requirement-clarification
88
- requirement_id: nickname
89
- project_root: ../../..
90
- analysis_scope: frontend
91
- clarification_status: complete
92
- clarification_rounds: 0
93
- source_product_analysis: ./product-analysis.md
94
- target_product_requirement: ./product-requirement.md
95
- ---
96
- # Requirement Clarification
97
- ## 1. 澄清结论
98
- - 状态:no-clarification-required
99
- - 原因:范围、权限、数据语义和验收结果已经明确。
100
- ## 2. 来源
101
- - Product Analysis:./product-analysis.md
102
- ## 3. 合并结果
103
- - Product Requirement 根据明确需求生成,无额外决策标记。
104
- `;
105
-
106
- const requirement = `---
107
- artifact_version: "3.0"
108
- artifact_type: product-requirement
109
- requirement_id: nickname
110
- project_root: ../../..
111
- requirement_status: complete
112
- analysis_scope: frontend
113
- source_requirement: inline
114
- source_product_analysis: ./product-analysis.md
115
- source_clarification: ./requirement-clarification.md
116
- ---
117
- # Product Requirement
118
- ## 1. 需求概述
119
- 在个人资料页维护当前用户的展示昵称。
120
- ## 2. 业务目标
121
- - 允许用户维护准确的展示名称。
122
- ## 3. 需求范围
123
- ### 3.1 已确认需求
124
- - 当前用户可以修改自己的昵称。
125
- ### 3.2 非目标
126
- - 不修改头像。
127
- ### 3.3 默认假设
128
- - 无未经确认的默认假设。
129
- ### 3.4 未决事项
130
- - 无。
131
- ## 4. 业务规则
132
- - 昵称长度必须为 2–20 个字符。
133
- ## 5. 前端用户故事
134
- ### FE-US-001 修改昵称
135
- - 角色:已登录用户
136
- - 目标:修改自己的展示昵称
137
- - 价值:保持个人资料准确
138
- - 入口:个人资料页
139
- - 验收标准:AC-FE-001
140
- ## 6. 前端输出规范
141
- ### FE-US-001 修改昵称
142
- - 页面/组件:个人资料页、昵称编辑表单
143
- - 展示内容:当前昵称、字符计数、校验提示
144
- - 交互动作:编辑、保存、取消、重试
145
- - UI 状态:normal、dirty、loading、success、error、disabled
146
- - 表单校验:昵称长度为 2–20 个字符
147
- - 权限可见性:仅当前登录用户
148
- - 边界处理:失败时保留输入,不得静默失败
149
- #### AC-FE-001 保存合法昵称
150
- Given:
151
- - 用户已登录并打开个人资料页。
152
- When:
153
- - 用户输入合法昵称并点击保存。
154
- Then:
155
- - 页面提交昵称修改请求。
156
- - 保存成功后展示新昵称。
157
- - 保存期间按钮保持 disabled。
158
- 异常场景:
159
- - 保存失败时保留输入并提供重试入口。
160
- ## 7. 决策追溯
161
- - 本轮无需澄清决策标记;需求直接来源于明确需求。
162
- `;
163
-
164
- writeFileSync(analysisPath, analysis);
165
- writeFileSync(clarificationPath, clarification);
166
- writeFileSync(requirementPath, requirement);
167
-
168
- let checks = 0;
169
- function expectPass(name, script, args) { pass(name, run(script, args)); checks += 1; }
170
- function expectFail(name, script, args, message) { fail(name, run(script, args), message); checks += 1; }
171
-
172
- expectPass("valid V3 analysis", analysisValidator, [analysisPath, "--target", "frontend"]);
173
- expectFail("analysis target mismatch", analysisValidator, [analysisPath, "--target", "backend"], "does not match");
174
- expectFail("analysis rejects formal AC", analysisValidator, [variant("analysis-ac.md", analysis.replace("验收关注点:", "验收关注点:AC-FE-001 "))], "must use acceptance concerns");
175
- expectFail("analysis requires matching spec", analysisValidator, [variant("analysis-no-spec.md", analysis.replace("### FE-US-001 修改昵称\n- 页面/组件", "### FE-US-002 修改昵称\n- 页面/组件"))], "missing output specification");
176
- expectFail("analysis rejects standalone roles", analysisValidator, [variant("analysis-roles.md", analysis.replace("## 4. 需求分析", "## 用户角色\n- 已登录用户。\n## 4. 需求分析"))], "must not contain standalone");
177
-
178
- expectPass("valid compact clarification", clarificationValidator, [analysisPath, clarificationPath, requirementPath]);
179
- expectFail("clarification rounds cannot exceed three", clarificationValidator, [analysisPath, variant("clarification-four-rounds.md", clarification.replace("clarification_rounds: 0", "clarification_rounds: 4")), requirementPath], "from 0 to 3");
180
- expectFail("compact clarification rejects extra section", clarificationValidator, [analysisPath, variant("clarification-extra.md", clarification + "\n## 4. 完成门禁\n- 已完成。\n"), requirementPath], "must not contain section");
181
-
182
- expectPass("valid V3 requirement", requirementValidator, [requirementPath, "--target", "frontend"]);
183
- expectFail("requirement target mismatch", requirementValidator, [requirementPath, "--target", "backend"], "does not match");
184
- expectFail("requirement requires role", requirementValidator, [variant("requirement-no-role.md", requirement.replace("- 角色:已登录用户\n", ""))], "missing story field 角色");
185
- expectFail("requirement requires matching spec", requirementValidator, [variant("requirement-no-spec.md", requirement.replace("### FE-US-001 修改昵称\n- 页面/组件", "### FE-US-002 修改昵称\n- 页面/组件"))], "missing output specification");
186
- expectFail("requirement AC refs match spec", requirementValidator, [variant("requirement-bad-ac.md", requirement.replace("- 验收标准:AC-FE-001", "- 验收标准:AC-FE-002"))], "must match its output specification");
187
- expectFail("requirement rejects standalone roles", requirementValidator, [variant("requirement-roles.md", requirement.replace("## 3. 需求范围", "## 用户角色\n- 已登录用户。\n## 3. 需求范围"))], "must not contain standalone");
188
- expectFail("pending requirement rejected by default", requirementValidator, [variant("requirement-pending.md", requirement.replace("requirement_status: complete", "requirement_status: pending"))], "must be complete");
189
- writeFileSync(requirementPath, requirement.replace("requirement_status: complete", "requirement_status: pending"));
190
- expectPass("pending requirement allowed explicitly", requirementValidator, [requirementPath, "--allow-pending"]);
191
- writeFileSync(requirementPath, requirement);
192
-
193
- console.log(`Requirement V3 validator matrix passed: ${checks} checks.`);
1
+ #!/usr/bin/env node
2
+
3
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { spawnSync } from "node:child_process";
8
+
9
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
10
+ const temp = mkdtempSync(join(tmpdir(), "product-requirement-v3-validators-"));
11
+ const projectRoot = join(temp, "project");
12
+ const artifactDir = join(projectRoot, "docs", "product-analysis", "nickname");
13
+ mkdirSync(artifactDir, { recursive: true });
14
+ const analysisValidator = join(scriptDir, "validate-product-analysis.mjs");
15
+ const clarificationValidator = join(scriptDir, "validate-requirement-clarification.mjs");
16
+ const requirementValidator = join(scriptDir, "validate-product-requirement.mjs");
17
+
18
+ const run = (script, args) => spawnSync(process.execPath, [script, ...args], { encoding: "utf8" });
19
+ function pass(name, result) {
20
+ if (result.status !== 0) throw new Error(`${name} should pass:\n${result.stderr}${result.stdout}`);
21
+ }
22
+ function fail(name, result, message) {
23
+ if (result.status === 0) throw new Error(`${name} should fail.`);
24
+ if (message && !`${result.stderr}${result.stdout}`.includes(message)) throw new Error(`${name} should mention ${message}:\n${result.stderr}${result.stdout}`);
25
+ }
26
+ function variant(name, text) {
27
+ const path = join(artifactDir, name);
28
+ writeFileSync(path, text);
29
+ return path;
30
+ }
31
+
32
+ const analysisPath = join(artifactDir, "product-analysis.md");
33
+ const clarificationPath = join(artifactDir, "requirement-clarification.md");
34
+ const requirementPath = join(artifactDir, "product-requirement.md");
35
+
36
+ const analysis = `---
37
+ artifact_version: "3.0"
38
+ artifact_type: product-analysis
39
+ requirement_id: nickname
40
+ project_root: ../../..
41
+ analysis_scope: frontend
42
+ analysis_status: no-clarification-required
43
+ source_requirement: inline
44
+ repository_root: none
45
+ ---
46
+ # Product Analysis
47
+ ## 1. 原始需求
48
+ 登录用户可以修改昵称,长度为 2–20 个字符。
49
+ ## 2. 需求概述
50
+ 在个人资料页维护展示昵称。
51
+ ## 3. 业务目标
52
+ - 允许用户维护展示名称。
53
+ ## 4. 需求分析
54
+ ### 4.1 明确需求
55
+ - 用户只能修改自己的昵称。
56
+ ### 4.2 推断需求
57
+ - 无未经确认的推断。
58
+ ### 4.3 待确认问题
59
+ - 无。范围、权限和结果已经明确。
60
+ ### 4.4 初步非目标
61
+ - 不修改头像。
62
+ ## 5. 外部事实
63
+ ### 5.1 知识库事实
64
+ - 未集成知识库。
65
+ ### 5.2 代码库事实
66
+ - 未提供代码仓库。
67
+ ## 6. 初步前端用户故事
68
+ ### FE-US-001 修改昵称
69
+ - 角色:已登录用户
70
+ - 目标:修改自己的展示昵称
71
+ - 价值:保持个人资料准确
72
+ - 入口:个人资料页
73
+ - 验收关注点:合法值可保存;非法值不可提交;失败时保留输入
74
+ ## 7. 初步前端输出规范
75
+ ### FE-US-001 修改昵称
76
+ - 页面/组件:个人资料页、昵称编辑表单
77
+ - 展示内容:当前昵称、字符计数、校验提示
78
+ - 交互动作:编辑、保存、取消、重试
79
+ - UI 状态:normal、dirty、loading、success、error、disabled
80
+ - 表单校验:昵称长度为 2–20 个字符
81
+ - 权限可见性:仅当前登录用户
82
+ - 边界处理:失败时保留输入,不得静默失败
83
+ `;
84
+
85
+ const clarification = `---
86
+ artifact_version: "3.0"
87
+ artifact_type: requirement-clarification
88
+ requirement_id: nickname
89
+ project_root: ../../..
90
+ analysis_scope: frontend
91
+ clarification_status: complete
92
+ clarification_rounds: 0
93
+ source_product_analysis: ./product-analysis.md
94
+ target_product_requirement: ./product-requirement.md
95
+ ---
96
+ # Requirement Clarification
97
+ ## 1. 澄清结论
98
+ - 状态:no-clarification-required
99
+ - 原因:范围、权限、数据语义和验收结果已经明确。
100
+ ## 2. 来源
101
+ - Product Analysis:./product-analysis.md
102
+ ## 3. 合并结果
103
+ - Product Requirement 根据明确需求生成,无额外决策标记。
104
+ `;
105
+
106
+ const requirement = `---
107
+ artifact_version: "3.0"
108
+ artifact_type: product-requirement
109
+ requirement_id: nickname
110
+ project_root: ../../..
111
+ requirement_status: complete
112
+ analysis_scope: frontend
113
+ source_requirement: inline
114
+ source_product_analysis: ./product-analysis.md
115
+ source_clarification: ./requirement-clarification.md
116
+ ---
117
+ # Product Requirement
118
+ ## 1. 需求概述
119
+ 在个人资料页维护当前用户的展示昵称。
120
+ ## 2. 业务目标
121
+ - 允许用户维护准确的展示名称。
122
+ ## 3. 需求范围
123
+ ### 3.1 已确认需求
124
+ - 当前用户可以修改自己的昵称。
125
+ ### 3.2 非目标
126
+ - 不修改头像。
127
+ ### 3.3 默认假设
128
+ - 无未经确认的默认假设。
129
+ ### 3.4 未决事项
130
+ - 无。
131
+ ## 4. 业务规则
132
+ - 昵称长度必须为 2–20 个字符。
133
+ ## 5. 前端用户故事
134
+ ### FE-US-001 修改昵称
135
+ - 角色:已登录用户
136
+ - 目标:修改自己的展示昵称
137
+ - 价值:保持个人资料准确
138
+ - 入口:个人资料页
139
+ - 验收标准:AC-FE-001
140
+ ## 6. 前端输出规范
141
+ ### FE-US-001 修改昵称
142
+ - 页面/组件:个人资料页、昵称编辑表单
143
+ - 展示内容:当前昵称、字符计数、校验提示
144
+ - 交互动作:编辑、保存、取消、重试
145
+ - UI 状态:normal、dirty、loading、success、error、disabled
146
+ - 表单校验:昵称长度为 2–20 个字符
147
+ - 权限可见性:仅当前登录用户
148
+ - 边界处理:失败时保留输入,不得静默失败
149
+ #### AC-FE-001 保存合法昵称
150
+ Given:
151
+ - 用户已登录并打开个人资料页。
152
+ When:
153
+ - 用户输入合法昵称并点击保存。
154
+ Then:
155
+ - 页面提交昵称修改请求。
156
+ - 保存成功后展示新昵称。
157
+ - 保存期间按钮保持 disabled。
158
+ 异常场景:
159
+ - 保存失败时保留输入并提供重试入口。
160
+ ## 7. 决策追溯
161
+ - 本轮无需澄清决策标记;需求直接来源于明确需求。
162
+ `;
163
+
164
+ writeFileSync(analysisPath, analysis);
165
+ writeFileSync(clarificationPath, clarification);
166
+ writeFileSync(requirementPath, requirement);
167
+
168
+ let checks = 0;
169
+ function expectPass(name, script, args) { pass(name, run(script, args)); checks += 1; }
170
+ function expectFail(name, script, args, message) { fail(name, run(script, args), message); checks += 1; }
171
+
172
+ expectPass("valid V3 analysis", analysisValidator, [analysisPath, "--target", "frontend"]);
173
+ expectFail("analysis target mismatch", analysisValidator, [analysisPath, "--target", "backend"], "does not match");
174
+ expectFail("analysis rejects formal AC", analysisValidator, [variant("analysis-ac.md", analysis.replace("验收关注点:", "验收关注点:AC-FE-001 "))], "must use acceptance concerns");
175
+ expectFail("analysis requires matching spec", analysisValidator, [variant("analysis-no-spec.md", analysis.replace("### FE-US-001 修改昵称\n- 页面/组件", "### FE-US-002 修改昵称\n- 页面/组件"))], "missing output specification");
176
+ expectFail("analysis rejects standalone roles", analysisValidator, [variant("analysis-roles.md", analysis.replace("## 4. 需求分析", "## 用户角色\n- 已登录用户。\n## 4. 需求分析"))], "must not contain standalone");
177
+
178
+ expectPass("valid compact clarification", clarificationValidator, [analysisPath, clarificationPath, requirementPath]);
179
+ expectFail("clarification rounds cannot exceed three", clarificationValidator, [analysisPath, variant("clarification-four-rounds.md", clarification.replace("clarification_rounds: 0", "clarification_rounds: 4")), requirementPath], "from 0 to 3");
180
+ expectFail("compact clarification rejects extra section", clarificationValidator, [analysisPath, variant("clarification-extra.md", clarification + "\n## 4. 完成门禁\n- 已完成。\n"), requirementPath], "must not contain section");
181
+
182
+ expectPass("valid V3 requirement", requirementValidator, [requirementPath, "--target", "frontend"]);
183
+ expectFail("requirement target mismatch", requirementValidator, [requirementPath, "--target", "backend"], "does not match");
184
+ expectFail("requirement requires role", requirementValidator, [variant("requirement-no-role.md", requirement.replace("- 角色:已登录用户\n", ""))], "missing story field 角色");
185
+ expectFail("requirement requires matching spec", requirementValidator, [variant("requirement-no-spec.md", requirement.replace("### FE-US-001 修改昵称\n- 页面/组件", "### FE-US-002 修改昵称\n- 页面/组件"))], "missing output specification");
186
+ expectFail("requirement AC refs match spec", requirementValidator, [variant("requirement-bad-ac.md", requirement.replace("- 验收标准:AC-FE-001", "- 验收标准:AC-FE-002"))], "must match its output specification");
187
+ expectFail("requirement rejects standalone roles", requirementValidator, [variant("requirement-roles.md", requirement.replace("## 3. 需求范围", "## 用户角色\n- 已登录用户。\n## 3. 需求范围"))], "must not contain standalone");
188
+ expectFail("pending requirement rejected by default", requirementValidator, [variant("requirement-pending.md", requirement.replace("requirement_status: complete", "requirement_status: pending"))], "must be complete");
189
+ writeFileSync(requirementPath, requirement.replace("requirement_status: complete", "requirement_status: pending"));
190
+ expectPass("pending requirement allowed explicitly", requirementValidator, [requirementPath, "--allow-pending"]);
191
+ writeFileSync(requirementPath, requirement);
192
+
193
+ console.log(`Requirement V3 validator matrix passed: ${checks} checks.`);
@@ -1,69 +1,69 @@
1
- #!/usr/bin/env node
2
-
3
- import { assertHeadings, field, loadMarkdown, metadata, outputSpecBlocks, printResult, section, storyBlocks, validateArtifactLocation, validateStorySpecCoverage } from "./validation-helpers.mjs";
4
-
5
- const args = process.argv.slice(2);
6
- const fileArg = args[0];
7
- const targetIndex = args.indexOf("--target");
8
- const expectedTarget = targetIndex >= 0 ? args[targetIndex + 1] : undefined;
9
- if (!fileArg) {
10
- console.error("Usage: node validate-product-analysis.mjs <product-analysis.md> [--target frontend|backend|both]");
11
- process.exit(2);
12
- }
13
-
14
- let artifact;
15
- try { artifact = loadMarkdown(fileArg, "Product Analysis"); }
16
- catch (error) { console.error(error.message); process.exit(2); }
17
-
18
- const { path, text } = artifact;
19
- const errors = [];
20
- validateArtifactLocation(artifact, errors, "product-analysis.md");
21
- const scope = metadata(text, "analysis_scope");
22
- const status = metadata(text, "analysis_status");
23
-
24
- if (metadata(text, "artifact_version") !== "3.0") errors.push("artifact_version must be 3.0.");
25
- if (metadata(text, "artifact_type") !== "product-analysis") errors.push("artifact_type must be product-analysis.");
26
- if (!["frontend", "backend", "both"].includes(scope)) errors.push("analysis_scope must be frontend, backend, or both.");
27
- if (targetIndex >= 0 && !["frontend", "backend", "both"].includes(expectedTarget)) errors.push("--target must be frontend, backend, or both.");
28
- else if (expectedTarget && scope !== expectedTarget) errors.push(`analysis_scope ${scope} does not match requested target ${expectedTarget}.`);
29
- if (!["ready-for-clarification", "no-clarification-required"].includes(status)) errors.push("analysis_status must be ready-for-clarification or no-clarification-required.");
30
- if (!metadata(text, "source_requirement")) errors.push("source_requirement is required.");
31
- if (!metadata(text, "repository_root")) errors.push("repository_root is required; use none when absent.");
32
-
33
- assertHeadings(text, 2, ["原始需求", "需求概述", "业务目标", "需求分析", "外部事实"], errors, "Product Analysis");
34
- const analysis = section(text, 2, "需求分析") ?? "";
35
- assertHeadings(analysis, 3, ["明确需求", "推断需求", "待确认问题", "初步非目标"], errors, "需求分析");
36
- const facts = section(text, 2, "外部事实") ?? "";
37
- assertHeadings(facts, 3, ["知识库事实", "代码库事实"], errors, "外部事实");
38
-
39
- const questions = section(analysis, 3, "待确认问题") ?? "";
40
- if (status === "no-clarification-required" && /(?:\bQ-\d{3,}\b|\bBR-\d{3,}\b|\bP[012]\b|[??])/.test(questions)) errors.push("no-clarification-required analysis cannot contain question markers, priorities, branches, or question sentences.");
41
- if (status === "ready-for-clarification" && !/(?:\bP[012]\b|[??])/.test(questions)) errors.push("ready-for-clarification analysis must contain at least one prioritized or explicit question.");
42
- if (/\bAC-(?:FE|BE)-\d{3,}\b|^Given[::]|^When[::]|^Then[::]/m.test(text)) errors.push("Product Analysis must use acceptance concerns, not formal AC IDs or Given/When/Then blocks.");
43
- if (/^##\s+(?:\d+[.、]?\s*)?(?:用户角色|初步验收标准汇总)\s*$/m.test(text)) errors.push("Product Analysis must not contain standalone 用户角色 or 初步验收标准汇总 sections.");
44
-
45
- function validateDomain(domain) {
46
- const frontend = domain === "frontend";
47
- const title = frontend ? "前端" : "后端";
48
- const prefix = frontend ? "FE-US" : "BE-US";
49
- const storyHeading = `初步${title}用户故事`;
50
- const specHeading = `初步${title}输出规范`;
51
- assertHeadings(text, 2, [storyHeading, specHeading], errors, "Product Analysis");
52
- const stories = storyBlocks(section(text, 2, storyHeading), prefix);
53
- const specs = outputSpecBlocks(section(text, 2, specHeading), prefix);
54
- if (!stories.length) errors.push(`${storyHeading} must contain at least one ${prefix}-* story.`);
55
- const storyFields = frontend ? ["角色", "目标", "价值", "入口", "验收关注点"] : ["系统能力", "使用方", "业务价值", "触发方式", "验收关注点"];
56
- const specFields = frontend ? ["页面/组件", "展示内容", "交互动作", "UI 状态", "表单校验", "权限可见性", "边界处理"] : ["输入语义", "输出语义", "数据读写", "权限规则", "业务规则", "安全要求", "幂等与并发", "错误与边界"];
57
- for (const story of stories) {
58
- for (const name of storyFields) if (!field(story.text, name)) errors.push(`${story.id} is missing story field ${name}.`);
59
- if (!frontend && !/^(?:API|定时任务|事件|消息|内部调用|数据迁移)(?:$|[、,,/])/i.test(field(story.text, "触发方式") ?? "")) errors.push(`${story.id} 触发方式 is invalid.`);
60
- }
61
- for (const spec of specs) for (const name of specFields) if (!field(spec.text, name)) errors.push(`${spec.id} output specification is missing field ${name}.`);
62
- validateStorySpecCoverage(stories, specs, errors, `${title} scope`);
63
- }
64
-
65
- if (scope === "frontend" || scope === "both") validateDomain("frontend");
66
- if (scope === "backend" || scope === "both") validateDomain("backend");
67
- if (scope === "frontend" && /(?:^##\s+.*后端|^###\s+BE-US-)/m.test(text)) errors.push("frontend scope must not contain backend sections or stories.");
68
- if (scope === "backend" && /(?:^##\s+.*前端|^###\s+FE-US-)/m.test(text)) errors.push("backend scope must not contain frontend sections or stories.");
69
- printResult("Product Analysis", path, errors);
1
+ #!/usr/bin/env node
2
+
3
+ import { assertHeadings, field, loadMarkdown, metadata, outputSpecBlocks, printResult, section, storyBlocks, validateArtifactLocation, validateStorySpecCoverage } from "./validation-helpers.mjs";
4
+
5
+ const args = process.argv.slice(2);
6
+ const fileArg = args[0];
7
+ const targetIndex = args.indexOf("--target");
8
+ const expectedTarget = targetIndex >= 0 ? args[targetIndex + 1] : undefined;
9
+ if (!fileArg) {
10
+ console.error("Usage: node validate-product-analysis.mjs <product-analysis.md> [--target frontend|backend|both]");
11
+ process.exit(2);
12
+ }
13
+
14
+ let artifact;
15
+ try { artifact = loadMarkdown(fileArg, "Product Analysis"); }
16
+ catch (error) { console.error(error.message); process.exit(2); }
17
+
18
+ const { path, text } = artifact;
19
+ const errors = [];
20
+ validateArtifactLocation(artifact, errors, "product-analysis.md");
21
+ const scope = metadata(text, "analysis_scope");
22
+ const status = metadata(text, "analysis_status");
23
+
24
+ if (metadata(text, "artifact_version") !== "3.0") errors.push("artifact_version must be 3.0.");
25
+ if (metadata(text, "artifact_type") !== "product-analysis") errors.push("artifact_type must be product-analysis.");
26
+ if (!["frontend", "backend", "both"].includes(scope)) errors.push("analysis_scope must be frontend, backend, or both.");
27
+ if (targetIndex >= 0 && !["frontend", "backend", "both"].includes(expectedTarget)) errors.push("--target must be frontend, backend, or both.");
28
+ else if (expectedTarget && scope !== expectedTarget) errors.push(`analysis_scope ${scope} does not match requested target ${expectedTarget}.`);
29
+ if (!["ready-for-clarification", "no-clarification-required"].includes(status)) errors.push("analysis_status must be ready-for-clarification or no-clarification-required.");
30
+ if (!metadata(text, "source_requirement")) errors.push("source_requirement is required.");
31
+ if (!metadata(text, "repository_root")) errors.push("repository_root is required; use none when absent.");
32
+
33
+ assertHeadings(text, 2, ["原始需求", "需求概述", "业务目标", "需求分析", "外部事实"], errors, "Product Analysis");
34
+ const analysis = section(text, 2, "需求分析") ?? "";
35
+ assertHeadings(analysis, 3, ["明确需求", "推断需求", "待确认问题", "初步非目标"], errors, "需求分析");
36
+ const facts = section(text, 2, "外部事实") ?? "";
37
+ assertHeadings(facts, 3, ["知识库事实", "代码库事实"], errors, "外部事实");
38
+
39
+ const questions = section(analysis, 3, "待确认问题") ?? "";
40
+ if (status === "no-clarification-required" && /(?:\bQ-\d{3,}\b|\bBR-\d{3,}\b|\bP[012]\b|[??])/.test(questions)) errors.push("no-clarification-required analysis cannot contain question markers, priorities, branches, or question sentences.");
41
+ if (status === "ready-for-clarification" && !/(?:\bP[012]\b|[??])/.test(questions)) errors.push("ready-for-clarification analysis must contain at least one prioritized or explicit question.");
42
+ if (/\bAC-(?:FE|BE)-\d{3,}\b|^Given[::]|^When[::]|^Then[::]/m.test(text)) errors.push("Product Analysis must use acceptance concerns, not formal AC IDs or Given/When/Then blocks.");
43
+ if (/^##\s+(?:\d+[.、]?\s*)?(?:用户角色|初步验收标准汇总)\s*$/m.test(text)) errors.push("Product Analysis must not contain standalone 用户角色 or 初步验收标准汇总 sections.");
44
+
45
+ function validateDomain(domain) {
46
+ const frontend = domain === "frontend";
47
+ const title = frontend ? "前端" : "后端";
48
+ const prefix = frontend ? "FE-US" : "BE-US";
49
+ const storyHeading = `初步${title}用户故事`;
50
+ const specHeading = `初步${title}输出规范`;
51
+ assertHeadings(text, 2, [storyHeading, specHeading], errors, "Product Analysis");
52
+ const stories = storyBlocks(section(text, 2, storyHeading), prefix);
53
+ const specs = outputSpecBlocks(section(text, 2, specHeading), prefix);
54
+ if (!stories.length) errors.push(`${storyHeading} must contain at least one ${prefix}-* story.`);
55
+ const storyFields = frontend ? ["角色", "目标", "价值", "入口", "验收关注点"] : ["系统能力", "使用方", "业务价值", "触发方式", "验收关注点"];
56
+ const specFields = frontend ? ["页面/组件", "展示内容", "交互动作", "UI 状态", "表单校验", "权限可见性", "边界处理"] : ["输入语义", "输出语义", "数据读写", "权限规则", "业务规则", "安全要求", "幂等与并发", "错误与边界"];
57
+ for (const story of stories) {
58
+ for (const name of storyFields) if (!field(story.text, name)) errors.push(`${story.id} is missing story field ${name}.`);
59
+ if (!frontend && !/^(?:API|定时任务|事件|消息|内部调用|数据迁移)(?:$|[、,,/])/i.test(field(story.text, "触发方式") ?? "")) errors.push(`${story.id} 触发方式 is invalid.`);
60
+ }
61
+ for (const spec of specs) for (const name of specFields) if (!field(spec.text, name)) errors.push(`${spec.id} output specification is missing field ${name}.`);
62
+ validateStorySpecCoverage(stories, specs, errors, `${title} scope`);
63
+ }
64
+
65
+ if (scope === "frontend" || scope === "both") validateDomain("frontend");
66
+ if (scope === "backend" || scope === "both") validateDomain("backend");
67
+ if (scope === "frontend" && /(?:^##\s+.*后端|^###\s+BE-US-)/m.test(text)) errors.push("frontend scope must not contain backend sections or stories.");
68
+ if (scope === "backend" && /(?:^##\s+.*前端|^###\s+FE-US-)/m.test(text)) errors.push("backend scope must not contain frontend sections or stories.");
69
+ printResult("Product Analysis", path, errors);