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,256 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ extract_source_assets_test.py —— extract_source_assets.py 的单测(内造 pptx/docx fixture)。
5
+
6
+ 锁定:
7
+ - pptx 抽出 slide 文字(含标题作 section)+ picture,manifest 记对 aspect_ratio/orient/section。
8
+ - docx 抽出 heading 分节 + 内嵌图,图关联到最近 heading。
9
+ - 横图判 landscape、竖图判 portrait。
10
+ - 无源文档 → has_source:false,不报错。
11
+ 依赖:python-pptx / python-docx / Pillow(已就绪)。
12
+ """
13
+
14
+ import json
15
+ import os
16
+ import sys
17
+ import tempfile
18
+ import unittest
19
+
20
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
21
+ import extract_source_assets as ex # noqa: E402
22
+
23
+
24
+ def _make_png(path, w, h, color=(80, 120, 200)):
25
+ from PIL import Image
26
+ Image.new("RGB", (w, h), color).save(path)
27
+
28
+
29
+ def _make_pptx(path, png_landscape):
30
+ from pptx import Presentation
31
+ from pptx.util import Inches
32
+ prs = Presentation()
33
+ slide = prs.slides.add_slide(prs.slide_layouts[5]) # Title Only(含标题占位符)
34
+ slide.shapes.title.text = "产品概览"
35
+ tb = slide.shapes.add_textbox(Inches(1), Inches(5), Inches(8), Inches(1))
36
+ tb.text_frame.text = "这是产品概览页的说明文字"
37
+ slide.shapes.add_picture(png_landscape, Inches(1), Inches(1), Inches(4))
38
+ prs.save(path)
39
+
40
+
41
+ def _make_docx(path, png_portrait):
42
+ from docx import Document
43
+ from docx.shared import Inches
44
+ doc = Document()
45
+ doc.add_heading("第一章 背景", level=1)
46
+ doc.add_paragraph("这是第一章的正文段落,描述背景。")
47
+ doc.add_picture(png_portrait, width=Inches(2))
48
+ doc.save(path)
49
+
50
+
51
+ class TestExtractPptx(unittest.TestCase):
52
+ def setUp(self):
53
+ self.tmp = tempfile.mkdtemp(prefix="extract-pptx-")
54
+ self.png = os.path.join(self.tmp, "land.png")
55
+ _make_png(self.png, 1600, 900) # 横图
56
+ self.doc = os.path.join(self.tmp, "brief.pptx")
57
+ _make_pptx(self.doc, self.png)
58
+ self.out = os.path.join(self.tmp, "build")
59
+
60
+ def test_pptx_text_image_manifest(self):
61
+ m = ex.extract(self.doc, self.out)
62
+ self.assertTrue(m["has_source"])
63
+ self.assertIn("brief.pptx", m["sources"])
64
+ # source.md 含标题
65
+ with open(os.path.join(self.out, "source.md"), encoding="utf-8") as f:
66
+ md = f.read()
67
+ self.assertIn("产品概览", md)
68
+ # 图片落盘 + manifest 正确
69
+ self.assertEqual(len(m["images"]), 1)
70
+ img = m["images"][0]
71
+ self.assertTrue(os.path.isfile(os.path.join(self.out, "assets", img["file"])))
72
+ self.assertEqual((img["w"], img["h"]), (1600, 900))
73
+ self.assertEqual(img["orient"], "landscape")
74
+ self.assertGreater(img["aspect_ratio"], 1.5)
75
+ self.assertEqual(img["section"], "产品概览")
76
+ self.assertIn("说明文字", img["near_text"])
77
+
78
+
79
+ class TestExtractDocx(unittest.TestCase):
80
+ def setUp(self):
81
+ self.tmp = tempfile.mkdtemp(prefix="extract-docx-")
82
+ self.png = os.path.join(self.tmp, "port.png")
83
+ _make_png(self.png, 600, 1000) # 竖图
84
+ self.doc = os.path.join(self.tmp, "report.docx")
85
+ _make_docx(self.doc, self.png)
86
+ self.out = os.path.join(self.tmp, "build")
87
+
88
+ def test_docx_heading_image_assoc(self):
89
+ m = ex.extract(self.doc, self.out)
90
+ self.assertTrue(m["has_source"])
91
+ with open(os.path.join(self.out, "source.md"), encoding="utf-8") as f:
92
+ md = f.read()
93
+ self.assertIn("第一章 背景", md)
94
+ self.assertEqual(len(m["images"]), 1)
95
+ img = m["images"][0]
96
+ self.assertEqual((img["w"], img["h"]), (600, 1000))
97
+ self.assertEqual(img["orient"], "portrait")
98
+ self.assertLess(img["aspect_ratio"], 0.8)
99
+ self.assertEqual(img["section"], "第一章 背景")
100
+
101
+ def test_docx_word_media_fallback_without_duplicate(self):
102
+ import zipfile
103
+
104
+ extra = os.path.join(self.tmp, "extra.png")
105
+ _make_png(extra, 1200, 500)
106
+ with open(extra, "rb") as f:
107
+ blob = f.read()
108
+ with zipfile.ZipFile(self.doc, "a") as z:
109
+ z.writestr("word/media/orphan_extra.png", blob)
110
+
111
+ m = ex.extract(self.doc, self.out)
112
+ self.assertTrue(m["has_source"])
113
+ self.assertEqual(len(m["images"]), 2)
114
+ sizes = {(im["w"], im["h"]) for im in m["images"]}
115
+ self.assertIn((600, 1000), sizes)
116
+ self.assertIn((1200, 500), sizes)
117
+
118
+
119
+ class TestExtractLegacyDoc(unittest.TestCase):
120
+ def test_doc_converts_then_uses_docx_extractor(self):
121
+ tmp = tempfile.mkdtemp(prefix="extract-doc-")
122
+ png = os.path.join(tmp, "port.png")
123
+ _make_png(png, 600, 1000)
124
+ converted = os.path.join(tmp, "converted.docx")
125
+ _make_docx(converted, png)
126
+ legacy = os.path.join(tmp, "legacy.doc")
127
+ with open(legacy, "wb") as f:
128
+ f.write(b"fake old word; conversion is mocked")
129
+ out = os.path.join(tmp, "build")
130
+
131
+ old = ex._convert_doc_to_docx
132
+ try:
133
+ ex._convert_doc_to_docx = lambda path, verbose: converted
134
+ m = ex.extract(legacy, out)
135
+ finally:
136
+ ex._convert_doc_to_docx = old
137
+
138
+ self.assertTrue(m["has_source"])
139
+ self.assertIn("legacy.doc", m["sources"])
140
+ self.assertEqual(len(m["images"]), 1)
141
+ with open(os.path.join(out, "source.md"), encoding="utf-8") as f:
142
+ md = f.read()
143
+ self.assertIn("老 Word `.doc` 已通过 LibreOffice 转换", md)
144
+ self.assertIn("第一章 背景", md)
145
+
146
+ def test_doc_conversion_error_does_not_claim_success(self):
147
+ tmp = tempfile.mkdtemp(prefix="extract-doc-fail-")
148
+ legacy = os.path.join(tmp, "bad.doc")
149
+ with open(legacy, "wb") as f:
150
+ f.write(b"bad")
151
+ out = os.path.join(tmp, "build")
152
+
153
+ old = ex._convert_doc_to_docx
154
+ try:
155
+ ex._convert_doc_to_docx = lambda path, verbose: (_ for _ in ()).throw(RuntimeError("LibreOffice missing"))
156
+ m = ex.extract(legacy, out)
157
+ finally:
158
+ ex._convert_doc_to_docx = old
159
+
160
+ self.assertFalse(m["has_source"])
161
+ self.assertEqual(m["images"], [])
162
+
163
+
164
+ class TestScanAndEmpty(unittest.TestCase):
165
+ def test_scan_dir_finds_docs_and_standalone_images(self):
166
+ # 混合输入:a.pptx + b.docx + 两张独立图片,全部应被识别为 source。
167
+ tmp = tempfile.mkdtemp(prefix="extract-scan-")
168
+ land = os.path.join(tmp, "l.png")
169
+ port = os.path.join(tmp, "p.png")
170
+ _make_png(land, 1600, 900)
171
+ _make_png(port, 600, 1000)
172
+ _make_pptx(os.path.join(tmp, "a.pptx"), land)
173
+ _make_docx(os.path.join(tmp, "b.docx"), port)
174
+ out = os.path.join(tmp, "build")
175
+ m = ex.extract(tmp, out, scan=True)
176
+ self.assertTrue(m["has_source"])
177
+ # 4 个 source:pptx + docx + 2 张独立图片(独立图片现也是合法输入)
178
+ self.assertEqual(len(m["sources"]), 4)
179
+ # 4 张图:pptx 内嵌 1 + docx 内嵌 1 + 独立 2
180
+ self.assertEqual(len(m["images"]), 4)
181
+ # src 编号连续不撞(src_1..src_4)
182
+ files = {im["file"] for im in m["images"]}
183
+ self.assertEqual(len(files), 4)
184
+ # 独立图片以文件名为 section
185
+ sections = {im["section"] for im in m["images"]}
186
+ self.assertTrue("l" in sections and "p" in sections)
187
+
188
+ def test_no_source_graceful(self):
189
+ tmp = tempfile.mkdtemp(prefix="extract-empty-")
190
+ out = os.path.join(tmp, "build")
191
+ m = ex.extract(tmp, out, scan=True)
192
+ self.assertFalse(m["has_source"])
193
+ self.assertEqual(m["images"], [])
194
+ # manifest 仍落盘(下游 Read 不炸)
195
+ with open(os.path.join(out, "source_assets.json"), encoding="utf-8") as f:
196
+ self.assertFalse(json.load(f)["has_source"])
197
+
198
+
199
+ class TestNewInputTypes(unittest.TestCase):
200
+ """csv / 独立图片 / xlsx 内嵌图(免 openpyxl)——本轮新增的输入类型。"""
201
+
202
+ def test_standalone_image_single_file(self):
203
+ tmp = tempfile.mkdtemp(prefix="extract-img-")
204
+ img = os.path.join(tmp, "封面图.png")
205
+ _make_png(img, 1920, 1080)
206
+ out = os.path.join(tmp, "build")
207
+ m = ex.extract(img, out) # 直接传单个图片文件
208
+ self.assertTrue(m["has_source"])
209
+ self.assertEqual(len(m["images"]), 1)
210
+ one = m["images"][0]
211
+ self.assertEqual((one["w"], one["h"]), (1920, 1080))
212
+ self.assertEqual(one["orient"], "landscape")
213
+ self.assertEqual(one["section"], "封面图") # section = 文件名(无扩展)
214
+ self.assertTrue(os.path.isfile(os.path.join(out, "assets", one["file"])))
215
+
216
+ def test_csv_to_markdown_table(self):
217
+ tmp = tempfile.mkdtemp(prefix="extract-csv-")
218
+ csv = os.path.join(tmp, "data.csv")
219
+ with open(csv, "w", encoding="utf-8") as f:
220
+ f.write("产品,销量,占比\n相机,1200,35%\n镜头,800,24%\n")
221
+ out = os.path.join(tmp, "build")
222
+ m = ex.extract(csv, out)
223
+ self.assertTrue(m["has_source"])
224
+ self.assertEqual(len(m["images"]), 0) # csv 无图
225
+ with open(os.path.join(out, "source.md"), encoding="utf-8") as f:
226
+ md = f.read()
227
+ self.assertIn("| 产品 | 销量 | 占比 |", md) # markdown 表头
228
+ self.assertIn("相机", md)
229
+
230
+ def test_xlsx_embedded_image_via_zipfile(self):
231
+ # openpyxl 未必安装:手工造一个含 xl/media 的 .xlsx(OOXML=zip),
232
+ # 验证「免 openpyxl 也能抽内嵌图」的 zipfile 路径。
233
+ import zipfile as zf
234
+ from PIL import Image
235
+
236
+ tmp = tempfile.mkdtemp(prefix="extract-xlsx-")
237
+ png_bytes_path = os.path.join(tmp, "_tmp.png")
238
+ Image.new("RGB", (800, 600), (10, 20, 30)).save(png_bytes_path)
239
+ with open(png_bytes_path, "rb") as f:
240
+ png_bytes = f.read()
241
+ xlsx = os.path.join(tmp, "报表.xlsx")
242
+ with zf.ZipFile(xlsx, "w") as z:
243
+ z.writestr("[Content_Types].xml", "<Types/>") # 占位,足够 zipfile 读取
244
+ z.writestr("xl/media/image1.png", png_bytes)
245
+ out = os.path.join(tmp, "build")
246
+ m = ex.extract(xlsx, out)
247
+ self.assertTrue(m["has_source"])
248
+ self.assertEqual(len(m["images"]), 1) # zipfile 抽到内嵌图(无需 openpyxl)
249
+ one = m["images"][0]
250
+ self.assertEqual((one["w"], one["h"]), (800, 600))
251
+ self.assertEqual(one["section"], "工作簿")
252
+ self.assertTrue(os.path.isfile(os.path.join(out, "assets", one["file"])))
253
+
254
+
255
+ if __name__ == "__main__":
256
+ unittest.main(verbosity=2)
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Collect generated-image semantic reviews and make a retry report.
4
+
5
+ The workflow writes one JSON review per generated image using a multimodal LLM.
6
+ This helper aggregates those results and creates a retry-degraded compatible
7
+ report for GenerateImage, so only semantically failed images are retried.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ from pathlib import Path
15
+ from typing import Any
16
+ import re
17
+
18
+
19
+ BAKED_TEXT_RE = re.compile(
20
+ r"(?:文字|数字|编号|序号|标签|题注|水印|logo|Logo|字母|1\s*/\s*2\s*/\s*3\s*/\s*4|"
21
+ r"\b(?:text|word|words|number|numbers|digit|digits|letter|letters|label|caption|logo|watermark)\b)",
22
+ re.I,
23
+ )
24
+
25
+
26
+ def _load_json(path: Path, default: Any) -> Any:
27
+ if not path.exists():
28
+ return default
29
+ try:
30
+ return json.loads(path.read_text(encoding="utf-8"))
31
+ except Exception:
32
+ text = path.read_text(encoding="utf-8", errors="replace")
33
+ # Some LLMs wrap JSON in prose. Try a coarse extraction.
34
+ start = text.find("{")
35
+ end = text.rfind("}")
36
+ if start >= 0 and end > start:
37
+ try:
38
+ return json.loads(text[start : end + 1])
39
+ except Exception:
40
+ pass
41
+ return default
42
+
43
+
44
+ def _is_pass(review: dict[str, Any]) -> bool:
45
+ value = review.get("pass")
46
+ if isinstance(value, bool):
47
+ return value
48
+ verdict = str(review.get("verdict") or review.get("status") or "").lower()
49
+ if verdict in ("pass", "ok", "passed"):
50
+ return True
51
+ if verdict in ("fail", "failed", "reject"):
52
+ return False
53
+ score = review.get("score")
54
+ try:
55
+ return float(score) >= 7.0
56
+ except Exception:
57
+ return False
58
+
59
+
60
+ def _text_of(value: Any) -> str:
61
+ if value is None:
62
+ return ""
63
+ if isinstance(value, str):
64
+ return value
65
+ if isinstance(value, (int, float, bool)):
66
+ return str(value)
67
+ if isinstance(value, list):
68
+ return " ".join(_text_of(v) for v in value)
69
+ if isinstance(value, dict):
70
+ return " ".join(_text_of(v) for v in value.values())
71
+ return str(value)
72
+
73
+
74
+ def _is_faithful_or_dense(item: dict[str, Any] | None) -> bool:
75
+ if not isinstance(item, dict):
76
+ return False
77
+ text = _text_of(
78
+ {
79
+ "mode": item.get("mode"),
80
+ "role": item.get("role"),
81
+ "target_layer": item.get("target_layer"),
82
+ "enhance_style": item.get("enhance_style"),
83
+ "enhance_intent": item.get("enhance_intent"),
84
+ "image_role": item.get("image_role"),
85
+ }
86
+ ).lower()
87
+ return any(term in text for term in ("dense", "faithful", "document", "info"))
88
+
89
+
90
+ def _is_background_like(file_name: str, item: dict[str, Any] | None) -> bool:
91
+ name = str(file_name or "").lower()
92
+ if "_fg." in name:
93
+ return False
94
+ if name.startswith("ai_p"):
95
+ return True
96
+ if not isinstance(item, dict):
97
+ return False
98
+ role_text = _text_of(
99
+ {
100
+ "role": item.get("role"),
101
+ "target_layer": item.get("target_layer"),
102
+ "visual_role": item.get("visual_role"),
103
+ "enhance_style": item.get("enhance_style"),
104
+ "enhance_intent": item.get("enhance_intent"),
105
+ }
106
+ ).lower()
107
+ return "background" in role_text or "bg" in role_text
108
+
109
+
110
+ def _has_baked_text_issue(review: dict[str, Any], file_name: str, item: dict[str, Any] | None) -> bool:
111
+ if _is_faithful_or_dense(item) or not _is_background_like(file_name, item):
112
+ return False
113
+ scan = _text_of(
114
+ {
115
+ "reason": review.get("reason"),
116
+ "issues": review.get("issues"),
117
+ "blocking_issues": review.get("blocking_issues"),
118
+ "aesthetic_issues": review.get("aesthetic_issues"),
119
+ "description": review.get("description"),
120
+ "notes": review.get("notes"),
121
+ }
122
+ )
123
+ return bool(BAKED_TEXT_RE.search(scan))
124
+
125
+
126
+ def collect(build: Path) -> dict[str, Any]:
127
+ build.mkdir(parents=True, exist_ok=True)
128
+ review_dir = build / "generated-image-reviews"
129
+ plan = _load_json(build / "img_plan.json", [])
130
+ by_file = {str(item.get("file")): item for item in plan if isinstance(item, dict) and item.get("file")}
131
+ reviews = []
132
+ failed = []
133
+ if review_dir.exists():
134
+ for path in sorted(review_dir.glob("*.json")):
135
+ data = _load_json(path, {})
136
+ file_name = data.get("file") or path.stem
137
+ if not str(file_name).lower().endswith((".png", ".jpg", ".jpeg", ".webp")):
138
+ # path.stem loses extension for files like ai_p1.png.json. Restore common case.
139
+ for candidate in by_file:
140
+ if path.name == f"{candidate}.json":
141
+ file_name = candidate
142
+ break
143
+ item = by_file.get(str(file_name))
144
+ passed = _is_pass(data if isinstance(data, dict) else {})
145
+ baked_text_issue = False
146
+ if isinstance(data, dict) and _has_baked_text_issue(data, str(file_name), item):
147
+ passed = False
148
+ baked_text_issue = True
149
+ entry = {
150
+ "file": file_name,
151
+ "pass": passed,
152
+ "score": data.get("score") if isinstance(data, dict) else None,
153
+ "reason": data.get("reason") or data.get("issues") if isinstance(data, dict) else "invalid review json",
154
+ }
155
+ if baked_text_issue:
156
+ entry["reason"] = f"{entry['reason']};背景/hero 图含不可编辑文字、数字、Logo 或标签,必须重试"
157
+ entry["hard_error"] = "baked_text_in_background"
158
+ reviews.append(entry)
159
+ if not passed and str(file_name) in by_file:
160
+ failed.append(
161
+ {
162
+ "file": str(file_name),
163
+ "reason": entry["reason"] or "semantic_review_failed",
164
+ "mode": by_file[str(file_name)].get("mode", "text-to-image"),
165
+ }
166
+ )
167
+
168
+ report = {
169
+ "ok": len(failed) == 0,
170
+ "reviewed": len(reviews),
171
+ "failed_count": len(failed),
172
+ "reviews": reviews,
173
+ "failed": failed,
174
+ }
175
+ (build / "generated_image_review.json").write_text(
176
+ json.dumps(report, ensure_ascii=False, indent=2),
177
+ encoding="utf-8",
178
+ )
179
+ retry_report = {
180
+ "ok": len(failed) == 0,
181
+ "qualityOk": len(failed) == 0,
182
+ "degraded": failed,
183
+ "hardErrors": [],
184
+ }
185
+ (build / "generated_image_semantic_retry_report.json").write_text(
186
+ json.dumps(retry_report, ensure_ascii=False, indent=2),
187
+ encoding="utf-8",
188
+ )
189
+ lines = ["# 生成图语义复核", "", f"- reviewed: {len(reviews)}", f"- failed: {len(failed)}", ""]
190
+ for item in failed:
191
+ lines.append(f"- {item['file']}: {item.get('reason')}")
192
+ (build / "generated_image_review.md").write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
193
+ print("GENERATED_IMAGE_REVIEW_OK" if not failed else "GENERATED_IMAGE_REVIEW_RETRY")
194
+ return report
195
+
196
+
197
+ def main() -> int:
198
+ parser = argparse.ArgumentParser(description="Collect generated image semantic reviews.")
199
+ parser.add_argument("--build", default="./build")
200
+ args = parser.parse_args()
201
+ collect(Path(args.build))
202
+ return 0
203
+
204
+
205
+ if __name__ == "__main__":
206
+ raise SystemExit(main())
@@ -0,0 +1,91 @@
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 generated_image_review_collect import collect # noqa: E402
14
+
15
+
16
+ class GeneratedImageReviewCollectTest(unittest.TestCase):
17
+ def test_background_baked_numbers_turn_pass_into_fail(self):
18
+ with tempfile.TemporaryDirectory() as d:
19
+ build = Path(d)
20
+ (build / "generated-image-reviews").mkdir()
21
+ (build / "img_plan.json").write_text(
22
+ json.dumps(
23
+ [
24
+ {
25
+ "file": "ai_p2.png",
26
+ "mode": "text-to-image",
27
+ "prompt": "clean background, no text",
28
+ }
29
+ ],
30
+ ensure_ascii=False,
31
+ ),
32
+ encoding="utf-8",
33
+ )
34
+ (build / "generated-image-reviews" / "ai_p2.png.json").write_text(
35
+ json.dumps(
36
+ {
37
+ "file": "ai_p2.png",
38
+ "pass": True,
39
+ "score": 7,
40
+ "reason": "画面可用,但背景里有 1/2/3/4 数字,可能和前景章节序号重复。",
41
+ },
42
+ ensure_ascii=False,
43
+ ),
44
+ encoding="utf-8",
45
+ )
46
+
47
+ report = collect(build)
48
+ self.assertFalse(report["ok"])
49
+ self.assertEqual(report["failed_count"], 1)
50
+ retry = json.loads((build / "generated_image_semantic_retry_report.json").read_text(encoding="utf-8"))
51
+ self.assertFalse(retry["qualityOk"])
52
+ self.assertEqual(retry["degraded"][0]["file"], "ai_p2.png")
53
+
54
+ def test_faithful_document_can_keep_text(self):
55
+ with tempfile.TemporaryDirectory() as d:
56
+ build = Path(d)
57
+ (build / "generated-image-reviews").mkdir()
58
+ (build / "img_plan.json").write_text(
59
+ json.dumps(
60
+ [
61
+ {
62
+ "file": "enhanced_src_5.png",
63
+ "mode": "image-to-image",
64
+ "enhance_style": "dense",
65
+ "prompt": "faithfully restore screenshot",
66
+ }
67
+ ],
68
+ ensure_ascii=False,
69
+ ),
70
+ encoding="utf-8",
71
+ )
72
+ (build / "generated-image-reviews" / "enhanced_src_5.png.json").write_text(
73
+ json.dumps(
74
+ {
75
+ "file": "enhanced_src_5.png",
76
+ "pass": True,
77
+ "score": 8,
78
+ "reason": "信息截图保真增强,保留原有文字和数字。",
79
+ },
80
+ ensure_ascii=False,
81
+ ),
82
+ encoding="utf-8",
83
+ )
84
+
85
+ report = collect(build)
86
+ self.assertTrue(report["ok"])
87
+ self.assertEqual(report["failed_count"], 0)
88
+
89
+
90
+ if __name__ == "__main__":
91
+ unittest.main(verbosity=2)