@openprd/cli 0.1.9 → 0.1.10

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 (134) hide show
  1. package/.openprd/benchmarks/index.md +15 -1
  2. package/.openprd/config.yaml +10 -0
  3. package/.openprd/design/README.md +47 -0
  4. package/.openprd/design/README_EN.md +25 -0
  5. package/.openprd/design/active/asset-spec.md +15 -0
  6. package/.openprd/design/active/direction-plan.md +7 -0
  7. package/.openprd/design/active/facts-sheet.md +13 -0
  8. package/.openprd/design/active/image-preflight.md +9 -0
  9. package/.openprd/design/active/selected-direction.md +11 -0
  10. package/.openprd/design/anti-slop.md +16 -0
  11. package/.openprd/design/assets/dark-mesh.svg +14 -0
  12. package/.openprd/design/assets/paper-grid.svg +14 -0
  13. package/.openprd/design/assets/surface-presets.md +21 -0
  14. package/.openprd/design/assets/workbench-grid.svg +16 -0
  15. package/.openprd/design/checklists/ui-quality-gate.md +22 -0
  16. package/.openprd/design/components/component-catalog.md +42 -0
  17. package/.openprd/design/layouts/layout-catalog.json +45 -0
  18. package/.openprd/design/lenses/frontend-lenses.md +31 -0
  19. package/.openprd/design/recipes/content-experience.md +26 -0
  20. package/.openprd/design/recipes/operational-tool.md +25 -0
  21. package/.openprd/design/recipes/product-launch.md +26 -0
  22. package/.openprd/design/templates/README.md +36 -0
  23. package/.openprd/design/templates/README_EN.md +31 -0
  24. package/.openprd/design/templates/content-home.html +415 -0
  25. package/.openprd/design/templates/ops-dashboard.html +340 -0
  26. package/.openprd/design/templates/product-launch.html +360 -0
  27. package/.openprd/design/themes/theme-catalog.json +77 -0
  28. package/.openprd/engagements/active/prd.md +111 -100
  29. package/.openprd/schema/prd.schema.yaml +10 -0
  30. package/.openprd/templates/base/intake.md +4 -0
  31. package/.openprd/templates/base/prd.md +16 -6
  32. package/AGENTS.md +10 -7
  33. package/README.md +26 -28
  34. package/README_EN.md +3 -3
  35. package/package.json +41 -2
  36. package/scripts/openprd-codex-isolated-worker.mjs +799 -0
  37. package/skills/openprd-benchmark-router/SKILL.md +4 -0
  38. package/skills/openprd-frontend-design/SKILL.md +161 -0
  39. package/skills/openprd-frontend-design/references/design-asset-contract.md +34 -0
  40. package/skills/openprd-frontend-design/references/direction-engine.md +46 -0
  41. package/skills/openprd-harness/SKILL.md +37 -20
  42. package/skills/openprd-learning-review/SKILL.md +2 -1
  43. package/skills/openprd-learning-review/references/style-packs/xianxia-cultivation.prompt.md +1 -1
  44. package/skills/openprd-quality/SKILL.md +5 -3
  45. package/skills/openprd-requirement-intake/SKILL.md +27 -5
  46. package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -3
  47. package/skills/openprd-requirement-intake/references/routing-rubric.md +9 -5
  48. package/skills/openprd-requirement-intake/references/startup-validation-lens.md +23 -0
  49. package/skills/openprd-router/SKILL.md +13 -5
  50. package/skills/openprd-shared/SKILL.md +38 -25
  51. package/src/agent-integration.js +120 -69
  52. package/src/brainstorm-artifacts.js +1657 -0
  53. package/src/brainstorm-presentation.js +223 -0
  54. package/src/brainstorm.js +603 -0
  55. package/src/cli/args.js +51 -3
  56. package/src/cli/doctor-print.js +34 -8
  57. package/src/cli/print.js +6 -0
  58. package/src/cli/quality-print.js +70 -0
  59. package/src/cli/run-print.js +43 -1
  60. package/src/cli/shared-print.js +33 -2
  61. package/src/cli/workflow-print.js +21 -0
  62. package/src/codex-hook-runner-template.mjs +838 -58
  63. package/src/design-starter-support.js +462 -0
  64. package/src/design-starter.js +1207 -0
  65. package/src/knowledge.js +232 -20
  66. package/src/learning-review.js +3 -3
  67. package/src/loop.js +839 -101
  68. package/src/openprd.js +86 -1
  69. package/src/openspec/change-validate.js +1 -0
  70. package/src/prd-core.js +18 -0
  71. package/src/quality-learning.js +50 -6
  72. package/src/quality.js +2 -2
  73. package/src/run-harness.js +191 -29
  74. package/src/self-update.js +301 -4
  75. package/src/standards.js +46 -5
  76. package/src/visual-prepare.js +940 -0
  77. package/src/workspace-core.js +137 -0
  78. package/src/workspace-workflow.js +190 -7
  79. package/.openprd/benchmarks/evidence/milvus-io-ai-code-review-gets-better-when-models-debate-claude-vs-gemini-vs-code.md +0 -14
  80. package/.openprd/benchmarks/evidence/nolanlawson-com-using-ai-to-write-better-code-more-slowly.md +0 -14
  81. package/.openprd/discovery/config.json +0 -35
  82. package/.openprd/engagements/active/flows.md +0 -35
  83. package/.openprd/engagements/active/handoff.md +0 -16
  84. package/.openprd/engagements/active/review.html +0 -61
  85. package/.openprd/engagements/active/roles.md +0 -22
  86. package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +0 -23
  87. package/.openprd/engagements/work-units/wu-20260602113956-a99b5b88.json +0 -18
  88. package/.openprd/engagements/work-units/wu-20260602122244-78656aaf.json +0 -18
  89. package/.openprd/engagements/work-units/wu-20260602122442-e96489e2.json +0 -18
  90. package/.openprd/engagements/work-units/wu-20260602132835-695429e8.json +0 -18
  91. package/.openprd/exports/.gitkeep +0 -0
  92. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/candidate.json +0 -78
  93. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/diagnostic-report.json +0 -129
  94. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/root-cause-candidates.json +0 -41
  95. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/timeline.json +0 -14
  96. package/.openprd/knowledge/drafts/openprd-experience-diagnostic-candidate-turn-1780116203372-5f266a79e968c758/SKILL.md +0 -49
  97. package/.openprd/knowledge/index.json +0 -47
  98. package/.openprd/reviews/v0001.html +0 -1322
  99. package/.openprd/reviews/v0002.html +0 -1150
  100. package/.openprd/reviews/v0003.html +0 -1150
  101. package/.openprd/reviews/v0004.html +0 -1150
  102. package/.openprd/reviews/v0005.html +0 -1150
  103. package/.openprd/sessions/.gitkeep +0 -0
  104. package/.openprd/state/changes.json +0 -27
  105. package/.openprd/state/current.json +0 -505
  106. package/.openprd/state/release-ledger.json +0 -387
  107. package/.openprd/state/version-index.json +0 -67
  108. package/.openprd/state/versions/.gitkeep +0 -0
  109. package/.openprd/state/versions/v0001.json +0 -121
  110. package/.openprd/state/versions/v0001.md +0 -161
  111. package/.openprd/state/versions/v0002.json +0 -264
  112. package/.openprd/state/versions/v0002.md +0 -183
  113. package/.openprd/state/versions/v0003.json +0 -269
  114. package/.openprd/state/versions/v0003.md +0 -188
  115. package/.openprd/state/versions/v0004.json +0 -274
  116. package/.openprd/state/versions/v0004.md +0 -193
  117. package/.openprd/state/versions/v0005.json +0 -299
  118. package/.openprd/state/versions/v0005.md +0 -189
  119. package/docs/assets/openprd-capability-overview-en.png +0 -0
  120. package/docs/assets/openprd-capability-overview-zh.png +0 -0
  121. package/docs/assets/openprd-learning-html.png +0 -0
  122. package/docs/assets/openprd-quality-html.png +0 -0
  123. package/docs/assets/openprd-requirement-routing-en.png +0 -0
  124. package/docs/assets/openprd-requirement-routing-en.svg +0 -102
  125. package/docs/assets/openprd-requirement-routing-zh-refined.png +0 -0
  126. package/docs/assets/openprd-requirement-routing-zh.png +0 -0
  127. package/docs/assets/openprd-requirement-routing-zh.svg +0 -102
  128. package/docs/assets/openprd-review-html.png +0 -0
  129. package/docs/assets/openprd-scenario-overview.png +0 -0
  130. package/docs/assets/openprd-scenario-overview.svg +0 -114
  131. package/docs/assets/openprd-self-evolving-mechanisms-en.png +0 -0
  132. package/docs/assets/openprd-self-evolving-mechanisms-zh.png +0 -0
  133. package/docs/assets/openprd-visual-compare-case-study-en.png +0 -0
  134. package/docs/assets/openprd-visual-compare-case-study-zh.png +0 -0
