@openprd/cli 0.1.1 → 0.1.8

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 (137) hide show
  1. package/.openprd/README.md +43 -69
  2. package/.openprd/README_EN.md +84 -0
  3. package/.openprd/benchmarks/index.md +7 -0
  4. package/.openprd/benchmarks/sources.yaml +25 -3
  5. package/.openprd/discovery/config.json +16 -2
  6. package/.openprd/engagements/active/flows.md +19 -14
  7. package/.openprd/engagements/active/handoff.md +11 -4
  8. package/.openprd/engagements/active/prd.md +99 -71
  9. package/.openprd/engagements/active/review.html +4 -4
  10. package/.openprd/engagements/active/roles.md +9 -8
  11. package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +4 -4
  12. package/.openprd/engagements/work-units/wu-20260602113956-a99b5b88.json +18 -0
  13. package/.openprd/engagements/work-units/wu-20260602122244-78656aaf.json +18 -0
  14. package/.openprd/engagements/work-units/wu-20260602122442-e96489e2.json +18 -0
  15. package/.openprd/engagements/work-units/wu-20260602132835-695429e8.json +18 -0
  16. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/candidate.json +78 -0
  17. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/diagnostic-report.json +129 -0
  18. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/root-cause-candidates.json +41 -0
  19. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/timeline.json +14 -0
  20. package/.openprd/knowledge/drafts/openprd-experience-diagnostic-candidate-turn-1780116203372-5f266a79e968c758/SKILL.md +49 -0
  21. package/.openprd/knowledge/index.json +44 -4
  22. package/.openprd/reviews/v0001.html +195 -129
  23. package/.openprd/reviews/v0002.html +1150 -0
  24. package/.openprd/reviews/v0003.html +1150 -0
  25. package/.openprd/reviews/v0004.html +1150 -0
  26. package/.openprd/reviews/v0005.html +1150 -0
  27. package/.openprd/standards/config.json +12 -9
  28. package/.openprd/state/changes.json +17 -2
  29. package/.openprd/state/current.json +399 -63
  30. package/.openprd/state/release-ledger.json +344 -0
  31. package/.openprd/state/version-index.json +52 -0
  32. package/.openprd/state/versions/v0002.json +264 -0
  33. package/.openprd/state/versions/v0002.md +183 -0
  34. package/.openprd/state/versions/v0003.json +269 -0
  35. package/.openprd/state/versions/v0003.md +188 -0
  36. package/.openprd/state/versions/v0004.json +274 -0
  37. package/.openprd/state/versions/v0004.md +193 -0
  38. package/.openprd/state/versions/v0005.json +299 -0
  39. package/.openprd/state/versions/v0005.md +189 -0
  40. package/.openprd/templates/agent/intake.md +5 -4
  41. package/.openprd/templates/b2b/intake.md +5 -4
  42. package/.openprd/templates/base/intake.md +10 -4
  43. package/.openprd/templates/company/README.md +9 -7
  44. package/.openprd/templates/company/README_EN.md +12 -0
  45. package/.openprd/templates/consumer/intake.md +5 -4
  46. package/.openprd/templates/industry/README.md +12 -10
  47. package/.openprd/templates/industry/README_EN.md +18 -0
  48. package/.openprd/templates/project/README.md +11 -9
  49. package/.openprd/templates/project/README_EN.md +16 -0
  50. package/.openprd/templates/session/README.md +11 -9
  51. package/.openprd/templates/session/README_EN.md +16 -0
  52. package/AGENTS.md +12 -8
  53. package/README.md +399 -438
  54. package/README_CN.md +4 -578
  55. package/README_EN.md +850 -0
  56. package/docs/assets/openprd-requirement-routing-en.png +0 -0
  57. package/docs/assets/openprd-requirement-routing-en.svg +102 -0
  58. package/docs/assets/openprd-requirement-routing-zh-refined.png +0 -0
  59. package/docs/assets/openprd-requirement-routing-zh.png +0 -0
  60. package/docs/assets/openprd-requirement-routing-zh.svg +102 -0
  61. package/package.json +6 -2
  62. package/scripts/dev-check-wrapup-copy.mjs +110 -0
  63. package/scripts/openprd-github-release-notes.mjs +99 -0
  64. package/scripts/quality-perf-check.mjs +203 -0
  65. package/skills/openprd-benchmark-router/SKILL.md +1 -0
  66. package/skills/openprd-benchmark-router/references/benchmark-sources.md +1 -0
  67. package/skills/openprd-benchmark-router/references/source-policy.md +2 -0
  68. package/skills/openprd-discovery-loop/SKILL.md +2 -2
  69. package/skills/openprd-harness/SKILL.md +46 -24
  70. package/skills/openprd-harness/references/workflow-gates.md +15 -0
  71. package/skills/openprd-quality/SKILL.md +10 -4
  72. package/skills/openprd-requirement-intake/SKILL.md +31 -20
  73. package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -6
  74. package/skills/openprd-requirement-intake/references/routing-rubric.md +10 -2
  75. package/skills/openprd-router/SKILL.md +2 -2
  76. package/skills/openprd-shared/SKILL.md +51 -23
  77. package/skills/openprd-standards/SKILL.md +2 -1
  78. package/src/agent-integration.js +265 -65
  79. package/src/benchmark/constants.js +107 -0
  80. package/src/benchmark/operations.js +235 -0
  81. package/src/benchmark/registry.js +64 -0
  82. package/src/benchmark/render.js +115 -0
  83. package/src/benchmark/source.js +617 -0
  84. package/src/benchmark/storage.js +121 -0
  85. package/src/benchmark/verify.js +235 -0
  86. package/src/benchmark.js +50 -851
  87. package/src/change-summary.js +339 -0
  88. package/src/cli/args.js +67 -6
  89. package/src/cli/basic-print.js +365 -0
  90. package/src/cli/benchmark-print.js +91 -0
  91. package/src/cli/change-print.js +221 -0
  92. package/src/cli/doctor-print.js +268 -0
  93. package/src/cli/growth-print.js +176 -0
  94. package/src/cli/print.js +73 -1384
  95. package/src/cli/quality-print.js +284 -0
  96. package/src/cli/run-print.js +297 -0
  97. package/src/cli/shared-print.js +127 -0
  98. package/src/cli/workflow-print.js +195 -0
  99. package/src/codex-hook-runner-template.mjs +639 -117
  100. package/src/codex-runtime.js +324 -0
  101. package/src/dev-standards.js +178 -5
  102. package/src/diagram-core.js +5 -5
  103. package/src/discovery.js +2 -1
  104. package/src/execution-strategy.js +369 -0
  105. package/src/fleet.js +4 -0
  106. package/src/github-release.js +156 -0
  107. package/src/growth.js +311 -13
  108. package/src/html-artifact-utils.js +25 -0
  109. package/src/html-artifacts.js +157 -1596
  110. package/src/knowledge.js +1176 -75
  111. package/src/language-policy.js +2 -112
  112. package/src/learning-html-artifact.js +1031 -0
  113. package/src/learning-review.js +3 -2
  114. package/src/loop.js +280 -9
  115. package/src/openprd.js +341 -38
  116. package/src/openspec/change-validate.js +0 -9
  117. package/src/openspec/execute.js +79 -3
  118. package/src/openspec/generate.js +33 -20
  119. package/src/openspec/tasks.js +33 -2
  120. package/src/prd-core.js +10 -9
  121. package/src/product-type-copy.js +69 -0
  122. package/src/quality-html-artifact.js +108 -9
  123. package/src/quality-learning.js +30 -0
  124. package/src/quality-visual-review.js +237 -0
  125. package/src/quality.js +329 -43
  126. package/src/registry-hygiene.js +54 -0
  127. package/src/release-ledger.js +413 -0
  128. package/src/review-presentation.js +12 -6
  129. package/src/run-harness.js +722 -48
  130. package/src/session-binding.js +40 -3
  131. package/src/session-registry.js +159 -0
  132. package/src/standards.js +5 -3
  133. package/src/test-strategy.js +386 -0
  134. package/src/visual-compare.js +915 -34
  135. package/src/work-unit-migration.js +5 -1
  136. package/src/workspace-core.js +343 -19
  137. package/src/workspace-workflow.js +538 -134
