@openprd/cli 0.1.9 → 0.1.11

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/benchmarks/index.md +16 -0
  2. package/.openprd/benchmarks/sources.yaml +53 -1
  3. package/.openprd/config.yaml +10 -0
  4. package/.openprd/design/README.md +47 -0
  5. package/.openprd/design/README_EN.md +25 -0
  6. package/.openprd/design/active/asset-spec.md +15 -0
  7. package/.openprd/design/active/direction-plan.md +7 -0
  8. package/.openprd/design/active/facts-sheet.md +13 -0
  9. package/.openprd/design/active/image-preflight.md +9 -0
  10. package/.openprd/design/active/selected-direction.md +11 -0
  11. package/.openprd/design/anti-slop.md +16 -0
  12. package/.openprd/design/assets/dark-mesh.svg +14 -0
  13. package/.openprd/design/assets/paper-grid.svg +14 -0
  14. package/.openprd/design/assets/surface-presets.md +21 -0
  15. package/.openprd/design/assets/workbench-grid.svg +16 -0
  16. package/.openprd/design/checklists/ui-quality-gate.md +22 -0
  17. package/.openprd/design/components/component-catalog.md +42 -0
  18. package/.openprd/design/layouts/layout-catalog.json +45 -0
  19. package/.openprd/design/lenses/frontend-lenses.md +31 -0
  20. package/.openprd/design/recipes/content-experience.md +26 -0
  21. package/.openprd/design/recipes/operational-tool.md +25 -0
  22. package/.openprd/design/recipes/product-launch.md +26 -0
  23. package/.openprd/design/templates/README.md +36 -0
  24. package/.openprd/design/templates/README_EN.md +31 -0
  25. package/.openprd/design/templates/content-home.html +415 -0
  26. package/.openprd/design/templates/ops-dashboard.html +340 -0
  27. package/.openprd/design/templates/product-launch.html +360 -0
  28. package/.openprd/design/themes/theme-catalog.json +77 -0
  29. package/.openprd/engagements/active/prd.md +111 -100
  30. package/.openprd/schema/prd.schema.yaml +25 -0
  31. package/.openprd/templates/base/intake.md +17 -0
  32. package/.openprd/templates/base/prd.md +31 -6
  33. package/AGENTS.md +10 -7
  34. package/README.md +26 -28
  35. package/README_EN.md +3 -3
  36. package/package.json +41 -2
  37. package/scripts/openprd-codex-isolated-worker.mjs +799 -0
  38. package/skills/openprd-benchmark-router/SKILL.md +5 -0
  39. package/skills/openprd-frontend-design/SKILL.md +161 -0
  40. package/skills/openprd-frontend-design/references/design-asset-contract.md +34 -0
  41. package/skills/openprd-frontend-design/references/direction-engine.md +46 -0
  42. package/skills/openprd-harness/SKILL.md +37 -20
  43. package/skills/openprd-learning-review/SKILL.md +2 -1
  44. package/skills/openprd-learning-review/references/style-packs/xianxia-cultivation.prompt.md +1 -1
  45. package/skills/openprd-quality/SKILL.md +5 -3
  46. package/skills/openprd-requirement-intake/SKILL.md +27 -5
  47. package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -3
  48. package/skills/openprd-requirement-intake/references/routing-rubric.md +9 -5
  49. package/skills/openprd-requirement-intake/references/startup-validation-lens.md +108 -0
  50. package/skills/openprd-router/SKILL.md +13 -5
  51. package/skills/openprd-shared/SKILL.md +38 -25
  52. package/src/agent-integration.js +120 -69
  53. package/src/brainstorm-artifacts.js +1805 -0
  54. package/src/brainstorm-presentation.js +233 -0
  55. package/src/brainstorm.js +766 -0
  56. package/src/cli/args.js +51 -3
  57. package/src/cli/doctor-print.js +34 -8
  58. package/src/cli/print.js +6 -0
  59. package/src/cli/quality-print.js +79 -3
  60. package/src/cli/run-print.js +43 -1
  61. package/src/cli/shared-print.js +33 -2
  62. package/src/cli/workflow-print.js +21 -0
  63. package/src/codex-hook-runner-template.mjs +838 -58
  64. package/src/design-starter-support.js +462 -0
  65. package/src/design-starter.js +1207 -0
  66. package/src/diagram-core.js +384 -151
  67. package/src/html-artifacts.js +403 -50
  68. package/src/knowledge.js +291 -32
  69. package/src/learning-review.js +3 -3
  70. package/src/loop.js +839 -101
  71. package/src/openprd.js +86 -1
  72. package/src/openspec/change-validate.js +1 -0
  73. package/src/prd-core.js +119 -2
  74. package/src/quality-html-artifact.js +30 -5
  75. package/src/quality-learning.js +50 -6
  76. package/src/quality.js +22 -12
  77. package/src/run-harness.js +191 -29
  78. package/src/self-update.js +301 -4
  79. package/src/standards.js +46 -5
  80. package/src/visual-prepare.js +940 -0
  81. package/src/workspace-core.js +175 -0
  82. package/src/workspace-workflow.js +307 -7
  83. package/.openprd/benchmarks/evidence/milvus-io-ai-code-review-gets-better-when-models-debate-claude-vs-gemini-vs-code.md +0 -14
  84. package/.openprd/benchmarks/evidence/nolanlawson-com-using-ai-to-write-better-code-more-slowly.md +0 -14
  85. package/.openprd/discovery/config.json +0 -35
  86. package/.openprd/engagements/active/flows.md +0 -35
  87. package/.openprd/engagements/active/handoff.md +0 -16
  88. package/.openprd/engagements/active/review.html +0 -61
  89. package/.openprd/engagements/active/roles.md +0 -22
  90. package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +0 -23
  91. package/.openprd/engagements/work-units/wu-20260602113956-a99b5b88.json +0 -18
  92. package/.openprd/engagements/work-units/wu-20260602122244-78656aaf.json +0 -18
  93. package/.openprd/engagements/work-units/wu-20260602122442-e96489e2.json +0 -18
  94. package/.openprd/engagements/work-units/wu-20260602132835-695429e8.json +0 -18
  95. package/.openprd/exports/.gitkeep +0 -0
  96. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/candidate.json +0 -78
  97. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/diagnostic-report.json +0 -129
  98. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/root-cause-candidates.json +0 -41
  99. package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/timeline.json +0 -14
  100. package/.openprd/knowledge/drafts/openprd-experience-diagnostic-candidate-turn-1780116203372-5f266a79e968c758/SKILL.md +0 -49
  101. package/.openprd/knowledge/index.json +0 -47
  102. package/.openprd/reviews/v0001.html +0 -1322
  103. package/.openprd/reviews/v0002.html +0 -1150
  104. package/.openprd/reviews/v0003.html +0 -1150
  105. package/.openprd/reviews/v0004.html +0 -1150
  106. package/.openprd/reviews/v0005.html +0 -1150
  107. package/.openprd/sessions/.gitkeep +0 -0
  108. package/.openprd/state/changes.json +0 -27
  109. package/.openprd/state/current.json +0 -505
  110. package/.openprd/state/release-ledger.json +0 -387
  111. package/.openprd/state/version-index.json +0 -67
  112. package/.openprd/state/versions/.gitkeep +0 -0
  113. package/.openprd/state/versions/v0001.json +0 -121
  114. package/.openprd/state/versions/v0001.md +0 -161
  115. package/.openprd/state/versions/v0002.json +0 -264
  116. package/.openprd/state/versions/v0002.md +0 -183
  117. package/.openprd/state/versions/v0003.json +0 -269
  118. package/.openprd/state/versions/v0003.md +0 -188
  119. package/.openprd/state/versions/v0004.json +0 -274
  120. package/.openprd/state/versions/v0004.md +0 -193
  121. package/.openprd/state/versions/v0005.json +0 -299
  122. package/.openprd/state/versions/v0005.md +0 -189
  123. package/docs/assets/openprd-capability-overview-en.png +0 -0
  124. package/docs/assets/openprd-capability-overview-zh.png +0 -0
  125. package/docs/assets/openprd-learning-html.png +0 -0
  126. package/docs/assets/openprd-quality-html.png +0 -0
  127. package/docs/assets/openprd-requirement-routing-en.png +0 -0
  128. package/docs/assets/openprd-requirement-routing-en.svg +0 -102
  129. package/docs/assets/openprd-requirement-routing-zh-refined.png +0 -0
  130. package/docs/assets/openprd-requirement-routing-zh.png +0 -0
  131. package/docs/assets/openprd-requirement-routing-zh.svg +0 -102
  132. package/docs/assets/openprd-review-html.png +0 -0
  133. package/docs/assets/openprd-scenario-overview.png +0 -0
  134. package/docs/assets/openprd-scenario-overview.svg +0 -114
  135. package/docs/assets/openprd-self-evolving-mechanisms-en.png +0 -0
  136. package/docs/assets/openprd-self-evolving-mechanisms-zh.png +0 -0
  137. package/docs/assets/openprd-visual-compare-case-study-en.png +0 -0
  138. package/docs/assets/openprd-visual-compare-case-study-zh.png +0 -0
