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.
- package/README.md +14 -12
- package/package.json +34 -1
- package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
- package/plugins/cron-runner/commands/cron.md +26 -0
- package/plugins/cron-runner/commands/crons.md +7 -0
- package/plugins/cron-runner/plugin.js +34 -0
- package/plugins/cron-runner/src/commands.js +348 -0
- package/plugins/cron-runner/src/cronTime.js +148 -0
- package/plugins/cron-runner/src/lock.js +68 -0
- package/plugins/cron-runner/src/runs.js +89 -0
- package/plugins/cron-runner/src/scheduleEngine.js +72 -0
- package/plugins/cron-runner/src/scheduler.js +355 -0
- package/plugins/cron-runner/src/schema.js +140 -0
- package/plugins/cron-runner/src/service.js +167 -0
- package/plugins/cron-runner/src/store.js +298 -0
- package/plugins/cron-runner/src/types.js +1 -0
- package/plugins/workflow-runner/commands/workflow.md +4 -4
- package/plugins/workflow-runner/commands/workflows.md +1 -1
- package/plugins/workflow-runner/plugin.js +3 -1
- package/plugins/workflow-runner/src/agentCommon.js +249 -51
- package/plugins/workflow-runner/src/dslManifest.js +72 -0
- package/plugins/workflow-runner/src/expressions.js +198 -15
- package/plugins/workflow-runner/src/index.js +40 -8
- package/plugins/workflow-runner/src/jsonPayload.js +1 -1
- package/plugins/workflow-runner/src/loader.js +286 -72
- package/plugins/workflow-runner/src/multimodal.js +37 -0
- package/plugins/workflow-runner/src/recovery.js +49 -39
- package/plugins/workflow-runner/src/runner.js +802 -287
- package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
- package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
- package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
- package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
- package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
- package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
- package/plugins/workflow-runner/src/taskIdentity.js +296 -0
- package/plugins/workflow-runner/src/types.js +19 -9
- package/plugins/workflow-runner/src/workflowState.js +516 -33
- package/skills/adversarial-review/SKILL.md +0 -1
- package/skills/ai-image/SKILL.md +89 -0
- package/skills/ai-image/scripts/generate.mjs +497 -0
- package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
- package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
- package/skills/ai-image/scripts/generate_test.mjs +74 -0
- package/skills/browser-automation/SKILL.md +47 -0
- package/skills/docx/SKILL.md +25 -3
- package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
- package/skills/docx/scripts/opc_check.py +253 -0
- package/skills/image-gen-openrouter/SKILL.md +10 -2
- package/skills/image-stats/SKILL.md +65 -0
- package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/image_stats.py +368 -0
- package/skills/image-stats/scripts/image_stats_test.py +181 -0
- package/skills/pptx/SKILL.md +123 -95
- package/skills/pptx/design.md +82 -0
- package/skills/pptx/examples/README.md +29 -0
- package/skills/pptx/examples/hybrid_example.mjs +107 -0
- package/skills/pptx/examples/hybrid_example.pptx +0 -0
- package/skills/pptx/pptxgenjs.md +41 -0
- package/skills/pptx/scripts/deck_helpers.mjs +224 -0
- package/skills/pptx/scripts/layout_lint.py +293 -0
- package/skills/pptx/scripts/layout_lint_test.py +160 -0
- package/skills/pptx/scripts/opc_check.py +339 -0
- package/skills/pptx/scripts/svg_fallback.mjs +145 -0
- package/skills/pptx/svg.md +67 -0
- package/skills/tts-zh/SKILL.md +19 -2
- package/skills/ui-design/SKILL.md +158 -0
- package/skills/ui-design/claude_design.md +309 -0
- package/skills/ui-design/evals/evals.json +254 -0
- package/skills/ui-design/references/aesthetic-direction.md +83 -0
- package/skills/ui-design/references/claude-design-protocol.md +36 -0
- package/skills/ui-design/references/design-context.md +53 -0
- package/skills/ui-design/references/design-execution-playbook.md +82 -0
- package/skills/ui-design/references/export-pptx.md +124 -0
- package/skills/ui-design/references/layout-composition.md +654 -0
- package/skills/ui-design/references/preview-feedback.md +37 -0
- package/skills/ui-design/references/source-image-layout.md +189 -0
- package/skills/ui-design/references/starter-components.md +78 -0
- package/skills/ui-design/references/vi-aegis-vector.md +144 -0
- package/skills/ui-design/references/vi-amber-prism.md +133 -0
- package/skills/ui-design/references/vi-aqua-glass.md +200 -0
- package/skills/ui-design/references/vi-comfort-vision.md +147 -0
- package/skills/ui-design/references/vi-ember-prism.md +140 -0
- package/skills/ui-design/references/vi-nebula-console.md +151 -0
- package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
- package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
- package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
- package/skills/ui-design/references/vi-precision-horizon.md +215 -0
- package/skills/ui-design/references/vi-solar-relay.md +130 -0
- package/skills/ui-design/references/vi-template-workflow.md +48 -0
- package/skills/ui-design/references/vi-verdant-grid.md +141 -0
- package/skills/ui-design/references/vi-violet-meridian.md +172 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/assemble_material.py +217 -0
- package/skills/ui-design/scripts/assemble_material_test.py +149 -0
- package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
- package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
- package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
- package/skills/ui-design/scripts/check_language_policy.py +351 -0
- package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
- package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
- package/skills/ui-design/scripts/deck_pages_check.py +158 -0
- package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
- package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
- package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
- package/skills/ui-design/scripts/design_budget_check.py +271 -0
- package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
- package/skills/ui-design/scripts/design_repair_hints.py +162 -0
- package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
- package/skills/ui-design/scripts/design_style_check.py +397 -0
- package/skills/ui-design/scripts/design_style_check_test.py +233 -0
- package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
- package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
- package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
- package/skills/ui-design/scripts/extract_source_assets.py +607 -0
- package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
- package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
- package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
- package/skills/ui-design/scripts/image_plan_lint.py +571 -0
- package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
- package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
- package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
- package/skills/ui-design/scripts/layout_balance.py +881 -0
- package/skills/ui-design/scripts/layout_balance_test.py +927 -0
- package/skills/ui-design/scripts/normalize_design_json.py +272 -0
- package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
- package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
- package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
- package/skills/ui-design/scripts/resolve_source_input.py +270 -0
- package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
- package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
- package/skills/ui-design/scripts/source_usage_check.py +581 -0
- package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
- package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
- package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
- package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
- package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
- package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
- package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
- package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
- package/skills/ui-design/tests/e2e/rubric.json +54 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
- package/skills/video-compose/SKILL.md +8 -1
- package/skills/web-crawler/SKILL.md +70 -0
- package/skills/web-crawler/scripts/crawl.mjs +538 -0
- package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
- package/skills/xlsx/SKILL.md +12 -0
- package/skills/xlsx/scripts/opc_check.py +253 -0
- package/src/advisorState.js +22 -0
- package/src/askBridge.js +97 -3
- package/src/bootstrap/workingDir.js +12 -1
- package/src/cli/args.js +17 -1
- package/src/cli/configMenu.js +236 -0
- package/src/cli/configWizard.js +7 -5
- package/src/cli/print.js +37 -4
- package/src/cli/streamJson.js +77 -24
- package/src/cli/wizards/advisorWizard.js +147 -0
- package/src/cli/wizards/fieldForm.js +149 -0
- package/src/cli/wizards/imageWizard.js +60 -0
- package/src/cli/wizards/openRouterWizard.js +23 -0
- package/src/cli/wizards/providerPresets.js +95 -0
- package/src/cli/wizards/providerWizard.js +219 -0
- package/src/cli/wizards/searchWizard.js +23 -0
- package/src/cli/wizards/ttsWizard.js +34 -0
- package/src/config/configFile.js +441 -83
- package/src/config/configService.js +446 -0
- package/src/config.js +24 -64
- package/src/context/agentHome.js +9 -0
- package/src/context/archive.js +7 -2
- package/src/context/compact.js +8 -5
- package/src/context/contextLock.js +81 -0
- package/src/context/persistContext.js +30 -11
- package/src/context/reactiveCompact.js +4 -2
- package/src/context/recentDirs.js +21 -61
- package/src/context/sessionPath.js +97 -23
- package/src/context/sessionRegistry.js +303 -24
- package/src/context/tokenCounter.js +18 -0
- package/src/context/workspaceIndex.js +731 -0
- package/src/infra/verification.js +28 -15
- package/src/llm/client.js +394 -159
- package/src/llm/providers/anthropicFormat.js +241 -0
- package/src/llm/providers/openaiFormat.js +190 -0
- package/src/loop.js +219 -32
- package/src/main.js +61 -11
- package/src/messageQueue.js +97 -0
- package/src/multimodal.js +203 -0
- package/src/plugin-sdk.js +15 -0
- package/src/plugins/commandRouter.js +3 -0
- package/src/plugins/hookEngine.js +5 -1
- package/src/plugins/pluginLifecycle.js +28 -0
- package/src/plugins/pluginLoader.js +2 -1
- package/src/plugins/pluginRunner.js +49 -21
- package/src/plugins/transcript.js +4 -3
- package/src/prompts/pluginList.js +5 -5
- package/src/prompts/projectInstructions.js +3 -3
- package/src/prompts/promptAssembly.js +133 -144
- package/src/prompts/skillList.js +12 -22
- package/src/prompts/system.js +0 -1
- package/src/session/runAgentTurn.js +70 -0
- package/src/session/runTurn.js +41 -6
- package/src/toolGate.js +17 -0
- package/src/tools/advisor/advisor.js +379 -0
- package/src/tools/agent/agent.js +555 -0
- package/src/tools/agent/agentTypes.js +59 -0
- package/src/tools/agent/coordinator.js +93 -0
- package/src/tools/agent/worktree.js +270 -0
- package/src/tools/ask/ask.js +7 -2
- package/src/tools/bash/bash.js +112 -18
- package/src/tools/download/download.js +144 -0
- package/src/tools/edit/edit.js +1 -0
- package/src/tools/edit/multi-edit.js +1 -0
- package/src/tools/genimage/genimage.js +242 -0
- package/src/tools/genspeech/genspeech.js +67 -0
- package/src/tools/glob/glob.js +1 -0
- package/src/tools/grep/grep.js +3 -0
- package/src/tools/grep/rgPath.js +2 -0
- package/src/tools/index.js +25 -2
- package/src/tools/read/read.js +1 -0
- package/src/tools/shared/killTree.js +52 -0
- package/src/tools/toolProgressBridge.js +48 -0
- package/src/tools/webbrowser/agentBrowserPath.js +258 -0
- package/src/tools/webbrowser/browser.js +59 -42
- package/src/tools/webbrowser/browserControl.js +118 -0
- package/src/tools/webbrowser/browserDaemon.js +97 -0
- package/src/tools/webbrowser/browserService.js +27 -0
- package/src/tools/webbrowser/browserStream.js +119 -0
- package/src/tools/webbrowser/webbrowser.js +699 -130
- package/src/tools/webfetch/webfetch.js +1 -0
- package/src/tools/websearch/websearch.js +134 -51
- package/src/tools/write/write.js +1 -0
- package/src/ui/App.js +249 -22
- package/src/ui/AskQuestion.js +29 -10
- package/src/ui/InputBox.js +104 -12
- package/src/ui/LiveArea.js +112 -19
- package/src/ui/MessageList.js +100 -14
- package/src/ui/ModelPicker.js +56 -0
- package/src/ui/QueuedMessagesBanner.js +14 -0
- package/src/ui/Root.js +18 -10
- package/src/ui/SessionPicker.js +102 -0
- package/src/ui/StatusLine.js +13 -4
- package/src/ui/WorkspacePicker.js +55 -0
- package/src/ui/figures.js +44 -0
- package/src/ui/hooks/askSlot.js +82 -0
- package/src/ui/hooks/useChat.js +358 -64
- package/src/ui/hooks/usePasteHandler.js +11 -1
- package/src/ui/hooks/useTextBuffer.js +1 -3
- package/src/ui/liveViewport.js +5 -1
- package/src/ui/markdown.js +206 -0
- package/src/ui/textBuffer.js +0 -7
- package/src/ui/theme.js +32 -0
- package/src/utils/debugLog.js +0 -4
- package/src/utils/resourcePaths.js +2 -2
- package/src/utils/stripThink.js +50 -0
- package/src/web/browserBridge.js +447 -0
- package/src/web/protocol.js +19 -0
- package/workflows/adaptive-research.yaml +13 -5
- package/workflows/book-review-short.yaml +36 -10
- package/workflows/e2e-write-greet.yaml +6 -2
- package/workflows/illustrate.yaml +227 -0
- package/workflows/plan.yaml +460 -179
- package/workflows/ppt.yaml +1659 -0
- package/workflows/ralph-loop.yaml +199 -0
- package/workflows/schema.json +81 -23
- package/workflows/webppt.yaml +496 -0
- package/workflows/word.yaml +710 -0
- package/workflows/youtube-shorts.yaml +46 -15
- package/MINIMAL-TRACE.md +0 -77
- package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
- package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
- package/plugins/ralph-wiggum/README.md +0 -197
- package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
- package/plugins/ralph-wiggum/commands/help.md +0 -137
- package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
- package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
- package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
- package/plugins/ralph-wiggum/plugin.js +0 -280
- package/plugins/ralph-wiggum/src/goalState.js +0 -258
- package/plugins/ralph-wiggum/src/sentinels.js +0 -76
- package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
- package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
- package/plugins/workflow-runner/src/miniReAct.js +0 -223
- package/plugins/workflow-runner/src/voteHelpers.js +0 -110
- package/skills/config/SKILL.md +0 -123
- package/skills/frontend-design/LICENSE.txt +0 -177
- package/skills/frontend-design/SKILL.md +0 -42
- package/src/ui/ProgressPanel.js +0 -54
- package/src/ui/StreamingBlock.js +0 -11
- package/src/ui/ToolStatus.js +0 -11
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""检查 PPT design.json 是否真正进入产品详情页式构图。
|
|
4
|
+
|
|
5
|
+
这个脚本只做确定性护栏:字段缺失、风格枚举非法、卡片/表格作为默认表达等会报 ERROR。
|
|
6
|
+
它不评判审美好坏,但会阻止弱模型退回「archetype + 卡片墙」旧路径。
|
|
7
|
+
|
|
8
|
+
视觉优先内容形状(2026-07-02,治「文本阅读型页面」)
|
|
9
|
+
====================================================
|
|
10
|
+
参考 visual-first 标准:一页一个想法、上屏文字 3–5 秒读完、短语不成段、
|
|
11
|
+
论证细节进 speaker_notes(讲稿)或拆页。三条确定性检查:
|
|
12
|
+
① max_visible_words 对账(ERROR)——页面自己声明了上屏字数预算却从无人核对,
|
|
13
|
+
takeaway+points 实际字数超声明×1.2 即违约(标题另由 design_budget_check 管长度);
|
|
14
|
+
② 影像叙事页内容形状(ERROR)——hero-scene 等「图是主角」页,points 只许 ≤3 条短语(≤20 字);
|
|
15
|
+
③ 文章化倾向(WARNING,保守防把对的改错)——≥4 条要点且平均 >18 字 = 文本阅读型排布。
|
|
16
|
+
`speaker_notes` 是可选讲稿字段(论证/机制/出处放这里,导出进 PPT 备注栏),
|
|
17
|
+
**不计入上屏字数、不参与卡片/表格倾向文本扫描**。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import json
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
ALLOWED_STYLES = {
|
|
30
|
+
"product-hero",
|
|
31
|
+
"feature-theater",
|
|
32
|
+
"detail-closeup",
|
|
33
|
+
"scenario-fullbleed",
|
|
34
|
+
"data-visual",
|
|
35
|
+
"gallery-strip",
|
|
36
|
+
"chapter-cinematic",
|
|
37
|
+
"quote-focus",
|
|
38
|
+
}
|
|
39
|
+
ALLOWED_PAGE_GRAMMARS = {
|
|
40
|
+
"hero-scene",
|
|
41
|
+
"cinematic-feature",
|
|
42
|
+
"product-closeup",
|
|
43
|
+
"image-mosaic",
|
|
44
|
+
"spec-overlay",
|
|
45
|
+
"data-visual",
|
|
46
|
+
"before-after-scene",
|
|
47
|
+
}
|
|
48
|
+
TEXT_ZONES = {"left", "right", "top", "bottom", "center"}
|
|
49
|
+
TEXT_OVERLAY_MODES = {"scrim", "floating-text", "frosted-caption", "side-copy", "none"}
|
|
50
|
+
PRIMARY_VISUAL_TYPES = {
|
|
51
|
+
"ai-background",
|
|
52
|
+
"foreground-ai",
|
|
53
|
+
"source-enhanced-background",
|
|
54
|
+
"source-enhanced-hero",
|
|
55
|
+
"transparent-closeup",
|
|
56
|
+
"scene-photo",
|
|
57
|
+
"product-render",
|
|
58
|
+
"html-svg-chart",
|
|
59
|
+
"image-mosaic",
|
|
60
|
+
}
|
|
61
|
+
REQUIRED_FIELDS = {
|
|
62
|
+
"one_sentence_takeaway",
|
|
63
|
+
"composition_style",
|
|
64
|
+
"visual_focus",
|
|
65
|
+
"text_zone",
|
|
66
|
+
"visual_role",
|
|
67
|
+
"copy_budget",
|
|
68
|
+
"page_grammar",
|
|
69
|
+
"image_weight",
|
|
70
|
+
"text_overlay_mode",
|
|
71
|
+
"primary_visual_type",
|
|
72
|
+
"safe_text_zone",
|
|
73
|
+
"max_visible_words",
|
|
74
|
+
"main_visual_layer",
|
|
75
|
+
"supporting_visual_layer",
|
|
76
|
+
"text_overlay_zone",
|
|
77
|
+
"visual_story",
|
|
78
|
+
"semantic_image_prompt",
|
|
79
|
+
"forbidden_layout",
|
|
80
|
+
"visual_asset_contract",
|
|
81
|
+
}
|
|
82
|
+
TEXT_FOCUS_RE = re.compile(r"文字|文案|内容|要点|标题|段落|copy|text", re.I)
|
|
83
|
+
CARD_RE = re.compile(r"卡片墙|悬浮卡|网格|四卡|五卡|六卡|card|cards|panel", re.I)
|
|
84
|
+
TABLE_RE = re.compile(r"表格|矩阵|table|matrix", re.I)
|
|
85
|
+
NO_TEXT_RE = re.compile(r"no\s+(?:text|words|letters|logo|logos)|不要.*(?:文字|字母|logo|水印)", re.I)
|
|
86
|
+
HTML_SVG_RE = re.compile(r"^html-svg|HTML/SVG|svg|图表|柱状|折线|参数轴|坐标轴|流程|架构|关系|时间线|bar|line|axis|chart", re.I)
|
|
87
|
+
GENERIC_BG_RE = re.compile(r"^(?:vi)?(?:同色系)?(?:抽象)?(?:渐变|纹理|背景|氛围|色块|底图)$", re.I)
|
|
88
|
+
BACKGROUND_TEXT_MARKER_RE = re.compile(
|
|
89
|
+
r"(?:章节|序号|编号|号码|数字|标号|标签|题注|文字|文案|1\s*/\s*2\s*/\s*3\s*/\s*4|"
|
|
90
|
+
r"\b(?:number|numbers|numbered|chapter|chapters|label|labels|caption|captions|text|words|letters)\b)",
|
|
91
|
+
re.I,
|
|
92
|
+
)
|
|
93
|
+
IMAGE_LED_STYLES = {
|
|
94
|
+
"product-hero",
|
|
95
|
+
"feature-theater",
|
|
96
|
+
"detail-closeup",
|
|
97
|
+
"scenario-fullbleed",
|
|
98
|
+
"chapter-cinematic",
|
|
99
|
+
"quote-focus",
|
|
100
|
+
}
|
|
101
|
+
AI_VISUAL_TYPES = {
|
|
102
|
+
"ai-background",
|
|
103
|
+
"foreground-ai",
|
|
104
|
+
"source-enhanced-background",
|
|
105
|
+
"source-enhanced-hero",
|
|
106
|
+
"transparent-closeup",
|
|
107
|
+
"scene-photo",
|
|
108
|
+
"product-render",
|
|
109
|
+
"image-mosaic",
|
|
110
|
+
}
|
|
111
|
+
# 「图是主角」的影像叙事页语法:文字只许少量短语浮净区(视觉优先内容形状检查 ②)。
|
|
112
|
+
IMAGE_NARRATIVE_GRAMMARS = {"hero-scene", "cinematic-feature", "product-closeup", "before-after-scene"}
|
|
113
|
+
VISIBLE_WORDS_FACTOR = 1.2 # 对账余量:takeaway+points 超声明 max_visible_words×此系数才报
|
|
114
|
+
NARRATIVE_MAX_POINTS = 3 # 影像叙事页 points 条数硬顶
|
|
115
|
+
NARRATIVE_MAX_POINT_LEN = 20 # 影像叙事页单条短语字数硬顶
|
|
116
|
+
PROSE_MIN_POINTS = 4 # 文章化倾向:至少这么多条
|
|
117
|
+
PROSE_AVG_LEN = 18 # 且平均字长超此 → 文本阅读型排布 WARNING
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def points_texts(page: dict) -> list[str]:
|
|
121
|
+
"""归一化上屏要点文本:优先 points,缺则回退 blocks 的 content(与 design_budget_check 同口径)。"""
|
|
122
|
+
def _texts(v) -> list[str]:
|
|
123
|
+
out: list[str] = []
|
|
124
|
+
if not isinstance(v, list):
|
|
125
|
+
return out
|
|
126
|
+
for x in v:
|
|
127
|
+
if isinstance(x, str):
|
|
128
|
+
out.append(x)
|
|
129
|
+
elif isinstance(x, (int, float)):
|
|
130
|
+
out.append(str(x))
|
|
131
|
+
elif isinstance(x, dict):
|
|
132
|
+
c = x.get("content") or x.get("text") or ""
|
|
133
|
+
if isinstance(c, str):
|
|
134
|
+
out.append(c)
|
|
135
|
+
return out
|
|
136
|
+
|
|
137
|
+
pts = _texts(page.get("points"))
|
|
138
|
+
return pts if pts else _texts(page.get("blocks"))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def load_pages(path: Path):
|
|
142
|
+
with path.open(encoding="utf-8-sig") as f:
|
|
143
|
+
data = json.load(f)
|
|
144
|
+
pages = data.get("pages") if isinstance(data, dict) else None
|
|
145
|
+
if not isinstance(pages, list):
|
|
146
|
+
raise ValueError("design.json 必须是包含 pages 数组的对象")
|
|
147
|
+
return pages
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def text_of(value) -> str:
|
|
151
|
+
if value is None:
|
|
152
|
+
return ""
|
|
153
|
+
if isinstance(value, str):
|
|
154
|
+
return value
|
|
155
|
+
if isinstance(value, (int, float, bool)):
|
|
156
|
+
return str(value)
|
|
157
|
+
if isinstance(value, list):
|
|
158
|
+
return " ".join(text_of(v) for v in value)
|
|
159
|
+
if isinstance(value, dict):
|
|
160
|
+
return " ".join(text_of(v) for v in value.values())
|
|
161
|
+
return str(value)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def check_page(page: dict, index: int):
|
|
165
|
+
label = page.get("page", index)
|
|
166
|
+
errors: list[str] = []
|
|
167
|
+
warnings: list[str] = []
|
|
168
|
+
|
|
169
|
+
missing = sorted(k for k in REQUIRED_FIELDS if k not in page)
|
|
170
|
+
if missing:
|
|
171
|
+
errors.append(f"缺少产品详情页式硬字段: {', '.join(missing)}")
|
|
172
|
+
|
|
173
|
+
style = page.get("composition_style")
|
|
174
|
+
if style is not None and style not in ALLOWED_STYLES:
|
|
175
|
+
errors.append(f"composition_style 非法: {style!r}")
|
|
176
|
+
|
|
177
|
+
grammar = page.get("page_grammar")
|
|
178
|
+
if grammar is not None and grammar not in ALLOWED_PAGE_GRAMMARS:
|
|
179
|
+
errors.append(f"page_grammar 非法: {grammar!r}")
|
|
180
|
+
|
|
181
|
+
zone = page.get("text_zone")
|
|
182
|
+
if zone is not None and zone not in TEXT_ZONES:
|
|
183
|
+
errors.append(f"text_zone 非法: {zone!r}")
|
|
184
|
+
safe_zone = page.get("safe_text_zone")
|
|
185
|
+
if safe_zone is not None and safe_zone not in TEXT_ZONES:
|
|
186
|
+
errors.append(f"safe_text_zone 非法: {safe_zone!r}")
|
|
187
|
+
|
|
188
|
+
overlay = page.get("text_overlay_mode")
|
|
189
|
+
if overlay is not None and overlay not in TEXT_OVERLAY_MODES:
|
|
190
|
+
errors.append(f"text_overlay_mode 非法: {overlay!r}")
|
|
191
|
+
|
|
192
|
+
visual_type = page.get("primary_visual_type")
|
|
193
|
+
if visual_type is not None and visual_type not in PRIMARY_VISUAL_TYPES:
|
|
194
|
+
errors.append(f"primary_visual_type 非法: {visual_type!r}")
|
|
195
|
+
|
|
196
|
+
contract = page.get("visual_asset_contract")
|
|
197
|
+
if "visual_asset_contract" in page:
|
|
198
|
+
if not isinstance(contract, dict):
|
|
199
|
+
errors.append("visual_asset_contract 必须是对象,声明背景/前景图片角色契约")
|
|
200
|
+
else:
|
|
201
|
+
for key in ("background_role", "foreground_role"):
|
|
202
|
+
if not str(contract.get(key) or "").strip():
|
|
203
|
+
errors.append(f"visual_asset_contract 缺少 {key}")
|
|
204
|
+
bg_asset = str(contract.get("background_asset") or "").strip()
|
|
205
|
+
fg_asset = str(contract.get("foreground_asset") or "").strip()
|
|
206
|
+
if bg_asset and fg_asset and bg_asset == fg_asset:
|
|
207
|
+
errors.append("visual_asset_contract 背景和前景不能使用同一张图片")
|
|
208
|
+
if fg_asset and re.fullmatch(r"ai_p\d+\.(?:png|jpg|jpeg|webp)", fg_asset, re.I):
|
|
209
|
+
errors.append("前景 AI 插图不能使用 ai_pN.png;必须独立规划 ai_pN_fg.png")
|
|
210
|
+
|
|
211
|
+
main_visual = str(page.get("main_visual_layer") or "").strip()
|
|
212
|
+
supporting_visual = str(page.get("supporting_visual_layer") or "").strip()
|
|
213
|
+
text_overlay_zone = page.get("text_overlay_zone")
|
|
214
|
+
semantic_prompt = str(page.get("semantic_image_prompt") or "").strip()
|
|
215
|
+
forbidden_layout = page.get("forbidden_layout")
|
|
216
|
+
|
|
217
|
+
if main_visual:
|
|
218
|
+
if TEXT_FOCUS_RE.search(main_visual):
|
|
219
|
+
errors.append(f"main_visual_layer 不能是文字/内容本身: {main_visual}")
|
|
220
|
+
if GENERIC_BG_RE.fullmatch(main_visual):
|
|
221
|
+
errors.append(f"main_visual_layer 不能只是抽象渐变/纹理: {main_visual}")
|
|
222
|
+
|
|
223
|
+
if text_overlay_zone is not None and not isinstance(text_overlay_zone, str):
|
|
224
|
+
errors.append("text_overlay_zone 必须是字符串,说明文字净区位置")
|
|
225
|
+
elif isinstance(text_overlay_zone, str) and not text_overlay_zone.strip():
|
|
226
|
+
errors.append("text_overlay_zone 不能为空")
|
|
227
|
+
|
|
228
|
+
if "semantic_image_prompt" in page and not semantic_prompt:
|
|
229
|
+
errors.append("semantic_image_prompt 不能为空")
|
|
230
|
+
if semantic_prompt:
|
|
231
|
+
is_html_svg_prompt = bool(HTML_SVG_RE.search(semantic_prompt))
|
|
232
|
+
if visual_type in AI_VISUAL_TYPES or (isinstance(page.get("visual"), dict) and str(page["visual"].get("route", "")).lower() in {"ai-a", "ai-b", "source-enhanced"}):
|
|
233
|
+
if is_html_svg_prompt:
|
|
234
|
+
errors.append("AI/图生图页面的 semantic_image_prompt 不能写成 html-svg,应是真实画面提示词")
|
|
235
|
+
if not NO_TEXT_RE.search(semantic_prompt):
|
|
236
|
+
errors.append("AI/图生图提示词必须明确 no text / no logo / no letters,避免图片里烧字")
|
|
237
|
+
bg_role = ""
|
|
238
|
+
if isinstance(contract, dict):
|
|
239
|
+
bg_role = str(contract.get("background_role") or "")
|
|
240
|
+
marker_source = " ".join(
|
|
241
|
+
str(page.get(k) or "")
|
|
242
|
+
for k in ("visual_focus", "visual_story", "main_visual_layer")
|
|
243
|
+
)
|
|
244
|
+
if ("background" in bg_role.lower() or visual_type == "ai-background") and BACKGROUND_TEXT_MARKER_RE.search(marker_source):
|
|
245
|
+
errors.append("AI 背景/场景图不得要求生成章节序号、编号、标签或文字;这些必须放到 HTML/SVG 可编辑前景层")
|
|
246
|
+
if visual_type == "html-svg-chart" or grammar == "data-visual":
|
|
247
|
+
if not is_html_svg_prompt:
|
|
248
|
+
errors.append("data-visual / html-svg-chart 页的 semantic_image_prompt 必须声明 html-svg 图表类型")
|
|
249
|
+
|
|
250
|
+
if "forbidden_layout" in page:
|
|
251
|
+
if not isinstance(forbidden_layout, list) or not forbidden_layout:
|
|
252
|
+
errors.append("forbidden_layout 必须是非空数组")
|
|
253
|
+
else:
|
|
254
|
+
joined = " ".join(str(x) for x in forbidden_layout)
|
|
255
|
+
if "card" not in joined.lower() and "卡" not in joined:
|
|
256
|
+
errors.append("forbidden_layout 必须显式禁止 card wall / 卡片墙")
|
|
257
|
+
if "table" not in joined.lower() and "表" not in joined:
|
|
258
|
+
errors.append("forbidden_layout 必须显式禁止 table wall / 表格墙")
|
|
259
|
+
|
|
260
|
+
image_weight = page.get("image_weight")
|
|
261
|
+
if "image_weight" in page:
|
|
262
|
+
if not isinstance(image_weight, (int, float)):
|
|
263
|
+
errors.append("image_weight 必须是 0.0-1.0 数字")
|
|
264
|
+
elif not 0 <= float(image_weight) <= 1:
|
|
265
|
+
errors.append(f"image_weight 越界: {image_weight!r}")
|
|
266
|
+
|
|
267
|
+
max_words = page.get("max_visible_words")
|
|
268
|
+
if "max_visible_words" in page:
|
|
269
|
+
if not isinstance(max_words, int):
|
|
270
|
+
errors.append("max_visible_words 必须是整数")
|
|
271
|
+
elif max_words > 80 and grammar not in {"data-visual", "image-mosaic"}:
|
|
272
|
+
errors.append("max_visible_words 过高,产品详情页式页面不应承载长文")
|
|
273
|
+
|
|
274
|
+
focus = str(page.get("visual_focus") or "").strip()
|
|
275
|
+
if focus and TEXT_FOCUS_RE.search(focus):
|
|
276
|
+
errors.append(f"visual_focus 不能是文字/内容本身: {focus}")
|
|
277
|
+
|
|
278
|
+
if style in IMAGE_LED_STYLES and semantic_prompt and HTML_SVG_RE.search(semantic_prompt):
|
|
279
|
+
errors.append(f"{style} 是影像叙事页,semantic_image_prompt 不应退成 HTML/SVG 图表")
|
|
280
|
+
if style == "quote-focus" and (not supporting_visual or supporting_visual.lower() in {"none", "无"}):
|
|
281
|
+
errors.append("quote-focus 也需要底部场景锚点、氛围背景或前景特写,不能只有文字")
|
|
282
|
+
if grammar == "before-after-scene" and zone == "bottom" and ("左" in focus and "右" in focus):
|
|
283
|
+
errors.append("before-after-scene 的左右对比画面不应把主文字净区放在 bottom,容易回到上下卡片化")
|
|
284
|
+
|
|
285
|
+
budget = page.get("copy_budget")
|
|
286
|
+
if "copy_budget" in page and not isinstance(budget, dict):
|
|
287
|
+
errors.append("copy_budget 必须是对象")
|
|
288
|
+
elif isinstance(budget, dict):
|
|
289
|
+
for key in ("title_max", "takeaway_max", "point_max", "note_max"):
|
|
290
|
+
if key not in budget:
|
|
291
|
+
errors.append(f"copy_budget 缺少 {key}")
|
|
292
|
+
|
|
293
|
+
archetype = str(page.get("archetype") or "")
|
|
294
|
+
scan_page = dict(page)
|
|
295
|
+
# forbidden_layout 里故意会写 no card wall / no table wall,不能反过来当作页面正在使用卡片/表格。
|
|
296
|
+
scan_page.pop("forbidden_layout", None)
|
|
297
|
+
# speaker_notes 是讲稿(论证/机制细节),提到「表格/矩阵/对比」是在讲内容、不是页面布局倾向。
|
|
298
|
+
scan_page.pop("speaker_notes", None)
|
|
299
|
+
all_text = text_of(scan_page)
|
|
300
|
+
if archetype == "cards" and style != "gallery-strip":
|
|
301
|
+
errors.append("cards archetype 只能用于 gallery-strip 或少量证据画廊,不得作为普通正文页默认")
|
|
302
|
+
if CARD_RE.search(all_text) and grammar != "image-mosaic" and style not in {"gallery-strip", "data-visual"}:
|
|
303
|
+
errors.append("页面仍含卡片墙/网格/悬浮卡倾向,但 composition_style 不是 gallery-strip/data-visual")
|
|
304
|
+
if TABLE_RE.search(all_text) and style != "data-visual" and grammar != "data-visual":
|
|
305
|
+
errors.append("页面仍含表格/矩阵倾向,但 composition_style 不是 data-visual")
|
|
306
|
+
|
|
307
|
+
if grammar == "image-mosaic":
|
|
308
|
+
if isinstance(max_words, int) and max_words > 55:
|
|
309
|
+
errors.append("image-mosaic 只能是影像拼贴 + 短标签,max_visible_words 不应超过 55")
|
|
310
|
+
if isinstance(image_weight, (int, float)) and float(image_weight) < 0.65:
|
|
311
|
+
errors.append("image-mosaic 的图片视觉重量必须至少 0.65")
|
|
312
|
+
elif grammar in {"hero-scene", "cinematic-feature", "product-closeup", "before-after-scene"}:
|
|
313
|
+
if isinstance(image_weight, (int, float)) and float(image_weight) < 0.60:
|
|
314
|
+
errors.append(f"{grammar} 的图片/场景视觉重量必须至少 0.60")
|
|
315
|
+
elif grammar == "spec-overlay":
|
|
316
|
+
if visual_type == "html-svg-chart":
|
|
317
|
+
warnings.append("spec-overlay 应以产品/场景图为主,参数只作叠层;若图表主导请改 data-visual")
|
|
318
|
+
|
|
319
|
+
points = page.get("points")
|
|
320
|
+
if isinstance(points, list) and len(points) > 5 and style not in {"data-visual", "gallery-strip"} and grammar not in {"data-visual", "image-mosaic"}:
|
|
321
|
+
warnings.append("points 超过 5 条,建议拆页或改成 data-visual")
|
|
322
|
+
|
|
323
|
+
# ── 视觉优先内容形状(治「文本阅读型页面」:屏上=短语/数字/图,论证进 speaker_notes/拆页)──
|
|
324
|
+
point_list = points_texts(page)
|
|
325
|
+
takeaway = str(page.get("one_sentence_takeaway") or "")
|
|
326
|
+
# ① max_visible_words 对账:页面自声明的上屏字数预算 vs takeaway+points 实际字数
|
|
327
|
+
# (标题长度另由 design_budget_check 管,不重复计入;1.2 余量防误伤贴线页)。
|
|
328
|
+
if isinstance(max_words, int) and max_words > 0:
|
|
329
|
+
visible = len(takeaway.strip()) + sum(len(str(p).strip()) for p in point_list)
|
|
330
|
+
visible_cap = int(max_words * VISIBLE_WORDS_FACTOR)
|
|
331
|
+
if visible > visible_cap:
|
|
332
|
+
errors.append(
|
|
333
|
+
f"上屏文案 {visible} 字超自声明 max_visible_words={max_words}(含余量上限 {visible_cap})"
|
|
334
|
+
":points 短语化 / 论证移入 speaker_notes / 拆页——观众 3–5 秒要能读完一页"
|
|
335
|
+
)
|
|
336
|
+
# ② 影像叙事页内容形状:图是主角,文字只许 ≤3 条短语(≤20 字)浮净区
|
|
337
|
+
if grammar in IMAGE_NARRATIVE_GRAMMARS and point_list:
|
|
338
|
+
longest = max(len(str(p).strip()) for p in point_list)
|
|
339
|
+
if len(point_list) > NARRATIVE_MAX_POINTS or longest > NARRATIVE_MAX_POINT_LEN:
|
|
340
|
+
errors.append(
|
|
341
|
+
f"{grammar} 影像叙事页文字超载({len(point_list)} 条 / 最长 {longest} 字)"
|
|
342
|
+
f":points ≤{NARRATIVE_MAX_POINTS} 条且每条 ≤{NARRATIVE_MAX_POINT_LEN} 字短语;"
|
|
343
|
+
"完整论证进 speaker_notes 或拆页"
|
|
344
|
+
)
|
|
345
|
+
# ③ 文章化倾向(保守 WARNING):多条长句 = 文本阅读型排布
|
|
346
|
+
if len(point_list) >= PROSE_MIN_POINTS:
|
|
347
|
+
avg_len = sum(len(str(p).strip()) for p in point_list) / len(point_list)
|
|
348
|
+
if avg_len > PROSE_AVG_LEN:
|
|
349
|
+
warnings.append(
|
|
350
|
+
f"文本阅读型排布倾向({len(point_list)} 条要点平均 {avg_len:.0f} 字)"
|
|
351
|
+
":改短语/数字标签、把论述交给图表或 speaker_notes"
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
title = str(page.get("title") or "")
|
|
355
|
+
if len(title) > 18 and style not in {"chapter-cinematic", "quote-focus"}:
|
|
356
|
+
warnings.append("标题偏长,可能破坏产品详情页式一眼重点")
|
|
357
|
+
|
|
358
|
+
return label, errors, warnings
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def main(argv=None):
|
|
362
|
+
parser = argparse.ArgumentParser()
|
|
363
|
+
parser.add_argument("design", help="build/design.json")
|
|
364
|
+
parser.add_argument("-v", "--verbose", action="store_true")
|
|
365
|
+
args = parser.parse_args(argv)
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
pages = load_pages(Path(args.design))
|
|
369
|
+
except Exception as exc: # noqa: BLE001
|
|
370
|
+
print(f"DESIGN_STYLE_FAILED: {exc}")
|
|
371
|
+
return 2
|
|
372
|
+
|
|
373
|
+
all_errors: list[str] = []
|
|
374
|
+
all_warnings: list[str] = []
|
|
375
|
+
for idx, page in enumerate(pages, 1):
|
|
376
|
+
if not isinstance(page, dict):
|
|
377
|
+
all_errors.append(f"第{idx}页: page 必须是对象")
|
|
378
|
+
continue
|
|
379
|
+
label, errors, warnings = check_page(page, idx)
|
|
380
|
+
all_errors.extend(f"第{label}页: {e}" for e in errors)
|
|
381
|
+
all_warnings.extend(f"第{label}页: {w}" for w in warnings)
|
|
382
|
+
|
|
383
|
+
for item in all_errors:
|
|
384
|
+
print(f"ERROR: {item}")
|
|
385
|
+
if args.verbose:
|
|
386
|
+
for item in all_warnings:
|
|
387
|
+
print(f"WARNING: {item}")
|
|
388
|
+
|
|
389
|
+
if all_errors:
|
|
390
|
+
print(f"DESIGN_STYLE_FAILED: {len(all_errors)} 个阻断问题")
|
|
391
|
+
return 1
|
|
392
|
+
print(f"DESIGN_STYLE_OK: {len(pages)} 页")
|
|
393
|
+
return 0
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
if __name__ == "__main__":
|
|
397
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
import unittest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
SCRIPT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "design_style_check.py")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_check(design):
|
|
16
|
+
with tempfile.NamedTemporaryFile("w", suffix=".json", encoding="utf-8", delete=False) as f:
|
|
17
|
+
json.dump(design, f, ensure_ascii=False)
|
|
18
|
+
path = f.name
|
|
19
|
+
try:
|
|
20
|
+
# 显式 UTF-8:中文 Windows 上 text=True 默认 locale=GBK,解码子进程 UTF-8 中文输出必崩
|
|
21
|
+
# (与 visual_quality_gate.py::_run 同一防御;bash.ts 的 PYTHONUTF8 只护工作流内运行)。
|
|
22
|
+
return subprocess.run(
|
|
23
|
+
[sys.executable, SCRIPT, path, "-v"],
|
|
24
|
+
text=True,
|
|
25
|
+
encoding="utf-8",
|
|
26
|
+
errors="replace",
|
|
27
|
+
stdout=subprocess.PIPE,
|
|
28
|
+
stderr=subprocess.PIPE,
|
|
29
|
+
check=False,
|
|
30
|
+
)
|
|
31
|
+
finally:
|
|
32
|
+
os.unlink(path)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def good_page(**extra):
|
|
36
|
+
page = {
|
|
37
|
+
"page": 1,
|
|
38
|
+
"archetype": "fullbleed",
|
|
39
|
+
"composition": "全幅浮层",
|
|
40
|
+
"composition_style": "scenario-fullbleed",
|
|
41
|
+
"page_grammar": "hero-scene",
|
|
42
|
+
"one_sentence_takeaway": "看见现场状态,才能更快闭环处置",
|
|
43
|
+
"visual_focus": "能源电网巡检场景背景",
|
|
44
|
+
"text_zone": "left",
|
|
45
|
+
"safe_text_zone": "left",
|
|
46
|
+
"visual_role": "AI 场景背景",
|
|
47
|
+
"primary_visual_type": "ai-background",
|
|
48
|
+
"image_weight": 0.78,
|
|
49
|
+
"text_overlay_mode": "scrim",
|
|
50
|
+
"max_visible_words": 42,
|
|
51
|
+
"copy_budget": {"title_max": 14, "takeaway_max": 24, "point_max": 3, "note_max": 24},
|
|
52
|
+
"main_visual_layer": "AI 能源电网巡检场景全幅底图",
|
|
53
|
+
"supporting_visual_layer": "底部设备剪影",
|
|
54
|
+
"text_overlay_zone": "left-center",
|
|
55
|
+
"visual_story": "用变电站巡检场景让观众先看到方案落地现场",
|
|
56
|
+
"semantic_image_prompt": "cinematic energy grid inspection scene, wide clean text-safe area on the left, premium product-detail-page lighting, no text, no words, no letters, no logos, no watermark",
|
|
57
|
+
"forbidden_layout": ["no card wall", "no table wall", "no white floating panels", "no image frame"],
|
|
58
|
+
"visual_asset_contract": {
|
|
59
|
+
"background_role": "ai_background",
|
|
60
|
+
"background_asset": "ai_p1.png",
|
|
61
|
+
"foreground_role": "none",
|
|
62
|
+
"foreground_asset": "",
|
|
63
|
+
},
|
|
64
|
+
"title": "现场状态可追溯",
|
|
65
|
+
"key_point": "用连续现场记录替代选择性拍照",
|
|
66
|
+
"points": ["覆盖更完整", "处置更直观"],
|
|
67
|
+
"visual": {"route": "ai-B", "side": "full"},
|
|
68
|
+
}
|
|
69
|
+
page.update(extra)
|
|
70
|
+
return page
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class DesignStyleCheckTest(unittest.TestCase):
|
|
74
|
+
def test_good_design_passes(self):
|
|
75
|
+
res = run_check({"pages": [good_page()]})
|
|
76
|
+
self.assertEqual(res.returncode, 0, res.stdout + res.stderr)
|
|
77
|
+
self.assertIn("DESIGN_STYLE_OK", res.stdout)
|
|
78
|
+
|
|
79
|
+
def test_missing_hard_fields_fails(self):
|
|
80
|
+
page = good_page()
|
|
81
|
+
del page["composition_style"]
|
|
82
|
+
del page["visual_focus"]
|
|
83
|
+
res = run_check({"pages": [page]})
|
|
84
|
+
self.assertNotEqual(res.returncode, 0)
|
|
85
|
+
self.assertIn("缺少产品详情页式硬字段", res.stdout)
|
|
86
|
+
|
|
87
|
+
def test_cards_archetype_without_gallery_fails(self):
|
|
88
|
+
res = run_check({"pages": [good_page(archetype="cards", composition_style="feature-theater")]})
|
|
89
|
+
self.assertNotEqual(res.returncode, 0)
|
|
90
|
+
self.assertIn("cards archetype", res.stdout)
|
|
91
|
+
|
|
92
|
+
def test_table_needs_data_visual(self):
|
|
93
|
+
res = run_check({"pages": [good_page(blocks=[{"content": "对比矩阵表格", "render": "html+text"}])]})
|
|
94
|
+
self.assertNotEqual(res.returncode, 0)
|
|
95
|
+
self.assertIn("表格/矩阵", res.stdout)
|
|
96
|
+
|
|
97
|
+
def test_text_focus_fails(self):
|
|
98
|
+
res = run_check({"pages": [good_page(visual_focus="文字内容")]})
|
|
99
|
+
self.assertNotEqual(res.returncode, 0)
|
|
100
|
+
self.assertIn("visual_focus", res.stdout)
|
|
101
|
+
|
|
102
|
+
def test_main_visual_cannot_be_gradient(self):
|
|
103
|
+
res = run_check({"pages": [good_page(main_visual_layer="渐变")]})
|
|
104
|
+
self.assertNotEqual(res.returncode, 0)
|
|
105
|
+
self.assertIn("main_visual_layer", res.stdout)
|
|
106
|
+
|
|
107
|
+
def test_ai_prompt_must_forbid_text(self):
|
|
108
|
+
res = run_check({"pages": [good_page(semantic_image_prompt="cinematic energy grid inspection scene")]})
|
|
109
|
+
self.assertNotEqual(res.returncode, 0)
|
|
110
|
+
self.assertIn("no text", res.stdout)
|
|
111
|
+
|
|
112
|
+
def test_ai_background_cannot_request_chapter_numbers(self):
|
|
113
|
+
res = run_check({"pages": [good_page(
|
|
114
|
+
visual_focus="4个章节序号浮在浅蓝矢量准线上",
|
|
115
|
+
visual_story="背景用 1/2/3/4 编号给读者导览",
|
|
116
|
+
semantic_image_prompt="cinematic energy operations route background, no text, no words, no letters, no logos",
|
|
117
|
+
)]})
|
|
118
|
+
self.assertNotEqual(res.returncode, 0)
|
|
119
|
+
self.assertIn("章节序号", res.stdout)
|
|
120
|
+
|
|
121
|
+
def test_data_visual_requires_html_svg_prompt(self):
|
|
122
|
+
res = run_check({"pages": [good_page(
|
|
123
|
+
composition_style="data-visual",
|
|
124
|
+
page_grammar="data-visual",
|
|
125
|
+
primary_visual_type="html-svg-chart",
|
|
126
|
+
image_weight=0.55,
|
|
127
|
+
semantic_image_prompt="cinematic background, no text, no words, no letters",
|
|
128
|
+
)]})
|
|
129
|
+
self.assertNotEqual(res.returncode, 0)
|
|
130
|
+
self.assertIn("html-svg", res.stdout)
|
|
131
|
+
|
|
132
|
+
def test_forbidden_layout_must_ban_cards_and_tables(self):
|
|
133
|
+
res = run_check({"pages": [good_page(forbidden_layout=["no image frame"])]})
|
|
134
|
+
self.assertNotEqual(res.returncode, 0)
|
|
135
|
+
self.assertIn("forbidden_layout", res.stdout)
|
|
136
|
+
|
|
137
|
+
def test_image_mosaic_requires_image_weight_and_short_words(self):
|
|
138
|
+
res = run_check({"pages": [good_page(
|
|
139
|
+
composition_style="gallery-strip",
|
|
140
|
+
page_grammar="image-mosaic",
|
|
141
|
+
image_weight=0.5,
|
|
142
|
+
max_visible_words=90,
|
|
143
|
+
)]})
|
|
144
|
+
self.assertNotEqual(res.returncode, 0)
|
|
145
|
+
self.assertIn("image-mosaic", res.stdout)
|
|
146
|
+
|
|
147
|
+
def test_invalid_page_grammar_fails(self):
|
|
148
|
+
res = run_check({"pages": [good_page(page_grammar="text-cards")]})
|
|
149
|
+
self.assertNotEqual(res.returncode, 0)
|
|
150
|
+
self.assertIn("page_grammar", res.stdout)
|
|
151
|
+
|
|
152
|
+
def test_visual_asset_contract_required(self):
|
|
153
|
+
page = good_page()
|
|
154
|
+
del page["visual_asset_contract"]
|
|
155
|
+
res = run_check({"pages": [page]})
|
|
156
|
+
self.assertNotEqual(res.returncode, 0)
|
|
157
|
+
self.assertIn("visual_asset_contract", res.stdout)
|
|
158
|
+
|
|
159
|
+
def test_visual_asset_contract_rejects_repeated_asset(self):
|
|
160
|
+
res = run_check({"pages": [good_page(visual_asset_contract={
|
|
161
|
+
"background_role": "ai_background",
|
|
162
|
+
"background_asset": "ai_p7.png",
|
|
163
|
+
"foreground_role": "foreground_ai",
|
|
164
|
+
"foreground_asset": "ai_p7.png",
|
|
165
|
+
})]})
|
|
166
|
+
self.assertNotEqual(res.returncode, 0)
|
|
167
|
+
self.assertIn("背景和前景不能使用同一张图片", res.stdout)
|
|
168
|
+
|
|
169
|
+
def test_foreground_ai_must_use_fg_name(self):
|
|
170
|
+
res = run_check({"pages": [good_page(visual_asset_contract={
|
|
171
|
+
"background_role": "ai_background",
|
|
172
|
+
"background_asset": "ai_p7.png",
|
|
173
|
+
"foreground_role": "foreground_ai",
|
|
174
|
+
"foreground_asset": "ai_p7.png",
|
|
175
|
+
})]})
|
|
176
|
+
self.assertNotEqual(res.returncode, 0)
|
|
177
|
+
self.assertIn("ai_pN.png", res.stdout)
|
|
178
|
+
|
|
179
|
+
# ── 视觉优先内容形状(治「文本阅读型页面」)──
|
|
180
|
+
|
|
181
|
+
def test_visible_words_ledger_overflow_fails(self):
|
|
182
|
+
# 对账检查:takeaway+points 实际字数超页面自声明的 max_visible_words×1.2 → ERROR。
|
|
183
|
+
# 用非影像叙事语法(spec-overlay)隔离对账规则本身。
|
|
184
|
+
long_point = "这是一条超过二十个字的完整论述句用来测试上屏字数对账检查" # 28 字
|
|
185
|
+
res = run_check({"pages": [good_page(
|
|
186
|
+
page_grammar="spec-overlay",
|
|
187
|
+
points=[long_point, long_point, long_point, long_point],
|
|
188
|
+
)]})
|
|
189
|
+
self.assertNotEqual(res.returncode, 0)
|
|
190
|
+
self.assertIn("超自声明 max_visible_words", res.stdout)
|
|
191
|
+
|
|
192
|
+
def test_narrative_page_text_overload_fails(self):
|
|
193
|
+
# 影像叙事页(hero-scene 等「图是主角」):points >3 条 或 单条 >20 字 → ERROR。
|
|
194
|
+
res = run_check({"pages": [good_page(points=["短语一", "短语二", "短语三", "短语四"])]})
|
|
195
|
+
self.assertNotEqual(res.returncode, 0)
|
|
196
|
+
self.assertIn("影像叙事页文字超载", res.stdout)
|
|
197
|
+
res2 = run_check({"pages": [good_page(points=["这一条是超过二十个字的完整论述句子用于测试"])]})
|
|
198
|
+
self.assertNotEqual(res2.returncode, 0)
|
|
199
|
+
self.assertIn("影像叙事页文字超载", res2.stdout)
|
|
200
|
+
|
|
201
|
+
def test_narrative_short_phrases_pass(self):
|
|
202
|
+
# 3 条 ≤20 字短语 = 合规影像叙事页,不得误伤。
|
|
203
|
+
res = run_check({"pages": [good_page(points=["覆盖更完整", "处置更直观", "留痕更可信"])]})
|
|
204
|
+
self.assertEqual(res.returncode, 0, res.stdout + res.stderr)
|
|
205
|
+
self.assertIn("DESIGN_STYLE_OK", res.stdout)
|
|
206
|
+
|
|
207
|
+
def test_prose_tendency_warning_only(self):
|
|
208
|
+
# 文章化倾向(≥4 条且平均 >18 字)在预算内 → 只 WARNING 不阻断(保守防把对的改错)。
|
|
209
|
+
p = "全景相机行业落地效果对比论述句共十九字" # 19 字
|
|
210
|
+
res = run_check({"pages": [good_page(
|
|
211
|
+
composition_style="data-visual",
|
|
212
|
+
page_grammar="data-visual",
|
|
213
|
+
primary_visual_type="html-svg-chart",
|
|
214
|
+
semantic_image_prompt="html-svg:柱状图",
|
|
215
|
+
max_visible_words=80,
|
|
216
|
+
points=[p, p, p, p],
|
|
217
|
+
visual={"route": "html", "side": "none"},
|
|
218
|
+
)]})
|
|
219
|
+
self.assertEqual(res.returncode, 0, res.stdout + res.stderr)
|
|
220
|
+
self.assertIn("文本阅读型排布倾向", res.stdout)
|
|
221
|
+
|
|
222
|
+
def test_speaker_notes_excluded_from_layout_scan(self):
|
|
223
|
+
# speaker_notes 是讲稿:提到「对比矩阵/表格」是在讲内容,不得触发卡片/表格倾向 ERROR;
|
|
224
|
+
# 也不计入上屏字数对账。
|
|
225
|
+
res = run_check({"pages": [good_page(
|
|
226
|
+
speaker_notes="这一页的论证:与传统方案的对比矩阵和参数表格细节见附录,演讲时口述即可。",
|
|
227
|
+
)]})
|
|
228
|
+
self.assertEqual(res.returncode, 0, res.stdout + res.stderr)
|
|
229
|
+
self.assertIn("DESIGN_STYLE_OK", res.stdout)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
if __name__ == "__main__":
|
|
233
|
+
unittest.main(verbosity=2)
|