@@ -20,6 +20,28 @@ const CORE_TEMPLATE_FILES = [
20
20
  'README.md',
21
21
  'README_EN.md',
22
22
  'config.yaml',
23
+ 'design/README.md',
24
+ 'design/README_EN.md',
25
+ 'design/anti-slop.md',
26
+ 'design/lenses/frontend-lenses.md',
27
+ 'design/themes/theme-catalog.json',
28
+ 'design/layouts/layout-catalog.json',
29
+ 'design/components/component-catalog.md',
30
+ 'design/recipes/product-launch.md',
31
+ 'design/recipes/content-experience.md',
32
+ 'design/recipes/operational-tool.md',
33
+ 'design/checklists/ui-quality-gate.md',
34
+ 'design/assets/surface-presets.md',
35
+ 'design/templates/README.md',
36
+ 'design/templates/README_EN.md',
37
+ 'design/templates/content-home.html',
38
+ 'design/templates/product-launch.html',
39
+ 'design/templates/ops-dashboard.html',
40
+ 'design/active/facts-sheet.md',
41
+ 'design/active/asset-spec.md',
42
+ 'design/active/image-preflight.md',
43
+ 'design/active/direction-plan.md',
44
+ 'design/active/selected-direction.md',
23
45
  'schema/prd.schema.yaml',
24
46
  'schema/diagram-architecture.schema.yaml',
25
47
  'schema/diagram-product-flow.schema.yaml',
@@ -60,6 +82,28 @@ const CORE_TEMPLATE_FILES = [
60
82
  const WORKSPACE_SEED_REFRESH_FILES = [
61
83
  'README.md',
62
84
  'README_EN.md',
85
+ 'design/README.md',
86
+ 'design/README_EN.md',
87
+ 'design/anti-slop.md',
88
+ 'design/lenses/frontend-lenses.md',
89
+ 'design/themes/theme-catalog.json',
90
+ 'design/layouts/layout-catalog.json',
91
+ 'design/components/component-catalog.md',
92
+ 'design/recipes/product-launch.md',
93
+ 'design/recipes/content-experience.md',
94
+ 'design/recipes/operational-tool.md',
95
+ 'design/checklists/ui-quality-gate.md',
96
+ 'design/assets/surface-presets.md',
97
+ 'design/templates/README.md',
98
+ 'design/templates/README_EN.md',
99
+ 'design/templates/content-home.html',
100
+ 'design/templates/product-launch.html',
101
+ 'design/templates/ops-dashboard.html',
102
+ 'design/active/facts-sheet.md',
103
+ 'design/active/asset-spec.md',
104
+ 'design/active/image-preflight.md',
105
+ 'design/active/direction-plan.md',
106
+ 'design/active/selected-direction.md',
63
107
  'schema/prd.schema.yaml',
64
108
  'schema/diagram-architecture.schema.yaml',
65
109
  'schema/diagram-product-flow.schema.yaml',
@@ -87,6 +131,9 @@ const WORKSPACE_SEED_REFRESH_FILES = [
87
131
  ];
88
132
  const WORKSPACE_SEED_COPY_IGNORE = new Set([
89
133
  'artifacts',
134
+ 'benchmarks/.DS_Store',
135
+ 'benchmarks/evidence',
136
+ 'benchmarks/inbox',
90
137
  'discovery',
91
138
  'growth',
92
139
  'harness',
@@ -99,6 +146,8 @@ const WORKSPACE_SEED_COPY_IGNORE = new Set([
99
146
  'engagements/active/architecture-diagram.html',
100
147
  'engagements/active/architecture-diagram.json',
101
148
  'engagements/active/architecture-diagram.mmd',
149
+ 'engagements/active/brainstorm.html',
150
+ 'engagements/active/brainstorm.json',
102
151
  'engagements/active/decision-log.md',
103
152
  'engagements/active/product-flow-diagram.html',
104
153
  'engagements/active/product-flow-diagram.json',
@@ -326,6 +375,7 @@ function buildWorkflowTaskGraph(snapshot = null, analysis = null, options = {})
326
375
  const isSynthesized = Boolean(snapshot?.digest || prdVersion > 0);
327
376
  const isFrozen = snapshot?.status === 'frozen';
328
377
  const isHandedOff = snapshot?.status === 'handed_off';
378
+ const brainstormDone = Boolean(snapshot?.brainstormMeta?.generatedAt || snapshot?.brainstormMeta?.artifactId);
329
379
  const isInterviewComplete = Boolean(
330
380
  snapshot?.sections?.problem?.problemStatement
331
381
  && snapshot?.sections?.users?.primaryUsers?.length
@@ -347,6 +397,13 @@ function buildWorkflowTaskGraph(snapshot = null, analysis = null, options = {})
347
397
  const clarifyGateActive = Boolean(clarificationState?.shouldAskUser);
348
398
 
349
399
  const workflow = [
400
+ {
401
+ id: 'brainstorm',
402
+ label: 'brainstorm',
403
+ kind: 'workflow-step',
404
+ status: brainstormDone ? 'done' : 'ready',
405
+ dependsOn: [],
406
+ },
350
407
  {
351
408
  id: 'clarify',
352
409
  label: 'clarify',
@@ -435,6 +492,13 @@ function buildWorkflowTaskGraph(snapshot = null, analysis = null, options = {})
435
492
  status: 'ready',
436
493
  dependsOn: [],
437
494
  },
495
+ {
496
+ id: 'brainstorm-file',
497
+ label: 'brainstorm.html',
498
+ kind: 'artifact',
499
+ status: brainstormDone ? 'done' : 'ready',
500
+ dependsOn: ['brainstorm'],
501
+ },
438
502
  {
439
503
  id: 'open-questions',
440
504
  label: 'open-questions.md',
@@ -560,6 +624,7 @@ function buildWorkflowTaskGraph(snapshot = null, analysis = null, options = {})
560
624
  })),
561
625
  ],
562
626
  edges: [
627
+ { from: 'brainstorm', to: 'brainstorm-file', relation: 'produces' },
563
628
  { from: 'clarify', to: 'classify', relation: 'unblocks' },
564
629
  { from: 'classify', to: 'interview', relation: 'enables' },
565
630
  { from: 'interview', to: 'synthesize', relation: 'enables' },
@@ -667,6 +732,15 @@ async function ensureWorkspaceSkeleton(projectRoot, options = {}) {
667
732
  await fs.mkdir(cjoin(workspaceRoot, 'artifacts', 'archive'), { recursive: true });
668
733
  await fs.mkdir(cjoin(workspaceRoot, 'learning', 'archive'), { recursive: true });
669
734
  await fs.mkdir(cjoin(workspaceRoot, 'quality', 'reports'), { recursive: true });
735
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'lenses'), { recursive: true });
736
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'themes'), { recursive: true });
737
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'layouts'), { recursive: true });
738
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'components'), { recursive: true });
739
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'recipes'), { recursive: true });
740
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'checklists'), { recursive: true });
741
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'assets'), { recursive: true });
742
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'templates'), { recursive: true });
743
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'active'), { recursive: true });
670
744
  await fs.mkdir(cjoin(workspaceRoot, 'growth'), { recursive: true });