@@ -32,6 +32,13 @@ const CONTINUATION_WORK_UNIT_PATTERN = /\bwu-[a-z0-9._-]+\b/i;
32
32
  const CONTINUATION_EXPLICIT_PATTERN = /(?:(?:继续|续做|接着做|继续执行|继续推进)(?:这个|这条|当前)?\s*(?:对话|任务|会话|记录|历史|Codex\s*任务)|(?:对话|任务|会话|记录|历史|Codex\s*任务).{0,6}(?:继续|续做|接着做|继续执行|继续推进)|^(?:继续|续做|接着做|继续执行|继续推进)\s*(?::|:))/i;
33
33
  const CONTINUATION_CURRENT_PATTERN = /(继续当前|当前(这个|这条)?(任务|会话|记录|需求|变更)|current\s+(task|change|session)|resume current)/i;
34
34
  const SHORT_AFFIRMATIVE_PATTERN = /^(可以|好|行|确认|没问题|OK|ok|yes|Yes|yep|Yep)[。!!,.,\s]*$/;
35
+ const EXPLICIT_ISOLATION_NEGATION_PATTERN = /(?:(?:不要|别|不用|无需|不需要|禁止|不要再|不想).{0,8}(?:单独环境|隔离环境|独立环境|独立\s*(?:session|cwd)|独立工作树|单独\s*worktree|worktree|新分支|独立分支)|(?:单独环境|隔离环境|独立环境|独立\s*(?:session|cwd)|独立工作树|单独\s*worktree|worktree|新分支|独立分支).{0,8}(?:不要|别|不用|无需|不需要|禁止))/i;
36
+ const EXPLICIT_ISOLATION_REQUEST_PATTERNS = [
37
+ /(?:请|麻烦|需要|希望|可以|最好|改成|按).{0,8}(?:单独环境|隔离环境|独立环境|独立\s*(?:session|cwd)|独立工作树|单独\s*worktree|worktree|新分支|独立分支).{0,12}(?:继续|处理|推进|实现|做|执行)/i,
38
+ /(?:继续|处理|推进|实现|做|执行).{0,12}(?:单独环境|隔离环境|独立环境|独立\s*(?:session|cwd)|独立工作树|单独\s*worktree|worktree|新分支|独立分支)/i,
39
+ /(?:use|with|in)\s+(?:an?\s+)?(?:isolated|separate|dedicated)\s+(?:environment|session|cwd|worktree|branch)/i,
40
+ ];
41
+ const EXPLICIT_BRAINSTORM_REQUEST = /(脑暴|brainstorm|帮忙梳理|梳理一下|梳理下|先想清楚)/iu;
35
42
  function harnessFile(projectRoot, relativePath) {
36
43
  return cjoin(projectRoot, relativePath);
37
44
  }
