@openprd/cli 0.1.19 → 0.1.22
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/changes/openprd-control-plane-v020/.openprd.yaml +2 -0
- package/.openprd/changes/openprd-control-plane-v020/design.md +78 -0
- package/.openprd/changes/openprd-control-plane-v020/proposal.md +54 -0
- package/.openprd/changes/openprd-control-plane-v020/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/openprd-control-plane-v020/task-events.jsonl +27 -0
- package/.openprd/changes/openprd-control-plane-v020/tasks-002.md +35 -0
- package/.openprd/changes/openprd-control-plane-v020/tasks.md +427 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/.openprd.yaml +2 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/design.md +52 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/proposal.md +35 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/specs/agent-requirements/spec.md +16 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/task-events.jsonl +1 -0
- package/.openprd/changes/remove-session-tracking-humane-approval/tasks.md +170 -0
- package/.openprd/design/active/asset-spec.md +19 -14
- package/.openprd/design/active/direction-plan.md +19 -3
- package/.openprd/design/active/facts-sheet.md +16 -7
- package/.openprd/design/active/image-preflight.md +6 -5
- package/.openprd/design/active/review-studio-v020-directions/compare-plan.json +34 -0
- package/.openprd/design/active/review-studio-v020-directions/contact-sheet.jpg +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/01.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/02.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/crops/03.png +0 -0
- package/.openprd/design/active/review-studio-v020-directions/focus-board.template.json +69 -0
- package/.openprd/design/active/review-studio-v020-directions/parallel-board.template.json +45 -0
- package/.openprd/design/active/review-studio-v020-directions/reference-set.json +143 -0
- package/.openprd/design/active/review-studio-v020-directions/source.png +0 -0
- package/.openprd/design/active/selected-direction.md +23 -9
- package/.openprd/engagements/active/control-plane-architecture.json +203 -0
- package/.openprd/engagements/active/control-plane-intake.json +418 -0
- package/.openprd/engagements/active/prd.md +183 -119
- package/.openprd/engagements/active/review-presentation-v0018.json +176 -0
- package/.openprd/i18n-config.json +12 -0
- package/.openprd/ledger/events.jsonl +6 -0
- package/.openprd/ledger/heads/ebea1a71a9daa566f1c91b53.json +10 -0
- package/.openprd/manifest.json +21 -0
- package/AGENTS.md +7 -6
- package/README.md +31 -29
- package/README_EN.md +36 -39
- package/package.json +1 -1
- package/skills/openprd-frontend-design/SKILL.md +16 -0
- package/skills/openprd-harness/SKILL.md +7 -7
- package/skills/openprd-quality/SKILL.md +1 -1
- package/skills/openprd-requirement-intake/SKILL.md +1 -1
- package/skills/openprd-shared/SKILL.md +6 -6
- package/src/adapters/adapter-spi.js +193 -0
- package/src/adapters/capability-envelope.js +98 -0
- package/src/adapters/event-normalizer.js +55 -0
- package/src/adapters/index.js +4 -0
- package/src/adapters/install-safety.js +249 -0
- package/src/agent-canonical-content.js +4 -2
- package/src/agent-integration.js +169 -46
- package/src/cli/args.js +63 -4
- package/src/cli/gate-print.js +17 -0
- package/src/cli/quality-commands.js +18 -0
- package/src/cli/quality-print.js +10 -0
- package/src/cli/runtime-print.js +24 -0
- package/src/codex-hook-runner-template.mjs +129 -199
- package/src/codex-runtime.js +48 -5
- package/src/context/cache.js +245 -0
- package/src/context/compiler.js +438 -0
- package/src/context/constants.js +30 -0
- package/src/context/index.js +39 -0
- package/src/context/redaction.js +84 -0
- package/src/context/stable.js +69 -0
- package/src/context/telemetry.js +42 -0
- package/src/dev-standards.js +57 -0
- package/src/fleet.js +112 -95
- package/src/gates/index.js +2 -0
- package/src/gates/scoped-gates.js +256 -0
- package/src/gates/store.js +126 -0
- package/src/gates/workspace.js +41 -0
- package/src/html-artifacts.js +725 -28
- package/src/kernel/atomic-store.js +299 -0
- package/src/kernel/event-envelope.js +166 -0
- package/src/kernel/index.js +4 -0
- package/src/kernel/project-ledger.js +467 -0
- package/src/kernel/project-manifest.js +205 -0
- package/src/knowledge-v3/index.js +1 -0
- package/src/knowledge-v3/lifecycle.js +290 -0
- package/src/knowledge.js +14 -7
- package/src/openprd.js +71 -2
- package/src/review-model.js +413 -0
- package/src/review-presentation.js +1 -1
- package/src/run-harness.js +432 -38
- package/src/runtime/cli_runtime_README.md +28 -0
- package/src/runtime/errors.js +66 -0
- package/src/runtime/index.js +44 -0
- package/src/runtime/lane-schema.js +141 -0
- package/src/runtime/lane-store.js +279 -0
- package/src/runtime/task-runtime.js +449 -0
- package/src/runtime/workspace.js +179 -0
- package/src/runtime/write-set.js +206 -0
- package/src/session-binding.js +16 -3
- package/src/session-registry.js +59 -1
- package/src/upgrade/fleet-mutation.js +166 -0
- package/src/upgrade/fleet-transaction.js +398 -0
- package/src/upgrade/transaction-store.js +416 -0
- package/src/visual-compare-core.js +66 -27
- package/src/visual-compare.js +18 -12
- package/src/workspace-core.js +109 -7
- package/src/workspace-registry.js +39 -1
- package/src/workspace-workflow.js +18 -15
|
@@ -765,45 +765,22 @@ function detectExecutionSignals(eventName, payload) {
|
|
|
765
765
|
};
|
|
766
766
|
}
|
|
767
767
|
|
|
768
|
-
function latestSessionRegistryEntry(root, sessionId) {
|
|
769
|
-
if (!sessionId) {
|
|
770
|
-
return null;
|
|
771
|
-
}
|
|
772
|
-
const filePath = sessionRegistryPath();
|
|
773
|
-
if (!fs.existsSync(filePath)) {
|
|
774
|
-
return null;
|
|
775
|
-
}
|
|
776
|
-
const workspaceRoot = path.resolve(root);
|
|
777
|
-
let latest = null;
|
|
778
|
-
for (const line of fs.readFileSync(filePath, 'utf8').split(/\r?\n/)) {
|
|
779
|
-
if (!line.trim()) {
|
|
780
|
-
continue;
|
|
781
|
-
}
|
|
782
|
-
try {
|
|
783
|
-
const entry = JSON.parse(line);
|
|
784
|
-
if (entry?.sessionId !== sessionId) {
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
const entryRoot = entry?.workspaceRoot ? path.resolve(String(entry.workspaceRoot)) : null;
|
|
788
|
-
if (entryRoot && entryRoot !== workspaceRoot) {
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
latest = entry;
|
|
792
|
-
} catch {}
|
|
793
|
-
}
|
|
794
|
-
return latest;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
768
|
function rememberAutomationMode(root, sessionId, payload, signals, laneKind) {
|
|
798
769
|
if (!sessionId) {
|
|
799
770
|
return null;
|
|
800
771
|
}
|
|
801
|
-
|
|
772
|
+
const gateStatus = laneKind === 'automation' ? 'automation-safe-mode' : 'openprd-opt-in';
|
|
773
|
+
const existing = readSessionBinding(root, sessionId);
|
|
774
|
+
// 每个 hook 事件都会经过这里;同一会话的 lane 记忆只需要落一次盘。
|
|
775
|
+
if (existing?.laneKind === laneKind && existing?.gateStatus === gateStatus) {
|
|
776
|
+
return existing;
|
|
777
|
+
}
|
|
778
|
+
return writeSessionBinding(root, sessionId, {
|
|
802
779
|
laneKind,
|
|
803
780
|
tool: 'openprd-hook',
|
|
804
781
|
promptPreview: preview(promptText(payload) || payloadText(payload), 500),
|
|
805
782
|
gateActive: false,
|
|
806
|
-
gateStatus
|
|
783
|
+
gateStatus,
|
|
807
784
|
title: laneKind === 'automation'
|
|
808
785
|
? 'Unattended automation bypassed OpenPrd'
|
|
809
786
|
: 'Unattended automation explicitly enabled OpenPrd',
|
|
@@ -820,7 +797,7 @@ function automationBypassDecision(root, eventName, payload, sessionId) {
|
|
|
820
797
|
if (signals.isAutomation) {
|
|
821
798
|
return { bypass: true, optIn: false, remembered: false, signals };
|
|
822
799
|
}
|
|
823
|
-
const remembered = eventName === 'UserPromptSubmit' ? null :
|
|
800
|
+
const remembered = eventName === 'UserPromptSubmit' ? null : readSessionBinding(root, sessionId);
|
|
824
801
|
if (remembered?.laneKind === 'automation' && remembered?.gateStatus === 'automation-safe-mode') {
|
|
825
802
|
return { bypass: true, optIn: false, remembered: true, signals };
|
|
826
803
|
}
|
|
@@ -912,44 +889,68 @@ function readSessionBinding(root, sessionId = null) {
|
|
|
912
889
|
return readJsonSync(sessionBindingPath(root, sessionId), null);
|
|
913
890
|
}
|
|
914
891
|
|
|
915
|
-
function
|
|
892
|
+
function appendJsonLineAtomic(filePath, value) {
|
|
916
893
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
917
|
-
fs.
|
|
894
|
+
const fd = fs.openSync(filePath, 'a', 0o600);
|
|
895
|
+
try {
|
|
896
|
+
fs.writeSync(fd, `${JSON.stringify(value)}\n`);
|
|
897
|
+
fs.fsyncSync(fd);
|
|
898
|
+
} finally {
|
|
899
|
+
fs.closeSync(fd);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function sessionRegistryProjection(entry = {}) {
|
|
904
|
+
return {
|
|
905
|
+
sessionId: entry.sessionId ?? null,
|
|
906
|
+
workspaceRoot: entry.workspaceRoot ?? null,
|
|
907
|
+
laneKind: entry.laneKind ?? 'requirement',
|
|
908
|
+
tool: entry.tool ?? 'codex',
|
|
909
|
+
threadId: entry.threadId ?? null,
|
|
910
|
+
changeId: entry.changeId ?? null,
|
|
911
|
+
taskHandle: entry.taskHandle ?? null,
|
|
912
|
+
workUnitId: entry.workUnitId ?? null,
|
|
913
|
+
versionId: entry.versionId ?? null,
|
|
914
|
+
digest: entry.digest ?? null,
|
|
915
|
+
title: entry.title ?? null,
|
|
916
|
+
targetRoot: entry.targetRoot ?? null,
|
|
917
|
+
promptPreview: entry.promptPreview ?? null,
|
|
918
|
+
reviewStatus: entry.reviewStatus ?? null,
|
|
919
|
+
gateStatus: entry.gateStatus ?? null,
|
|
920
|
+
gateActive: entry.gateActive === true,
|
|
921
|
+
};
|
|
918
922
|
}
|
|
919
923
|
|
|
920
|
-
function writeSessionRegistryEntry(root, sessionId,
|
|
924
|
+
function writeSessionRegistryEntry(root, sessionId, binding, previous = null) {
|
|
921
925
|
if (!sessionId) {
|
|
922
926
|
return null;
|
|
923
927
|
}
|
|
924
928
|
const workspaceRoot = path.resolve(root);
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
version: 1,
|
|
929
|
+
const projection = sessionRegistryProjection({
|
|
930
|
+
...binding,
|
|
928
931
|
sessionId,
|
|
929
932
|
workspaceRoot,
|
|
933
|
+
});
|
|
934
|
+
const previousProjection = previous
|
|
935
|
+
? sessionRegistryProjection({ ...previous, sessionId, workspaceRoot })
|
|
936
|
+
: null;
|
|
937
|
+
if (previousProjection && JSON.stringify(previousProjection) === JSON.stringify(projection)) {
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
const recordedAt = binding.updatedAt || now();
|
|
941
|
+
const entry = {
|
|
942
|
+
version: 1,
|
|
943
|
+
...projection,
|
|
930
944
|
realpath: workspaceRoot,
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
taskHandle: patch.taskHandle || null,
|
|
936
|
-
workUnitId: patch.workUnitId || null,
|
|
937
|
-
versionId: patch.versionId || null,
|
|
938
|
-
digest: patch.digest || null,
|
|
939
|
-
title: patch.title || null,
|
|
940
|
-
targetRoot: patch.targetRoot ? path.resolve(patch.targetRoot) : null,
|
|
941
|
-
promptPreview: patch.promptPreview || null,
|
|
942
|
-
reviewStatus: patch.reviewStatus || null,
|
|
943
|
-
gateStatus: patch.gateStatus || null,
|
|
944
|
-
gateActive: patch.gateActive === true,
|
|
945
|
-
statePath: patch.statePath || sessionStatePath(root, sessionId),
|
|
946
|
-
bindingPath: patch.bindingPath || sessionBindingPath(root, sessionId),
|
|
947
|
-
firstRegisteredAt: patch.firstRegisteredAt || recordedAt,
|
|
945
|
+
targetRoot: projection.targetRoot ? path.resolve(projection.targetRoot) : null,
|
|
946
|
+
statePath: sessionStatePath(root, sessionId),
|
|
947
|
+
bindingPath: sessionBindingPath(root, sessionId),
|
|
948
|
+
firstRegisteredAt: previous?.createdAt || binding.createdAt || recordedAt,
|
|
948
949
|
lastRegisteredAt: recordedAt,
|
|
949
|
-
lastUpdatedAt:
|
|
950
|
+
lastUpdatedAt: recordedAt,
|
|
950
951
|
recordedAt,
|
|
951
952
|
};
|
|
952
|
-
|
|
953
|
+
appendJsonLineAtomic(sessionRegistryPath(), entry);
|
|
953
954
|
return entry;
|
|
954
955
|
}
|
|
955
956
|
|
|
@@ -968,25 +969,7 @@ function writeSessionBinding(root, sessionId, patch = {}) {
|
|
|
968
969
|
updatedAt: patch.updatedAt ?? now(),
|
|
969
970
|
};
|
|
970
971
|
writeJsonSync(filePath, next);
|
|
971
|
-
writeSessionRegistryEntry(root, sessionId,
|
|
972
|
-
laneKind: patch.laneKind ?? previous?.laneKind ?? 'requirement',
|
|
973
|
-
tool: patch.tool ?? previous?.tool ?? 'codex',
|
|
974
|
-
threadId: patch.threadId ?? previous?.threadId ?? null,
|
|
975
|
-
changeId: patch.changeId ?? next.changeId ?? null,
|
|
976
|
-
taskHandle: patch.taskHandle ?? next.taskHandle ?? null,
|
|
977
|
-
workUnitId: patch.workUnitId ?? next.workUnitId ?? null,
|
|
978
|
-
versionId: patch.versionId ?? next.versionId ?? null,
|
|
979
|
-
digest: patch.digest ?? next.digest ?? null,
|
|
980
|
-
title: patch.title ?? next.title ?? null,
|
|
981
|
-
targetRoot: patch.targetRoot ?? next.targetRoot ?? null,
|
|
982
|
-
promptPreview: patch.promptPreview ?? next.promptPreview ?? null,
|
|
983
|
-
reviewStatus: patch.reviewStatus ?? next.reviewStatus ?? null,
|
|
984
|
-
gateStatus: patch.gateStatus ?? next.gateStatus ?? null,
|
|
985
|
-
gateActive: patch.gateActive ?? next.gateActive ?? false,
|
|
986
|
-
bindingPath: filePath,
|
|
987
|
-
statePath: sessionStatePath(root, sessionId),
|
|
988
|
-
updatedAt: next.updatedAt,
|
|
989
|
-
});
|
|
972
|
+
writeSessionRegistryEntry(root, sessionId, next, previous);
|
|
990
973
|
return next;
|
|
991
974
|
}
|
|
992
975
|
|
|
@@ -1561,6 +1544,9 @@ function knowledgeSkillContextLines(knowledgeSkills) {
|
|
|
1561
1544
|
}
|
|
1562
1545
|
|
|
1563
1546
|
function renderRunContextText(result) {
|
|
1547
|
+
if (String(result?.contextText || '').trim()) {
|
|
1548
|
+
return String(result.contextText).trim();
|
|
1549
|
+
}
|
|
1564
1550
|
const lines = [
|
|
1565
1551
|
'当前进展参考',
|
|
1566
1552
|
'当前项目: ' + result.projectRoot,
|
|
@@ -1938,6 +1924,8 @@ function analyzePromptIntent(prompt) {
|
|
|
1938
1924
|
const canvasCollaborationRequest = /(画布|canvas|excalidraw)/i.test(text)
|
|
1939
1925
|
&& /(标注|批注|协同|改图|回填|贴到画布|放到画布|插入画布|插到画布|画布评审|画布反馈|一起看|openprd\s*canvas|annotation)/i.test(text);
|
|
1940
1926
|
const visualReview = /效果图|实现截图|视觉对比|视觉评审|对标效果图|复刻|不一致|好丑|没对齐|对不上|不像/i.test(text);
|
|
1927
|
+
const largeUiVisualDirectionRequest = visualMockupRequest
|
|
1928
|
+
&& /(界面改动比较大|大界面|大改|改版|重做|重新设计|三种设计方向|3\s*种设计方向|多个设计方向|信息架构|核心布局|首屏|关键路径)/i.test(text);
|
|
1941
1929
|
const directBugfixExecution = explicitExecution && bugfixOrDiagnostic;
|
|
1942
1930
|
const newFeatureVerbMatched = /(新增|增加|新建)/.test(text);
|
|
1943
1931
|
const capabilityCreationMatched = capabilityCreationPatterns.some((pattern) => pattern.test(text));
|
|
@@ -2031,6 +2019,7 @@ function analyzePromptIntent(prompt) {
|
|
|
2031
2019
|
readOnly,
|
|
2032
2020
|
simpleConcrete,
|
|
2033
2021
|
visualMockupRequest,
|
|
2022
|
+
largeUiVisualDirectionRequest,
|
|
2034
2023
|
continuationRequest,
|
|
2035
2024
|
continuationSessionId,
|
|
2036
2025
|
continuationTaskHandle,
|
|
@@ -2719,11 +2708,10 @@ function detectHighRiskAutonomyMutation(root, payload, intent, risk) {
|
|
|
2719
2708
|
|
|
2720
2709
|
function formatHighRiskAutonomyBlock(detection) {
|
|
2721
2710
|
return [
|
|
2722
|
-
`OpenPrd
|
|
2723
|
-
'当前用户消息只授权了本轮实现范围,不等于授权 Agent 私自做云端热修复、生产远端写入、业务兜底、写死逻辑或缓存补行。',
|
|
2711
|
+
`OpenPrd detected an autonomy risk: ${detection.title}.`,
|
|
2724
2712
|
detection.details ? `Matched action: ${detection.details}` : null,
|
|
2725
|
-
'
|
|
2726
|
-
'
|
|
2713
|
+
'Agent 责任:优先改成配置、迁移、标准状态机、幂等脚本或可回滚发布路径并继续。OpenPrd 只提供风险提醒,不承担授权审批。',
|
|
2714
|
+
'OpenPrd 不阻断工具调用,也不要求用户确认或复述固定授权口令;是否需要确认由当前 Agent 依据宿主安全规则和真实上下文自行判断。',
|
|
2727
2715
|
].filter(Boolean).join('\n');
|
|
2728
2716
|
}
|
|
2729
2717
|
|
|
@@ -3057,7 +3045,7 @@ function skillVisualizationGateMessage(gate) {
|
|
|
3057
3045
|
return [
|
|
3058
3046
|
'OpenPrd skill/AGENTS 可视化确认门禁: active。',
|
|
3059
3047
|
'当前请求涉及 skill、SKILL.md、AGENTS.md 或相关 workflow 规则变更。编辑前必须先读取现状,输出彩色 Mermaid 方案图,再用简短文字说明新增、修改、保持不变和删除/阻断项。',
|
|
3060
|
-
'Mermaid
|
|
3048
|
+
'Mermaid 应包含 `unchanged`、`added`、`changed`、`removed` 四种 classDef,用于解释变更;它不是 OpenPrd 授权门禁,用户已提出修改时可继续执行。',
|
|
3061
3049
|
].join('\n');
|
|
3062
3050
|
}
|
|
3063
3051
|
|
|
@@ -3118,7 +3106,6 @@ function composeHookContext(root, intent = null, gate = null, progress = null, s
|
|
|
3118
3106
|
weappGateMessage(readNamedGate(root, 'weapp', sessionId)),
|
|
3119
3107
|
browserSafetyMessage(intent),
|
|
3120
3108
|
productCopyMessage(intent),
|
|
3121
|
-
largeUiVisualDirectionMessage(intent),
|
|
3122
3109
|
].filter(Boolean).join('\n');
|
|
3123
3110
|
}
|
|
3124
3111
|
|
|
@@ -3371,7 +3358,6 @@ function recordRunHook(cwd, baseEvent, outcome) {
|
|
|
3371
3358
|
|
|
3372
3359
|
function requirementGateMessage(intent, gate) {
|
|
3373
3360
|
const gateBlocksImplementation = isBlockingRequirementGate(gate);
|
|
3374
|
-
const approvalPolicy = requirementApprovalPolicy(gate);
|
|
3375
3361
|
if (!intent?.requiresIntake && !gateBlocksImplementation) {
|
|
3376
3362
|
return null;
|
|
3377
3363
|
}
|
|
@@ -3386,21 +3372,13 @@ function requirementGateMessage(intent, gate) {
|
|
|
3386
3372
|
'Before generation, do a quick anti-slop check: without brand or reference support, avoid default purple/blue-purple gradients, generic font taste, white card piles, and context-free decoration.',
|
|
3387
3373
|
].join('\n');
|
|
3388
3374
|
}
|
|
3389
|
-
const status = gateBlocksImplementation ? 'active' : 'opened';
|
|
3390
3375
|
return [
|
|
3391
|
-
'OpenPrd requirement intake
|
|
3392
|
-
'This prompt looks like a likely 新功能/新流程方案 (L2)
|
|
3376
|
+
'OpenPrd requirement intake runs in background and does not create a user approval stop.',
|
|
3377
|
+
'This prompt looks like a likely 新功能/新流程方案 (L2). Use $openprd-requirement-intake to classify it by impact, unknowns, decision cost, and validation cost while continuing the work the user requested.',
|
|
3393
3378
|
'Keep this mapping visible for internal review: 直接处理=L0, 现有功能优化=L1, 新功能/新流程方案=L2.',
|
|
3394
3379
|
'L0 and L1 stay on lightweight paths and should not be forced through formal PRD/review/change/tasks unless the scope expands.',
|
|
3395
|
-
'
|
|
3396
|
-
|
|
3397
|
-
? '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.'
|
|
3398
|
-
: 'Decision-point policy: first output a short structured requirement summary in chat with 需求判断 / 需求理解 / 功能范围 / 技术方案, where 功能范围 and 技术方案 should prefer Markdown tables; if this is still a 0-to-1 or worth-doing discussion, also surface the validation/startup loop with 第一批最容易触达的社区或种子用户、你为什么算这个社区里的自己人、当前替代方案和痛点证据、先怎么手工交付、手工作战卡怎么写、能不能先用 spreadsheet / 表单 / no-code 跑起来、什么承诺才算真需求、有没有 10 个样本和更强付费信号、最低成本先验证什么、达到什么条件才允许产品化、增长阶段守什么纪律、以及验证阶段怎样先活下来; 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.',
|
|
3399
|
-
reviewPolicyAllowsSilentRecord(approvalPolicy)
|
|
3400
|
-
? '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.'
|
|
3401
|
-
: 'Requirement-summary confirmation, review-artifact confirmation, and implementation authorization are different gates: do not treat "可以开做", "继续实现", plain "请帮我实现", or "不需要评审" as permission to skip them.',
|
|
3402
|
-
'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.',
|
|
3403
|
-
'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 技术方案, and if the ask still looks like 0-to-1 validation also add 第一批最容易触达的社区或种子用户、你为什么算这个社区里的自己人、当前替代方案和痛点证据、先怎么手工交付、手工作战卡怎么写、能不能先用 spreadsheet / 表单 / no-code 跑起来、什么承诺才算真需求、有没有 10 个样本和更强付费信号、最低成本先验证什么、达到什么条件才允许产品化、增长阶段守什么纪律、以及验证阶段怎样先活下来; 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.',
|
|
3380
|
+
'For L2, maintain PRD/review/change/tasks and the appropriate scene lens in the background. Do not block implementation files, do not ask the user to approve OpenPrd artifacts, and do not expose internal enum values unless necessary.',
|
|
3381
|
+
'Write back the best-supported facts with explicit source labels, synthesize the PRD, record the stable review artifact, generate the change, and prepare tasks as Agent-owned maintenance. If information is uncertain, choose a reversible default and state the assumption instead of turning OpenPrd into a confirmation gate.',
|
|
3404
3382
|
].join('\n');
|
|
3405
3383
|
}
|
|
3406
3384
|
|
|
@@ -3502,13 +3480,18 @@ function learningReviewMessage(intent = null) {
|
|
|
3502
3480
|
return lines.join('\n');
|
|
3503
3481
|
}
|
|
3504
3482
|
|
|
3505
|
-
function largeUiVisualDirectionMessage() {
|
|
3483
|
+
function largeUiVisualDirectionMessage(intent = null) {
|
|
3484
|
+
if (!intent?.largeUiVisualDirectionRequest) {
|
|
3485
|
+
return null;
|
|
3486
|
+
}
|
|
3506
3487
|
return [
|
|
3507
3488
|
'OpenPrd 大界面改动视觉方案评审:',
|
|
3508
3489
|
'位置: 需求分流之后、PRD 定稿或实现开工之前;它不同于 review.html,也不同于实现后的 visual-compare。',
|
|
3509
3490
|
'判断: 先从用户目标、信息架构变化、视觉决策成本和验收风险判断是否需要方向评审,不用关键词触发。会决定首屏、核心布局、主视觉、关键路径、组件层级/密度,或用户需要先选设计方向时触发。',
|
|
3510
|
-
'
|
|
3511
|
-
'
|
|
3491
|
+
'自动性: 用户不需要额外提出“请生图”。只要语义判断命中大界面改动或视觉决策成本高,就主动完成 3 方向生成;卡片宽度、间距、对齐、颜色、圆角、字号等轻量微调不触发。',
|
|
3492
|
+
'步骤: 已有界面时,Codex 原生 App 用 Computer Use,Codex 网页环境用 Chrome 插件的任务专用窗口,Cursor 用当前可用的浏览器或项目预览能力,进入对应功能并截当前真实界面。三个方向必须把同一张截图作为 image-to-image 参考;除非用户明确要求换风格,否则锁定现有字体角色、色彩系统、明暗模式、表面语言、圆角/边框/阴影、图标语言、平台框架和信息密度,只探索布局、信息层级、导航、关键路径和空间构图。冷启动没有现有界面时,基于已确认 PRD、用户画像、第一版切片和视觉目标写 design brief,不伪造修改前截图。brief 必须写清用途、受众、气质端点、约束和记忆点,3 个方向要分别说明审美主张和主动避开的模板味。然后按生图路由调用内置生图工具(Codex=`imagegen`/Image 2,Cursor=`GenerateImage`;都不可用时按 `.openprd/harness/image-generation-preference.json` 的用户已确认偏好,没有偏好先问用户)生成至少 3 个不同设计思想方向;把效果图横向拼成一张带 1/2/3 序号的大图,先作为候选效果图给用户评审,不要默认写入 `.openprd/harness/visual-reviews/` 当验收参考。',
|
|
3493
|
+
'质量: 每个方向都要从用途、用户、业务约束、核心动作和唯一记忆点出发,同时检查字体角色、色彩、空间构图、信息层级、表面/图标语言和动效含义;拒绝无依据蓝紫渐变、默认字体、等权白卡堆叠、过量大圆角、无语境装饰和只换颜色的伪方向。',
|
|
3494
|
+
'交互: 把三个方向直接给用户看,并说明 Agent 采用的可逆默认方向及原因;OpenPrd 不强制等待确认,也不阻断大 UI 实现。用户明确选择时再覆盖默认方向并更新 reference-set。',
|
|
3512
3495
|
'多对象参考处理: 如果候选效果图是一张图里包含多个子图、网格、多对象或多方向,不要拿整板直接硬比;先运行 `openprd visual-prepare . --reference <效果图> --grid <列>x<行>` 或 `--boxes <plan.json>`,检查 contact sheet,确认 reference-set 后再逐项进入实现和验收。',
|
|
3513
3496
|
].join('\n');
|
|
3514
3497
|
}
|
|
@@ -3525,69 +3508,30 @@ function visualLocalAdjustmentMessage() {
|
|
|
3525
3508
|
}
|
|
3526
3509
|
|
|
3527
3510
|
function codexConfirmationReplyRule() {
|
|
3528
|
-
return 'Codex 回复规则:
|
|
3511
|
+
return 'Codex 回复规则: OpenPrd 不创建用户确认停顿,不要求用户批准需求摘要、评审稿、change、tasks、设计合同、测试证据或风险分类,也不要求粘贴 digest、work unit、版本号或命令。Agent 在后台维护这些材料并继续用户要求的工作;是否因真实上下文需要向用户提问,只由当前 Agent 依据宿主安全规则和任务信息自行判断,不由 OpenPrd 强制。';
|
|
3529
3512
|
}
|
|
3530
3513
|
|
|
3531
3514
|
function confirmationGateMessage(gate) {
|
|
3532
3515
|
if (!gate || gate.active) {
|
|
3533
3516
|
return null;
|
|
3534
3517
|
}
|
|
3535
|
-
const intro = gate.status === 'execution-authorized'
|
|
3536
|
-
? 'OpenPrd requirement intake gate auto-authorized execution after review confirmation and task preparation because the original user request already asked to implement.'
|
|
3537
|
-
: 'OpenPrd requirement intake gate was explicitly confirmed by the user.';
|
|
3538
3518
|
return [
|
|
3539
|
-
|
|
3540
|
-
'
|
|
3519
|
+
'OpenPrd requirement materials are Agent-maintained background records and do not create a user approval stop.',
|
|
3520
|
+
'Continue the user-requested work while keeping docs, review records, tasks, and verification evidence accurate.',
|
|
3541
3521
|
'For UI or visual work with an existing reference image, first confirm the user has accepted that image as a later comparison reference; if one image contains multiple sub-images, grid cells, or objects, run openprd visual-prepare . --reference <effect-image> --grid <columns>x<rows> or --boxes <plan.json>, inspect the contact sheet, and use the generated reference-set / compare-plan before comparing. Then 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> --locale <zh-CN|en> 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> --locale <zh-CN|en>. If ordinary screenshots or Computer/Browser/Playwright checks are used as evidence, assemble them with the checked path and checkpoints through openprd visual-compare . --board <verification-board.json> --locale <zh-CN|en> before claiming visual completion. If the UI contains repeated homogeneous cards, lists, grids, or tables, or the user reports misalignment, capture the real screen, overlay guide lines, measure both container-track and internal content-slot x/y/size/baseline spread, and assemble an alignment-board through openprd visual-compare . --board <alignment-board.json> --locale <zh-CN|en>. If a single logo, icon, avatar, badge, button graphic, or image crop needs internal-centering or visual-centroid judgment, crop the target and assemble a centering-board through openprd visual-compare . --board <centering-board.json> --locale <zh-CN|en> before claiming it is centered.',
|
|
3542
3522
|
'If the user later says the implementation does not match the effect image, looks wrong, or asks for replication, do not rely on subjective narration alone; produce at least one visual evidence artifact before claiming completion.',
|
|
3543
3523
|
].join('\n');
|
|
3544
3524
|
}
|
|
3545
3525
|
|
|
3546
3526
|
function currentRequirementStatusLine(gate, progress) {
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
? '用户刚刚已经确认这版需求,并且明确表示继续。先记录这次确认结果,然后直接接着整理本次调整和后续任务;只有后面真的还缺额外授权时,再用人话说明影响和下一步。'
|
|
3550
|
-
: '用户刚刚已经确认这版需求;本回合只允许记录这次确认结果,不能把它直接扩展成开工授权。';
|
|
3551
|
-
}
|
|
3552
|
-
if (gate?.status === 'review-recording-authorized') {
|
|
3553
|
-
return '这版需求确认稿已经按免再次确认的规则授权记录;只记录这一次确认结果,然后继续整理本次调整和后续任务。';
|
|
3554
|
-
}
|
|
3555
|
-
switch (progress?.nextStep) {
|
|
3556
|
-
case 'clarification-confirmation-required':
|
|
3557
|
-
return '当前卡点: 先让用户确认当前需求摘要;在此之前不要把核心需求写成既定事实,也不要直接往后推进。请求确认时按“一次确认合同”表达:明确告诉用户这是本条需求默认唯一的一次人工确认,只要回复“确认,继续执行”,后面的需求稿整理、调整范围、任务拆解和实现都会自动连续推进,不会再逐环节找他确认。';
|
|
3558
|
-
case 'clarification-writeback-required':
|
|
3559
|
-
return '当前卡点: 用户已经确认这轮需求摘要或默认方案,但 current.json 里还没有把确认后的事实稳定写回。先按 canonical 字段路径用 user-confirmed 回写,再继续 synthesize;不要再回退成泛化 clarify,也不要继续写 agent-inferred。';
|
|
3560
|
-
case 'product-type-classification-required':
|
|
3561
|
-
return '当前卡点: 用户已经确认这轮需求摘要,但产品场景还没锁定。先完成 classify 并让当前 lane 的产品场景与写回事实一致,再继续 synthesize。';
|
|
3562
|
-
case 'requirement-interview-required':
|
|
3563
|
-
return '当前卡点: 用户已经确认这轮需求摘要,但 current.json 里还缺这轮 requirement 的关键事实。先补齐 canonical 字段,必要时再用 openprd interview 收敛剩余问题,然后继续 synthesize。';
|
|
3564
|
-
case 'prd-synthesis-required':
|
|
3565
|
-
return reviewPolicyAllowsSilentRecord(requirementApprovalPolicy(gate))
|
|
3566
|
-
? '当前卡点: 用户已明确表示不需要再停下来确认,可以直接整理已确认内容,并生成这版需求确认稿。'
|
|
3567
|
-
: '当前卡点: 当前需求摘要已确认,下一步把已确认内容整理成这版需求确认稿。';
|
|
3568
|
-
case 'prd-review-required':
|
|
3569
|
-
return progress?.review?.versionId
|
|
3570
|
-
? '当前卡点: 先等用户确认这版需求确认稿;不要把“继续做”“开落地吧”或单纯的“请帮我实现/继续实现”当成已经认可这版内容。请求确认时只需要用户回一句自然语言,例如“确认这版,继续执行”;版本、digest、work unit 由 hook 自动绑定,绝对不要让用户粘贴 digest、work unit 或任何 openprd 命令。同时说明这一句确认之后整条链会自动执行到实现,不会再停。'
|
|
3571
|
-
: '当前卡点: 先整理出这版需求确认稿,再等待用户确认。';
|
|
3572
|
-
case 'review-recording-required':
|
|
3573
|
-
return '当前卡点: 这版需求确认稿已经生成,而且用户已明确表示不需要再停下来确认;可以先记录这次确认结果,再继续后面的整理。';
|
|
3574
|
-
case 'change-generation-required':
|
|
3575
|
-
return '当前卡点: 这版需求已经确认,下一步先整理本次调整范围;这是当前流程的直接延续,不需要再重复索取同一轮确认。';
|
|
3576
|
-
case 'task-breakdown-required':
|
|
3577
|
-
return '当前卡点: 本次调整范围已经立起来了,下一步把它拆成可直接执行的后续任务。';
|
|
3578
|
-
case 'implementation-ready':
|
|
3579
|
-
return '当前卡点: 这版需求和后续任务都准备好了。如果用户一开始就明确要继续做,就直接进入实现;否则再用人话说明这次会做什么、不会做什么、完成后怎么检查。若用户刚刚确认的是 L1 范围边界或 mini-plan,承接话术要写成“已确认,我按这个继续”,不要再写成像二次索取确认的句子。';
|
|
3580
|
-
default:
|
|
3581
|
-
return '当前卡点: 继续按“澄清需求 -> 确认需求 -> 整理本次调整 -> 拆分后续任务 -> 开始实现”的顺序推进。';
|
|
3582
|
-
}
|
|
3527
|
+
const next = progress?.nextStep || gate?.status || 'continue';
|
|
3528
|
+
return `后台维护状态: ${next}。这是 Agent 的记录任务,不是用户卡点;继续用户要求的工作并自行补齐。`;
|
|
3583
3529
|
}
|
|
3584
3530
|
|
|
3585
3531
|
function currentRequirementMessage(intent, gate, progress) {
|
|
3586
3532
|
const prompt = stripMarkdown(gate?.promptPreview || '');
|
|
3587
3533
|
const approvalPolicy = requirementApprovalPolicy(gate);
|
|
3588
|
-
const gateStatus =
|
|
3589
|
-
? 'OpenPrd requirement intake gate was explicitly confirmed by the user.'
|
|
3590
|
-
: 'OpenPrd requirement intake gate: active.';
|
|
3534
|
+
const gateStatus = 'OpenPrd requirement intake runs in background and does not block the user-requested implementation.';
|
|
3591
3535
|
const lines = [
|
|
3592
3536
|
'OpenPrd 当前需求入口',
|
|
3593
3537
|
gateStatus,
|
|
@@ -3600,21 +3544,17 @@ function currentRequirementMessage(intent, gate, progress) {
|
|
|
3600
3544
|
gate?.intakeMode === 'deep-reflection'
|
|
3601
3545
|
? '需求入口: 先运行需求自省,再输出对话内澄清摘要或简短清单。'
|
|
3602
3546
|
: '需求入口: 先做轻量项目映射,再确认影响点和验收方式。',
|
|
3603
|
-
|
|
3604
|
-
? '当前 approval policy: decision-points / silent-record。之所以进入 silent-record,是因为用户已经明确表示不需要进行任何确认;单纯的“请帮我实现/继续实现”或“不要评审”都不够,仍然只能记录版本、digest、work unit 精确匹配的 artifact。'
|
|
3605
|
-
: '当前 approval policy: decision-points / human-review。当前 lane 仍需要一次 requirement 摘要确认和一次稳定 review artifact 的明确人类决策;单纯的“请帮我实现/继续实现”不算这两次决策。',
|
|
3547
|
+
'当前 approval policy: agent-maintained。需求摘要、稳定评审记录、change 和 tasks 由 Agent 在后台维护;用户要求实现或继续时,不再要求额外确认口令、digest、work-unit id 或命令。',
|
|
3606
3548
|
intent?.reviewContinuationRequested
|
|
3607
3549
|
? '这条消息同时表达了“确认当前稳定评审稿并继续当前 OpenPrd 下一步”的意图:先记录精确 review artifact,再继续当前 lane;如果 review 后 tasks 已就绪但还需要执行授权,立刻展示执行确认清单,不要停在“如果你要我继续”。'
|
|
3608
3550
|
: '',
|
|
3609
3551
|
currentRequirementStatusLine(gate, progress),
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
: 'Decision-point order: clarify the requirement, summarize it in chat using 需求判断 / 需求理解 / 功能范围 / 技术方案, prefer Markdown tables for 功能范围 and 技术方案, and if the ask still looks like 0-to-1 validation also surface 第一批最容易触达的社区或种子用户、你为什么算这个社区里的自己人、当前替代方案和痛点证据、先怎么手工交付、手工作战卡怎么写、能不能先用 spreadsheet / 表单 / no-code 跑起来、什么承诺才算真需求、有没有 10 个样本和更强付费信号、最低成本先验证什么、达到什么条件才允许产品化、增长阶段守什么纪律、以及验证阶段怎样先活下来; 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.',
|
|
3613
|
-
'Recommended next action: 先在 chat 输出“需求类型判断”,默认把路由码并进“需求类型:新功能/新流程方案(L2)”这类标签里;只有内部排障确实需要时,才额外写“内部路由码”。若为新功能/新流程方案(L2),再运行 openprd clarify .,并按“需求判断 / 需求理解 / 功能范围 / 技术方案”给出结构化摘要,其中“功能范围”和“技术方案”优先用 Markdown 表格;`需求判断` 和 `需求理解` 先用轻量主句说清这次是什么、核心问题和第一版目标,再把边界、风险、异常例子和技术细项下沉到后续分项或表格,不要把某条示例文案写成固定模板。如果这轮本质上还在判断值不值得做,还要主动补上:第一批最容易触达的社区或种子用户、当前替代方案、先怎么手工交付、什么承诺才算真需求、最低成本先验证什么,以及验证阶段怎样先活下来。请求确认后再写回 requirement 事实并继续 classify/synthesize,不要把这一步表述成“确认后直接开始实现”。Do not open clarification HTML; use review.html only after synthesize/review.',
|
|
3552
|
+
'Decision-point order: Agent 先吸收用户原始意图并继续当前安全范围内的工作,同时在后台补写需求事实、PRD、评审记录、change 和 tasks;材料不完整时记录缺口,不中断用户任务。',
|
|
3553
|
+
'Recommended next action: 用户要求实现、继续、修复、部署或发布时直接推进;需要的需求类型判断和结构化摘要由 Agent 自行整理,只有真实产品方向存在多个会明显改变结果的选择时才向用户提一个必要问题。',
|
|
3614
3554
|
];
|
|
3615
3555
|
if (isImplementationAdvanceIntent(intent)) {
|
|
3616
3556
|
lines.splice(2, 1, gate?.active
|
|
3617
|
-
? '
|
|
3557
|
+
? '用户表达了继续实现的意图;直接进入安全范围内的实现,review、change 和任务拆解由 Agent 在后台补齐。'
|
|
3618
3558
|
: '用户已确认当前需求可以进入执行范围;仍需保持实现范围与已确认的需求入口一致。');
|
|
3619
3559
|
}
|
|
3620
3560
|
lines.push(codexConfirmationReplyRule());
|
|
@@ -3622,7 +3562,7 @@ function currentRequirementMessage(intent, gate, progress) {
|
|
|
3622
3562
|
}
|
|
3623
3563
|
|
|
3624
3564
|
function requirementRoutingSummary() {
|
|
3625
|
-
return '需求类型由 $openprd-requirement-intake 按影响面、未知数、决策成本和验证成本判断:直接处理(L0)
|
|
3565
|
+
return '需求类型由 $openprd-requirement-intake 按影响面、未知数、决策成本和验证成本判断:直接处理(L0)直接完成;现有功能优化(L1)用轻量 mini-plan;新功能/新流程方案(L2)由 Agent 在后台维护 requirement、PRD、review、change 和 tasks。用户说“实现、继续、修复、部署、发布”就是当前目标范围内的有效执行意图,不再要求补确认口令或流程材料。';
|
|
3626
3566
|
}
|
|
3627
3567
|
|
|
3628
3568
|
function historicalRequirementReminder(root, runContext, intent, gate) {
|
|
@@ -3660,6 +3600,9 @@ function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
|
3660
3600
|
: runOpenPrdContext(cwd, intent?.promptText ?? null);
|
|
3661
3601
|
const effectiveProgress = progress ?? evaluateRequirementGateProgress(cwd);
|
|
3662
3602
|
const gateMessage = requirementGateMessage(intent, gate) || confirmationGateMessage(gate);
|
|
3603
|
+
const runText = String(run.stdout || '');
|
|
3604
|
+
const largeUiContext = runText.includes('OpenPrd 大界面改动视觉方案评审:') ? null : largeUiVisualDirectionMessage(intent);
|
|
3605
|
+
const visualAdjustmentContext = runText.includes('如果这轮验收重点在局部变化') ? null : visualLocalAdjustmentMessage();
|
|
3663
3606
|
if (run.ok) {
|
|
3664
3607
|
const separateCurrentRequirement = Boolean(intent?.requiresIntake || ((intent?.confirmation || intent?.reviewDecision) && gate?.promptPreview));
|
|
3665
3608
|
if (separateCurrentRequirement) {
|
|
@@ -3672,13 +3615,13 @@ function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
|
3672
3615
|
visualMockupMessage(intent, cwd),
|
|
3673
3616
|
canvasCollaborationMessage(intent),
|
|
3674
3617
|
learningReviewMessage(intent),
|
|
3675
|
-
|
|
3676
|
-
|
|
3618
|
+
largeUiContext,
|
|
3619
|
+
visualAdjustmentContext,
|
|
3677
3620
|
requirementRoutingSummary(),
|
|
3678
3621
|
'如果用户只是要求看看、规划、分析、审查、解释影响或列出文件,请保持只读并基于证据回答;不要运行 OpenPrd loop、任务推进、discovery 推进、commit 或其他写入命令。',
|
|
3679
3622
|
'只有当用户当前明确要求开发、实现、修复、继续任务、深度调研、对标复刻或提交时,才运行 openprd loop --run、openprd tasks --advance、openprd discovery --advance、commit/push 等执行命令。',
|
|
3680
3623
|
'代码修改完成后、最终回复前,针对本轮实际 touched code files 运行 openprd dev-check . <file...>;若出现需要关注的文件:自动优化开启(默认)时,先在本轮完成高内聚低耦合拆分(不丢功能、行为不变、拆完重跑测试),最终回复以 **OpenPRD 自动优化报告** 为标题直接复用 dev-check 生成的 Markdown 表格(列:影响对象、问题级别、源文件规模、优化原因、本次处理结果、后续建议),并附自动优化开关确认一句话;自动优化关闭时,以 **后续建议** 为标题复用表格(列:影响对象、关注程度、规模信号、预警原因、本次处理结果、后续建议)。两种模式都按 🔴 → 🟠 → 🟡 排序;不要把“问题级别/关注程度”列改写成纯 emoji,必须保留例如“🟠 中风险|建议优先关注”这类完整标签;如果你改写了表格文案,先用 `node scripts/dev-check-wrapup-copy.mjs --validate` 校验每格不超过 20 字;若报错,按提示缩短后重试。',
|
|
3681
|
-
'
|
|
3624
|
+
'大界面改动的效果图先当候选效果图;Agent 可选择最符合目标且可逆的默认方向继续,并说明假设。用户明确选择时再更新 `.openprd/harness/visual-reviews/`,OpenPrd 不强制确认停顿。',
|
|
3682
3625
|
'涉及界面、页面、视觉、样式或前端体验时,已有确认参考图才运行 openprd visual-compare . --reference <效果图> --actual <实现截图> --locale <zh-CN|en>;如果一张参考图里有多个子图/对象/网格,先运行 openprd visual-prepare . --reference <效果图> --grid <列>x<行> 或 --boxes <plan.json>,确认 contact sheet 后再逐项对比。没有明确参考图时先判断新建还是修改:新建界面走实现前 3 方向方案评审;修改既有界面则动手前先截修改前截图,完成后运行 openprd visual-compare . --before <修改前截图> --after <修改后截图> --locale <zh-CN|en>;如果并行试了多个优化方向,再补一份 openprd visual-compare . --board <parallel-board.json> --locale <zh-CN|en>。普通截图、Computer/Browser/Playwright 实测截图只能作为原始素材,收口前要补 openprd visual-compare . --board <verification-board.json> --locale <zh-CN|en> 的截图实测证据板。新开发或修改同构列表、卡片、网格、表格时,或用户反馈没对齐/排版漂移时,还要补 openprd visual-compare . --board <alignment-board.json> --locale <zh-CN|en> 的对齐辅助线证据板,同时量测容器轨道和标题/副标题/描述/标签/状态/价格/按钮/图标等内部内容槽位的 x/y/宽高/baseline spread。单个素材/图标/头像/徽标/按钮图形/图片内部居中、偏心或视觉重心判断时,还要补 openprd visual-compare . --board <centering-board.json> --locale <zh-CN|en> 的内部居中证据板,同时量测画布中心、主体外接框中心和视觉重心偏移。用户后续如果说“跟效果图”“不一致”“好丑”“复刻”,不能只口头说对比过了,至少产出一份 visual-compare / focus-board / parallel-board / verification-board / alignment-board / centering-board 证据图。',
|
|
3683
3626
|
'发现可沉淀项时不要中途打断任务:代码扩展识别这类白名单工具补全会自动应用并记录;用户偏好、项目协作规矩和 OpenPrd 默认行为先记录为候选,收工时运行 openprd grow . --review 集中确认。',
|
|
3684
3627
|
'维护 OpenPrd 本身且涉及配置类能力时,先判断是否应纳入 openprd grow;高置信可成长默认纳入,不确定则主动询问用户。',
|
|
@@ -3695,13 +3638,13 @@ function contextMessage(cwd, intent = null, gate = null, progress = null) {
|
|
|
3695
3638
|
visualMockupMessage(intent, cwd),
|
|
3696
3639
|
canvasCollaborationMessage(intent),
|
|
3697
3640
|
learningReviewMessage(intent),
|
|
3698
|
-
|
|
3699
|
-
|
|
3641
|
+
largeUiContext,
|
|
3642
|
+
visualAdjustmentContext,
|
|
3700
3643
|
requirementRoutingSummary(),
|
|
3701
3644
|
'如果用户只是要求看看、规划、分析、审查、解释影响或列出文件,请保持只读并基于证据回答;不要运行 OpenPrd loop、任务推进、discovery 推进、commit 或其他写入命令。',
|
|
3702
3645
|
'只有当用户当前明确要求开发、实现、修复、继续任务、深度调研、对标复刻或提交时,才运行 openprd loop --run、openprd tasks --advance、openprd discovery --advance、commit/push 等执行命令。',
|
|
3703
3646
|
'代码修改完成后、最终回复前,针对本轮实际 touched code files 运行 openprd dev-check . <file...>;若出现需要关注的文件:自动优化开启(默认)时,先在本轮完成高内聚低耦合拆分(不丢功能、行为不变、拆完重跑测试),最终回复以 **OpenPRD 自动优化报告** 为标题直接复用 dev-check 生成的 Markdown 表格(列:影响对象、问题级别、源文件规模、优化原因、本次处理结果、后续建议),并附自动优化开关确认一句话;自动优化关闭时,以 **后续建议** 为标题复用表格(列:影响对象、关注程度、规模信号、预警原因、本次处理结果、后续建议)。两种模式都按 🔴 → 🟠 → 🟡 排序;不要把“问题级别/关注程度”列改写成纯 emoji,必须保留例如“🟠 中风险|建议优先关注”这类完整标签;如果你改写了表格文案,先用 `node scripts/dev-check-wrapup-copy.mjs --validate` 校验每格不超过 20 字;若报错,按提示缩短后重试。',
|
|
3704
|
-
'
|
|
3647
|
+
'大界面改动的效果图先当候选效果图;Agent 可选择最符合目标且可逆的默认方向继续,并说明假设。用户明确选择时再更新 `.openprd/harness/visual-reviews/`,OpenPrd 不强制确认停顿。',
|
|
3705
3648
|
'涉及界面、页面、视觉、样式或前端体验时,已有确认参考图才运行 openprd visual-compare . --reference <效果图> --actual <实现截图> --locale <zh-CN|en>;如果一张参考图里有多个子图/对象/网格,先运行 openprd visual-prepare . --reference <效果图> --grid <列>x<行> 或 --boxes <plan.json>,确认 contact sheet 后再逐项对比。没有明确参考图时先判断新建还是修改:新建界面走实现前 3 方向方案评审;修改既有界面则动手前先截修改前截图,完成后运行 openprd visual-compare . --before <修改前截图> --after <修改后截图> --locale <zh-CN|en>;如果并行试了多个优化方向,再补一份 openprd visual-compare . --board <parallel-board.json> --locale <zh-CN|en>。普通截图、Computer/Browser/Playwright 实测截图只能作为原始素材,收口前要补 openprd visual-compare . --board <verification-board.json> --locale <zh-CN|en> 的截图实测证据板。新开发或修改同构列表、卡片、网格、表格时,或用户反馈没对齐/排版漂移时,还要补 openprd visual-compare . --board <alignment-board.json> --locale <zh-CN|en> 的对齐辅助线证据板,同时量测容器轨道和标题/副标题/描述/标签/状态/价格/按钮/图标等内部内容槽位的 x/y/宽高/baseline spread。单个素材/图标/头像/徽标/按钮图形/图片内部居中、偏心或视觉重心判断时,还要补 openprd visual-compare . --board <centering-board.json> --locale <zh-CN|en> 的内部居中证据板,同时量测画布中心、主体外接框中心和视觉重心偏移。用户后续如果说“跟效果图”“不一致”“好丑”“复刻”,不能只口头说对比过了,至少产出一份 visual-compare / focus-board / parallel-board / verification-board / alignment-board / centering-board 证据图。',
|
|
3706
3649
|
'发现可沉淀项时不要中途打断任务:代码扩展识别这类白名单工具补全会自动应用并记录;用户偏好、项目协作规矩和 OpenPrd 默认行为先记录为候选,收工时运行 openprd grow . --review 集中确认。',
|
|
3707
3650
|
'维护 OpenPrd 本身且涉及配置类能力时,先判断是否应纳入 openprd grow;高置信可成长默认纳入,不确定则主动询问用户。',
|
|
@@ -4306,13 +4249,11 @@ function handle(eventName, cwd, payload) {
|
|
|
4306
4249
|
|| isSilentRecordReviewMarkCommand(payload, gate, progress)
|
|
4307
4250
|
);
|
|
4308
4251
|
if (skillGate?.active && looksLikeSkillContractMutation(root, payload)) {
|
|
4309
|
-
appendEvent(root, { ...baseEvent, outcome: '
|
|
4310
|
-
recordRunHook(root, baseEvent, '
|
|
4252
|
+
appendEvent(root, { ...baseEvent, outcome: 'allowed-skill-visualization-advisory' });
|
|
4253
|
+
recordRunHook(root, baseEvent, 'allowed-skill-visualization-advisory');
|
|
4311
4254
|
updateHookState(root, baseEvent);
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
'Before editing SKILL.md, AGENTS.md, or related skill workflow files, first output a color-coded Mermaid plan, summarize added/changed/unchanged/removed items, then wait for explicit user confirmation.',
|
|
4315
|
-
].join('\n'));
|
|
4255
|
+
recordTouchedFiles(root, payload);
|
|
4256
|
+
return allowHook('OpenPrd skill visualization advisory: use a color-coded Mermaid plan to explain the change when useful, but do not block the user-requested edit or ask for OpenPrd-specific confirmation.');
|
|
4316
4257
|
}
|
|
4317
4258
|
if (researchGate?.active && isMutationPayload(payload, risk) && !authorizedReviewMutation) {
|
|
4318
4259
|
const researchBlockStreak = bumpProcessGateBlockCount(root, 'external-evidence');
|
|
@@ -4342,10 +4283,11 @@ function handle(eventName, cwd, payload) {
|
|
|
4342
4283
|
}
|
|
4343
4284
|
const autonomyRisk = detectHighRiskAutonomyMutation(root, payload, turnIntent, risk);
|
|
4344
4285
|
if (autonomyRisk && !hasExplicitAutonomyRiskApproval(turnIntent)) {
|
|
4345
|
-
appendEvent(root, { ...baseEvent, outcome: '
|
|
4346
|
-
recordRunHook(root, baseEvent, '
|
|
4286
|
+
appendEvent(root, { ...baseEvent, outcome: 'allowed-autonomy-advisory', autonomyRisk });
|
|
4287
|
+
recordRunHook(root, baseEvent, 'allowed-autonomy-advisory');
|
|
4347
4288
|
updateHookState(root, baseEvent);
|
|
4348
|
-
|
|
4289
|
+
recordTouchedFiles(root, payload);
|
|
4290
|
+
return allowHook(formatHighRiskAutonomyBlock(autonomyRisk));
|
|
4349
4291
|
}
|
|
4350
4292
|
if (isBlockingRequirementGate(gate) && isMutationPayload(payload, risk) && !isAllowedDuringRequirementGate(root, payload, gate, sessionId)) {
|
|
4351
4293
|
const reviewMark = parseReviewMarkCommand(commandText(payload));
|
|
@@ -4389,18 +4331,14 @@ function handle(eventName, cwd, payload) {
|
|
|
4389
4331
|
? '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.'
|
|
4390
4332
|
: 'Decision-point order: clarify the requirement, summarize it in chat using 需求判断 / 需求理解 / 功能范围 / 技术方案, prefer Markdown tables for 功能范围 and 技术方案, and if the ask still looks like 0-to-1 validation also surface 第一批最容易触达的社区或种子用户、你为什么算这个社区里的自己人、当前替代方案和痛点证据、先怎么手工交付、手工作战卡怎么写、能不能先用 spreadsheet / 表单 / no-code 跑起来、什么承诺才算真需求、有没有 10 个样本和更强付费信号、最低成本先验证什么、达到什么条件才允许产品化、增长阶段守什么纪律、以及验证阶段怎样先活下来; 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.',
|
|
4391
4333
|
].join('\n');
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
appendEvent(root, { ...baseEvent, outcome: 'requirement-gate-fuse-open' });
|
|
4395
|
-
recordRunHook(root, baseEvent, 'requirement-gate-fuse-open');
|
|
4396
|
-
updateHookState(root, baseEvent);
|
|
4397
|
-
recordTouchedFiles(root, payload);
|
|
4398
|
-
return allowHook(processGateFuseMessage('requirement-intake', requirementBlockStreak, reason));
|
|
4399
|
-
}
|
|
4400
|
-
appendEvent(root, { ...baseEvent, outcome: 'blocked-requirement-intake' });
|
|
4401
|
-
recordRunHook(root, baseEvent, 'blocked-requirement-intake');
|
|
4334
|
+
appendEvent(root, { ...baseEvent, outcome: 'allowed-requirement-background-maintenance' });
|
|
4335
|
+
recordRunHook(root, baseEvent, 'allowed-requirement-background-maintenance');
|
|
4402
4336
|
updateHookState(root, baseEvent);
|
|
4403
|
-
|
|
4337
|
+
recordTouchedFiles(root, payload);
|
|
4338
|
+
return allowHook([
|
|
4339
|
+
'OpenPrd requirement artifacts are incomplete, but this does not block the user-requested implementation.',
|
|
4340
|
+
'Continue the safe in-scope work now; maintain PRD/review/change/tasks in the background and report any remaining evidence gap at closeout.',
|
|
4341
|
+
].join('\n'));
|
|
4404
4342
|
}
|
|
4405
4343
|
let patchModeGate = syncPatchModeGate(root, payload, sessionId, turnIntent);
|
|
4406
4344
|
if (patchModeGate?.active) {
|
|
@@ -4463,27 +4401,14 @@ function handle(eventName, cwd, payload) {
|
|
|
4463
4401
|
const frontendTargets = frontendImplementationTargets(root, payload);
|
|
4464
4402
|
const designIssues = frontendDesignPreflightIssues(root, turnIntent, frontendTargets);
|
|
4465
4403
|
if (designIssues.length > 0) {
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
appendEvent(root, { ...baseEvent, outcome: 'frontend-design-gate-fuse-open' });
|
|
4469
|
-
recordRunHook(root, baseEvent, 'frontend-design-gate-fuse-open');
|
|
4470
|
-
updateHookState(root, baseEvent);
|
|
4471
|
-
recordTouchedFiles(root, payload);
|
|
4472
|
-
return allowHook(processGateFuseMessage(
|
|
4473
|
-
'frontend-design-preflight',
|
|
4474
|
-
designBlockStreak,
|
|
4475
|
-
designIssues.map((issue) => `- ${issue.file}: ${issue.reason}`).join('\n'),
|
|
4476
|
-
));
|
|
4477
|
-
}
|
|
4478
|
-
appendEvent(root, { ...baseEvent, outcome: 'blocked-frontend-design-preflight' });
|
|
4479
|
-
recordRunHook(root, baseEvent, 'blocked-frontend-design-preflight');
|
|
4404
|
+
appendEvent(root, { ...baseEvent, outcome: 'allowed-frontend-design-background-maintenance' });
|
|
4405
|
+
recordRunHook(root, baseEvent, 'allowed-frontend-design-background-maintenance');
|
|
4480
4406
|
updateHookState(root, baseEvent);
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
'
|
|
4485
|
-
|
|
4486
|
-
].filter(Boolean).join('\n'));
|
|
4407
|
+
recordTouchedFiles(root, payload);
|
|
4408
|
+
return allowHook([
|
|
4409
|
+
'OpenPrd design contracts are incomplete, but this does not block the requested frontend implementation.',
|
|
4410
|
+
'Continue the safe in-scope work and let the Agent maintain `.openprd/design/active/` in the background; do not ask the user to fill templates.',
|
|
4411
|
+
].join('\n'));
|
|
4487
4412
|
}
|
|
4488
4413
|
if (turnIntent.browserSafetyRequest && isHighRiskBrowserAction(payload)) {
|
|
4489
4414
|
appendEvent(root, { ...baseEvent, outcome: 'browser-safety-reminder' });
|
|
@@ -4497,7 +4422,12 @@ function handle(eventName, cwd, payload) {
|
|
|
4497
4422
|
recordRunHook(root, baseEvent, gates.ok ? 'allowed-high-risk' : 'blocked-high-risk');
|
|
4498
4423
|
updateHookState(root, baseEvent);
|
|
4499
4424
|
if (!gates.ok) {
|
|
4500
|
-
|
|
4425
|
+
recordTouchedFiles(root, payload);
|
|
4426
|
+
return allowHook([
|
|
4427
|
+
'OpenPrd release/readiness checks found incomplete evidence, but evidence debt does not block the user-requested operation by itself.',
|
|
4428
|
+
formatHighRiskGateBlock(gates),
|
|
4429
|
+
'Proceed within the user-authorized scope and report the incomplete checks accurately; do not ask the user to provide gate artifacts or fixed approval wording.',
|
|
4430
|
+
].join('\n'));
|
|
4501
4431
|
}
|
|
4502
4432
|
recordTouchedFiles(root, payload);
|
|
4503
4433
|
return allowHook(`OpenPrd high-risk gate passed: ${gates.summary}.`);
|