@openprd/cli 0.1.11 → 0.1.19
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/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/openprd-generated-change/.openprd.yaml +2 -0
- package/.openprd/changes/openprd-generated-change/design.md +50 -0
- package/.openprd/changes/openprd-generated-change/proposal.md +37 -0
- package/.openprd/changes/openprd-generated-change/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/openprd-generated-change/task-events.jsonl +21 -0
- package/.openprd/changes/openprd-generated-change/tasks.md +357 -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 +137 -112
- package/.openprd/standards/config.json +7 -1
- package/AGENTS.md +13 -3
- package/README.md +127 -14
- package/README_EN.md +156 -13
- package/package.json +1 -1
- package/scripts/dev-check-wrapup-copy.mjs +46 -21
- 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 +2 -0
- package/skills/openprd-router/SKILL.md +2 -1
- package/skills/openprd-shared/SKILL.md +26 -16
- package/skills/openprd-standards/SKILL.md +4 -2
- package/src/agent-canonical-content.js +810 -0
- package/src/agent-integration.js +258 -777
- package/src/canvas-app-client-app.js +838 -0
- package/src/canvas-app-client-helpers.js +1138 -0
- package/src/canvas-app-styles.js +898 -0
- package/src/canvas-app.html.js +135 -0
- package/src/canvas-binding.js +414 -0
- package/src/canvas-codex-relay.js +430 -0
- package/src/canvas-i18n.js +282 -0
- package/src/canvas-scene.js +377 -0
- package/src/canvas-session-store.js +148 -0
- package/src/canvas-workspace.js +1264 -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 +28 -2
- 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 +978 -58
- 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 +6 -20
- 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 +428 -7
- package/src/knowledge.js +45 -11
- 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 +1 -1
- package/src/quality-visual-review.js +257 -14
- package/src/quality.js +46 -8
- 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 +94 -4
- package/src/workspace-workflow.js +46 -1
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);
|
|
@@ -1888,6 +1971,11 @@ async function shouldIgnoreWorkspaceScenarioEntry(projectRoot, entry, managedPat
|
|
|
1888
1971
|
return true;
|
|
1889
1972
|
}
|
|
1890
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
|
+
|
|
1891
1979
|
if (entry.isFile() && WORKSPACE_SCENARIO_BOOTSTRAP_FILE_MARKERS.has(entry.name)) {
|
|
1892
1980
|
const marker = WORKSPACE_SCENARIO_BOOTSTRAP_FILE_MARKERS.get(entry.name);
|
|
1893
1981
|
const content = await readText(path.join(projectRoot, entry.name)).catch(() => null);
|
|
@@ -2189,10 +2277,12 @@ export {
|
|
|
2189
2277
|
loadWorkspace,
|
|
2190
2278
|
migrateWorkspaceSkeleton,
|
|
2191
2279
|
normalizeLaneSessionId,
|
|
2280
|
+
OPERATIONAL_GITIGNORE_ENTRIES,
|
|
2192
2281
|
normalizeVersionId,
|
|
2193
2282
|
persistWorkspaceCurrentState,
|
|
2194
2283
|
readVersionIndex,
|
|
2195
2284
|
readActiveRequirementLane,
|
|
2285
|
+
readOperationalGitignoreStatus,
|
|
2196
2286
|
readVersionSnapshot,
|
|
2197
2287
|
renderFlowDoc,
|
|
2198
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';
|
|
@@ -1765,6 +1765,44 @@ async function reviewWorkspace(projectRoot, options = {}) {
|
|
|
1765
1765
|
requiredCommand: presentationGate.requiredCommand,
|
|
1766
1766
|
};
|
|
1767
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
|
+
|
|
1768
1806
|
const reviewFiles = await writeReviewFiles(ws, snapshot, { writeEntry: isLatest });
|
|
1769
1807
|
const bindingBefore = await readWorkUnitBinding(ws, snapshot.workUnitId);
|
|
1770
1808
|
const before = isLatest
|
|
@@ -1796,6 +1834,10 @@ async function reviewWorkspace(projectRoot, options = {}) {
|
|
|
1796
1834
|
stableArtifact: reviewFiles.canonicalReview,
|
|
1797
1835
|
updatedAt: timestamp(),
|
|
1798
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),
|
|
1799
1841
|
},
|
|
1800
1842
|
};
|
|
1801
1843
|
await persistWorkspaceCurrentState(ws, currentState);
|
|
@@ -1811,11 +1853,13 @@ async function reviewWorkspace(projectRoot, options = {}) {
|
|
|
1811
1853
|
versionId: snapshot.versionId,
|
|
1812
1854
|
workUnitId: snapshot.workUnitId ?? null,
|
|
1813
1855
|
status,
|
|
1856
|
+
designDirection: selectedDesignDirection?.id ?? null,
|
|
1814
1857
|
});
|
|
1815
1858
|
await appendProgress(ws, [
|
|
1816
1859
|
`PRD 评审状态: ${status}。`,
|
|
1817
1860
|
`版本: ${snapshot.versionId}。`,
|
|
1818
1861
|
snapshot.workUnitId ? `工作单元: ${snapshot.workUnitId}。` : null,
|
|
1862
|
+
selectedDesignDirection ? `选定交互方向: ${selectedDesignDirection.id}(${selectedDesignDirection.title})。` : null,
|
|
1819
1863
|
]);
|
|
1820
1864
|
await syncSessionBindingFromReview(projectRoot, snapshot, {
|
|
1821
1865
|
sessionId: ws.data.currentSessionId ?? null,
|
|
@@ -1849,6 +1893,7 @@ async function reviewWorkspace(projectRoot, options = {}) {
|
|
|
1849
1893
|
status: after.status,
|
|
1850
1894
|
previousStatus: before.status,
|
|
1851
1895
|
marked,
|
|
1896
|
+
designDirection: selectedDesignDirection ?? null,
|
|
1852
1897
|
reviewArtifact: after.entryArtifact ?? reviewFiles.activeReviewEntry,
|
|
1853
1898
|
stableReviewArtifact: after.artifact,
|
|
1854
1899
|
reviewPath: after.artifact,
|