package/src/openprd.js CHANGED
@@ -1,33 +1,74 @@
1
+ /*
2
+ * 核心功能
3
+ * 汇总 OpenPrd CLI 的命令入口、workspace 函数编排和测试可复用导出。
4
+ *
5
+ * 输入
6
+ * 接收 CLI 参数、项目路径、OpenPrd 工作区文件和各子模块 workspace 结果。
7
+ *
8
+ * 输出
9
+ * 执行 init/setup/update/doctor/run/loop 等命令,打印结果并导出内部 workspace API。
10
+ *
11
+ * 定位
12
+ * 位于 CLI 应用层,负责路由与组合,不承载单个领域的深层业务规则。
13
+ *
14
+ * 依赖
15
+ * 依赖 cli/args、cli/print、agent-integration、loop、standards、quality、openspec 等模块。
16
+ *
17
+ * 维护规则
18
+ * 新增命令或参数时同步更新 usage、打印契约、docs/basic/backend-structure.md 和相关测试。
19
+ */
1
20
  import fs from 'node:fs/promises';
2
21
  import path from 'node:path';
3
22
  import { analyzePrdSnapshot, buildPrdSnapshot, formatVersionId } from './prd-core.js';
23
+ import { formatTemplatePackDisplay } from './product-type-copy.js';
24
+ import { buildSnapshotChangeSummary } from './change-summary.js';
25
+ import { appendReleaseEntry, buildReleaseChangeSummary, buildReleaseLedgerSummary, loadReleaseLedger, saveReleaseLedger, setCurrentReleaseVersion, setReleaseLedgerEnabled, setReleaseVersionStatus } from './release-ledger.js';
4
26
  import { validateOpenSpecChangeWorkspace } from './openspec/change-validate.js';
5
27
  import { generateOpenSpecChangeWorkspace as writeOpenSpecChangeWorkspace } from './openspec/generate.js';
6
- import { advanceOpenSpecTaskWorkspace, listOpenSpecTaskWorkspace, verifyOpenSpecTaskWorkspace } from './openspec/execute.js';
28
+ import { advanceOpenSpecTaskWorkspace, checkOpenSpecTaskEvidenceWorkspace, listOpenSpecTaskWorkspace, verifyOpenSpecTaskWorkspace } from './openspec/execute.js';
29
+ import { ensureCodexCliReady } from './codex-runtime.js';
7
30
  import { activateOpenPrdChangeWorkspace, applyOpenPrdChangeWorkspace, archiveOpenPrdChangeWorkspace, closeOpenPrdChangeWorkspace, listAcceptedSpecsWorkspace, listOpenPrdChangesWorkspace } from './openspec/change-lifecycle.js';
8
31
  import { checkStandardsWorkspace, classifyExternalReferenceWorkspace, initStandardsWorkspace } from './standards.js';
9
32
  import { doctorOpenPrdAgentIntegration, setupOpenPrdAgentIntegration, updateOpenPrdAgentIntegration } from './agent-integration.js';
10
33
  import { finishLoopWorkspace, initLoopWorkspace, nextLoopWorkspace, planLoopWorkspace, promptLoopWorkspace, runLoopWorkspace, statusLoopWorkspace, verifyLoopWorkspace } from './loop.js';
11
34
  import { timestamp } from './time.js';
12
35
  import { parseCommandArgs, usage } from './cli/args.js';
