@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.
Files changed (102) hide show
  1. package/.openprd/changes/openprd-control-plane-v020/.openprd.yaml +2 -0
  2. package/.openprd/changes/openprd-control-plane-v020/design.md +78 -0
  3. package/.openprd/changes/openprd-control-plane-v020/proposal.md +54 -0
  4. package/.openprd/changes/openprd-control-plane-v020/specs/agent-requirements/spec.md +16 -0
  5. package/.openprd/changes/openprd-control-plane-v020/task-events.jsonl +27 -0
  6. package/.openprd/changes/openprd-control-plane-v020/tasks-002.md +35 -0
  7. package/.openprd/changes/openprd-control-plane-v020/tasks.md +427 -0
  8. package/.openprd/changes/remove-session-tracking-humane-approval/.openprd.yaml +2 -0
  9. package/.openprd/changes/remove-session-tracking-humane-approval/design.md +52 -0
  10. package/.openprd/changes/remove-session-tracking-humane-approval/proposal.md +35 -0
  11. package/.openprd/changes/remove-session-tracking-humane-approval/specs/agent-requirements/spec.md +16 -0
  12. package/.openprd/changes/remove-session-tracking-humane-approval/task-events.jsonl +1 -0
  13. package/.openprd/changes/remove-session-tracking-humane-approval/tasks.md +170 -0
  14. package/.openprd/design/active/asset-spec.md +19 -14
  15. package/.openprd/design/active/direction-plan.md +19 -3
  16. package/.openprd/design/active/facts-sheet.md +16 -7
  17. package/.openprd/design/active/image-preflight.md +6 -5
  18. package/.openprd/design/active/review-studio-v020-directions/compare-plan.json +34 -0
  19. package/.openprd/design/active/review-studio-v020-directions/contact-sheet.jpg +0 -0
  20. package/.openprd/design/active/review-studio-v020-directions/crops/01.png +0 -0
  21. package/.openprd/design/active/review-studio-v020-directions/crops/02.png +0 -0
  22. package/.openprd/design/active/review-studio-v020-directions/crops/03.png +0 -0
  23. package/.openprd/design/active/review-studio-v020-directions/focus-board.template.json +69 -0
  24. package/.openprd/design/active/review-studio-v020-directions/parallel-board.template.json +45 -0
  25. package/.openprd/design/active/review-studio-v020-directions/reference-set.json +143 -0
  26. package/.openprd/design/active/review-studio-v020-directions/source.png +0 -0
  27. package/.openprd/design/active/selected-direction.md +23 -9
  28. package/.openprd/engagements/active/control-plane-architecture.json +203 -0
  29. package/.openprd/engagements/active/control-plane-intake.json +418 -0
  30. package/.openprd/engagements/active/prd.md +183 -119
  31. package/.openprd/engagements/active/review-presentation-v0018.json +176 -0
  32. package/.openprd/i18n-config.json +12 -0
  33. package/.openprd/ledger/events.jsonl +6 -0
  34. package/.openprd/ledger/heads/ebea1a71a9daa566f1c91b53.json +10 -0
  35. package/.openprd/manifest.json +21 -0
  36. package/AGENTS.md +7 -6
  37. package/README.md +31 -29
  38. package/README_EN.md +36 -39
  39. package/package.json +1 -1
  40. package/skills/openprd-frontend-design/SKILL.md +16 -0
  41. package/skills/openprd-harness/SKILL.md +7 -7
  42. package/skills/openprd-quality/SKILL.md +1 -1
  43. package/skills/openprd-requirement-intake/SKILL.md +1 -1
  44. package/skills/openprd-shared/SKILL.md +6 -6
  45. package/src/adapters/adapter-spi.js +193 -0
  46. package/src/adapters/capability-envelope.js +98 -0
  47. package/src/adapters/event-normalizer.js +55 -0
  48. package/src/adapters/index.js +4 -0
  49. package/src/adapters/install-safety.js +249 -0
  50. package/src/agent-canonical-content.js +4 -2
  51. package/src/agent-integration.js +169 -46
  52. package/src/cli/args.js +63 -4
  53. package/src/cli/gate-print.js +17 -0
  54. package/src/cli/quality-commands.js +18 -0
  55. package/src/cli/quality-print.js +10 -0
  56. package/src/cli/runtime-print.js +24 -0
  57. package/src/codex-hook-runner-template.mjs +129 -199
  58. package/src/codex-runtime.js +48 -5
  59. package/src/context/cache.js +245 -0
  60. package/src/context/compiler.js +438 -0
  61. package/src/context/constants.js +30 -0
  62. package/src/context/index.js +39 -0
  63. package/src/context/redaction.js +84 -0
  64. package/src/context/stable.js +69 -0
  65. package/src/context/telemetry.js +42 -0
  66. package/src/dev-standards.js +57 -0
  67. package/src/fleet.js +112 -95
  68. package/src/gates/index.js +2 -0
  69. package/src/gates/scoped-gates.js +256 -0
  70. package/src/gates/store.js +126 -0
  71. package/src/gates/workspace.js +41 -0
  72. package/src/html-artifacts.js +725 -28
  73. package/src/kernel/atomic-store.js +299 -0
  74. package/src/kernel/event-envelope.js +166 -0
  75. package/src/kernel/index.js +4 -0
  76. package/src/kernel/project-ledger.js +467 -0
  77. package/src/kernel/project-manifest.js +205 -0
  78. package/src/knowledge-v3/index.js +1 -0
  79. package/src/knowledge-v3/lifecycle.js +290 -0
  80. package/src/knowledge.js +14 -7
  81. package/src/openprd.js +71 -2
  82. package/src/review-model.js +413 -0
  83. package/src/review-presentation.js +1 -1
  84. package/src/run-harness.js +432 -38
  85. package/src/runtime/cli_runtime_README.md +28 -0
  86. package/src/runtime/errors.js +66 -0
  87. package/src/runtime/index.js +44 -0
  88. package/src/runtime/lane-schema.js +141 -0
  89. package/src/runtime/lane-store.js +279 -0
  90. package/src/runtime/task-runtime.js +449 -0
  91. package/src/runtime/workspace.js +179 -0
  92. package/src/runtime/write-set.js +206 -0
  93. package/src/session-binding.js +16 -3
  94. package/src/session-registry.js +59 -1
  95. package/src/upgrade/fleet-mutation.js +166 -0
  96. package/src/upgrade/fleet-transaction.js +398 -0
  97. package/src/upgrade/transaction-store.js +416 -0
  98. package/src/visual-compare-core.js +66 -27
  99. package/src/visual-compare.js +18 -12
  100. package/src/workspace-core.js +109 -7
  101. package/src/workspace-registry.js +39 -1
  102. 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
