@openprd/cli 0.1.10 → 0.1.18
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 +17 -15
- package/.openprd/benchmarks/sources.yaml +53 -1
- package/.openprd/changes/adaptive-project-framing/.openprd.yaml +2 -0
- package/.openprd/changes/adaptive-project-framing/design.md +47 -0
- package/.openprd/changes/adaptive-project-framing/proposal.md +35 -0
- package/.openprd/changes/adaptive-project-framing/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/adaptive-project-framing/task-events.jsonl +20 -0
- package/.openprd/changes/adaptive-project-framing/tasks.md +340 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/.openprd.yaml +2 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/design.md +34 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/proposal.md +23 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/task-events.jsonl +14 -0
- package/.openprd/changes/docs-anti-pollution-and-design-direction-review/tasks.md +238 -0
- package/.openprd/changes/loop-isolated-worktree-commit/.openprd.yaml +2 -0
- package/.openprd/changes/loop-isolated-worktree-commit/design.md +53 -0
- package/.openprd/changes/loop-isolated-worktree-commit/proposal.md +38 -0
- package/.openprd/changes/loop-isolated-worktree-commit/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/loop-isolated-worktree-commit/task-events.jsonl +22 -0
- package/.openprd/changes/loop-isolated-worktree-commit/tasks.md +357 -0
- package/.openprd/changes/openprd/.openprd.yaml +2 -0
- package/.openprd/changes/openprd/design.md +46 -0
- package/.openprd/changes/openprd/proposal.md +36 -0
- package/.openprd/changes/openprd/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/openprd/task-events.jsonl +23 -0
- package/.openprd/changes/openprd/tasks.md +242 -0
- package/.openprd/changes/pm-human-in-the-loop/.openprd.yaml +2 -0
- package/.openprd/changes/pm-human-in-the-loop/design.md +38 -0
- package/.openprd/changes/pm-human-in-the-loop/proposal.md +29 -0
- package/.openprd/changes/pm-human-in-the-loop/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/pm-human-in-the-loop/task-events.jsonl +18 -0
- package/.openprd/changes/pm-human-in-the-loop/tasks.md +306 -0
- package/.openprd/changes/test-strategy-router/.openprd.yaml +2 -0
- package/.openprd/changes/test-strategy-router/design.md +65 -0
- package/.openprd/changes/test-strategy-router/proposal.md +45 -0
- package/.openprd/changes/test-strategy-router/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/test-strategy-router/task-events.jsonl +1 -0
- package/.openprd/changes/test-strategy-router/tasks.md +132 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/.openprd.yaml +2 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/design.md +28 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/proposal.md +23 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/task-events.jsonl +11 -0
- package/.openprd/changes/verify-boundaries-and-historical-refresh/tasks.md +66 -0
- package/.openprd/changes/vibe-coding-dev-check/.openprd.yaml +2 -0
- package/.openprd/changes/vibe-coding-dev-check/design.md +37 -0
- package/.openprd/changes/vibe-coding-dev-check/proposal.md +28 -0
- package/.openprd/changes/vibe-coding-dev-check/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/vibe-coding-dev-check/task-events.jsonl +18 -0
- package/.openprd/changes/vibe-coding-dev-check/tasks.md +306 -0
- package/.openprd/design/README.md +11 -6
- package/.openprd/design/README_EN.md +4 -1
- package/.openprd/design/active/asset-spec.md +6 -0
- package/.openprd/design/active/direction-plan.md +5 -5
- package/.openprd/design/active/selected-direction.md +2 -0
- package/.openprd/design/anti-slop.md +26 -1
- package/.openprd/design/assets/surface-presets.md +6 -0
- package/.openprd/design/checklists/ui-quality-gate.md +7 -0
- package/.openprd/design/lenses/frontend-lenses.md +28 -0
- package/.openprd/design/recipes/content-experience.md +8 -1
- package/.openprd/design/recipes/operational-tool.md +7 -0
- package/.openprd/design/recipes/product-launch.md +8 -1
- package/.openprd/design/templates/README.md +6 -3
- package/.openprd/design/templates/README_EN.md +6 -3
- package/.openprd/design/templates/ops-dashboard.html +8 -1
- package/.openprd/design/templates/product-launch.html +2 -1
- package/.openprd/design/themes/theme-catalog.json +23 -5
- package/.openprd/engagements/active/prd.md +103 -100
- package/.openprd/schema/prd.schema.yaml +15 -0
- package/.openprd/standards/config.json +7 -1
- package/.openprd/templates/base/intake.md +13 -0
- package/.openprd/templates/base/prd.md +15 -0
- package/AGENTS.md +14 -4
- package/README.md +107 -14
- package/README_EN.md +131 -13
- package/package.json +1 -1
- package/scripts/dev-check-wrapup-copy.mjs +46 -21
- package/skills/openprd-benchmark-router/SKILL.md +2 -1
- package/skills/openprd-diagram-review/SKILL.md +27 -2
- package/skills/openprd-diagram-review/references/explanation-svg-patterns.md +131 -0
- package/skills/openprd-discovery-loop/SKILL.md +2 -2
- package/skills/openprd-frontend-design/SKILL.md +44 -20
- package/skills/openprd-frontend-design/references/design-asset-contract.md +6 -0
- package/skills/openprd-frontend-design/references/direction-engine.md +29 -4
- package/skills/openprd-harness/SKILL.md +22 -16
- package/skills/openprd-quality/SKILL.md +10 -8
- package/skills/openprd-requirement-intake/SKILL.md +5 -3
- package/skills/openprd-requirement-intake/references/prd-template-lenses.md +2 -2
- package/skills/openprd-requirement-intake/references/startup-validation-lens.md +98 -13
- package/skills/openprd-router/SKILL.md +2 -1
- package/skills/openprd-shared/SKILL.md +27 -17
- package/skills/openprd-standards/SKILL.md +4 -2
- package/src/agent-canonical-content.js +808 -0
- package/src/agent-integration.js +259 -778
- package/src/brainstorm-artifacts.js +156 -8
- package/src/brainstorm-presentation.js +14 -4
- package/src/brainstorm.js +165 -2
- package/src/canvas-app.html.js +2910 -0
- package/src/canvas-i18n.js +274 -0
- package/src/canvas-workspace.js +2271 -0
- package/src/cli/args.js +72 -4
- package/src/cli/basic-print.js +3 -0
- package/src/cli/canvas-print.js +106 -0
- package/src/cli/doctor-print.js +20 -0
- package/src/cli/print.js +2 -0
- package/src/cli/quality-commands.js +222 -0
- package/src/cli/quality-print.js +37 -5
- package/src/cli/run-print.js +5 -0
- package/src/cli/workflow-print.js +3 -0
- package/src/codex-app-server-relay.js +251 -0
- package/src/codex-hook-runner-template.mjs +963 -65
- package/src/design-starter-support.js +2 -2
- package/src/design-starter.js +39 -3
- package/src/dev-standards.js +125 -10
- package/src/diagram-core.js +390 -171
- package/src/discovery.js +2 -8
- package/src/docs-compact.js +125 -0
- package/src/execution-strategy.js +1 -1
- package/src/fleet.js +9 -3
- package/src/guide.js +227 -0
- package/src/html-artifacts.js +825 -51
- package/src/knowledge.js +101 -20
- package/src/language-policy.js +63 -3
- package/src/learning-review.js +1 -1
- package/src/loop.js +1 -1
- package/src/openprd.js +68 -131
- package/src/openspec/change-validate.js +2 -2
- package/src/openspec/constants.js +6 -7
- package/src/openspec/execute.js +2 -2
- package/src/openspec/generate.js +318 -102
- package/src/openspec/migration.js +168 -0
- package/src/openspec/paths.js +1 -22
- package/src/prd-core.js +102 -3
- package/src/quality-html-artifact.js +30 -5
- package/src/quality-visual-review.js +257 -14
- package/src/quality.js +66 -18
- package/src/review-presentation.js +60 -0
- package/src/run-harness.js +41 -3
- package/src/session-evidence.js +363 -0
- package/src/standards.js +64 -0
- package/src/test-strategy.js +7 -4
- package/src/visual-compare-centering.js +622 -0
- package/src/visual-compare-core.js +1123 -0
- package/src/visual-compare.js +620 -698
- package/src/workspace-core.js +132 -4
- package/src/workspace-workflow.js +172 -10
package/src/workspace-core.js
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { buildSnapshotChangeSummary } from './change-summary.js';
|
|
6
6
|
import { formatProductTypeDisplay, formatTemplatePackDisplay } from './product-type-copy.js';
|
|
7
7
|
import { buildDiagramArtifact, renderDiagramMermaidFromModel } from './diagram-core.js';
|
|
8
|
+
import { migrateLegacyOpenPrdArtifacts } from './openspec/migration.js';
|
|
8
9
|
import { analyzePrdSnapshot, buildPrdSnapshot, formatVersionId, getRequiredFieldDescriptors, renderPrdMarkdown, summarizeSnapshot } from './prd-core.js';
|
|
9
10
|
import { normalizeReleaseLedger } from './release-ledger.js';
|
|
10
11
|
import { appendJsonl, appendText, cjoin, exists, readJson, readText, readYaml, stringifyYaml, writeJson, writeText, writeYaml } from './fs-utils.js';
|
|
@@ -130,16 +131,20 @@ const WORKSPACE_SEED_REFRESH_FILES = [
|
|
|
130
131
|
'standards/folder-readme-template.md',
|
|
131
132
|
];
|
|
132
133
|
const WORKSPACE_SEED_COPY_IGNORE = new Set([
|
|
134
|
+
'archive',
|
|
133
135
|
'artifacts',
|
|
134
136
|
'benchmarks/.DS_Store',
|
|
135
137
|
'benchmarks/evidence',
|
|
136
138
|
'benchmarks/inbox',
|
|
139
|
+
'changes',
|
|
137
140
|
'discovery',
|
|
138
141
|
'growth',
|
|
139
142
|
'harness',
|
|
140
143
|
'knowledge',
|
|
141
144
|
'learning',
|
|
142
145
|
'quality',
|
|
146
|
+
'reviews',
|
|
147
|
+
'specs',
|
|
143
148
|
'state',
|
|
144
149
|
'sessions',
|
|
145
150
|
'exports',
|
|
@@ -157,6 +162,11 @@ const WORKSPACE_SEED_COPY_IGNORE = new Set([
|
|
|
157
162
|
'engagements/active/review.html',
|
|
158
163
|
'engagements/active/verification.md',
|
|
159
164
|
]);
|
|
165
|
+
const OPERATIONAL_GITIGNORE_ENTRIES = [
|
|
166
|
+
'.openprd/harness/',
|
|
167
|
+
'.openprd/learning/',
|
|
168
|
+
'.openprd/quality/reports/',
|
|
169
|
+
];
|
|
160
170
|
|
|
161
171
|
const DEFAULT_DISCOVERY_CONFIG = {
|
|
162
172
|
activeChange: null,
|
|
@@ -770,9 +780,69 @@ async function ensureWorkspaceSkeleton(projectRoot, options = {}) {
|
|
|
770
780
|
}
|
|
771
781
|
}
|
|
772
782
|
|
|
783
|
+
await ensureOperationalGitignore(projectRoot);
|
|
784
|
+
|
|
773
785
|
return { workspaceRoot, created };
|
|
774
786
|
}
|
|
775
787
|
|
|
788
|
+
function normalizeIgnoreLine(line) {
|
|
789
|
+
const trimmed = String(line ?? '').trim();
|
|
790
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
791
|
+
return '';
|
|
792
|
+
}
|
|
793
|
+
return trimmed
|
|
794
|
+
.replace(/^\/+/, '')
|
|
795
|
+
.replace(/\/+$/, '');
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function missingOperationalGitignoreEntries(text) {
|
|
799
|
+
const present = new Set(String(text ?? '')
|
|
800
|
+
.split(/\r?\n/)
|
|
801
|
+
.map(normalizeIgnoreLine)
|
|
802
|
+
.filter(Boolean));
|
|
803
|
+
return OPERATIONAL_GITIGNORE_ENTRIES.filter((entry) => !present.has(normalizeIgnoreLine(entry)));
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function gitignoreContainsOnlyOpenPrdOperationalEntries(text) {
|
|
807
|
+
const allowed = new Set(OPERATIONAL_GITIGNORE_ENTRIES.map(normalizeIgnoreLine));
|
|
808
|
+
const entries = String(text ?? '')
|
|
809
|
+
.split(/\r?\n/)
|
|
810
|
+
.map(normalizeIgnoreLine)
|
|
811
|
+
.filter(Boolean);
|
|
812
|
+
return entries.length > 0 && entries.every((entry) => allowed.has(entry));
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
async function readOperationalGitignoreStatus(projectRoot) {
|
|
816
|
+
const gitignorePath = cjoin(projectRoot, '.gitignore');
|
|
817
|
+
const text = await readText(gitignorePath).catch(() => '');
|
|
818
|
+
const missing = missingOperationalGitignoreEntries(text);
|
|
819
|
+
return {
|
|
820
|
+
ok: missing.length === 0,
|
|
821
|
+
path: '.gitignore',
|
|
822
|
+
missing,
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async function ensureOperationalGitignore(projectRoot, changes = null) {
|
|
827
|
+
const gitignorePath = cjoin(projectRoot, '.gitignore');
|
|
828
|
+
const current = await readText(gitignorePath).catch(() => '');
|
|
829
|
+
const missing = missingOperationalGitignoreEntries(current);
|
|
830
|
+
if (missing.length === 0) {
|
|
831
|
+
changes?.push({ path: '.gitignore', status: 'unchanged' });
|
|
832
|
+
return { ok: true, path: '.gitignore', status: 'unchanged', missing: [] };
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const prefix = current.trimEnd();
|
|
836
|
+
const next = `${prefix ? `${prefix}\n` : ''}${missing.join('\n')}\n`;
|
|
837
|
+
await writeText(gitignorePath, next);
|
|
838
|
+
changes?.push({
|
|
839
|
+
path: '.gitignore',
|
|
840
|
+
status: current ? 'updated' : 'created',
|
|
841
|
+
message: `added OpenPrd generated artifact ignores: ${missing.join(', ')}`,
|
|
842
|
+
});
|
|
843
|
+
return { ok: true, path: '.gitignore', status: current ? 'updated' : 'created', missing };
|
|
844
|
+
}
|
|
845
|
+
|
|
776
846
|
async function copySeedFileIfChanged(projectRoot, relativePath, changes) {
|
|
777
847
|
const sourcePath = cjoin(SEED_WORKSPACE, relativePath);
|
|
778
848
|
const targetPath = cjoin(projectRoot, '.openprd', relativePath);
|
|
@@ -1022,6 +1092,12 @@ async function migrateWorkspaceSkeleton(projectRoot, options = {}) {
|
|
|
1022
1092
|
await fs.mkdir(cjoin(workspaceRoot, 'benchmarks', 'evidence'), { recursive: true });
|
|
1023
1093
|
await fs.mkdir(cjoin(workspaceRoot, 'engagements', 'active'), { recursive: true });
|
|
1024
1094
|
|
|
1095
|
+
const legacyOpenPrdMigration = await migrateLegacyOpenPrdArtifacts(projectRoot);
|
|
1096
|
+
changes.push(...legacyOpenPrdMigration.changes.map((change) => ({
|
|
1097
|
+
...change,
|
|
1098
|
+
source: 'legacy-openprd-artifacts',
|
|
1099
|
+
})));
|
|
1100
|
+
|
|
1025
1101
|
await migrateWorkspaceConfig(projectRoot, changes);
|
|
1026
1102
|
await migrateStandardsConfig(projectRoot, changes);
|
|
1027
1103
|
for (const relativePath of WORKSPACE_SEED_REFRESH_FILES) {
|
|
@@ -1072,21 +1148,28 @@ async function migrateWorkspaceSkeleton(projectRoot, options = {}) {
|
|
|
1072
1148
|
await writeText(cjoin(projectRoot, '.openprd', 'benchmarks', 'index.md'), '# OpenPrd Benchmark Registry\n\n## 规则\n\n- 项目级 approved benchmark 优先于 OpenPrd 内置 Source Map。\n- `inbox/` 里的 candidate 只表示待确认线索,不表示长期最佳实践。\n- 每次只挑 1-3 个高相关来源;来源目录不是事实来源。\n\n## Approved Sources\n\n- 暂无已批准来源。\n\n## Candidate Sources\n\n- 暂无待确认来源。\n');
|
|
1073
1149
|
changes.push({ path: cjoin('.openprd', 'benchmarks', 'index.md'), status: 'created' });
|
|
1074
1150
|
}
|
|
1151
|
+
await ensureOperationalGitignore(projectRoot, changes);
|
|
1075
1152
|
|
|
1076
1153
|
const changedCount = changes.filter((change) => change.status !== 'unchanged').length;
|
|
1077
1154
|
if (options.recordEvent && changedCount > 0) {
|
|
1078
1155
|
const nextWs = await loadWorkspace(projectRoot);
|
|
1079
1156
|
await appendWorkflowEvent(nextWs, 'workspace_migrated', {
|
|
1080
1157
|
changed: changedCount,
|
|
1158
|
+
legacyOpenPrdArtifacts: {
|
|
1159
|
+
migrated: legacyOpenPrdMigration.migrated,
|
|
1160
|
+
conflicts: legacyOpenPrdMigration.conflicts,
|
|
1161
|
+
},
|
|
1081
1162
|
});
|
|
1082
1163
|
}
|
|
1083
1164
|
|
|
1084
1165
|
return {
|
|
1085
|
-
ok:
|
|
1166
|
+
ok: legacyOpenPrdMigration.ok,
|
|
1086
1167
|
action: 'migrate',
|
|
1087
1168
|
projectRoot,
|
|
1088
1169
|
workspaceRoot,
|
|
1089
1170
|
changes,
|
|
1171
|
+
legacyOpenPrdMigration,
|
|
1172
|
+
errors: legacyOpenPrdMigration.errors,
|
|
1090
1173
|
};
|
|
1091
1174
|
}
|
|
1092
1175
|
|
|
@@ -1186,9 +1269,9 @@ async function loadWorkspace(projectRoot, options = {}) {
|
|
|
1186
1269
|
standardsFileManualTemplate: cjoin(workspaceRoot, 'standards', 'file-manual-template.md'),
|
|
1187
1270
|
standardsFolderReadmeTemplate: cjoin(workspaceRoot, 'standards', 'folder-readme-template.md'),
|
|
1188
1271
|
exportsDir: cjoin(workspaceRoot, 'exports'),
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1272
|
+
openprdExportDir: cjoin(workspaceRoot, 'exports', 'openprd'),
|
|
1273
|
+
openprdHandoffJson: cjoin(workspaceRoot, 'exports', 'openprd', 'handoff.json'),
|
|
1274
|
+
openprdHandoffMd: cjoin(workspaceRoot, 'exports', 'openprd', 'handoff.md'),
|
|
1192
1275
|
};
|
|
1193
1276
|
|
|
1194
1277
|
const workspaceCurrentState = await readJson(paths.workspaceCurrentState).catch(() => null);
|
|
@@ -1238,11 +1321,26 @@ const USER_CLARIFICATION_PATHS = new Set([
|
|
|
1238
1321
|
'users.primaryUsers',
|
|
1239
1322
|
'validation.community',
|
|
1240
1323
|
'validation.seedUsers',
|
|
1324
|
+
'validation.communityFit',
|
|
1241
1325
|
'validation.currentAlternative',
|
|
1326
|
+
'validation.painEvidence',
|
|
1242
1327
|
'validation.manualPath',
|
|
1328
|
+
'validation.manualPlaybook',
|
|
1243
1329
|
'validation.commitmentSignals',
|
|
1244
1330
|
'validation.firstValidationStep',
|
|
1245
1331
|
'validation.defaultAlivePlan',
|
|
1332
|
+
'validation.paymentProof',
|
|
1333
|
+
'validation.mvpSlice',
|
|
1334
|
+
'validation.weekendTest',
|
|
1335
|
+
'validation.smallestExecution',
|
|
1336
|
+
'validation.productizeGate',
|
|
1337
|
+
'validation.firstCustomerPath',
|
|
1338
|
+
'validation.pricingHypothesis',
|
|
1339
|
+
'validation.customerOneProfitability',
|
|
1340
|
+
'validation.growthDiscipline',
|
|
1341
|
+
'validation.reversibility',
|
|
1342
|
+
'validation.customerTruth',
|
|
1343
|
+
'validation.valuesFit',
|
|
1246
1344
|
'goals.goals',
|
|
1247
1345
|
'goals.successMetrics',
|
|
1248
1346
|
'scope.inScope',
|
|
@@ -1285,11 +1383,26 @@ const FIELD_PATH_TO_STATE_KEY = {
|
|
|
1285
1383
|
'users.stakeholders': 'stakeholders',
|
|
1286
1384
|
'validation.community': 'community',
|
|
1287
1385
|
'validation.seedUsers': 'seedUsers',
|
|
1386
|
+
'validation.communityFit': 'communityFit',
|
|
1288
1387
|
'validation.currentAlternative': 'currentAlternative',
|
|
1388
|
+
'validation.painEvidence': 'painEvidence',
|
|
1289
1389
|
'validation.manualPath': 'manualPath',
|
|
1390
|
+
'validation.manualPlaybook': 'manualPlaybook',
|
|
1290
1391
|
'validation.commitmentSignals': 'commitmentSignals',
|
|
1291
1392
|
'validation.firstValidationStep': 'firstValidationStep',
|
|
1292
1393
|
'validation.defaultAlivePlan': 'defaultAlivePlan',
|
|
1394
|
+
'validation.paymentProof': 'paymentProof',
|
|
1395
|
+
'validation.mvpSlice': 'mvpSlice',
|
|
1396
|
+
'validation.weekendTest': 'weekendTest',
|
|
1397
|
+
'validation.smallestExecution': 'smallestExecution',
|
|
1398
|
+
'validation.productizeGate': 'productizeGate',
|
|
1399
|
+
'validation.firstCustomerPath': 'firstCustomerPath',
|
|
1400
|
+
'validation.pricingHypothesis': 'pricingHypothesis',
|
|
1401
|
+
'validation.customerOneProfitability': 'customerOneProfitability',
|
|
1402
|
+
'validation.growthDiscipline': 'growthDiscipline',
|
|
1403
|
+
'validation.reversibility': 'reversibility',
|
|
1404
|
+
'validation.customerTruth': 'customerTruth',
|
|
1405
|
+
'validation.valuesFit': 'valuesFit',
|
|
1293
1406
|
'goals.goals': 'goals',
|
|
1294
1407
|
'goals.successMetrics': 'successMetrics',
|
|
1295
1408
|
'goals.acceptanceGoals': 'acceptanceGoals',
|
|
@@ -1728,9 +1841,17 @@ function coerceCapturedValue(pathString, rawValue, append = false) {
|
|
|
1728
1841
|
'users.stakeholders',
|
|
1729
1842
|
'validation.community',
|
|
1730
1843
|
'validation.seedUsers',
|
|
1844
|
+
'validation.communityFit',
|
|
1845
|
+
'validation.painEvidence',
|
|
1731
1846
|
'validation.manualPath',
|
|
1847
|
+
'validation.manualPlaybook',
|
|
1732
1848
|
'validation.commitmentSignals',
|
|
1733
1849
|
'validation.defaultAlivePlan',
|
|
1850
|
+
'validation.paymentProof',
|
|
1851
|
+
'validation.smallestExecution',
|
|
1852
|
+
'validation.productizeGate',
|
|
1853
|
+
'validation.firstCustomerPath',
|
|
1854
|
+
'validation.growthDiscipline',
|
|
1734
1855
|
'goals.goals',
|
|
1735
1856
|
'goals.successMetrics',
|
|
1736
1857
|
'goals.acceptanceGoals',
|
|
@@ -1850,6 +1971,11 @@ async function shouldIgnoreWorkspaceScenarioEntry(projectRoot, entry, managedPat
|
|
|
1850
1971
|
return true;
|
|
1851
1972
|
}
|
|
1852
1973
|
|
|
1974
|
+
if (entry.isFile() && entry.name === '.gitignore') {
|
|
1975
|
+
const content = await readText(path.join(projectRoot, entry.name)).catch(() => null);
|
|
1976
|
+
return gitignoreContainsOnlyOpenPrdOperationalEntries(content);
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1853
1979
|
if (entry.isFile() && WORKSPACE_SCENARIO_BOOTSTRAP_FILE_MARKERS.has(entry.name)) {
|
|
1854
1980
|
const marker = WORKSPACE_SCENARIO_BOOTSTRAP_FILE_MARKERS.get(entry.name);
|
|
1855
1981
|
const content = await readText(path.join(projectRoot, entry.name)).catch(() => null);
|
|
@@ -2151,10 +2277,12 @@ export {
|
|
|
2151
2277
|
loadWorkspace,
|
|
2152
2278
|
migrateWorkspaceSkeleton,
|
|
2153
2279
|
normalizeLaneSessionId,
|
|
2280
|
+
OPERATIONAL_GITIGNORE_ENTRIES,
|
|
2154
2281
|
normalizeVersionId,
|
|
2155
2282
|
persistWorkspaceCurrentState,
|
|
2156
2283
|
readVersionIndex,
|
|
2157
2284
|
readActiveRequirementLane,
|
|
2285
|
+
readOperationalGitignoreStatus,
|
|
2158
2286
|
readVersionSnapshot,
|
|
2159
2287
|
renderFlowDoc,
|
|
2160
2288
|
renderHandoffDoc,
|
|
@@ -5,7 +5,7 @@ import { analyzePrdSnapshot, buildPrdSnapshot, diffSnapshots, formatVersionId, r
|
|
|
5
5
|
import { formatProductTypeDisplay, formatProductTypeOptions, formatProductTypeQuestion, formatProductTypeSentence, formatTemplatePackDisplay } from './product-type-copy.js';
|
|
6
6
|
import { getDiagramReviewState } from './diagram-workspace.js';
|
|
7
7
|
import { exists, parseYamlText, readJson, readText, writeJson, writeText } from './fs-utils.js';
|
|
8
|
-
import { artifactBundlePaths, canonicalReviewPath, defaultReviewArtifactPath, openArtifactInBrowser, renderPlaygroundArtifact, renderPlaygroundMarkdown, renderPlaygroundPatch, renderReviewArtifact, renderReviewEntryHtml, writeHtmlArtifact } from './html-artifacts.js';
|
|
8
|
+
import { artifactBundlePaths, canonicalReviewPath, defaultReviewArtifactPath, openArtifactInBrowser, renderPlaygroundArtifact, renderPlaygroundMarkdown, renderPlaygroundPatch, renderReviewArtifact, renderReviewEntryHtml, reviewDesignDirections, writeHtmlArtifact } from './html-artifacts.js';
|
|
9
9
|
import { buildReleaseLedgerSummary } from './release-ledger.js';
|
|
10
10
|
import { assertReviewPresentationReady, getReviewPresentationGate } from './review-presentation.js';
|
|
11
11
|
import { syncSessionBindingFromReview, syncSessionBindingFromSnapshot } from './session-binding.js';
|
|
@@ -63,11 +63,26 @@ const REVIEW_PRESENTATION_RELEVANT_OVERRIDE_KEYS = new Set([
|
|
|
63
63
|
'stakeholders',
|
|
64
64
|
'community',
|
|
65
65
|
'seedUsers',
|
|
66
|
+
'communityFit',
|
|
66
67
|
'currentAlternative',
|
|
68
|
+
'painEvidence',
|
|
67
69
|
'manualPath',
|
|
70
|
+
'manualPlaybook',
|
|
68
71
|
'commitmentSignals',
|
|
69
72
|
'firstValidationStep',
|
|
70
73
|
'defaultAlivePlan',
|
|
74
|
+
'paymentProof',
|
|
75
|
+
'mvpSlice',
|
|
76
|
+
'weekendTest',
|
|
77
|
+
'smallestExecution',
|
|
78
|
+
'productizeGate',
|
|
79
|
+
'firstCustomerPath',
|
|
80
|
+
'pricingHypothesis',
|
|
81
|
+
'customerOneProfitability',
|
|
82
|
+
'growthDiscipline',
|
|
83
|
+
'reversibility',
|
|
84
|
+
'customerTruth',
|
|
85
|
+
'valuesFit',
|
|
71
86
|
'goals',
|
|
72
87
|
'successMetrics',
|
|
73
88
|
'acceptanceGoals',
|
|
@@ -123,11 +138,26 @@ const SYNTHESIZE_CONTENT_OVERRIDE_KEYS = new Set([
|
|
|
123
138
|
'stakeholders',
|
|
124
139
|
'community',
|
|
125
140
|
'seedUsers',
|
|
141
|
+
'communityFit',
|
|
126
142
|
'currentAlternative',
|
|
143
|
+
'painEvidence',
|
|
127
144
|
'manualPath',
|
|
145
|
+
'manualPlaybook',
|
|
128
146
|
'commitmentSignals',
|
|
129
147
|
'firstValidationStep',
|
|
130
148
|
'defaultAlivePlan',
|
|
149
|
+
'paymentProof',
|
|
150
|
+
'mvpSlice',
|
|
151
|
+
'weekendTest',
|
|
152
|
+
'smallestExecution',
|
|
153
|
+
'productizeGate',
|
|
154
|
+
'firstCustomerPath',
|
|
155
|
+
'pricingHypothesis',
|
|
156
|
+
'customerOneProfitability',
|
|
157
|
+
'growthDiscipline',
|
|
158
|
+
'reversibility',
|
|
159
|
+
'customerTruth',
|
|
160
|
+
'valuesFit',
|
|
131
161
|
'goals',
|
|
132
162
|
'successMetrics',
|
|
133
163
|
'acceptanceGoals',
|
|
@@ -242,15 +272,20 @@ function gateHasClarificationConfirmation(gate) {
|
|
|
242
272
|
return Boolean(gate?.clarificationConfirmedAt || gate?.status === 'clarification-confirmed');
|
|
243
273
|
}
|
|
244
274
|
|
|
245
|
-
function
|
|
275
|
+
function latestUserConfirmedCaptureTimestamp(currentState, paths = null) {
|
|
246
276
|
const timestamps = Object.entries(currentState?.captureMeta ?? {})
|
|
247
|
-
.filter(([field, entry]) =>
|
|
277
|
+
.filter(([field, entry]) => (!paths || paths.has(field)) && entry?.source === 'user-confirmed')
|
|
248
278
|
.map(([, entry]) => entry?.capturedAt)
|
|
249
279
|
.filter(Boolean)
|
|
250
280
|
.map(String);
|
|
251
281
|
return timestamps.length > 0 ? timestamps.sort().at(-1) : null;
|
|
252
282
|
}
|
|
253
283
|
|
|
284
|
+
function latestConfirmedClarificationCaptureTimestamp(currentState) {
|
|
285
|
+
return latestUserConfirmedCaptureTimestamp(currentState, USER_CLARIFICATION_PATHS)
|
|
286
|
+
|| latestUserConfirmedCaptureTimestamp(currentState);
|
|
287
|
+
}
|
|
288
|
+
|
|
254
289
|
function hasDerivedClarificationCaptureSince(currentState, gateAt) {
|
|
255
290
|
const reference = String(gateAt || '');
|
|
256
291
|
return Object.entries(currentState?.captureMeta ?? {}).some(([field, entry]) => {
|
|
@@ -616,13 +651,28 @@ function collectProjectRiskProbes(gate, snapshot) {
|
|
|
616
651
|
function buildValidationFraming(sections = {}) {
|
|
617
652
|
const validation = sections.validation ?? {};
|
|
618
653
|
return {
|
|
619
|
-
community: shortList(validation.community, '
|
|
620
|
-
seedUsers: shortList(validation.seedUsers, '
|
|
654
|
+
community: shortList(validation.community, '需要先确认第一批最容易触达的 1 到 3 个社区、渠道或人群。'),
|
|
655
|
+
seedUsers: shortList(validation.seedUsers, '需要先确认至少 3 到 10 个具体先找谁聊、先服务谁。'),
|
|
656
|
+
communityFit: shortList(validation.communityFit, '需要先确认你为什么算这个社区里的自己人、他们在哪里聚集、为什么现在就能触达。'),
|
|
621
657
|
currentAlternative: shortList(validation.currentAlternative, '需要先确认他们现在主要靠什么替代方案在解决。'),
|
|
658
|
+
painEvidence: shortList(validation.painEvidence, '需要先确认这个问题到底有多痛、用户是否已经在为更差的办法花时间或花钱。'),
|
|
622
659
|
manualPath: shortList(validation.manualPath, '需要先确认不做完整产品时先怎么手工交付价值。'),
|
|
660
|
+
manualPlaybook: shortList(validation.manualPlaybook, '需要先确认手工交付的触发条件、步骤、工具、耗时和交接点。'),
|
|
623
661
|
commitmentSignals: shortList(validation.commitmentSignals, '需要先确认什么真实承诺能证明不是口头兴趣。'),
|
|
624
662
|
firstValidationStep: shortList(validation.firstValidationStep, '需要先确认最低成本先做哪一步验证。'),
|
|
625
663
|
defaultAlivePlan: shortList(validation.defaultAlivePlan, '需要先确认验证阶段怎样先活下来。'),
|
|
664
|
+
paymentProof: shortList(validation.paymentProof, '需要先确认有没有 10 个潜在用户样本、3/10 付费意愿或更强交易信号。'),
|
|
665
|
+
mvpSlice: shortList(validation.mvpSlice, '需要先确认第一版到底只做哪一件事。'),
|
|
666
|
+
weekendTest: shortList(validation.weekendTest, '需要先确认能不能压成周末级 MVP 或更轻的试跑。'),
|
|
667
|
+
smallestExecution: shortList(validation.smallestExecution, '需要先确认能否先用 spreadsheet、表单或 no-code 工具把价值跑出来。'),
|
|
668
|
+
productizeGate: shortList(validation.productizeGate, '需要先确认达到什么条件才允许继续产品化或加功能。'),
|
|
669
|
+
firstCustomerPath: shortList(validation.firstCustomerPath, '需要先确认第一批客户最现实的触达顺序。'),
|
|
670
|
+
pricingHypothesis: shortList(validation.pricingHypothesis, '需要先确认从第一个客户开始准备怎么收费。'),
|
|
671
|
+
customerOneProfitability: shortList(validation.customerOneProfitability, '需要先确认第一个客户如何覆盖时间和交付成本。'),
|
|
672
|
+
growthDiscipline: shortList(validation.growthDiscipline, '需要先确认销售、launch 和增长阶段准备守哪些纪律。'),
|
|
673
|
+
reversibility: shortList(validation.reversibility, '需要先确认验证结果一般时这条路是否容易回退,是否会逼出重招聘、长期绑定或重平台化。'),
|
|
674
|
+
customerTruth: shortList(validation.customerTruth, '需要先确认这更像在解决客户真问题还是满足内部冲动。'),
|
|
675
|
+
valuesFit: shortList(validation.valuesFit, '需要先确认这条路是否符合团队想坚持的价值观,以及它是不是你愿意长期住进去的业务形态。'),
|
|
626
676
|
};
|
|
627
677
|
}
|
|
628
678
|
|
|
@@ -644,6 +694,21 @@ function hasSatisfiedConfirmedClarificationWriteback({ gate, currentState, analy
|
|
|
644
694
|
return !hasDerivedClarificationCaptureSince(currentState, gateAt);
|
|
645
695
|
}
|
|
646
696
|
|
|
697
|
+
function hasSatisfiedFreshRequirementWriteback({ gate, currentState, analysis, hasProductType }) {
|
|
698
|
+
if (!gate || gateHasClarificationConfirmation(gate)) {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
if (!hasProductType || Number(analysis?.missingRequiredFields ?? 0) > 0) {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
const gateAt = requirementGateReferenceTimestamp(gate);
|
|
705
|
+
if (!gateAt) {
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
const capturedAt = latestCaptureTimestamp(currentState);
|
|
709
|
+
return Boolean(capturedAt && String(capturedAt) >= String(gateAt));
|
|
710
|
+
}
|
|
711
|
+
|
|
647
712
|
function buildProjectFraming({ gate, snapshot, scenario, productType }) {
|
|
648
713
|
const sections = snapshot.sections ?? {};
|
|
649
714
|
const guardrailHints = [
|
|
@@ -973,11 +1038,26 @@ function buildInlineClarification({ clarification, reflection, presentation }) {
|
|
|
973
1038
|
`- 这轮先不碰:${projectFraming.nonGoals ?? '待确认'}。`,
|
|
974
1039
|
`- 必须守住:${projectFraming.guardrails ?? '待确认'}。`,
|
|
975
1040
|
`- 第一批最容易触达:${validationFraming.community ?? '待确认'}。`,
|
|
1041
|
+
`- 社区契合与触达依据:${validationFraming.communityFit ?? '待确认'}。`,
|
|
976
1042
|
`- 当前主要替代:${validationFraming.currentAlternative ?? '待确认'}。`,
|
|
1043
|
+
`- 痛点与替代证据:${validationFraming.painEvidence ?? '待确认'}。`,
|
|
977
1044
|
`- 先怎么手工交付:${validationFraming.manualPath ?? '待确认'}。`,
|
|
1045
|
+
`- 手工作战卡:${validationFraming.manualPlaybook ?? '待确认'}。`,
|
|
978
1046
|
`- 什么承诺才算真需求:${validationFraming.commitmentSignals ?? '待确认'}。`,
|
|
979
1047
|
`- 最低成本先验证:${validationFraming.firstValidationStep ?? '待确认'}。`,
|
|
980
1048
|
`- 怎么先活下来:${validationFraming.defaultAlivePlan ?? '待确认'}。`,
|
|
1049
|
+
`- 付费验证信号:${validationFraming.paymentProof ?? '待确认'}。`,
|
|
1050
|
+
`- 第一版只做一件事:${validationFraming.mvpSlice ?? '待确认'}。`,
|
|
1051
|
+
`- 周末级验证长什么样:${validationFraming.weekendTest ?? '待确认'}。`,
|
|
1052
|
+
`- 最小工具桥接:${validationFraming.smallestExecution ?? '待确认'}。`,
|
|
1053
|
+
`- 产品化门槛:${validationFraming.productizeGate ?? '待确认'}。`,
|
|
1054
|
+
`- 第一批客户路径:${validationFraming.firstCustomerPath ?? '待确认'}。`,
|
|
1055
|
+
`- 初始收费假设:${validationFraming.pricingHypothesis ?? '待确认'}。`,
|
|
1056
|
+
`- 客户 1 盈利路径:${validationFraming.customerOneProfitability ?? '待确认'}。`,
|
|
1057
|
+
`- 销售与增长纪律:${validationFraming.growthDiscipline ?? '待确认'}。`,
|
|
1058
|
+
`- 结果一般是否容易回退:${validationFraming.reversibility ?? '待确认'}。`,
|
|
1059
|
+
`- 更像客户真问题还是自嗨:${validationFraming.customerTruth ?? '待确认'}。`,
|
|
1060
|
+
`- 是否符合团队价值观:${validationFraming.valuesFit ?? '待确认'}。`,
|
|
981
1061
|
];
|
|
982
1062
|
if (projectFraming.architectureSignals) {
|
|
983
1063
|
lines.push(`- 这次更可能会影响:${projectFraming.architectureSignals}。`);
|
|
@@ -1101,7 +1181,17 @@ async function buildRequirementIntakeReflection({ projectRoot, ws, snapshot, ana
|
|
|
1101
1181
|
const validationLoopQuestion = reflectionQuestion(
|
|
1102
1182
|
'validation-loop',
|
|
1103
1183
|
'验证与创业闭环',
|
|
1104
|
-
'
|
|
1184
|
+
'请确认第一批最容易触达的社区或用户是谁、你为什么算这个社区里的自己人、他们现在靠什么替代、问题到底有多痛、如果先不做完整产品怎么手工交付、手工作战卡怎么写、什么真实承诺最能证明值得继续,以及最低成本验证和先活下来底线是什么。'
|
|
1185
|
+
);
|
|
1186
|
+
const mvpAndSalesQuestion = reflectionQuestion(
|
|
1187
|
+
'mvp-sales',
|
|
1188
|
+
'最小 MVP 与首批成交',
|
|
1189
|
+
'请确认第一版到底只做哪一件事、能不能压成周末级 MVP 或更轻试跑、能不能先用 spreadsheet 或 no-code 工具跑起来、第一批客户最现实的触达顺序是什么,以及从第一个客户开始准备怎么收费、有没有 10 个潜在用户样本和更强付费信号、达到什么条件才允许产品化。'
|
|
1190
|
+
);
|
|
1191
|
+
const minimalistReviewQuestion = reflectionQuestion(
|
|
1192
|
+
'minimalist-review',
|
|
1193
|
+
'可逆性 / 客户真问题 / 价值观',
|
|
1194
|
+
'请确认如果验证结果一般,这条路有多可逆,是否会逼出重招聘、长期绑定或重平台化;这更像在解决客户真问题还是满足内部技术冲动;以及它是否符合团队现在想坚持的价值观、是不是你愿意长期住进去的业务形态。'
|
|
1105
1195
|
);
|
|
1106
1196
|
const needsDeliveryShapeQuestion = !needsInterfaceSketch
|
|
1107
1197
|
&& (
|
|
@@ -1113,6 +1203,8 @@ async function buildRequirementIntakeReflection({ projectRoot, ws, snapshot, ana
|
|
|
1113
1203
|
reflectionQuestion('intent', '意图与目标', '请确认我理解得对不对:这次主要是谁在什么场景下遇到什么问题,第一版最想先改善什么结果?'),
|
|
1114
1204
|
lensQuestion,
|
|
1115
1205
|
validationLoopQuestion,
|
|
1206
|
+
mvpAndSalesQuestion,
|
|
1207
|
+
minimalistReviewQuestion,
|
|
1116
1208
|
reflectionQuestion('project-context', '项目影响范围', '结合当前项目,请确认第一版最小可用切片是什么;哪些已有模块、入口、流程或历史需求必须复用,哪些可以调整?'),
|
|
1117
1209
|
reflectionQuestion('scope-quality', '范围与验收', '请确认这次先做到哪一步就算有价值;哪些这轮先不动;哪些老用户习惯、现有业务结果或交付节奏不能被影响?'),
|
|
1118
1210
|
needsInterfaceSketch
|
|
@@ -1165,7 +1257,8 @@ async function buildRequirementIntakeReflection({ projectRoot, ws, snapshot, ana
|
|
|
1165
1257
|
`当前产品:${productName};当前产品场景:${formatProductTypeDisplay(productType, { fallback: '待确认' })};已记录问题:${currentProblem}`,
|
|
1166
1258
|
`当前范围线索:${currentScope}`,
|
|
1167
1259
|
`首轮画像:用户群体=${projectFraming.audience};产品形态=${projectFraming.productShape};第一版先做=${projectFraming.firstSlice}`,
|
|
1168
|
-
`验证闭环:可触达人群=${projectFraming.validationFraming?.community};当前替代=${projectFraming.validationFraming?.currentAlternative}
|
|
1260
|
+
`验证闭环:可触达人群=${projectFraming.validationFraming?.community};社区契合=${projectFraming.validationFraming?.communityFit};当前替代=${projectFraming.validationFraming?.currentAlternative};痛点证据=${projectFraming.validationFraming?.painEvidence};手工作战卡=${projectFraming.validationFraming?.manualPlaybook}`,
|
|
1261
|
+
`最小成交链:一件事 MVP=${projectFraming.validationFraming?.mvpSlice};周末级验证=${projectFraming.validationFraming?.weekendTest};最小工具桥接=${projectFraming.validationFraming?.smallestExecution};产品化门槛=${projectFraming.validationFraming?.productizeGate}`,
|
|
1169
1262
|
activeChange ? `仍有 active change:${activeChange.activeChange}(${activeChange.status}),需要和本轮需求分开评估。` : '当前没有检测到 active change 冲突。',
|
|
1170
1263
|
],
|
|
1171
1264
|
},
|
|
@@ -1175,7 +1268,8 @@ async function buildRequirementIntakeReflection({ projectRoot, ws, snapshot, ana
|
|
|
1175
1268
|
findings: [
|
|
1176
1269
|
`仍需确认的信息:${shortList(missing, '暂无明显缺口')}`,
|
|
1177
1270
|
`边界与约束:先不做=${projectFraming.nonGoals};不能破坏=${projectFraming.guardrails}`,
|
|
1178
|
-
`商业验证:承诺信号=${projectFraming.validationFraming?.commitmentSignals};低成本验证=${projectFraming.validationFraming?.firstValidationStep};先活下来=${projectFraming.validationFraming?.defaultAlivePlan}`,
|
|
1271
|
+
`商业验证:承诺信号=${projectFraming.validationFraming?.commitmentSignals};低成本验证=${projectFraming.validationFraming?.firstValidationStep};先活下来=${projectFraming.validationFraming?.defaultAlivePlan};付费验证=${projectFraming.validationFraming?.paymentProof};初始收费=${projectFraming.validationFraming?.pricingHypothesis};客户 1 盈利=${projectFraming.validationFraming?.customerOneProfitability};增长纪律=${projectFraming.validationFraming?.growthDiscipline}`,
|
|
1272
|
+
`极简判断:最小工具桥接=${projectFraming.validationFraming?.smallestExecution};产品化门槛=${projectFraming.validationFraming?.productizeGate};可逆性=${projectFraming.validationFraming?.reversibility};客户真问题=${projectFraming.validationFraming?.customerTruth};价值观一致性=${projectFraming.validationFraming?.valuesFit}`,
|
|
1179
1273
|
needsInterfaceSketch ? '需求看起来涉及界面或流程,需要先给用户确认草图或关键操作路径。' : `影响环节:${projectFraming.architectureSignals}`,
|
|
1180
1274
|
`业务提醒:${projectFraming.riskProbeSummary}`,
|
|
1181
1275
|
'进入实现前必须保留范围、非目标、异常路径和验收证据。',
|
|
@@ -1215,11 +1309,26 @@ function renderRequirementIntakeReflection(reflection) {
|
|
|
1215
1309
|
`| 暂不处理 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.nonGoals ?? '待补充')} |`,
|
|
1216
1310
|
`| 不能破坏 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.guardrails ?? '待补充')} |`,
|
|
1217
1311
|
`| 可触达人群 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.community ?? '待补充')} |`,
|
|
1312
|
+
`| 社区契合与触达依据 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.communityFit ?? '待补充')} |`,
|
|
1218
1313
|
`| 当前替代 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.currentAlternative ?? '待补充')} |`,
|
|
1314
|
+
`| 痛点与替代证据 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.painEvidence ?? '待补充')} |`,
|
|
1219
1315
|
`| 手工路径 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.manualPath ?? '待补充')} |`,
|
|
1316
|
+
`| 手工作战卡 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.manualPlaybook ?? '待补充')} |`,
|
|
1220
1317
|
`| 承诺信号 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.commitmentSignals ?? '待补充')} |`,
|
|
1221
1318
|
`| 最低成本验证 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.firstValidationStep ?? '待补充')} |`,
|
|
1222
1319
|
`| 先活下来 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.defaultAlivePlan ?? '待补充')} |`,
|
|
1320
|
+
`| 付费验证信号 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.paymentProof ?? '待补充')} |`,
|
|
1321
|
+
`| 第一版只做一件事 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.mvpSlice ?? '待补充')} |`,
|
|
1322
|
+
`| 周末级验证 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.weekendTest ?? '待补充')} |`,
|
|
1323
|
+
`| 最小工具桥接 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.smallestExecution ?? '待补充')} |`,
|
|
1324
|
+
`| 产品化门槛 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.productizeGate ?? '待补充')} |`,
|
|
1325
|
+
`| 第一批客户路径 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.firstCustomerPath ?? '待补充')} |`,
|
|
1326
|
+
`| 初始收费假设 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.pricingHypothesis ?? '待补充')} |`,
|
|
1327
|
+
`| 客户 1 盈利路径 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.customerOneProfitability ?? '待补充')} |`,
|
|
1328
|
+
`| 销售与增长纪律 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.growthDiscipline ?? '待补充')} |`,
|
|
1329
|
+
`| 可逆性判断 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.reversibility ?? '待补充')} |`,
|
|
1330
|
+
`| 客户真问题校验 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.customerTruth ?? '待补充')} |`,
|
|
1331
|
+
`| 价值观一致性 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.validationFraming?.valuesFit ?? '待补充')} |`,
|
|
1223
1332
|
`| 影响环节 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.architectureSignals ?? '待补充')} |`,
|
|
1224
1333
|
`| 业务提醒 | ${escapeMarkdownTableCell(reflection.projectContext.projectFraming?.riskProbeSummary ?? '待补充')} |`,
|
|
1225
1334
|
'',
|
|
@@ -1253,7 +1362,9 @@ function buildRequirementIntakeDepth(gate, reflection = null) {
|
|
|
1253
1362
|
const fallbackLayers = [
|
|
1254
1363
|
reflectionQuestion('product-context', '用户 / 产品形态 / 问题', '先确认:这是给谁用的、它更像个人产品 / 团队流程 / Agent 协作中的哪一种、为什么现在值得解决?'),
|
|
1255
1364
|
reflectionQuestion('product-outcome', '第一版切片 / 目标 / 成功标准', '请确认第一版最小可用切片是什么;解决后用户先能完成什么,用什么业务结果或验收标准判断有效?'),
|
|
1256
|
-
reflectionQuestion('product-validation', '验证闭环 / 承诺信号', '
|
|
1365
|
+
reflectionQuestion('product-validation', '验证闭环 / 承诺信号', '请确认第一批最容易触达的社区或用户是谁、你为什么算这个社区里的自己人、他们现在靠什么替代、问题到底有多痛、如果先不做完整产品怎么手工交付、手工作战卡怎么写、什么真实承诺最能证明值得继续,以及最低成本验证和先活下来底线是什么。'),
|
|
1366
|
+
reflectionQuestion('product-mvp-sales', '最小 MVP / 首批成交', '请确认第一版到底只做哪一件事、能不能压成周末级 MVP 或更轻试跑、第一批客户最现实的触达顺序是什么,以及从第一个客户开始准备怎么收费、怎么验证客户 1 也能打平时间和交付成本。'),
|
|
1367
|
+
reflectionQuestion('product-minimalist-review', '可逆性 / 客户真问题 / 价值观', '请确认如果验证结果一般,这条路有多可逆;这更像在解决客户真问题还是满足内部技术冲动;以及它是否符合团队现在想坚持的价值观、是不是你愿意长期住进去的业务形态。'),
|
|
1257
1368
|
reflectionQuestion('product-flow', '范围 / 非目标 / 异常路径', '请拆出本轮先做什么、不做什么、哪些既有行为不能被破坏,以及关键失败路径和恢复方式。'),
|
|
1258
1369
|
reflectionQuestion(
|
|
1259
1370
|
'product-detail',
|
|
@@ -1654,6 +1765,44 @@ async function reviewWorkspace(projectRoot, options = {}) {
|
|
|
1654
1765
|
requiredCommand: presentationGate.requiredCommand,
|
|
1655
1766
|
};
|
|
1656
1767
|
}
|
|
1768
|
+
let selectedDesignDirection = null;
|
|
1769
|
+
if (options.direction) {
|
|
1770
|
+
if (!options.mark) {
|
|
1771
|
+
return {
|
|
1772
|
+
ok: false,
|
|
1773
|
+
action: 'review',
|
|
1774
|
+
projectRoot,
|
|
1775
|
+
versionId: snapshot.versionId,
|
|
1776
|
+
workUnitId: snapshot.workUnitId ?? null,
|
|
1777
|
+
status: 'blocked',
|
|
1778
|
+
errors: ['--direction 需要配合 --mark 一起使用,例如 --mark confirmed --direction direction-1。'],
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
const directions = reviewDesignDirections(snapshot)?.directions ?? [];
|
|
1782
|
+
const found = directions.find((item) => `${item?.id ?? ''}`.trim() === `${options.direction}`.trim());
|
|
1783
|
+
if (!found) {
|
|
1784
|
+
return {
|
|
1785
|
+
ok: false,
|
|
1786
|
+
action: 'review',
|
|
1787
|
+
projectRoot,
|
|
1788
|
+
versionId: snapshot.versionId,
|
|
1789
|
+
workUnitId: snapshot.workUnitId ?? null,
|
|
1790
|
+
status: 'blocked',
|
|
1791
|
+
errors: [
|
|
1792
|
+
directions.length === 0
|
|
1793
|
+
? '当前版本没有 designDirections 交互方向,无法记录 --direction 选择。'
|
|
1794
|
+
: `未找到交互方向 ${options.direction};可选: ${directions.map((item) => item?.id).filter(Boolean).join(', ')}。`,
|
|
1795
|
+
],
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
selectedDesignDirection = {
|
|
1799
|
+
id: `${found.id}`.trim(),
|
|
1800
|
+
title: `${found.title ?? found.id}`.trim(),
|
|
1801
|
+
image: found.image ?? null,
|
|
1802
|
+
selectedAt: timestamp(),
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1657
1806
|
const reviewFiles = await writeReviewFiles(ws, snapshot, { writeEntry: isLatest });
|
|
1658
1807
|
const bindingBefore = await readWorkUnitBinding(ws, snapshot.workUnitId);
|
|
1659
1808
|
const before = isLatest
|
|
@@ -1685,6 +1834,10 @@ async function reviewWorkspace(projectRoot, options = {}) {
|
|
|
1685
1834
|
stableArtifact: reviewFiles.canonicalReview,
|
|
1686
1835
|
updatedAt: timestamp(),
|
|
1687
1836
|
notes: options.notes ?? null,
|
|
1837
|
+
designDirection: selectedDesignDirection
|
|
1838
|
+
?? (ws.data.currentState?.reviewStatus?.versionId === snapshot.versionId
|
|
1839
|
+
? ws.data.currentState?.reviewStatus?.designDirection ?? null
|
|
1840
|
+
: null),
|
|
1688
1841
|
},
|
|
1689
1842
|
};
|
|
1690
1843
|
await persistWorkspaceCurrentState(ws, currentState);
|
|
@@ -1700,11 +1853,13 @@ async function reviewWorkspace(projectRoot, options = {}) {
|
|
|
1700
1853
|
versionId: snapshot.versionId,
|
|
1701
1854
|
workUnitId: snapshot.workUnitId ?? null,
|
|
1702
1855
|
status,
|
|
1856
|
+
designDirection: selectedDesignDirection?.id ?? null,
|
|
1703
1857
|
});
|
|
1704
1858
|
await appendProgress(ws, [
|
|
1705
1859
|
`PRD 评审状态: ${status}。`,
|
|
1706
1860
|
`版本: ${snapshot.versionId}。`,
|
|
1707
1861
|
snapshot.workUnitId ? `工作单元: ${snapshot.workUnitId}。` : null,
|
|
1862
|
+
selectedDesignDirection ? `选定交互方向: ${selectedDesignDirection.id}(${selectedDesignDirection.title})。` : null,
|
|
1708
1863
|
]);
|
|
1709
1864
|
await syncSessionBindingFromReview(projectRoot, snapshot, {
|
|
1710
1865
|
sessionId: ws.data.currentSessionId ?? null,
|
|
@@ -1738,6 +1893,7 @@ async function reviewWorkspace(projectRoot, options = {}) {
|
|
|
1738
1893
|
status: after.status,
|
|
1739
1894
|
previousStatus: before.status,
|
|
1740
1895
|
marked,
|
|
1896
|
+
designDirection: selectedDesignDirection ?? null,
|
|
1741
1897
|
reviewArtifact: after.entryArtifact ?? reviewFiles.activeReviewEntry,
|
|
1742
1898
|
stableReviewArtifact: after.artifact,
|
|
1743
1899
|
reviewPath: after.artifact,
|
|
@@ -2084,6 +2240,12 @@ async function computeWorkspaceGuidance(ws, options = {}) {
|
|
|
2084
2240
|
analysis,
|
|
2085
2241
|
hasProductType,
|
|
2086
2242
|
});
|
|
2243
|
+
const intakeSatisfiedByFreshWriteback = hasSatisfiedFreshRequirementWriteback({
|
|
2244
|
+
gate: requirementGate,
|
|
2245
|
+
currentState,
|
|
2246
|
+
analysis,
|
|
2247
|
+
hasProductType,
|
|
2248
|
+
});
|
|
2087
2249
|
const intakeReflection = await buildRequirementIntakeReflection({
|
|
2088
2250
|
projectRoot: ws.projectRoot,
|
|
2089
2251
|
ws,
|
|
@@ -2101,7 +2263,7 @@ async function computeWorkspaceGuidance(ws, options = {}) {
|
|
|
2101
2263
|
prdReviewState,
|
|
2102
2264
|
limit: Number(options.questionLimit ?? 5),
|
|
2103
2265
|
}), requirementGate, intakeReflection, {
|
|
2104
|
-
satisfied: intakeSatisfiedByReview || intakeSatisfiedByConfirmedWriteback,
|
|
2266
|
+
satisfied: intakeSatisfiedByReview || intakeSatisfiedByConfirmedWriteback || intakeSatisfiedByFreshWriteback,
|
|
2105
2267
|
});
|
|
2106
2268
|
|
|
2107
2269
|
let nextAction = 'synthesize';
|