@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,97 +1,97 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync, readFileSync } from "node:fs";
4
- import { dirname, resolve } from "node:path";
5
- import { acceptanceBlocks, assertHeadings, containsUnresolvedBlockingPriority, field, loadMarkdown, metadata, outputSpecBlocks, printResult, section, storyBlocks, validateAcceptance, validateArtifactLocation, validateStorySpecCoverage } from "./validation-helpers.mjs";
6
-
7
- const args = process.argv.slice(2);
8
- const allowPending = args.includes("--allow-pending");
9
- const targetIndex = args.indexOf("--target");
10
- const expectedTarget = targetIndex >= 0 ? args[targetIndex + 1] : undefined;
11
- const fileArg = args.find((arg, index) => !arg.startsWith("--") && (targetIndex < 0 || index !== targetIndex + 1));
12
- if (!fileArg) {
13
- console.error("Usage: node validate-product-requirement.mjs <product-requirement.md> [--target frontend|backend|both] [--allow-pending]");
14
- process.exit(2);
15
- }
16
-
17
- let artifact;
18
- try { artifact = loadMarkdown(fileArg, "Product Requirement"); }
19
- catch (error) { console.error(error.message); process.exit(2); }
20
-
21
- const { path, text } = artifact;
22
- const errors = [];
23
- validateArtifactLocation(artifact, errors, "product-requirement.md");
24
- const scope = metadata(text, "analysis_scope");
25
- const status = metadata(text, "requirement_status");
26
- if (metadata(text, "artifact_version") !== "3.0") errors.push("artifact_version must be 3.0.");
27
- if (metadata(text, "artifact_type") !== "product-requirement") errors.push("artifact_type must be product-requirement.");
28
- if (!["frontend", "backend", "both"].includes(scope)) errors.push("analysis_scope must be frontend, backend, or both.");
29
- if (targetIndex >= 0 && !["frontend", "backend", "both"].includes(expectedTarget)) errors.push("--target must be frontend, backend, or both.");
30
- else if (expectedTarget && scope !== expectedTarget) errors.push(`analysis_scope ${scope} does not match requested target ${expectedTarget}.`);
31
- if (!["pending", "complete"].includes(status)) errors.push("requirement_status must be pending or complete.");
32
- if (!allowPending && status !== "complete") errors.push("Final Product Requirement must be complete.");
33
-
34
- function sourcePath(name) {
35
- const value = metadata(text, name);
36
- if (!value || ["inline", "none"].includes(value)) return value;
37
- return resolve(dirname(path), value);
38
- }
39
- for (const name of ["source_requirement", "source_product_analysis", "source_clarification"]) {
40
- const value = sourcePath(name);
41
- if (!value) errors.push(`${name} is required.`);
42
- else if (!["inline", "none"].includes(value) && !existsSync(value)) errors.push(`${name} does not exist: ${value}`);
43
- }
44
- for (const name of ["source_product_analysis", "source_clarification"]) {
45
- const value = sourcePath(name);
46
- if (value && !["inline", "none"].includes(value) && dirname(value) !== dirname(path)) errors.push(`${name} must reference an artifact in the same requirement directory.`);
47
- }
48
-
49
- assertHeadings(text, 2, ["需求概述", "业务目标", "需求范围", "业务规则", "决策追溯"], errors, "Product Requirement");
50
- const range = section(text, 2, "需求范围") ?? "";
51
- assertHeadings(range, 3, ["已确认需求", "非目标", "默认假设", "未决事项"], errors, "需求范围");
52
- if (status === "complete" && containsUnresolvedBlockingPriority(section(range, 3, "未决事项") ?? "")) errors.push("Complete Product Requirement cannot contain unresolved P0/P1 items.");
53
- if (/^##\s+(?:\d+[.、]?\s*)?(?:用户角色|验收标准汇总)\s*$/m.test(text)) errors.push("Product Requirement must not contain standalone 用户角色 or 验收标准汇总 sections.");
54
-
55
- function validateDomain(domain) {
56
- const frontend = domain === "frontend";
57
- const title = frontend ? "前端" : "后端";
58
- const prefix = frontend ? "FE-US" : "BE-US";
59
- const acPrefix = frontend ? "AC-FE" : "AC-BE";
60
- const storyHeading = `${title}用户故事`;
61
- const specHeading = `${title}输出规范`;
62
- assertHeadings(text, 2, [storyHeading, specHeading], errors, "Product Requirement");
63
- const stories = storyBlocks(section(text, 2, storyHeading), prefix);
64
- const specs = outputSpecBlocks(section(text, 2, specHeading), prefix);
65
- if (!stories.length) errors.push(`${storyHeading} must contain at least one ${prefix}-* story.`);
66
- validateStorySpecCoverage(stories, specs, errors, `${title} scope`);
67
- const storyFields = frontend ? ["角色", "目标", "价值", "入口", "验收标准"] : ["系统能力", "使用方", "业务价值", "触发方式", "验收标准"];
68
- const specFields = frontend ? ["页面/组件", "展示内容", "交互动作", "UI 状态", "表单校验", "权限可见性", "边界处理"] : ["输入语义", "输出语义", "数据读写", "权限规则", "业务规则", "安全要求", "幂等与并发", "错误与边界"];
69
- for (const story of stories) {
70
- for (const name of storyFields) if (!field(story.text, name)) errors.push(`${story.id} is missing story field ${name}.`);
71
- if (!frontend && !/^(?:API|定时任务|事件|消息|内部调用|数据迁移)(?:$|[、,,/])/i.test(field(story.text, "触发方式") ?? "")) errors.push(`${story.id} 触发方式 is invalid.`);
72
- const spec = specs.find((item) => item.id === story.id);
73
- if (!spec) continue;
74
- for (const name of specFields) if (!field(spec.text, name)) errors.push(`${spec.id} output specification is missing field ${name}.`);
75
- const criteria = acceptanceBlocks(spec, acPrefix);
76
- if (!criteria.length) errors.push(`${spec.id} output specification must contain at least one ${acPrefix}-* acceptance criterion.`);
77
- for (const item of criteria) validateAcceptance(item.text, item.id, errors);
78
- const referenced = [...(field(story.text, "验收标准") ?? "").matchAll(new RegExp(`${acPrefix}-\\d{3,}`, "g"))].map((item) => item[0]).sort();
79
- const actual = criteria.map((item) => item.id).sort();
80
- if (referenced.join() !== actual.join()) errors.push(`${story.id} acceptance references must match its output specification criteria.`);
81
- }
82
- }
83
-
84
- if (scope === "frontend" || scope === "both") validateDomain("frontend");
85
- if (scope === "backend" || scope === "both") validateDomain("backend");
86
- if (scope === "frontend" && /(?:^##\s+.*后端|^###\s+BE-US-)/m.test(text)) errors.push("frontend scope must not contain backend sections or stories.");
87
- if (scope === "backend" && /(?:^##\s+.*前端|^###\s+FE-US-)/m.test(text)) errors.push("backend scope must not contain frontend sections or stories.");
88
-
89
- const clarificationSource = sourcePath("source_clarification");
90
- if (status === "complete" && clarificationSource && !["inline", "none"].includes(clarificationSource) && existsSync(clarificationSource)) {
91
- const clarification = readFileSync(clarificationSource, "utf8");
92
- if (metadata(clarification, "clarification_status") !== "complete") errors.push("Complete Product Requirement requires complete source clarification.");
93
- const markers = [...clarification.matchAll(/^-\s*决策标记[::]\s*(DEC-Q-\d{3,})/gm)].map((item) => item[1]);
94
- for (const marker of markers) if (!text.includes(marker)) errors.push(`${marker} from source clarification is missing from Product Requirement.`);
95
- }
96
-
97
- printResult("Product Requirement", path, errors);
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { acceptanceBlocks, assertHeadings, containsUnresolvedBlockingPriority, field, loadMarkdown, metadata, outputSpecBlocks, printResult, section, storyBlocks, validateAcceptance, validateArtifactLocation, validateStorySpecCoverage } from "./validation-helpers.mjs";
6
+
7
+ const args = process.argv.slice(2);
8
+ const allowPending = args.includes("--allow-pending");
9
+ const targetIndex = args.indexOf("--target");
10
+ const expectedTarget = targetIndex >= 0 ? args[targetIndex + 1] : undefined;
11
+ const fileArg = args.find((arg, index) => !arg.startsWith("--") && (targetIndex < 0 || index !== targetIndex + 1));
12
+ if (!fileArg) {
13
+ console.error("Usage: node validate-product-requirement.mjs <product-requirement.md> [--target frontend|backend|both] [--allow-pending]");
14
+ process.exit(2);
15
+ }
16
+
17
+ let artifact;
18
+ try { artifact = loadMarkdown(fileArg, "Product Requirement"); }
19
+ catch (error) { console.error(error.message); process.exit(2); }
20
+
21
+ const { path, text } = artifact;
22
+ const errors = [];
23
+ validateArtifactLocation(artifact, errors, "product-requirement.md");
24
+ const scope = metadata(text, "analysis_scope");
25
+ const status = metadata(text, "requirement_status");
26
+ if (metadata(text, "artifact_version") !== "3.0") errors.push("artifact_version must be 3.0.");
27
+ if (metadata(text, "artifact_type") !== "product-requirement") errors.push("artifact_type must be product-requirement.");
28
+ if (!["frontend", "backend", "both"].includes(scope)) errors.push("analysis_scope must be frontend, backend, or both.");
29
+ if (targetIndex >= 0 && !["frontend", "backend", "both"].includes(expectedTarget)) errors.push("--target must be frontend, backend, or both.");
30
+ else if (expectedTarget && scope !== expectedTarget) errors.push(`analysis_scope ${scope} does not match requested target ${expectedTarget}.`);
31
+ if (!["pending", "complete"].includes(status)) errors.push("requirement_status must be pending or complete.");
32
+ if (!allowPending && status !== "complete") errors.push("Final Product Requirement must be complete.");
33
+
34
+ function sourcePath(name) {
35
+ const value = metadata(text, name);
36
+ if (!value || ["inline", "none"].includes(value)) return value;
37
+ return resolve(dirname(path), value);
38
+ }
39
+ for (const name of ["source_requirement", "source_product_analysis", "source_clarification"]) {
40
+ const value = sourcePath(name);
41
+ if (!value) errors.push(`${name} is required.`);
42
+ else if (!["inline", "none"].includes(value) && !existsSync(value)) errors.push(`${name} does not exist: ${value}`);
43
+ }
44
+ for (const name of ["source_product_analysis", "source_clarification"]) {
45
+ const value = sourcePath(name);
46
+ if (value && !["inline", "none"].includes(value) && dirname(value) !== dirname(path)) errors.push(`${name} must reference an artifact in the same requirement directory.`);
47
+ }
48
+
49
+ assertHeadings(text, 2, ["需求概述", "业务目标", "需求范围", "业务规则", "决策追溯"], errors, "Product Requirement");
50
+ const range = section(text, 2, "需求范围") ?? "";
51
+ assertHeadings(range, 3, ["已确认需求", "非目标", "默认假设", "未决事项"], errors, "需求范围");
52
+ if (status === "complete" && containsUnresolvedBlockingPriority(section(range, 3, "未决事项") ?? "")) errors.push("Complete Product Requirement cannot contain unresolved P0/P1 items.");
53
+ if (/^##\s+(?:\d+[.、]?\s*)?(?:用户角色|验收标准汇总)\s*$/m.test(text)) errors.push("Product Requirement must not contain standalone 用户角色 or 验收标准汇总 sections.");
54
+
55
+ function validateDomain(domain) {
56
+ const frontend = domain === "frontend";
57
+ const title = frontend ? "前端" : "后端";
58
+ const prefix = frontend ? "FE-US" : "BE-US";
59
+ const acPrefix = frontend ? "AC-FE" : "AC-BE";
60
+ const storyHeading = `${title}用户故事`;
61
+ const specHeading = `${title}输出规范`;
62
+ assertHeadings(text, 2, [storyHeading, specHeading], errors, "Product Requirement");
63
+ const stories = storyBlocks(section(text, 2, storyHeading), prefix);
64
+ const specs = outputSpecBlocks(section(text, 2, specHeading), prefix);
65
+ if (!stories.length) errors.push(`${storyHeading} must contain at least one ${prefix}-* story.`);
66
+ validateStorySpecCoverage(stories, specs, errors, `${title} scope`);
67
+ const storyFields = frontend ? ["角色", "目标", "价值", "入口", "验收标准"] : ["系统能力", "使用方", "业务价值", "触发方式", "验收标准"];
68
+ const specFields = frontend ? ["页面/组件", "展示内容", "交互动作", "UI 状态", "表单校验", "权限可见性", "边界处理"] : ["输入语义", "输出语义", "数据读写", "权限规则", "业务规则", "安全要求", "幂等与并发", "错误与边界"];
69
+ for (const story of stories) {
70
+ for (const name of storyFields) if (!field(story.text, name)) errors.push(`${story.id} is missing story field ${name}.`);
71
+ if (!frontend && !/^(?:API|定时任务|事件|消息|内部调用|数据迁移)(?:$|[、,,/])/i.test(field(story.text, "触发方式") ?? "")) errors.push(`${story.id} 触发方式 is invalid.`);
72
+ const spec = specs.find((item) => item.id === story.id);
73
+ if (!spec) continue;
74
+ for (const name of specFields) if (!field(spec.text, name)) errors.push(`${spec.id} output specification is missing field ${name}.`);
75
+ const criteria = acceptanceBlocks(spec, acPrefix);
76
+ if (!criteria.length) errors.push(`${spec.id} output specification must contain at least one ${acPrefix}-* acceptance criterion.`);
77
+ for (const item of criteria) validateAcceptance(item.text, item.id, errors);
78
+ const referenced = [...(field(story.text, "验收标准") ?? "").matchAll(new RegExp(`${acPrefix}-\\d{3,}`, "g"))].map((item) => item[0]).sort();
79
+ const actual = criteria.map((item) => item.id).sort();
80
+ if (referenced.join() !== actual.join()) errors.push(`${story.id} acceptance references must match its output specification criteria.`);
81
+ }
82
+ }
83
+
84
+ if (scope === "frontend" || scope === "both") validateDomain("frontend");
85
+ if (scope === "backend" || scope === "both") validateDomain("backend");
86
+ if (scope === "frontend" && /(?:^##\s+.*后端|^###\s+BE-US-)/m.test(text)) errors.push("frontend scope must not contain backend sections or stories.");
87
+ if (scope === "backend" && /(?:^##\s+.*前端|^###\s+FE-US-)/m.test(text)) errors.push("backend scope must not contain frontend sections or stories.");
88
+
89
+ const clarificationSource = sourcePath("source_clarification");
90
+ if (status === "complete" && clarificationSource && !["inline", "none"].includes(clarificationSource) && existsSync(clarificationSource)) {
91
+ const clarification = readFileSync(clarificationSource, "utf8");
92
+ if (metadata(clarification, "clarification_status") !== "complete") errors.push("Complete Product Requirement requires complete source clarification.");
93
+ const markers = [...clarification.matchAll(/^-\s*决策标记[::]\s*(DEC-Q-\d{3,})/gm)].map((item) => item[1]);
94
+ for (const marker of markers) if (!text.includes(marker)) errors.push(`${marker} from source clarification is missing from Product Requirement.`);
95
+ }
96
+
97
+ printResult("Product Requirement", path, errors);
@@ -1,98 +1,98 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync } from "node:fs";
4
- import { dirname, resolve } from "node:path";
5
- import { assertHeadings, canonical, field, headings, loadMarkdown, metadata, printResult, section, validateArtifactLocation } from "./validation-helpers.mjs";
6
-
7
- const args = process.argv.slice(2);
8
- const allowPending = args.includes("--allow-pending");
9
- const positional = args.filter((arg) => !arg.startsWith("--"));
10
- if (positional.length < 3) {
11
- console.error("Usage: node validate-requirement-clarification.mjs <product-analysis.md> <requirement-clarification.md> <product-requirement.md> [--allow-pending]");
12
- process.exit(2);
13
- }
14
-
15
- let analysis, clarification, requirement;
16
- try {
17
- analysis = loadMarkdown(positional[0], "Product Analysis");
18
- clarification = loadMarkdown(positional[1], "Requirement Clarification");
19
- requirement = loadMarkdown(positional[2], "Product Requirement");
20
- } catch (error) { console.error(error.message); process.exit(2); }
21
-
22
- const errors = [];
23
- validateArtifactLocation(clarification, errors, "requirement-clarification.md");
24
- const scope = metadata(clarification.text, "analysis_scope");
25
- const status = metadata(clarification.text, "clarification_status");
26
- const clarificationRounds = Number(metadata(clarification.text, "clarification_rounds"));
27
- if (metadata(clarification.text, "artifact_version") !== "3.0") errors.push("artifact_version must be 3.0.");
28
- if (metadata(clarification.text, "artifact_type") !== "requirement-clarification") errors.push("artifact_type must be requirement-clarification.");
29
- if (!["frontend", "backend", "both"].includes(scope)) errors.push("analysis_scope must be frontend, backend, or both.");
30
- if (!["pending", "complete"].includes(status)) errors.push("clarification_status must be pending or complete.");
31
- if (!Number.isInteger(clarificationRounds) || clarificationRounds < 0 || clarificationRounds > 3) errors.push("clarification_rounds must be an integer from 0 to 3.");
32
- if (!allowPending && status !== "complete") errors.push("Final Requirement Clarification must be complete.");
33
- if (scope !== metadata(analysis.text, "analysis_scope")) errors.push("analysis_scope must match Product Analysis.");
34
-
35
- const resolveRef = (value) => value ? resolve(dirname(clarification.path), value) : "";
36
- const source = metadata(clarification.text, "source_product_analysis");
37
- const target = metadata(clarification.text, "target_product_requirement");
38
- if (resolveRef(source) !== analysis.path) errors.push("source_product_analysis must reference the supplied Product Analysis.");
39
- if (resolveRef(target) !== requirement.path) errors.push("target_product_requirement must reference the supplied Product Requirement.");
40
- if (source && dirname(resolveRef(source)) !== dirname(clarification.path)) errors.push("source_product_analysis must be in the same requirement directory.");
41
- if (target && dirname(resolveRef(target)) !== dirname(clarification.path)) errors.push("target_product_requirement must be in the same requirement directory.");
42
- if (source && !existsSync(resolveRef(source))) errors.push(`source_product_analysis does not exist: ${resolveRef(source)}`);
43
- if (target && !existsSync(resolveRef(target))) errors.push(`target_product_requirement does not exist: ${resolveRef(target)}`);
44
-
45
- const analysisStatus = metadata(analysis.text, "analysis_status");
46
- const questionBlocks = headings(clarification.text, 3).filter((item) => /^Q-\d{3,}\s+/.test(item.title));
47
-
48
- if (!questionBlocks.length) {
49
- if (clarificationRounds !== 0) errors.push("Compact clarification must set clarification_rounds to 0.");
50
- if (analysisStatus !== "no-clarification-required") errors.push("Compact clarification is allowed only when Product Analysis is no-clarification-required.");
51
- const required = ["澄清结论", "来源", "合并结果"];
52
- assertHeadings(clarification.text, 2, required, errors, "Compact Requirement Clarification");
53
- for (const heading of headings(clarification.text, 2)) if (!required.includes(canonical(heading.title))) errors.push(`Compact Requirement Clarification must not contain section ${canonical(heading.title)}.`);
54
- if (!/状态[::]\s*no-clarification-required/.test(section(clarification.text, 2, "澄清结论") ?? "")) errors.push("Compact clarification must declare no-clarification-required.");
55
- if (/\b(?:BR|Q|DEC-Q)-\d{3,}\b/.test(clarification.text)) errors.push("Compact clarification must not fabricate BR, Q, or DEC-Q markers.");
56
- } else {
57
- if (clarificationRounds < 1 || clarificationRounds > 3) errors.push("Question-based clarification must set clarification_rounds from 1 to 3.");
58
- if (analysisStatus !== "ready-for-clarification") errors.push("Question records require Product Analysis status ready-for-clarification.");
59
- assertHeadings(clarification.text, 2, ["澄清来源", "决策分支", "问题记录", "决策索引"], errors, "Requirement Clarification");
60
- const branchSection = section(clarification.text, 2, "决策分支") ?? "";
61
- const branchIds = [...new Set(branchSection.match(/\bBR-\d{3,}\b/g) ?? [])];
62
- if (branchIds.length < 3 || branchIds.length > 6) errors.push("Question-based clarification must define 3–6 top-level BR-* branches.");
63
- if (status === "complete") {
64
- const unresolved = branchSection.split(/\r?\n/).filter((line) => /\|\s*BR-\d{3,}\s*\|/.test(line) && !/\|\s*resolved\s*\|\s*$/.test(line));
65
- if (unresolved.length) errors.push("Complete clarification requires every branch to be resolved.");
66
- }
67
- const requiredFields = ["澄清轮次", "分支", "优先级", "影响范围", "推荐答案", "推荐理由", "用户回答", "最终决策", "决策来源", "状态", "决策标记", "目标位置"];
68
- const markers = new Set();
69
- for (const block of questionBlocks) {
70
- const id = block.title.match(/^(Q-\d{3,})/)?.[1];
71
- for (const name of requiredFields) if (!field(block.text, name)) errors.push(`${id} is missing field ${name}.`);
72
- const branch = field(block.text, "分支");
73
- const round = Number(field(block.text, "澄清轮次"));
74
- const priority = field(block.text, "优先级");
75
- const impactScope = field(block.text, "影响范围");
76
- const answer = field(block.text, "用户回答");
77
- const decisionStatus = field(block.text, "状态");
78
- const decisionSource = field(block.text, "决策来源");
79
- if (!branchIds.includes(branch)) errors.push(`${id} 分支 must reference a defined BR-* branch.`);
80
- if (!Number.isInteger(round) || round < 1 || round > clarificationRounds) errors.push(`${id} 澄清轮次 must be between 1 and clarification_rounds.`);
81
- if (!["P0", "P1", "P2"].includes(priority)) errors.push(`${id} 优先级 must be P0, P1, or P2.`);
82
- if (!["common", "frontend", "backend", "both"].includes(impactScope)) errors.push(`${id} 影响范围 is invalid.`);
83
- if (!["confirmed", "default-confirmed", "pending-blocking", "pending-non-blocking"].includes(decisionStatus)) errors.push(`${id} 状态 is invalid.`);
84
- if (!["user", "source-requirement", "code-evidence", "confirmed-default"].includes(decisionSource)) errors.push(`${id} 决策来源 is invalid.`);
85
- const marker = field(block.text, "决策标记");
86
- if (marker !== `DEC-${id}`) errors.push(`${id} 决策标记 must be DEC-${id}.`);
87
- if (markers.has(marker)) errors.push(`Duplicate decision marker: ${marker}.`);
88
- markers.add(marker);
89
- if (decisionSource === "code-evidence" && !field(block.text, "代码证据")) errors.push(`${id} code-evidence decision requires 代码证据.`);
90
- if (decisionStatus === "pending-non-blocking" && !field(block.text, "未确认影响")) errors.push(`${id} pending-non-blocking decision requires 未确认影响.`);
91
- if (status === "complete" && ["P0", "P1"].includes(priority) && (decisionStatus !== "confirmed" || decisionSource !== "user" || /^(?:未回答|待确认|无)$/.test(answer ?? ""))) errors.push(`${id} ${priority} must contain an explicit user confirmation.`);
92
- if (status === "complete" && marker && !requirement.text.includes(marker)) errors.push(`${marker} is missing from Product Requirement decision traceability.`);
93
- if (!(section(clarification.text, 2, "决策索引") ?? "").includes(marker ?? "__missing__")) errors.push(`${marker ?? id} is missing from 决策索引.`);
94
- }
95
- }
96
-
97
- if (status === "complete" && metadata(requirement.text, "requirement_status") !== "complete") errors.push("Complete clarification requires a complete Product Requirement.");
98
- printResult("Requirement Clarification", clarification.path, errors);
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { assertHeadings, canonical, field, headings, loadMarkdown, metadata, printResult, section, validateArtifactLocation } from "./validation-helpers.mjs";
6
+
7
+ const args = process.argv.slice(2);
8
+ const allowPending = args.includes("--allow-pending");
9
+ const positional = args.filter((arg) => !arg.startsWith("--"));
10
+ if (positional.length < 3) {
11
+ console.error("Usage: node validate-requirement-clarification.mjs <product-analysis.md> <requirement-clarification.md> <product-requirement.md> [--allow-pending]");
12
+ process.exit(2);
13
+ }
14
+
15
+ let analysis, clarification, requirement;
16
+ try {
17
+ analysis = loadMarkdown(positional[0], "Product Analysis");
18
+ clarification = loadMarkdown(positional[1], "Requirement Clarification");
19
+ requirement = loadMarkdown(positional[2], "Product Requirement");
20
+ } catch (error) { console.error(error.message); process.exit(2); }
21
+
22
+ const errors = [];
23
+ validateArtifactLocation(clarification, errors, "requirement-clarification.md");
24
+ const scope = metadata(clarification.text, "analysis_scope");
25
+ const status = metadata(clarification.text, "clarification_status");
26
+ const clarificationRounds = Number(metadata(clarification.text, "clarification_rounds"));
27
+ if (metadata(clarification.text, "artifact_version") !== "3.0") errors.push("artifact_version must be 3.0.");
28
+ if (metadata(clarification.text, "artifact_type") !== "requirement-clarification") errors.push("artifact_type must be requirement-clarification.");
29
+ if (!["frontend", "backend", "both"].includes(scope)) errors.push("analysis_scope must be frontend, backend, or both.");
30
+ if (!["pending", "complete"].includes(status)) errors.push("clarification_status must be pending or complete.");
31
+ if (!Number.isInteger(clarificationRounds) || clarificationRounds < 0 || clarificationRounds > 3) errors.push("clarification_rounds must be an integer from 0 to 3.");
32
+ if (!allowPending && status !== "complete") errors.push("Final Requirement Clarification must be complete.");
33
+ if (scope !== metadata(analysis.text, "analysis_scope")) errors.push("analysis_scope must match Product Analysis.");
34
+
35
+ const resolveRef = (value) => value ? resolve(dirname(clarification.path), value) : "";
36
+ const source = metadata(clarification.text, "source_product_analysis");
37
+ const target = metadata(clarification.text, "target_product_requirement");
38
+ if (resolveRef(source) !== analysis.path) errors.push("source_product_analysis must reference the supplied Product Analysis.");
39
+ if (resolveRef(target) !== requirement.path) errors.push("target_product_requirement must reference the supplied Product Requirement.");
40
+ if (source && dirname(resolveRef(source)) !== dirname(clarification.path)) errors.push("source_product_analysis must be in the same requirement directory.");
41
+ if (target && dirname(resolveRef(target)) !== dirname(clarification.path)) errors.push("target_product_requirement must be in the same requirement directory.");
42
+ if (source && !existsSync(resolveRef(source))) errors.push(`source_product_analysis does not exist: ${resolveRef(source)}`);
43
+ if (target && !existsSync(resolveRef(target))) errors.push(`target_product_requirement does not exist: ${resolveRef(target)}`);
44
+
45
+ const analysisStatus = metadata(analysis.text, "analysis_status");
46
+ const questionBlocks = headings(clarification.text, 3).filter((item) => /^Q-\d{3,}\s+/.test(item.title));
47
+
48
+ if (!questionBlocks.length) {
49
+ if (clarificationRounds !== 0) errors.push("Compact clarification must set clarification_rounds to 0.");
50
+ if (analysisStatus !== "no-clarification-required") errors.push("Compact clarification is allowed only when Product Analysis is no-clarification-required.");
51
+ const required = ["澄清结论", "来源", "合并结果"];
52
+ assertHeadings(clarification.text, 2, required, errors, "Compact Requirement Clarification");
53
+ for (const heading of headings(clarification.text, 2)) if (!required.includes(canonical(heading.title))) errors.push(`Compact Requirement Clarification must not contain section ${canonical(heading.title)}.`);
54
+ if (!/状态[::]\s*no-clarification-required/.test(section(clarification.text, 2, "澄清结论") ?? "")) errors.push("Compact clarification must declare no-clarification-required.");
55
+ if (/\b(?:BR|Q|DEC-Q)-\d{3,}\b/.test(clarification.text)) errors.push("Compact clarification must not fabricate BR, Q, or DEC-Q markers.");
56
+ } else {
57
+ if (clarificationRounds < 1 || clarificationRounds > 3) errors.push("Question-based clarification must set clarification_rounds from 1 to 3.");
58
+ if (analysisStatus !== "ready-for-clarification") errors.push("Question records require Product Analysis status ready-for-clarification.");
59
+ assertHeadings(clarification.text, 2, ["澄清来源", "决策分支", "问题记录", "决策索引"], errors, "Requirement Clarification");
60
+ const branchSection = section(clarification.text, 2, "决策分支") ?? "";
61
+ const branchIds = [...new Set(branchSection.match(/\bBR-\d{3,}\b/g) ?? [])];
62
+ if (branchIds.length < 3 || branchIds.length > 6) errors.push("Question-based clarification must define 3–6 top-level BR-* branches.");
63
+ if (status === "complete") {
64
+ const unresolved = branchSection.split(/\r?\n/).filter((line) => /\|\s*BR-\d{3,}\s*\|/.test(line) && !/\|\s*resolved\s*\|\s*$/.test(line));
65
+ if (unresolved.length) errors.push("Complete clarification requires every branch to be resolved.");
66
+ }
67
+ const requiredFields = ["澄清轮次", "分支", "优先级", "影响范围", "推荐答案", "推荐理由", "用户回答", "最终决策", "决策来源", "状态", "决策标记", "目标位置"];
68
+ const markers = new Set();
69
+ for (const block of questionBlocks) {
70
+ const id = block.title.match(/^(Q-\d{3,})/)?.[1];
71
+ for (const name of requiredFields) if (!field(block.text, name)) errors.push(`${id} is missing field ${name}.`);
72
+ const branch = field(block.text, "分支");
73
+ const round = Number(field(block.text, "澄清轮次"));
74
+ const priority = field(block.text, "优先级");
75
+ const impactScope = field(block.text, "影响范围");
76
+ const answer = field(block.text, "用户回答");
77
+ const decisionStatus = field(block.text, "状态");
78
+ const decisionSource = field(block.text, "决策来源");
79
+ if (!branchIds.includes(branch)) errors.push(`${id} 分支 must reference a defined BR-* branch.`);
80
+ if (!Number.isInteger(round) || round < 1 || round > clarificationRounds) errors.push(`${id} 澄清轮次 must be between 1 and clarification_rounds.`);
81
+ if (!["P0", "P1", "P2"].includes(priority)) errors.push(`${id} 优先级 must be P0, P1, or P2.`);
82
+ if (!["common", "frontend", "backend", "both"].includes(impactScope)) errors.push(`${id} 影响范围 is invalid.`);
83
+ if (!["confirmed", "default-confirmed", "pending-blocking", "pending-non-blocking"].includes(decisionStatus)) errors.push(`${id} 状态 is invalid.`);
84
+ if (!["user", "source-requirement", "code-evidence", "confirmed-default"].includes(decisionSource)) errors.push(`${id} 决策来源 is invalid.`);
85
+ const marker = field(block.text, "决策标记");
86
+ if (marker !== `DEC-${id}`) errors.push(`${id} 决策标记 must be DEC-${id}.`);
87
+ if (markers.has(marker)) errors.push(`Duplicate decision marker: ${marker}.`);
88
+ markers.add(marker);
89
+ if (decisionSource === "code-evidence" && !field(block.text, "代码证据")) errors.push(`${id} code-evidence decision requires 代码证据.`);
90
+ if (decisionStatus === "pending-non-blocking" && !field(block.text, "未确认影响")) errors.push(`${id} pending-non-blocking decision requires 未确认影响.`);
91
+ if (status === "complete" && ["P0", "P1"].includes(priority) && (decisionStatus !== "confirmed" || decisionSource !== "user" || /^(?:未回答|待确认|无)$/.test(answer ?? ""))) errors.push(`${id} ${priority} must contain an explicit user confirmation.`);
92
+ if (status === "complete" && marker && !requirement.text.includes(marker)) errors.push(`${marker} is missing from Product Requirement decision traceability.`);
93
+ if (!(section(clarification.text, 2, "决策索引") ?? "").includes(marker ?? "__missing__")) errors.push(`${marker ?? id} is missing from 决策索引.`);
94
+ }
95
+ }
96
+
97
+ if (status === "complete" && metadata(requirement.text, "requirement_status") !== "complete") errors.push("Complete clarification requires a complete Product Requirement.");
98
+ printResult("Requirement Clarification", clarification.path, errors);