@openprd/cli 0.1.10 → 0.1.18

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 (146) hide show
  1. package/.openprd/benchmarks/index.md +17 -15
  2. package/.openprd/benchmarks/sources.yaml +53 -1
  3. package/.openprd/changes/adaptive-project-framing/.openprd.yaml +2 -0
  4. package/.openprd/changes/adaptive-project-framing/design.md +47 -0
  5. package/.openprd/changes/adaptive-project-framing/proposal.md +35 -0
  6. package/.openprd/changes/adaptive-project-framing/specs/agent-requirements/spec.md +16 -0
  7. package/.openprd/changes/adaptive-project-framing/task-events.jsonl +20 -0
  8. package/.openprd/changes/adaptive-project-framing/tasks.md +340 -0
  9. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/.openprd.yaml +2 -0
  10. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/design.md +34 -0
  11. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/proposal.md +23 -0
  12. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/specs/agent-requirements/spec.md +16 -0
  13. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/task-events.jsonl +14 -0
  14. package/.openprd/changes/docs-anti-pollution-and-design-direction-review/tasks.md +238 -0
  15. package/.openprd/changes/loop-isolated-worktree-commit/.openprd.yaml +2 -0
  16. package/.openprd/changes/loop-isolated-worktree-commit/design.md +53 -0
  17. package/.openprd/changes/loop-isolated-worktree-commit/proposal.md +38 -0
  18. package/.openprd/changes/loop-isolated-worktree-commit/specs/agent-requirements/spec.md +16 -0
  19. package/.openprd/changes/loop-isolated-worktree-commit/task-events.jsonl +22 -0
  20. package/.openprd/changes/loop-isolated-worktree-commit/tasks.md +357 -0
  21. package/.openprd/changes/openprd/.openprd.yaml +2 -0
  22. package/.openprd/changes/openprd/design.md +46 -0
  23. package/.openprd/changes/openprd/proposal.md +36 -0
  24. package/.openprd/changes/openprd/specs/agent-requirements/spec.md +16 -0
  25. package/.openprd/changes/openprd/task-events.jsonl +23 -0
  26. package/.openprd/changes/openprd/tasks.md +242 -0
  27. package/.openprd/changes/pm-human-in-the-loop/.openprd.yaml +2 -0
  28. package/.openprd/changes/pm-human-in-the-loop/design.md +38 -0
  29. package/.openprd/changes/pm-human-in-the-loop/proposal.md +29 -0
  30. package/.openprd/changes/pm-human-in-the-loop/specs/agent-requirements/spec.md +16 -0
  31. package/.openprd/changes/pm-human-in-the-loop/task-events.jsonl +18 -0
  32. package/.openprd/changes/pm-human-in-the-loop/tasks.md +306 -0
  33. package/.openprd/changes/test-strategy-router/.openprd.yaml +2 -0
  34. package/.openprd/changes/test-strategy-router/design.md +65 -0
  35. package/.openprd/changes/test-strategy-router/proposal.md +45 -0
  36. package/.openprd/changes/test-strategy-router/specs/agent-requirements/spec.md +16 -0
  37. package/.openprd/changes/test-strategy-router/task-events.jsonl +1 -0
  38. package/.openprd/changes/test-strategy-router/tasks.md +132 -0
  39. package/.openprd/changes/verify-boundaries-and-historical-refresh/.openprd.yaml +2 -0
  40. package/.openprd/changes/verify-boundaries-and-historical-refresh/design.md +28 -0
  41. package/.openprd/changes/verify-boundaries-and-historical-refresh/proposal.md +23 -0
  42. package/.openprd/changes/verify-boundaries-and-historical-refresh/specs/agent-requirements/spec.md +16 -0
  43. package/.openprd/changes/verify-boundaries-and-historical-refresh/task-events.jsonl +11 -0
  44. package/.openprd/changes/verify-boundaries-and-historical-refresh/tasks.md +66 -0
  45. package/.openprd/changes/vibe-coding-dev-check/.openprd.yaml +2 -0
  46. package/.openprd/changes/vibe-coding-dev-check/design.md +37 -0
  47. package/.openprd/changes/vibe-coding-dev-check/proposal.md +28 -0
  48. package/.openprd/changes/vibe-coding-dev-check/specs/agent-requirements/spec.md +16 -0
  49. package/.openprd/changes/vibe-coding-dev-check/task-events.jsonl +18 -0
  50. package/.openprd/changes/vibe-coding-dev-check/tasks.md +306 -0
  51. package/.openprd/design/README.md +11 -6
  52. package/.openprd/design/README_EN.md +4 -1
  53. package/.openprd/design/active/asset-spec.md +6 -0
  54. package/.openprd/design/active/direction-plan.md +5 -5
  55. package/.openprd/design/active/selected-direction.md +2 -0
  56. package/.openprd/design/anti-slop.md +26 -1
  57. package/.openprd/design/assets/surface-presets.md +6 -0
  58. package/.openprd/design/checklists/ui-quality-gate.md +7 -0
  59. package/.openprd/design/lenses/frontend-lenses.md +28 -0
  60. package/.openprd/design/recipes/content-experience.md +8 -1
  61. package/.openprd/design/recipes/operational-tool.md +7 -0
  62. package/.openprd/design/recipes/product-launch.md +8 -1
  63. package/.openprd/design/templates/README.md +6 -3
  64. package/.openprd/design/templates/README_EN.md +6 -3
  65. package/.openprd/design/templates/ops-dashboard.html +8 -1
  66. package/.openprd/design/templates/product-launch.html +2 -1
  67. package/.openprd/design/themes/theme-catalog.json +23 -5
  68. package/.openprd/engagements/active/prd.md +103 -100
  69. package/.openprd/schema/prd.schema.yaml +15 -0
  70. package/.openprd/standards/config.json +7 -1
  71. package/.openprd/templates/base/intake.md +13 -0
  72. package/.openprd/templates/base/prd.md +15 -0
  73. package/AGENTS.md +14 -4
  74. package/README.md +107 -14
  75. package/README_EN.md +131 -13
  76. package/package.json +1 -1
  77. package/scripts/dev-check-wrapup-copy.mjs +46 -21
  78. package/skills/openprd-benchmark-router/SKILL.md +2 -1
  79. package/skills/openprd-diagram-review/SKILL.md +27 -2
  80. package/skills/openprd-diagram-review/references/explanation-svg-patterns.md +131 -0
  81. package/skills/openprd-discovery-loop/SKILL.md +2 -2
  82. package/skills/openprd-frontend-design/SKILL.md +44 -20
  83. package/skills/openprd-frontend-design/references/design-asset-contract.md +6 -0
  84. package/skills/openprd-frontend-design/references/direction-engine.md +29 -4
  85. package/skills/openprd-harness/SKILL.md +22 -16
  86. package/skills/openprd-quality/SKILL.md +10 -8
  87. package/skills/openprd-requirement-intake/SKILL.md +5 -3
  88. package/skills/openprd-requirement-intake/references/prd-template-lenses.md +2 -2
  89. package/skills/openprd-requirement-intake/references/startup-validation-lens.md +98 -13
  90. package/skills/openprd-router/SKILL.md +2 -1
  91. package/skills/openprd-shared/SKILL.md +27 -17
  92. package/skills/openprd-standards/SKILL.md +4 -2
  93. package/src/agent-canonical-content.js +808 -0
  94. package/src/agent-integration.js +259 -778
  95. package/src/brainstorm-artifacts.js +156 -8
  96. package/src/brainstorm-presentation.js +14 -4
  97. package/src/brainstorm.js +165 -2
  98. package/src/canvas-app.html.js +2910 -0
  99. package/src/canvas-i18n.js +274 -0
  100. package/src/canvas-workspace.js +2271 -0
  101. package/src/cli/args.js +72 -4
  102. package/src/cli/basic-print.js +3 -0
  103. package/src/cli/canvas-print.js +106 -0
  104. package/src/cli/doctor-print.js +20 -0
  105. package/src/cli/print.js +2 -0
  106. package/src/cli/quality-commands.js +222 -0
  107. package/src/cli/quality-print.js +37 -5
  108. package/src/cli/run-print.js +5 -0
  109. package/src/cli/workflow-print.js +3 -0
  110. package/src/codex-app-server-relay.js +251 -0
  111. package/src/codex-hook-runner-template.mjs +963 -65
  112. package/src/design-starter-support.js +2 -2
  113. package/src/design-starter.js +39 -3
  114. package/src/dev-standards.js +125 -10
  115. package/src/diagram-core.js +390 -171
  116. package/src/discovery.js +2 -8
  117. package/src/docs-compact.js +125 -0
  118. package/src/execution-strategy.js +1 -1
  119. package/src/fleet.js +9 -3
  120. package/src/guide.js +227 -0
  121. package/src/html-artifacts.js +825 -51
  122. package/src/knowledge.js +101 -20
  123. package/src/language-policy.js +63 -3
  124. package/src/learning-review.js +1 -1
  125. package/src/loop.js +1 -1
  126. package/src/openprd.js +68 -131
  127. package/src/openspec/change-validate.js +2 -2
  128. package/src/openspec/constants.js +6 -7
  129. package/src/openspec/execute.js +2 -2
  130. package/src/openspec/generate.js +318 -102
  131. package/src/openspec/migration.js +168 -0
  132. package/src/openspec/paths.js +1 -22
  133. package/src/prd-core.js +102 -3
  134. package/src/quality-html-artifact.js +30 -5
  135. package/src/quality-visual-review.js +257 -14
  136. package/src/quality.js +66 -18
  137. package/src/review-presentation.js +60 -0
  138. package/src/run-harness.js +41 -3
  139. package/src/session-evidence.js +363 -0
  140. package/src/standards.js +64 -0
  141. package/src/test-strategy.js +7 -4
  142. package/src/visual-compare-centering.js +622 -0
  143. package/src/visual-compare-core.js +1123 -0
  144. package/src/visual-compare.js +620 -698
  145. package/src/workspace-core.js +132 -4
  146. package/src/workspace-workflow.js +172 -10
