@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.
Files changed (147) hide show
  1. package/.openprd/changes/adaptive-project-framing/.openprd.yaml +2 -0
  2. package/.openprd/changes/adaptive-project-framing/design.md +47 -0
  3. package/.openprd/changes/adaptive-project-framing/proposal.md +35 -0
  4. package/.openprd/changes/adaptive-project-framing/specs/agent-requirements/spec.md +16 -0
  5. package/.openprd/changes/adaptive-project-framing/task-events.jsonl +20 -0
  6. package/.openprd/changes/adaptive-project-framing/tasks.md +340 -0
  7. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/.openprd.yaml +2 -0
  8. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/design.md +34 -0
  9. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/proposal.md +23 -0
  10. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/specs/agent-requirements/spec.md +16 -0
  11. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/task-events.jsonl +14 -0
  12. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/tasks.md +238 -0
  13. package/.openprd/changes/loop-isolated-worktree-commit/.openprd.yaml +2 -0
  14. package/.openprd/changes/loop-isolated-worktree-commit/design.md +53 -0
  15. package/.openprd/changes/loop-isolated-worktree-commit/proposal.md +38 -0
  16. package/.openprd/changes/loop-isolated-worktree-commit/specs/agent-requirements/spec.md +16 -0
  17. package/.openprd/changes/loop-isolated-worktree-commit/task-events.jsonl +22 -0
  18. package/.openprd/changes/loop-isolated-worktree-commit/tasks.md +357 -0
  19. package/.openprd/changes/openprd/.openprd.yaml +2 -0
  20. package/.openprd/changes/openprd/design.md +46 -0
  21. package/.openprd/changes/openprd/proposal.md +36 -0
  22. package/.openprd/changes/openprd/specs/agent-requirements/spec.md +16 -0
  23. package/.openprd/changes/openprd/task-events.jsonl +23 -0
  24. package/.openprd/changes/openprd/tasks.md +242 -0
  25. package/.openprd/changes/openprd-generated-change/.openprd.yaml +2 -0
  26. package/.openprd/changes/openprd-generated-change/design.md +50 -0
  27. package/.openprd/changes/openprd-generated-change/proposal.md +37 -0
  28. package/.openprd/changes/openprd-generated-change/specs/agent-requirements/spec.md +16 -0
  29. package/.openprd/changes/openprd-generated-change/task-events.jsonl +21 -0
  30. package/.openprd/changes/openprd-generated-change/tasks.md +357 -0
  31. package/.openprd/changes/pm-human-in-the-loop/.openprd.yaml +2 -0
  32. package/.openprd/changes/pm-human-in-the-loop/design.md +38 -0
  33. package/.openprd/changes/pm-human-in-the-loop/proposal.md +29 -0
  34. package/.openprd/changes/pm-human-in-the-loop/specs/agent-requirements/spec.md +16 -0
  35. package/.openprd/changes/pm-human-in-the-loop/task-events.jsonl +18 -0
  36. package/.openprd/changes/pm-human-in-the-loop/tasks.md +306 -0
  37. package/.openprd/changes/test-strategy-router/.openprd.yaml +2 -0
  38. package/.openprd/changes/test-strategy-router/design.md +65 -0
  39. package/.openprd/changes/test-strategy-router/proposal.md +45 -0
  40. package/.openprd/changes/test-strategy-router/specs/agent-requirements/spec.md +16 -0
  41. package/.openprd/changes/test-strategy-router/task-events.jsonl +1 -0
  42. package/.openprd/changes/test-strategy-router/tasks.md +132 -0
  43. package/.openprd/changes/verify-boundaries-and-historical-refresh/.openprd.yaml +2 -0
  44. package/.openprd/changes/verify-boundaries-and-historical-refresh/design.md +28 -0
  45. package/.openprd/changes/verify-boundaries-and-historical-refresh/proposal.md +23 -0
  46. package/.openprd/changes/verify-boundaries-and-historical-refresh/specs/agent-requirements/spec.md +16 -0
  47. package/.openprd/changes/verify-boundaries-and-historical-refresh/task-events.jsonl +11 -0
  48. package/.openprd/changes/verify-boundaries-and-historical-refresh/tasks.md +66 -0
  49. package/.openprd/changes/vibe-coding-dev-check/.openprd.yaml +2 -0
  50. package/.openprd/changes/vibe-coding-dev-check/design.md +37 -0
  51. package/.openprd/changes/vibe-coding-dev-check/proposal.md +28 -0
  52. package/.openprd/changes/vibe-coding-dev-check/specs/agent-requirements/spec.md +16 -0
  53. package/.openprd/changes/vibe-coding-dev-check/task-events.jsonl +18 -0
  54. package/.openprd/changes/vibe-coding-dev-check/tasks.md +306 -0
  55. package/.openprd/design/README.md +11 -6
  56. package/.openprd/design/README_EN.md +4 -1
  57. package/.openprd/design/active/asset-spec.md +6 -0
  58. package/.openprd/design/active/direction-plan.md +5 -5
  59. package/.openprd/design/active/selected-direction.md +2 -0
  60. package/.openprd/design/anti-slop.md +26 -1
  61. package/.openprd/design/assets/surface-presets.md +6 -0
  62. package/.openprd/design/checklists/ui-quality-gate.md +7 -0
  63. package/.openprd/design/lenses/frontend-lenses.md +28 -0
  64. package/.openprd/design/recipes/content-experience.md +8 -1
  65. package/.openprd/design/recipes/operational-tool.md +7 -0
  66. package/.openprd/design/recipes/product-launch.md +8 -1
  67. package/.openprd/design/templates/README.md +6 -3
  68. package/.openprd/design/templates/README_EN.md +6 -3
  69. package/.openprd/design/templates/ops-dashboard.html +8 -1
  70. package/.openprd/design/templates/product-launch.html +2 -1
  71. package/.openprd/design/themes/theme-catalog.json +23 -5
  72. package/.openprd/engagements/active/prd.md +137 -112
  73. package/.openprd/standards/config.json +7 -1
  74. package/AGENTS.md +13 -3
  75. package/README.md +127 -14
  76. package/README_EN.md +156 -13
  77. package/package.json +1 -1
  78. package/scripts/dev-check-wrapup-copy.mjs +46 -21
  79. package/skills/openprd-diagram-review/SKILL.md +27 -2
  80. package/skills/openprd-diagram-review/references/explanation-svg-patterns.md +131 -0
  81. package/skills/openprd-discovery-loop/SKILL.md +2 -2
  82. package/skills/openprd-frontend-design/SKILL.md +44 -20
  83. package/skills/openprd-frontend-design/references/design-asset-contract.md +6 -0
  84. package/skills/openprd-frontend-design/references/direction-engine.md +29 -4
  85. package/skills/openprd-harness/SKILL.md +22 -16
  86. package/skills/openprd-quality/SKILL.md +10 -8
  87. package/skills/openprd-requirement-intake/SKILL.md +2 -0
  88. package/skills/openprd-router/SKILL.md +2 -1
  89. package/skills/openprd-shared/SKILL.md +26 -16
  90. package/skills/openprd-standards/SKILL.md +4 -2
  91. package/src/agent-canonical-content.js +810 -0
  92. package/src/agent-integration.js +258 -777
  93. package/src/canvas-app-client-app.js +838 -0
  94. package/src/canvas-app-client-helpers.js +1138 -0
  95. package/src/canvas-app-styles.js +898 -0
  96. package/src/canvas-app.html.js +135 -0
  97. package/src/canvas-binding.js +414 -0
  98. package/src/canvas-codex-relay.js +430 -0
  99. package/src/canvas-i18n.js +282 -0
  100. package/src/canvas-scene.js +377 -0
  101. package/src/canvas-session-store.js +148 -0
  102. package/src/canvas-workspace.js +1264 -0
  103. package/src/cli/args.js +72 -4
  104. package/src/cli/basic-print.js +3 -0
  105. package/src/cli/canvas-print.js +106 -0
  106. package/src/cli/doctor-print.js +20 -0
  107. package/src/cli/print.js +2 -0
  108. package/src/cli/quality-commands.js +222 -0
  109. package/src/cli/quality-print.js +28 -2
  110. package/src/cli/run-print.js +5 -0
  111. package/src/cli/workflow-print.js +3 -0
  112. package/src/codex-app-server-relay.js +251 -0
  113. package/src/codex-hook-runner-template.mjs +978 -58
  114. package/src/design-starter-support.js +2 -2
  115. package/src/design-starter.js +39 -3
  116. package/src/dev-standards.js +125 -10
  117. package/src/diagram-core.js +6 -20
  118. package/src/discovery.js +2 -8
  119. package/src/docs-compact.js +125 -0
  120. package/src/execution-strategy.js +1 -1
  121. package/src/fleet.js +9 -3
  122. package/src/guide.js +227 -0
  123. package/src/html-artifacts.js +428 -7
  124. package/src/knowledge.js +45 -11
  125. package/src/language-policy.js +63 -3
  126. package/src/learning-review.js +1 -1
  127. package/src/loop.js +1 -1
  128. package/src/openprd.js +68 -131
  129. package/src/openspec/change-validate.js +2 -2
  130. package/src/openspec/constants.js +6 -7
  131. package/src/openspec/execute.js +2 -2
  132. package/src/openspec/generate.js +318 -102
  133. package/src/openspec/migration.js +168 -0
  134. package/src/openspec/paths.js +1 -22
  135. package/src/prd-core.js +1 -1
  136. package/src/quality-visual-review.js +257 -14
  137. package/src/quality.js +46 -8
  138. package/src/review-presentation.js +60 -0
  139. package/src/run-harness.js +41 -3
  140. package/src/session-evidence.js +363 -0
  141. package/src/standards.js +64 -0
  142. package/src/test-strategy.js +7 -4
  143. package/src/visual-compare-centering.js +622 -0
  144. package/src/visual-compare-core.js +1123 -0
  145. package/src/visual-compare.js +620 -698
  146. package/src/workspace-core.js +94 -4
  147. package/src/workspace-workflow.js +46 -1
@@ -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: true,
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
- openspecExportDir: cjoin(workspaceRoot, 'exports', 'openspec'),
1190
- openspecHandoffJson: cjoin(workspaceRoot, 'exports', 'openspec', 'handoff.json'),
1191
- openspecHandoffMd: cjoin(workspaceRoot, 'exports', 'openspec', 'handoff.md'),
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,