@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.
- package/.openprd/benchmarks/index.md +15 -1
- package/.openprd/config.yaml +10 -0
- package/.openprd/design/README.md +47 -0
- package/.openprd/design/README_EN.md +25 -0
- package/.openprd/design/active/asset-spec.md +15 -0
- package/.openprd/design/active/direction-plan.md +7 -0
- package/.openprd/design/active/facts-sheet.md +13 -0
- package/.openprd/design/active/image-preflight.md +9 -0
- package/.openprd/design/active/selected-direction.md +11 -0
- package/.openprd/design/anti-slop.md +16 -0
- package/.openprd/design/assets/dark-mesh.svg +14 -0
- package/.openprd/design/assets/paper-grid.svg +14 -0
- package/.openprd/design/assets/surface-presets.md +21 -0
- package/.openprd/design/assets/workbench-grid.svg +16 -0
- package/.openprd/design/checklists/ui-quality-gate.md +22 -0
- package/.openprd/design/components/component-catalog.md +42 -0
- package/.openprd/design/layouts/layout-catalog.json +45 -0
- package/.openprd/design/lenses/frontend-lenses.md +31 -0
- package/.openprd/design/recipes/content-experience.md +26 -0
- package/.openprd/design/recipes/operational-tool.md +25 -0
- package/.openprd/design/recipes/product-launch.md +26 -0
- package/.openprd/design/templates/README.md +36 -0
- package/.openprd/design/templates/README_EN.md +31 -0
- package/.openprd/design/templates/content-home.html +415 -0
- package/.openprd/design/templates/ops-dashboard.html +340 -0
- package/.openprd/design/templates/product-launch.html +360 -0
- package/.openprd/design/themes/theme-catalog.json +77 -0
- package/.openprd/engagements/active/prd.md +111 -100
- package/.openprd/schema/prd.schema.yaml +10 -0
- package/.openprd/templates/base/intake.md +4 -0
- package/.openprd/templates/base/prd.md +16 -6
- package/AGENTS.md +10 -7
- package/README.md +26 -28
- package/README_EN.md +3 -3
- package/package.json +41 -2
- package/scripts/openprd-codex-isolated-worker.mjs +799 -0
- package/skills/openprd-benchmark-router/SKILL.md +4 -0
- package/skills/openprd-frontend-design/SKILL.md +161 -0
- package/skills/openprd-frontend-design/references/design-asset-contract.md +34 -0
- package/skills/openprd-frontend-design/references/direction-engine.md +46 -0
- package/skills/openprd-harness/SKILL.md +37 -20
- package/skills/openprd-learning-review/SKILL.md +2 -1
- package/skills/openprd-learning-review/references/style-packs/xianxia-cultivation.prompt.md +1 -1
- package/skills/openprd-quality/SKILL.md +5 -3
- package/skills/openprd-requirement-intake/SKILL.md +27 -5
- package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -3
- package/skills/openprd-requirement-intake/references/routing-rubric.md +9 -5
- package/skills/openprd-requirement-intake/references/startup-validation-lens.md +23 -0
- package/skills/openprd-router/SKILL.md +13 -5
- package/skills/openprd-shared/SKILL.md +38 -25
- package/src/agent-integration.js +120 -69
- package/src/brainstorm-artifacts.js +1657 -0
- package/src/brainstorm-presentation.js +223 -0
- package/src/brainstorm.js +603 -0
- package/src/cli/args.js +51 -3
- package/src/cli/doctor-print.js +34 -8
- package/src/cli/print.js +6 -0
- package/src/cli/quality-print.js +70 -0
- package/src/cli/run-print.js +43 -1
- package/src/cli/shared-print.js +33 -2
- package/src/cli/workflow-print.js +21 -0
- package/src/codex-hook-runner-template.mjs +838 -58
- package/src/design-starter-support.js +462 -0
- package/src/design-starter.js +1207 -0
- package/src/knowledge.js +232 -20
- package/src/learning-review.js +3 -3
- package/src/loop.js +839 -101
- package/src/openprd.js +86 -1
- package/src/openspec/change-validate.js +1 -0
- package/src/prd-core.js +18 -0
- package/src/quality-learning.js +50 -6
- package/src/quality.js +2 -2
- package/src/run-harness.js +191 -29
- package/src/self-update.js +301 -4
- package/src/standards.js +46 -5
- package/src/visual-prepare.js +940 -0
- package/src/workspace-core.js +137 -0
- package/src/workspace-workflow.js +190 -7
- package/.openprd/benchmarks/evidence/milvus-io-ai-code-review-gets-better-when-models-debate-claude-vs-gemini-vs-code.md +0 -14
- package/.openprd/benchmarks/evidence/nolanlawson-com-using-ai-to-write-better-code-more-slowly.md +0 -14
- package/.openprd/discovery/config.json +0 -35
- package/.openprd/engagements/active/flows.md +0 -35
- package/.openprd/engagements/active/handoff.md +0 -16
- package/.openprd/engagements/active/review.html +0 -61
- package/.openprd/engagements/active/roles.md +0 -22
- package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +0 -23
- package/.openprd/engagements/work-units/wu-20260602113956-a99b5b88.json +0 -18
- package/.openprd/engagements/work-units/wu-20260602122244-78656aaf.json +0 -18
- package/.openprd/engagements/work-units/wu-20260602122442-e96489e2.json +0 -18
- package/.openprd/engagements/work-units/wu-20260602132835-695429e8.json +0 -18
- package/.openprd/exports/.gitkeep +0 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/candidate.json +0 -78
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/diagnostic-report.json +0 -129
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/root-cause-candidates.json +0 -41
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/timeline.json +0 -14
- package/.openprd/knowledge/drafts/openprd-experience-diagnostic-candidate-turn-1780116203372-5f266a79e968c758/SKILL.md +0 -49
- package/.openprd/knowledge/index.json +0 -47
- package/.openprd/reviews/v0001.html +0 -1322
- package/.openprd/reviews/v0002.html +0 -1150
- package/.openprd/reviews/v0003.html +0 -1150
- package/.openprd/reviews/v0004.html +0 -1150
- package/.openprd/reviews/v0005.html +0 -1150
- package/.openprd/sessions/.gitkeep +0 -0
- package/.openprd/state/changes.json +0 -27
- package/.openprd/state/current.json +0 -505
- package/.openprd/state/release-ledger.json +0 -387
- package/.openprd/state/version-index.json +0 -67
- package/.openprd/state/versions/.gitkeep +0 -0
- package/.openprd/state/versions/v0001.json +0 -121
- package/.openprd/state/versions/v0001.md +0 -161
- package/.openprd/state/versions/v0002.json +0 -264
- package/.openprd/state/versions/v0002.md +0 -183
- package/.openprd/state/versions/v0003.json +0 -269
- package/.openprd/state/versions/v0003.md +0 -188
- package/.openprd/state/versions/v0004.json +0 -274
- package/.openprd/state/versions/v0004.md +0 -193
- package/.openprd/state/versions/v0005.json +0 -299
- package/.openprd/state/versions/v0005.md +0 -189
- package/docs/assets/openprd-capability-overview-en.png +0 -0
- package/docs/assets/openprd-capability-overview-zh.png +0 -0
- package/docs/assets/openprd-learning-html.png +0 -0
- package/docs/assets/openprd-quality-html.png +0 -0
- package/docs/assets/openprd-requirement-routing-en.png +0 -0
- package/docs/assets/openprd-requirement-routing-en.svg +0 -102
- package/docs/assets/openprd-requirement-routing-zh-refined.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.svg +0 -102
- package/docs/assets/openprd-review-html.png +0 -0
- package/docs/assets/openprd-scenario-overview.png +0 -0
- package/docs/assets/openprd-scenario-overview.svg +0 -114
- package/docs/assets/openprd-self-evolving-mechanisms-en.png +0 -0
- package/docs/assets/openprd-self-evolving-mechanisms-zh.png +0 -0
- package/docs/assets/openprd-visual-compare-case-study-en.png +0 -0
- package/docs/assets/openprd-visual-compare-case-study-zh.png +0 -0
package/src/workspace-core.js
CHANGED
|
@@ -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
|
-
|
|
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 '
|
|
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:
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|