671
745
  await fs.mkdir(cjoin(workspaceRoot, 'knowledge', 'incidents'), { recursive: true });
672
746
  await fs.mkdir(cjoin(workspaceRoot, 'knowledge', 'patterns'), { recursive: true });
@@ -735,6 +809,17 @@ async function migrateWorkspaceConfig(projectRoot, changes) {
735
809
  ...(seed.quality ?? {}),
736
810
  ...(current.quality ?? {}),
737
811
  },
812
+ design: {
813
+ ...(seed.design ?? {}),
814
+ ...(current.design ?? {}),
815
+ enabled: current.design?.enabled ?? seed.design?.enabled ?? true,
816
+ frameworkRoot: current.design?.frameworkRoot ?? seed.design?.frameworkRoot ?? '.openprd/design',
817
+ templateRoot: current.design?.templateRoot ?? seed.design?.templateRoot ?? '.openprd/design/templates',
818
+ activeArtifacts: {
819
+ ...(seed.design?.activeArtifacts ?? {}),
820
+ ...(current.design?.activeArtifacts ?? {}),
821
+ },
822
+ },
738
823
  supportedProductTypes: seed.supportedProductTypes,
739
824
  templateInheritance: seed.templateInheritance,
740
825
  workflow: seed.workflow,
@@ -918,6 +1003,15 @@ async function migrateWorkspaceSkeleton(projectRoot, options = {}) {
918
1003
  await fs.mkdir(cjoin(workspaceRoot, 'artifacts', 'archive'), { recursive: true });
919
1004
  await fs.mkdir(cjoin(workspaceRoot, 'learning', 'archive'), { recursive: true });
920
1005
  await fs.mkdir(cjoin(workspaceRoot, 'quality', 'reports'), { recursive: true });
1006
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'lenses'), { recursive: true });
1007
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'themes'), { recursive: true });
1008
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'layouts'), { recursive: true });
1009
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'components'), { recursive: true });
1010
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'recipes'), { recursive: true });
1011
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'checklists'), { recursive: true });
1012
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'assets'), { recursive: true });
1013
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'templates'), { recursive: true });
1014
+ await fs.mkdir(cjoin(workspaceRoot, 'design', 'active'), { recursive: true });
921
1015
  await fs.mkdir(cjoin(workspaceRoot, 'growth'), { recursive: true });
922
1016
  await fs.mkdir(cjoin(workspaceRoot, 'knowledge', 'incidents'), { recursive: true });
923
1017
  await fs.mkdir(cjoin(workspaceRoot, 'knowledge', 'patterns'), { recursive: true });
