@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.
- package/.openprd/README.md +43 -69
- package/.openprd/README_EN.md +84 -0
- package/.openprd/benchmarks/index.md +7 -0
- package/.openprd/benchmarks/sources.yaml +25 -3
- package/.openprd/discovery/config.json +16 -2
- package/.openprd/engagements/active/flows.md +19 -14
- package/.openprd/engagements/active/handoff.md +11 -4
- package/.openprd/engagements/active/prd.md +99 -71
- package/.openprd/engagements/active/review.html +4 -4
- package/.openprd/engagements/active/roles.md +9 -8
- package/.openprd/engagements/work-units/wu-20260524015648-6d33ded7.json +4 -4
- package/.openprd/engagements/work-units/wu-20260602113956-a99b5b88.json +18 -0
- package/.openprd/engagements/work-units/wu-20260602122244-78656aaf.json +18 -0
- package/.openprd/engagements/work-units/wu-20260602122442-e96489e2.json +18 -0
- package/.openprd/engagements/work-units/wu-20260602132835-695429e8.json +18 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/candidate.json +78 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/diagnostic-report.json +129 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/root-cause-candidates.json +41 -0
- package/.openprd/knowledge/candidates/candidate-turn-1780116203372-5f266a79e968c758/timeline.json +14 -0
- package/.openprd/knowledge/drafts/openprd-experience-diagnostic-candidate-turn-1780116203372-5f266a79e968c758/SKILL.md +49 -0
- package/.openprd/knowledge/index.json +44 -4
- package/.openprd/reviews/v0001.html +195 -129
- package/.openprd/reviews/v0002.html +1150 -0
- package/.openprd/reviews/v0003.html +1150 -0
- package/.openprd/reviews/v0004.html +1150 -0
- package/.openprd/reviews/v0005.html +1150 -0
- package/.openprd/standards/config.json +12 -9
- package/.openprd/state/changes.json +17 -2
- package/.openprd/state/current.json +399 -63
- package/.openprd/state/release-ledger.json +344 -0
- package/.openprd/state/version-index.json +52 -0
- package/.openprd/state/versions/v0002.json +264 -0
- package/.openprd/state/versions/v0002.md +183 -0
- package/.openprd/state/versions/v0003.json +269 -0
- package/.openprd/state/versions/v0003.md +188 -0
- package/.openprd/state/versions/v0004.json +274 -0
- package/.openprd/state/versions/v0004.md +193 -0
- package/.openprd/state/versions/v0005.json +299 -0
- package/.openprd/state/versions/v0005.md +189 -0
- package/.openprd/templates/agent/intake.md +5 -4
- package/.openprd/templates/b2b/intake.md +5 -4
- package/.openprd/templates/base/intake.md +10 -4
- package/.openprd/templates/company/README.md +9 -7
- package/.openprd/templates/company/README_EN.md +12 -0
- package/.openprd/templates/consumer/intake.md +5 -4
- package/.openprd/templates/industry/README.md +12 -10
- package/.openprd/templates/industry/README_EN.md +18 -0
- package/.openprd/templates/project/README.md +11 -9
- package/.openprd/templates/project/README_EN.md +16 -0
- package/.openprd/templates/session/README.md +11 -9
- package/.openprd/templates/session/README_EN.md +16 -0
- package/AGENTS.md +12 -8
- package/README.md +402 -441
- package/README_CN.md +4 -578
- package/README_EN.md +850 -0
- package/docs/assets/openprd-requirement-routing-en.png +0 -0
- package/docs/assets/openprd-requirement-routing-en.svg +102 -0
- 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 +102 -0
- package/package.json +6 -2
- package/scripts/dev-check-wrapup-copy.mjs +110 -0
- package/scripts/openprd-github-release-notes.mjs +99 -0
- package/scripts/quality-perf-check.mjs +203 -0
- package/skills/openprd-benchmark-router/SKILL.md +1 -0
- package/skills/openprd-benchmark-router/references/benchmark-sources.md +1 -0
- package/skills/openprd-benchmark-router/references/source-policy.md +2 -0
- package/skills/openprd-discovery-loop/SKILL.md +2 -2
- package/skills/openprd-harness/SKILL.md +46 -24
- package/skills/openprd-harness/references/workflow-gates.md +15 -0
- package/skills/openprd-quality/SKILL.md +10 -4
- package/skills/openprd-requirement-intake/SKILL.md +39 -23
- package/skills/openprd-requirement-intake/references/prd-template-lenses.md +6 -6
- package/skills/openprd-requirement-intake/references/routing-rubric.md +22 -8
- package/skills/openprd-router/SKILL.md +2 -2
- package/skills/openprd-shared/SKILL.md +51 -23
- package/skills/openprd-standards/SKILL.md +2 -1
- package/src/agent-integration.js +265 -65
- package/src/benchmark/constants.js +107 -0
- package/src/benchmark/operations.js +235 -0
- package/src/benchmark/registry.js +64 -0
- package/src/benchmark/render.js +115 -0
- package/src/benchmark/source.js +617 -0
- package/src/benchmark/storage.js +121 -0
- package/src/benchmark/verify.js +235 -0
- package/src/benchmark.js +50 -851
- package/src/change-summary.js +339 -0
- package/src/cli/args.js +67 -6
- package/src/cli/basic-print.js +365 -0
- package/src/cli/benchmark-print.js +91 -0
- package/src/cli/change-print.js +221 -0
- package/src/cli/doctor-print.js +268 -0
- package/src/cli/growth-print.js +176 -0
- package/src/cli/print.js +73 -1384
- package/src/cli/quality-print.js +284 -0
- package/src/cli/run-print.js +297 -0
- package/src/cli/shared-print.js +127 -0
- package/src/cli/workflow-print.js +195 -0
- package/src/codex-hook-runner-template.mjs +639 -117
- package/src/codex-runtime.js +324 -0
- package/src/dev-standards.js +178 -5
- package/src/diagram-core.js +5 -5
- package/src/discovery.js +2 -1
- package/src/execution-strategy.js +369 -0
- package/src/fleet.js +4 -0
- package/src/github-release.js +156 -0
- package/src/growth.js +311 -13
- package/src/html-artifact-utils.js +25 -0
- package/src/html-artifacts.js +157 -1596
- package/src/knowledge.js +1176 -75
- package/src/language-policy.js +2 -112
- package/src/learning-html-artifact.js +1031 -0
- package/src/learning-review.js +3 -2
- package/src/loop.js +280 -9
- package/src/openprd.js +341 -38
- package/src/openspec/change-validate.js +0 -9
- package/src/openspec/execute.js +79 -3
- package/src/openspec/generate.js +33 -20
- package/src/openspec/tasks.js +33 -2
- package/src/prd-core.js +10 -9
- package/src/product-type-copy.js +69 -0
- package/src/quality-html-artifact.js +108 -9
- package/src/quality-learning.js +30 -0
- package/src/quality-visual-review.js +237 -0
- package/src/quality.js +329 -43
- package/src/registry-hygiene.js +54 -0
- package/src/release-ledger.js +413 -0
- package/src/review-presentation.js +12 -6
- package/src/run-harness.js +722 -48
- package/src/self-update.js +1 -1
- package/src/session-binding.js +40 -3
- package/src/session-registry.js +159 -0
- package/src/standards.js +5 -3
- package/src/test-strategy.js +386 -0
- package/src/visual-compare.js +915 -34
- package/src/work-unit-migration.js +5 -1
- package/src/workspace-core.js +343 -19
- package/src/workspace-workflow.js +538 -134
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import crypto from 'node:crypto';
|
|
4
5
|
import { spawnSync } from 'node:child_process';
|
|
5
6
|
|
|
@@ -367,6 +368,21 @@ function sessionBindingPath(root, sessionId = null) {
|
|
|
367
368
|
return path.join(sessionBindingDir(root), `${String(sessionId).replace(/[^A-Za-z0-9._-]/g, '_')}.json`);
|
|
368
369
|
}
|
|
369
370
|
|
|
371
|
+
function openprdHome() {
|
|
372
|
+
return path.resolve(process.env.OPENPRD_HOME || path.join(os.homedir(), '.openprd'));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function sessionRegistryPath() {
|
|
376
|
+
return path.join(openprdHome(), 'registry', 'sessions.jsonl');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function sessionStatePath(root, sessionId = null) {
|
|
380
|
+
if (!sessionId) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
return path.join(harnessDir(root), 'session-states', `${String(sessionId).replace(/[^A-Za-z0-9._-]/g, '_')}.json`);
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
function currentStatePath(root) {
|
|
371
387
|
return path.join(root, '.openprd', 'state', 'current.json');
|
|
372
388
|
}
|
|
@@ -407,7 +423,7 @@ function reviewPolicyAllowsSilentRecord(policy) {
|
|
|
407
423
|
}
|
|
408
424
|
|
|
409
425
|
function resolveRequirementApprovalPolicy(prompt, intent = {}) {
|
|
410
|
-
const suppressExtraConfirmation = Boolean(intent?.
|
|
426
|
+
const suppressExtraConfirmation = Boolean(intent?.noConfirmationRequested);
|
|
411
427
|
const explicitExecution = Boolean(intent?.explicitExecution);
|
|
412
428
|
return normalizeRequirementApprovalPolicy({
|
|
413
429
|
mode: 'decision-points',
|
|
@@ -445,6 +461,47 @@ function readSessionBinding(root, sessionId = null) {
|
|
|
445
461
|
return readJsonSync(sessionBindingPath(root, sessionId), null);
|
|
446
462
|
}
|
|
447
463
|
|
|
464
|
+
function appendJsonLine(filePath, value) {
|
|
465
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
466
|
+
fs.appendFileSync(filePath, JSON.stringify(value) + '\n');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function writeSessionRegistryEntry(root, sessionId, patch = {}) {
|
|
470
|
+
if (!sessionId) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
const workspaceRoot = path.resolve(root);
|
|
474
|
+
const recordedAt = patch.recordedAt || now();
|
|
475
|
+
const entry = {
|
|
476
|
+
version: 1,
|
|
477
|
+
sessionId,
|
|
478
|
+
workspaceRoot,
|
|
479
|
+
realpath: workspaceRoot,
|
|
480
|
+
laneKind: patch.laneKind || 'requirement',
|
|
481
|
+
tool: patch.tool || 'codex',
|
|
482
|
+
threadId: patch.threadId || null,
|
|
483
|
+
changeId: patch.changeId || null,
|
|
484
|
+
taskHandle: patch.taskHandle || null,
|
|
485
|
+
workUnitId: patch.workUnitId || null,
|
|
486
|
+
versionId: patch.versionId || null,
|
|
487
|
+
digest: patch.digest || null,
|
|
488
|
+
title: patch.title || null,
|
|
489
|
+
targetRoot: patch.targetRoot ? path.resolve(patch.targetRoot) : null,
|
|
490
|
+
promptPreview: patch.promptPreview || null,
|
|
491
|
+
reviewStatus: patch.reviewStatus || null,
|
|
492
|
+
gateStatus: patch.gateStatus || null,
|
|
493
|
+
gateActive: patch.gateActive === true,
|
|
494
|
+
statePath: patch.statePath || sessionStatePath(root, sessionId),
|
|
495
|
+
bindingPath: patch.bindingPath || sessionBindingPath(root, sessionId),
|
|
496
|
+
firstRegisteredAt: patch.firstRegisteredAt || recordedAt,
|
|
497
|
+
lastRegisteredAt: recordedAt,
|
|
498
|
+
lastUpdatedAt: patch.updatedAt || recordedAt,
|
|
499
|
+
recordedAt,
|
|
500
|
+
};
|
|
501
|
+
appendJsonLine(sessionRegistryPath(), entry);
|
|
502
|
+
return entry;
|
|
503
|
+
}
|
|
504
|
+
|
|
448
505
|
function writeSessionBinding(root, sessionId, patch = {}) {
|
|
449
506
|
if (!sessionId) {
|
|
450
507
|
return null;
|
|
@@ -460,6 +517,25 @@ function writeSessionBinding(root, sessionId, patch = {}) {
|
|
|
460
517
|
updatedAt: patch.updatedAt ?? now(),
|
|
461
518
|
};
|
|
462
519
|
writeJsonSync(filePath, next);
|
|
520
|
+
writeSessionRegistryEntry(root, sessionId, {
|
|
521
|
+
laneKind: patch.laneKind ?? previous?.laneKind ?? 'requirement',
|
|
522
|
+
tool: patch.tool ?? previous?.tool ?? 'codex',
|
|
523
|
+
threadId: patch.threadId ?? previous?.threadId ?? null,
|
|
524
|
+
changeId: patch.changeId ?? next.changeId ?? null,
|
|
525
|
+
taskHandle: patch.taskHandle ?? next.taskHandle ?? null,
|
|
526
|
+
workUnitId: patch.workUnitId ?? next.workUnitId ?? null,
|
|
527
|
+
versionId: patch.versionId ?? next.versionId ?? null,
|
|
528
|
+
digest: patch.digest ?? next.digest ?? null,
|
|
529
|
+
title: patch.title ?? next.title ?? null,
|
|
530
|
+
targetRoot: patch.targetRoot ?? next.targetRoot ?? null,
|
|
531
|
+
promptPreview: patch.promptPreview ?? next.promptPreview ?? null,
|
|
532
|
+
reviewStatus: patch.reviewStatus ?? next.reviewStatus ?? null,
|
|
533
|
+
gateStatus: patch.gateStatus ?? next.gateStatus ?? null,
|
|
534
|
+
gateActive: patch.gateActive ?? next.gateActive ?? false,
|
|
535
|
+
bindingPath: filePath,
|
|
536
|
+
statePath: sessionStatePath(root, sessionId),
|
|
537
|
+
updatedAt: next.updatedAt,
|
|
538
|
+
});
|
|
463
539
|
return next;
|
|
464
540
|
}
|
|
465
541
|
|
|
@@ -676,7 +752,7 @@ function changeRequirementSummary(root, changeId) {
|
|
|
676
752
|
}
|
|
677
753
|
|
|
678
754
|
function runOpenPrdContext(cwd, prompt = null) {
|
|
679
|
-
const args = ['run', '.', '--context', '--json'];
|
|
755
|
+
const args = ['run', '.', '--context', '--json', '--hook-inject'];
|
|
680
756
|
if (String(prompt || '').trim()) {
|
|
681
757
|
args.push('--message', String(prompt).trim());
|
|
682
758
|
}
|
|
@@ -705,43 +781,103 @@ function runOpenPrdContext(cwd, prompt = null) {
|
|
|
705
781
|
};
|
|
706
782
|
}
|
|
707
783
|
|
|
784
|
+
function knowledgeSkillContextLines(knowledgeSkills) {
|
|
785
|
+
const matched = Array.isArray(knowledgeSkills?.matched) ? knowledgeSkills.matched : [];
|
|
786
|
+
if (matched.length === 0) {
|
|
787
|
+
return [];
|
|
788
|
+
}
|
|
789
|
+
const lines = [
|
|
790
|
+
`项目级 Skill: 自动命中 ${matched.length} 个,并已加入当前上下文`,
|
|
791
|
+
];
|
|
792
|
+
for (const skill of matched.slice(0, 3)) {
|
|
793
|
+
lines.push(`- ${skill.skillName}: ${skill.matchSummary || '命中当前上下文'}`);
|
|
794
|
+
if (skill.description) {
|
|
795
|
+
lines.push(` 说明: ${skill.description}`);
|
|
796
|
+
}
|
|
797
|
+
if (Array.isArray(skill.touchedFiles) && skill.touchedFiles.length > 0) {
|
|
798
|
+
lines.push(` 相关文件: ${skill.touchedFiles.slice(0, 4).join(';')}`);
|
|
799
|
+
}
|
|
800
|
+
if (skill.adoption) {
|
|
801
|
+
lines.push(` 复用指标: 命中 ${skill.adoption.hitCount || 0} / 引用 ${skill.adoption.referencedCount || 0} / 注入 ${skill.adoption.injectedCount || 0}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return lines;
|
|
805
|
+
}
|
|
806
|
+
|
|
708
807
|
function renderRunContextText(result) {
|
|
709
808
|
const lines = [
|
|
710
|
-
'
|
|
711
|
-
'
|
|
712
|
-
'
|
|
809
|
+
'当前进展参考',
|
|
810
|
+
'当前项目: ' + result.projectRoot,
|
|
811
|
+
'基础检查: ' + (result.validation?.valid ? '通过' : '失败'),
|
|
713
812
|
];
|
|
813
|
+
if (result.activeRequirementGate) {
|
|
814
|
+
const gateStatus = result.activeRequirementGate.status ?? 'active';
|
|
815
|
+
const gateSuffix = result.activeRequirementGate.relevance === 'background' ? '(仅背景提醒)' : '';
|
|
816
|
+
lines.push('当前处理阶段: ' + gateStatus + gateSuffix);
|
|
817
|
+
}
|
|
714
818
|
if (result.activeChange) {
|
|
715
|
-
|
|
819
|
+
const label = result.recommendation?.type === 'requirement-intake' ? '历史聚焦事项' : '当前聚焦事项';
|
|
820
|
+
lines.push(label + ': ' + result.activeChange);
|
|
716
821
|
}
|
|
717
822
|
if (result.lane?.summary) {
|
|
718
|
-
lines.push('
|
|
823
|
+
lines.push('当前处理路径: ' + result.lane.summary);
|
|
719
824
|
}
|
|
720
825
|
if (result.taskSummary) {
|
|
721
|
-
lines.push('
|
|
826
|
+
lines.push('后续任务进度: ' + result.taskSummary.completed + '/' + result.taskSummary.total + ' 完成,' + result.taskSummary.pending + ' 待处理,' + result.taskSummary.blocked + ' 阻塞');
|
|
827
|
+
if (result.taskSummary.implementation) {
|
|
828
|
+
lines.push('待落地任务: ' + result.taskSummary.implementation.completed + '/' + result.taskSummary.implementation.total + ' 完成,' + result.taskSummary.implementation.pending + ' 待处理');
|
|
829
|
+
}
|
|
722
830
|
}
|
|
723
831
|
if (result.discovery) {
|
|
724
|
-
lines.push('
|
|
832
|
+
lines.push('调研进度: ' + result.discovery.runId + ' 已覆盖 ' + result.discovery.summary.covered + '/' + result.discovery.summary.total + ',待处理 ' + result.discovery.summary.pending);
|
|
725
833
|
}
|
|
834
|
+
lines.push(...knowledgeSkillContextLines(result.knowledgeSkills));
|
|
835
|
+
lines.push('对外表达: 面向用户时,请优先说“本次调整”“后续任务”“继续落地”“完成后检查”这类人话,不要直接复述内部编号、命令、路径、版本号或流程术语。');
|
|
726
836
|
const recommendation = result.recommendation || {};
|
|
727
|
-
lines.push('
|
|
728
|
-
lines.push('
|
|
729
|
-
lines.push('原因: ' + recommendation.reason);
|
|
730
|
-
lines.push('建议只读命令: ' + recommendation.command);
|
|
837
|
+
lines.push('建议下一步: ' + recommendation.title);
|
|
838
|
+
lines.push('这样安排的原因: ' + recommendation.reason);
|
|
731
839
|
if (recommendation.preparationCommand || recommendation.executionCommand || recommendation.commitCommand) {
|
|
732
|
-
lines.push('
|
|
840
|
+
lines.push('开始动手前提: 只有在用户明确要求继续落地、实现、修复、深挖或提交时,才继续往下做;如果还缺这一步,就先用人话说明范围和影响。');
|
|
841
|
+
}
|
|
842
|
+
const checklist = recommendation.executionConfirmationChecklist;
|
|
843
|
+
if (checklist?.required) {
|
|
844
|
+
lines.push((checklist.title || '开始动手前先确认这些') + ':');
|
|
845
|
+
if (checklist.objective) {
|
|
846
|
+
lines.push('- 这次要做什么: ' + checklist.objective);
|
|
847
|
+
}
|
|
848
|
+
if (checklist.scope?.length > 0) {
|
|
849
|
+
lines.push('- 这次范围: ' + checklist.scope.join(';'));
|
|
850
|
+
}
|
|
851
|
+
if (checklist.implementationItems?.length > 0) {
|
|
852
|
+
lines.push('- 我会这样推进: ' + checklist.implementationItems.join(';'));
|
|
853
|
+
}
|
|
854
|
+
if (checklist.outOfScope?.length > 0) {
|
|
855
|
+
lines.push('- 这次先不做: ' + checklist.outOfScope.join(';'));
|
|
856
|
+
}
|
|
857
|
+
if (checklist.verification?.length > 0) {
|
|
858
|
+
lines.push('- 完成后会检查: ' + checklist.verification.join(';'));
|
|
859
|
+
}
|
|
860
|
+
if (checklist.risks?.length > 0) {
|
|
861
|
+
lines.push('- 需要提前知道: ' + checklist.risks.join(';'));
|
|
862
|
+
}
|
|
863
|
+
if (checklist.confirmationPrompt) {
|
|
864
|
+
lines.push('- 如果要我现在继续: ' + checklist.confirmationPrompt);
|
|
865
|
+
}
|
|
733
866
|
}
|
|
734
867
|
if (recommendation.preparationCommand) {
|
|
735
|
-
lines.push('
|
|
868
|
+
lines.push('内部准备参考: ' + recommendation.preparationCommand);
|
|
736
869
|
}
|
|
737
870
|
if (recommendation.executionCommand) {
|
|
738
|
-
lines.push('
|
|
871
|
+
lines.push('内部执行参考: ' + recommendation.executionCommand);
|
|
739
872
|
}
|
|
740
873
|
if (recommendation.commitCommand) {
|
|
741
|
-
lines.push('
|
|
874
|
+
lines.push('内部提交参考: ' + recommendation.commitCommand);
|
|
875
|
+
}
|
|
876
|
+
if (recommendation.loop?.worktreeRecommended) {
|
|
877
|
+
lines.push('环境建议: 最好放到单独环境里继续,避免和别的事项串线。');
|
|
742
878
|
}
|
|
743
|
-
lines.push('
|
|
744
|
-
lines.push('
|
|
879
|
+
lines.push('内部检查参考: ' + recommendation.verifyCommand);
|
|
880
|
+
lines.push('内部状态参考: ' + (result.files?.runState || '.openprd/harness/run-state.json'));
|
|
745
881
|
return lines.filter(Boolean).join('\n');
|
|
746
882
|
}
|
|
747
883
|
|
|
@@ -751,32 +887,31 @@ function analyzePromptIntent(prompt) {
|
|
|
751
887
|
const continuationSessionId = text.match(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i)?.[0] ?? null;
|
|
752
888
|
const continuationTaskHandle = text.match(/\b[a-z0-9._-]+:T\d{3}\.\d{2}:[a-z0-9._-]+\b/i)?.[0] ?? null;
|
|
753
889
|
const continuationWorkUnitId = text.match(/\bwu-[a-z0-9._-]+\b/i)?.[0] ?? null;
|
|
754
|
-
const
|
|
755
|
-
|
|
890
|
+
const promptReviewCommand = parseReviewMarkCommand(text);
|
|
891
|
+
const continuationVerbMatched = /(?:(?:继续|续做|接着做|继续执行|继续推进)(?:这个|这条|当前)?\s*(?:对话|任务|会话|记录|历史|Codex\s*任务)|(?:对话|任务|会话|记录|历史|Codex\s*任务).{0,6}(?:继续|续做|接着做|继续执行|继续推进)|^(?:继续|续做|接着做|继续执行|继续推进)\s*(?::|:))/i.test(text);
|
|
892
|
+
const continuationRequest = continuationVerbMatched
|
|
893
|
+
|| Boolean(
|
|
894
|
+
continuationTaskHandle
|
|
895
|
+
|| (continuationWorkUnitId && !promptReviewCommand)
|
|
896
|
+
);
|
|
756
897
|
const githubRepoPattern = /(?:https?:\/\/)?github\.com\/[^\s/]+\/[^\s/#?]+|(?:^|[\s(])[\w.-]+\/[\w.-]+(?=$|[\s)#?])/i;
|
|
757
898
|
const internalOpenPrdExecution = /^#\s*OpenPrd\s+长程单任务执行会话/m.test(text)
|
|
758
899
|
|| /模式:\s*loop-run\b/i.test(text)
|
|
759
900
|
|| /模式:\s*loop-finish\b/i.test(text);
|
|
760
901
|
// These signals only decide when to inject/open the requirement-intake lane.
|
|
761
|
-
// The
|
|
762
|
-
//
|
|
763
|
-
const
|
|
902
|
+
// The matcher should stay conservative: only likely L2 requests open the
|
|
903
|
+
// heavy gate; L0/L1 stay lightweight unless the user expands the scope.
|
|
904
|
+
const requirementRoutingSignalsStrong = [
|
|
764
905
|
/新增/,
|
|
765
906
|
/增加/,
|
|
766
907
|
/新建/,
|
|
767
908
|
/我希望/,
|
|
768
|
-
/用户反馈/,
|
|
769
909
|
/需求/,
|
|
770
|
-
/功能/,
|
|
771
910
|
/模块/,
|
|
772
|
-
/页面/,
|
|
773
|
-
/界面/,
|
|
774
|
-
/视觉/,
|
|
775
911
|
/入口/,
|
|
776
912
|
/流程/,
|
|
777
913
|
/编排/,
|
|
778
914
|
/一站式/,
|
|
779
|
-
/体验/,
|
|
780
915
|
/信息架构/,
|
|
781
916
|
/团队搭建/,
|
|
782
917
|
/agent\s*市场/i,
|
|
@@ -786,6 +921,34 @@ function analyzePromptIntent(prompt) {
|
|
|
786
921
|
/workflow/i,
|
|
787
922
|
/wizard/i,
|
|
788
923
|
];
|
|
924
|
+
const requirementRoutingSignalsWeak = [
|
|
925
|
+
/用户反馈/,
|
|
926
|
+
/功能/,
|
|
927
|
+
/页面/,
|
|
928
|
+
/界面/,
|
|
929
|
+
/视觉/,
|
|
930
|
+
/体验/,
|
|
931
|
+
];
|
|
932
|
+
const requirementChangeIntentSignals = [
|
|
933
|
+
/新增/,
|
|
934
|
+
/增加/,
|
|
935
|
+
/新建/,
|
|
936
|
+
/改(动|版|造|进|一下)?/,
|
|
937
|
+
/优化/,
|
|
938
|
+
/调整/,
|
|
939
|
+
/重做/,
|
|
940
|
+
/重构/,
|
|
941
|
+
/放到/,
|
|
942
|
+
/移到/,
|
|
943
|
+
/移动到/,
|
|
944
|
+
/切到/,
|
|
945
|
+
/切换到/,
|
|
946
|
+
/替换(成|为)?/,
|
|
947
|
+
/串联/,
|
|
948
|
+
/接入/,
|
|
949
|
+
/实现/,
|
|
950
|
+
/落地/,
|
|
951
|
+
];
|
|
789
952
|
const tinyEditPatterns = [
|
|
790
953
|
/加(一个|个)?空格/,
|
|
791
954
|
/增加(一个|个)?空格/,
|
|
@@ -802,15 +965,77 @@ function analyzePromptIntent(prompt) {
|
|
|
802
965
|
/按钮|文案|颜色|圆角|位置|间距|字号|图标|标题|空格|标点|label|copy/i,
|
|
803
966
|
/从.+(改到|移到|移动到|换到|变成|改成|改为).+/,
|
|
804
967
|
];
|
|
805
|
-
const
|
|
806
|
-
|
|
968
|
+
const l2StructuralScopePatterns = [
|
|
969
|
+
/模块/,
|
|
970
|
+
/入口/,
|
|
971
|
+
/流程/,
|
|
972
|
+
/导入流/,
|
|
973
|
+
/编排/,
|
|
974
|
+
/workflow/i,
|
|
975
|
+
/wizard/i,
|
|
976
|
+
];
|
|
977
|
+
const l2StrategicScopePatterns = [
|
|
978
|
+
/一站式/,
|
|
979
|
+
/团队搭建/,
|
|
980
|
+
/agent\s*市场/i,
|
|
981
|
+
/skill\s*library/i,
|
|
982
|
+
/cli\s*库/i,
|
|
983
|
+
/权限/,
|
|
984
|
+
/审批/,
|
|
985
|
+
/计费/,
|
|
986
|
+
/账号/,
|
|
987
|
+
/第三方/,
|
|
988
|
+
/云服务/,
|
|
989
|
+
/迁移/,
|
|
990
|
+
/跨系统/,
|
|
991
|
+
/(AI|模型).{0,8}(接入|集成|编排)/i,
|
|
992
|
+
];
|
|
993
|
+
const structuralExpansionIntensityPatterns = [
|
|
994
|
+
/串联/,
|
|
995
|
+
/一站式/,
|
|
996
|
+
/整体/,
|
|
997
|
+
/全链路/,
|
|
998
|
+
/端到端/,
|
|
999
|
+
/体系/,
|
|
1000
|
+
/平台/,
|
|
1001
|
+
/多个/,
|
|
1002
|
+
];
|
|
1003
|
+
const featurePlanningPatterns = [
|
|
1004
|
+
/需求/,
|
|
1005
|
+
/方案/,
|
|
1006
|
+
/规划/,
|
|
1007
|
+
/产品/,
|
|
1008
|
+
];
|
|
1009
|
+
const capabilityCreationPatterns = [
|
|
1010
|
+
/新增/,
|
|
1011
|
+
/增加/,
|
|
1012
|
+
/新建/,
|
|
1013
|
+
/做(一个|个)?/,
|
|
1014
|
+
/支持/,
|
|
1015
|
+
/提供/,
|
|
1016
|
+
/搭建/,
|
|
1017
|
+
/引入/,
|
|
1018
|
+
/接入/,
|
|
1019
|
+
/集成/,
|
|
1020
|
+
];
|
|
1021
|
+
const crossSystemRiskPatterns = [
|
|
1022
|
+
/支付|登录|注册|账号|权限|订单|回调|风控|退款|计费|同步|迁移|数据库|schema|协议|网关|跨系统|第三方|云服务|OSS|CDN|MCP|SDK|CLI/i,
|
|
1023
|
+
];
|
|
1024
|
+
const l1OptimizationPatterns = [
|
|
1025
|
+
/(优化|调整|改版|重做|重构|增强|补齐|梳理|统一|整理)/,
|
|
1026
|
+
];
|
|
1027
|
+
const l0AdjustmentPatterns = [
|
|
1028
|
+
/(修复|修一下|改一下|调一下|换成|改成|去掉|补一个|补一下)/,
|
|
807
1029
|
];
|
|
808
1030
|
const explicitExecutionPatterns = [
|
|
809
1031
|
/直接(帮我|给我)?(改|做|实现|落地|修|修复|处理|解决)/,
|
|
810
1032
|
/如果.{0,24}(定位|确认|找到).{0,12}(原因|根因).{0,24}直接.{0,12}(帮我|给我)?(修|修复|改|处理|解决)/,
|
|
811
1033
|
/开始(改|做|实现|开发|落地)/,
|
|
1034
|
+
/继续(改|做|实现|开发|落地|修|修复|处理|解决)/,
|
|
812
1035
|
/请(直接)?(实现|落地|修改|修复|处理|解决)/,
|
|
813
1036
|
/可以(执行|落地|实现|开发)/,
|
|
1037
|
+
/去(改|做|实现|开发|落地|修|修复|处理|解决)吧/,
|
|
1038
|
+
/那你去(改|做|实现|开发|落地|修|修复|处理|解决)吧/,
|
|
814
1039
|
];
|
|
815
1040
|
const implementationConfirmationPatterns = [
|
|
816
1041
|
/确认.*(执行|落地|实现|继续|开发|修复|修改|处理|解决|改)/,
|
|
@@ -832,7 +1057,12 @@ function analyzePromptIntent(prompt) {
|
|
|
832
1057
|
/别再确认/,
|
|
833
1058
|
/不需要再跟我确认/,
|
|
834
1059
|
];
|
|
835
|
-
const
|
|
1060
|
+
const reviewContinuationPatterns = [
|
|
1061
|
+
/认可方案并继续/,
|
|
1062
|
+
/认可并继续/,
|
|
1063
|
+
/继续当前\s*openprd\s*下一步/i,
|
|
1064
|
+
/按当前\s*openprd\s*下一步继续/i,
|
|
1065
|
+
];
|
|
836
1066
|
const reviewConfirmPatterns = [
|
|
837
1067
|
/认可方案/,
|
|
838
1068
|
/确认(?:当前|这个|这版|该)?(?:PRD|评审稿|评审页|review|需求稿|版本)/i,
|
|
@@ -844,22 +1074,38 @@ function analyzePromptIntent(prompt) {
|
|
|
844
1074
|
];
|
|
845
1075
|
const readOnlyPatterns = [
|
|
846
1076
|
/看看/,
|
|
1077
|
+
/看下(一下)?/,
|
|
1078
|
+
/你看/,
|
|
847
1079
|
/规划/,
|
|
848
1080
|
/分析/,
|
|
1081
|
+
/先分析(一下)?/,
|
|
849
1082
|
/梳理/,
|
|
850
1083
|
/评估/,
|
|
851
1084
|
/怎么改/,
|
|
852
1085
|
/预计动哪些文件/,
|
|
1086
|
+
/看(看)?(一下)?(原因|问题|风险|情况)/,
|
|
1087
|
+
/什么原因/,
|
|
1088
|
+
/怎么看/,
|
|
1089
|
+
/你觉得/,
|
|
1090
|
+
/可行吗/,
|
|
1091
|
+
/有没有可能/,
|
|
1092
|
+
/会有什么问题/,
|
|
1093
|
+
/值不值得/,
|
|
853
1094
|
/review/i,
|
|
854
1095
|
/explain/i,
|
|
855
1096
|
];
|
|
856
1097
|
const bugfixOrDiagnostic = /诊断包|报错|错误|异常|崩溃|bug|问题|排查|定位|根因|复现|日志|故障/i.test(text)
|
|
857
1098
|
|| /失败.{0,20}(原因|根因|排查|定位|修|修复|处理|解决)|(?:原因|根因|排查|定位).{0,20}失败/.test(text);
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
const
|
|
862
|
-
const
|
|
1099
|
+
const tinyEdit = tinyEditPatterns.some((pattern) => pattern.test(text));
|
|
1100
|
+
const crossSystemRiskMatched = crossSystemRiskPatterns.some((pattern) => pattern.test(text));
|
|
1101
|
+
const localUiScopeMatched = /(按钮|文案|颜色|圆角|位置|间距|字号|图标|标题|空格|标点|label|copy|toast|placeholder|样式|页面|界面|布局|信息架构|导航|列表|详情页|设置页)/i.test(text);
|
|
1102
|
+
const reviewContinuationRequested = reviewContinuationPatterns.some((pattern) => pattern.test(text));
|
|
1103
|
+
const explicitExecution = internalOpenPrdExecution
|
|
1104
|
+
|| continuationVerbMatched
|
|
1105
|
+
|| reviewContinuationRequested
|
|
1106
|
+
|| explicitExecutionPatterns.some((pattern) => pattern.test(text));
|
|
1107
|
+
const implementationConfirmation = reviewContinuationRequested
|
|
1108
|
+
|| implementationConfirmationPatterns.some((pattern) => pattern.test(text));
|
|
863
1109
|
const noReviewRequested = noReviewRequestedPatterns.some((pattern) => pattern.test(text));
|
|
864
1110
|
const noConfirmationRequested = noConfirmationRequestedPatterns.some((pattern) => pattern.test(text));
|
|
865
1111
|
const reviewDecision = promptReviewCommand?.mark
|
|
@@ -873,8 +1119,38 @@ function analyzePromptIntent(prompt) {
|
|
|
873
1119
|
const visualMockupRequest = imageGenerationTerms.test(text)
|
|
874
1120
|
&& imageGenerationAction.test(text)
|
|
875
1121
|
&& !codeVisualArtifactRequested;
|
|
1122
|
+
const largeUiChangeRequest = /(界面|页面|视觉|样式|UI|前端体验|布局|信息架构|主视觉|效果图|视觉稿|mockup|设计方向|设计预览)/i.test(text)
|
|
1123
|
+
&& /(大|较大|比较大|明显|重做|重构|改版|优化|重新设计|设计方向|三种|3种|方案|效果图|先看样子|确认方向|体验优化|产品内)/i.test(text);
|
|
876
1124
|
const visualReview = /效果图|实现截图|视觉对比|视觉评审|对标效果图|复刻/i.test(text);
|
|
877
1125
|
const directBugfixExecution = explicitExecution && bugfixOrDiagnostic;
|
|
1126
|
+
const newFeatureVerbMatched = /(新增|增加|新建)/.test(text);
|
|
1127
|
+
const capabilityCreationMatched = capabilityCreationPatterns.some((pattern) => pattern.test(text));
|
|
1128
|
+
const featurePlanningMatched = featurePlanningPatterns.some((pattern) => pattern.test(text));
|
|
1129
|
+
const structuralL2ScopeMatchCount = l2StructuralScopePatterns.filter((pattern) => pattern.test(text)).length;
|
|
1130
|
+
const strategicL2ScopeMatched = l2StrategicScopePatterns.some((pattern) => pattern.test(text));
|
|
1131
|
+
const structuralExpansionMatched = structuralL2ScopeMatchCount >= 2
|
|
1132
|
+
|| (structuralL2ScopeMatchCount >= 1
|
|
1133
|
+
&& (structuralExpansionIntensityPatterns.some((pattern) => pattern.test(text)) || (capabilityCreationMatched && !localUiScopeMatched)));
|
|
1134
|
+
const l2ScopeMatched = strategicL2ScopeMatched || structuralExpansionMatched;
|
|
1135
|
+
const simpleConcrete = text.length <= 80
|
|
1136
|
+
&& simpleConcretePatterns.some((pattern) => pattern.test(text))
|
|
1137
|
+
&& !l2ScopeMatched;
|
|
1138
|
+
const requirementSignalMatched = requirementRoutingSignalsStrong.some((pattern) => pattern.test(text))
|
|
1139
|
+
|| (requirementRoutingSignalsWeak.some((pattern) => pattern.test(text))
|
|
1140
|
+
&& requirementChangeIntentSignals.some((pattern) => pattern.test(text)));
|
|
1141
|
+
const l2FeatureExpansionMatched = !localUiScopeMatched
|
|
1142
|
+
&& (
|
|
1143
|
+
(capabilityCreationMatched && (strategicL2ScopeMatched || structuralExpansionMatched))
|
|
1144
|
+
|| (newFeatureVerbMatched && text.length > 18)
|
|
1145
|
+
);
|
|
1146
|
+
const l2PlanningRequestMatched = !readOnly
|
|
1147
|
+
&& featurePlanningMatched
|
|
1148
|
+
&& requirementChangeIntentSignals.some((pattern) => pattern.test(text))
|
|
1149
|
+
&& (strategicL2ScopeMatched || structuralExpansionMatched);
|
|
1150
|
+
const l1OptimizationMatched = l1OptimizationPatterns.some((pattern) => pattern.test(text))
|
|
1151
|
+
&& /(页面|界面|视觉|样式|布局|信息架构|交互|体验|流程|入口|导航|表单|设置页|列表|详情页)/i.test(text);
|
|
1152
|
+
const l0AdjustmentMatched = l0AdjustmentPatterns.some((pattern) => pattern.test(text))
|
|
1153
|
+
&& /(按钮|文案|颜色|圆角|位置|间距|字号|图标|标题|空格|标点|label|copy|toast|placeholder|样式|一处)/i.test(text);
|
|
878
1154
|
const publicRepoResearchRequest = githubRepoPattern.test(text)
|
|
879
1155
|
&& /(github|仓库|repo|项目|参考|对标|复刻|review|学习|架构|模块|流程|构建|测试|扩展点)/i.test(text);
|
|
880
1156
|
const externalTechResearchRequest = /(第三方|library|framework|sdk|api|mcp|cli|依赖|包|版本|迁移|弃用|官方文档|参数|返回值|生命周期)/i.test(text)
|
|
@@ -883,19 +1159,41 @@ function analyzePromptIntent(prompt) {
|
|
|
883
1159
|
|| (/(^|[^a-z])(skill|skills)([^a-z]|$)/i.test(text) && /(创建|修改|优化|重构|合并|拆分|更新|工作流|workflow|流程|路由|router|提示词|规则)/i.test(text))
|
|
884
1160
|
|| (/AGENTS\.md/i.test(text) && /(创建|修改|优化|精简|收薄|重构|更新)/i.test(text));
|
|
885
1161
|
const secretsRequest = /(api\s*key|token|secret|credential|password|凭证|密钥|密码|账号信息|第三方服务凭证|个人信息|登录信息)/i.test(text);
|
|
886
|
-
const
|
|
1162
|
+
const weappMention = /(微信小程序|miniprogram|weapp|微信开发者工具|weapp-dev-mcp)/i.test(text);
|
|
1163
|
+
const weappValidationAction = /(测试|验证|实测|复现|截图|日志|抓日志|抓包|网络请求|network|运行态|开发者工具自动化|从\s*0\s*到\s*1|冷启动|重开|重新打开|全流程)/i.test(text);
|
|
1164
|
+
const weappValidationRequest = /weapp-dev-mcp/i.test(text)
|
|
1165
|
+
|| (weappMention && weappValidationAction);
|
|
887
1166
|
const browserSafetyRequest = /(computer use|browser use|浏览器|browser|网页|页面|窗口|标签页|tab|profile)/i.test(text)
|
|
888
1167
|
&& /(点击|输入|提交|登录|注销|退出|支付|关闭|send|submit|type|click|switch account|切换账号)/i.test(text);
|
|
889
1168
|
const productCopyRequest = /(文案|copy|错误文案|空状态|成功提示|按钮文案|提示语|toast|placeholder|设置项文案|国际化|i18n|locales|translations|localizable)/i.test(text);
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1169
|
+
const l2RequirementCandidate = (bugfixOrDiagnostic && crossSystemRiskMatched)
|
|
1170
|
+
|| l2FeatureExpansionMatched
|
|
1171
|
+
|| l2PlanningRequestMatched;
|
|
1172
|
+
const requirementTier = !internalOpenPrdExecution
|
|
1173
|
+
? (l2RequirementCandidate
|
|
1174
|
+
&& !tinyEdit
|
|
1175
|
+
&& !simpleConcrete
|
|
1176
|
+
&& !visualMockupRequest
|
|
1177
|
+
&& !(readOnly && !explicitExecution)
|
|
1178
|
+
? 'l2'
|
|
1179
|
+
: (!visualMockupRequest
|
|
1180
|
+
&& (!readOnly || explicitExecution)
|
|
1181
|
+
&& (tinyEdit
|
|
1182
|
+
|| simpleConcrete
|
|
1183
|
+
|| l0AdjustmentMatched
|
|
1184
|
+
|| (directBugfixExecution && !crossSystemRiskMatched)
|
|
1185
|
+
|| (bugfixOrDiagnostic && !crossSystemRiskMatched))
|
|
1186
|
+
? 'l0'
|
|
1187
|
+
: (!visualMockupRequest
|
|
1188
|
+
&& !readOnly
|
|
1189
|
+
&& (largeUiChangeRequest || l1OptimizationMatched || requirementSignalMatched)
|
|
1190
|
+
? 'l1'
|
|
1191
|
+
: null)))
|
|
1192
|
+
: null;
|
|
1193
|
+
const requiresIntake = requirementTier === 'l2';
|
|
897
1194
|
return {
|
|
898
1195
|
promptText: text,
|
|
1196
|
+
requirementTier,
|
|
899
1197
|
requiresIntake,
|
|
900
1198
|
explicitExecution,
|
|
901
1199
|
confirmation,
|
|
@@ -904,9 +1202,11 @@ function analyzePromptIntent(prompt) {
|
|
|
904
1202
|
noConfirmationRequested,
|
|
905
1203
|
reviewDecision,
|
|
906
1204
|
reviewCommand: promptReviewCommand,
|
|
1205
|
+
reviewContinuationRequested,
|
|
907
1206
|
readOnly,
|
|
908
1207
|
simpleConcrete,
|
|
909
1208
|
visualMockupRequest,
|
|
1209
|
+
largeUiChangeRequest,
|
|
910
1210
|
continuationRequest,
|
|
911
1211
|
continuationSessionId,
|
|
912
1212
|
continuationTaskHandle,
|
|
@@ -919,10 +1219,12 @@ function analyzePromptIntent(prompt) {
|
|
|
919
1219
|
browserSafetyRequest,
|
|
920
1220
|
productCopyRequest,
|
|
921
1221
|
shouldInject: requiresIntake
|
|
1222
|
+
|| requirementTier === 'l1'
|
|
922
1223
|
|| explicitExecution
|
|
923
1224
|
|| confirmation
|
|
924
1225
|
|| readOnly
|
|
925
1226
|
|| visualMockupRequest
|
|
1227
|
+
|| largeUiChangeRequest
|
|
926
1228
|
|| continuationRequest
|
|
927
1229
|
|| visualReview
|
|
928
1230
|
|| publicRepoResearchRequest
|
|
@@ -988,7 +1290,7 @@ function openRequirementGate(root, prompt, intent, sessionId = null) {
|
|
|
988
1290
|
openedAt: current?.openedAt || now(),
|
|
989
1291
|
updatedAt: now(),
|
|
990
1292
|
promptPreview: preview(prompt, 500),
|
|
991
|
-
reason: '
|
|
1293
|
+
reason: 'likely-l2 requirement flow',
|
|
992
1294
|
requiredFlow: ['requirement-intake', 'clarify', 'capture', 'synthesize', 'review', 'change-generate', 'tasks', 'implementation'],
|
|
993
1295
|
intakeMode: detectRequirementIntakeMode(prompt),
|
|
994
1296
|
intent,
|
|
@@ -1132,7 +1434,7 @@ function openWeappGate(root, prompt, sessionId = null) {
|
|
|
1132
1434
|
return writeNamedGate(root, 'weapp', {
|
|
1133
1435
|
version: 1,
|
|
1134
1436
|
active: true,
|
|
1135
|
-
status: 'needs-weapp-
|
|
1437
|
+
status: 'needs-weapp-runtime-validation',
|
|
1136
1438
|
openedAt: now(),
|
|
1137
1439
|
updatedAt: now(),
|
|
1138
1440
|
promptPreview: preview(prompt, 500),
|
|
@@ -1174,27 +1476,34 @@ function evaluateRequirementGateProgress(root, sessionId = null) {
|
|
|
1174
1476
|
const taskSummary = activeChange ? readLocalTaskSummary(root, activeChange) : null;
|
|
1175
1477
|
const hasTaskBreakdown = Boolean(activeChange && Number(taskSummary?.total ?? 0) > 0);
|
|
1176
1478
|
const approvalPolicy = requirementApprovalPolicy(gate);
|
|
1479
|
+
const clarificationConfirmed = requirementWritePathExplicitlyAuthorized(gate);
|
|
1177
1480
|
let nextStep = 'implementation-ready';
|
|
1178
|
-
let reason = '
|
|
1481
|
+
let reason = '这版需求和后续任务都已经准备好了;如果用户原本就明确要继续做,就直接往下推进,否则再补一句清楚的人话授权。';
|
|
1179
1482
|
if (!review.versionId) {
|
|
1180
|
-
nextStep = 'prd-
|
|
1181
|
-
reason =
|
|
1483
|
+
nextStep = clarificationConfirmed ? 'prd-synthesis-required' : 'clarification-confirmation-required';
|
|
1484
|
+
reason = clarificationConfirmed
|
|
1485
|
+
? (
|
|
1486
|
+
reviewPolicyAllowsSilentRecord(approvalPolicy)
|
|
1487
|
+
? '用户已明确表示不需要再停下来确认,本轮可以直接整理需求事实、生成这版确认稿,并继续后面的整理步骤。'
|
|
1488
|
+
: '当前需求摘要已经确认,下一步把已确认内容整理成可确认的需求稿。'
|
|
1489
|
+
)
|
|
1490
|
+
: '当前还缺需求摘要确认。先在对话里按“需求判断 / 需求理解 / 功能范围 / 技术方案”整理结构化摘要,其中“功能范围”和“技术方案”优先用 Markdown 表格;用户没确认前,不要直接把核心需求写成既定事实。';
|
|
1182
1491
|
} else if (review.status === 'needs-revision') {
|
|
1183
1492
|
nextStep = 'prd-review-required';
|
|
1184
|
-
reason = '
|
|
1493
|
+
reason = '当前这版需求确认稿已经被标记为需要调整,先改完再继续后面的整理或实现。';
|
|
1185
1494
|
} else if (review.status !== 'confirmed') {
|
|
1186
1495
|
nextStep = reviewPolicyAllowsSilentRecord(approvalPolicy)
|
|
1187
1496
|
? 'review-recording-required'
|
|
1188
1497
|
: 'prd-review-required';
|
|
1189
1498
|
reason = reviewPolicyAllowsSilentRecord(approvalPolicy)
|
|
1190
|
-
? '
|
|
1191
|
-
: '
|
|
1499
|
+
? '这版需求确认稿已经生成。由于用户已经明确表示不需要再停下来确认,本轮可以直接记录这次确认结果,再继续整理本次调整和后续任务。'
|
|
1500
|
+
: '这版需求确认稿还没有得到用户认可,不能把“请帮我实现/继续实现”当成已经确认这版内容。';
|
|
1192
1501
|
} else if (!activeChange) {
|
|
1193
1502
|
nextStep = 'change-generation-required';
|
|
1194
|
-
reason = '
|
|
1503
|
+
reason = '这版需求已经确认,下一步先整理本次调整范围。';
|
|
1195
1504
|
} else if (!hasTaskBreakdown) {
|
|
1196
1505
|
nextStep = 'task-breakdown-required';
|
|
1197
|
-
reason = '
|
|
1506
|
+
reason = '本次调整范围已经立起来了,但还没拆成可直接执行的后续任务。';
|
|
1198
1507
|
}
|
|
1199
1508
|
return {
|
|
1200
1509
|
runContext: parsed,
|
|
@@ -1225,6 +1534,11 @@ function reviewActionAuthorizationFor(intent, progress, prompt) {
|
|
|
1225
1534
|
versionId: review.versionId,
|
|
1226
1535
|
digest: review.digest,
|
|
1227
1536
|
workUnitId: review.workUnitId,
|
|
1537
|
+
continueAfterReview: Boolean(
|
|
1538
|
+
intent?.reviewContinuationRequested
|
|
1539
|
+
|| intent?.implementationConfirmation
|
|
1540
|
+
|| intent?.explicitExecution
|
|
1541
|
+
),
|
|
1228
1542
|
promptPreview: preview(prompt, 500),
|
|
1229
1543
|
grantedAt: now(),
|
|
1230
1544
|
source: 'explicit-user-review-decision',
|
|
@@ -1253,11 +1567,16 @@ function silentReviewActionAuthorizationFor(gate, progress, prompt) {
|
|
|
1253
1567
|
|
|
1254
1568
|
function holdRequirementGate(root, prompt, progress, sessionId = null, extra = {}) {
|
|
1255
1569
|
const reviewActionAuthorization = extra.reviewActionAuthorization ?? null;
|
|
1256
|
-
|
|
1570
|
+
const patch = {
|
|
1257
1571
|
status: extra.status ?? progress.nextStep,
|
|
1258
1572
|
confirmationPreview: preview(prompt, 500),
|
|
1259
1573
|
reviewActionAuthorization,
|
|
1260
|
-
}
|
|
1574
|
+
};
|
|
1575
|
+
if (extra.clarificationConfirmedAt) {
|
|
1576
|
+
patch.clarificationConfirmedAt = extra.clarificationConfirmedAt;
|
|
1577
|
+
patch.clarificationConfirmationPreview = extra.clarificationConfirmationPreview ?? preview(prompt, 500);
|
|
1578
|
+
}
|
|
1579
|
+
return updateRequirementGate(root, patch, sessionId);
|
|
1261
1580
|
}
|
|
1262
1581
|
|
|
1263
1582
|
function isImplementationAdvanceIntent(intent) {
|
|
@@ -1336,6 +1655,28 @@ function isMutationPayload(payload, risk) {
|
|
|
1336
1655
|
|| /\*\*\* Begin Patch/.test(text);
|
|
1337
1656
|
}
|
|
1338
1657
|
|
|
1658
|
+
function captureSourceFromCommand(command) {
|
|
1659
|
+
return readCliFlagValue(command, '--source');
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function isNonSemanticCaptureCommand(command) {
|
|
1663
|
+
return /openprd\s+capture\b/i.test(command)
|
|
1664
|
+
&& captureSourceFromCommand(command) === 'agent-normalized';
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
function gateHasClarificationConfirmation(gate) {
|
|
1668
|
+
return Boolean(gate?.clarificationConfirmedAt || gate?.status === 'clarification-confirmed');
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
function requirementWritePathExplicitlyAuthorized(gate) {
|
|
1672
|
+
return gateHasClarificationConfirmation(gate)
|
|
1673
|
+
|| reviewPolicyAllowsSilentRecord(requirementApprovalPolicy(gate));
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
function isRequirementWritePathMutation(command) {
|
|
1677
|
+
return /openprd\s+(capture|classify|synthesize|diagram)\b/i.test(command);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1339
1680
|
function isAllowedDuringRequirementGate(root, payload, gate, sessionId = null) {
|
|
1340
1681
|
const text = payloadText(payload);
|
|
1341
1682
|
const command = commandText(payload);
|
|
@@ -1346,12 +1687,7 @@ function isAllowedDuringRequirementGate(root, payload, gate, sessionId = null) {
|
|
|
1346
1687
|
/openprd\s+run\s+\.\s+--context\b/i,
|
|
1347
1688
|
/openprd\s+run\s+\.\s+--verify\b/i,
|
|
1348
1689
|
/openprd\s+clarify\b/i,
|
|
1349
|
-
/openprd\s+capture\b/i,
|
|
1350
|
-
/openprd\s+classify\b/i,
|
|
1351
1690
|
/openprd\s+interview\b/i,
|
|
1352
|
-
/openprd\s+synthesize\b/i,
|
|
1353
|
-
/openprd\s+diagram\b/i,
|
|
1354
|
-
/openprd\s+review-presentation\b/i,
|
|
1355
1691
|
/openprd\s+standards\s+.*--verify/i,
|
|
1356
1692
|
/openprd\s+quality\s+.*--verify/i,
|
|
1357
1693
|
/openprd\s+doctor\b/i,
|
|
@@ -1359,6 +1695,15 @@ function isAllowedDuringRequirementGate(root, payload, gate, sessionId = null) {
|
|
|
1359
1695
|
if (alwaysAllowed.some((pattern) => pattern.test(text))) {
|
|
1360
1696
|
return true;
|
|
1361
1697
|
}
|
|
1698
|
+
if (/openprd\s+capture\b/i.test(command)) {
|
|
1699
|
+
return isNonSemanticCaptureCommand(command) || requirementWritePathExplicitlyAuthorized(gate);
|
|
1700
|
+
}
|
|
1701
|
+
if (/openprd\s+(classify|synthesize|diagram)\b/i.test(command)) {
|
|
1702
|
+
return requirementWritePathExplicitlyAuthorized(gate);
|
|
1703
|
+
}
|
|
1704
|
+
if (/openprd\s+review-presentation\b/i.test(command)) {
|
|
1705
|
+
return /--template\b/i.test(command) || Boolean(progress.review.versionId);
|
|
1706
|
+
}
|
|
1362
1707
|
if (/openprd\s+review\b/i.test(command)) {
|
|
1363
1708
|
if (!/--mark\b/i.test(command)) {
|
|
1364
1709
|
return true;
|
|
@@ -1458,7 +1803,7 @@ function applyWeappToolSignal(root, payload, sessionId = null) {
|
|
|
1458
1803
|
const satisfied = validationSignals.ensureConnection && validationSignals.runtimeAction;
|
|
1459
1804
|
if (satisfied) {
|
|
1460
1805
|
return closeWeappGate(root, sessionId, {
|
|
1461
|
-
status: 'validated-through-weapp-
|
|
1806
|
+
status: 'validated-through-weapp-runtime-evidence',
|
|
1462
1807
|
validationSignals,
|
|
1463
1808
|
validatedAt: now(),
|
|
1464
1809
|
});
|
|
@@ -1523,8 +1868,10 @@ function weappGateMessage(gate) {
|
|
|
1523
1868
|
}
|
|
1524
1869
|
return [
|
|
1525
1870
|
'OpenPrd 微信小程序验证门禁: active。',
|
|
1526
|
-
'
|
|
1527
|
-
'
|
|
1871
|
+
'只有当用户明确要求小程序实测、复现、截图、抓日志/网络、从 0 到 1 走流程,或当前改动高风险到必须依赖运行态证据时,才升级到本地小程序运行态验证。',
|
|
1872
|
+
'一旦进入小程序运行态验证,默认沿用当前小程序运行态或开发者工具会话连续验证,不要为了验证自动重开应用;只有用户明确要求从 0 到 1、冷启动、重开或重新打开时,才从头启动。',
|
|
1873
|
+
'优先使用当前环境已配置的小程序本地验证能力;如果当前客户端没有相应工具,不要假定已经安装,也不要把缺少工具本身当成任务失败。',
|
|
1874
|
+
'未拿到本地运行态证据前,不要宣称“小程序已验证”。',
|
|
1528
1875
|
].join('\n');
|
|
1529
1876
|
}
|
|
1530
1877
|
|
|
@@ -1559,12 +1906,44 @@ function composeHookContext(root, intent = null, gate = null, progress = null, s
|
|
|
1559
1906
|
weappGateMessage(readNamedGate(root, 'weapp', sessionId)),
|
|
1560
1907
|
browserSafetyMessage(intent),
|
|
1561
1908
|
productCopyMessage(intent),
|
|
1909
|
+
largeUiVisualDirectionMessage(intent),
|
|
1562
1910
|
].filter(Boolean).join('\n');
|
|
1563
1911
|
}
|
|
1564
1912
|
|
|
1565
1913
|
function runOpenPrd(args, cwd) {
|
|
1566
|
-
const
|
|
1567
|
-
const
|
|
1914
|
+
const configuredCommand = String(process.env.OPENPRD_CLI || 'openprd').trim() || 'openprd';
|
|
1915
|
+
const jsEntry = /\.(?:c|m)?js$/i.test(configuredCommand);
|
|
1916
|
+
let command = configuredCommand;
|
|
1917
|
+
let commandArgs = args;
|
|
1918
|
+
if (jsEntry) {
|
|
1919
|
+
const candidates = [];
|
|
1920
|
+
const addCollapsedPackageCandidate = (candidatePath) => {
|
|
1921
|
+
if (!candidatePath) {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
const normalized = path.resolve(candidatePath);
|
|
1925
|
+
const binDir = path.dirname(normalized);
|
|
1926
|
+
const packageDir = path.dirname(binDir);
|
|
1927
|
+
const packageName = path.basename(packageDir);
|
|
1928
|
+
if (path.basename(binDir) === 'bin' && packageName === 'openprd') {
|
|
1929
|
+
candidates.push(path.join(path.dirname(packageDir), 'bin', 'openprd.js'));
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1932
|
+
if (path.isAbsolute(configuredCommand)) {
|
|
1933
|
+
candidates.push(configuredCommand);
|
|
1934
|
+
addCollapsedPackageCandidate(configuredCommand);
|
|
1935
|
+
} else {
|
|
1936
|
+
candidates.push(path.resolve(cwd, configuredCommand));
|
|
1937
|
+
candidates.push(path.resolve(configuredCommand));
|
|
1938
|
+
addCollapsedPackageCandidate(path.resolve(cwd, configuredCommand));
|
|
1939
|
+
addCollapsedPackageCandidate(path.resolve(configuredCommand));
|
|
1940
|
+
}
|
|
1941
|
+
candidates.push(path.join(cwd, 'bin', 'openprd.js'));
|
|
1942
|
+
const resolvedEntry = candidates.find((candidate) => candidate && fs.existsSync(candidate));
|
|
1943
|
+
command = process.execPath;
|
|
1944
|
+
commandArgs = [resolvedEntry || configuredCommand, ...args];
|
|
1945
|
+
}
|
|
1946
|
+
const result = spawnSync(command, commandArgs, {
|
|
1568
1947
|
cwd,
|
|
1569
1948
|
encoding: 'utf8',
|
|
1570
1949
|
timeout: 15000,
|
|
@@ -1590,6 +1969,34 @@ function parseJsonOutput(text) {
|
|
|
1590
1969
|
}
|
|
1591
1970
|
}
|
|
1592
1971
|
|
|
1972
|
+
function devCheckWrapUpMessage(root, turnState) {
|
|
1973
|
+
const files = Array.isArray(turnState?.touchedFiles)
|
|
1974
|
+
? [...new Set(turnState.touchedFiles)].filter(Boolean)
|
|
1975
|
+
: [];
|
|
1976
|
+
if (files.length === 0) {
|
|
1977
|
+
return null;
|
|
1978
|
+
}
|
|
1979
|
+
const result = runOpenPrd(['dev-check', '.', ...files, '--json'], root);
|
|
1980
|
+
if (!result.ok && !result.stdout) {
|
|
1981
|
+
return [
|
|
1982
|
+
'OpenPrd 收工回顾:本轮有 touched code files,但 dev-check 未能完成。',
|
|
1983
|
+
'最终回复里请说明无法生成大文件审查表,并列出失败原因。',
|
|
1984
|
+
result.stderr ? `错误: ${result.stderr}` : null,
|
|
1985
|
+
].filter(Boolean).join('\n');
|
|
1986
|
+
}
|
|
1987
|
+
const parsed = parseJsonOutput(result.stdout);
|
|
1988
|
+
if (!parsed?.wrapUp?.required || !parsed.wrapUp.markdownTable) {
|
|
1989
|
+
return null;
|
|
1990
|
+
}
|
|
1991
|
+
return [
|
|
1992
|
+
'OpenPrd 后续建议:本轮有改动对象需要主动说明。',
|
|
1993
|
+
'最终回复必须直接复用下面的 Markdown 表格,按 🔴 → 🟠 → 🟡 的顺序帮助产品或业务理解影响对象、本次处理结果和后续建议;不要只用工具名或一段话带过。',
|
|
1994
|
+
'如果你改写了“预警原因 / 本次处理结果 / 后续建议”,先用 `node scripts/dev-check-wrapup-copy.mjs --validate` 校验每格不超过 20 字;若报错,按提示缩短后重试。',
|
|
1995
|
+
'不要把“关注程度”列改写成纯 emoji;必须保留例如“🟠 中风险|建议优先关注”这类完整标签。',
|
|
1996
|
+
parsed.wrapUp.markdownBlock ?? parsed.wrapUp.markdownTable,
|
|
1997
|
+
].join('\n');
|
|
1998
|
+
}
|
|
1999
|
+
|
|
1593
2000
|
function shouldRunDoctorForHighRisk(payload) {
|
|
1594
2001
|
const text = commandText(payload) || payloadText(payload);
|
|
1595
2002
|
return /(git\s+(commit|push)\b|npm\s+publish|pnpm\s+publish|yarn\s+npm\s+publish|gh\s+release|openprd\s+(freeze|handoff|setup|update|fleet|doctor)\b|openprd\s+change\s+.*--(apply|archive)\b|release|publish)/i.test(text);
|
|
@@ -1608,7 +2015,8 @@ function summarizeRunVerifyCheck(parsed, fallbackText = '') {
|
|
|
1608
2015
|
return `run-verify: taskReady=no${failedTaskChecks.length ? ` (${failedTaskChecks.join(', ')})` : ''}`;
|
|
1609
2016
|
}
|
|
1610
2017
|
if (readiness.workspaceReady === false) {
|
|
1611
|
-
|
|
2018
|
+
const workspaceDetail = parsed.workspaceAttention?.summary ?? workspaceWarnings[0] ?? null;
|
|
2019
|
+
return `run-verify: taskReady=yes, workspaceReady=no${workspaceDetail ? ` (${workspaceDetail})` : ''}`;
|
|
1612
2020
|
}
|
|
1613
2021
|
return 'run-verify: taskReady=yes, workspaceReady=yes';
|
|
1614
2022
|
}
|
|
@@ -1655,7 +2063,11 @@ function buildGateFailureEnvelope(result) {
|
|
|
1655
2063
|
if (runCheck?.workspaceReady === false) {
|
|
1656
2064
|
return {
|
|
1657
2065
|
kind: 'workspace-debt',
|
|
1658
|
-
details: [
|
|
2066
|
+
details: [
|
|
2067
|
+
runCheck.summary,
|
|
2068
|
+
runCheck.workspaceAttention?.detail ?? null,
|
|
2069
|
+
...(runCheck.warnings ?? []),
|
|
2070
|
+
].filter(Boolean),
|
|
1659
2071
|
repair: 'Repair path: resolve the workspace-level debt from openprd run . --verify or openprd quality . --verify, then retry this high-risk action.',
|
|
1660
2072
|
};
|
|
1661
2073
|
}
|
|
@@ -1732,40 +2144,81 @@ function requirementGateMessage(intent, gate) {
|
|
|
1732
2144
|
'The user is asking for an image asset such as a cover image, poster, illustration, icon, sticker, visual mockup, or effect image, not code implementation.',
|
|
1733
2145
|
'For logo, icon, avatar, badge, and similar development assets, default to a standalone asset: full-frame single subject with no extra UI frame, card shell, device mockup, or presentation container unless the user explicitly asked for one.',
|
|
1734
2146
|
'Do not create temporary HTML/SVG/CSS files for this image unless the user explicitly requested that format.',
|
|
1735
|
-
'Use Codex native Image 2 to generate the image; keep implementation, PRD review, and visual-compare for later explicit confirmation.',
|
|
2147
|
+
'Use `imagegen`, which is Codex native Image 2, to generate the image; keep implementation, PRD review, and visual-compare for later explicit confirmation.',
|
|
1736
2148
|
].join('\n');
|
|
1737
2149
|
}
|
|
1738
2150
|
const status = gateBlocksImplementation ? 'active' : 'opened';
|
|
1739
2151
|
return [
|
|
1740
2152
|
'OpenPrd requirement intake gate: ' + status + '.',
|
|
1741
|
-
'This prompt
|
|
1742
|
-
'
|
|
2153
|
+
'This prompt looks like a likely 新功能/新流程方案 (L2), so the heavy requirement-intake lane is active. Do not decide from fixed keywords; first use $openprd-requirement-intake to classify the user-visible requirement type by impact, unknowns, decision cost, and validation cost.',
|
|
2154
|
+
'Keep this mapping visible for internal review: 快速修正=L0, 现有功能优化=L1, 新功能/新流程方案=L2.',
|
|
2155
|
+
'L0 and L1 stay on lightweight paths and should not be forced through formal PRD/review/change/tasks unless the scope expands.',
|
|
2156
|
+
'If the requirement type is 新功能/新流程方案 (L2), do not edit implementation files yet and proceed through PRD/review/change/tasks with the appropriate PRD scene lens: 通用场景、面向个人消费者场景、面向企业服务场景,或以 Agent 为主要使用场景。 Keep raw enum values such as base / consumer / b2b / agent for internal commands or records only; do not surface them to the user unless truly necessary.',
|
|
1743
2157
|
reviewPolicyAllowsSilentRecord(approvalPolicy)
|
|
1744
|
-
? 'Decision-point policy:
|
|
1745
|
-
: 'Decision-point policy:
|
|
2158
|
+
? 'Decision-point policy: because the user explicitly said there is no need for any confirmation stop, you may skip the requirement-summary confirmation stop, write back the requirement facts, synthesize the PRD, record the exact current stable review artifact, generate the OpenPrd change, prepare the task breakdown, then implement within the confirmed scope.'
|
|
2159
|
+
: 'Decision-point policy: first output a short structured requirement summary in chat with 需求判断 / 需求理解 / 功能范围 / 技术方案, where 功能范围 and 技术方案 should prefer Markdown tables; wait for the user to confirm that summary, then write back confirmed facts, synthesize the PRD, wait for a human decision on the stable review artifact, generate the OpenPrd change, prepare the task breakdown, then implement within the confirmed scope.',
|
|
1746
2160
|
reviewPolicyAllowsSilentRecord(approvalPolicy)
|
|
1747
|
-
? 'This lane is in silent-record mode because the user
|
|
1748
|
-
: '
|
|
2161
|
+
? 'This lane is in silent-record mode only because the user explicitly said there is no need for any further review or confirmation stop. Plain "请帮我实现" is not enough; you may record only the exact current version, digest, and work unit.'
|
|
2162
|
+
: 'Requirement-summary confirmation, review-artifact confirmation, and implementation authorization are different gates: do not treat "可以开做", "继续实现", plain "请帮我实现", or "不需要评审" as permission to skip them.',
|
|
1749
2163
|
'If the original request already asked to implement, execution can continue once the active approval policy and tasks are ready; otherwise wait for a clear execution request.',
|
|
1750
|
-
'Recommended next action: write a short
|
|
2164
|
+
'Recommended next action: write a short 需求类型判断 in chat, and by default merge the route into the label as 需求类型:新功能/新流程方案(L2); only add a separate 内部路由码 when internal debugging truly benefits. Then run openprd clarify ., summarize the requirement in chat using 需求判断 / 需求理解 / 功能范围 / 技术方案, prefer Markdown tables for 功能范围 and 技术方案, ask for confirmation, and only after that write back confirmed facts. Do not open a clarification HTML page; the formal HTML review happens after synthesize/review.',
|
|
1751
2165
|
].join('\n');
|
|
1752
2166
|
}
|
|
1753
2167
|
|
|
2168
|
+
function lightweightRequirementMessage(intent) {
|
|
2169
|
+
if (intent?.requirementTier === 'l0') {
|
|
2170
|
+
return [
|
|
2171
|
+
'OpenPrd 轻量需求路径: 当前更接近快速修正 (L0)。',
|
|
2172
|
+
'先在 chat 用短格式写出“需求类型 / 理由 / 推荐下一步”,并默认写成“需求类型:快速修正(L0)”;只有内部排障确实需要时,才额外单列“内部路由码”。',
|
|
2173
|
+
'直接处理并事后说明即可,不打开正式 PRD/review/change/tasks。',
|
|
2174
|
+
'优先做最小足够验证,并用 1-2 句说明本轮特别需要强化的测试点;默认不要求正式测试报告。',
|
|
2175
|
+
'如果过程中暴露出跨系统依赖、支付/账号/权限/回调等高风险因素,再升级到 L2 重流程。',
|
|
2176
|
+
].join('\n');
|
|
2177
|
+
}
|
|
2178
|
+
if (intent?.requirementTier === 'l1') {
|
|
2179
|
+
return [
|
|
2180
|
+
'OpenPrd 轻量需求路径: 当前更接近现有功能优化 (L1)。',
|
|
2181
|
+
'先在 chat 用短格式写出“需求类型 / 理由 / 推荐下一步”,并默认写成“需求类型:现有功能优化(L1)”,再给 3-5 行 mini-plan;只有内部排障确实需要时,才额外单列“内部路由码”。',
|
|
2182
|
+
'先在对话里给 3-5 行 mini-plan,至少写清目标、范围内、范围外和验证方式。',
|
|
2183
|
+
'默认不要打开正式 PRD/review/change/tasks;只有在 mini-plan 暴露新决策缺口、跨系统风险或范围升级时,才提升到 L2 重流程。',
|
|
2184
|
+
'验证采用最小足够组合即可,重点说明需要强化测试的地方;默认不要求正式测试报告。',
|
|
2185
|
+
intent?.largeUiChangeRequest
|
|
2186
|
+
? '如果这是大界面改动,mini-plan 之后先做 3 方向视觉方案评审,再进入实现。'
|
|
2187
|
+
: '',
|
|
2188
|
+
].filter(Boolean).join('\n');
|
|
2189
|
+
}
|
|
2190
|
+
return null;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
1754
2193
|
function visualMockupMessage(intent) {
|
|
1755
2194
|
if (!intent?.visualMockupRequest) {
|
|
1756
2195
|
return null;
|
|
1757
2196
|
}
|
|
1758
2197
|
return [
|
|
1759
2198
|
'当前用户要的是图片内容生成,例如图片、封面图、配图、海报、插画、图标、贴纸、头像、banner、主视觉/KV、运营图、效果图、视觉稿或 mockup。',
|
|
1760
|
-
'默认直接调用 Codex 原生 Image 2
|
|
2199
|
+
'默认直接调用 `imagegen`,也就是 Codex 原生 Image 2,来生成图片;除非用户明确指定 HTML、SVG、CSS、Canvas、代码稿或可编辑矢量/source artifact,不要改用临时 HTML/SVG/CSS 再截图。',
|
|
1761
2200
|
'对 logo、icon、avatar、badge 等开发素材,如果用户没有明确要求 mockup、场景图、设备框、卡片承载、名片/包装展示或参考界面复刻,默认按独立素材输出(standalone asset)处理:使用全画布单主体,不额外添加 UI frame、卡片、设备壳、名片、桌面陈列、手持实拍或其他展示容器。',
|
|
1762
2201
|
'只有当用户明确要求 mockup、场景化效果图、容器化呈现,或参考图本身就包含这些承载结构时,才生成对应的容器或场景。',
|
|
1763
|
-
'
|
|
2202
|
+
'只有在实际发生 `imagegen` 调用后,才能汇报生图结果、失败或限流;未调用 `imagegen` 前,不要声称“生图限流”或“生图失败”。',
|
|
2203
|
+
'OpenPrd review.html 只用于需求评审,visual-compare 只用于实现阶段视觉证据:已有参考图时做效果图/实现截图对比,无参考图但改动界面时做修改前/修改后自检;局部细节优先补局部焦点证据板,并行优化方向优先补并行实验证据板。',
|
|
2204
|
+
].join('\n');
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
function largeUiVisualDirectionMessage(intent) {
|
|
2208
|
+
if (!intent?.largeUiChangeRequest) {
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
return [
|
|
2212
|
+
'OpenPrd 大界面改动视觉方案评审:',
|
|
2213
|
+
'位置: 需求分流之后、PRD 定稿或实现开工之前;它不同于 review.html,也不同于实现后的 visual-compare。',
|
|
2214
|
+
'判断: 会明显改变信息架构、核心布局、主视觉、关键路径、组件层级/密度,或用户需要先选设计方向时触发。',
|
|
2215
|
+
'步骤: 用 Codex Computer Use 进入产品内对应功能并截当前真实界面;基于截图调用 `imagegen`(Codex 原生 Image 2)做图生图,至少生成 3 个不同设计思想方向;把效果图横向拼成一张大图,每张左上角标注 1/2/3,并保存到 .openprd/harness/visual-reviews/。',
|
|
2216
|
+
'交互: 把横向大图展示给用户评审确认;用户确认方向前,不进入大 UI 实现,也不要声称界面方案已定。',
|
|
1764
2217
|
].join('\n');
|
|
1765
2218
|
}
|
|
1766
2219
|
|
|
1767
2220
|
function codexConfirmationReplyRule() {
|
|
1768
|
-
return 'Codex
|
|
2221
|
+
return 'Codex 回复规则: 只有当前真的还缺人来拍板时,才在 final answer 里停下来请求确认。只要用户已经明确说了“认可并继续”,或已经确认了 mini-plan、范围边界、正式产品边界,就直接沿着已确认路径继续,不要再写“如果你认可”“确认的话我就继续”这类二次索取确认的话。面向用户时,不要直接抛 lane、change、tasks、review artifact、work unit、digest、worker shard、write-scope、worktree、内部版本号、命令或文件路径这些内部词;统一改说“需求确认稿”“本次调整”“后续任务”“继续落地”“完成后检查”。如果后面真的还需要额外授权,再用人话把会做什么、不会做什么、完成后怎么检查说清楚。如果当前仍在 L2 的首轮澄清或需求摘要确认阶段,不要写成“你回我一句我就开始实现”;只能承诺“我先整理需求摘要给你确认,确认后再继续”。如果当前 lane 已进入 silent-record 策略,只能说明用户已经明确说了“不需要进行任何确认”;单纯的“请帮我实现/继续实现”不能把 lane 切到 silent-record。';
|
|
1769
2222
|
}
|
|
1770
2223
|
|
|
1771
2224
|
function confirmationGateMessage(gate) {
|
|
@@ -1778,32 +2231,40 @@ function confirmationGateMessage(gate) {
|
|
|
1778
2231
|
return [
|
|
1779
2232
|
intro,
|
|
1780
2233
|
'Implementation may proceed only within the confirmed scope, with docs/basic, file manuals, folder README docs, standards verification, and OpenPrd run verification kept up to date. For backend, script, agent, tooling, service, or data-processing changes, keep CLI and API surface review current in docs/basic/backend-structure.md.',
|
|
1781
|
-
'For UI or visual work with an existing reference image, capture the implemented UI and run openprd visual-compare . --reference <effect-image> --actual <implementation-screenshot>;
|
|
2234
|
+
'For UI or visual work with an existing reference image, capture the implemented UI and run openprd visual-compare . --reference <effect-image> --actual <implementation-screenshot>; if local detail matters more than the whole screen, add openprd visual-compare . --board <focus-board.json> so the agent can review numbered zoom regions. When there is no reference image, capture the before screenshot first, implement, capture the after screenshot from the same entry, viewport, account, and data state, then run openprd visual-compare . --before <before-screenshot> --after <after-screenshot>; if the agent explored multiple optimization directions, add openprd visual-compare . --board <parallel-board.json> and inspect expected changes plus unintended drift before claiming completion.',
|
|
1782
2235
|
].join('\n');
|
|
1783
2236
|
}
|
|
1784
2237
|
|
|
1785
2238
|
function currentRequirementStatusLine(gate, progress) {
|
|
1786
2239
|
if (gate?.status === 'review-confirmation-authorized') {
|
|
1787
|
-
return
|
|
2240
|
+
return gate?.reviewActionAuthorization?.continueAfterReview
|
|
2241
|
+
? '用户刚刚已经确认这版需求,并且明确表示继续。先记录这次确认结果,然后直接接着整理本次调整和后续任务;只有后面真的还缺额外授权时,再用人话说明影响和下一步。'
|
|
2242
|
+
: '用户刚刚已经确认这版需求;本回合只允许记录这次确认结果,不能把它直接扩展成开工授权。';
|
|
1788
2243
|
}
|
|
1789
2244
|
if (gate?.status === 'review-recording-authorized') {
|
|
1790
|
-
return '
|
|
2245
|
+
return '这版需求确认稿已经按免再次确认的规则授权记录;只记录这一次确认结果,然后继续整理本次调整和后续任务。';
|
|
1791
2246
|
}
|
|
1792
2247
|
switch (progress?.nextStep) {
|
|
2248
|
+
case 'clarification-confirmation-required':
|
|
2249
|
+
return '当前卡点: 先让用户确认当前需求摘要;在此之前不要把核心需求写成既定事实,也不要直接往后推进。';
|
|
2250
|
+
case 'prd-synthesis-required':
|
|
2251
|
+
return reviewPolicyAllowsSilentRecord(requirementApprovalPolicy(gate))
|
|
2252
|
+
? '当前卡点: 用户已明确表示不需要再停下来确认,可以直接整理已确认内容,并生成这版需求确认稿。'
|
|
2253
|
+
: '当前卡点: 当前需求摘要已确认,下一步把已确认内容整理成这版需求确认稿。';
|
|
1793
2254
|
case 'prd-review-required':
|
|
1794
2255
|
return progress?.review?.versionId
|
|
1795
|
-
? '当前卡点:
|
|
1796
|
-
: '当前卡点:
|
|
2256
|
+
? '当前卡点: 先等用户确认这版需求确认稿;不要把“继续做”“开落地吧”或单纯的“请帮我实现/继续实现”当成已经认可这版内容。'
|
|
2257
|
+
: '当前卡点: 先整理出这版需求确认稿,再等待用户确认。';
|
|
1797
2258
|
case 'review-recording-required':
|
|
1798
|
-
return '当前卡点:
|
|
2259
|
+
return '当前卡点: 这版需求确认稿已经生成,而且用户已明确表示不需要再停下来确认;可以先记录这次确认结果,再继续后面的整理。';
|
|
1799
2260
|
case 'change-generation-required':
|
|
1800
|
-
return '当前卡点:
|
|
2261
|
+
return '当前卡点: 这版需求已经确认,下一步先整理本次调整范围;这是当前流程的直接延续,不需要再重复索取同一轮确认。';
|
|
1801
2262
|
case 'task-breakdown-required':
|
|
1802
|
-
return '当前卡点:
|
|
2263
|
+
return '当前卡点: 本次调整范围已经立起来了,下一步把它拆成可直接执行的后续任务。';
|
|
1803
2264
|
case 'implementation-ready':
|
|
1804
|
-
return '当前卡点:
|
|
2265
|
+
return '当前卡点: 这版需求和后续任务都准备好了。如果用户一开始就明确要继续做,就直接进入实现;否则再用人话说明这次会做什么、不会做什么、完成后怎么检查。若用户刚刚确认的是 L1 范围边界或 mini-plan,承接话术要写成“已确认,我按这个继续”,不要再写成像二次索取确认的句子。';
|
|
1805
2266
|
default:
|
|
1806
|
-
return '当前卡点:
|
|
2267
|
+
return '当前卡点: 继续按“澄清需求 -> 确认需求 -> 整理本次调整 -> 拆分后续任务 -> 开始实现”的顺序推进。';
|
|
1807
2268
|
}
|
|
1808
2269
|
}
|
|
1809
2270
|
|
|
@@ -1816,20 +2277,29 @@ function currentRequirementMessage(intent, gate, progress) {
|
|
|
1816
2277
|
const lines = [
|
|
1817
2278
|
'OpenPrd 当前需求入口',
|
|
1818
2279
|
gateStatus,
|
|
1819
|
-
'
|
|
1820
|
-
'
|
|
2280
|
+
'对外表达要求: 面向用户不要直接复述 PRD、review artifact、change、tasks、lane、approval policy、work unit、digest、worker shard、write-scope、worktree、内部版本号、命令或文件路径;改说“需求确认稿”“本次调整”“后续任务”“继续落地”“完成后检查”。',
|
|
2281
|
+
'当前输入已被判定为可能的新功能/新流程方案(L2),因此进入重流程需求入口。不要按固定关键词判断;先用 $openprd-requirement-intake 按影响面、未知数、决策成本和验证成本判断用户可见需求类型。',
|
|
2282
|
+
'内部审查保留固定对照:快速修正=L0,现有功能优化=L1,新功能/新流程方案=L2。',
|
|
2283
|
+
'如果用户刚刚已经确认了现有功能优化(L1)的 mini-plan、范围边界或正式产品边界,下一句要明确写成“已确认,我按这个继续/收口/落地”;不要只写一个“确认”,更不要写成“确认,我们就按这个……”这种容易让用户误以为还要再表态的句子。',
|
|
2284
|
+
'如果需求类型是新功能/新流程方案(L2),本轮只围绕这个新需求推进 PRD/review/change/tasks,并选择通用场景 / 面向个人消费者场景 / 面向企业服务场景 / 以 Agent 为主要使用场景的 PRD 视角,不自动继续历史 active change。对用户复述时不要直接把 consumer / b2b / agent 当展示词;这些枚举值只用于内部记录和命令。',
|
|
1821
2285
|
prompt ? '本轮需求: ' + prompt : '',
|
|
1822
2286
|
gate?.intakeMode === 'deep-reflection'
|
|
1823
2287
|
? '需求入口: 先运行需求自省,再输出对话内澄清摘要或简短清单。'
|
|
1824
2288
|
: '需求入口: 先做轻量项目映射,再确认影响点和验收方式。',
|
|
1825
2289
|
reviewPolicyAllowsSilentRecord(approvalPolicy)
|
|
1826
|
-
? '当前 approval policy: decision-points / silent-record
|
|
1827
|
-
: '当前 approval policy: decision-points / human-review
|
|
2290
|
+
? '当前 approval policy: decision-points / silent-record。之所以进入 silent-record,是因为用户已经明确表示不需要进行任何确认;单纯的“请帮我实现/继续实现”或“不要评审”都不够,仍然只能记录版本、digest、work unit 精确匹配的 artifact。'
|
|
2291
|
+
: '当前 approval policy: decision-points / human-review。当前 lane 仍需要一次 requirement 摘要确认和一次稳定 review artifact 的明确人类决策;单纯的“请帮我实现/继续实现”不算这两次决策。',
|
|
2292
|
+
intent?.reviewContinuationRequested
|
|
2293
|
+
? '这条消息同时表达了“确认当前稳定评审稿并继续当前 OpenPrd 下一步”的意图:先记录精确 review artifact,再继续当前 lane;如果 review 后 tasks 已就绪但还需要执行授权,立刻展示执行确认清单,不要停在“如果你要我继续”。'
|
|
2294
|
+
: '',
|
|
1828
2295
|
currentRequirementStatusLine(gate, progress),
|
|
1829
2296
|
reviewPolicyAllowsSilentRecord(approvalPolicy)
|
|
1830
|
-
? 'Decision-point order:
|
|
1831
|
-
: 'Decision-point order: clarify the requirement,
|
|
1832
|
-
|
|
2297
|
+
? 'Decision-point order: because the user explicitly waived any confirmation stop, you may skip requirement-summary confirmation, write back requirement facts, synthesize the PRD, record the exact stable review artifact, generate the OpenPrd change, prepare the task breakdown, then implement within the confirmed scope.'
|
|
2298
|
+
: 'Decision-point order: clarify the requirement, summarize it in chat using 需求判断 / 需求理解 / 功能范围 / 技术方案, prefer Markdown tables for 功能范围 and 技术方案, wait for the user to confirm that requirement summary, write back only confirmed facts, synthesize the PRD, wait for a human review decision on the stable artifact, generate the OpenPrd change, prepare the task breakdown, then implement within the confirmed scope.',
|
|
2299
|
+
intent?.largeUiChangeRequest
|
|
2300
|
+
? 'Large UI direction gate: before PRD freeze or implementation, capture the current in-product screen with Codex Computer Use, generate at least three Image 2 directions, combine them into one horizontal numbered contact sheet, and wait for the user to choose a direction.'
|
|
2301
|
+
: '',
|
|
2302
|
+
'Recommended next action: 先在 chat 输出“需求类型判断”,默认把路由码并进“需求类型:新功能/新流程方案(L2)”这类标签里;只有内部排障确实需要时,才额外写“内部路由码”。若为新功能/新流程方案(L2),再运行 openprd clarify .,并按“需求判断 / 需求理解 / 功能范围 / 技术方案”给出十句话左右的结构化摘要,其中“功能范围”和“技术方案”优先用 Markdown 表格;请求确认后再写回 requirement 事实并继续 classify/synthesize,不要把这一步表述成“确认后直接开始实现”。Do not open clarification HTML; use review.html only after synthesize/review.',
|
|
1833
2303
|
];
|
|
1834
2304
|
if (isImplementationAdvanceIntent(intent)) {
|
|
1835
2305
|
lines.splice(2, 1, gate?.active
|
|
@@ -1840,6 +2310,10 @@ function currentRequirementMessage(intent, gate, progress) {
|
|
|
1840
2310
|
return lines.filter(Boolean).join('\n');
|
|
1841
2311
|
}
|
|
1842
2312
|
|
|
2313
|
+
function requirementRoutingSummary() {
|
|
2314
|
+
return '需求类型由 $openprd-requirement-intake 按影响面、未知数、决策成本和验证成本判断:快速修正(L0)直接处理并事后说明,不打开正式 PRD/review/change/tasks;现有功能优化(L1)先给对话内 mini-plan,默认不生成正式 PRD/change/tasks;新功能/新流程方案(L2)才进入 requirement intake 与 PRD/review/change/tasks,并选择通用场景 / 面向个人消费者场景 / 面向企业服务场景 / 以 Agent 为主要使用场景的 PRD 视角。对用户复述时不要直接把 consumer / b2b / agent 当展示词;这些枚举值只用于内部记录和命令。单纯的“请帮我实现/继续实现”只表示有执行意图,不表示跳过 requirement 摘要确认或 review;只有用户明确表示不需要进行任何确认时,才允许静默走完整 requirement write path。';
|
|
2315
|
+
}
|
|
2316
|
+
|
|
1843
2317
|
function historicalRequirementReminder(root, runContext, intent, gate) {
|
|
1844
2318
|
const activeChange = runContext?.activeChange;
|
|
1845
2319
|
if (!activeChange || (!intent?.requiresIntake && !(intent?.confirmation && gate?.promptPreview))) {
|
|
@@ -1864,6 +2338,11 @@ function historicalRequirementReminder(root, runContext, intent, gate) {
|
|
|
1864
2338
|
].filter(Boolean).join('\n');
|
|
1865
2339
|
}
|
|
1866
2340
|
|
|
2341
|
+
function knowledgeSkillReminder(runContext) {
|
|
2342
|
+
const lines = knowledgeSkillContextLines(runContext?.knowledgeSkills);
|
|
2343
|
+
return lines.length > 0 ? lines.join('\n') : null;
|
|
2344
|
+
}
|
|
2345
|
+
|
|
1867
2346
|
function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
1868
2347
|
const run = progress?.runContext
|
|
1869
2348
|
? { ok: true, parsed: progress.runContext, stdout: renderRunContextText(progress.runContext) }
|
|
@@ -1876,14 +2355,18 @@ function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
|
1876
2355
|
return [
|
|
1877
2356
|
currentRequirementMessage(intent, gate, effectiveProgress),
|
|
1878
2357
|
historicalRequirementReminder(cwd, run.parsed, intent, gate),
|
|
2358
|
+
knowledgeSkillReminder(run.parsed),
|
|
1879
2359
|
'OpenPrd 上下文只是建议,不是自动执行指令。请先判断用户当前意图。',
|
|
2360
|
+
lightweightRequirementMessage(intent),
|
|
1880
2361
|
visualMockupMessage(intent),
|
|
1881
|
-
|
|
2362
|
+
largeUiVisualDirectionMessage(intent),
|
|
2363
|
+
requirementRoutingSummary(),
|
|
1882
2364
|
'如果用户只是要求看看、规划、分析、审查、解释影响或列出文件,请保持只读并基于证据回答;不要运行 OpenPrd loop、任务推进、discovery 推进、commit 或其他写入命令。',
|
|
1883
2365
|
'只有当用户当前明确要求开发、实现、修复、继续任务、深度调研、对标复刻或提交时,才运行 openprd loop --run、openprd tasks --advance、openprd discovery --advance、commit/push 等执行命令。',
|
|
1884
|
-
'代码修改完成后、最终回复前,针对本轮实际 touched code files 运行 openprd dev-check . <file
|
|
1885
|
-
'
|
|
1886
|
-
'
|
|
2366
|
+
'代码修改完成后、最终回复前,针对本轮实际 touched code files 运行 openprd dev-check . <file...>;若出现需要关注的文件,最终回复必须以 **后续建议** 为标题,直接复用 dev-check 生成的 Markdown 表格,列出影响对象、关注程度、规模信号、预警原因、本次处理结果和后续建议,并按 🔴 → 🟠 → 🟡 排序;不要把“关注程度”列改写成纯 emoji,必须保留例如“🟠 中风险|建议优先关注”这类完整标签;如果你改写了“预警原因 / 本次处理结果 / 后续建议”,先用 `node scripts/dev-check-wrapup-copy.mjs --validate` 校验每格不超过 20 字;若报错,按提示缩短后重试。',
|
|
2367
|
+
'大界面改动进入实现前,先用 Codex Computer Use 截取产品内当前功能截图,再用 `imagegen`(Codex 原生 Image 2)基于截图生成至少 3 个设计方向,横向拼接为一张带 1/2/3 序号的大图给用户确认;未确认方向前不要进入大 UI 实现。',
|
|
2368
|
+
'涉及界面、页面、视觉、样式或前端体验,且已经有效果图/设计稿/用户给图并进入实现阶段时,阶段性完成后必须截图并运行 openprd visual-compare . --reference <效果图> --actual <实现截图>;如果这次重点在局部细节,再补一份 openprd visual-compare . --board <focus-board.json>。没有明确参考图但改动界面时,动手前先截修改前截图,完成后用同一入口、视口、账号和数据状态截修改后截图,并运行 openprd visual-compare . --before <修改前截图> --after <修改后截图>;如果并行试了多个优化方向,再补一份 openprd visual-compare . --board <parallel-board.json>;默认输出 JPG 到 .openprd/harness/visual-reviews/。查看合成图后继续对标或自检,直到没有明显视觉差异或意外漂移。',
|
|
2369
|
+
'发现可沉淀项时不要中途打断任务:代码扩展识别这类白名单工具补全会自动应用并记录;用户偏好、项目协作规矩和 OpenPrd 默认行为先记录为候选,收工时运行 openprd grow . --review 集中确认。',
|
|
1887
2370
|
'维护 OpenPrd 本身且涉及配置类能力时,先判断是否应纳入 openprd grow;高置信可成长默认纳入,不确定则主动询问用户。',
|
|
1888
2371
|
'涉及后端、脚本、Agent、工具链、服务或数据处理变更时,把 CLI 与 API 视为同级接入面:同步检查命令入口、参数、输出契约、help/doctor/dry-run/status 与接口协议、返回结构、身份边界是否受影响,并更新 docs/basic/backend-structure.md 或明确写不适用原因。',
|
|
1889
2372
|
'声明实现就绪前,先运行 openprd standards . --verify 和 openprd run . --verify。',
|
|
@@ -1892,14 +2375,18 @@ function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
|
1892
2375
|
return [
|
|
1893
2376
|
run.stdout,
|
|
1894
2377
|
gateMessage,
|
|
2378
|
+
knowledgeSkillReminder(run.parsed),
|
|
1895
2379
|
'OpenPrd 上下文只是建议,不是自动执行指令。请先判断用户当前意图。',
|
|
2380
|
+
lightweightRequirementMessage(intent),
|
|
1896
2381
|
visualMockupMessage(intent),
|
|
1897
|
-
|
|
2382
|
+
largeUiVisualDirectionMessage(intent),
|
|
2383
|
+
requirementRoutingSummary(),
|
|
1898
2384
|
'如果用户只是要求看看、规划、分析、审查、解释影响或列出文件,请保持只读并基于证据回答;不要运行 OpenPrd loop、任务推进、discovery 推进、commit 或其他写入命令。',
|
|
1899
2385
|
'只有当用户当前明确要求开发、实现、修复、继续任务、深度调研、对标复刻或提交时,才运行 openprd loop --run、openprd tasks --advance、openprd discovery --advance、commit/push 等执行命令。',
|
|
1900
|
-
'代码修改完成后、最终回复前,针对本轮实际 touched code files 运行 openprd dev-check . <file
|
|
1901
|
-
'
|
|
1902
|
-
'
|
|
2386
|
+
'代码修改完成后、最终回复前,针对本轮实际 touched code files 运行 openprd dev-check . <file...>;若出现需要关注的文件,最终回复必须以 **后续建议** 为标题,直接复用 dev-check 生成的 Markdown 表格,列出影响对象、关注程度、规模信号、预警原因、本次处理结果和后续建议,并按 🔴 → 🟠 → 🟡 排序;不要把“关注程度”列改写成纯 emoji,必须保留例如“🟠 中风险|建议优先关注”这类完整标签;如果你改写了“预警原因 / 本次处理结果 / 后续建议”,先用 `node scripts/dev-check-wrapup-copy.mjs --validate` 校验每格不超过 20 字;若报错,按提示缩短后重试。',
|
|
2387
|
+
'大界面改动进入实现前,先用 Codex Computer Use 截取产品内当前功能截图,再用 `imagegen`(Codex 原生 Image 2)基于截图生成至少 3 个设计方向,横向拼接为一张带 1/2/3 序号的大图给用户确认;未确认方向前不要进入大 UI 实现。',
|
|
2388
|
+
'涉及界面、页面、视觉、样式或前端体验,且已经有效果图/设计稿/用户给图并进入实现阶段时,阶段性完成后必须截图并运行 openprd visual-compare . --reference <效果图> --actual <实现截图>;如果这次重点在局部细节,再补一份 openprd visual-compare . --board <focus-board.json>。没有明确参考图但改动界面时,动手前先截修改前截图,完成后用同一入口、视口、账号和数据状态截修改后截图,并运行 openprd visual-compare . --before <修改前截图> --after <修改后截图>;如果并行试了多个优化方向,再补一份 openprd visual-compare . --board <parallel-board.json>;默认输出 JPG 到 .openprd/harness/visual-reviews/。查看合成图后继续对标或自检,直到没有明显视觉差异或意外漂移。',
|
|
2389
|
+
'发现可沉淀项时不要中途打断任务:代码扩展识别这类白名单工具补全会自动应用并记录;用户偏好、项目协作规矩和 OpenPrd 默认行为先记录为候选,收工时运行 openprd grow . --review 集中确认。',
|
|
1903
2390
|
'维护 OpenPrd 本身且涉及配置类能力时,先判断是否应纳入 openprd grow;高置信可成长默认纳入,不确定则主动询问用户。',
|
|
1904
2391
|
'涉及后端、脚本、Agent、工具链、服务或数据处理变更时,把 CLI 与 API 视为同级接入面:同步检查命令入口、参数、输出契约、help/doctor/dry-run/status 与接口协议、返回结构、身份边界是否受影响,并更新 docs/basic/backend-structure.md 或明确写不适用原因。',
|
|
1905
2392
|
'声明实现就绪前,先运行 openprd standards . --verify 和 openprd run . --verify。',
|
|
@@ -1915,11 +2402,13 @@ function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
|
1915
2402
|
status.ok ? status.stdout : '',
|
|
1916
2403
|
next.ok ? next.stdout : '',
|
|
1917
2404
|
gateMessage,
|
|
2405
|
+
lightweightRequirementMessage(intent),
|
|
1918
2406
|
visualMockupMessage(intent),
|
|
1919
|
-
|
|
2407
|
+
largeUiVisualDirectionMessage(intent),
|
|
2408
|
+
requirementRoutingSummary(),
|
|
1920
2409
|
'OpenPrd 下一步只是建议。规划、分析、审查类请求保持只读;只有用户当前明确要求开发、深度调研、对标复刻或继续任务时才执行。',
|
|
1921
|
-
'代码修改完成后、最终回复前,针对本轮实际 touched code files 运行 openprd dev-check . <file
|
|
1922
|
-
'
|
|
2410
|
+
'代码修改完成后、最终回复前,针对本轮实际 touched code files 运行 openprd dev-check . <file...>;若出现需要关注的文件,最终回复必须以 **后续建议** 为标题,直接复用 dev-check 生成的 Markdown 表格,列出影响对象、关注程度、规模信号、预警原因、本次处理结果和后续建议,并按 🔴 → 🟠 → 🟡 排序;不要把“关注程度”列改写成纯 emoji,必须保留例如“🟠 中风险|建议优先关注”这类完整标签;如果你改写了“预警原因 / 本次处理结果 / 后续建议”,先用 `node scripts/dev-check-wrapup-copy.mjs --validate` 校验每格不超过 20 字;若报错,按提示缩短后重试。',
|
|
2411
|
+
'发现可沉淀项时不要中途打断任务:代码扩展识别这类白名单工具补全会自动应用并记录;用户偏好、项目协作规矩和 OpenPrd 默认行为先记录为候选,收工时运行 openprd grow . --review 集中确认。',
|
|
1923
2412
|
'维护 OpenPrd 本身且涉及配置类能力时,先判断是否应纳入 openprd grow;高置信可成长默认纳入,不确定则主动询问用户。',
|
|
1924
2413
|
'涉及后端、脚本、Agent、工具链、服务或数据处理变更时,把 CLI 与 API 视为同级接入面,并同步更新 docs/basic/backend-structure.md 或明确写不适用原因。',
|
|
1925
2414
|
'声明就绪前请验证 docs/basic 标准。',
|
|
@@ -1932,8 +2421,13 @@ function shouldInjectOpenPrdContext(payload) {
|
|
|
1932
2421
|
return false;
|
|
1933
2422
|
}
|
|
1934
2423
|
const intent = analyzePromptIntent(prompt);
|
|
2424
|
+
const hasProjectPathReference = /(?:^|[\s`"'(])(?:src|app|lib|server|scripts|test|tests|docs|skills|openprd|openspec)\/[^\s`"'()]+/i.test(prompt);
|
|
1935
2425
|
if (
|
|
1936
2426
|
intent.simpleConcrete
|
|
2427
|
+
&& !intent.visualMockupRequest
|
|
2428
|
+
&& !intent.largeUiChangeRequest
|
|
2429
|
+
&& !intent.continuationRequest
|
|
2430
|
+
&& !hasProjectPathReference
|
|
1937
2431
|
&& !intent.productCopyRequest
|
|
1938
2432
|
&& !intent.secretsRequest
|
|
1939
2433
|
&& !intent.weappValidationRequest
|
|
@@ -2042,6 +2536,7 @@ function runGateChecks(cwd, payload, risk) {
|
|
|
2042
2536
|
taskReady: runTaskReady,
|
|
2043
2537
|
workspaceReady: runWorkspaceReady,
|
|
2044
2538
|
summary: summarizeRunVerifyCheck(runParsed, run.stdout || run.stderr),
|
|
2539
|
+
workspaceAttention: runParsed?.workspaceAttention ?? null,
|
|
2045
2540
|
warnings: Array.isArray(runParsed?.warnings) ? runParsed.warnings : [],
|
|
2046
2541
|
errors: Array.isArray(runParsed?.errors) ? runParsed.errors : [],
|
|
2047
2542
|
details: [
|
|
@@ -2182,6 +2677,18 @@ function handle(eventName, cwd, payload) {
|
|
|
2182
2677
|
progress = null;
|
|
2183
2678
|
}
|
|
2184
2679
|
if (isBlockingRequirementGate(gate)) {
|
|
2680
|
+
if ((intent.confirmation || shortAffirmative) && progress?.nextStep === 'clarification-confirmation-required') {
|
|
2681
|
+
gate = holdRequirementGate(root, prompt, progress, sessionId, {
|
|
2682
|
+
status: 'clarification-confirmed',
|
|
2683
|
+
clarificationConfirmedAt: now(),
|
|
2684
|
+
clarificationConfirmationPreview: preview(prompt, 500),
|
|
2685
|
+
});
|
|
2686
|
+
progress = evaluateRequirementGateProgress(root, sessionId);
|
|
2687
|
+
appendEvent(root, { ...baseEvent, outcome: 'requirement-gate-clarification-confirmed' });
|
|
2688
|
+
recordRunHook(root, baseEvent, 'requirement-gate-clarification-confirmed');
|
|
2689
|
+
updateHookState(root, baseEvent);
|
|
2690
|
+
return allowHook(composeHookContext(root, intent, gate, progress, sessionId));
|
|
2691
|
+
}
|
|
2185
2692
|
if (intent.reviewDecision) {
|
|
2186
2693
|
const authorization = reviewActionAuthorizationFor(intent, progress, prompt);
|
|
2187
2694
|
gate = holdRequirementGate(root, prompt, progress, sessionId, {
|
|
@@ -2279,6 +2786,7 @@ function handle(eventName, cwd, payload) {
|
|
|
2279
2786
|
const reviewMark = parseReviewMarkCommand(commandText(payload));
|
|
2280
2787
|
const approvalPolicy = requirementApprovalPolicy(gate);
|
|
2281
2788
|
const silentRecord = reviewPolicyAllowsSilentRecord(approvalPolicy);
|
|
2789
|
+
const writePathMutation = isRequirementWritePathMutation(commandText(payload));
|
|
2282
2790
|
const reason = reviewMark
|
|
2283
2791
|
? [
|
|
2284
2792
|
silentRecord
|
|
@@ -2292,19 +2800,23 @@ function handle(eventName, cwd, payload) {
|
|
|
2292
2800
|
: 'Current review artifact has not been synthesized yet. Run openprd synthesize . --open first.',
|
|
2293
2801
|
silentRecord
|
|
2294
2802
|
? 'Do not mark any stale or different review artifact; only the exact current artifact is allowed.'
|
|
2295
|
-
: 'Implementation approval and review confirmation are different gates; do not treat "可以开做" or similar wording as permission to run openprd review --mark confirmed.',
|
|
2803
|
+
: 'Implementation approval and review confirmation are different gates; do not treat "可以开做", plain "请帮我实现/继续实现", or similar wording as permission to run openprd review --mark confirmed.',
|
|
2296
2804
|
].filter(Boolean).join('\n')
|
|
2297
2805
|
: [
|
|
2298
2806
|
'OpenPrd blocked a mutating action because the requirement gate is still active.',
|
|
2299
2807
|
progress?.reason || 'The requirement still needs PRD review, change generation, or task preparation.',
|
|
2300
2808
|
progress?.nextStep === 'implementation-ready'
|
|
2301
2809
|
? 'Do not edit implementation files until the user clearly asks to execute this reviewed requirement.'
|
|
2810
|
+
: writePathMutation && progress?.nextStep === 'clarification-confirmation-required'
|
|
2811
|
+
? 'Do not write requirement facts, classify, or synthesize yet. First summarize the requirement in chat using 需求判断 / 需求理解 / 功能范围 / 技术方案, prefer Markdown tables for 功能范围 and 技术方案, wait for the user to confirm that summary, then continue the requirement write path.'
|
|
2812
|
+
: writePathMutation && progress?.nextStep === 'prd-synthesis-required'
|
|
2813
|
+
? 'You may continue the requirement write path only within the confirmed summary: write back confirmed facts, classify if needed, synthesize the PRD, then proceed to review/change/tasks.'
|
|
2302
2814
|
: progress?.nextStep === 'review-recording-required'
|
|
2303
2815
|
? 'Do not edit implementation files yet. First record the exact current stable review artifact, then generate change and tasks.'
|
|
2304
2816
|
: 'Do not edit implementation files until the active approval policy is satisfied and the OpenPrd change has generated tasks.',
|
|
2305
2817
|
silentRecord
|
|
2306
|
-
? 'Decision-point order:
|
|
2307
|
-
: 'Decision-point order: clarify the requirement,
|
|
2818
|
+
? 'Decision-point order: because the user explicitly waived any confirmation stop, you may skip requirement-summary confirmation, write back requirement facts, synthesize the PRD, record the exact stable review artifact, generate the OpenPrd change, prepare the task breakdown, then implement within the confirmed scope.'
|
|
2819
|
+
: 'Decision-point order: clarify the requirement, summarize it in chat using 需求判断 / 需求理解 / 功能范围 / 技术方案, prefer Markdown tables for 功能范围 and 技术方案, wait for the user to confirm that requirement summary, write back only confirmed facts, synthesize the PRD, wait for a human review decision on the stable artifact, generate the OpenPrd change, prepare the task breakdown, then implement within the confirmed scope.',
|
|
2308
2820
|
].join('\n');
|
|
2309
2821
|
appendEvent(root, { ...baseEvent, outcome: 'blocked-requirement-intake' });
|
|
2310
2822
|
recordRunHook(root, baseEvent, 'blocked-requirement-intake');
|
|
@@ -2333,7 +2845,7 @@ function handle(eventName, cwd, payload) {
|
|
|
2333
2845
|
recordRunHook(root, baseEvent, 'allowed-medium-risk');
|
|
2334
2846
|
updateHookState(root, baseEvent);
|
|
2335
2847
|
recordTouchedFiles(root, payload);
|
|
2336
|
-
return allowHook('OpenPrd 检测到写入动作。本轮写入完成后、最终回复前,请针对实际 touched code files 运行 openprd dev-check . <file
|
|
2848
|
+
return allowHook('OpenPrd 检测到写入动作。本轮写入完成后、最终回复前,请针对实际 touched code files 运行 openprd dev-check . <file...>;如出现需要关注的文件,最终回复必须以 **后续建议** 为标题,直接复用 dev-check 生成的 Markdown 表格,说明影响对象、关注程度、规模信号、预警原因、本次处理结果和后续建议,并按 🔴 → 🟠 → 🟡 排序;不要把“关注程度”列改写成纯 emoji,必须保留例如“🟠 中风险|建议优先关注”这类完整标签;如果你改写了“预警原因 / 本次处理结果 / 后续建议”,先用 `node scripts/dev-check-wrapup-copy.mjs --validate` 校验每格不超过 20 字;若报错,按提示缩短后重试;如涉及界面视觉且已有参考效果图并进入实现阶段,阶段性完成后运行 openprd visual-compare . --reference <效果图> --actual <实现截图> 并查看 JPG 对比图;若局部细节更重要,再补 openprd visual-compare . --board <focus-board.json>;如无参考图但改动界面,确认已先截修改前截图,并在完成后运行 openprd visual-compare . --before <修改前截图> --after <修改后截图> 查看 JPG 自检图;若并行试了多个优化方向,再补 openprd visual-compare . --board <parallel-board.json>;发现可沉淀项时不要中途打断任务,代码扩展识别这类白名单工具补全会自动应用并记录,用户偏好、项目协作规矩和 OpenPrd 默认行为留到收工时用 openprd grow . --review 集中确认;维护 OpenPrd 本身且涉及配置类能力时,先判断是否应纳入 openprd grow;声明就绪前,请同步维护 docs/basic、文件说明书、文件夹 README,以及相关 OpenPrd change/task 状态;如果涉及后端、脚本、Agent、工具链、服务或数据处理变更,还要把 CLI 与 API 视为同级接入面并更新 docs/basic/backend-structure.md。');
|
|
2337
2849
|
}
|
|
2338
2850
|
return allowHook();
|
|
2339
2851
|
}
|
|
@@ -2363,13 +2875,23 @@ function handle(eventName, cwd, payload) {
|
|
|
2363
2875
|
const turnState = readTurnState(root);
|
|
2364
2876
|
const stopIntent = analyzePromptIntent(turnState.prompt || '');
|
|
2365
2877
|
const weappGate = readNamedGate(root, 'weapp', sessionId);
|
|
2366
|
-
if (weappGate?.active &&
|
|
2878
|
+
if (weappGate?.active && stopIntent.weappValidationRequest) {
|
|
2367
2879
|
return allowHook([
|
|
2368
|
-
'OpenPrd
|
|
2369
|
-
'
|
|
2370
|
-
'
|
|
2880
|
+
'OpenPrd 在本轮收工回顾里发现小程序运行态验证仍未完成。',
|
|
2881
|
+
'如果这次任务是用户明确要求的小程序实测、复现、截图、抓日志/网络,或你已经承诺提供运行态证据,请补齐本地运行态验证;补齐时默认沿用当前小程序运行态或开发者工具会话连续验证,不要为了验证自动重开应用;否则不要把普通代码改动默认升级成小程序实测。',
|
|
2882
|
+
'如果当前环境没有可用的小程序本地验证工具,请明确说明未完成运行态验证,不要假定工具已安装。',
|
|
2371
2883
|
].join('\n'));
|
|
2372
2884
|
}
|
|
2885
|
+
if (stopIntent.visualMockupRequest && (!Array.isArray(turnState.touchedFiles) || turnState.touchedFiles.length === 0)) {
|
|
2886
|
+
return allowHook([
|
|
2887
|
+
'OpenPrd 生图事实对齐提醒:如果这轮要汇报图片结果、失败或限流,请确保它来自一次实际的 `imagegen` 调用。',
|
|
2888
|
+
'未实际调用 `imagegen` 前,不要声称“生图限流”“生图失败”或“已经生成图片结果”;若本轮还没调用,请如实说明仍未开始或尚未完成生图。',
|
|
2889
|
+
].join('\n'));
|
|
2890
|
+
}
|
|
2891
|
+
const devCheckMessage = devCheckWrapUpMessage(root, turnState);
|
|
2892
|
+
if (devCheckMessage) {
|
|
2893
|
+
return allowHook(devCheckMessage);
|
|
2894
|
+
}
|
|
2373
2895
|
if (Array.isArray(turnState.touchedFiles) && turnState.touchedFiles.length > 0) {
|
|
2374
2896
|
const review = runOpenPrd(['quality', '.', '--learn', '--review', '--from', '.openprd/harness/turn-state.json', '--json'], root);
|
|
2375
2897
|
if (review.ok) {
|