@openprd/cli 0.1.0 → 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 (138) 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 +402 -441
  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 +39 -23
  73. package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -6
  74. package/skills/openprd-requirement-intake/references/routing-rubric.md +22 -8
  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/self-update.js +1 -1
  131. package/src/session-binding.js +40 -3
  132. package/src/session-registry.js +159 -0
  133. package/src/standards.js +5 -3
  134. package/src/test-strategy.js +386 -0
  135. package/src/visual-compare.js +915 -34
  136. package/src/work-unit-migration.js +5 -1
  137. package/src/workspace-core.js +343 -19
  138. package/src/workspace-workflow.js +538 -134
@@ -0,0 +1,369 @@
1
+ const EXECUTION_MODE_LABELS = {
2
+ serial: '主 Agent 串行',
3
+ 'parallel-workers': '主 Agent 协调并行 Worker',
4
+ 'parallel-workers-isolated': '独立隔离环境并行 Worker',
5
+ };
6
+
7
+ const PARALLEL_GROUP_LABELS = {
8
+ governance: '治理收口',
9
+ contracts: '契约与入口',
10
+ domain: '领域逻辑',
11
+ surface: '展示与界面',
12
+ implementation: '功能实现',
13
+ integration: '集成收口',
14
+ verification: '验证证据',
15
+ docs: '文档维护',
16
+ none: '无',
17
+ };
18
+
19
+ const OWNER_ROLE_LABELS = {
20
+ 'main-agent': '主 Agent',
21
+ worker: 'Worker',
22
+ };
23
+
24
+ const INTEGRATION_OWNER_LABELS = {
25
+ 'main-agent': '主 Agent',
26
+ };
27
+
28
+ export const EXECUTION_STRATEGY_METADATA_KEYS = [
29
+ 'execution-mode',
30
+ 'parallel-group',
31
+ 'write-scope',
32
+ 'owner-role',
33
+ 'local-verify',
34
+ 'integration-owner',
35
+ ];
36
+
37
+ export const EXECUTION_MODE_VALUES = Object.keys(EXECUTION_MODE_LABELS);
38
+ export const PARALLEL_GROUP_VALUES = Object.keys(PARALLEL_GROUP_LABELS);
39
+ export const OWNER_ROLE_VALUES = Object.keys(OWNER_ROLE_LABELS);
40
+ export const INTEGRATION_OWNER_VALUES = Object.keys(INTEGRATION_OWNER_LABELS);
41
+
42
+ function normalizeToken(value) {
43
+ return String(value ?? '').trim().toLowerCase();
44
+ }
45
+
46
+ function splitValues(value) {
47
+ return String(value ?? '')
48
+ .split(',')
49
+ .map((item) => item.trim())
50
+ .filter(Boolean);
51
+ }
52
+
53
+ function includesAny(text, patterns) {
54
+ return patterns.some((pattern) => pattern.test(text));
55
+ }
56
+
57
+ function firstKnown(values, allowed, fallback) {
58
+ return values.find((value) => allowed.includes(value)) ?? fallback;
59
+ }
60
+
61
+ function inferParallelGroup(text, type, phase) {
62
+ if (type === 'governance' || phase.includes('governance')) {
63
+ return 'governance';
64
+ }
65
+ if (type === 'documentation' || /docs\/basic|readme|文档|说明书|documentation|docs/i.test(text)) {
66
+ return 'docs';
67
+ }
68
+ if (type === 'verification' || phase.includes('verification') || /验证|回归|测试|验收|qa|verify|test/.test(text)) {
69
+ return 'verification';
70
+ }
71
+ if (phase.includes('integration') || /主流程|集成|闭环|联调|发布|integration|flow/.test(text)) {
72
+ return 'integration';
73
+ }
74
+ if (includesAny(text, [/contract|schema|api|hook|cli|ipc|preload|adapter|类型|契约|协议|接口|命令行|接线/])) {
75
+ return 'contracts';
76
+ }
77
+ if (includesAny(text, [/renderer|view|page|dialog|modal|surface|sidebar|route|navigation|界面|页面|弹窗|样式|组件|布局|入口|导航/])) {
78
+ return 'surface';
79
+ }
80
+ if (includesAny(text, [/domain|service|gateway|repository|storage|cache|backend|billing|order|entitlement|sync|状态|领域|服务|仓储|后端|数据/])) {
81
+ return 'domain';
82
+ }
83
+ return 'implementation';
84
+ }
85
+
86
+ function inferWriteScope(parallelGroup) {
87
+ const scopeByGroup = {
88
+ governance: ['openprd/changes/**', '.openprd/**'],
89
+ contracts: ['src/**', 'test/**', 'docs/basic/backend-structure.md'],
90
+ domain: ['src/**', 'test/**'],
91
+ surface: ['src/cli/**', 'src/**', 'test/**'],
92
+ implementation: ['src/**', 'test/**'],
93
+ integration: ['src/**', 'test/**', 'docs/basic/**'],
94
+ verification: ['test/**', '.openprd/harness/test-reports/**'],
95
+ docs: ['docs/basic/**', 'README*.md'],
96
+ none: ['src/**'],
97
+ };
98
+ return scopeByGroup[parallelGroup] ?? scopeByGroup.implementation;
99
+ }
100
+
101
+ function defaultLocalVerify(task = {}) {
102
+ return task.verify ?? task.metadata?.verify ?? 'openprd tasks . --change <change-id> --verify --item <task-id>';
103
+ }
104
+
105
+ export function normalizeWriteScopes(value) {
106
+ return [...new Set(splitValues(value))];
107
+ }
108
+
109
+ export function labelExecutionMode(mode) {
110
+ return EXECUTION_MODE_LABELS[normalizeToken(mode)] ?? mode ?? '未指定';
111
+ }
112
+
113
+ export function labelParallelGroup(group) {
114
+ return PARALLEL_GROUP_LABELS[normalizeToken(group)] ?? group ?? '未指定';
115
+ }
116
+
117
+ export function labelOwnerRole(role) {
118
+ return OWNER_ROLE_LABELS[normalizeToken(role)] ?? role ?? '未指定';
119
+ }
120
+
121
+ export function labelIntegrationOwner(owner) {
122
+ return INTEGRATION_OWNER_LABELS[normalizeToken(owner)] ?? owner ?? '未指定';
123
+ }
124
+
125
+ export function describeExecutionStrategy(strategy) {
126
+ const mode = labelExecutionMode(strategy.mode);
127
+ const group = labelParallelGroup(strategy.parallelGroup);
128
+ const ownerRole = labelOwnerRole(strategy.ownerRole);
129
+ const writeScope = (strategy.writeScope ?? []).join(', ') || '未指定';
130
+ const localVerify = strategy.localVerify || '未指定';
131
+ const integrationOwner = labelIntegrationOwner(strategy.integrationOwner);
132
+ return `${mode} / ${group} / ${ownerRole};写入范围:${writeScope};局部验证:${localVerify};最终集成:${integrationOwner}`;
133
+ }
134
+
135
+ export function inferExecutionStrategyForTask(task = {}) {
136
+ const type = normalizeToken(task.type ?? task.metadata?.type ?? task.metadata?.category ?? task.metadata?.kind);
137
+ const phase = normalizeToken(task.phase);
138
+ const text = [
139
+ task.id,
140
+ task.title,
141
+ task.done,
142
+ task.verify,
143
+ task.metadata?.done,
144
+ task.metadata?.verify,
145
+ ].map((value) => String(value ?? '')).join('\n').toLowerCase();
146
+
147
+ if (type === 'governance' || phase.includes('governance')) {
148
+ return {
149
+ mode: 'serial',
150
+ parallelGroup: 'governance',
151
+ writeScope: inferWriteScope('governance'),
152
+ ownerRole: 'main-agent',
153
+ localVerify: defaultLocalVerify(task),
154
+ integrationOwner: 'main-agent',
155
+ inferred: true,
156
+ };
157
+ }
158
+
159
+ if (type === 'documentation' || /docs\/basic|readme|文档|说明书|documentation|docs/i.test(text)) {
160
+ return {
161
+ mode: 'parallel-workers',
162
+ parallelGroup: 'docs',
163
+ writeScope: inferWriteScope('docs'),
164
+ ownerRole: 'worker',
165
+ localVerify: defaultLocalVerify(task),
166
+ integrationOwner: 'main-agent',
167
+ inferred: true,
168
+ };
169
+ }
170
+
171
+ if (type === 'verification' || phase.includes('verification') || /验证|回归|测试|qa|verify|test/.test(text)) {
172
+ return {
173
+ mode: 'parallel-workers',
174
+ parallelGroup: 'verification',
175
+ writeScope: inferWriteScope('verification'),
176
+ ownerRole: 'worker',
177
+ localVerify: defaultLocalVerify(task),
178
+ integrationOwner: 'main-agent',
179
+ inferred: true,
180
+ };
181
+ }
182
+
183
+ if (phase.includes('integration') || /主流程|集成|闭环|联调|integration|flow/.test(text)) {
184
+ return {
185
+ mode: 'serial',
186
+ parallelGroup: 'integration',
187
+ writeScope: inferWriteScope('integration'),
188
+ ownerRole: 'main-agent',
189
+ localVerify: defaultLocalVerify(task),
190
+ integrationOwner: 'main-agent',
191
+ inferred: true,
192
+ };
193
+ }
194
+
195
+ const parallelGroup = inferParallelGroup(text, type, phase);
196
+ return {
197
+ mode: 'parallel-workers',
198
+ parallelGroup,
199
+ writeScope: inferWriteScope(parallelGroup),
200
+ ownerRole: 'worker',
201
+ localVerify: defaultLocalVerify(task),
202
+ integrationOwner: 'main-agent',
203
+ inferred: true,
204
+ };
205
+ }
206
+
207
+ export function taskExecutionStrategy(task = {}) {
208
+ const metadata = task.metadata ?? {};
209
+ const inferred = inferExecutionStrategyForTask(task);
210
+ const rawMode = normalizeToken(metadata['execution-mode']);
211
+ const rawGroup = normalizeToken(metadata['parallel-group']);
212
+ const rawOwnerRole = normalizeToken(metadata['owner-role']);
213
+ const rawIntegrationOwner = normalizeToken(metadata['integration-owner']);
214
+ const explicitWriteScope = normalizeWriteScopes(metadata['write-scope']);
215
+ const localVerify = String(metadata['local-verify'] ?? '').trim();
216
+
217
+ return {
218
+ mode: EXECUTION_MODE_VALUES.includes(rawMode) ? rawMode : inferred.mode,
219
+ parallelGroup: PARALLEL_GROUP_VALUES.includes(rawGroup) ? rawGroup : inferred.parallelGroup,
220
+ writeScope: explicitWriteScope.length > 0 ? explicitWriteScope : inferred.writeScope,
221
+ ownerRole: OWNER_ROLE_VALUES.includes(rawOwnerRole) ? rawOwnerRole : inferred.ownerRole,
222
+ localVerify: localVerify || inferred.localVerify,
223
+ integrationOwner: INTEGRATION_OWNER_VALUES.includes(rawIntegrationOwner) ? rawIntegrationOwner : inferred.integrationOwner,
224
+ inferred: !EXECUTION_MODE_VALUES.includes(rawMode)
225
+ || !PARALLEL_GROUP_VALUES.includes(rawGroup)
226
+ || explicitWriteScope.length === 0
227
+ || !OWNER_ROLE_VALUES.includes(rawOwnerRole)
228
+ || !localVerify
229
+ || !INTEGRATION_OWNER_VALUES.includes(rawIntegrationOwner),
230
+ };
231
+ }
232
+
233
+ export function formatTaskExecutionStrategyMetadata(task = {}) {
234
+ const strategy = inferExecutionStrategyForTask(task);
235
+ return [
236
+ `execution-mode: ${strategy.mode}`,
237
+ `parallel-group: ${strategy.parallelGroup}`,
238
+ `write-scope: ${strategy.writeScope.join(', ')}`,
239
+ `owner-role: ${strategy.ownerRole}`,
240
+ `local-verify: ${strategy.localVerify}`,
241
+ `integration-owner: ${strategy.integrationOwner}`,
242
+ ];
243
+ }
244
+
245
+ export function validateTaskExecutionStrategy(task = {}) {
246
+ const metadata = task.metadata ?? {};
247
+ const errors = [];
248
+ if (metadata['execution-mode']) {
249
+ const mode = normalizeToken(metadata['execution-mode']);
250
+ if (!EXECUTION_MODE_VALUES.includes(mode)) {
251
+ errors.push(`execution-mode 无效: ${metadata['execution-mode']};允许值: ${EXECUTION_MODE_VALUES.join(', ')}`);
252
+ }
253
+ }
254
+ if (metadata['parallel-group']) {
255
+ const group = normalizeToken(metadata['parallel-group']);
256
+ if (!PARALLEL_GROUP_VALUES.includes(group)) {
257
+ errors.push(`parallel-group 无效: ${metadata['parallel-group']};允许值: ${PARALLEL_GROUP_VALUES.join(', ')}`);
258
+ }
259
+ }
260
+ if (metadata['owner-role']) {
261
+ const role = normalizeToken(metadata['owner-role']);
262
+ if (!OWNER_ROLE_VALUES.includes(role)) {
263
+ errors.push(`owner-role 无效: ${metadata['owner-role']};允许值: ${OWNER_ROLE_VALUES.join(', ')}`);
264
+ }
265
+ }
266
+ if (metadata['integration-owner']) {
267
+ const owner = normalizeToken(metadata['integration-owner']);
268
+ if (!INTEGRATION_OWNER_VALUES.includes(owner)) {
269
+ errors.push(`integration-owner 无效: ${metadata['integration-owner']};允许值: ${INTEGRATION_OWNER_VALUES.join(', ')}`);
270
+ }
271
+ }
272
+
273
+ const hasExecutionMetadata = EXECUTION_STRATEGY_METADATA_KEYS.some((key) => Boolean(metadata[key]));
274
+ if (hasExecutionMetadata) {
275
+ if (normalizeWriteScopes(metadata['write-scope']).length === 0) {
276
+ errors.push('已声明执行策略,但缺少 write-scope。');
277
+ }
278
+ if (!String(metadata['local-verify'] ?? task.verify ?? '').trim()) {
279
+ errors.push('已声明执行策略,但缺少 local-verify 或 verify。');
280
+ }
281
+ }
282
+
283
+ const role = normalizeToken(metadata['owner-role']);
284
+ if (role === 'worker' && !String(metadata['integration-owner'] ?? '').trim()) {
285
+ errors.push('worker 任务必须声明 integration-owner。');
286
+ }
287
+ return errors;
288
+ }
289
+
290
+ export function summarizeTaskExecutionStrategies(tasks = []) {
291
+ const modeCounts = Object.fromEntries(EXECUTION_MODE_VALUES.map((mode) => [mode, 0]));
292
+ const groupCounts = Object.fromEntries(PARALLEL_GROUP_VALUES.map((group) => [group, 0]));
293
+ const ownerRoleCounts = Object.fromEntries(OWNER_ROLE_VALUES.map((role) => [role, 0]));
294
+ const taskStrategies = [];
295
+ let explicit = 0;
296
+ let inferred = 0;
297
+ let workerCount = 0;
298
+ let isolatedCount = 0;
299
+ let writeScopeDeclared = 0;
300
+
301
+ for (const task of tasks) {
302
+ const strategy = taskExecutionStrategy(task);
303
+ if (strategy.inferred) {
304
+ inferred += 1;
305
+ } else {
306
+ explicit += 1;
307
+ }
308
+ modeCounts[strategy.mode] = (modeCounts[strategy.mode] ?? 0) + 1;
309
+ groupCounts[strategy.parallelGroup] = (groupCounts[strategy.parallelGroup] ?? 0) + 1;
310
+ ownerRoleCounts[strategy.ownerRole] = (ownerRoleCounts[strategy.ownerRole] ?? 0) + 1;
311
+ if (strategy.ownerRole === 'worker') {
312
+ workerCount += 1;
313
+ }
314
+ if (strategy.mode === 'parallel-workers-isolated') {
315
+ isolatedCount += 1;
316
+ }
317
+ if ((strategy.writeScope ?? []).length > 0) {
318
+ writeScopeDeclared += 1;
319
+ }
320
+ taskStrategies.push({
321
+ id: task.id ?? null,
322
+ title: task.title ?? null,
323
+ mode: strategy.mode,
324
+ parallelGroup: strategy.parallelGroup,
325
+ ownerRole: strategy.ownerRole,
326
+ integrationOwner: strategy.integrationOwner,
327
+ writeScope: strategy.writeScope,
328
+ localVerify: strategy.localVerify,
329
+ inferred: strategy.inferred,
330
+ description: describeExecutionStrategy(strategy),
331
+ });
332
+ }
333
+
334
+ const total = taskStrategies.length;
335
+ const warnings = [];
336
+ if (total > 0 && explicit === 0) {
337
+ warnings.push('当前任务尚未显式声明执行策略,run/loop 将使用结构推导结果。');
338
+ }
339
+ if (workerCount > 0 && writeScopeDeclared < workerCount) {
340
+ warnings.push('部分 worker 任务缺少明确 write-scope,主 Agent 分片时需要先补边界。');
341
+ }
342
+ if (isolatedCount > 0 && ownerRoleCounts.worker === 0) {
343
+ warnings.push('声明了 isolated 执行模式,但没有 worker 角色任务。');
344
+ }
345
+
346
+ return {
347
+ total,
348
+ explicit,
349
+ inferred,
350
+ workerCount,
351
+ isolatedCount,
352
+ writeScopeDeclared,
353
+ modeCounts,
354
+ groupCounts,
355
+ ownerRoleCounts,
356
+ tasks: taskStrategies,
357
+ warnings,
358
+ recommendations: [
359
+ 'L0 或小范围修正默认保持 serial,由主 Agent 直接收口。',
360
+ 'L1/L2 中写入范围可切开的任务优先用 parallel-workers,让 worker 先做局部实现和局部验证。',
361
+ '高风险重构或大量实现任务再升级到 parallel-workers-isolated,由主 Agent 统一集成和总验证。',
362
+ ],
363
+ };
364
+ }
365
+
366
+ export function chooseExecutionMode(task = {}) {
367
+ const strategy = taskExecutionStrategy(task);
368
+ return firstKnown([strategy.mode], EXECUTION_MODE_VALUES, 'serial');
369
+ }
package/src/fleet.js CHANGED
@@ -297,12 +297,16 @@ async function collectFleetProjectHealth(projectPath, options, doctorWorkspace)
297
297
  return { ok: true, errors: [] };