@@ -157,8 +157,8 @@ function normalizeTheme(theme) {
157
157
  }
158
158
  return {
159
159
  ...theme,
160
- bodyFont: String(theme.bodyFont ?? '"Inter", "PingFang SC", sans-serif'),
161
- headlineFont: String(theme.headlineFont ?? '"Inter", "PingFang SC", sans-serif'),
160
+ bodyFont: String(theme.bodyFont ?? '"Avenir Next", "Noto Sans SC", "PingFang SC", sans-serif'),
161
+ headlineFont: String(theme.headlineFont ?? '"Avenir Next", "Noto Sans SC", "PingFang SC", sans-serif'),
162
162
  surfacePreset: String(theme.surfacePreset ?? 'paper-grid'),
163
163
  };
164
164
  }
@@ -41,6 +41,8 @@ const STARTER_CATALOG = [
41
41
  lens: 'editorial-contrast',
42
42
  theme: 'warm-editorial',
43
43
  layout: 'story-map',
44
+ aesthetic: '温暖编辑式内容首页,用标题字体、图文节奏和章节停顿建立可长期阅读的气质。',
45
+ memoryPoint: '第一眼记住清楚的主题入口和像杂志目录一样的内容节奏。',
44
46
  components: ['editorial-hero', 'stat-row', 'story-section', 'feature-grid', 'timeline', 'cta-banner'],
45
47
  },