13
- import { printAcceptedSpecsResult, printAgentIntegrationResult, printBenchmarkResult, printCaptureResult, printClarifyResult, printClassifyResult, printDevelopmentStandardsResult, printDiagramResult, printDiffResult, printDoctorResult, printFleetResult, printFreezeResult, printGrowthResult, printHandoffResult, printHistoryResult, printInitResult, printInterviewResult, printLearningResult, printLoopResult, printNextResult, printOpenPrdChangeActionResult, printOpenPrdChangesResult, printOpenSpecChangeValidationResult, printOpenSpecDiscoveryResult, printOpenSpecGenerateResult, printOpenSpecTaskResult, printPlaygroundResult, printQualityResult, printReviewResult, printRunResult, printSelfUpdateResult, printStandardsResult, printStatus, printSynthesizeResult, printUpgradeResult, printValidation, printVisualCompareResult } from './cli/print.js';
14
- import { cjoin, exists, writeJson, writeText, writeYaml } from './fs-utils.js';
36
+ import { printAcceptedSpecsResult, printAgentIntegrationResult, printBenchmarkResult, printCaptureResult, printClarifyResult, printClassifyResult, printDevelopmentStandardsResult, printDiagramResult, printDiffResult, printDoctorResult, printFleetResult, printFreezeResult, printGrowthResult, printHandoffResult, printHistoryResult, printInitResult, printInterviewResult, printKnowledgeResult, printLearningResult, printLoopResult, printNextResult, printOpenPrdChangeActionResult, printOpenPrdChangesResult, printOpenSpecChangeValidationResult, printOpenSpecDiscoveryResult, printOpenSpecGenerateResult, printOpenSpecTaskResult, printPlaygroundResult, printQualityResult, printReleaseResult, printReviewResult, printRunResult, printSelfUpdateResult, printStandardsResult, printStatus, printSynthesizeResult, printUpgradeResult, printValidation, printVisualCompareResult } from './cli/print.js';
37
+ import { cjoin, exists, readJson, writeJson, writeText, writeYaml } from './fs-utils.js';
15
38
  import { diagramWorkspace } from './diagram-workspace.js';
16
39
  import { createOpenSpecDiscoveryWorkspace } from './discovery.js';
17
40
  import { createFleetWorkspace } from './fleet.js';
18
41
  import { selfUpdateWorkspace, upgradeWorkspace } from './self-update.js';
19
42
  import { backfillWorkUnitsWorkspace } from './work-unit-migration.js';
20
43
  import { generateLearningReviewWorkspace, setLearningReviewModeWorkspace } from './learning-review.js';
21
- import { addBenchmarkWorkspace, approveBenchmarkWorkspace, benchmarkWorkspace, listBenchmarkWorkspace, verifyBenchmarkWorkspace } from './benchmark.js';
44
+ import { addBenchmarkWorkspace, approveBenchmarkWorkspace, benchmarkWorkspace, listBenchmarkWorkspace, observeBenchmarkSourceWorkspace, verifyBenchmarkWorkspace } from './benchmark.js';
22
45
  import { initQualityWorkspace, qualityWorkspace, verifyQualityWorkspace, learnQualityWorkspace } from './quality.js';
46
+ import {
47
+ archiveKnowledgeCandidate,
48
+ listKnowledgeCandidates,
49
+ recordKnowledgeReviewSignal,
50
+ rejectKnowledgeCandidate,
51
+ restoreKnowledgeCandidate,
52
+ reviewKnowledgeWorkspace,
53
+ } from './knowledge.js';
23
54
  import { createRunWorkspace } from './run-harness.js';
24
55
  import { checkDevelopmentStandardsWorkspace } from './dev-standards.js';
25
- import { applyGrowthCandidateWorkspace, checkGrowthWorkspace, initGrowthWorkspace, rejectGrowthCandidateWorkspace, reviewGrowthWorkspace } from './growth.js';
56
+ import {
57
+ applyGrowthCandidateWorkspace,
58
+ checkGrowthWorkspace,
59
+ initGrowthWorkspace,
60
+ recordGrowthCheckpointWorkspace,
61
+ rejectGrowthCandidateWorkspace,
62
+ reviewGrowthWorkspace,
63
+ } from './growth.js';
26
64
  import { buildReviewPresentationTemplatePayload, reviewPresentationWorkspace } from './review-presentation.js';
65
+ import { analyzeWorkspaceRegistryHygiene } from './registry-hygiene.js';
27
66
  import { syncSessionBindingFromChange } from './session-binding.js';
67
+ import { readSessionRegistry } from './session-registry.js';
28
68
  import { visualCompareWorkspace } from './visual-compare.js';
29
69
  import { captureWorkspace, clarifyWorkspace, classifyWorkspace, computeWorkspaceGuidance, diffWorkspace, historyWorkspace, interviewWorkspace, nextWorkspace, playgroundWorkspace, reviewWorkspace, synthesizeWorkspace } from './workspace-workflow.js';
30
- import { appendDecision, appendProgress, appendVerification, appendWorkflowEvent, buildWorkflowTaskGraph, computeWorkspaceDigest, CORE_TEMPLATE_FILES, ensureWorkspaceSkeleton, isSupportedProductType, loadLatestVersionSnapshot, loadWorkspace, migrateWorkspaceSkeleton, normalizeVersionId, readVersionIndex, resolveActiveTemplatePack, resolveCurrentProductType, validateWorkspace } from './workspace-core.js';
70
+ import { appendDecision, appendProgress, appendVerification, appendWorkflowEvent, buildCurrentStateSnapshot, buildWorkflowTaskGraph, computeWorkspaceDigest, CORE_TEMPLATE_FILES, ensureWorkspaceSkeleton, isSupportedProductType, loadCurrentLaneSnapshot, loadLatestVersionSnapshot, loadWorkspace, migrateWorkspaceSkeleton, normalizeVersionId, persistWorkspaceCurrentState, readVersionIndex, resolveActiveTemplatePack, resolveCurrentProductType, validateWorkspace } from './workspace-core.js';
71
+ import { readWorkspaceRegistry } from './workspace-registry.js';
31
72
 
32
73
  async function initWorkspace(projectRoot, options) {
33
74
  const ws = await ensureWorkspaceSkeleton(projectRoot, options);
@@ -41,7 +82,9 @@ async function initWorkspace(projectRoot, options) {
41
82
  action: 'init',
42
83
  enableUserCodexConfig: Boolean(options.enableUserCodexConfig),
43
84
  codexHome: options.codexHome,
85
+ cursorHome: options.cursorHome,
44
86
  openprdHome: options.openprdHome,
87
+ platform: options.platform,
45
88
  hookProfile: options.hookProfile,
46
89
  });