@@ -1020,6 +1114,8 @@ async function loadWorkspace(projectRoot, options = {}) {
1020
1114
  activeFlows: cjoin(workspaceRoot, 'engagements', 'active', 'flows.md'),
1021
1115
  activeRoles: cjoin(workspaceRoot, 'engagements', 'active', 'roles.md'),
1022
1116
  activeHandoff: cjoin(workspaceRoot, 'engagements', 'active', 'handoff.md'),
1117
+ activeBrainstormHtml: cjoin(workspaceRoot, 'engagements', 'active', 'brainstorm.html'),
1118
+ activeBrainstormState: cjoin(workspaceRoot, 'engagements', 'active', 'brainstorm.json'),
1023
1119
  activeReviewHtml: cjoin(workspaceRoot, 'engagements', 'active', 'review.html'),
1024
1120
  reviewsDir: cjoin(workspaceRoot, 'reviews'),
1025
1121
  artifactsActiveDir: cjoin(workspaceRoot, 'artifacts', 'active'),
@@ -1033,6 +1129,24 @@ async function loadWorkspace(projectRoot, options = {}) {
1033
1129
  qualityReportsDir: cjoin(workspaceRoot, 'quality', 'reports'),
1034
1130
  qualityReportIndex: cjoin(workspaceRoot, 'quality', 'reports', 'index.json'),
1035
1131
  qualityReportLatest: cjoin(workspaceRoot, 'quality', 'reports', 'latest.json'),
1132
+ designRoot: cjoin(workspaceRoot, 'design'),
1133
+ designReadme: cjoin(workspaceRoot, 'design', 'README.md'),
1134
+ designLenses: cjoin(workspaceRoot, 'design', 'lenses', 'frontend-lenses.md'),
1135
+ designThemes: cjoin(workspaceRoot, 'design', 'themes', 'theme-catalog.json'),
1136
+ designLayouts: cjoin(workspaceRoot, 'design', 'layouts', 'layout-catalog.json'),
1137
+ designComponents: cjoin(workspaceRoot, 'design', 'components', 'component-catalog.md'),
1138
+ designChecklist: cjoin(workspaceRoot, 'design', 'checklists', 'ui-quality-gate.md'),
1139
+ designAntiSlop: cjoin(workspaceRoot, 'design', 'anti-slop.md'),
1140
+ designTemplatesRoot: cjoin(workspaceRoot, 'design', 'templates'),
1141
+ designTemplatesReadme: cjoin(workspaceRoot, 'design', 'templates', 'README.md'),
1142
+ designContentHomeTemplate: cjoin(workspaceRoot, 'design', 'templates', 'content-home.html'),
1143
+ designProductLaunchTemplate: cjoin(workspaceRoot, 'design', 'templates', 'product-launch.html'),
1144
+ designOpsDashboardTemplate: cjoin(workspaceRoot, 'design', 'templates', 'ops-dashboard.html'),
1145
+ designFactsSheet: cjoin(workspaceRoot, 'design', 'active', 'facts-sheet.md'),
1146
+ designAssetSpec: cjoin(workspaceRoot, 'design', 'active', 'asset-spec.md'),
1147
+ designImagePreflight: cjoin(workspaceRoot, 'design', 'active', 'image-preflight.md'),
1148
+ designDirectionPlan: cjoin(workspaceRoot, 'design', 'active', 'direction-plan.md'),
1149
+ designSelectedDirection: cjoin(workspaceRoot, 'design', 'active', 'selected-direction.md'),
1036
1150
  growthDir: cjoin(workspaceRoot, 'growth'),
1037
1151
  growthCandidates: cjoin(workspaceRoot, 'growth', 'candidates.jsonl'),
1038
1152
  growthAccepted: cjoin(workspaceRoot, 'growth', 'accepted.json'),
@@ -1122,6 +1236,13 @@ const USER_CLARIFICATION_PATHS = new Set([
1122
1236
  'problem.problemStatement',
1123
1237
  'problem.whyNow',
1124
1238
  'users.primaryUsers',
1239
+ 'validation.community',
1240
+ 'validation.seedUsers',
1241
+ 'validation.currentAlternative',
1242
+ 'validation.manualPath',
1243
+ 'validation.commitmentSignals',
1244
+ 'validation.firstValidationStep',
1245
+ 'validation.defaultAlivePlan',
1125
1246
  'goals.goals',
1126
1247
  'goals.successMetrics',
1127
1248
  'scope.inScope',
@@ -1162,6 +1283,13 @@ const FIELD_PATH_TO_STATE_KEY = {
1162
1283
  'users.primaryUsers': 'primaryUsers',
1163
1284
  'users.secondaryUsers': 'secondaryUsers',
1164
1285
  'users.stakeholders': 'stakeholders',
1286
+ 'validation.community': 'community',
1287
+ 'validation.seedUsers': 'seedUsers',
1288
+ 'validation.currentAlternative': 'currentAlternative',
1289
+ 'validation.manualPath': 'manualPath',
1290
+ 'validation.commitmentSignals': 'commitmentSignals',
1291
+ 'validation.firstValidationStep': 'firstValidationStep',
1292
+ 'validation.defaultAlivePlan': 'defaultAlivePlan',
1165
1293
  'goals.goals': 'goals',
1166
1294
  'goals.successMetrics': 'successMetrics',
1167
1295
  'goals.acceptanceGoals': 'acceptanceGoals',
@@ -1598,6 +1726,11 @@ function coerceCapturedValue(pathString, rawValue, append = false) {
1598
1726
  'users.primaryUsers',
1599
1727
  'users.secondaryUsers',
1600
1728
  'users.stakeholders',
1729
+ 'validation.community',
1730
+ 'validation.seedUsers',
1731
+ 'validation.manualPath',
1732
+ 'validation.commitmentSignals',
1733
+ 'validation.defaultAlivePlan',
1601
1734
  'goals.goals',
1602
1735
  'goals.successMetrics',
1603
1736
  'goals.acceptanceGoals',
@@ -1867,8 +2000,11 @@ function buildClarificationPlan(snapshot, analysis) {
1867
2000
  const mustAsk = analysis.missingFields.filter((field) => USER_CLARIFICATION_PATHS.has(field.path));
1868
2001
  const derived = analysis.missingFields.filter((field) => !USER_CLARIFICATION_PATHS.has(field.path));
1869
2002
  const kickoffQuestions = [
2003
+ { id: 'problem.problemStatement', label: '核心问题', prompt: '我们要解决什么问题?' },
1870
2004
  { id: 'project-overview', label: '项目轮廓', prompt: '这件事主要是给谁用的,他们会在什么场景下最先感受到价值?' },
2005
+ { id: 'current-alternative', label: '当前替代', prompt: '他们现在主要靠什么办法、流程或替代品在解决这个问题?' },
1871
2006
  { id: 'first-slice', label: '首版切片', prompt: '如果先做第一版,最值得先让用户完成什么关键动作?' },
2007
+ { id: 'validation-loop', label: '验证闭环', prompt: '如果先不做完整产品,最容易先找谁验证、先怎么手工交付、什么真实承诺最能证明值得继续?' },
1872
2008
  { id: 'guardrails', label: '保护项', prompt: '这轮明确先不做什么,或者哪些既有体验、流程和业务结果不能被影响?' },
1873
2009
  ];
1874
2010
  return {
@@ -2003,6 +2139,7 @@ export {
2003
2139
  coerceCapturedValue,
2004
2140
  computeWorkspaceDigest,
2005
2141
  CORE_TEMPLATE_FILES,
2142
+ USER_CLARIFICATION_PATHS,
2006
2143
  deriveGateLabels,
2007
2144
  detectWorkspaceScenario,
2008
2145
  ensureWorkspaceSkeleton,
@@ -11,12 +11,16 @@ import { assertReviewPresentationReady, getReviewPresentationGate } from './revi
11
11
  import { syncSessionBindingFromReview, syncSessionBindingFromSnapshot } from './session-binding.js';
12
12
  import { timestamp } from './time.js';
13
13
  import { generateWorkUnitId, normalizeWorkUnitId, readWorkUnitBinding, resolveTargetRoot, writeWorkUnitBinding } from './work-unit.js';
14
- import { appendDecision, appendOpenQuestions, appendProgress, appendWorkflowEvent, buildClarificationPlan, buildClarificationState, buildCurrentStateSnapshot, buildWorkflowTaskGraph, CAPTURE_SOURCES, coerceCapturedValue, deriveGateLabels, detectWorkspaceScenario, extractMarkdownSection, FIELD_PATH_TO_STATE_KEY, isSupportedProductType, loadCurrentLaneSnapshot, loadLatestVersionSnapshot, loadWorkspace, normalizeVersionId, persistWorkspaceCurrentState, readActiveRequirementLane, readVersionIndex, readVersionSnapshot, renderFlowDoc, renderHandoffDoc, renderRolesDoc, resolveActiveTemplatePack, resolveCurrentProductType, validateWorkspace, writeVersionIndex, writeVersionSnapshot } from './workspace-core.js';
14
+ import { appendDecision, appendOpenQuestions, appendProgress, appendWorkflowEvent, buildClarificationPlan, buildClarificationState, buildCurrentStateSnapshot, buildWorkflowTaskGraph, CAPTURE_SOURCES, coerceCapturedValue, deriveGateLabels, detectWorkspaceScenario, extractMarkdownSection, FIELD_PATH_TO_STATE_KEY, isSupportedProductType, loadCurrentLaneSnapshot, loadLatestVersionSnapshot, loadWorkspace, normalizeVersionId, persistWorkspaceCurrentState, readActiveRequirementLane, readVersionIndex, readVersionSnapshot, renderFlowDoc, renderHandoffDoc, renderRolesDoc, resolveActiveTemplatePack, resolveCurrentProductType, USER_CLARIFICATION_PATHS, validateWorkspace, writeVersionIndex, writeVersionSnapshot } from './workspace-core.js';
15
15
 
16
16
  function requirementGatePath(projectRoot) {
17
17
  return path.join(projectRoot, '.openprd', 'harness', 'requirement-gate.json');
18
18
  }
19
19
 
20
+ function normalizedText(value) {
21
+ return String(value ?? '').replace(/\s+/g, ' ').trim();
22
+ }
23
+
20
24
  const PRD_REVIEW_STATUSES = ['pending-confirmation', 'confirmed', 'needs-revision'];
21
25
  const CURRENT_SNAPSHOT_CACHE_KEYS = [
22
26
  'versionId',
@@ -30,6 +34,7 @@ const CURRENT_SNAPSHOT_CACHE_KEYS = [
30
34
  const REVIEW_PRESENTATION_RELEVANT_FIELD_PREFIXES = [
31
35
  'problem.',
32
36
  'users.',
37
+ 'validation.',
33
38
  'goals.',
34
39
  'scope.',
35
40
  'scenarios.',
@@ -56,6 +61,13 @@ const REVIEW_PRESENTATION_RELEVANT_OVERRIDE_KEYS = new Set([
56
61
  'primaryUsers',
57
62
  'secondaryUsers',
58
63
  'stakeholders',
64
+ 'community',
65
+ 'seedUsers',
66
+ 'currentAlternative',
67
+ 'manualPath',
68
+ 'commitmentSignals',
69
+ 'firstValidationStep',
70
+ 'defaultAlivePlan',
59
71
  'goals',
60
72
  'successMetrics',
61
73
  'acceptanceGoals',
@@ -109,6 +121,13 @@ const SYNTHESIZE_CONTENT_OVERRIDE_KEYS = new Set([
109
121
  'primaryUsers',
110
122
  'secondaryUsers',
111
123
  'stakeholders',
124
+ 'community',
125
+ 'seedUsers',
126
+ 'currentAlternative',
127
+ 'manualPath',
128
+ 'commitmentSignals',
129
+ 'firstValidationStep',
130
+ 'defaultAlivePlan',
112
131
  'goals',
113
132
  'successMetrics',
114
133
  'acceptanceGoals',
@@ -157,6 +176,29 @@ const SYNTHESIZE_CONTENT_OVERRIDE_KEYS = new Set([
157
176
  'evalPlan',
158
177
  ]);
159
178
 
179
+ function buildBrainstormSuggestion({ currentState, analysis, clarification, requirementGate }) {
180
+ const promptPreview = String(requirementGate?.promptPreview ?? '');
181
+ const explicit = /(脑暴|brainstorm|帮忙梳理|梳理一下|梳理下|先想清楚)/iu.test(promptPreview);
182
+ const missingRequiredFields = Number(analysis?.missingRequiredFields ?? 0);
183
+ const noProblemStatement = !normalizedText(currentState?.problemStatement);
184
+ const noPrimaryUsers = !Array.isArray(currentState?.primaryUsers) || currentState.primaryUsers.length === 0;
185
+ const shouldSuggest = explicit || (clarification?.shouldAskUser && missingRequiredFields >= 4) || (noProblemStatement && noPrimaryUsers && missingRequiredFields >= 3);
186
+ if (!shouldSuggest) {
187
+ return { recommended: false };
188
+ }
189
+ const reasons = [];
190
+ if (explicit) reasons.push('用户当前更像是在请求先梳理业务方向');
191
+ if (missingRequiredFields >= 4) reasons.push(`还有 ${missingRequiredFields} 个关键信息未确认`);
192
+ if (noProblemStatement) reasons.push('问题定义还不够稳定');
193
+ if (noPrimaryUsers) reasons.push('核心用户和场景还没锁定');
194
+ return {
195
+ recommended: true,
196
+ explicitTrigger: explicit,
197
+ reason: reasons.join(';'),
198
+ suggestedCommand: 'openprd brainstorm . --open',
199
+ };
200
+ }
201
+
160
202
  function normalizePrdReviewStatus(status) {
161
203
  return PRD_REVIEW_STATUSES.includes(status) ? status : 'pending-confirmation';
162
204
  }
@@ -192,6 +234,36 @@ function requirementGateReferenceTimestamp(gate) {
192
234
  return gate?.confirmedAt ?? gate?.updatedAt ?? gate?.openedAt ?? null;
193
235
  }
194
236
 
237
+ function requirementNextActionBlocksSynthesize(nextAction) {
238
+ return nextAction === 'clarify-user' || nextAction === 'classify' || nextAction === 'interview';
239
+ }
240
+
241
+ function gateHasClarificationConfirmation(gate) {
242
+ return Boolean(gate?.clarificationConfirmedAt || gate?.status === 'clarification-confirmed');
243
+ }
244
+
245
+ function latestConfirmedClarificationCaptureTimestamp(currentState) {
246
+ const timestamps = Object.entries(currentState?.captureMeta ?? {})
247
+ .filter(([field, entry]) => USER_CLARIFICATION_PATHS.has(field) && entry?.source === 'user-confirmed')
248
+ .map(([, entry]) => entry?.capturedAt)
249
+ .filter(Boolean)
250
+ .map(String);
251
+ return timestamps.length > 0 ? timestamps.sort().at(-1) : null;
252
+ }
253
+
254
+ function hasDerivedClarificationCaptureSince(currentState, gateAt) {
255
+ const reference = String(gateAt || '');
256
+ return Object.entries(currentState?.captureMeta ?? {}).some(([field, entry]) => {
257
+ if (!USER_CLARIFICATION_PATHS.has(field)) {
258
+ return false;
259
+ }
260
+ if (!entry?.capturedAt || String(entry.capturedAt) < reference) {
261
+ return false;
262
+ }
263
+ return Boolean(entry.source) && entry.source !== 'user-confirmed' && !NON_SEMANTIC_CAPTURE_SOURCES.has(entry.source);
264
+ });
265
+ }
266
+
195
267
  function gateQuestionLimit(gate, fallback) {
196
268
  const raw = Number(gate?.approvalPolicy?.maxClarificationQuestions);
197
269
  if (!Number.isFinite(raw) || raw <= 0) {
@@ -210,7 +282,23 @@ function ensureFreshRequirementStateForSynthesize({ gate, currentState, override
210
282
  }
211
283
  const capturedAt = latestCaptureTimestamp(currentState);
212
284
  if (capturedAt && String(capturedAt) >= String(gateAt)) {
213
- return;
285
+ if (!gateHasClarificationConfirmation(gate)) {
286
+ return;
287
+ }
288
+ const confirmedCaptureAt = latestConfirmedClarificationCaptureTimestamp(currentState);
289
+ const hasDerivedCapture = hasDerivedClarificationCaptureSince(currentState, gateAt);
290
+ if (confirmedCaptureAt && String(confirmedCaptureAt) >= String(gateAt) && !hasDerivedCapture) {
291
+ return;
292
+ }
293
+ throw new Error([
294
+ 'OpenPrd 已阻止 synthesize:当前需求摘要虽已确认,但 current.json 还没有把本轮确认内容稳定写回。',
295
+ hasDerivedCapture
296
+ ? '检测到本轮仍有 user clarification 字段被写成 agent-inferred / project-derived;请先改用 openprd capture 按 canonical 字段路径并以 user-confirmed 写回,再继续 synthesize。'
297
+ : '请先用 openprd capture 按 canonical 字段路径并以 user-confirmed 写回本轮确认事实,再继续 synthesize。',
298
+ hasSynthesizeContentOverrides(overrides)
299
+ ? 'partial override 不能替代这一步 requirement write-back。'
300
+ : null,
301
+ ].filter(Boolean).join(' '));
214
302
  }
215
303
  throw new Error([
216
304
  'OpenPrd 已阻止 synthesize:当前有新的需求入口,但 current.json 还没有记录本轮确认答案。',
@@ -220,6 +308,27 @@ function ensureFreshRequirementStateForSynthesize({ gate, currentState, override
220
308
  ].join(' '));
221
309
  }
222
310
 
311
+ async function ensureRequirementLaneReadyForSynthesize(ws) {
312
+ const guidance = await computeWorkspaceGuidance(ws, { questionLimit: 5 });
313
+ const missingRequiredFields = Number(guidance.analysis?.missingRequiredFields ?? 0);
314
+ const lacksProductType = !guidance.analysis?.productType;
315
+ const blockedByStructuredGap = guidance.nextAction === 'classify'
316
+ || guidance.nextAction === 'interview'
317
+ || (guidance.nextAction === 'clarify-user' && (lacksProductType || missingRequiredFields > 0));
318
+ if (!blockedByStructuredGap) {
319
+ return;
320
+ }
321
+ throw new Error([
322
+ 'OpenPrd 已阻止 synthesize:当前 requirement lane 还没有离开需求补齐阶段。',
323
+ `当前下一步仍是 ${guidance.nextAction}。`,
324
+ guidance.reason ? `原因: ${guidance.reason}` : null,
325
+ guidance.suggestedCommand ? `建议先执行: ${guidance.suggestedCommand}。` : null,
326
+ Array.isArray(guidance.suggestedQuestions) && guidance.suggestedQuestions.length > 0
327
+ ? `优先补齐: ${guidance.suggestedQuestions.slice(0, 2).join(';')}。`
328
+ : null,
329
+ ].filter(Boolean).join(' '));
330
+ }
331
+
223
332
  function resolveReviewPaths(ws, snapshot) {
224
333
  const canonicalReview = canonicalReviewPath(ws, snapshot.versionId);
225
334
  const activeReviewEntry = defaultReviewArtifactPath(ws);
@@ -383,7 +492,6 @@ function detectRequirementIntakeComplexity(gate) {
383
492
  ];
384
493
  const simpleConcretePatterns = [
385
494
  /按钮|文案|颜色|圆角|位置|间距|字号|图标|标题|空格|标点|错别字|拼写|label|copy/i,
386
- /红圈|描边|边框|卡片|平铺|去掉|去除|移除|隐藏|对齐|留白|背景/,
387
495
  /从.+(改到|移到|移动到|换到|变成|改成|改为).+/,
388
496
  ];
389
497
  const reasons = [];
@@ -505,6 +613,37 @@ function collectProjectRiskProbes(gate, snapshot) {
505
613
  return probes;
506
614
  }
507
615
 
616
+ function buildValidationFraming(sections = {}) {
617
+ const validation = sections.validation ?? {};
618
+ return {
619
+ community: shortList(validation.community, '需要先确认第一批最容易触达的社区、渠道或人群。'),
620
+ seedUsers: shortList(validation.seedUsers, '需要先确认第一批具体先找谁聊、先服务谁。'),
621
+ currentAlternative: shortList(validation.currentAlternative, '需要先确认他们现在主要靠什么替代方案在解决。'),
622
+ manualPath: shortList(validation.manualPath, '需要先确认不做完整产品时先怎么手工交付价值。'),
623
+ commitmentSignals: shortList(validation.commitmentSignals, '需要先确认什么真实承诺能证明不是口头兴趣。'),
624
+ firstValidationStep: shortList(validation.firstValidationStep, '需要先确认最低成本先做哪一步验证。'),
625
+ defaultAlivePlan: shortList(validation.defaultAlivePlan, '需要先确认验证阶段怎样先活下来。'),
626
+ };
627
+ }
628
+
629
+ function hasSatisfiedConfirmedClarificationWriteback({ gate, currentState, analysis, hasProductType }) {
630
+ if (!gateHasClarificationConfirmation(gate)) {
631
+ return false;
632
+ }
633
+ if (!hasProductType || Number(analysis?.missingRequiredFields ?? 0) > 0) {
634
+ return false;
635
+ }
636
+ const gateAt = requirementGateReferenceTimestamp(gate);
637
+ if (!gateAt) {
638
+ return false;
639
+ }
640
+ const confirmedCaptureAt = latestConfirmedClarificationCaptureTimestamp(currentState);
641
+ if (!confirmedCaptureAt || String(confirmedCaptureAt) < String(gateAt)) {
642
+ return false;
643
+ }
644
+ return !hasDerivedClarificationCaptureSince(currentState, gateAt);
645
+ }
646
+
508
647
  function buildProjectFraming({ gate, snapshot, scenario, productType }) {
509
648
  const sections = snapshot.sections ?? {};
510
649
  const guardrailHints = [
@@ -512,6 +651,7 @@ function buildProjectFraming({ gate, snapshot, scenario, productType }) {
512
651
  ...normalizeTextList(sections.constraints?.dependencies),
513
652
  ];
514
653
  const riskProbes = collectProjectRiskProbes(gate, snapshot);
654
+ const validationFraming = buildValidationFraming(sections);
515
655
  return {
516
656
  audience: shortList(
517
657
  sections.users?.primaryUsers,
@@ -524,6 +664,7 @@ function buildProjectFraming({ gate, snapshot, scenario, productType }) {
524
664
  nonGoals: shortList(sections.scope?.outOfScope, '需要先确认本轮先不做什么。'),
525
665
  guardrails: shortList(guardrailHints, '需要先确认哪些现有能力、数据、流程或体验不能被破坏。'),
526
666
  architectureSignals: summarizeArchitectureSignals(gate, snapshot),
667
+ validationFraming,
527
668
  riskProbes,
528
669
  riskProbeSummary: shortList(riskProbes, '当前没有明显命中额外风险探针。'),
529
670
  };
@@ -553,7 +694,7 @@ function describeProductLensFocus(productType) {
553
694
  function requirementTypeDisplay(gate) {
554
695
  const tier = String(gate?.intent?.requirementTier ?? '').toLowerCase();
555
696
  if (tier === 'l0') {
556
- return '快速修正(L0)';
697
+ return '直接处理(L0)';
557
698
  }
558
699
  if (tier === 'l1') {
559
700
  return '现有功能优化(L1)';
@@ -800,6 +941,7 @@ function buildInlineClarification({ clarification, reflection, presentation }) {
800
941
  const prompt = reflection?.promptPreview || '本轮需求';
801
942
  const projectContext = reflection?.projectContext ?? {};
802
943
  const projectFraming = projectContext.projectFraming ?? {};
944
+ const validationFraming = projectFraming.validationFraming ?? {};
803
945
  const productType = projectContext.productType;
804
946
  const primaryQuestion = clarification.mustAskUser[0] ?? null;
805
947
  const followUpQuestions = clarification.mustAskUser.slice(1, presentation.mode === 'inline' ? 2 : 3);
@@ -830,6 +972,12 @@ function buildInlineClarification({ clarification, reflection, presentation }) {
830
972
  `- 第一版先让用户做到:${projectFraming.firstSlice ?? '待确认'}。`,
831
973
  `- 这轮先不碰:${projectFraming.nonGoals ?? '待确认'}。`,
832
974
  `- 必须守住:${projectFraming.guardrails ?? '待确认'}。`,
975
+ `- 第一批最容易触达:${validationFraming.community ?? '待确认'}。`,
976
+ `- 当前主要替代:${validationFraming.currentAlternative ?? '待确认'}。`,
977
+ `- 先怎么手工交付:${validationFraming.manualPath ?? '待确认'}。`,
978
+ `- 什么承诺才算真需求:${validationFraming.commitmentSignals ?? '待确认'}。`,
979
+ `- 最低成本先验证:${validationFraming.firstValidationStep ?? '待确认'}。`,
980
+ `- 怎么先活下来:${validationFraming.defaultAlivePlan ?? '待确认'}。`,
833
981
  ];
834
982
  if (projectFraming.architectureSignals) {
835
983
  lines.push(`- 这次更可能会影响:${projectFraming.architectureSignals}。`);
@@ -950,6 +1098,11 @@ async function buildRequirementIntakeReflection({ projectRoot, ws, snapshot, ana
950
1098
  productType: snapshot.productType ?? resolveCurrentProductType(ws),
951
1099
  });
952
1100
  const lensQuestion = buildLensReflectionQuestion(snapshot.productType ?? resolveCurrentProductType(ws));
1101
+ const validationLoopQuestion = reflectionQuestion(
1102
+ 'validation-loop',
1103
+ '验证与创业闭环',
1104
+ '请确认第一批最容易触达的社区或用户是谁、他们现在靠什么替代、如果先不做完整产品怎么手工交付、什么真实承诺最能证明值得继续,以及最低成本验证和先活下来底线是什么。'
1105
+ );
953
1106
  const needsDeliveryShapeQuestion = !needsInterfaceSketch
954
1107
  && (
955
1108
  projectFraming.riskProbes.length > 0
@@ -959,6 +1112,7 @@ async function buildRequirementIntakeReflection({ projectRoot, ws, snapshot, ana
959
1112
  ? [
960
1113
  reflectionQuestion('intent', '意图与目标', '请确认我理解得对不对:这次主要是谁在什么场景下遇到什么问题,第一版最想先改善什么结果?'),
961
1114
  lensQuestion,
1115
+ validationLoopQuestion,
962
1116
  reflectionQuestion('project-context', '项目影响范围', '结合当前项目,请确认第一版最小可用切片是什么;哪些已有模块、入口、流程或历史需求必须复用,哪些可以调整?'),
963
1117
  reflectionQuestion('scope-quality', '范围与验收', '请确认这次先做到哪一步就算有价值;哪些这轮先不动;哪些老用户习惯、现有业务结果或交付节奏不能被影响?'),
964
1118
  needsInterfaceSketch
@@ -1011,6 +1165,7 @@ async function buildRequirementIntakeReflection({ projectRoot, ws, snapshot, ana
1011
1165
  `当前产品:${productName};当前产品场景:${formatProductTypeDisplay(productType, { fallback: '待确认' })};已记录问题:${currentProblem}`,
1012
1166
  `当前范围线索:${currentScope}`,
1013
1167
  `首轮画像:用户群体=${projectFraming.audience};产品形态=${projectFraming.productShape};第一版先做=${projectFraming.firstSlice}`,
1168
+ `验证闭环:可触达人群=${projectFraming.validationFraming?.community};当前替代=${projectFraming.validationFraming?.currentAlternative};手工路径=${projectFraming.validationFraming?.manualPath}`,
1014
1169
  activeChange ? `仍有 active change:${activeChange.activeChange}(${activeChange.status}),需要和本轮需求分开评估。` : '当前没有检测到 active change 冲突。',
1015
1170
  ],
1016
1171
  },
@@ -1020,6 +1175,7 @@ async function buildRequirementIntakeReflection({ projectRoot, ws, snapshot, ana
1020
1175
  findings: [
1021
1176
  `仍需确认的信息:${shortList(missing, '暂无明显缺口')}`,
1022
1177
  `边界与约束:先不做=${projectFraming.nonGoals};不能破坏=${projectFraming.guardrails}`,
1178
+ `商业验证:承诺信号=${projectFraming.validationFraming?.commitmentSignals};低成本验证=${projectFraming.validationFraming?.firstValidationStep};先活下来=${projectFraming.validationFraming?.defaultAlivePlan}`,
1023
1179
  needsInterfaceSketch ? '需求看起来涉及界面或流程,需要先给用户确认草图或关键操作路径。' : `影响环节:${projectFraming.architectureSignals}`,
1024
1180
  `业务提醒:${projectFraming.riskProbeSummary}`,
1025
1181
  '进入实现前必须保留范围、非目标、异常路径和验收证据。',
@@ -1058,6 +1214,12 @@ function renderRequirementIntakeReflection(reflection) {
1058
1214
  `| 第一版先做 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.firstSlice ?? '待补充')} |`,
1059
1215
  `| 暂不处理 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.nonGoals ?? '待补充')} |`,
1060
1216
  `| 不能破坏 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.guardrails ?? '待补充')} |`,
1217
+ `| 可触达人群 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.community ?? '待补充')} |`,
1218
+ `| 当前替代 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.currentAlternative ?? '待补充')} |`,
1219
+ `| 手工路径 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.manualPath ?? '待补充')} |`,
1220
+ `| 承诺信号 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.commitmentSignals ?? '待补充')} |`,
1221
+ `| 最低成本验证 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.firstValidationStep ?? '待补充')} |`,
1222
+ `| 先活下来 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.defaultAlivePlan ?? '待补充')} |`,
1061
1223
  `| 影响环节 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.architectureSignals ?? '待补充')} |`,
1062
1224
  `| 业务提醒 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.riskProbeSummary ?? '待补充')} |`,
1063
1225
  '',
@@ -1091,6 +1253,7 @@ function buildRequirementIntakeDepth(gate, reflection = null) {
1091
1253
  const fallbackLayers = [
1092
1254
  reflectionQuestion('product-context', '用户 / 产品形态 / 问题', '先确认:这是给谁用的、它更像个人产品 / 团队流程 / Agent 协作中的哪一种、为什么现在值得解决?'),
1093
1255
  reflectionQuestion('product-outcome', '第一版切片 / 目标 / 成功标准', '请确认第一版最小可用切片是什么;解决后用户先能完成什么,用什么业务结果或验收标准判断有效?'),
1256
+ reflectionQuestion('product-validation', '验证闭环 / 承诺信号', '请确认第一批最容易触达的社区或用户是谁、他们现在靠什么替代、如果先不做完整产品怎么手工交付、什么真实承诺最能证明值得继续,以及最低成本验证和先活下来底线是什么。'),
1094
1257
  reflectionQuestion('product-flow', '范围 / 非目标 / 异常路径', '请拆出本轮先做什么、不做什么、哪些既有行为不能被破坏,以及关键失败路径和恢复方式。'),
1095
1258
  reflectionQuestion(
1096
1259
  'product-detail',
@@ -1226,11 +1389,15 @@ async function synthesizeWorkspace(projectRoot, overrides = {}) {
1226
1389
  if (!(await exists(ws.workspaceRoot))) {
1227
1390
  throw new Error(`Missing workspace: ${ws.workspaceRoot}`);
1228
1391
  }
1392
+ const requirementGate = await readActiveRequirementGate(projectRoot);
1229
1393
  ensureFreshRequirementStateForSynthesize({
1230
- gate: await readActiveRequirementGate(projectRoot),
1394
+ gate: requirementGate,
1231
1395
  currentState: ws.data.currentState ?? {},
1232
1396
  overrides,
1233
1397
  });
1398
+ if (requirementGate?.active && gateHasClarificationConfirmation(requirementGate)) {
1399
+ await ensureRequirementLaneReadyForSynthesize(ws);
1400
+ }
1234
1401
 
1235
1402
  const versionIndex = await readVersionIndex(ws);
1236
1403
  const nextVersionNumber = overrides.versionNumber ?? (versionIndex.length > 0
@@ -1911,6 +2078,12 @@ async function computeWorkspaceGuidance(ws, options = {}) {
1911
2078
  const scenario = await detectWorkspaceScenario(ws.projectRoot, ws, versionIndex);
1912
2079
  const requirementGate = await readActiveRequirementGate(ws.projectRoot);
1913
2080
  const intakeSatisfiedByReview = prdReviewState.status === 'confirmed' && analysis.missingRequiredFields === 0;
2081
+ const intakeSatisfiedByConfirmedWriteback = hasSatisfiedConfirmedClarificationWriteback({
2082
+ gate: requirementGate,
2083
+ currentState,
2084
+ analysis,
2085
+ hasProductType,
2086
+ });
1914
2087
  const intakeReflection = await buildRequirementIntakeReflection({
1915
2088
  projectRoot: ws.projectRoot,
1916
2089
  ws,
@@ -1927,7 +2100,9 @@ async function computeWorkspaceGuidance(ws, options = {}) {
1927
2100
  captureMeta: currentState.captureMeta ?? {},
1928
2101
  prdReviewState,
1929
2102
  limit: Number(options.questionLimit ?? 5),
1930
- }), requirementGate, intakeReflection, { satisfied: intakeSatisfiedByReview });
2103
+ }), requirementGate, intakeReflection, {
2104
+ satisfied: intakeSatisfiedByReview || intakeSatisfiedByConfirmedWriteback,
2105
+ });
1931
2106
 
1932
2107
  let nextAction = 'synthesize';
1933
2108
  let reason = 'PRD 可以合成为第一个版本。';
@@ -1987,6 +2162,12 @@ async function computeWorkspaceGuidance(ws, options = {}) {
1987
2162
 
1988
2163
  const taskGraph = buildWorkflowTaskGraph(analysisSnapshot, analysis, { diagramState, prdReviewState, clarificationState: clarification });
1989
2164
  const gates = deriveGateLabels({ nextAction, diagramState, clarification });
2165
+ const brainstormSuggestion = buildBrainstormSuggestion({
2166
+ currentState,
2167
+ analysis,
2168
+ clarification,
2169
+ requirementGate,
2170
+ });
1990
2171
 
1991
2172
  return {
1992
2173
  versionIndex,
@@ -2002,6 +2183,7 @@ async function computeWorkspaceGuidance(ws, options = {}) {
2002
2183
  suggestedCommand,
2003
2184
  suggestedQuestions,
2004
2185
  gates,
2186
+ brainstormSuggestion,
2005
2187
  };
2006
2188
  }
2007
2189
 
@@ -2065,7 +2247,8 @@ async function nextWorkspace(projectRoot) {
2065
2247
  currentGate: gates.currentGate,
2066
2248
  upcomingGate: gates.upcomingGate,
2067
2249
  },
2068
- workflow: ['clarify', 'classify', 'interview', 'synthesize', 'diagram', 'review', 'freeze', 'handoff'],
2250
+ brainstormSuggestion: guidance.brainstormSuggestion,
2251
+ workflow: ['brainstorm', 'clarify', 'classify', 'interview', 'synthesize', 'diagram', 'review', 'freeze', 'handoff'],
2069
2252
  };
2070
2253
  }
2071
2254
 
@@ -1,14 +0,0 @@
1
- # milvus.io/ai-code-review-gets-better-when-models-debate-claude-vs-gemini-vs-codex-vs-qwen-vs-minimax.md
2
-
3
- - ID: milvus-io-ai-code-review-gets-better-when-models-debate-claude-vs-gemini-vs-code
4
- - 状态: approved
5
- - 场景: agent-harness, pr-review-harness
6
- - 触发: 设计 Agent harness、长程任务、状态持久化、验证门禁或人工接管;设计 merge 前高风险复核、独立 reviewer 交叉验证、误报过滤、reviewer agreement 或 merge recommendation
7
- - 不适用: 普通 PRD / 产品流程设计;与 CLI 无关的一次性 UI 视觉问题;默认给每个低风险 PR 拉起多 reviewer 并行审查
8
- - 研究方式: official_page_first
9
- - 来源: https://milvus.io/ar/blog/ai-code-review-gets-better-when-models-debate-claude-vs-gemini-vs-codex-vs-qwen-vs-minimax.md
10
-
11
- ## 备注
12
-
13
- AI code review / PR review harness;reviewer agreement、交叉验证、误报过滤、hallucination filter、merge recommendation
14
-