@tea-agent/loop-agent 0.25.6 → 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 (162) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +1020 -1006
  3. package/bin/loop-agent.js +21 -21
  4. package/dist/commands/cursor-prompt.js +6 -6
  5. package/dist/commands/loop-benchmark.js +11 -11
  6. package/dist/commands/pi-reuse-benchmark.js +16 -16
  7. package/dist/executors/dag-pi-executor.js +26 -20
  8. package/dist/executors/model-routing.js +34 -18
  9. package/dist/governance/manifest-types.js +33 -5
  10. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  11. package/dist/task/task-demand-routing.js +3 -1
  12. package/dist/worker/console/chat/model-resolver.js +15 -3
  13. package/dist/worker/observe/static/constants.js +3 -2
  14. package/dist/worker/observe/static/copy.js +67 -67
  15. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  16. package/dist/worker/observe/static/dag-layout.js +83 -83
  17. package/dist/worker/observe/static/dag-model.js +1 -0
  18. package/dist/worker/observe/static/dom.js +220 -220
  19. package/dist/worker/observe/static/relations.js +133 -133
  20. package/dist/worker/observe/static/router.js +93 -93
  21. package/dist/worker/observe/static/run-processing.js +148 -148
  22. package/dist/worker/observe/static/styles.css +182 -42
  23. package/dist/worker/observe/static/views/batch.js +227 -227
  24. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  25. package/dist/worker/observe/static/views/failures.js +143 -143
  26. package/dist/worker/observe/static/views/feature.js +492 -492
  27. package/dist/worker/observe/static/views/run.js +453 -453
  28. package/dist/worker/observe/static/views/shell.js +7 -7
  29. package/dist/worker/observe/static/views/timeline.js +163 -163
  30. package/dist/workflows/dag/canvas-observer.js +275 -275
  31. package/dist/workflows/dag/lifecycle.js +40 -30
  32. package/dist/workflows/dag/node-execution.js +13 -0
  33. package/dist/workflows/dag/types.js +59 -19
  34. package/docs/skills/README.md +7 -7
  35. package/docs/templates/adr.md +60 -60
  36. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  37. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  38. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  39. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  40. package/docs/templates/agent-dag-report.schema.json +473 -473
  41. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  42. package/docs/templates/backend-test-result.schema.json +99 -99
  43. package/docs/templates/feature-spec.md +53 -53
  44. package/docs/templates/frontend-design-contract.md +42 -42
  45. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
  46. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
  47. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
  48. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
  49. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
  50. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
  51. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
  52. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
  53. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
  54. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
  55. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
  56. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
  57. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
  58. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
  59. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
  60. package/docs/templates/frontend-eval/metrics.md +138 -138
  61. package/docs/templates/frontend-eval/smoke-targets.md +53 -53
  62. package/docs/templates/frontend-task-constraints.md +35 -35
  63. package/docs/templates/frontend-task-requirement.md +70 -70
  64. package/docs/templates/harness.schema.json +29 -7
  65. package/docs/templates/init-evolution-review.md +35 -35
  66. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  67. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  68. package/docs/templates/knowledge-sync-dag.json +178 -178
  69. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  70. package/docs/templates/product-line/closeout.yaml +9 -9
  71. package/docs/templates/product-line/design.md +13 -13
  72. package/docs/templates/product-line/links.md +10 -10
  73. package/docs/templates/product-line/requirement.md +17 -17
  74. package/docs/templates/product-line/test-plan.md +7 -7
  75. package/docs/templates/production-readiness-checklist.md +57 -57
  76. package/docs/templates/project-start-checklist.md +9 -9
  77. package/docs/templates/qa-report.md +48 -48
  78. package/docs/templates/sprint-contract.md +29 -29
  79. package/docs/templates/worker-dogfood-evidence.md +80 -80
  80. package/docs/templates/worker-dogfood-setup.md +68 -68
  81. package/harness.json +1 -2
  82. package/package.json +1 -1
  83. package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
  84. package/scripts/kb-graph-incremental-prepare.mjs +386 -386
  85. package/scripts/kb-graph-materialize.mjs +105 -105
  86. package/scripts/kb-graph-promote.mjs +164 -164
  87. package/scripts/kb-query.mjs +554 -554
  88. package/skills/ai-engineering-context/SKILL.md +48 -48
  89. package/skills/analyze-product-dependencies/SKILL.md +67 -67
  90. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -4
  91. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -30
  92. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -28
  93. package/skills/analyze-product-dependencies/references/example.md +76 -76
  94. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -35
  95. package/skills/analyze-product-dependencies/references/input-contract.md +11 -11
  96. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -61
  97. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -267
  98. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -101
  99. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -142
  100. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -76
  101. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -146
  102. package/skills/analyze-product-requirements/SKILL.md +90 -90
  103. package/skills/analyze-product-requirements/agents/openai.yaml +4 -4
  104. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -91
  105. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -56
  106. package/skills/analyze-product-requirements/references/example.md +86 -86
  107. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -66
  108. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -32
  109. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -33
  110. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -35
  111. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -193
  112. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -69
  113. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -97
  114. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -98
  115. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -156
  116. package/skills/browser-tools/browser-content.js +103 -103
  117. package/skills/browser-tools/browser-cookies.js +35 -35
  118. package/skills/browser-tools/browser-eval.js +53 -53
  119. package/skills/browser-tools/browser-hn-scraper.js +108 -108
  120. package/skills/browser-tools/browser-nav.js +44 -44
  121. package/skills/browser-tools/browser-pick.js +162 -162
  122. package/skills/browser-tools/browser-screenshot.js +34 -34
  123. package/skills/browser-tools/browser-start.js +86 -86
  124. package/skills/browser-tools/package-lock.json +2556 -2556
  125. package/skills/browser-tools/package.json +19 -19
  126. package/skills/code-review-core/SKILL.md +20 -20
  127. package/skills/codebase-scout/SKILL.md +19 -19
  128. package/skills/grill-me/SKILL.md +10 -10
  129. package/skills/loop-agent/references/README.md +67 -67
  130. package/skills/loop-agent/references/docs-converge.md +126 -126
  131. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  132. package/skills/loop-agent/references/learned/README.md +21 -21
  133. package/skills/loop-agent/references/long-running-loop.md +57 -57
  134. package/skills/loop-agent/references/model-routing.md +2 -0
  135. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  136. package/skills/loop-agent/references/pi-prompt.md +23 -23
  137. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  138. package/skills/playwright-cli/SKILL.md +420 -420
  139. package/skills/playwright-cli/references/element-attributes.md +23 -23
  140. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  141. package/skills/playwright-cli/references/request-mocking.md +87 -87
  142. package/skills/playwright-cli/references/running-code.md +241 -241
  143. package/skills/playwright-cli/references/session-management.md +225 -225
  144. package/skills/playwright-cli/references/storage-state.md +275 -275
  145. package/skills/playwright-cli/references/test-generation.md +433 -433
  146. package/skills/playwright-cli/references/tracing.md +139 -139
  147. package/skills/playwright-cli/references/video-recording.md +143 -143
  148. package/skills/requesting-code-review/SKILL.md +101 -101
  149. package/skills/requesting-code-review/code-reviewer.md +168 -168
  150. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  151. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  152. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  153. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  154. package/skills/systematic-debugging/find-polluter.sh +63 -63
  155. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  156. package/skills/systematic-debugging/test-academic.md +14 -14
  157. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  158. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  159. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  160. package/skills/using-git-worktrees/SKILL.md +215 -215
  161. package/skills/verification-before-completion/SKILL.md +154 -154
  162. 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);