47
90
  const config = workspace.data.config ?? {};
@@ -86,7 +129,7 @@ async function initWorkspace(projectRoot, options) {
86
129
  });
87
130
  await appendProgress(workspace, [
88
131
  `已初始化工作区: ${workspace.workspaceRoot}。`,
89
- `模板包: ${currentState.templatePack}。`,
132
+ `场景模板: ${formatTemplatePackDisplay(currentState.templatePack, { fallback: '待确认' })}。`,
90
133
  ]);
91
134
 
92
135
  return { ws: workspace, created: ws.created, currentState, standards, quality, growth, agentIntegration };
@@ -119,7 +162,9 @@ async function setupAgentIntegrationWorkspace(projectRoot, options = {}) {
119
162
  action: 'setup',
120
163
  enableUserCodexConfig: Boolean(options.enableUserCodexConfig),
121
164
  codexHome: options.codexHome,
165
+ cursorHome: options.cursorHome,
122
166
  openprdHome: options.openprdHome,
167
+ platform: options.platform,
123
168
  hookProfile: options.hookProfile,
124
169
  });
125
170
  return { ...agentIntegration, initialized: false, migration, standards, quality, growth };
@@ -135,7 +180,9 @@ async function updateAgentIntegrationWorkspace(projectRoot, options = {}) {
135
180
  force: Boolean(options.force),
136
181
  enableUserCodexConfig: Boolean(options.enableUserCodexConfig),
137
182
  codexHome: options.codexHome,
183
+ cursorHome: options.cursorHome,
138
184
  openprdHome: options.openprdHome,
185
+ platform: options.platform,
139
186
  hookProfile: options.hookProfile,
140
187
  });
141
188
  return { ...agentIntegration, migration, standards, quality, growth };
@@ -146,16 +193,31 @@ async function doctorWorkspace(projectRoot, options = {}) {
146
193
  tools: options.tools ?? 'all',
147
194
  enableUserCodexConfig: Boolean(options.enableUserCodexConfig),
148
195
  codexHome: options.codexHome,
196
+ cursorHome: options.cursorHome,
149
197
  hookProfile: options.hookProfile,
150
198
  });