298
298
  }
299
299
  try {
300
+ // Batch refresh should focus on generated guidance and workspace skeleton health.
301
+ // Legacy content-doc debt remains available in direct doctor/standards checks.
300
302
  const doctor = await doctorWorkspace(projectPath, {
301
303
  tools: options.tools ?? 'all',
302
304
  hookProfile: options.hookProfile,
303
305
  enableUserCodexConfig: Boolean(options.enableUserCodexConfig),
304
306
  codexHome: options.codexHome,
305
307
  openprdHome: options.openprdHome,
308
+ docsContent: false,
309
+ sourceManuals: false,
306
310
  });
307
311
  return {
308
312
  ok: doctor.ok,
@@ -0,0 +1,156 @@
1
+ /*
2
+ * 核心功能
3
+ * 从 package.json 与项目级 release-ledger 生成 GitHub Release 标题和正文。
4
+ *
5
+ * 输入
6
+ * 接收项目根目录、目标版本号、tag 和可选标题覆盖。
7
+ *
8
+ * 输出
9
+ * 返回结构化 GitHub Release payload,或把正文写入指定 Markdown 文件。
10
+ *
11
+ * 定位
12
+ * 位于 OpenPrd 的开源发布表达层,保证 GitHub Release 与 release-ledger 共用同一份版本事实。
13
+ *
14
+ * 依赖
15
+ * 仅依赖 Node 内置文件系统和 change-summary,不要求安装额外 node_modules。
16
+ *
17
+ * 维护规则
18
+ * 发现 package version、tag 或 release-ledger 条目不一致时必须显式失败,避免出现有版本但没有对应 Release 的发布。
19
+ */
20
+ import fs from 'node:fs/promises';
21
+ import path from 'node:path';
22
+ import { buildChangeSummaryFromEntries } from './change-summary.js';
23
+
24
+ function cjoin(...parts) {
25
+ return path.join(...parts);
26
+ }
27
+
28
+ async function readJson(filePath) {
29
+ const text = await fs.readFile(filePath, 'utf8');
30
+ return JSON.parse(text);
31
+ }
32
+
33
+ function normalizeVersionInput(value) {
34
+ const text = String(value ?? '').trim();
35
+ if (!text) return '';
36
+ return text.startsWith('v') ? text.slice(1) : text;
37
+ }
38
+
39
+ function normalizeTagInput(value, fallbackVersion = '') {
40
+ const text = String(value ?? '').trim();
41
+ if (text) return text;
42
+ const version = normalizeVersionInput(fallbackVersion);
43
+ return version ? `v${version}` : '';
44
+ }
45
+
46
+ function renderReleaseMarkdown({ version, tag, changeSummary }) {
47
+ const bullets = changeSummary.items.map((item) => `- ${item.sentence}`);
48
+ return [
49
+ '## 安装',
50
+ '',
51
+ '```bash',
52
+ `npm install -g @openprd/cli@${version}`,
53
+ '```',
54
+ '',
55
+ '## 本次更新',
56
+ '',
57
+ ...bullets,
58
+ '',
59
+ '## 发布信息',
60
+ '',
61
+ `- 版本:${version}`,
62
+ `- Tag:${tag}`,
63
+ '- 来源:GitHub Release 文案由当前项目的 release-ledger 自动生成。',
64
+ '',
65
+ ].join('\n').trimEnd();
66
+ }
67
+
68
+ function errorResult(projectRoot, code, error) {
69
+ return {
70
+ ok: false,
71
+ projectRoot,
72
+ code,
73
+ error: error instanceof Error ? error.message : String(error),
74
+ };
75
+ }
76
+
77
+ export async function buildGitHubReleasePayload(projectRoot, options = {}) {
78
+ const resolvedRoot = path.resolve(projectRoot || '.');
79
+ const packageJsonPath = cjoin(resolvedRoot, 'package.json');
80
+ const ledgerPath = cjoin(resolvedRoot, '.openprd', 'state', 'release-ledger.json');
81
+
82
+ try {
83
+ const requestedVersion = normalizeVersionInput(options.version || options.tag || '');
84
+ const pkg = await readJson(packageJsonPath).catch((error) => {
85
+ throw new Error(`Missing package.json at ${packageJsonPath}: ${error.message}`);
86
+ });
87
+ const packageVersion = normalizeVersionInput(pkg.version);
88
+ if (!packageVersion) {
89
+ throw new Error(`Missing package version in ${packageJsonPath}.`);
90
+ }
91
+ if (requestedVersion && requestedVersion !== packageVersion) {
92
+ throw new Error(`Package version mismatch: package.json is ${packageVersion} but requested ${requestedVersion}.`);
93
+ }
94
+
95
+ const version = requestedVersion || packageVersion;
96
+ const tag = normalizeTagInput(options.tag, version);
97
+ const title = String(options.title || '').trim() || `OpenPrd ${version}`;
98
+
99
+ const ledger = await readJson(ledgerPath).catch((error) => {
100
+ throw new Error(`Missing release ledger at ${ledgerPath}: ${error.message}`);
101
+ });
102
+ const versions = Array.isArray(ledger.versions) ? ledger.versions : [];
103
+ const releaseEntry = versions.find((entry) => normalizeVersionInput(entry?.version) === version);
104
+ if (!releaseEntry) {
105
+ throw new Error([
106
+ `Missing release-ledger entry for version ${version}.`,
107
+ `Run: openprd release ${resolvedRoot} --set ${version}`,
108
+ 'Then add user-visible notes with `openprd release <path> --notes "..."` before publishing.',
109
+ ].join(' '));
110
+ }
111
+
112
+ const changeSummary = buildChangeSummaryFromEntries(releaseEntry.items ?? [], {
113
+ title: `${version} 变化摘要`,
114
+ limit: null,
115
+ fallbackType: '调整',
116
+ });
117
+ if (changeSummary.items.length === 0) {
118
+ throw new Error([
119
+ `Release-ledger entry for version ${version} has no release items.`,
120
+ `Run: openprd release ${resolvedRoot} --notes "新增 / 修复 / 优化 ..."`,
121
+ ].join(' '));
122
+ }
123
+
124
+ const markdown = renderReleaseMarkdown({ version, tag, changeSummary });
125
+ return {
126
+ ok: true,
127
+ projectRoot: resolvedRoot,
128
+ version,
129
+ tag,
130
+ title,
131
+ packageName: typeof pkg.name === 'string' ? pkg.name : null,
132
+ packageJsonPath,
133
+ ledgerPath,
134
+ itemCount: changeSummary.items.length,
135
+ changeSummary,
136
+ markdown,
137
+ };
138
+ } catch (error) {
139
+ return errorResult(resolvedRoot, 'github-release-build-failed', error);
140
+ }
141
+ }
142
+
143
+ export async function writeGitHubReleaseNotes(projectRoot, options = {}) {
144
+ const payload = await buildGitHubReleasePayload(projectRoot, options);
145
+ if (!payload.ok) {
146
+ return payload;
147
+ }
148
+
149
+ const out = path.resolve(options.out || cjoin(payload.projectRoot, 'release-notes.md'));
150
+ await fs.mkdir(path.dirname(out), { recursive: true });
151
+ await fs.writeFile(out, `${payload.markdown.trimEnd()}\n`, 'utf8');
152
+ return {
153
+ ...payload,
154
+ out,
155
+ };
156
+ }