minimal-agent 0.8.2 → 0.10.0

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 (484) hide show
  1. package/README.md +14 -12
  2. package/package.json +34 -1
  3. package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
  4. package/plugins/cron-runner/commands/cron.md +26 -0
  5. package/plugins/cron-runner/commands/crons.md +7 -0
  6. package/plugins/cron-runner/plugin.js +34 -0
  7. package/plugins/cron-runner/src/commands.js +348 -0
  8. package/plugins/cron-runner/src/cronTime.js +148 -0
  9. package/plugins/cron-runner/src/lock.js +68 -0
  10. package/plugins/cron-runner/src/runs.js +89 -0
  11. package/plugins/cron-runner/src/scheduleEngine.js +72 -0
  12. package/plugins/cron-runner/src/scheduler.js +355 -0
  13. package/plugins/cron-runner/src/schema.js +140 -0
  14. package/plugins/cron-runner/src/service.js +167 -0
  15. package/plugins/cron-runner/src/store.js +298 -0
  16. package/plugins/cron-runner/src/types.js +1 -0
  17. package/plugins/workflow-runner/commands/workflow.md +4 -4
  18. package/plugins/workflow-runner/commands/workflows.md +1 -1
  19. package/plugins/workflow-runner/plugin.js +3 -1
  20. package/plugins/workflow-runner/src/agentCommon.js +249 -51
  21. package/plugins/workflow-runner/src/dslManifest.js +72 -0
  22. package/plugins/workflow-runner/src/expressions.js +198 -15
  23. package/plugins/workflow-runner/src/index.js +40 -8
  24. package/plugins/workflow-runner/src/jsonPayload.js +1 -1
  25. package/plugins/workflow-runner/src/loader.js +286 -72
  26. package/plugins/workflow-runner/src/multimodal.js +37 -0
  27. package/plugins/workflow-runner/src/recovery.js +49 -39
  28. package/plugins/workflow-runner/src/runner.js +802 -287
  29. package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
  30. package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
  31. package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
  32. package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
  33. package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
  34. package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
  35. package/plugins/workflow-runner/src/taskIdentity.js +296 -0
  36. package/plugins/workflow-runner/src/types.js +19 -9
  37. package/plugins/workflow-runner/src/workflowState.js +516 -33
  38. package/skills/adversarial-review/SKILL.md +0 -1
  39. package/skills/ai-image/SKILL.md +89 -0
  40. package/skills/ai-image/scripts/generate.mjs +497 -0
  41. package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
  42. package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
  43. package/skills/ai-image/scripts/generate_test.mjs +74 -0
  44. package/skills/browser-automation/SKILL.md +47 -0
  45. package/skills/docx/SKILL.md +25 -3
  46. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  47. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  48. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  49. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  50. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  51. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  52. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  53. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  54. package/skills/docx/scripts/opc_check.py +253 -0
  55. package/skills/image-gen-openrouter/SKILL.md +10 -2
  56. package/skills/image-stats/SKILL.md +65 -0
  57. package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
  58. package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
  59. package/skills/image-stats/scripts/image_stats.py +368 -0
  60. package/skills/image-stats/scripts/image_stats_test.py +181 -0
  61. package/skills/pptx/SKILL.md +123 -95
  62. package/skills/pptx/design.md +82 -0
  63. package/skills/pptx/examples/README.md +29 -0
  64. package/skills/pptx/examples/hybrid_example.mjs +107 -0
  65. package/skills/pptx/examples/hybrid_example.pptx +0 -0
  66. package/skills/pptx/pptxgenjs.md +41 -0
  67. package/skills/pptx/scripts/deck_helpers.mjs +224 -0
  68. package/skills/pptx/scripts/layout_lint.py +293 -0
  69. package/skills/pptx/scripts/layout_lint_test.py +160 -0
  70. package/skills/pptx/scripts/opc_check.py +339 -0
  71. package/skills/pptx/scripts/svg_fallback.mjs +145 -0
  72. package/skills/pptx/svg.md +67 -0
  73. package/skills/tts-zh/SKILL.md +19 -2
  74. package/skills/ui-design/SKILL.md +158 -0
  75. package/skills/ui-design/claude_design.md +309 -0
  76. package/skills/ui-design/evals/evals.json +254 -0
  77. package/skills/ui-design/references/aesthetic-direction.md +83 -0
  78. package/skills/ui-design/references/claude-design-protocol.md +36 -0
  79. package/skills/ui-design/references/design-context.md +53 -0
  80. package/skills/ui-design/references/design-execution-playbook.md +82 -0
  81. package/skills/ui-design/references/export-pptx.md +124 -0
  82. package/skills/ui-design/references/layout-composition.md +654 -0
  83. package/skills/ui-design/references/preview-feedback.md +37 -0
  84. package/skills/ui-design/references/source-image-layout.md +189 -0
  85. package/skills/ui-design/references/starter-components.md +78 -0
  86. package/skills/ui-design/references/vi-aegis-vector.md +144 -0
  87. package/skills/ui-design/references/vi-amber-prism.md +133 -0
  88. package/skills/ui-design/references/vi-aqua-glass.md +200 -0
  89. package/skills/ui-design/references/vi-comfort-vision.md +147 -0
  90. package/skills/ui-design/references/vi-ember-prism.md +140 -0
  91. package/skills/ui-design/references/vi-nebula-console.md +151 -0
  92. package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
  93. package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
  94. package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
  95. package/skills/ui-design/references/vi-precision-horizon.md +215 -0
  96. package/skills/ui-design/references/vi-solar-relay.md +130 -0
  97. package/skills/ui-design/references/vi-template-workflow.md +48 -0
  98. package/skills/ui-design/references/vi-verdant-grid.md +141 -0
  99. package/skills/ui-design/references/vi-violet-meridian.md +172 -0
  100. package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
  101. package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
  102. package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
  103. package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
  104. package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
  105. package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
  106. package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
  107. package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
  108. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
  109. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
  110. package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
  111. package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
  112. package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
  113. package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
  114. package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
  115. package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
  116. package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
  117. package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
  118. package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
  119. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
  120. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
  121. package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
  122. package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
  123. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
  124. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
  125. package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
  126. package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
  127. package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
  128. package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
  129. package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
  130. package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
  131. package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
  132. package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
  133. package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
  134. package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
  135. package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
  136. package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
  137. package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
  138. package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
  139. package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
  140. package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
  141. package/skills/ui-design/scripts/assemble_material.py +217 -0
  142. package/skills/ui-design/scripts/assemble_material_test.py +149 -0
  143. package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
  144. package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
  145. package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
  146. package/skills/ui-design/scripts/check_language_policy.py +351 -0
  147. package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
  148. package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
  149. package/skills/ui-design/scripts/deck_pages_check.py +158 -0
  150. package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
  151. package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
  152. package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
  153. package/skills/ui-design/scripts/design_budget_check.py +271 -0
  154. package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
  155. package/skills/ui-design/scripts/design_repair_hints.py +162 -0
  156. package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
  157. package/skills/ui-design/scripts/design_style_check.py +397 -0
  158. package/skills/ui-design/scripts/design_style_check_test.py +233 -0
  159. package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
  160. package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
  161. package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
  162. package/skills/ui-design/scripts/extract_source_assets.py +607 -0
  163. package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
  164. package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
  165. package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
  166. package/skills/ui-design/scripts/image_plan_lint.py +571 -0
  167. package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
  168. package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
  169. package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
  170. package/skills/ui-design/scripts/layout_balance.py +881 -0
  171. package/skills/ui-design/scripts/layout_balance_test.py +927 -0
  172. package/skills/ui-design/scripts/normalize_design_json.py +272 -0
  173. package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
  174. package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
  175. package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
  176. package/skills/ui-design/scripts/resolve_source_input.py +270 -0
  177. package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
  178. package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
  179. package/skills/ui-design/scripts/source_usage_check.py +581 -0
  180. package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
  181. package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
  182. package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
  183. package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
  184. package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
  185. package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
  186. package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
  187. package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
  188. package/skills/ui-design/tests/e2e/rubric.json +54 -0
  189. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
  190. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
  191. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
  192. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
  193. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
  194. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
  195. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
  196. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
  197. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
  198. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
  199. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
  200. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
  201. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  202. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  203. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  204. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  205. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  206. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  207. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
  208. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
  209. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
  210. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
  211. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
  212. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
  213. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
  214. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
  215. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
  216. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
  217. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
  218. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
  219. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
  220. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
  221. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
  222. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
  223. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
  224. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
  225. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
  226. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
  227. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
  228. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
  229. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
  230. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
  231. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
  232. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
  233. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
  234. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
  235. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  236. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  237. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  238. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  239. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  240. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  241. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
  242. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
  243. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
  244. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
  245. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
  246. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
  247. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
  248. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
  249. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
  250. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
  251. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
  252. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
  253. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
  254. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
  255. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
  256. package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
  257. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
  258. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
  259. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
  260. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
  261. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
  262. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
  263. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
  264. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
  265. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
  266. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
  267. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
  268. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
  269. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
  270. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
  271. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
  272. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
  273. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
  274. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
  275. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
  276. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
  277. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
  278. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
  279. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
  280. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
  281. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
  282. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
  283. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
  284. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
  285. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
  286. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
  287. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
  288. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
  289. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
  290. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
  291. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
  292. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
  293. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
  294. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
  295. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
  296. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
  297. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
  298. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
  299. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
  300. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
  301. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
  302. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
  303. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
  304. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
  305. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
  306. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
  307. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
  308. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
  309. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
  310. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
  311. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
  312. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
  313. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
  314. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
  315. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
  316. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
  317. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
  318. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
  319. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
  320. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
  321. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
  322. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
  323. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
  324. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
  325. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
  326. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
  327. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
  328. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
  329. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
  330. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
  331. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
  332. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
  333. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
  334. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
  335. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
  336. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
  337. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
  338. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
  339. package/skills/video-compose/SKILL.md +8 -1
  340. package/skills/web-crawler/SKILL.md +70 -0
  341. package/skills/web-crawler/scripts/crawl.mjs +538 -0
  342. package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
  343. package/skills/xlsx/SKILL.md +12 -0
  344. package/skills/xlsx/scripts/opc_check.py +253 -0
  345. package/src/advisorState.js +22 -0
  346. package/src/askBridge.js +97 -3
  347. package/src/bootstrap/workingDir.js +12 -1
  348. package/src/cli/args.js +17 -1
  349. package/src/cli/configMenu.js +236 -0
  350. package/src/cli/configWizard.js +7 -5
  351. package/src/cli/print.js +37 -4
  352. package/src/cli/streamJson.js +77 -24
  353. package/src/cli/wizards/advisorWizard.js +147 -0
  354. package/src/cli/wizards/fieldForm.js +149 -0
  355. package/src/cli/wizards/imageWizard.js +60 -0
  356. package/src/cli/wizards/openRouterWizard.js +23 -0
  357. package/src/cli/wizards/providerPresets.js +95 -0
  358. package/src/cli/wizards/providerWizard.js +219 -0
  359. package/src/cli/wizards/searchWizard.js +23 -0
  360. package/src/cli/wizards/ttsWizard.js +34 -0
  361. package/src/config/configFile.js +441 -83
  362. package/src/config/configService.js +446 -0
  363. package/src/config.js +24 -64
  364. package/src/context/agentHome.js +9 -0
  365. package/src/context/archive.js +7 -2
  366. package/src/context/compact.js +8 -5
  367. package/src/context/contextLock.js +81 -0
  368. package/src/context/persistContext.js +30 -11
  369. package/src/context/reactiveCompact.js +4 -2
  370. package/src/context/recentDirs.js +21 -61
  371. package/src/context/sessionPath.js +97 -23
  372. package/src/context/sessionRegistry.js +303 -24
  373. package/src/context/tokenCounter.js +18 -0
  374. package/src/context/workspaceIndex.js +731 -0
  375. package/src/infra/verification.js +28 -15
  376. package/src/llm/client.js +394 -159
  377. package/src/llm/providers/anthropicFormat.js +241 -0
  378. package/src/llm/providers/openaiFormat.js +190 -0
  379. package/src/loop.js +219 -32
  380. package/src/main.js +61 -11
  381. package/src/messageQueue.js +97 -0
  382. package/src/multimodal.js +203 -0
  383. package/src/plugin-sdk.js +15 -0
  384. package/src/plugins/commandRouter.js +3 -0
  385. package/src/plugins/hookEngine.js +5 -1
  386. package/src/plugins/pluginLifecycle.js +28 -0
  387. package/src/plugins/pluginLoader.js +2 -1
  388. package/src/plugins/pluginRunner.js +49 -21
  389. package/src/plugins/transcript.js +4 -3
  390. package/src/prompts/pluginList.js +5 -5
  391. package/src/prompts/projectInstructions.js +3 -3
  392. package/src/prompts/promptAssembly.js +133 -144
  393. package/src/prompts/skillList.js +12 -22
  394. package/src/prompts/system.js +0 -1
  395. package/src/session/runAgentTurn.js +70 -0
  396. package/src/session/runTurn.js +41 -6
  397. package/src/toolGate.js +17 -0
  398. package/src/tools/advisor/advisor.js +379 -0
  399. package/src/tools/agent/agent.js +555 -0
  400. package/src/tools/agent/agentTypes.js +59 -0
  401. package/src/tools/agent/coordinator.js +93 -0
  402. package/src/tools/agent/worktree.js +270 -0
  403. package/src/tools/ask/ask.js +7 -2
  404. package/src/tools/bash/bash.js +112 -18
  405. package/src/tools/download/download.js +144 -0
  406. package/src/tools/edit/edit.js +1 -0
  407. package/src/tools/edit/multi-edit.js +1 -0
  408. package/src/tools/genimage/genimage.js +242 -0
  409. package/src/tools/genspeech/genspeech.js +67 -0
  410. package/src/tools/glob/glob.js +1 -0
  411. package/src/tools/grep/grep.js +3 -0
  412. package/src/tools/grep/rgPath.js +2 -0
  413. package/src/tools/index.js +25 -2
  414. package/src/tools/read/read.js +1 -0
  415. package/src/tools/shared/killTree.js +52 -0
  416. package/src/tools/toolProgressBridge.js +48 -0
  417. package/src/tools/webbrowser/agentBrowserPath.js +258 -0
  418. package/src/tools/webbrowser/browser.js +59 -42
  419. package/src/tools/webbrowser/browserControl.js +118 -0
  420. package/src/tools/webbrowser/browserDaemon.js +97 -0
  421. package/src/tools/webbrowser/browserService.js +27 -0
  422. package/src/tools/webbrowser/browserStream.js +119 -0
  423. package/src/tools/webbrowser/webbrowser.js +699 -130
  424. package/src/tools/webfetch/webfetch.js +1 -0
  425. package/src/tools/websearch/websearch.js +134 -51
  426. package/src/tools/write/write.js +1 -0
  427. package/src/ui/App.js +249 -22
  428. package/src/ui/AskQuestion.js +29 -10
  429. package/src/ui/InputBox.js +104 -12
  430. package/src/ui/LiveArea.js +112 -19
  431. package/src/ui/MessageList.js +100 -14
  432. package/src/ui/ModelPicker.js +56 -0
  433. package/src/ui/QueuedMessagesBanner.js +14 -0
  434. package/src/ui/Root.js +18 -10
  435. package/src/ui/SessionPicker.js +102 -0
  436. package/src/ui/StatusLine.js +13 -4
  437. package/src/ui/WorkspacePicker.js +55 -0
  438. package/src/ui/figures.js +44 -0
  439. package/src/ui/hooks/askSlot.js +82 -0
  440. package/src/ui/hooks/useChat.js +358 -64
  441. package/src/ui/hooks/usePasteHandler.js +11 -1
  442. package/src/ui/hooks/useTextBuffer.js +1 -3
  443. package/src/ui/liveViewport.js +5 -1
  444. package/src/ui/markdown.js +206 -0
  445. package/src/ui/textBuffer.js +0 -7
  446. package/src/ui/theme.js +32 -0
  447. package/src/utils/debugLog.js +0 -4
  448. package/src/utils/resourcePaths.js +2 -2
  449. package/src/utils/stripThink.js +50 -0
  450. package/src/web/browserBridge.js +447 -0
  451. package/src/web/protocol.js +19 -0
  452. package/workflows/adaptive-research.yaml +13 -5
  453. package/workflows/book-review-short.yaml +36 -10
  454. package/workflows/e2e-write-greet.yaml +6 -2
  455. package/workflows/illustrate.yaml +227 -0
  456. package/workflows/plan.yaml +460 -179
  457. package/workflows/ppt.yaml +1659 -0
  458. package/workflows/ralph-loop.yaml +199 -0
  459. package/workflows/schema.json +81 -23
  460. package/workflows/webppt.yaml +496 -0
  461. package/workflows/word.yaml +710 -0
  462. package/workflows/youtube-shorts.yaml +46 -15
  463. package/MINIMAL-TRACE.md +0 -77
  464. package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
  465. package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
  466. package/plugins/ralph-wiggum/README.md +0 -197
  467. package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
  468. package/plugins/ralph-wiggum/commands/help.md +0 -137
  469. package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
  470. package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
  471. package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
  472. package/plugins/ralph-wiggum/plugin.js +0 -280
  473. package/plugins/ralph-wiggum/src/goalState.js +0 -258
  474. package/plugins/ralph-wiggum/src/sentinels.js +0 -76
  475. package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
  476. package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
  477. package/plugins/workflow-runner/src/miniReAct.js +0 -223
  478. package/plugins/workflow-runner/src/voteHelpers.js +0 -110
  479. package/skills/config/SKILL.md +0 -123
  480. package/skills/frontend-design/LICENSE.txt +0 -177
  481. package/skills/frontend-design/SKILL.md +0 -42
  482. package/src/ui/ProgressPanel.js +0 -54
  483. package/src/ui/StreamingBlock.js +0 -11
  484. package/src/ui/ToolStatus.js +0 -11
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ design_budget_check.py —— 设计文档「每页内容预算」确定性检查(ppt 工作流专用,治「内容顶出画布」)。
5
+
6
+ 为什么需要
7
+ ==========
8
+ 真机反复出现:第一次设计出的网页,内容稍多的页(典型=目录页页数一多)把内容**顶出 1920×1080**(尤其底部)。
9
+ 根因不是缺规则——`layout-composition.md §0.6` 早有「每页内容预算(archetype × 1920×1080)」——而是**设计 agent(弱模型)
10
+ 对「一页装多少」没确定性概念**,读 prose 预算照样塞超。等到末端构图门 `layout_balance.py` 的【超出画布】WARNING
11
+ 才发现已经晚了(还有「每页改 3 次放行」逃生口)。
12
+
13
+ 本脚本把 §0.6 的预算**收成确定性检查**:读 build/design.json,逐页对照其 archetype 的「最大要点数 + 每条字数软上限」,
14
+ 超标就**像 zod 报错一样指出:哪页 · 超在哪 · 建议怎么改(双列/拆页/精炼)**,让设计/评审在**便宜的 JSON 层**就解决,
15
+ 不把顶出丢给末端构图门。与 source_usage_check / image_plan_lint / plan_source_layout 同哲学:可计算的决策交确定性脚本。
16
+
17
+ 预算来源(单一真相源 = layout-composition.md §0.6,改那里也要同步本表)
18
+ ====================================================================
19
+ archetype → 最大要点数 / 每条字数软上限:
20
+ cover 0 / section 1 / split 5(×22) / cards 6(×卡标+说明) / metric 4 / fullbleed 3 / list 7(×28) / 其它 6(×30)
21
+
22
+ 判定(advisory,不 halt——治本靠下游 review 在 design.json 层改)
23
+ ================================================================
24
+ - 标题字数 > 上限(cover12/section10/其余14) → 【标题过长】… → 精简到一行(治 Office 重折行顶出)
25
+ - 要点数 > 上限 → 【要点过多】… → 改双列 / 拆两页 / 精炼合并
26
+ - 单条字数 > 软上限×1.3 → 【单条过长】… → 精炼说人话(类比/例子代替长定义)
27
+ (数量上限 + 单条字数两条已覆盖「过度塞页」全部情形——总字数超标必然由这两条之一先报,故不再单列「整页过密」冗余判定。)
28
+ 退出码:0=全部在预算内(BUDGET_OK);1=有超标(诊断用;调用方按需,不强制 halt)。只用标准库。
29
+
30
+ 用法
31
+ ====
32
+ python design_budget_check.py [./build/design.json] [-v]
33
+ (省略路径默认 ./build/design.json;恒写 ./build/design_budget_report.txt 同目录)
34
+ """
35
+
36
+ import argparse
37
+ import json
38
+ import os
39
+ import re
40
+ import sys
41
+
42
+
43
+ # archetype → (最大要点数, 每条字数软上限)。数值同步 layout-composition.md §0.6。
44
+ BUDGET = {
45
+ "cover": (0, 20),
46
+ "section": (1, 24),
47
+ "split": (5, 22),
48
+ "cards": (6, 60), # 每卡=卡标≤8 + 说明 2–3 行,故每条字数上限放宽
49
+ "metric": (4, 24),
50
+ "fullbleed": (3, 28),
51
+ "list": (7, 28),
52
+ }
53
+ DEFAULT_BUDGET = (6, 30)
54
+ LONG_POINT_FACTOR = 1.3 # 单条字数 > 软上限×此 → 判过长(留余量,不为略超报噪声)
55
+
56
+ # ── cover/section「短前景项」豁免(治「编辑式封面/章节页放 kicker + 参数胶囊被误判要点过多」)──
57
+ # 编辑式封面/章节页(§2.2 产品详情页式)会在标题旁放少量**短前景元素**:1 个 kicker(eyebrow 短 label)
58
+ # + 2–3 个**参数胶囊/指标数字/spec**(如「99.9%」「<50ms」「10亿次」)。它们是前景装饰/数据点,
59
+ # **不是「要点列表 points」**,过去被 cover(0)/section(1) 的要点上限误判【要点过多】→ 编辑式封面报假超。
60
+ # 对策:cover/section 计要点数时先扣掉「短前景项」——
61
+ # · 「参数胶囊/指标」= ≤8 字且含数字/百分号(数据 spec)→ 豁免 ≤3 个;
62
+ # · 「kicker」= ≤8 字纯文字 eyebrow → 豁免 1 个;
63
+ # 只有**长要点(>8 字)**与**超量短项**才计入「要点过多」——真·堆长句(多条长要点)仍被拦。
64
+ # 保守:只对 cover/section 生效,内容页(list/cards/split…)预算【不放宽】。
65
+ SHORT_FG_ARCHETYPES = ("cover", "section")
66
+ SHORT_FG_LEN = 8 # ≤8 字才算「短前景项」(参数胶囊/指标/kicker),长句不在此列
67
+ METRIC_FG_ALLOWANCE = 3 # 参数胶囊/指标数字(含数字)豁免上限(§0.6 metric 同量级)
68
+ KICKER_ALLOWANCE = 1 # 纯文字 kicker(eyebrow)豁免上限
69
+ _METRIC_FG_RE = re.compile(r"[0-9%]") # 含阿拉伯数字或百分号 → 视为参数胶囊/指标 spec
70
+
71
+ # 标题字数上限(治「Office 用本机字体重折行 → 长标题折两行下溢压副标题/顶出」,§0.6 封面头号事故)。
72
+ # cover 主标 ≤12(§0.6 铁律);section 章节短标 ≤10;其余内容页 ≤14。
73
+ TITLE_LIMIT = {"cover": 12, "section": 10}
74
+ DEFAULT_TITLE_LIMIT = 14
75
+
76
+ # ── 渲染高度粗估(治「文字过多排下来顶出页底」;与计数预算互补:计数管「几条」、这里管「排下来多高」)──
77
+ # 计数预算可 BUDGET_OK 却仍顶出(真机实证 slide#5 底卡超 107px)——因为它没算渲染高度。
78
+ USABLE_H = 1080 - 104 - 150 # 可用内容高度 ≈ 826(1080 − 上下 padding 56+48 − header 带 ≈150)
79
+ LINE_H = 53 # 正文 38px × 1.4 放映级行高
80
+ CARD_CHROME = 90 # 每卡固定开销(padding + 卡标 + gap)估值
81
+ GAP_H = 24 # 行间距估值
82
+ # archetype → (每行卡数, 每卡每行字符数);据此把 points 铺进网格、逐行取最高卡累加估高。
83
+ HEIGHT_COLS = {
84
+ "cover": (1, 40), "section": (1, 40), "split": (2, 26),
85
+ "cards": (2, 20), "metric": (2, 16), "fullbleed": (1, 40), "list": (2, 24),
86
+ }
87
+ DEFAULT_HEIGHT_COLS = (2, 22)
88
+
89
+
90
+ def _ceil_div(a, b):
91
+ return -(-a // b) if b else 1
92
+
93
+
94
+ def estimate_page_height(archetype, points):
95
+ """粗估该页内容排下来的高度(px)。保守估,只为 advisory 预警明显超高的页(避免误报把对的改错)。"""
96
+ if not points:
97
+ return 0
98
+ cols, cpl = HEIGHT_COLS.get(archetype, DEFAULT_HEIGHT_COLS)
99
+ rows = _ceil_div(len(points), cols)
100
+ total = 0
101
+ for r in range(rows):
102
+ row_cards = points[r * cols:(r + 1) * cols]
103
+ row_h = 0
104
+ for p in row_cards:
105
+ lines = max(1, _ceil_div(len(str(p).strip()), cpl))
106
+ row_h = max(row_h, CARD_CHROME + lines * LINE_H)
107
+ total += row_h
108
+ return total + max(0, rows - 1) * GAP_H
109
+
110
+
111
+ def _as_text_list(v):
112
+ """把 points/evidence 归一成字符串列表(容缺、容杂类型)。"""
113
+ if not isinstance(v, list):
114
+ return []
115
+ out = []
116
+ for x in v:
117
+ if isinstance(x, str):
118
+ out.append(x)
119
+ elif isinstance(x, (int, float)):
120
+ out.append(str(x))
121
+ elif isinstance(x, dict):
122
+ # blocks 形如 {content, render};取 content 文本
123
+ c = x.get("content") or x.get("text") or ""
124
+ if isinstance(c, str):
125
+ out.append(c)
126
+ return out
127
+
128
+
129
+ def _page_points(page):
130
+ """优先 points(上屏文案草案);缺则回退 blocks 的 content。"""
131
+ pts = _as_text_list(page.get("points"))
132
+ if pts:
133
+ return pts
134
+ return _as_text_list(page.get("blocks"))
135
+
136
+
137
+ def _effective_point_count(archetype, points):
138
+ """计「要点过多」时的**有效条数**。
139
+
140
+ cover/section:先扣掉「短前景项」的豁免额度——参数胶囊/指标(含数字,≤8 字)豁免 ≤3 个、
141
+ kicker(纯文字,≤8 字)豁免 1 个;只有长要点(>8 字)+ 超量短项才计入。这样编辑式封面带
142
+ kicker + 2–3 参数胶囊不再误报【要点过多】,但真·堆长要点(多条长句)仍被拦。
143
+ 其它 archetype(内容页)原样返回 len(points)——预算【不放宽】。"""
144
+ if archetype not in SHORT_FG_ARCHETYPES:
145
+ return len(points)
146
+ long_n = metric_short = plain_short = 0
147
+ for p in points:
148
+ s = str(p).strip()
149
+ if len(s) > SHORT_FG_LEN:
150
+ long_n += 1
151
+ elif _METRIC_FG_RE.search(s):
152
+ metric_short += 1 # 参数胶囊/指标数字(数据 spec)
153
+ else:
154
+ plain_short += 1 # kicker / 纯文字短前景
155
+ return (
156
+ long_n
157
+ + max(0, metric_short - METRIC_FG_ALLOWANCE)
158
+ + max(0, plain_short - KICKER_ALLOWANCE)
159
+ )
160
+
161
+
162
+ def check_page(page):
163
+ """对单页判预算,返回 finding 字符串列表(空=该页在预算内)。"""
164
+ archetype = (page.get("archetype") or "").strip().lower()
165
+ max_count, char_limit = BUDGET.get(archetype, DEFAULT_BUDGET)
166
+ pno = page.get("page", "?")
167
+ title = page.get("title") or ""
168
+ label = f"页 {pno}({archetype or '未注 archetype'}{('·' + title) if title else ''})"
169
+ points = _page_points(page)
170
+ findings = []
171
+
172
+ # ⓪ 标题过长(Office 重折行 → 长标题折两行下溢压副标题/顶出)
173
+ title_limit = TITLE_LIMIT.get(archetype, DEFAULT_TITLE_LIMIT)
174
+ tlen = len(title.strip())
175
+ if tlen > title_limit:
176
+ findings.append(
177
+ f"【标题过长】{label} · 标题 {tlen} 字 > 上限 {title_limit} 字 → 精简到一行"
178
+ "(Office 用本机字体会把长标题重折两行、下溢压住副标题或顶出画布)"
179
+ )
180
+
181
+ # ① 要点数超上限(cover/section 先扣短前景项豁免——kicker + 参数胶囊不算「要点」,见 _effective_point_count)
182
+ eff_count = _effective_point_count(archetype, points)
183
+ if eff_count > max_count:
184
+ action = (
185
+ "封面/章节页不该堆长要点 → 长句移到正文页(kicker + ≤3 参数胶囊/指标的短前景已豁免)"
186
+ if max_count <= 1
187
+ else ("改双列(grid-template-columns:1fr 1fr)/ 拆成两页 / 精炼合并同类项"
188
+ if archetype in ("list", "cards")
189
+ else "精炼合并 / 拆成两页 / 改更密版式")
190
+ )
191
+ findings.append(
192
+ f"【要点过多】{label} · {eff_count} 条 > 上限 {max_count} 条 → {action}"
193
+ )
194
+
195
+ # ② 单条过长
196
+ long_limit = int(char_limit * LONG_POINT_FACTOR)
197
+ for i, p in enumerate(points, 1):
198
+ n = len(p.strip())
199
+ if n > long_limit:
200
+ findings.append(
201
+ f"【单条过长】{label} · 第 {i} 条 {n} 字 > 软上限 {char_limit} 字 → 精炼说人话"
202
+ "(用类比/例子代替长定义,近似但好懂)"
203
+ )
204
+
205
+ # ③ 渲染高度溢出(与计数互补:内容排下来超过可用高 → 顶出页底;计数 OK 也可能高度超)
206
+ est_h = estimate_page_height(archetype, points)
207
+ if est_h > USABLE_H:
208
+ findings.append(
209
+ f"【高度溢出】{label} · 内容估高 ~{est_h}px > 可用 {USABLE_H}px(会顶出页底/下边缘)"
210
+ " → ① 精炼文案 → ② 正文字号缩 ≤20%(主标/章节大标【不缩】)→ ③ 客户图 ≤1× 不放大 → ④ 拆成两页"
211
+ )
212
+
213
+ return findings
214
+
215
+
216
+ def check(design_path):
217
+ """返回 (code, report_text)。code: 0 全在预算内 / 1 有超标 / 2 无 design.json。"""
218
+ try:
219
+ with open(design_path, encoding="utf-8-sig") as f:
220
+ design = json.load(f)
221
+ except FileNotFoundError:
222
+ return 2, f"NO_DESIGN: {design_path} 不存在(设计步尚未产出 design.json)"
223
+ except (OSError, json.JSONDecodeError) as exc:
224
+ return 2, f"DESIGN_PARSE_ERROR: {design_path} 解析失败:{exc}"
225
+
226
+ pages = design.get("pages") if isinstance(design, dict) else None
227
+ if not isinstance(pages, list) or not pages:
228
+ return 0, "BUDGET_OK: design.json 无 pages(空设计,跳过预算检查)"
229
+
230
+ all_findings = []
231
+ for page in pages:
232
+ if isinstance(page, dict):
233
+ all_findings.extend(check_page(page))
234
+
235
+ if not all_findings:
236
+ return 0, f"BUDGET_OK: {len(pages)} 页全部在 archetype 内容预算内(不会顶出画布)。"
237
+
238
+ lines = [
239
+ f"BUDGET_OVER: {len(all_findings)} 处超预算(治顶出根因——在 design.json 层就改掉,别等末端构图门):",
240
+ ]
241
+ lines.extend(" - " + f for f in all_findings)
242
+ lines.append(
243
+ "修法阶梯(与构图门【超出画布】同序):① 精炼说人话 + 图文并茂 → ② 缩字 ≤20%(仍 ≥ 放映级下限)"
244
+ " → ③ 单列改双列/散列 → ④ 拆成两页 → ⑤ 删次要描述做取舍。**绝不缩字到 <24px、绝不硬塞顶出。**"
245
+ )
246
+ return 1, "\n".join(lines)
247
+
248
+
249
+ def main() -> int:
250
+ parser = argparse.ArgumentParser(
251
+ description="设计文档每页内容预算确定性检查(治内容顶出画布;读 design.json,advisory 不 halt)"
252
+ )
253
+ parser.add_argument("design", nargs="?", default="./build/design.json", help="design.json 路径(默认 ./build/design.json)")
254
+ parser.add_argument("-v", "--verbose", action="store_true", help="(预留)打印每页明细")
255
+ args = parser.parse_args()
256
+
257
+ code, report = check(args.design)
258
+ print(report)
259
+
260
+ # 恒写报告到 design.json 同目录,供评审步 Read。
261
+ try:
262
+ out_dir = os.path.dirname(os.path.abspath(args.design)) or "."
263
+ with open(os.path.join(out_dir, "design_budget_report.txt"), "w", encoding="utf-8") as f:
264
+ f.write(report + "\n")
265
+ except OSError:
266
+ pass
267
+ return code
268
+
269
+
270
+ if __name__ == "__main__":
271
+ sys.exit(main())
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ design_budget_check_test.py —— design_budget_check.py 单测。
5
+
6
+ 锁定(治「内容顶出画布」根因的确定性预算判定):
7
+ - list 9 条 > 上限 7 → 【要点过多】+ 建议双列/拆页。
8
+ - split 某条超长 → 【单条过长】。
9
+ - 整页总字数过密 → 【整页过密】。
10
+ - cards 6 卡(恰在上限)→ 不报。
11
+ - 封面 cover 堆要点 → 报(cover 预算 0)。
12
+ - 全在预算内 → code 0 / BUDGET_OK。
13
+ - 无 design.json → code 2 / NO_DESIGN。
14
+ 依赖:标准库。
15
+ """
16
+
17
+ import json
18
+ import os
19
+ import sys
20
+ import tempfile
21
+ import unittest
22
+
23
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
24
+ import design_budget_check as dbc # noqa: E402
25
+
26
+
27
+ def _write(path, obj):
28
+ with open(path, "w", encoding="utf-8") as f:
29
+ json.dump(obj, f, ensure_ascii=False)
30
+
31
+
32
+ class TestBudget(unittest.TestCase):
33
+ def setUp(self):
34
+ self.d = tempfile.mkdtemp(prefix="budget-")
35
+ self.design = os.path.join(self.d, "design.json")
36
+
37
+ def test_list_too_many_points(self):
38
+ _write(self.design, {"pages": [
39
+ {"page": 1, "archetype": "list", "title": "目录",
40
+ "points": [f"第{i}章 标题" for i in range(9)]},
41
+ ]})
42
+ code, report = dbc.check(self.design)
43
+ self.assertEqual(code, 1)
44
+ self.assertIn("要点过多", report)
45
+ self.assertIn("9 条", report)
46
+ self.assertIn("双列", report)
47
+
48
+ def test_split_long_single_point(self):
49
+ long = "这是一条被硬塞进一页的超长定义" * 4 # 远超 split 22 字软上限
50
+ _write(self.design, {"pages": [
51
+ {"page": 2, "archetype": "split", "points": ["短要点", long]},
52
+ ]})
53
+ code, report = dbc.check(self.design)
54
+ self.assertEqual(code, 1)
55
+ self.assertIn("单条过长", report)
56
+
57
+ def test_cards_at_limit_passes(self):
58
+ _write(self.design, {"pages": [
59
+ {"page": 3, "archetype": "cards",
60
+ "points": [f"卡{i}:一句简短说明文字" for i in range(6)]},
61
+ ]})
62
+ code, report = dbc.check(self.design)
63
+ self.assertEqual(code, 0)
64
+ self.assertIn("BUDGET_OK", report)
65
+
66
+ def test_cover_should_not_stack_points(self):
67
+ _write(self.design, {"pages": [
68
+ {"page": 1, "archetype": "cover", "points": ["要点A", "要点B", "要点C"]},
69
+ ]})
70
+ code, report = dbc.check(self.design)
71
+ self.assertEqual(code, 1)
72
+ self.assertIn("要点过多", report)
73
+
74
+ def test_cover_kicker_plus_metric_capsules_not_over(self):
75
+ # 编辑式封面:kicker(纯文字短前景)+ 3 个参数胶囊/指标(含数字,≤8 字)→ **不**报 BUDGET_OVER。
76
+ # 这正是 cover(0)/section(1) 旧上限误判【要点过多】的真机场景,现按短前景项豁免后应 BUDGET_OK。
77
+ _write(self.design, {"pages": [
78
+ {"page": 1, "archetype": "cover", "title": "智造新平台",
79
+ "kicker": "AI 新引擎",
80
+ "points": ["AI 新引擎", "99.9% 可用", "<50ms 响应", "10亿次 调用"]},
81
+ ]})
82
+ code, report = dbc.check(self.design)
83
+ self.assertEqual(code, 0)
84
+ self.assertIn("BUDGET_OK", report)
85
+ self.assertNotIn("BUDGET_OVER", report)
86
+ self.assertNotIn("要点过多", report)
87
+
88
+ def test_cover_stacking_long_points_still_over(self):
89
+ # 真·堆长要点(多条长句)仍要拦:cover 堆 4 条长要点 → 仍报 BUDGET_OVER /【要点过多】。
90
+ _write(self.design, {"pages": [
91
+ {"page": 1, "archetype": "cover", "title": "封面",
92
+ "points": [
93
+ "这是一条相当长的封面要点描述文字甲项",
94
+ "这是一条相当长的封面要点描述文字乙项",
95
+ "这是一条相当长的封面要点描述文字丙项",
96
+ "这是一条相当长的封面要点描述文字丁项",
97
+ ]},
98
+ ]})
99
+ code, report = dbc.check(self.design)
100
+ self.assertEqual(code, 1)
101
+ self.assertIn("要点过多", report)
102
+
103
+ def test_content_page_long_title(self):
104
+ _write(self.design, {"pages": [
105
+ {"page": 2, "archetype": "split",
106
+ "title": "这是一个被写得过长的内容页主标题会在Office里折两行",
107
+ "points": ["要点一", "要点二"]},
108
+ ]})
109
+ code, report = dbc.check(self.design)
110
+ self.assertEqual(code, 1)
111
+ self.assertIn("标题过长", report)
112
+
113
+ def test_cover_title_over_twelve(self):
114
+ _write(self.design, {"pages": [
115
+ {"page": 1, "archetype": "cover", "title": "面向投资人的产品与商业模式深度解读", "points": []},
116
+ ]})
117
+ code, report = dbc.check(self.design)
118
+ self.assertEqual(code, 1)
119
+ self.assertIn("标题过长", report)
120
+
121
+ def test_short_title_passes(self):
122
+ _write(self.design, {"pages": [
123
+ {"page": 1, "archetype": "cover", "title": "产品发布会", "points": []},
124
+ {"page": 2, "archetype": "list", "title": "目录", "points": ["一", "二", "三"]},
125
+ ]})
126
+ code, report = dbc.check(self.design)
127
+ self.assertEqual(code, 0)
128
+ self.assertIn("BUDGET_OK", report)
129
+
130
+ def test_all_within_budget(self):
131
+ _write(self.design, {"pages": [
132
+ {"page": 1, "archetype": "cover", "points": []},
133
+ {"page": 2, "archetype": "list", "points": ["要点一", "要点二", "要点三"]},
134
+ {"page": 3, "archetype": "cards", "points": ["卡A 说明", "卡B 说明", "卡C 说明", "卡D 说明"]},
135
+ ]})
136
+ code, report = dbc.check(self.design)
137
+ self.assertEqual(code, 0)
138
+ self.assertIn("BUDGET_OK", report)
139
+
140
+ def test_blocks_fallback_when_no_points(self):
141
+ _write(self.design, {"pages": [
142
+ {"page": 1, "archetype": "list",
143
+ "blocks": [{"content": f"条目{i}", "render": "html+text"} for i in range(9)]},
144
+ ]})
145
+ code, report = dbc.check(self.design)
146
+ self.assertEqual(code, 1)
147
+ self.assertIn("要点过多", report)
148
+
149
+ def test_height_overflow_tall_cards(self):
150
+ # 6 张卡(=数量上限、每条 72 字 < 单条上限 78)→ 计数全过,但排成 3 行估高超 826 → 仅【高度溢出】触发。
151
+ _write(self.design, {"pages": [
152
+ {"page": 1, "archetype": "cards", "title": "能力矩阵",
153
+ "points": ["指标说明" * 18 for _ in range(6)]},
154
+ ]})
155
+ code, report = dbc.check(self.design)
156
+ self.assertEqual(code, 1)
157
+ self.assertIn("高度溢出", report)
158
+ # 不应误报数量/单条(证明高度检查是「计数过但高度超」的补充)
159
+ self.assertNotIn("要点过多", report)
160
+ self.assertNotIn("单条过长", report)
161
+
162
+ def test_height_ok_short_cards_no_false_positive(self):
163
+ # 短文 4 卡 2×2 → 估高远低于可用,绝不误报高度溢出(防「把对的改错」过度修正)。
164
+ _write(self.design, {"pages": [
165
+ {"page": 1, "archetype": "cards", "points": ["卡A 简短说明", "卡B 简短说明", "卡C 简短说明", "卡D 简短说明"]},
166
+ ]})
167
+ code, report = dbc.check(self.design)
168
+ self.assertEqual(code, 0)
169
+ self.assertNotIn("高度溢出", report)
170
+
171
+ def test_no_design_file(self):
172
+ code, report = dbc.check(os.path.join(self.d, "nope.json"))
173
+ self.assertEqual(code, 2)
174
+ self.assertIn("NO_DESIGN", report)
175
+
176
+ def test_empty_pages_ok(self):
177
+ _write(self.design, {"pages": []})
178
+ code, report = dbc.check(self.design)
179
+ self.assertEqual(code, 0)
180
+
181
+
182
+ if __name__ == "__main__":
183
+ unittest.main(verbosity=2)
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Summarize design check failures into a tiny repair batch.
4
+
5
+ The PPT workflow can produce dozens of design_budget/design_style findings.
6
+ Weak agents often burn all turns trying to repair the whole report. This helper
7
+ turns the current recheck JSON into a page-limited batch so the workflow can
8
+ repair 1-3 pages per round and continue after a bounded number of rounds.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import json
15
+ import re
16
+ from collections import defaultdict
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+
21
+ PAGE_PATTERNS = [
22
+ re.compile(r"第\s*(\d+)\s*页"),
23
+ re.compile(r"\bpage\s*(\d+)\b", re.IGNORECASE),
24
+ re.compile(r"\b页\s*(\d+)\b"),
25
+ ]
26
+
27
+
28
+ def _load_json(path: Path, default: Any) -> Any:
29
+ if not path.exists():
30
+ return default
31
+ try:
32
+ return json.loads(path.read_text(encoding="utf-8"))
33
+ except Exception:
34
+ return default
35
+
36
+
37
+ def _page_from_line(line: str) -> int | None:
38
+ for pattern in PAGE_PATTERNS:
39
+ match = pattern.search(line)
40
+ if match:
41
+ try:
42
+ return int(match.group(1))
43
+ except ValueError:
44
+ return None
45
+ return None
46
+
47
+
48
+ def _collect_issues(report: dict[str, Any]) -> dict[int, list[str]]:
49
+ by_page: dict[int, list[str]] = defaultdict(list)
50
+ for key in ("style_log", "budget_log"):
51
+ text = str(report.get(key) or "")
52
+ for raw_line in text.splitlines():
53
+ line = raw_line.strip()
54
+ if not line:
55
+ continue
56
+ page = _page_from_line(line)
57
+ if page is None:
58
+ continue
59
+ # Keep the actionable portion short. The full report remains on disk.
60
+ by_page[page].append(line[:420])
61
+ return dict(by_page)
62
+
63
+
64
+ def _page_title_map(design: dict[str, Any]) -> dict[int, str]:
65
+ titles: dict[int, str] = {}
66
+ for page in design.get("pages", []) if isinstance(design, dict) else []:
67
+ if not isinstance(page, dict):
68
+ continue
69
+ try:
70
+ page_no = int(page.get("page"))
71
+ except Exception:
72
+ continue
73
+ titles[page_no] = str(page.get("title") or page.get("one_sentence_takeaway") or "")
74
+ return titles
75
+
76
+
77
+ def make_hints(build: Path, max_pages: int) -> tuple[dict[str, Any], str]:
78
+ report = _load_json(build / "design_review_recheck.json", {})
79
+ design = _load_json(build / "design.json", {})
80
+ issues_by_page = _collect_issues(report if isinstance(report, dict) else {})
81
+ titles = _page_title_map(design if isinstance(design, dict) else {})
82
+
83
+ ranked = sorted(
84
+ issues_by_page.items(),
85
+ key=lambda item: (-len(item[1]), item[0]),
86
+ )
87
+ selected = ranked[:max_pages]
88
+ batch_pages = []
89
+ for page_no, issues in selected:
90
+ batch_pages.append(
91
+ {
92
+ "page": page_no,
93
+ "title": titles.get(page_no, ""),
94
+ "issue_count": len(issues),
95
+ "issues": issues[:8],
96
+ }
97
+ )
98
+
99
+ payload = {
100
+ "ok": bool(report.get("ok")) if isinstance(report, dict) else False,
101
+ "budget_ok": bool(report.get("budget_ok")) if isinstance(report, dict) else False,
102
+ "style_ok": bool(report.get("style_ok")) if isinstance(report, dict) else False,
103
+ "max_pages": max_pages,
104
+ "total_pages_with_issues": len(issues_by_page),
105
+ "total_issue_lines": sum(len(v) for v in issues_by_page.values()),
106
+ "pages": batch_pages,
107
+ "auto_repair_guidance": [
108
+ "只修本批页,不扩散重写全稿。",
109
+ "先减字、合并要点、补齐 composition_style/visual_focus/text_zone/copy_budget 等字段。",
110
+ "禁止为填满页面新增卡片墙、表格墙、大白/灰浮动面板。",
111
+ "无客户图且非 HTML/SVG 精确信息页,必须规划真实 AI 语义图。",
112
+ "数据/流程/架构/关系图继续用 HTML/SVG,不交给 AI 生图。",
113
+ ],
114
+ }
115
+
116
+ lines = [
117
+ "# 设计短修复批次",
118
+ "",
119
+ f"- ok: {payload['ok']}",
120
+ f"- budget_ok: {payload['budget_ok']}",
121
+ f"- style_ok: {payload['style_ok']}",
122
+ f"- 本批最多修复页数: {max_pages}",
123
+ f"- 有问题页数: {payload['total_pages_with_issues']}",
124
+ f"- 问题行数: {payload['total_issue_lines']}",
125
+ "",
126
+ "## 修复原则",
127
+ "",
128
+ ]
129
+ for item in payload["auto_repair_guidance"]:
130
+ lines.append(f"- {item}")
131
+ lines.extend(["", "## 本批页", ""])
132
+ if not batch_pages:
133
+ lines.append("当前没有可定位到页号的问题。若 recheck 仍失败,只做全局字段/格式修复。")
134
+ for page in batch_pages:
135
+ lines.append(f"### 第 {page['page']} 页 {page['title']}")
136
+ lines.append(f"- 问题数: {page['issue_count']}")
137
+ for issue in page["issues"]:
138
+ lines.append(f"- {issue}")
139
+ lines.append("")
140
+ return payload, "\n".join(lines).rstrip() + "\n"
141
+
142
+
143
+ def main() -> int:
144
+ parser = argparse.ArgumentParser(description="Create a small design repair batch from recheck reports.")
145
+ parser.add_argument("--build", default="./build", help="Build directory")
146
+ parser.add_argument("--max-pages", type=int, default=3, help="Maximum pages to include")
147
+ args = parser.parse_args()
148
+
149
+ build = Path(args.build)
150
+ build.mkdir(parents=True, exist_ok=True)
151
+ payload, markdown = make_hints(build, max(1, args.max_pages))
152
+ (build / "design_repair_batch.json").write_text(
153
+ json.dumps(payload, ensure_ascii=False, indent=2),
154
+ encoding="utf-8",
155
+ )
156
+ (build / "design_repair_batch.md").write_text(markdown, encoding="utf-8")
157
+ print("DESIGN_REPAIR_BATCH_OK")
158
+ return 0
159
+
160
+
161
+ if __name__ == "__main__":
162
+ raise SystemExit(main())
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import json
5
+ import os
6
+ import sys
7
+ import tempfile
8
+ import unittest
9
+ from pathlib import Path
10
+
11
+ HERE = os.path.dirname(os.path.abspath(__file__))
12
+ sys.path.insert(0, HERE)
13
+ from design_repair_hints import make_hints # noqa: E402
14
+
15
+
16
+ class DesignRepairHintsTest(unittest.TestCase):
17
+ def test_groups_issues_by_page_and_limits_batch(self):
18
+ with tempfile.TemporaryDirectory() as d:
19
+ build = Path(d)
20
+ (build / "design.json").write_text(
21
+ json.dumps(
22
+ {
23
+ "pages": [
24
+ {"page": 1, "title": "封面"},
25
+ {"page": 7, "title": "风险收敛"},
26
+ {"page": 12, "title": "系统架构"},
27
+ ]
28
+ },
29
+ ensure_ascii=False,
30
+ ),
31
+ encoding="utf-8",
32
+ )
33
+ (build / "design_review_recheck.json").write_text(
34
+ json.dumps(
35
+ {
36
+ "ok": False,
37
+ "budget_ok": False,
38
+ "style_ok": False,
39
+ "budget_log": "第7页 标题过长\n第12页 正文过多\n",
40
+ "style_log": "第7页 卡片墙\n第7页 缺少主视觉\n第12页 表格墙\n第1页 缺少 safe_text_zone\n",
41
+ },
42
+ ensure_ascii=False,
43
+ ),
44
+ encoding="utf-8",
45
+ )
46
+ payload, markdown = make_hints(build, max_pages=2)
47
+ self.assertEqual(payload["total_pages_with_issues"], 3)
48
+ self.assertEqual([p["page"] for p in payload["pages"]], [7, 12])
49
+ self.assertIn("第 7 页 风险收敛", markdown)
50
+ self.assertIn("禁止为填满页面新增卡片", markdown)
51
+
52
+
53
+ if __name__ == "__main__":
54
+ unittest.main(verbosity=2)