46
48
  directions: [
@@ -50,6 +52,8 @@ const STARTER_CATALOG = [
50
52
  lens: 'editorial-contrast',
51
53
  theme: 'warm-editorial',
52
54
  layout: 'story-map',
55
+ aesthetic: '温暖编辑感,靠标题字体和章节节奏组织内容。',
56
+ memoryPoint: '像一本打开的专题目录,用户知道从哪里开始读。',
53
57
  suitable: '长期写作首页、专题内容页、个人博客首页',
54
58
  risk: '如果信息密度控制不好,首屏会显得偏满。',
55
59
  },
@@ -59,6 +63,8 @@ const STARTER_CATALOG = [
59
63
  lens: 'catalog-clarity',
60
64
  theme: 'quiet-sand',
61
65
  layout: 'editorial-rail',
66
+ aesthetic: '克制资料库感,靠清楚分类和可比较信息单元建立秩序。',
67
+ memoryPoint: '用户能快速回到分类、筛选和上一次浏览位置。',
62
68
  suitable: '知识整理页、文章归档页、资源导航页',
63
69
  risk: '如果目录层级太重,会削弱阅读感。',
64
70
  },
@@ -68,6 +74,8 @@ const STARTER_CATALOG = [
68
74
  lens: 'immersive-showcase',
69
75
  theme: 'deep-launch',
70
76
  layout: 'story-map',
77
+ aesthetic: '沉浸展示感,靠强主视觉和少量文字信号制造记忆。',
78
+ memoryPoint: '首屏真实场景或关键视觉成为页面锚点。',
71
79
  suitable: '需要更强个人风格的内容展示页',
72
80
  risk: '容易更像展示页而不是稳定阅读首页。',
73
81
  },
@@ -84,6 +92,8 @@ const STARTER_CATALOG = [
84
92
  lens: 'product-launch',
85
93
  theme: 'deep-launch',
86
94
  layout: 'product-spec-runway',
95
+ aesthetic: '暗色发布叙事,用产品状态、规格跑道和强对比动作建立发布感。',
96
+ memoryPoint: '用户记住一个明确承诺和一个可验证的产品状态。',
87
97
  components: ['spec-hero', 'stat-row', 'feature-grid', 'logo-wall', 'cta-banner'],
88
98
  },
89
99
  directions: [
@@ -93,6 +103,8 @@ const STARTER_CATALOG = [
93
103
  lens: 'product-launch',
94
104
  theme: 'deep-launch',
95
105
  layout: 'product-spec-runway',
106
+ aesthetic: '深色电影感发布页,强调新能力、证明材料和决策动作。',
107
+ memoryPoint: '一个强主视觉承载发布承诺,而不是一组泛泛功能卡片。',
96
108
  suitable: '正式发布页、版本更新页、关键能力介绍页',
97
109
  risk: '如果证据不足,会只剩氛围没有事实。',
98
110
  },
@@ -102,6 +114,8 @@ const STARTER_CATALOG = [
102
114
  lens: 'editorial-contrast',
103
115
  theme: 'warm-editorial',
104
116
  layout: 'pricing-and-proof',
117
+ aesthetic: '编辑型解释页,降低销售感,用证据、价格或适用对象帮助决策。',
118
+ memoryPoint: '用户记住“为什么现在值得用”和“适合谁”。',
105
119
  suitable: '要兼顾解释与决策的介绍页',
106
120
  risk: '可能不够强势,发布感会偏弱。',
107
121
  },
@@ -111,6 +125,8 @@ const STARTER_CATALOG = [
111
125
  lens: 'immersive-showcase',
112
126
  theme: 'deep-launch',
113
127
  layout: 'immersive-hero-stack',
128
+ aesthetic: '沉浸式产品故事,靠媒体层级和少量强文本拉开情绪。',
129
+ memoryPoint: '首屏像一段产品片头,但仍保留下一步动作。',
114
130
  suitable: '视觉驱动型发布叙事',
115
131
  risk: '容易把页面做成纯展示而削弱可扫描性。',
116
132
  },
@@ -127,6 +143,8 @@ const STARTER_CATALOG = [
127
143
  lens: 'operational-density',
128
144
  theme: 'tool-neutral',
129
145
  layout: 'ops-density-grid',
146
+ aesthetic: '安静工作台,用稳定密度、清楚状态和短路径体现专业感。',
147
+ memoryPoint: '用户记住这是一个能反复使用、快速扫描的控制台。',
130
148
  components: ['filter-rail', 'summary-panel', 'dense-table', 'settings-group'],
131
149
  },
132
150
  directions: [
@@ -136,6 +154,8 @@ const STARTER_CATALOG = [
136
154
  lens: 'operational-density',
137
155
  theme: 'tool-neutral',
138
156
  layout: 'ops-density-grid',
157
+ aesthetic: '克制工具感,靠密度、对齐、状态和控件层级让用户快速完成工作。',
158
+ memoryPoint: '首屏一眼知道待处理、关键指标和下一步动作。',
139
159
  suitable: '后台首页、运营看板、任务工作台',
140
160
  risk: '如果主次不清,扫描效率会下降。',
141
161
  },
@@ -145,6 +165,8 @@ const STARTER_CATALOG = [
145
165
  lens: 'catalog-clarity',
146
166
  theme: 'quiet-sand',
147
167
  layout: 'catalog-browser',
168
+ aesthetic: '资料浏览器感,靠分类、筛选和可比较对象减少重复判断。',
169
+ memoryPoint: '用户记住清楚的浏览结构和稳定的回看路径。',
148
170
  suitable: '对象浏览与筛选更重的系统首页',
149
171
  risk: '容易太像列表页,动作感不足。',
150
172
  },
@@ -154,6 +176,8 @@ const STARTER_CATALOG = [
154
176
  lens: 'operational-density',
155
177
  theme: 'deep-launch',
156
178
  layout: 'settings-split',
179
+ aesthetic: '深色控制中心感,适合高风险配置和集中监控。',
180
+ memoryPoint: '关键状态像仪表盘一样醒目,但操作仍克制。',
157
181
  suitable: '偏控制中心与配置中枢的场景',
158
182
  risk: '暗色高密度更考验层级控制。',
159
183
  },
@@ -269,6 +293,7 @@ function buildStarterContext(starter, defaults, options = {}) {
269
293
  noRealImages: Boolean(options.noRealImages),
270
294
  themeConfig: null,
271
295
  imageBundle: null,
296
+ selectedDirection: starter.selectedDirection,
272
297
  };
273
298
  }
274
299
 
@@ -383,8 +408,12 @@ function buildGenericAssetSpec(context) {
383
408
  | logo | ${normalizePathForMarkdown(context.noBrandAssets ? '无外部 logo,优先用文字字标' : '待补品牌字标或官方 logo')} | ${context.noBrandAssets ? 'n/a' : '待确认'} | ${context.noBrandAssets ? 'not-needed' : 'defined'} | ${normalizePathForMarkdown(assetLine)} |
384
409
  | 产品图 | ${normalizePathForMarkdown(context.noBrandAssets ? '无' : '按任务需要补产品图')} | ${context.noBrandAssets ? 'n/a' : '待确认'} | ${context.noBrandAssets ? 'not-needed' : 'defined'} | ${normalizePathForMarkdown(imageLine)} |
385
410
  | UI 图 | ${normalizePathForMarkdown(context.starterId === 'ops-dashboard' ? '如需现有界面截图再补' : '无必需 UI 截图')} | ${context.starterId === 'ops-dashboard' ? '待确认' : 'n/a'} | ${context.starterId === 'ops-dashboard' ? 'defined' : 'not-needed'} | |
411
+ | 功能图标 | ${normalizePathForMarkdown('新入口/按钮/空态默认配图标;先复用项目已有图标体系,缺失时按最佳实践路由自动补')} | ${normalizePathForMarkdown('UI 图标 Phosphor;实现库 Lucide/Tabler/React Icons;AI 品牌 LobeHub;技术栈 Tech Icons')} | defined | 接入后把图标名与用途登记到本行备注,不等用户要求 |
386
412
  | 摄影 / 插图 | ${normalizePathForMarkdown(context.noRealImages ? '无' : '按任务需要补真实图片或插图')} | ${context.noRealImages ? 'n/a' : '待确认'} | ${context.noRealImages ? 'not-needed' : 'defined'} | ${normalizePathForMarkdown(imageLine)} |
387
413
  | 色板 / 字体 | ${normalizePathForMarkdown(`${context.defaults.theme ?? 'starter-default'} 对应的默认 tokens`)} | implementation | defined | 先锁定 starter 默认组合,再按页面细化 |
414
+ | 动效节奏 | ${normalizePathForMarkdown('一段关键 reveal / 状态反馈 / 滚动节奏,按审美主张控制复杂度')} | implementation | defined | 不到处散落无意义 hover |
415
+ | 背景 / 表面 | ${normalizePathForMarkdown(`${context.defaults.theme ?? 'starter-default'} 对应表面预设`)} | implementation | defined | 背景和纹理服务内容,不替代真实素材 |
416
+ | 构图记忆点 | ${normalizePathForMarkdown(context.selectedDirection?.memoryPoint ?? '按当前方向继续补记忆点')} | implementation | defined | 进入 Patch Mode 时优先保住 |
388
417
 
389
418
  ${fetchedImageRows.length > 0 ? `
390
419
  ## 已补真实图片
@@ -399,6 +428,8 @@ ${fetchedImageRows.join('\n')}
399
428
  - lens: ${context.defaults.lens ?? context.starterId}
400
429
  - theme: ${context.defaults.theme ?? '待补'}
401
430
  - layout: ${context.defaults.layout ?? '待补'}
431
+ - aesthetic: ${normalizePathForMarkdown(context.selectedDirection?.aesthetic ?? '待补')}
432
+ - memory-point: ${normalizePathForMarkdown(context.selectedDirection?.memoryPoint ?? '待补')}
402
433
  ${imageBundle?.succeeded ? `- image-query: ${normalizePathForMarkdown(imageBundle.query)}\n- image-manifest: ${normalizePathForMarkdown(imageBundle.relativeManifestPath ?? '待补')}` : ''}
403
434
  `;
404
435
  }
@@ -456,11 +487,11 @@ function buildGenericImagePreflight(context) {
456
487
  }
457
488
 
458
489
  function buildGenericDirectionPlan(starter, context) {
459
- const lines = starter.directions.map((direction) => `| ${direction.label} | ${direction.logic} | ${direction.lens} | ${direction.theme} | ${direction.layout} | ${normalizePathForMarkdown(`${context.brief} 下刻意拉开气质和信息组织方式`)} | ${normalizePathForMarkdown(direction.suitable)} | ${normalizePathForMarkdown(direction.risk)} |`);
490
+ const lines = starter.directions.map((direction) => `| ${direction.label} | ${direction.logic} | ${direction.lens} | ${direction.theme} | ${direction.layout} | ${normalizePathForMarkdown(direction.aesthetic ?? `${context.brief} 下刻意拉开气质和信息组织方式`)} | ${normalizePathForMarkdown(direction.memoryPoint ?? '当前方向的首屏视觉锚点和任务路径')} | ${normalizePathForMarkdown(`${context.brief} 下刻意拉开气质和信息组织方式`)} | ${normalizePathForMarkdown(direction.suitable)} | ${normalizePathForMarkdown(direction.risk)} |`);
460
491
  return `# Direction Plan
461
492
 
462
- | 方向 | 生成逻辑 | lens | theme | layout | 为什么不同 | 适用场景 | 主要风险 |
463
- | --- | --- | --- | --- | --- | --- | --- | --- |
493
+ | 方向 | 生成逻辑 | lens | theme | layout | 审美主张 | 记忆点 | 为什么不同 | 适用场景 | 主要风险 |
494
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
464
495
  ${lines.join('\n')}
465
496
  `;
466
497
  }
@@ -474,11 +505,14 @@ function buildGenericSelectedDirection(starter) {
474
505
  - lens: ${selected.lens}
475
506
  - theme: ${selected.theme}
476
507
  - layout: ${selected.layout}
508
+ - aesthetic: ${normalizePathForMarkdown(selected.aesthetic ?? '当前方向需要在实现前补充审美主张')}
509
+ - memory-point: ${normalizePathForMarkdown(selected.memoryPoint ?? '当前方向需要在实现前补充记忆点')}
477
510
  - components:
478
511
  ${selected.components.map((component) => ` - ${component}`).join('\n')}
479
512
  - follow-up risks:
480
513
  - 进入实现时继续控制信息密度,避免局部突然换轨
481
514
  - 页面文案要保留当前任务语气,不要把模板示例话术原样带入交付
515
+ - 进入 Patch Mode 前用 anti-slop 自检,避免回到通用字体、紫白渐变或均匀卡片堆叠
482
516
  `;
483
517
  }
484
518
 
@@ -586,6 +620,8 @@ function buildBasicDocMap(projectRoot, starter, context, relativeOutputPath) {
586
620
  - 把最后一批必要的查事实、查图、读模板动作放在口头宣布之前做完;一旦已经说“开始覆盖入口文件”或“开始整页重写”,下一步必须是对 \`${relativeOutputPath}\` 或 sibling draft 的实际写入,不要继续只读扫描、压图或停在口头承诺;必要时 hook 会把这类非写入动作挡回去。
587
621
  - 主要区块固定为: ${sections.join('、')}。
588
622
  - 当前视觉方向锁定为 \`${context.defaults.lens ?? starter.id}\`,主题采用 \`${context.defaults.theme ?? 'starter-default'}\`,页面骨架采用 \`${context.defaults.layout ?? 'starter-layout'}\`。
623
+ - 当前审美主张和记忆点记录在 \`.openprd/design/active/selected-direction.md\`;后续样式细化必须保住这个气质,而不是回到通用模板。
624
+ - 进入样式细化前,用 \`.openprd/design/anti-slop.md\` 排除默认紫白渐变、通用字体栈、白底卡片堆叠和无语境装饰。
589
625
  - 优先使用稳定的信息层级、卡片、标签、导航锚点和纯文字表达;不通过花哨装饰替代真实结构。
590
626
 
591
627
  ## 交互规范
@@ -8,7 +8,8 @@ import { recordKnowledgeReviewSignal, reviewKnowledgeWorkspace } from './knowled
8
8
 
9
9
  const DEVELOPMENT_STANDARDS_CONFIG = cjoin('.openprd', 'standards', 'config.json');
10
10
  const DEV_CHECK_WRAPUP_COPY_SCRIPT = fileURLToPath(new URL('../scripts/dev-check-wrapup-copy.mjs', import.meta.url));
11
- const DEV_CHECK_WRAPUP_FIELDS = ['规模信号', '预警原因', '本次处理结果', '后续建议'];
11
+ const DEV_CHECK_WRAPUP_FIELDS_AUTO = ['源文件规模', '优化原因', '本次处理结果', '后续建议'];
12
+ const DEV_CHECK_WRAPUP_FIELDS_RECOMMEND = ['规模信号', '预警原因', '本次处理结果', '后续建议'];
12
13
  const CODE_FILE_EXTENSIONS = new Set([
13
14
  '.c',
14
15
  '.cc',
@@ -68,8 +69,25 @@ export const DEFAULT_DEVELOPMENT_STANDARDS = {
68
69
  attentionMax: 1500,
69
70
  appliesTo: 'agent-touched-code-files',
70
71
  },
72
+ autoRefactor: {
73
+ enabled: true,
74
+ },
71
75
  };
72
76
 
77
+ const UI_FILE_EXTENSIONS = new Set([
78
+ '.css',
79
+ '.html',
80
+ '.jsx',
81
+ '.scss',
82
+ '.svelte',
83
+ '.tsx',
84
+ '.vue',
85
+ '.wxml',
86
+ '.wxss',
87
+ ]);
88
+ const VISUAL_REVIEWS_DIR = cjoin('.openprd', 'harness', 'visual-reviews');
89
+ const VISUAL_EVIDENCE_FRESH_MS = 24 * 60 * 60 * 1000;
90
+
73
91
  function normalizePathForReport(value) {
74
92
  return String(value ?? '').split(path.sep).join('/');
75
93
  }
@@ -167,6 +185,7 @@ function normalizeLineConfig(config = {}) {
167
185
  exemptFilePatterns: [...EXEMPT_FILE_PATTERNS, ...customPatterns],
168
186
  growthEnabled: config?.growth?.enabled !== false,
169
187
  growthAutoApply: config?.growth?.autoApply,
188
+ autoRefactorEnabled: (config?.developmentStandards?.autoRefactor ?? config?.autoRefactor)?.enabled !== false,
170
189
  };
171
190
  }
172
191
 
@@ -212,13 +231,20 @@ function fileStatus(lineCount, lineConfig) {
212
231
  }
213
232
 
214
233
  function nextActionForStatus(status, lineConfig) {
234
+ const autoMode = lineConfig?.autoRefactorEnabled !== false;
215
235
  if (status === 'ok') {
216
236
  return `本轮没有显著维护风险;最终回复可简要说明已回顾本次改动文件。`;
217
237
  }
218
238
  if (status === 'attention') {
239
+ if (autoMode) {
240
+ return `本轮若为该文件新增了职责,直接按入口、状态、渲染或数据边界完成小步拆分(保持行为不变、测试通过);若只是小修,说明没有继续扩展职责即可。`;
241
+ }
219
242
  return `本轮只做当前目标相关的小范围改动,并说明没有继续扩展该文件职责。`;
220
243
  }
221
244
  if (status === 'warning') {
245
+ if (autoMode) {
246
+ return `本轮直接执行拆分:先确认现有测试可保住行为,再按职责边界拆出独立模块(高内聚低耦合、不丢任何功能),拆完重跑测试与 dev-check,并把拆分产出写进最终回复的“OpenPRD 自动优化报告”。`;
247
+ }
222
248
  return `先判断这次是否新增职责;如果新增了,优先拆分或解耦后再收尾;如果只是小修,说明暂不拆的原因和后续拆分建议。`;
223
249
  }
224
250
  if (status === 'exempt') {
@@ -321,9 +347,10 @@ function countDisplayChars(value) {
321
347
  return Array.from(String(value ?? '')).length;
322
348
  }
323
349
 
324
- function buildCompactWrapUpRows(files) {
350
+ function buildCompactWrapUpRows(files, options = {}) {
325
351
  const result = spawnSync(process.execPath, [DEV_CHECK_WRAPUP_COPY_SCRIPT], {
326
352
  input: JSON.stringify({
353
+ autoRefactor: Boolean(options.autoRefactor),
327
354
  files: files.map((file) => ({
328
355
  status: file.status,
329
356
  lineCount: file.lineCount,
@@ -351,8 +378,9 @@ function buildCompactWrapUpRows(files) {
351
378
  if (!rows || rows.length !== files.length) {
352
379
  throw new Error('Compact dev-check wrap-up copy returned an unexpected row count.');
353
380
  }
381
+ const expectedFields = options.autoRefactor ? DEV_CHECK_WRAPUP_FIELDS_AUTO : DEV_CHECK_WRAPUP_FIELDS_RECOMMEND;
354
382
  for (const row of rows) {
355
- for (const field of DEV_CHECK_WRAPUP_FIELDS) {
383
+ for (const field of expectedFields) {
356
384
  if (typeof row[field] !== 'string') {
357
385
  throw new Error(`Compact dev-check wrap-up copy is missing field: ${field}`);
358
386
  }
@@ -364,29 +392,78 @@ function buildCompactWrapUpRows(files) {
364
392
  return rows;
365
393
  }
366
394
 
367
- function buildDevCheckWrapUp(files) {
368
- const title = '后续建议';
369
- const columns = ['影响对象', '关注程度', '规模信号', '预警原因', '本次处理结果', '后续建议'];
395
+ function buildDevCheckWrapUp(files, lineConfig = {}) {
396
+ const autoMode = lineConfig?.autoRefactorEnabled !== false;
397
+ const title = autoMode ? 'OpenPRD 自动优化报告' : '后续建议';
398
+ const statusColumn = autoMode ? '问题级别' : '关注程度';
399
+ const columns = autoMode
400
+ ? ['影响对象', '问题级别', '源文件规模', '优化原因', '本次处理结果', '后续建议']
401
+ : ['影响对象', '关注程度', '规模信号', '预警原因', '本次处理结果', '后续建议'];
370
402
  const attentionFiles = files
371
403
  .filter((file) => ['attention', 'warning', 'error'].includes(file.status))
372
404
  .sort((left, right) => devCheckConcernRank(right) - devCheckConcernRank(left));
373
- const compactRows = attentionFiles.length > 0 ? buildCompactWrapUpRows(attentionFiles) : [];
405
+ const compactRows = attentionFiles.length > 0
406
+ ? buildCompactWrapUpRows(attentionFiles, { autoRefactor: autoMode })
407
+ : [];
374
408
  const rows = attentionFiles.map((file, index) => ({
375
409
  影响对象: file.path,
376
- 关注程度: devCheckStatusLabel(file.status),
410
+ [statusColumn]: devCheckStatusLabel(file.status),
377
411
  ...compactRows[index],
378
412
  }));
379
413
  const markdownTable = renderMarkdownTable(columns, rows);
414
+ const switchHint = autoMode
415
+ ? '自动代码结构优化当前默认开启。如果下次不需要 Agent 自动优化,回复“关闭自动优化”,Agent 会运行 `openprd dev-check . --auto-refactor off` 转为仅推荐。'
416
+ : '自动代码结构优化当前已关闭(仅推荐模式)。要恢复 Agent 自动优化,运行 `openprd dev-check . --auto-refactor on`。';
380
417
  return {
381
418
  required: rows.length > 0,
419
+ mode: autoMode ? 'auto-refactor' : 'recommend',
382
420
  reason: rows.length > 0
383
- ? '存在需要用户关注的影响对象;最终回复需要用“后续建议”说明本次处理结果和后续建议。'
421
+ ? (autoMode
422
+ ? '存在超行影响对象;本轮 Agent 应先按“本次处理结果”完成高内聚低耦合拆分(不丢功能、测试保住行为),再在最终回复用“OpenPRD 自动优化报告”呈现优化完成的结果,并附一句自动优化开关确认。'
423
+ : '存在需要用户关注的影响对象;最终回复需要用“后续建议”说明本次处理结果和后续建议。')
384
424
  : '本轮改动文件未触发影响范围提醒;最终回复可简要说明已完成改动对象回顾。',
385
425
  title,
386
426
  columns,
387
427
  rows,
388
428
  markdownTable,
389
429
  markdownBlock: markdownTable ? `**${title}**\n\n${markdownTable}` : '',
430
+ switchHint: rows.length > 0 ? switchHint : null,
431
+ };
432
+ }
433
+
434
+ async function buildVisualEvidenceReminder(projectRoot, files) {
435
+ const uiFiles = files
436
+ .filter((file) => file.status !== 'error' && UI_FILE_EXTENSIONS.has(path.extname(file.path).toLowerCase()))
437
+ .map((file) => file.path);
438
+ if (uiFiles.length === 0) {
439
+ return { required: false, uiFiles: [] };
440
+ }
441
+ const reviewsDir = cjoin(projectRoot, VISUAL_REVIEWS_DIR);
442
+ let freshEvidence = [];
443
+ const entries = await fs.readdir(reviewsDir).catch(() => []);
444
+ const now = Date.now();
445
+ for (const entry of entries) {
446
+ if (!/\.(jpe?g|png|webp)$/i.test(entry)) continue;
447
+ const stat = await fs.stat(path.join(reviewsDir, entry)).catch(() => null);
448
+ if (stat && now - stat.mtimeMs <= VISUAL_EVIDENCE_FRESH_MS) {
449
+ freshEvidence.push(normalizePathForReport(path.join(VISUAL_REVIEWS_DIR, entry)));
450
+ }
451
+ }
452
+ freshEvidence = freshEvidence.slice(-5);
453
+ const required = freshEvidence.length === 0;
454
+ return {
455
+ required,
456
+ uiFiles,
457
+ freshEvidence,
458
+ reason: required
459
+ ? '本轮触达界面文件,但最近 24 小时没有任何拼图证据板产出;用户可见变化需要视觉证据,不能只靠构建通过或单张截图。'
460
+ : '本轮触达界面文件,最近已有拼图证据板产出;确认它覆盖了本轮改动,并把证据图嵌入对话流。',
461
+ suggestedCommands: [
462
+ 'openprd visual-compare . --before <改前截图> --after <改后截图>',
463
+ 'openprd visual-compare . --board <alignment-board.json>(同构列表/卡片/网格/表格对齐,含容器轨道与内容槽位)',
464
+ 'openprd visual-compare . --board <centering-board.json>(单素材内部居中/视觉重心)',
465
+ ],
466
+ chatEmbedHint: '生成后把证据图用 `![视觉证据](<输出路径>)` 直接嵌入最终回复,让用户在对话流里看到对齐效果。',
390
467
  };
391
468
  }
392
469
 
@@ -526,6 +603,10 @@ export async function checkDevelopmentStandardsWorkspace(projectRoot, options =
526
603
  action: 'dev-check',
527
604
  projectRoot,
528
605
  enabled: lineConfig.enabled,
606
+ autoRefactor: {
607
+ enabled: lineConfig.autoRefactorEnabled,
608
+ mode: lineConfig.autoRefactorEnabled ? 'auto-refactor' : 'recommend',
609
+ },
529
610
  thresholds: {
530
611
  okMax: lineConfig.okMax,
531
612
  attentionMax: lineConfig.attentionMax,
@@ -538,8 +619,42 @@ export async function checkDevelopmentStandardsWorkspace(projectRoot, options =
538
619
  warning: statusCounts.warning ?? 0,
539
620
  },
540
621
  files,
541
- wrapUp: buildDevCheckWrapUp(files),
622
+ wrapUp: buildDevCheckWrapUp(files, lineConfig),
623
+ visualEvidence: await buildVisualEvidenceReminder(projectRoot, files),
542
624
  knowledgeReview,
543
625
  errors,
544
626
  };
545
627
  }
628
+
629
+ export async function setDevCheckAutoRefactor(projectRoot, enabled) {
630
+ const configPath = cjoin(projectRoot, DEVELOPMENT_STANDARDS_CONFIG);
631
+ const config = (await exists(configPath)) ? await readJson(configPath).catch(() => ({})) : {};
632
+ const developmentStandards = config.developmentStandards && typeof config.developmentStandards === 'object'
633
+ ? config.developmentStandards
634
+ : {};
635
+ const autoRefactor = developmentStandards.autoRefactor && typeof developmentStandards.autoRefactor === 'object'
636
+ ? developmentStandards.autoRefactor
637
+ : {};
638
+ const next = {
639
+ ...config,
640
+ developmentStandards: {
641
+ ...developmentStandards,
642
+ autoRefactor: {
643
+ ...autoRefactor,
644
+ enabled: Boolean(enabled),
645
+ },
646
+ },
647
+ };
648
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
649
+ await fs.writeFile(configPath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
650
+ return {
651
+ ok: true,
652
+ action: 'dev-check-auto-refactor',
653
+ projectRoot,
654
+ configPath: normalizePathForReport(path.relative(projectRoot, configPath)),
655
+ autoRefactor: {
656
+ enabled: Boolean(enabled),
657
+ mode: enabled ? 'auto-refactor' : 'recommend',
658
+ },
659
+ };
660
+ }