@@ -43,6 +50,14 @@ function rootsEqual(left, right) {
43
50
  return path.resolve(left) === path.resolve(right);
44
51
  }
45
52
 
53
+ function hasExplicitIsolationRequest(message = null) {
54
+ const text = String(message ?? '').trim();
55
+ if (!text || EXPLICIT_ISOLATION_NEGATION_PATTERN.test(text)) {
56
+ return false;
57
+ }
58
+ return EXPLICIT_ISOLATION_REQUEST_PATTERNS.some((pattern) => pattern.test(text));
59
+ }
60
+
46
61
  async function ensureRunHarness(projectRoot) {
47
62
  await fs.mkdir(harnessFile(projectRoot, OPENPRD_HARNESS_DIR), { recursive: true });
48
63
  const statePath = harnessFile(projectRoot, OPENPRD_HARNESS_RUN_STATE);
@@ -339,6 +354,61 @@ function analyzeRunMessage(message = null) {
339
354
  };
340
355
  }
341
356
 
357
+ function inferPromptDrivenLightweightRecommendation(message = null) {
358
+ const text = String(message ?? '').trim();
359
+ if (!text) {
360
+ return null;
361
+ }
362
+ const explicitBrainstorm = EXPLICIT_BRAINSTORM_REQUEST.test(text);
363
+ const readOnly = /(先分析|先看看|先评估|review|审查|怎么改|规划一下|先梳理|先想清楚|会动哪些文件)/i.test(text)
364
+ && !/(直接实现|直接做|直接改|帮我修|帮我实现|请直接实现|继续实现|直接完成)/i.test(text);
365
+ if (explicitBrainstorm || readOnly) {
366
+ return null;
367
+ }
368
+ const noConfirmationRequested = /(不需要(?:先)?(?:来回)?确认|无需(?:任何)?确认|不用确认|别再确认|不要确认|直接完成|直接做完)/i.test(text);
369
+ const scopedFrontendSurface = /(首页|页面|界面|落地页|原型|静态单页|静态页|详情页|设置页|列表页|dashboard|hero|layout|app\s*首页)/i.test(text);
370
+ const scopedFrontendChange = scopedFrontendSurface
371
+ && /(实现|做一个|做个|完成|新增|增加|补一个|优化|调整|改版|重做|prototype|原型)/i.test(text);
372
+ const tinyUiAdjustment = /(按钮|文案|颜色|圆角|位置|间距|字号|图标|标题|空格|标点|label|copy|toast|placeholder)/i.test(text)
373
+ && /(改|调|修|优化|调整|短一点|换成|统一)/i.test(text);
374
+ if (tinyUiAdjustment) {
375
+ return {
376
+ type: 'lightweight-implementation',
377
+ nextAction: 'lightweight-l0',
378
+ title: '按直接处理路径继续',
379
+ command: null,
380
+ verifyCommand: 'openprd run . --verify',
381
+ reason: [
382
+ '当前更像一次局部直接处理,不必先走正式 PRD/review/change/tasks。',
383
+ '直接落地改动并补最小足够验证即可。',
384
+ ].join(' '),
385
+ changeId: null,
386
+ task: null,
387
+ coverageItem: null,
388
+ };
389
+ }
390
+ if (!scopedFrontendChange) {
391
+ return null;
392
+ }
393
+ return {
394
+ type: 'lightweight-implementation',
395
+ nextAction: 'lightweight-l1',
396
+ title: noConfirmationRequested ? '按轻量原型路径继续实现' : '先给 mini-plan 再继续实现',
397
+ command: null,
398
+ verifyCommand: 'openprd run . --verify',
399
+ reason: [
400
+ '当前更像现有功能优化或轻量原型实现,不必先走正式 PRD/review/change/tasks。',
401
+ noConfirmationRequested
402
+ ? '用户已经明确表示不需要先来回确认,可以先用 3-5 行 mini-plan 自定目标、范围内、范围外和验证方式,然后继续实现。'
403
+ : '先用 3-5 行 mini-plan 收一下目标、范围内、范围外和验证方式,再继续实现。',
404
+ '如果是新界面或大幅首页调整,先补 `.openprd/design/active/` 下的 facts-sheet、asset-spec、image-preflight、direction-plan 和 selected-direction;空白工作区优先从 `.openprd/design/templates/` 里挑最近模板。若当前轮用户已经把页面主题、模块范围或“直接实现”的意图说清,优先改用 `openprd run . --context --message <用户原话>` 对齐建议,而不是先跑不带 message 的 context。若页面主题和模块范围已经明确,优先运行 `openprd design-starter . --starter <starter-id> --out index.html --brief "<页面主题>" --sections "<模块1|模块2|模块3>"`,让 starter 一次写实合同和首版文案;只有像个人博客、工具台、纯结构化产品页这类确认不靠真实图片成立的页面,才补 `--no-external-facts --no-brand-assets --no-real-images`。若任务更像旅游、导览、展览、博物馆、城市、自然观察或案例内容页,先不要带 `--no-real-images`,让 starter 先尝试补首批真实图片。若用户已经给了效果图、设计稿、参考截图或其他明确参考图,先把它当主参考源;只有现有 starter、theme、layout 足够接近时才复用,不接近就以参考图为准。若这类冷启动即使带 message 仍短暂返回 `clarify-user`,把它视为摘要级提醒:先用 3-5 行 mini-plan 收口,再进入 starter 后的 `Patch Mode`。`Patch Mode` 的默认动作是继续在当前 `index.html` 上补丁细化;就算想整页重写,也是在同一路径内覆盖,不做 delete-first。如果确实要整页重写,先把完整新稿写到 sibling draft,例如 `index.next.html`,确认内容成形后再覆盖回 `index.html`,不要让入口文件出现空窗。starter 一落地后,只允许做一轮就地对焦:快速读一次生成的入口文件和必要的 active design artifacts;这轮对焦结束后,下一步就必须是真实写入口,不要再回头搜网页、翻 `docs/basic/` 或继续模板漫游。把最后一批必要的查事实、查图、读模板动作放在口头宣布之前做完;一旦已经说“开始覆盖入口文件”或“开始整页重写”,下一步必须出现真实写文件动作,而不是继续只读浏览、压图或停在口头承诺;必要时 hook 会把这类非写入动作挡回去。真正完成还包括:入口文件本体已改完、主要占位已清掉、已准备好的真实图片或参考约束已落进页面,不是只补合同或只下载素材。',
405
+ ].join(' '),
406
+ changeId: null,
407
+ task: null,
408
+ coverageItem: null,
409
+ };
410
+ }
411
+
342
412
  function normalizeSearchText(value) {
343
413
  return String(value ?? '')
344
414
  .normalize('NFKC')
@@ -1184,6 +1254,7 @@ function buildSessionContinuationRecommendation(recommendation, lane) {
1184
1254
  const sessionId = lane?.target?.sessionId ?? lane?.sessionId ?? lane?.selector ?? null;
1185
1255
  const targetWorkspaceRoot = lane?.target?.workspaceRoot ?? lane?.resolution?.workspaceRoot ?? lane?.currentProjectRoot ?? null;
1186
1256
  const crossWorkspace = Boolean(targetWorkspaceRoot && lane?.currentProjectRoot && !rootsEqual(targetWorkspaceRoot, lane.currentProjectRoot));
1257
+ const explicitIsolationRequest = hasExplicitIsolationRequest(lane?.text);
1187
1258
  const recoveredTarget = [
1188
1259
  lane?.target?.changeId ? `变更 ${lane.target.changeId}` : null,
1189
1260
  lane?.target?.taskHandle ? `任务句柄 ${lane.target.taskHandle}` : null,
@@ -1221,9 +1292,19 @@ function buildSessionContinuationRecommendation(recommendation, lane) {
1221
1292
  coverageItem: null,
1222
1293
  continuationTarget: lane.target ?? null,
1223
1294
  isolation: {
1224
- required: true,
1225
- worktreeRecommended: true,
1226
- reason: crossWorkspace ? '跨工作区恢复时先回到正确 workspace,再在隔离环境里继续实现。' : '历史会话恢复默认建议使用独立 session / cwd,避免共享执行线内容。',
1295
+ required: crossWorkspace,
1296
+ worktreeRecommended: explicitIsolationRequest,
1297
+ reason: explicitIsolationRequest
1298
+ ? (
1299
+ crossWorkspace
1300
+ ? '先切回正确工作区,再按你明确要求的单独环境方式继续推进。'
1301
+ : '你已经明确要求按单独环境处理;恢复到对应上下文后,我会按隔离方式继续推进。'
1302
+ )
1303
+ : (
1304
+ crossWorkspace
1305
+ ? '先切回正确工作区继续推进,不默认追加单独环境。'
1306
+ : '历史会话恢复默认沿用当前上下文继续推进,不额外建议单独环境。'
1307
+ ),
1227
1308
  },
1228
1309
  previousRecommendation: recommendation
1229
1310
  ? {
@@ -1355,7 +1436,14 @@ function buildPrdPromotionRecommendation({ changes, next }) {
1355
1436
 
1356
1437
  function buildRequirementIntakeRecommendation({ gate, next, activeChange }) {
1357
1438
  const nextAction = next?.recommendation?.nextAction ?? 'clarify-user';
1439
+ const brainstormSuggestion = next?.brainstormSuggestion?.recommended ? next.brainstormSuggestion : null;
1440
+ const shouldRouteToBrainstorm = Boolean(
1441
+ brainstormSuggestion
1442
+ && brainstormSuggestion.explicitTrigger === true
1443
+ && ['clarify-user', 'classify', 'interview'].includes(nextAction)
1444
+ );
1358
1445
  const titleByAction = {
1446
+ brainstorm: '先进入脑暴模式收敛方向',
1359
1447
  'clarify-user': '继续本轮需求入口澄清',
1360
1448
  classify: '补齐本轮需求的产品类型',
1361
1449
  interview: '补齐本轮需求的关键事实',
@@ -1367,13 +1455,20 @@ function buildRequirementIntakeRecommendation({ gate, next, activeChange }) {
1367
1455
  };
1368
1456
  return {
1369
1457
  type: 'requirement-intake',
1370
- title: titleByAction[nextAction] ?? '继续本轮需求入口',
1371
- command: next?.recommendation?.suggestedCommand ?? 'openprd clarify .',
1458
+ nextAction: shouldRouteToBrainstorm ? 'brainstorm' : nextAction,
1459
+ title: titleByAction[shouldRouteToBrainstorm ? 'brainstorm' : nextAction] ?? '继续本轮需求入口',
1460
+ command: shouldRouteToBrainstorm
1461
+ ? (brainstormSuggestion?.suggestedCommand ?? 'openprd brainstorm . --open')
1462
+ : (next?.recommendation?.suggestedCommand ?? 'openprd clarify .'),
1372
1463
  verifyCommand: 'openprd run . --verify',
1373
1464
  reason: [
1374
- '当前有一条还在推进中的新需求;先把需求澄清、确认,再整理本次调整和后续任务。',
1465
+ shouldRouteToBrainstorm
1466
+ ? '当前这条需求更适合先把方向、替代方案、目标结果和验证方式梳理清楚,再进入正式 PRD。'
1467
+ : '当前有一条还在推进中的新需求;先把需求澄清、确认,再整理本次调整和后续任务。',
1375
1468
  activeChange ? `之前还有一项历史事项 ${activeChange},这里只把它当背景提醒,不抢这次主线。` : null,
1376
- next?.recommendation?.reason ?? null,
1469
+ shouldRouteToBrainstorm
1470
+ ? (brainstormSuggestion?.reason ?? next?.recommendation?.reason ?? null)
1471
+ : (next?.recommendation?.reason ?? null),
1377
1472
  ].filter(Boolean).join(' '),
1378
1473
  changeId: null,
1379
1474
  task: null,
@@ -1387,6 +1482,40 @@ function buildRequirementIntakeRecommendation({ gate, next, activeChange }) {
1387
1482
  };
1388
1483
  }
1389
1484
 
1485
+ function hasExplicitBrainstormRequest(message) {
1486
+ return EXPLICIT_BRAINSTORM_REQUEST.test(String(message ?? ''));
1487
+ }
1488
+
1489
+ function buildBrainstormRunRecommendation({ next, message, activeChange, focusedChangeId }) {
1490
+ const brainstormSuggestion = next?.brainstormSuggestion?.recommended ? next.brainstormSuggestion : null;
1491
+ const nextAction = next?.recommendation?.nextAction ?? 'clarify-user';
1492
+ const explicitBrainstorm = hasExplicitBrainstormRequest(message) || brainstormSuggestion?.explicitTrigger === true;
1493
+ if (
1494
+ !explicitBrainstorm
1495
+ || !['clarify-user', 'classify', 'interview'].includes(nextAction)
1496
+ ) {
1497
+ return null;
1498
+ }
1499
+ return {
1500
+ type: 'workflow',
1501
+ nextAction: 'brainstorm',
1502
+ title: '先进入脑暴模式收敛方向',
1503
+ command: brainstormSuggestion.suggestedCommand ?? 'openprd brainstorm . --open',
1504
+ verifyCommand: 'openprd validate .',
1505
+ reason: [
1506
+ '当前这条需求更适合先把方向、替代方案、目标结果和验证方式梳理清楚,再进入正式 PRD。',
1507
+ activeChange ? `之前还有一项历史事项 ${activeChange},这里只把它当背景提醒,不抢这次主线。` : null,
1508
+ brainstormSuggestion?.reason
1509
+ ?? (explicitBrainstorm ? '用户当前更像是在请求先梳理业务方向。' : null)
1510
+ ?? next?.recommendation?.reason
1511
+ ?? null,
1512
+ ].filter(Boolean).join(' '),
1513
+ changeId: focusedChangeId ?? activeChange,
1514
+ task: null,
1515
+ coverageItem: null,
1516
+ };
1517
+ }
1518
+
1390
1519
  function buildStoredVerificationRecommendation(runState, options = {}) {
1391
1520
  const lastVerification = runState?.lastVerification;
1392
1521
  if (!lastVerification || lastVerification.taskReady !== true) {
@@ -1495,19 +1624,27 @@ function buildRunRecommendation({
1495
1624
  if (requirementGateAssessment?.relevance === 'primary') {
1496
1625
  return buildRequirementIntakeRecommendation({ gate: requirementGate, next, activeChange });
1497
1626
  }
1627
+ const brainstormRecommendation = (
1628
+ !laneRequest?.requested
1629
+ && !resolvedTarget?.matched
1630
+ )
1631
+ ? buildBrainstormRunRecommendation({ next, message, activeChange, focusedChangeId })
1632
+ : null;
1633
+ if (brainstormRecommendation) {
1634
+ return brainstormRecommendation;
1635
+ }
1498
1636
  if (taskState?.nextTask) {
1499
1637
  const task = compactTask(taskState.nextTask);
1500
1638
  const totalTasks = Number(taskState.summary?.total ?? taskState.tasks?.length ?? 0);
1501
1639
  const pendingTasks = Number(taskState.summary?.pending ?? 0);
1502
1640
  const implementationTasks = Number(taskState.summary?.implementation?.total ?? 0);
1503
1641
  const pendingImplementationTasks = Number(taskState.summary?.implementation?.pending ?? 0);
1504
- const laneRequiresIsolation = Boolean(
1642
+ const explicitIsolationRequest = hasExplicitIsolationRequest(message);
1643
+ const laneTargetsHistoricalContext = Boolean(
1505
1644
  resolvedTarget?.workspaceRoot && !rootsEqual(resolvedTarget.workspaceRoot, projectRoot)
1506
1645
  || ['session', 'task-handle', 'work-unit'].includes(laneRequest?.selectorType ?? '')
1507
1646
  );
1508
- const executionMode = laneRequiresIsolation
1509
- ? 'parallel-workers-isolated'
1510
- : (
1647
+ const executionMode = (
1511
1648
  implementationTasks >= OPENPRD_LOOP_REQUIRED_IMPLEMENTATION_TASK_THRESHOLD
1512
1649
  || pendingImplementationTasks >= OPENPRD_LOOP_REQUIRED_IMPLEMENTATION_TASK_THRESHOLD
1513
1650
  )
@@ -1522,7 +1659,7 @@ function buildRunRecommendation({
1522
1659
  executionMode,
1523
1660
  taskState,
1524
1661
  focusTask: task,
1525
- worktreeRecommended: laneRequiresIsolation || executionMode === 'parallel-workers-isolated',
1662
+ worktreeRecommended: explicitIsolationRequest || executionMode === 'parallel-workers-isolated',
1526
1663
  });
1527
1664
  if (
1528
1665
  implementationTasks >= OPENPRD_LOOP_REQUIRED_IMPLEMENTATION_TASK_THRESHOLD
@@ -1541,9 +1678,11 @@ function buildRunRecommendation({
1541
1678
  : `openprd loop . --plan --change ${shellQuote(taskState.changeId)} && openprd loop . --run --agent codex --item ${shellQuote(task.id)}`,
1542
1679
  commitCommand: `openprd loop . --finish --item ${shellQuote(task.id)} --commit`,
1543
1680
  verifyCommand: `openprd loop . --verify --item ${shellQuote(task.id)}`,
1544
- reason: laneRequiresIsolation
1545
- ? '这件事来自指定历史记录,最好放到单独环境里接着做,避免和别的事项串线。'
1546
- : '待落地内容比较多,适合拆成一个个独立小任务推进,再统一收口检查。',
1681
+ reason: explicitIsolationRequest
1682
+ ? '你已经明确要求按单独环境继续;我会先回到对应上下文,再按长程 loop 方式推进。'
1683
+ : laneTargetsHistoricalContext
1684
+ ? '这件事来自指定历史记录;先回到对应上下文,再按长程 loop 方式拆成小任务推进。'
1685
+ : '待落地内容比较多,适合拆成一个个独立小任务推进,再统一收口检查。',
1547
1686
  changeId: taskState.changeId,
1548
1687
  task,
1549
1688
  coverageItem: null,
@@ -1562,8 +1701,10 @@ function buildRunRecommendation({
1562
1701
  },
1563
1702
  };
1564
1703
  }
1565
- const lightweightReason = laneRequiresIsolation
1566
- ? '这件事来自指定历史记录,最好放到单独环境里继续,避免和别的事项串线。'
1704
+ const lightweightReason = explicitIsolationRequest
1705
+ ? '你已经明确要求按单独环境继续;我会沿着当前任务上下文按隔离方式推进。'
1706
+ : laneTargetsHistoricalContext
1707
+ ? '这件事来自指定历史记录;先回到对应上下文继续推进,不默认追加单独环境。'
1567
1708
  : executionMode === 'parallel-workers'
1568
1709
  ? '待处理的落地内容比较多,适合先分头推进,再统一收口检查。'
1569
1710
  : '已经有一项可以继续推进的后续任务;只要用户明确要继续,就可以往下做。';
@@ -1590,7 +1731,7 @@ function buildRunRecommendation({
1590
1731
  pendingTasks,
1591
1732
  implementationTasks,
1592
1733
  pendingImplementationTasks,
1593
- worktreeRecommended: laneRequiresIsolation,
1734
+ worktreeRecommended: explicitIsolationRequest,
1594
1735
  },
1595
1736
  };
1596
1737
  }
@@ -1674,7 +1815,15 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
1674
1815
  } = dependencies;
1675
1816
  await ensureRunHarness(projectRoot);
1676
1817
  const runState = await readRunState(projectRoot);
1677
- const laneRequest = analyzeRunMessage(options.message);
1818
+ const currentTurn = await readJson(harnessFile(projectRoot, OPENPRD_HARNESS_TURN_STATE)).catch(() => null);
1819
+ const currentTurnPrompt = String(currentTurn?.prompt ?? '').trim();
1820
+ const fallbackPrompt = !options.hookInject
1821
+ ? (inferPromptDrivenLightweightRecommendation(currentTurnPrompt) ? currentTurnPrompt : null)
1822
+ : null;
1823
+ const effectiveMessage = String(options.message ?? '').trim()
1824
+ || fallbackPrompt
1825
+ || null;
1826
+ const laneRequest = analyzeRunMessage(effectiveMessage);
1678
1827
  const validation = await validateWorkspace(projectRoot)
1679
1828
  .then(({ report }) => report)
1680
1829
  .catch((error) => ({
@@ -1713,12 +1862,12 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
1713
1862
  }
1714
1863
  const currentWorkspaceArtifacts = await resolveWorkspaceArtifacts(projectRoot);
1715
1864
  const loopFeatureList = currentWorkspaceArtifacts.loopFeatureList;
1716
- const shouldResolveTarget = Boolean(String(options.message ?? '').trim());
1865
+ const shouldResolveTarget = Boolean(String(effectiveMessage ?? '').trim());
1717
1866
  const resolutionIndex = shouldResolveTarget ? currentWorkspaceArtifacts.index : null;
1718
1867
  const resolvedTarget = shouldResolveTarget
1719
1868
  ? await resolveRunTarget({
1720
1869
  projectRoot,
1721
- message: options.message,
1870
+ message: effectiveMessage,
1722
1871
  request: laneRequest,
1723
1872
  index: resolutionIndex,
1724
1873
  loopFeatureList,
@@ -1726,7 +1875,7 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
1726
1875
  })
1727
1876
  : null;
1728
1877
  const requirementGateAssessment = assessRequirementGateRelevance({
1729
- message: options.message,
1878
+ message: effectiveMessage,
1730
1879
  gate: requirementGate,
1731
1880
  laneRequest,
1732
1881
  resolvedTarget,
@@ -1737,9 +1886,19 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
1737
1886
  : null;
1738
1887
  const resumedDiscovery = await resumeOpenSpecDiscoveryWorkspace(projectRoot).catch(() => null);
1739
1888
  const discovery = shouldSurfaceDiscoveryInRunContext(resumedDiscovery) ? resumedDiscovery : null;
1740
- const recommendation = buildRunRecommendation({
1889
+ const promptDrivenRecommendation = (
1890
+ !requirementGate
1891
+ && !activeChange
1892
+ && !taskState?.nextTask
1893
+ && !discovery
1894
+ && !resolvedTarget?.matched
1895
+ && !laneRequest?.requested
1896
+ )
1897
+ ? inferPromptDrivenLightweightRecommendation(effectiveMessage)
1898
+ : null;
1899
+ const recommendation = promptDrivenRecommendation ?? buildRunRecommendation({
1741
1900
  projectRoot,
1742
- message: options.message,
1901
+ message: effectiveMessage,
1743
1902
  changes,
1744
1903
  activeChange,
1745
1904
  focusedChangeId,
@@ -1755,7 +1914,7 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
1755
1914
  });
1756
1915
  const nextTask = compactTask(taskState?.nextTask ?? null);
1757
1916
  const lane = buildRunLane({
1758
- message: options.message,
1917
+ message: effectiveMessage,
1759
1918
  recommendation,
1760
1919
  activeChange,
1761
1920
  latestPrd,
@@ -1776,8 +1935,8 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
1776
1935
  { requirementGate },
1777
1936
  );
1778
1937
  const knowledgeSkillMatches = await resolveKnowledgeSkillMatches(projectRoot, {
1779
- message: options.message,
1780
- prompt: options.message,
1938
+ message: effectiveMessage,
1939
+ prompt: effectiveMessage,
1781
1940
  recommendationTitle: effectiveRecommendation.title,
1782
1941
  recommendationReason: effectiveRecommendation.reason,
1783
1942
  activeChange,
@@ -1794,7 +1953,7 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
1794
1953
  stages: options.hookInject ? ['hit', 'referenced', 'injected'] : ['hit', 'referenced'],
1795
1954
  source: options.hookInject ? 'run-context-hook' : 'run-context',
1796
1955
  sessionId: lane.target?.sessionId ?? resolvedTarget?.sessionId ?? requirementGate?.sessionId ?? null,
1797
- promptPreview: options.message,
1956
+ promptPreview: effectiveMessage,
1798
1957
  }).catch(() => null)
1799
1958
  : null;
1800
1959
  const knowledgeStageBump = {
@@ -1806,7 +1965,7 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
1806
1965
  ...skill,
1807
1966
  adoption: skill.adoption
1808
1967
  ? {
1809
- ...skill.adoption,
1968
+ ...skill.adoption,
1810
1969
  hitCount: Number(skill.adoption.hitCount ?? 0) + knowledgeStageBump.hitCount,
1811
1970
  referencedCount: Number(skill.adoption.referencedCount ?? 0) + knowledgeStageBump.referencedCount,
1812
1971
  injectedCount: Number(skill.adoption.injectedCount ?? 0) + knowledgeStageBump.injectedCount,
@@ -1878,9 +2037,12 @@ async function buildRunContext(projectRoot, dependencies, options = {}) {
1878
2037
  recommendation: effectiveRecommendation,
1879
2038
  knowledgeSkills: {
1880
2039
  matched: renderedKnowledgeSkills,
2040
+ mandatoryCheck: knowledgeSkillMatches.mandatoryCheck ?? null,
1881
2041
  summary: {
1882
2042
  matched: renderedKnowledgeSkills.length,
1883
2043
  hookInjected: Boolean(options.hookInject && renderedKnowledgeSkills.length > 0),
2044
+ reviewRequired: Boolean(knowledgeSkillMatches.mandatoryCheck?.required),
2045
+ reviewMode: knowledgeSkillMatches.mandatoryCheck?.mode ?? null,
1884
2046
  adoption: knowledgeAdoption?.summary ?? null,
1885
2047
  },
1886
2048
  },