151
- const standards = await checkStandardsWorkspace(projectRoot).catch((error) => ({
199
+ const codexRuntime = (agentIntegration.tools ?? []).includes('codex') && options.checkCodexRuntime
200
+ ? await ensureCodexCliReady({
201
+ cwd: projectRoot,
202
+ repair: Boolean(options.fix),
203
+ runCommand: options.codexRunCommand,
204
+ packageManager: options.packageManager,
205
+ })
206
+ : null;
207
+ const standards = await checkStandardsWorkspace(projectRoot, {
208
+ sourceManuals: options.sourceManuals,
209
+ docsContent: options.docsContent,
210
+ }).catch((error) => ({
152
211
  ok: false,
153
212
  errors: [error instanceof Error ? error.message : String(error)],
154
213
  warnings: [],
155
214
  checks: [],
156
215
  docsRoot: path.join('docs', 'basic'),
157
216
  }));
158
- const validation = await validateWorkspace(projectRoot)
217
+ const validation = await validateWorkspace(projectRoot, {
218
+ sourceManuals: options.sourceManuals,
219
+ docsContent: options.docsContent,
220
+ })
159
221
  .then(({ report }) => report)
160
222
  .catch((error) => ({
161
223
  valid: false,
@@ -163,20 +225,88 @@ async function doctorWorkspace(projectRoot, options = {}) {
163
225
  warnings: [],
164
226
  checks: [],
165
227
  }));
228
+ const workspaceRegistry = await readWorkspaceRegistry({ openprdHome: options.openprdHome }).catch(() => null);
229
+ const sessionRegistry = await readSessionRegistry({ openprdHome: options.openprdHome }).catch(() => null);
230
+ const registryHygiene = workspaceRegistry
231
+ ? analyzeWorkspaceRegistryHygiene(workspaceRegistry.entries)
232
+ : { ok: true, issues: [] };
233
+ const registryWarnings = [
234
+ ...(registryHygiene.issues ?? []).map((issue) => `registry: ${issue.message}`),
235
+ ...((workspaceRegistry?.staleEntries ?? []).map((entry) => `registry: stale workspace ${entry.workspaceRoot} (${entry.reason})`)),
236
+ ...((sessionRegistry?.staleEntries ?? []).map((entry) => `session-registry: stale session ${entry.sessionId} (${entry.reason})`)),
237
+ ];
238
+ const doctorOk = agentIntegration.ok && (codexRuntime?.ok ?? true) && standards.ok && validation.valid;
239
+ const latestQuality = await readJson(cjoin(projectRoot, '.openprd', 'quality', 'reports', 'latest.json')).catch(() => null);
240
+ const doctorSignal = doctorOk
241
+ ? {
242
+ kind: 'doctor-green',
243
+ ok: true,
244
+ summary: 'doctor passed',
245
+ }
246
+ : null;
247
+ if (doctorSignal) {
248
+ await recordKnowledgeReviewSignal(projectRoot, doctorSignal).catch(() => null);
249
+ }
250
+ const growthCheckpoint = doctorSignal
251
+ ? await recordGrowthCheckpointWorkspace(projectRoot, {
252
+ outcome: 'doctor-passed',
253
+ reason: 'doctor-post-completion',
254
+ }).catch((error) => ({
255
+ ok: false,
256
+ action: 'growth-checkpoint',
257
+ projectRoot,
258
+ recorded: false,
259
+ errors: [error instanceof Error ? error.message : String(error)],
260
+ }))
261
+ : {
262
+ ok: true,
263
+ action: 'growth-checkpoint',
264
+ projectRoot,
265
+ recorded: false,
266
+ skipped: true,
267
+ reason: 'doctor-not-green',
268
+ };
269
+ const knowledgeReview = doctorSignal
270
+ ? await reviewKnowledgeWorkspace(projectRoot, {
271
+ from: latestQuality?.jsonPath ?? null,
272
+ signal: doctorSignal,
273
+ }).catch((error) => ({
274
+ ok: false,
275
+ action: 'quality-knowledge-review',
276
+ skipped: false,
277
+ errors: [error instanceof Error ? error.message : String(error)],
278
+ }))
279
+ : {
280
+ ok: true,
281
+ action: 'quality-knowledge-review',
282
+ skipped: true,
283
+ reason: 'doctor-not-green',
284
+ };
166
285
 
167
286
  return {
168
- ok: agentIntegration.ok && standards.ok && validation.valid,
287
+ ok: doctorOk,
169
288
  action: 'doctor',
170
289
  projectRoot,
171
290
  tools: agentIntegration.tools,
172
291
  agentIntegration,
292
+ codexRuntime,
173
293
  standards,
174
294
  validation,
295
+ registry: {
296
+ workspace: workspaceRegistry,
297
+ sessions: sessionRegistry,
298
+ hygiene: registryHygiene,
299
+ warnings: registryWarnings,
300
+ },
301
+ growthCheckpoint,
302
+ knowledgeReview,
175
303
  errors: [
176
304
  ...agentIntegration.errors,
305
+ ...(codexRuntime?.errors ?? []).map((error) => `codex-runtime: ${error}`),
177
306
  ...(standards.errors ?? []).map((error) => `standards: ${error}`),
178
307
  ...(validation.errors ?? []).map((error) => `validate: ${error}`),
179
308
  ],
309
+ warnings: registryWarnings,
180
310
  };
181
311
  }
182
312
 
@@ -236,7 +366,7 @@ async function freezeWorkspace(projectRoot) {
236
366
  }
237
367
 
238
368
  let ws = validation.ws;
239
- let latest = await loadLatestVersionSnapshot(ws);
369
+ let latest = await loadCurrentLaneSnapshot(ws, { fallbackToLatest: true });
240
370
  if (!latest) {
241
371
  const synthesized = await synthesizeWorkspace(projectRoot, {});
242
372
  ws = synthesized.ws;
@@ -294,28 +424,26 @@ async function freezeWorkspace(projectRoot) {
294
424
  frozenAt: snapshot.frozenAt,
295
425
  digest,
296
426
  };
297
- await writeJson(ws.paths.currentState, currentState);
427
+ const storedCurrentState = await persistWorkspaceCurrentState(ws, currentState);
298
428
  await appendWorkflowEvent(ws, 'frozen', {
299
429
  versionId: snapshot.latestVersionId,
300
430
  digest,
301
431
  });
302
432
  await appendVerification(ws, [
303
- 'Freeze 验证通过。',
304
- `版本: ${snapshot.latestVersionId}`,
305
- `Digest: ${digest}`,
306
- `PRD 版本: ${snapshot.prdVersion}`,
433
+ '定稿前检查通过。',
434
+ `本次确认稿: ${snapshot.latestVersionId}`,
435
+ `需求稿版本序号: ${snapshot.prdVersion}`,
307
436
  ]);
308
437
  await appendProgress(ws, [
309
- `已 freeze PRD 版本 ${snapshot.latestVersionId}。`,
310
- `Digest: ${digest}`,
438
+ `这版需求已经定稿。`,
311
439
  ]);
312
440
  await appendDecision(ws, [
313
- `已 freeze 版本 ${snapshot.latestVersionId}。`,
441
+ '这版需求已经定稿。',
314
442
  `已准备好交接给 ${resolveActiveTemplatePack(ws) === 'base' ? '下游执行方' : '执行系统'}。`,
315
443
  ]);
316
- await writeJson(ws.paths.taskGraph, buildWorkflowTaskGraph(currentState));
444
+ await writeJson(ws.paths.taskGraph, buildWorkflowTaskGraph(storedCurrentState));
317
445
 
318
- return { ok: true, ws, report, snapshot, latest };
446
+ return { ok: true, ws: { ...ws, data: { ...ws.data, currentState: storedCurrentState } }, report, snapshot, latest };
319
447
  }
320
448
 
321
449
  async function handoffWorkspace(projectRoot, target) {
@@ -325,13 +453,23 @@ async function handoffWorkspace(projectRoot, target) {
325
453
  }
326
454
 
327
455
  const { ws, snapshot } = freeze;
456
+ const sourceSnapshot = freeze.latest?.snapshot ?? snapshot;
328
457
  const exportDir = cjoin(ws.paths.exportsDir, target);
458
+ const releaseState = await loadReleaseLedger(projectRoot);
459
+ const releaseSummary = buildReleaseLedgerSummary(releaseState.ledger);
460
+ const releaseChangeSummary = releaseSummary.enabled && releaseSummary.currentVersion
461
+ ? buildReleaseChangeSummary(releaseState.ledger, { limit: 5 })
462
+ : null;
463
+ const changeSummary = releaseChangeSummary?.items?.length
464
+ ? releaseChangeSummary
465
+ : buildSnapshotChangeSummary(sourceSnapshot, { limit: 5 });
329
466
  await fs.mkdir(exportDir, { recursive: true });
330
467
 
331
468
  const handoff = {
332
469
  version: 1,
333
470
  versionId: snapshot.latestVersionId,
334
471
  versionNumber: snapshot.prdVersion,
472
+ projectVersion: releaseSummary.currentVersion,
335
473
  target,
336
474
  generatedAt: timestamp(),
337
475
  workspaceRoot: ws.workspaceRoot,
@@ -341,6 +479,10 @@ async function handoffWorkspace(projectRoot, target) {
341
479
  productTypes: ws.data.config?.supportedProductTypes ?? [],
342
480
  productType: resolveCurrentProductType(ws),
343
481
  digest: snapshot.digest,
482
+ projectRelease: releaseSummary,
483
+ changeSummarySource: releaseChangeSummary?.items?.length ? 'release-ledger' : 'snapshot',
484
+ changeSummary,
485
+ releaseNotes: changeSummary.items.map((item) => item.sentence),
344
486
  sourceFiles: [
345
487
  ...CORE_TEMPLATE_FILES,
346
488
  ...((await exists(ws.paths.activeArchitectureDiagramHtml)) ? ['engagements/active/architecture-diagram.html'] : []),
@@ -356,7 +498,12 @@ async function handoffWorkspace(projectRoot, target) {
356
498
  };
357
499
 
358
500
  await writeJson(cjoin(exportDir, 'handoff.json'), handoff);
359
- await writeText(cjoin(exportDir, 'handoff.md'), `# 交接\n\n- 目标: ${target}\n- 版本: ${handoff.versionId}\n- Schema: ${handoff.schema}\n- 模板包: ${handoff.templatePack}\n- Digest: ${handoff.digest}\n- 下一步: ${handoff.nextStep}\n`);
501
+ const summarySection = handoff.changeSummary.markdown
502
+ ? `\n## 变化摘要\n\n${handoff.changeSummary.markdown}\n`
503
+ : '';
504
+ const handoffMarkdown = `# 交接\n\n- 交接去向: ${target}\n${handoff.projectVersion ? `- 项目版本: ${handoff.projectVersion}\n` : ''}- 使用格式: ${handoff.schema}\n- 场景模板: ${formatTemplatePackDisplay(handoff.templatePack, { fallback: '待确认' })}\n- 下一步: ${handoff.nextStep}\n${summarySection}`;
505
+ await writeText(cjoin(exportDir, 'handoff.md'), handoffMarkdown);
506
+ await writeText(ws.paths.activeHandoff, handoffMarkdown);
360
507
  if (await exists(ws.paths.activeArchitectureDiagramHtml)) {
361
508
  await fs.copyFile(ws.paths.activeArchitectureDiagramHtml, cjoin(exportDir, 'architecture-diagram.html'));
362
509
  }
@@ -394,10 +541,107 @@ async function handoffWorkspace(projectRoot, target) {
394
541
  handedOffAt: handoff.generatedAt,
395
542
  handoffTarget: target,
396
543
  };
397
- await writeJson(ws.paths.currentState, currentState);
398
- await writeJson(ws.paths.taskGraph, buildWorkflowTaskGraph(currentState));
544
+ const storedCurrentState = await persistWorkspaceCurrentState(ws, currentState);
545
+ await writeJson(ws.paths.taskGraph, buildWorkflowTaskGraph(storedCurrentState));
399
546
 
400
- return { ok: true, ws, report: freeze.report, snapshot, handoff, exportDir };
547
+ return { ok: true, ws: { ...ws, data: { ...ws.data, currentState: storedCurrentState } }, report: freeze.report, snapshot, handoff, exportDir };
548
+ }
549
+
550
+ async function releaseWorkspace(projectRoot, options = {}) {
551
+ const workspaceRoot = cjoin(projectRoot, '.openprd');
552
+ if (!(await exists(workspaceRoot))) {
553
+ return {
554
+ ok: false,
555
+ action: 'release',
556
+ projectRoot,
557
+ errors: [`Missing workspace: ${workspaceRoot}. Please run openprd init first.`],
558
+ };
559
+ }
560
+
561
+ const loaded = await loadReleaseLedger(projectRoot);
562
+ let ledger = loaded.ledger;
563
+ const actions = [];
564
+ const warnings = [];
565
+ let changed = false;
566
+
567
+ try {
568
+ if (options.disable) {
569
+ ({ ledger } = setReleaseLedgerEnabled(ledger, false));
570
+ actions.push('disable');
571
+ changed = true;
572
+ }
573
+
574
+ if (options.enable) {
575
+ ({ ledger } = setReleaseLedgerEnabled(ledger, true));
576
+ actions.push('enable');
577
+ changed = true;
578
+ }
579
+
580
+ if (options.setVersion) {
581
+ const updated = setCurrentReleaseVersion(ledger, options.setVersion, {
582
+ status: options.status === 'released' ? 'current' : (options.status ?? 'current'),
583
+ });
584
+ ledger = updated.ledger;
585
+ actions.push('set-version');
586
+ changed = true;
587
+ if (updated.previousVersion) {
588
+ warnings.push(`当前项目版本已从 ${updated.previousVersion} 切换到 ${updated.entry.version};旧版本默认改为 released。`);
589
+ }
590
+ if (updated.semver.warning) {
591
+ warnings.push(updated.semver.warning);
592
+ }
593
+ }
594
+
595
+ if (options.status && !options.setVersion) {
596
+ const updated = setReleaseVersionStatus(ledger, options.status, { version: options.version });
597
+ ledger = updated.ledger;
598
+ actions.push('set-status');
599
+ changed = true;
600
+ }
601
+
602
+ if (options.notes) {
603
+ const updated = appendReleaseEntry(ledger, options.notes, {
604
+ version: options.version,
605
+ fallbackType: '调整',
606
+ source: {
607
+ kind: 'manual-note',
608
+ manualId: `manual-note-${Date.now()}`,
609
+ },
610
+ });
611
+ ledger = updated.ledger;
612
+ actions.push('append-note');
613
+ changed = true;
614
+ }
615
+ } catch (error) {
616
+ return {
617
+ ok: false,
618
+ action: 'release',
619
+ projectRoot,
620
+ errors: [error instanceof Error ? error.message : String(error)],
621
+ };
622
+ }
623
+
624
+ if (changed) {
625
+ await saveReleaseLedger(projectRoot, ledger);
626
+ }
627
+
628
+ const summary = buildReleaseLedgerSummary(ledger, { version: options.version });
629
+ const changeSummary = buildReleaseChangeSummary(ledger, {
630
+ version: options.version ?? summary.currentVersion,
631
+ limit: 5,
632
+ });
633
+
634
+ return {
635
+ ok: true,
636
+ action: 'release',
637
+ projectRoot,
638
+ releaseLedgerPath: loaded.filePath,
639
+ changed,
640
+ actions,
641
+ warnings,
642
+ summary,
643
+ changeSummary,
644
+ };
401
645
  }
402
646
 
403
647
  function reviewConfirmationCommand(snapshot) {
@@ -431,17 +675,9 @@ function assertPrdReviewConfirmedForChange(currentState, snapshot) {
431
675
  async function generateOpenSpecChangeWorkspace(projectRoot, options = {}) {
432
676
  const ws = await loadWorkspace(projectRoot);
433
677
  const versionIndex = await readVersionIndex(ws);
434
- const latest = await loadLatestVersionSnapshot(ws);
678
+ const latest = await loadCurrentLaneSnapshot(ws, { fallbackToLatest: true });
435
679
  const currentState = ws.data.currentState ?? {};
436
- const snapshot = latest?.snapshot ?? buildPrdSnapshot(ws, {
437
- ...currentState,
438
- versionNumber: currentState.prdVersion ?? (versionIndex.at(-1)?.versionNumber ?? 0),
439
- versionId: currentState.prdVersion > 0
440
- ? formatVersionId(currentState.prdVersion)
441
- : (versionIndex.at(-1)?.versionId ?? 'v0000'),
442
- productType: resolveCurrentProductType(ws),
443
- templatePack: resolveActiveTemplatePack(ws),
444
- });
680
+ const snapshot = latest?.snapshot ?? buildCurrentStateSnapshot(ws, currentState, versionIndex);
445
681
  assertPrdReviewConfirmedForChange(currentState, snapshot);
446
682
  const analysis = analyzePrdSnapshot(snapshot);
447
683
  const result = await writeOpenSpecChangeWorkspace(projectRoot, {
@@ -463,6 +699,7 @@ async function generateOpenSpecChangeWorkspace(projectRoot, options = {}) {
463
699
  `验证: ${validation.valid ? '通过' : '失败'}。`,
464
700
  ]);
465
701
  await syncSessionBindingFromChange(projectRoot, result.changeId, {
702
+ sessionId: ws.data.currentSessionId ?? null,
466
703
  title: snapshot.title ?? null,
467
704
  versionId: snapshot.versionId ?? null,
468
705
  digest: snapshot.digest ?? null,
@@ -572,7 +809,7 @@ export async function main(argv = process.argv.slice(2)) {
572
809
  }
573
810
 
574
811
  if (command === 'doctor') {
575
- const result = await doctorWorkspace(projectPath, { tools: flags.tools, hookProfile: flags.hookProfile, enableUserCodexConfig: true });
812
+ const result = await doctorWorkspace(projectPath, { tools: flags.tools, hookProfile: flags.hookProfile, fix: flags.fix, checkCodexRuntime: true, enableUserCodexConfig: true });
576
813
  printDoctorResult(result, flags.json);
577
814
  return result.ok ? 0 : 1;
578
815
  }
@@ -604,6 +841,7 @@ export async function main(argv = process.argv.slice(2)) {
604
841
  context: flags.context,
605
842
  verify: flags.verify,
606
843
  recordHook: flags.recordHook,
844
+ hookInject: flags.hookInject,
607
845
  event: flags.event,
608
846
  risk: flags.risk,
609
847
  outcome: flags.outcome,
@@ -627,6 +865,7 @@ export async function main(argv = process.argv.slice(2)) {
627
865
  message: flags.message,
628
866
  evidence: flags.evidence,
629
867
  notes: flags.notes,
868
+ repairAgent: flags.repairAgent,
630
869
  };
631
870
  if (flags.init) {
632
871
  result = await initLoopWorkspace(projectPath, options);
@@ -674,10 +913,44 @@ export async function main(argv = process.argv.slice(2)) {
674
913
  return result.ok ? 0 : 1;
675
914
  }
676
915
 
916
+ if (command === 'knowledge') {
917
+ const subcommand = positionals[0] ?? 'candidates';
918
+ const firstArgIsProjectPath = !flags.path && positionals.length > 1 && await isDirectoryPath(positionals[1]);
919
+ const knowledgeProjectPath = path.resolve(flags.path ?? (firstArgIsProjectPath ? positionals[1] : process.cwd()));
920
+ let result;
921
+ if (subcommand === 'candidates' || subcommand === 'list') {
922
+ result = await listKnowledgeCandidates(knowledgeProjectPath, {
923
+ status: flags.status ?? 'pending-review',
924
+ });
925
+ } else if (subcommand === 'reject') {
926
+ result = await rejectKnowledgeCandidate(knowledgeProjectPath, {
927
+ id: flags.id ?? positionals[1] ?? null,
928
+ reason: flags.reason ?? flags.notes,
929
+ });
930
+ } else if (subcommand === 'archive') {
931
+ result = await archiveKnowledgeCandidate(knowledgeProjectPath, {
932
+ id: flags.id ?? positionals[1] ?? null,
933
+ reason: flags.reason ?? flags.notes,
934
+ });
935
+ } else if (subcommand === 'restore') {
936
+ result = await restoreKnowledgeCandidate(knowledgeProjectPath, {
937
+ id: flags.id ?? positionals[1] ?? null,
938
+ });
939
+ } else {
940
+ console.log('Usage: openprd knowledge <candidates|reject|archive|restore> [path-or-id] [--status <pending-review|all|rejected|archived|promoted|merged>] [--id <candidate-id>] [--reason <text>] [--json]');
941
+ return 1;
942
+ }
943
+ printKnowledgeResult(result, flags.json);
944
+ return result.ok ? 0 : 1;
945
+ }
946
+
677
947
  if (command === 'visual-compare') {
678
948
  const result = await visualCompareWorkspace(projectPath, {
679
949
  reference: flags.reference,
680
950
  actual: flags.actual,
951
+ before: flags.before,
952
+ after: flags.after,
953
+ board: flags.board,
681
954
  out: flags.out,
682
955
  format: flags.format,
683
956
  quality: flags.quality,
@@ -733,6 +1006,14 @@ export async function main(argv = process.argv.slice(2)) {
733
1006
  source: target ?? flags.source,
734
1007
  notes: flags.notes,
735
1008
  });
1009
+ } else if (subcommand === 'observe') {
1010
+ result = await observeBenchmarkSourceWorkspace(benchmarkProjectPath, {
1011
+ source: target ?? flags.source,
1012
+ notes: flags.notes,
1013
+ task: flags.item ?? flags.event,
1014
+ adoptedSignal: flags.status ?? flags.outcome,
1015
+ threshold: flags.threshold,
1016
+ });
736
1017
  } else if (subcommand === 'approve') {
737
1018
  result = await approveBenchmarkWorkspace(benchmarkProjectPath, {
738
1019
  id: flags.id ?? target,
@@ -742,7 +1023,7 @@ export async function main(argv = process.argv.slice(2)) {
742
1023
  } else if (subcommand === 'list') {
743
1024
  result = await listBenchmarkWorkspace(benchmarkProjectPath);
744
1025
  } else {
745
- console.log('Usage: openprd benchmark <add|list|approve|verify> [target-or-id] [path-for-list-or-verify] [--path <project>] [--notes <text>] [--id <benchmark-id>]');
1026
+ console.log('Usage: openprd benchmark <add|observe|list|approve|verify> [target-or-id] [path-for-list-or-verify] [--path <project>] [--notes <text>] [--id <benchmark-id>]');
746
1027
  return 1;
747
1028
  }
748
1029
  printBenchmarkResult(result, flags.json);
@@ -906,6 +1187,19 @@ export async function main(argv = process.argv.slice(2)) {
906
1187
  return 0;
907
1188
  }
908
1189
 
1190
+ if (command === 'release') {
1191
+ const result = await releaseWorkspace(projectPath, {
1192
+ enable: flags.enable,
1193
+ disable: flags.disable,
1194
+ setVersion: flags.set,
1195
+ status: flags.status,
1196
+ version: flags.version,
1197
+ notes: flags.notes,
1198
+ });
1199
+ printReleaseResult(result, flags.json);
1200
+ return result.ok ? 0 : 1;
1201
+ }
1202
+
909
1203
  if (command === 'validate') {
910
1204
  const { report } = await validateWorkspace(projectPath);
911
1205
  printValidation(report, flags.json);
@@ -1012,7 +1306,9 @@ export async function main(argv = process.argv.slice(2)) {
1012
1306
  evidence: flags.evidence,
1013
1307
  notes: flags.notes,
1014
1308
  };
1015
- const result = flags.advance
1309
+ const result = flags.evidenceRequired
1310
+ ? await checkOpenSpecTaskEvidenceWorkspace(projectPath, taskOptions)
1311
+ : flags.advance
1016
1312
  ? await advanceOpenSpecTaskWorkspace(projectPath, taskOptions)
1017
1313
  : flags.verify
1018
1314
  ? await verifyOpenSpecTaskWorkspace(projectPath, taskOptions)
@@ -1094,6 +1390,7 @@ export {
1094
1390
  nextWorkspace,
1095
1391
  diffWorkspace,
1096
1392
  historyWorkspace,
1393
+ releaseWorkspace,
1097
1394
  reviewWorkspace,
1098
1395
  reviewPresentationWorkspace,
1099
1396
  freezeWorkspace,
@@ -1104,6 +1401,7 @@ export {
1104
1401
  listOpenSpecTaskWorkspace,
1105
1402
  advanceOpenSpecTaskWorkspace,
1106
1403
  verifyOpenSpecTaskWorkspace,
1404
+ checkOpenSpecTaskEvidenceWorkspace,
1107
1405
  listOpenPrdChangesWorkspace,
1108
1406
  activateOpenPrdChangeWorkspace,
1109
1407
  closeOpenPrdChangeWorkspace,
@@ -1121,6 +1419,10 @@ export {
1121
1419
  verifyQualityWorkspace,
1122
1420
  learnQualityWorkspace,
1123
1421
  qualityWorkspace,
1422
+ listKnowledgeCandidates,
1423
+ rejectKnowledgeCandidate,
1424
+ archiveKnowledgeCandidate,
1425
+ restoreKnowledgeCandidate,
1124
1426
  visualCompareWorkspace,
1125
1427
  checkDevelopmentStandardsWorkspace,
1126
1428
  initGrowthWorkspace,
@@ -1130,6 +1432,7 @@ export {
1130
1432
  rejectGrowthCandidateWorkspace,
1131
1433
  benchmarkWorkspace,
1132
1434
  addBenchmarkWorkspace,
1435
+ observeBenchmarkSourceWorkspace,
1133
1436
  listBenchmarkWorkspace,
1134
1437
  approveBenchmarkWorkspace,
1135
1438
  verifyBenchmarkWorkspace,
@@ -4,7 +4,6 @@ import YAML from 'yaml';
4
4
  import { listChangeDirs, readDiscoveryConfig, resolveChangeDir } from './paths.js';
5
5
  import { analyzeOpenSpecTaskVolumes } from './tasks.js';
6
6
  import { checkStandardsWorkspace } from '../standards.js';
7
- import { findOpenPrdSpecLanguageViolations } from '../language-policy.js';
8
7
 
9
8
  function cjoin(...parts) {
10
9
  return path.join(...parts);
@@ -70,7 +69,6 @@ const THEN_STEP_RE = /^-\s+\*\*(?:THEN|则|那么)\*\*/im;
70
69
  function validateOpenSpecSpecText(relativePath, text, errors, checks) {
71
70
  const requirementMatches = [...text.matchAll(REQUIREMENT_HEADING_RE)];
72
71
  const sectionMatches = [...text.matchAll(SPEC_SECTION_HEADING_RE)];
73
- const languageViolations = findOpenPrdSpecLanguageViolations(text);
74
72
 
75
73
  if (sectionMatches.length === 0) {
76
74
  errors.push(`${relativePath} 必须包含“新增需求”“修改需求”或“移除需求”章节。`);
@@ -104,13 +102,6 @@ function validateOpenSpecSpecText(relativePath, text, errors, checks) {
104
102
  }
105
103
  }
106
104
 
107
- for (const violation of languageViolations.slice(0, 5)) {
108
- errors.push(`${relativePath}:${violation.line} 不符合简体中文规则: ${violation.reason}。除必要专业字段、命令、文件名或英文产品名外,spec.md 正文必须使用简体中文。`);
109
- }
110
- if (languageViolations.length > 5) {
111
- errors.push(`${relativePath} 还有 ${languageViolations.length - 5} 处简体中文规则问题。`);
112
- }
113
-
114
105
  checks.push(`${relativePath}: ${requirementMatches.length} 个需求。`);
115
106
  }
116
107