- return writeSessionRegistryEntry(root, sessionId, {
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: laneKind === 'automation' ? 'automation-safe-mode' : 'openprd-opt-in',
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 : latestSessionRegistryEntry(root, sessionId);
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 appendJsonLine(filePath, value) {
892
+ function appendJsonLineAtomic(filePath, value) {
916
893
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
917
- fs.appendFileSync(filePath, JSON.stringify(value) + '\n');
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, patch = {}) {
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 recordedAt = patch.recordedAt || now();
926
- const entry = {
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
- laneKind: patch.laneKind || 'requirement',
932
- tool: patch.tool || 'codex',
933
- threadId: patch.threadId || null,
934
- changeId: patch.changeId || null,
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: patch.updatedAt || recordedAt,
950
+ lastUpdatedAt: recordedAt,
950
951
  recordedAt,
951
952
  };
952
- appendJsonLine(sessionRegistryPath(), entry);
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 blocked a high-risk autonomy action: ${detection.title}.`,
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 必须包含 `unchanged`、`added`、`changed`、`removed` 四种 classDef,并等待用户明确确认后才能修改相关文件。',
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 gate: ' + status + '.',
3392
- '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.',
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
- '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.',
3396
- reviewPolicyAllowsSilentRecord(approvalPolicy)
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
- '步骤: 已有界面时用 Codex Computer Use 进入产品内对应功能并截当前真实界面;冷启动没有现有界面时,基于已确认 PRD、用户画像、第一版切片和视觉目标写设计 brief。brief 必须写清用途、受众、气质端点、约束和记忆点,3 个方向要分别说明审美主张和主动避开的模板味。然后按生图路由调用内置生图工具(Codex=`imagegen`/Image 2,Cursor=`GenerateImage`;都不可用时按 `.openprd/harness/image-generation-preference.json` 的用户已确认偏好,没有偏好先问用户)生成至少 3 个不同设计思想方向;把效果图横向拼成一张带 1/2/3 序号的大图,先作为候选效果图给用户评审,不要默认写入 `.openprd/harness/visual-reviews/` 当验收参考。',
3511
- '交互: 主动追问三件事:是否符合预期、是否纳入后续效果图/实现截图对比、是否按此继续后续实现。只有用户确认纳入后续对比或继续实现后,才把选定方向、整张图或其中子图整理成 reference-set 并写入 `.openprd/harness/visual-reviews/`;用户确认前,不进入大 UI 实现,也不要声称界面方案已定。',
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 回复规则: 只有当前真的还缺人来拍板时,才在 final answer 里停下来请求确认。停下请求确认时执行“一次确认合同”:把本条需求后续本来会出现的全部确认点合并成一段话一次性说清,并告诉用户只要回一句“确认,继续执行”,剩余环节会自动连续推进;同一条需求不允许出现第二个人工确认停顿。绝对不要让用户粘贴 digest、work unit、版本号或任何 openprd 命令——这些值 hook 会自动绑定,需要时由你自己填进命令。只要用户已经明确说了“认可并继续”,或已经确认了 mini-plan、范围边界、正式产品边界,就直接沿着已确认路径继续,不要再写“如果你认可”“确认的话我就继续”这类二次索取确认的话。面向用户时,不要直接抛 lane、change、tasks、review artifact、work unit、digest、worker shard、write-scope、worktree、内部版本号、命令或文件路径这些内部词;统一改说“需求确认稿”“本次调整”“后续任务”“继续落地”“完成后检查”。如果后面真的还需要额外授权,再用人话把会做什么、不会做什么、完成后怎么检查说清楚。如果当前仍在 L2 的首轮澄清或需求摘要确认阶段,不要写成“你回我一句我就开始实现”;只能承诺“我先整理需求摘要给你确认,确认后再继续”。如果当前 lane 已进入 silent-record 策略,只能说明用户已经明确说了“不需要进行任何确认”;单纯的“请帮我实现/继续实现”不能把 lane 切到 silent-record。';
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
- intro,
3540
- '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.',
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
- if (gate?.status === 'review-confirmation-authorized') {
3548
- return gate?.reviewActionAuthorization?.continueAfterReview
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 = isImplementationAdvanceIntent(intent)
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
- reviewPolicyAllowsSilentRecord(approvalPolicy)
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
- reviewPolicyAllowsSilentRecord(approvalPolicy)
3611
- ? '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.'
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
- ? '用户表达了继续实现的意图;但在 review 评审、change 和任务拆解走完之前,仍然阻断实现写入。'
3557
+ ? '用户表达了继续实现的意图;直接进入安全范围内的实现,reviewchange 和任务拆解由 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)可直接处理并事后说明,不打开正式 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。';
3565
+ return '需求类型由 $openprd-requirement-intake 按影响面、未知数、决策成本和验证成本判断:直接处理(L0)直接完成;现有功能优化(L1)用轻量 mini-plan;新功能/新流程方案(L2) Agent 在后台维护 requirement、PRDreviewchange 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
- largeUiVisualDirectionMessage(),
3676
- visualLocalAdjustmentMessage(),
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
- '大界面改动的效果图先当候选效果图;出图后主动确认是否符合预期、是否纳入后续效果图/实现截图对比、是否按此继续实现。只有确认后,才把选定方向、整张图或其中子图整理到 `.openprd/harness/visual-reviews/` 并进入大 UI 实现。',
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
- largeUiVisualDirectionMessage(),
3699
- visualLocalAdjustmentMessage(),
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
- '大界面改动的效果图先当候选效果图;出图后主动确认是否符合预期、是否纳入后续效果图/实现截图对比、是否按此继续实现。只有确认后,才把选定方向、整张图或其中子图整理到 `.openprd/harness/visual-reviews/` 并进入大 UI 实现。',
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: 'blocked-skill-visualization-gate' });
4310
- recordRunHook(root, baseEvent, 'blocked-skill-visualization-gate');
4252
+ appendEvent(root, { ...baseEvent, outcome: 'allowed-skill-visualization-advisory' });
4253
+ recordRunHook(root, baseEvent, 'allowed-skill-visualization-advisory');
4311
4254
  updateHookState(root, baseEvent);
4312
- return blockHook([
4313
- 'OpenPrd blocked a skill/AGENTS mutation because the visualization-confirmation gate is still active.',
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: 'blocked-high-risk-autonomy', autonomyRisk });
4346
- recordRunHook(root, baseEvent, 'blocked-high-risk-autonomy');
4286
+ appendEvent(root, { ...baseEvent, outcome: 'allowed-autonomy-advisory', autonomyRisk });
4287
+ recordRunHook(root, baseEvent, 'allowed-autonomy-advisory');
4347
4288
  updateHookState(root, baseEvent);
4348
- return blockHook(formatHighRiskAutonomyBlock(autonomyRisk));
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
- const requirementBlockStreak = bumpProcessGateBlockCount(root, 'requirement-intake');
4393
- if (requirementBlockStreak > PROCESS_GATE_FUSE_LIMIT) {
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
- return blockHook(reason);
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
- const designBlockStreak = bumpProcessGateBlockCount(root, 'frontend-design-preflight');
4467
- if (designBlockStreak > PROCESS_GATE_FUSE_LIMIT) {
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
- return blockHook([
4482
- 'OpenPrd blocked a frontend implementation write because the design-preflight contract is still incomplete.',
4483
- frontendTargets.length > 0 ? `Trying to edit: ${frontendTargets.join(', ')}.` : null,
4484
- 'Before touching implementation files, update the required files under `.openprd/design/active/` and remove every `待填写` placeholder. If something truly does not apply, write `不适用` or a clear gap note instead of leaving the template unchanged.',
4485
- ...designIssues.map((issue) => `- ${issue.file}: ${issue.reason}`),
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
- return blockHook(formatHighRiskGateBlock(gates));
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}.`);