@openprd/cli 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.openprd/benchmarks/index.md +15 -1
- package/.openprd/config.yaml +10 -0
- package/.openprd/design/README.md +47 -0
- package/.openprd/design/README_EN.md +25 -0
- package/.openprd/design/active/asset-spec.md +15 -0
- package/.openprd/design/active/direction-plan.md +7 -0
- package/.openprd/design/active/facts-sheet.md +13 -0
- package/.openprd/design/active/image-preflight.md +9 -0
- package/.openprd/design/active/selected-direction.md +11 -0
- package/.openprd/design/anti-slop.md +16 -0
- package/.openprd/design/assets/dark-mesh.svg +14 -0
- package/.openprd/design/assets/paper-grid.svg +14 -0
- package/.openprd/design/assets/surface-presets.md +21 -0
- package/.openprd/design/assets/workbench-grid.svg +16 -0
- package/.openprd/design/checklists/ui-quality-gate.md +22 -0
- package/.openprd/design/components/component-catalog.md +42 -0
- package/.openprd/design/layouts/layout-catalog.json +45 -0
- package/.openprd/design/lenses/frontend-lenses.md +31 -0
- package/.openprd/design/recipes/content-experience.md +26 -0
- package/.openprd/design/recipes/operational-tool.md +25 -0
- package/.openprd/design/recipes/product-launch.md +26 -0
- package/.openprd/design/templates/README.md +36 -0
- package/.openprd/design/templates/README_EN.md +31 -0
- package/.openprd/design/templates/content-home.html +415 -0
- package/.openprd/design/templates/ops-dashboard.html +340 -0
- package/.openprd/design/templates/product-launch.html +360 -0
- package/.openprd/design/themes/theme-catalog.json +77 -0
- package/.openprd/engagements/active/prd.md +111 -100
- package/.openprd/schema/prd.schema.yaml +10 -0
- package/.openprd/templates/base/intake.md +4 -0
- package/.openprd/templates/base/prd.md +16 -6
- package/AGENTS.md +10 -7
- package/README.md +28 -10
- package/README_EN.md +23 -3
- package/package.json +41 -2
- package/scripts/openprd-codex-isolated-worker.mjs +799 -0
- package/skills/openprd-benchmark-router/SKILL.md +4 -0
- package/skills/openprd-frontend-design/SKILL.md +161 -0
- package/skills/openprd-frontend-design/references/design-asset-contract.md +34 -0
- package/skills/openprd-frontend-design/references/direction-engine.md +46 -0
- package/skills/openprd-harness/SKILL.md +38 -21
- package/skills/openprd-learning-review/SKILL.md +2 -1
- package/skills/openprd-learning-review/references/style-packs/xianxia-cultivation.prompt.md +1 -1
- package/skills/openprd-quality/SKILL.md +6 -4
- package/skills/openprd-requirement-intake/SKILL.md +27 -5
- package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -3
- package/skills/openprd-requirement-intake/references/routing-rubric.md +9 -5
- package/skills/openprd-requirement-intake/references/startup-validation-lens.md +23 -0
- package/skills/openprd-router/SKILL.md +13 -5
- package/skills/openprd-shared/SKILL.md +38 -25
- package/src/agent-integration.js +126 -75
- package/src/brainstorm-artifacts.js +1657 -0
- package/src/brainstorm-presentation.js +223 -0
- package/src/brainstorm.js +603 -0
- package/src/cli/args.js +51 -3
- package/src/cli/doctor-print.js +34 -8
- package/src/cli/print.js +6 -0
- package/src/cli/quality-print.js +70 -0
- package/src/cli/run-print.js +43 -1
- package/src/cli/shared-print.js +33 -2
- package/src/cli/workflow-print.js +21 -0
- package/src/codex-hook-runner-template.mjs +858 -65
- package/src/design-starter-support.js +462 -0
- package/src/design-starter.js +1207 -0
- package/src/knowledge.js +377 -21
- package/src/learning-review.js +3 -3
- package/src/loop.js +839 -101
- package/src/openprd.js +86 -1
- package/src/openspec/change-validate.js +1 -0
- package/src/prd-core.js +18 -0
- package/src/quality-learning.js +50 -6
- package/src/quality.js +2 -2
- package/src/run-harness.js +191 -29
- package/src/self-update.js +301 -4
- package/src/standards.js +46 -5
- package/src/visual-prepare.js +940 -0
- package/src/workspace-core.js +137 -0
- package/src/workspace-workflow.js +190 -7
- package/.openprd/benchmarks/evidence/milvus-io-ai-code-review-gets-better-when-models-debate-claude-vs-gemini-vs-code.md +0 -14
- package/.openprd/benchmarks/evidence/nolanlawson-com-using-ai-to-write-better-code-more-slowly.md +0 -14
- package/.openprd/discovery/config.json +0 -35
- package/.openprd/engagements/active/flows.md +0 -35
- package/.openprd/engagements/active/handoff.md +0 -16
- package/.openprd/engagements/active/review.html +0 -61
- package/.openprd/engagements/active/roles.md +0 -22
- package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +0 -23
- package/.openprd/engagements/work-units/wu-20260602113956-a99b5b88.json +0 -18
- package/.openprd/engagements/work-units/wu-20260602122244-78656aaf.json +0 -18
- package/.openprd/engagements/work-units/wu-20260602122442-e96489e2.json +0 -18
- package/.openprd/engagements/work-units/wu-20260602132835-695429e8.json +0 -18
- package/.openprd/exports/.gitkeep +0 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/candidate.json +0 -78
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/diagnostic-report.json +0 -129
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/root-cause-candidates.json +0 -41
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/timeline.json +0 -14
- package/.openprd/knowledge/drafts/openprd-experience-diagnostic-candidate-turn-1780116203372-5f266a79e968c758/SKILL.md +0 -49
- package/.openprd/knowledge/index.json +0 -47
- package/.openprd/reviews/v0001.html +0 -1322
- package/.openprd/reviews/v0002.html +0 -1150
- package/.openprd/reviews/v0003.html +0 -1150
- package/.openprd/reviews/v0004.html +0 -1150
- package/.openprd/reviews/v0005.html +0 -1150
- package/.openprd/sessions/.gitkeep +0 -0
- package/.openprd/state/changes.json +0 -27
- package/.openprd/state/current.json +0 -505
- package/.openprd/state/release-ledger.json +0 -344
- package/.openprd/state/version-index.json +0 -67
- package/.openprd/state/versions/.gitkeep +0 -0
- package/.openprd/state/versions/v0001.json +0 -121
- package/.openprd/state/versions/v0001.md +0 -161
- package/.openprd/state/versions/v0002.json +0 -264
- package/.openprd/state/versions/v0002.md +0 -183
- package/.openprd/state/versions/v0003.json +0 -269
- package/.openprd/state/versions/v0003.md +0 -188
- package/.openprd/state/versions/v0004.json +0 -274
- package/.openprd/state/versions/v0004.md +0 -193
- package/.openprd/state/versions/v0005.json +0 -299
- package/.openprd/state/versions/v0005.md +0 -189
- package/docs/assets/openprd-capability-overview-en.png +0 -0
- package/docs/assets/openprd-capability-overview-zh.png +0 -0
- package/docs/assets/openprd-learning-html.png +0 -0
- package/docs/assets/openprd-quality-html.png +0 -0
- package/docs/assets/openprd-requirement-routing-en.png +0 -0
- package/docs/assets/openprd-requirement-routing-en.svg +0 -102
- package/docs/assets/openprd-requirement-routing-zh-refined.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.png +0 -0
- package/docs/assets/openprd-requirement-routing-zh.svg +0 -102
- package/docs/assets/openprd-review-html.png +0 -0
- package/docs/assets/openprd-scenario-overview.png +0 -0
- package/docs/assets/openprd-scenario-overview.svg +0 -114
- package/docs/assets/openprd-self-evolving-mechanisms-en.png +0 -0
- package/docs/assets/openprd-self-evolving-mechanisms-zh.png +0 -0
- package/docs/assets/openprd-visual-compare-case-study-en.png +0 -0
- package/docs/assets/openprd-visual-compare-case-study-zh.png +0 -0
package/src/run-harness.js
CHANGED
|
@@ -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:
|
|
1225
|
-
worktreeRecommended:
|
|
1226
|
-
reason:
|
|
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
|
-
|
|
1371
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
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:
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
1780
|
-
prompt:
|
|
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:
|
|
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
|
-
|
|
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
|
},
|