@litfamily/litopencode 1.0.7 → 1.0.9
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/CHANGELOG.md +18 -0
- package/README-Ko-KR.md +314 -31
- package/README.md +312 -29
- package/dist/activation-primary-prompts.d.ts +1 -0
- package/dist/activation-primary-prompts.js +16 -0
- package/dist/activation-probe.js +4 -1
- package/dist/activation-prompt-utils.d.ts +1 -1
- package/dist/activation-routing.d.ts +6 -1
- package/dist/activation-routing.js +174 -16
- package/dist/activation-workflow-prompts.d.ts +1 -1
- package/dist/activation-workflow-prompts.js +18 -17
- package/dist/activation.d.ts +3 -2
- package/dist/activation.js +6 -6
- package/dist/agents/defaults.js +14 -6
- package/dist/cli/command-aliases.js +24 -22
- package/dist/cli/doctor.js +3 -0
- package/dist/cli/install-report.d.ts +1 -0
- package/dist/cli/install-report.js +4 -0
- package/dist/cli/install.js +7 -4
- package/dist/cli/managed-skill-assets.d.ts +1 -1
- package/dist/cli/managed-skill-assets.js +15 -5
- package/dist/cli/motion-runtime.d.ts +6 -0
- package/dist/cli/motion-runtime.js +46 -0
- package/dist/cli/native-skill-install.js +32 -9
- package/dist/cli/native-skill-integrity.js +1 -0
- package/dist/cli/native-skills.d.ts +2 -0
- package/dist/cli/native-skills.js +2 -1
- package/dist/cli/office-dependencies.d.ts +14 -0
- package/dist/cli/office-dependencies.js +24 -0
- package/dist/cli.js +13 -3
- package/dist/commands.d.ts +2 -11
- package/dist/commands.js +9 -17
- package/dist/deliverable-hedge-guard.d.ts +20 -21
- package/dist/deliverable-hedge-guard.js +234 -275
- package/dist/features.d.ts +81 -2
- package/dist/features.js +80 -13
- package/dist/hooks.d.ts +3 -2
- package/dist/hooks.js +4 -4
- package/dist/ignition.d.ts +1 -1
- package/dist/ignition.js +15 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/interface-handoff.d.ts +1 -0
- package/dist/interface-handoff.js +1 -0
- package/dist/motion-image-reads.d.ts +6 -0
- package/dist/motion-image-reads.js +46 -0
- package/dist/server.js +13 -11
- package/dist/skill-renames.d.ts +4 -2
- package/dist/skill-renames.js +12 -4
- package/dist/skills.d.ts +31 -3
- package/dist/skills.js +53 -9
- package/dist/tools.js +4 -4
- package/docs/ab/S11/baseline-desktop.webp +0 -0
- package/docs/ab/S11/baseline-phone.webp +0 -0
- package/docs/ab/S11/lit-desktop.webp +0 -0
- package/docs/ab/S11/lit-phone.webp +0 -0
- package/docs/ab/S3/baseline-desktop.webp +0 -0
- package/docs/ab/S3/baseline-phone.webp +0 -0
- package/docs/ab/S3/lit-desktop.webp +0 -0
- package/docs/ab/S3/lit-phone.webp +0 -0
- package/docs/ab/S4/baseline-desktop.webp +0 -0
- package/docs/ab/S4/baseline-phone.webp +0 -0
- package/docs/ab/S4/lit-desktop.webp +0 -0
- package/docs/ab/S4/lit-phone.webp +0 -0
- package/docs/ab/S5/lit-slides.webp +0 -0
- package/docs/ab/S7/lit-diagram.webp +0 -0
- package/docs/ab/S8/baseline-slides.webp +0 -0
- package/docs/ab/S8/lit-slides.webp +0 -0
- package/docs/ab/S9/lit-pages.webp +0 -0
- package/docs/assets/cover-motion-still.webp +0 -0
- package/docs/assets/cover-motion.webp +0 -0
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/assets/skills/autoconference.webp +0 -0
- package/docs/assets/skills/automatic-guards.webp +0 -0
- package/docs/assets/skills/autoresearch.webp +0 -0
- package/docs/assets/skills/browser-drive.webp +0 -0
- package/docs/assets/skills/debugging.webp +0 -0
- package/docs/assets/skills/deep-interview.webp +0 -0
- package/docs/assets/skills/doctor-installer.webp +0 -0
- package/docs/assets/skills/durable-litgoal.webp +0 -0
- package/docs/assets/skills/frontend-ui-ux.webp +0 -0
- package/docs/assets/skills/lit-burnoff-file.webp +0 -0
- package/docs/assets/skills/lit-burnoff.webp +0 -0
- package/docs/assets/skills/lit-code.webp +0 -0
- package/docs/assets/skills/lit-commit.webp +0 -0
- package/docs/assets/skills/lit-comprehend.webp +0 -0
- package/docs/assets/skills/lit-crucible.webp +0 -0
- package/docs/assets/skills/lit-diagram-drawer.webp +0 -0
- package/docs/assets/skills/lit-docx.webp +0 -0
- package/docs/assets/skills/lit-fetch.webp +0 -0
- package/docs/assets/skills/lit-handoff.webp +0 -0
- package/docs/assets/skills/lit-humanizer.webp +0 -0
- package/docs/assets/skills/lit-init.webp +0 -0
- package/docs/assets/skills/lit-plan.webp +0 -0
- package/docs/assets/skills/lit-pptx.webp +0 -0
- package/docs/assets/skills/lit-recap.webp +0 -0
- package/docs/assets/skills/lit-scientific-visualization.webp +0 -0
- package/docs/assets/skills/lit-typographic-motion.webp +0 -0
- package/docs/assets/skills/litresearch.webp +0 -0
- package/docs/assets/skills/litwork.webp +0 -0
- package/docs/assets/skills/lsp-setup.webp +0 -0
- package/docs/assets/skills/lsp.webp +0 -0
- package/docs/assets/skills/readme-studio.webp +0 -0
- package/docs/assets/skills/refactor.webp +0 -0
- package/docs/assets/skills/review-work.webp +0 -0
- package/docs/assets/skills/rules.webp +0 -0
- package/docs/assets/skills/start-work.webp +0 -0
- package/docs/assets/skills/structural-search.webp +0 -0
- package/docs/assets/skills/visual-qa.webp +0 -0
- package/docs/assets/skills/wikify.webp +0 -0
- package/docs/reference-Ko-KR.md +5 -1
- package/docs/reference.md +6 -2
- package/package.json +1 -1
- package/rules/bundled-rules/lit-humanizer.md +12 -0
- package/skills/autoconference/SKILL.md +1 -1
- package/skills/autoconference/assets/report_template.md +1 -1
- package/skills/autoresearch/references/type-checklists.md +1 -1
- package/skills/browser-drive/SKILL.md +19 -5
- package/skills/browser-drive/scripts/capability-probe.mjs +26 -5
- package/skills/frontend-ui-ux/SKILL.md +10 -15
- package/skills/frontend-ui-ux/data/THIRD-PARTY-NOTICE.txt +10 -0
- package/skills/frontend-ui-ux/references/adaptive-layout.md +3 -11
- package/skills/frontend-ui-ux/references/brand-and-imagery.md +6 -11
- package/skills/frontend-ui-ux/references/complete-contract.md +10 -3
- package/skills/frontend-ui-ux/references/composition.md +1 -7
- package/skills/frontend-ui-ux/references/craft-floor.md +94 -0
- package/skills/frontend-ui-ux/references/creative-directions.md +1 -7
- package/skills/frontend-ui-ux/references/evidence-review.md +2 -12
- package/skills/frontend-ui-ux/references/implementation-platforms.md +1 -4
- package/skills/frontend-ui-ux/references/inclusive-interface.md +1 -3
- package/skills/frontend-ui-ux/references/interaction-motion.md +1 -7
- package/skills/frontend-ui-ux/references/motion-guide.md +125 -0
- package/skills/frontend-ui-ux/references/operating-lanes.md +2 -8
- package/skills/frontend-ui-ux/references/product-direction.md +1 -6
- package/skills/frontend-ui-ux/references/production.md +12 -5
- package/skills/frontend-ui-ux/references/redesign-playbook.md +1 -7
- package/skills/frontend-ui-ux/references/slop-register.md +103 -0
- package/skills/frontend-ui-ux/references/system-foundations.md +1 -8
- package/skills/frontend-ui-ux/references/taste-direction.md +4 -11
- package/skills/frontend-ui-ux/references/visual-language.md +1 -6
- package/skills/frontend-ui-ux/references/visual-reconstruction.md +2 -7
- package/skills/frontend-ui-ux/scripts/probe-page.mjs +326 -0
- package/skills/frontend-ui-ux/scripts/probe-server.mjs +24 -0
- package/skills/frontend-ui-ux/scripts/probe-static.mjs +42 -0
- package/skills/frontend-ui-ux/scripts/probe-thresholds.json +86 -0
- package/skills/frontend-ui-ux/scripts/probe.mjs +183 -0
- package/skills/lit-comprehend/SKILL.md +10 -11
- package/skills/lit-comprehend/references/artifact-template.md +2 -18
- package/skills/lit-comprehend/scripts/verify-explainer.ts +0 -14
- package/skills/lit-diagram-drawer/NOTICE.md +33 -0
- package/skills/lit-diagram-drawer/SKILL.md +101 -0
- package/skills/lit-diagram-drawer/assets/examples/type-architecture-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-architecture-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-architecture-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-bar-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-bar-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-bar-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-beeswarm-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-beeswarm-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-beeswarm-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-bubble-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-bubble-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-bubble-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-bump-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-bump-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-bump-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-data-flow-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-data-flow-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-data-flow-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-datalake-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-datalake-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-datalake-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-db-schema-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-db-schema-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-db-schema-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-dependency-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-dependency-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-dependency-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-deployment-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-deployment-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-deployment-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-dp-integration-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-dp-integration-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-dp-integration-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-dp-security-matrix-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-dp-security-matrix-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-dp-security-matrix-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-er-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-er-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-er-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-fishbone-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-fishbone-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-fishbone-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-flowchart-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-flowchart-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-flowchart-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-gantt-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-gantt-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-gantt-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-heatmap-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-heatmap-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-heatmap-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-high-level-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-high-level-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-high-level-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-high-level-vertical-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-high-level-vertical-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-high-level-vertical-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-import-drawio-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-import-drawio-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-import-drawio-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-import-excalidraw-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-import-excalidraw-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-import-excalidraw-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-import-mermaid-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-import-mermaid-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-import-mermaid-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-it-state-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-it-state-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-it-state-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-journey-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-journey-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-journey-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-kanban-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-kanban-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-kanban-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-layers-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-layers-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-layers-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-line-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-line-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-line-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-loop-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-loop-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-loop-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-loop-terminal-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-loop-terminal-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-loop-terminal-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-marimekko-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-marimekko-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-marimekko-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-medallion-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-medallion-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-medallion-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-nested-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-nested-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-nested-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-org-chart-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-org-chart-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-org-chart-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-paved-road-animated-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-paved-road-animated-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-paved-road-animated-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-polar-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-polar-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-polar-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-policy-trace-animated-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-policy-trace-animated-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-policy-trace-animated-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-process-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-process-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-process-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-pyramid-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-pyramid-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-pyramid-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-quadrant-consultant-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-quadrant-consultant-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-quadrant-consultant-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-quadrant-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-quadrant-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-quadrant-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-queue-animated-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-queue-animated-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-queue-animated-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-radar-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-radar-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-radar-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-ridgeline-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-ridgeline-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-ridgeline-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-sankey-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-sankey-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-sankey-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-scatter-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-scatter-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-scatter-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-sequence-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-sequence-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-sequence-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-sequence-oauth-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-sequence-oauth-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-sequence-oauth-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-slopegraph-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-slopegraph-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-slopegraph-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-state-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-state-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-state-lifecycle-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-state-lifecycle-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-state-lifecycle-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-state-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-story-map-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-story-map-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-story-map-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-streamgraph-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-streamgraph-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-streamgraph-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-swimlane-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-swimlane-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-swimlane-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-timeline-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-timeline-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-timeline-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-tree-block-decomposition-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-tree-block-decomposition-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-tree-block-decomposition-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-tree-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-tree-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-tree-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-treemap-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-treemap-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-treemap-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-uml-class-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-uml-class-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-uml-class-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-venn-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-venn-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-venn-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-wardley-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-wardley-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-wardley-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-waterfall-dark.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-waterfall-full.html +1 -0
- package/skills/lit-diagram-drawer/assets/examples/type-waterfall-light.html +1 -0
- package/skills/lit-diagram-drawer/assets/fonts/OFL.txt +94 -0
- package/skills/lit-diagram-drawer/assets/fonts/PretendardVariable.woff2 +0 -0
- package/skills/lit-diagram-drawer/assets/fonts/provenance.json +23 -0
- package/skills/lit-diagram-drawer/assets/icons.html +231 -0
- package/skills/lit-diagram-drawer/assets/licenses/devicon-MIT.txt +20 -0
- package/skills/lit-diagram-drawer/assets/licenses/diagram-design-MIT.txt +21 -0
- package/skills/lit-diagram-drawer/assets/licenses/log-z-MIT.txt +21 -0
- package/skills/lit-diagram-drawer/assets/licenses/simple-icons-CC0.md +30 -0
- package/skills/lit-diagram-drawer/assets/licenses/tabler-MIT.txt +21 -0
- package/skills/lit-diagram-drawer/examples/01-service-architecture/after.html +1 -0
- package/skills/lit-diagram-drawer/examples/01-service-architecture/brief.md +33 -0
- package/skills/lit-diagram-drawer/examples/02-release-decision/after.html +1 -0
- package/skills/lit-diagram-drawer/examples/02-release-decision/brief.md +18 -0
- package/skills/lit-diagram-drawer/examples/03-data-retention/after.html +1 -0
- package/skills/lit-diagram-drawer/examples/03-data-retention/brief.md +19 -0
- package/skills/lit-diagram-drawer/examples/04-oauth-sequence/after.html +1 -0
- package/skills/lit-diagram-drawer/examples/04-oauth-sequence/brief.md +17 -0
- package/skills/lit-diagram-drawer/examples/05-review-lanes/after.html +1 -0
- package/skills/lit-diagram-drawer/examples/05-review-lanes/brief.md +17 -0
- package/skills/lit-diagram-drawer/examples/06-rollout-gates/after.html +1 -0
- package/skills/lit-diagram-drawer/examples/06-rollout-gates/brief.md +17 -0
- package/skills/lit-diagram-drawer/examples/07-deployment-boundary/after.html +1 -0
- package/skills/lit-diagram-drawer/examples/07-deployment-boundary/brief.md +19 -0
- package/skills/lit-diagram-drawer/examples/08-policy-traces/after.html +1 -0
- package/skills/lit-diagram-drawer/examples/08-policy-traces/brief.md +20 -0
- package/skills/lit-diagram-drawer/references/accessibility.md +37 -0
- package/skills/lit-diagram-drawer/references/export.md +17 -0
- package/skills/lit-diagram-drawer/references/import-drawio.md +13 -0
- package/skills/lit-diagram-drawer/references/import-excalidraw.md +11 -0
- package/skills/lit-diagram-drawer/references/import-mermaid.md +12 -0
- package/skills/lit-diagram-drawer/references/import-schema.md +28 -0
- package/skills/lit-diagram-drawer/references/korean-typography.md +34 -0
- package/skills/lit-diagram-drawer/references/motion.md +29 -0
- package/skills/lit-diagram-drawer/references/office-pptx-docx.md +28 -0
- package/skills/lit-diagram-drawer/references/output-spec.md +48 -0
- package/skills/lit-diagram-drawer/references/primitive-annotation.md +12 -0
- package/skills/lit-diagram-drawer/references/primitive-icons.md +21 -0
- package/skills/lit-diagram-drawer/references/primitive-sketchy.md +11 -0
- package/skills/lit-diagram-drawer/references/primitive-terminal.md +11 -0
- package/skills/lit-diagram-drawer/references/profiles.md +24 -0
- package/skills/lit-diagram-drawer/references/semantic-patterns.md +27 -0
- package/skills/lit-diagram-drawer/references/style-guide.md +67 -0
- package/skills/lit-diagram-drawer/references/type-architecture.md +51 -0
- package/skills/lit-diagram-drawer/references/type-bar.md +51 -0
- package/skills/lit-diagram-drawer/references/type-beeswarm.md +49 -0
- package/skills/lit-diagram-drawer/references/type-bubble.md +49 -0
- package/skills/lit-diagram-drawer/references/type-bump.md +49 -0
- package/skills/lit-diagram-drawer/references/type-catalog.json +1433 -0
- package/skills/lit-diagram-drawer/references/type-data-flow.md +51 -0
- package/skills/lit-diagram-drawer/references/type-datalake.md +49 -0
- package/skills/lit-diagram-drawer/references/type-db-schema.md +49 -0
- package/skills/lit-diagram-drawer/references/type-dependency.md +49 -0
- package/skills/lit-diagram-drawer/references/type-deployment.md +49 -0
- package/skills/lit-diagram-drawer/references/type-dp-integration.md +49 -0
- package/skills/lit-diagram-drawer/references/type-dp-security-matrix.md +49 -0
- package/skills/lit-diagram-drawer/references/type-er.md +49 -0
- package/skills/lit-diagram-drawer/references/type-fishbone.md +49 -0
- package/skills/lit-diagram-drawer/references/type-flowchart.md +49 -0
- package/skills/lit-diagram-drawer/references/type-gantt.md +49 -0
- package/skills/lit-diagram-drawer/references/type-heatmap.md +53 -0
- package/skills/lit-diagram-drawer/references/type-high-level-vertical.md +53 -0
- package/skills/lit-diagram-drawer/references/type-high-level.md +53 -0
- package/skills/lit-diagram-drawer/references/type-import-drawio.md +53 -0
- package/skills/lit-diagram-drawer/references/type-import-excalidraw.md +53 -0
- package/skills/lit-diagram-drawer/references/type-import-mermaid.md +53 -0
- package/skills/lit-diagram-drawer/references/type-it-state.md +54 -0
- package/skills/lit-diagram-drawer/references/type-journey.md +54 -0
- package/skills/lit-diagram-drawer/references/type-kanban.md +54 -0
- package/skills/lit-diagram-drawer/references/type-layers.md +54 -0
- package/skills/lit-diagram-drawer/references/type-line.md +54 -0
- package/skills/lit-diagram-drawer/references/type-loop-terminal.md +53 -0
- package/skills/lit-diagram-drawer/references/type-loop.md +54 -0
- package/skills/lit-diagram-drawer/references/type-marimekko.md +54 -0
- package/skills/lit-diagram-drawer/references/type-medallion.md +54 -0
- package/skills/lit-diagram-drawer/references/type-nested.md +59 -0
- package/skills/lit-diagram-drawer/references/type-org-chart.md +58 -0
- package/skills/lit-diagram-drawer/references/type-paved-road-animated.md +72 -0
- package/skills/lit-diagram-drawer/references/type-polar.md +67 -0
- package/skills/lit-diagram-drawer/references/type-policy-trace-animated.md +69 -0
- package/skills/lit-diagram-drawer/references/type-process.md +58 -0
- package/skills/lit-diagram-drawer/references/type-pyramid.md +59 -0
- package/skills/lit-diagram-drawer/references/type-quadrant-consultant.md +60 -0
- package/skills/lit-diagram-drawer/references/type-quadrant.md +57 -0
- package/skills/lit-diagram-drawer/references/type-queue-animated.md +63 -0
- package/skills/lit-diagram-drawer/references/type-radar.md +57 -0
- package/skills/lit-diagram-drawer/references/type-ridgeline.md +63 -0
- package/skills/lit-diagram-drawer/references/type-sankey.md +68 -0
- package/skills/lit-diagram-drawer/references/type-scatter.md +60 -0
- package/skills/lit-diagram-drawer/references/type-sequence-oauth.md +66 -0
- package/skills/lit-diagram-drawer/references/type-sequence.md +63 -0
- package/skills/lit-diagram-drawer/references/type-slopegraph.md +65 -0
- package/skills/lit-diagram-drawer/references/type-state-lifecycle.md +53 -0
- package/skills/lit-diagram-drawer/references/type-state.md +59 -0
- package/skills/lit-diagram-drawer/references/type-story-map.md +52 -0
- package/skills/lit-diagram-drawer/references/type-streamgraph.md +53 -0
- package/skills/lit-diagram-drawer/references/type-swimlane.md +52 -0
- package/skills/lit-diagram-drawer/references/type-timeline.md +52 -0
- package/skills/lit-diagram-drawer/references/type-tree-block-decomposition.md +57 -0
- package/skills/lit-diagram-drawer/references/type-tree.md +54 -0
- package/skills/lit-diagram-drawer/references/type-treemap.md +54 -0
- package/skills/lit-diagram-drawer/references/type-uml-class.md +52 -0
- package/skills/lit-diagram-drawer/references/type-venn.md +51 -0
- package/skills/lit-diagram-drawer/references/type-wardley.md +52 -0
- package/skills/lit-diagram-drawer/references/type-waterfall.md +54 -0
- package/skills/lit-diagram-drawer/references/verifier-guide.md +59 -0
- package/skills/lit-diagram-drawer/scripts/block-registry.mjs +43 -0
- package/skills/lit-diagram-drawer/scripts/brief-contract.mjs +94 -0
- package/skills/lit-diagram-drawer/scripts/capability-probe.mjs +132 -0
- package/skills/lit-diagram-drawer/scripts/check-visible-text.mjs +15 -0
- package/skills/lit-diagram-drawer/scripts/craft-thresholds.json +15 -0
- package/skills/lit-diagram-drawer/scripts/diagram-metrics.mjs +216 -0
- package/skills/lit-diagram-drawer/scripts/doctor.mjs +8 -0
- package/skills/lit-diagram-drawer/scripts/drawio-extract.mjs +214 -0
- package/skills/lit-diagram-drawer/scripts/excalidraw-extract.mjs +151 -0
- package/skills/lit-diagram-drawer/scripts/export.mjs +133 -0
- package/skills/lit-diagram-drawer/scripts/import-shared.mjs +130 -0
- package/skills/lit-diagram-drawer/scripts/mermaid-extract.mjs +173 -0
- package/skills/lit-diagram-drawer/scripts/office-safety.mjs +120 -0
- package/skills/lit-diagram-drawer/scripts/renderer-safety.mjs +46 -0
- package/skills/lit-diagram-drawer/scripts/test-block-registry.mjs +20 -0
- package/skills/lit-diagram-drawer/scripts/test-brief-contract.mjs +27 -0
- package/skills/lit-diagram-drawer/scripts/test-importers.mjs +119 -0
- package/skills/lit-diagram-drawer/scripts/test-office-safety.mjs +67 -0
- package/skills/lit-diagram-drawer/scripts/test-sequence-verifier.mjs +28 -0
- package/skills/lit-diagram-drawer/scripts/test-visual-quality.mjs +68 -0
- package/skills/lit-diagram-drawer/scripts/verify-all.mjs +74 -0
- package/skills/lit-diagram-drawer/scripts/verify-brief.mjs +47 -0
- package/skills/lit-diagram-drawer/scripts/verify-coverage.mjs +56 -0
- package/skills/lit-diagram-drawer/scripts/verify-diagram.mjs +26 -0
- package/skills/lit-diagram-drawer/scripts/verify-motion.mjs +12 -0
- package/skills/lit-diagram-drawer/scripts/verify-type.mjs +48 -0
- package/skills/lit-diagram-drawer/scripts/visual-quality.mjs +363 -0
- package/skills/lit-docx/LICENSE-MIT.txt +21 -0
- package/skills/lit-docx/SKILL.md +78 -0
- package/skills/lit-docx/references/frontmatter_schema.md +42 -0
- package/skills/lit-docx/references/journal_style_spec.md +17 -0
- package/skills/lit-docx/references/markdown_quality_checklist.md +37 -0
- package/skills/lit-docx/references/slop_phrase_list.yaml +58 -0
- package/skills/lit-docx/references/slop_rules.md +35 -0
- package/skills/lit-docx/requirements.in +5 -0
- package/skills/lit-docx/requirements.lock +289 -0
- package/skills/lit-docx/run.py +64 -0
- package/skills/lit-docx/scripts/clean_markdown.py +95 -0
- package/skills/lit-docx/scripts/convert_md_to_docx.py +1047 -0
- package/skills/lit-docx/scripts/convert_md_to_pdf.py +281 -0
- package/skills/lit-docx/scripts/convert_pdf.py +130 -0
- package/skills/lit-docx/scripts/craft-thresholds.json +11 -0
- package/skills/lit-docx/scripts/craft_docx.py +83 -0
- package/skills/lit-docx/scripts/edit_docx.py +297 -0
- package/skills/lit-docx/scripts/embed_images.py +78 -0
- package/skills/lit-docx/scripts/generate_docx_templates.py +491 -0
- package/skills/lit-docx/scripts/slop_lint.py +485 -0
- package/skills/lit-docx/scripts/visual_audit.py +41 -0
- package/skills/lit-docx/templates/docx/acs.docx +0 -0
- package/skills/lit-docx/templates/docx/elsevier.docx +0 -0
- package/skills/lit-docx/templates/docx/ieee.docx +0 -0
- package/skills/lit-docx/templates/docx/korean-generic.docx +0 -0
- package/skills/lit-docx/templates/docx/nature.docx +0 -0
- package/skills/lit-docx/templates/latex/acs/README.md +5 -0
- package/skills/lit-docx/templates/latex/acs/template.tex +37 -0
- package/skills/lit-docx/templates/latex/elsevier/README.md +5 -0
- package/skills/lit-docx/templates/latex/elsevier/template.tex +38 -0
- package/skills/lit-docx/templates/latex/ieee/README.md +5 -0
- package/skills/lit-docx/templates/latex/ieee/template.tex +37 -0
- package/skills/lit-docx/templates/latex/korean-generic/README.md +5 -0
- package/skills/lit-docx/templates/latex/korean-generic/template.tex +37 -0
- package/skills/lit-docx/templates/latex/nature/README.md +5 -0
- package/skills/lit-docx/templates/latex/nature/template.tex +37 -0
- package/skills/lit-docx/templates/registry.yaml +1366 -0
- package/skills/lit-humanizer/NOTICE +160 -0
- package/skills/lit-humanizer/SKILL.md +122 -0
- package/skills/lit-humanizer/assets/always-on-rule.md +9 -0
- package/skills/lit-humanizer/assets/report-skeleton.md +25 -0
- package/skills/lit-humanizer/assets/slide-text-skeleton.md +28 -0
- package/skills/lit-humanizer/examples/accessibility-note/after.md +7 -0
- package/skills/lit-humanizer/examples/accessibility-note/before.md +7 -0
- package/skills/lit-humanizer/examples/api-reference/after.md +9 -0
- package/skills/lit-humanizer/examples/api-reference/before.md +15 -0
- package/skills/lit-humanizer/examples/changelog/after.md +5 -0
- package/skills/lit-humanizer/examples/changelog/before.md +12 -0
- package/skills/lit-humanizer/examples/code-comments/after.md +10 -0
- package/skills/lit-humanizer/examples/code-comments/before.md +19 -0
- package/skills/lit-humanizer/examples/commit/after.md +5 -0
- package/skills/lit-humanizer/examples/commit/before.md +7 -0
- package/skills/lit-humanizer/examples/data-release/after.md +7 -0
- package/skills/lit-humanizer/examples/data-release/before.md +9 -0
- package/skills/lit-humanizer/examples/english-essay/after.md +5 -0
- package/skills/lit-humanizer/examples/english-essay/before.md +5 -0
- package/skills/lit-humanizer/examples/incident-update/after.md +7 -0
- package/skills/lit-humanizer/examples/incident-update/before.md +7 -0
- package/skills/lit-humanizer/examples/ko-essay/after.md +7 -0
- package/skills/lit-humanizer/examples/ko-essay/before.md +9 -0
- package/skills/lit-humanizer/examples/ko-report/after.md +9 -0
- package/skills/lit-humanizer/examples/ko-report/before.md +13 -0
- package/skills/lit-humanizer/examples/ko-slide/after.md +19 -0
- package/skills/lit-humanizer/examples/ko-slide/before.md +21 -0
- package/skills/lit-humanizer/examples/meeting-brief/after.md +9 -0
- package/skills/lit-humanizer/examples/meeting-brief/before.md +11 -0
- package/skills/lit-humanizer/examples/policy-faq/after.md +9 -0
- package/skills/lit-humanizer/examples/policy-faq/before.md +9 -0
- package/skills/lit-humanizer/examples/pr/after.md +15 -0
- package/skills/lit-humanizer/examples/pr/before.md +17 -0
- package/skills/lit-humanizer/examples/project-update/after.md +7 -0
- package/skills/lit-humanizer/examples/project-update/before.md +9 -0
- package/skills/lit-humanizer/examples/readme/after.md +12 -0
- package/skills/lit-humanizer/examples/readme/before.md +14 -0
- package/skills/lit-humanizer/examples/report/after.md +13 -0
- package/skills/lit-humanizer/examples/report/before.md +11 -0
- package/skills/lit-humanizer/examples/research-abstract/after.md +5 -0
- package/skills/lit-humanizer/examples/research-abstract/before.md +5 -0
- package/skills/lit-humanizer/examples/slides/after.md +19 -0
- package/skills/lit-humanizer/examples/slides/before.md +19 -0
- package/skills/lit-humanizer/examples/troubleshooting/after.md +11 -0
- package/skills/lit-humanizer/examples/troubleshooting/before.md +11 -0
- package/skills/lit-humanizer/fixtures/ko-metrics-golden.json +49 -0
- package/skills/lit-humanizer/fixtures/office/minimal.docx +0 -0
- package/skills/lit-humanizer/fixtures/office/minimal.pptx +0 -0
- package/skills/lit-humanizer/fixtures/pos-real.txt +26 -0
- package/skills/lit-humanizer/fixtures/rule-cases.json +306 -0
- package/skills/lit-humanizer/references/code-patterns.md +468 -0
- package/skills/lit-humanizer/references/deliverable-channels.md +111 -0
- package/skills/lit-humanizer/references/en-patterns-checklist.md +43 -0
- package/skills/lit-humanizer/references/en-patterns-content.md +243 -0
- package/skills/lit-humanizer/references/en-patterns-structure.md +375 -0
- package/skills/lit-humanizer/references/en-patterns.md +57 -0
- package/skills/lit-humanizer/references/ko-metrics.md +33 -0
- package/skills/lit-humanizer/references/ko-patterns-a-d.md +659 -0
- package/skills/lit-humanizer/references/ko-patterns-e-j.md +387 -0
- package/skills/lit-humanizer/references/ko-patterns.md +31 -0
- package/skills/lit-humanizer/references/rewrite-playbook.md +54 -0
- package/skills/lit-humanizer/references/taxonomy.md +153 -0
- package/skills/lit-humanizer/rules.json +884 -0
- package/skills/lit-humanizer/scripts/core.mjs +208 -0
- package/skills/lit-humanizer/scripts/detect.mjs +91 -0
- package/skills/lit-humanizer/scripts/extract_office_text.py +89 -0
- package/skills/lit-humanizer/scripts/guard-scan.mjs +36 -0
- package/skills/lit-humanizer/scripts/ko-metrics.mjs +124 -0
- package/skills/lit-humanizer/scripts/test-fixtures.mjs +54 -0
- package/skills/lit-humanizer/scripts/test-ko-metrics.mjs +27 -0
- package/skills/lit-pptx/FORBIDDEN_TERMS.json +55 -0
- package/skills/lit-pptx/NOTICE.md +3 -0
- package/skills/lit-pptx/SKILL.md +76 -0
- package/skills/lit-pptx/agents/analyst.md +49 -0
- package/skills/lit-pptx/agents/antithesis.md +59 -0
- package/skills/lit-pptx/agents/architect.md +44 -0
- package/skills/lit-pptx/agents/critic.md +42 -0
- package/skills/lit-pptx/agents/synthesizer.md +47 -0
- package/skills/lit-pptx/agents/thesis.md +33 -0
- package/skills/lit-pptx/assets/azure/circle-lg.png +0 -0
- package/skills/lit-pptx/assets/azure/circle-md.png +0 -0
- package/skills/lit-pptx/assets/azure/circle-sm.png +0 -0
- package/skills/lit-pptx/fonts/a2z-font/A2Z-Black.otf +0 -0
- package/skills/lit-pptx/fonts/a2z-font/A2Z-Bold.otf +0 -0
- package/skills/lit-pptx/fonts/a2z-font/A2Z-Light.otf +0 -0
- package/skills/lit-pptx/fonts/a2z-font/A2Z-Medium.otf +0 -0
- package/skills/lit-pptx/fonts/a2z-font/A2Z-Regular.otf +0 -0
- package/skills/lit-pptx/fonts/a2z-font/OFL.txt +94 -0
- package/skills/lit-pptx/fonts/a2z-font/SOURCE.md +29 -0
- package/skills/lit-pptx/package-lock.json +747 -0
- package/skills/lit-pptx/package.json +13 -0
- package/skills/lit-pptx/pretendard-font/LICENSE.txt +94 -0
- package/skills/lit-pptx/pretendard-font/public/static/LitOpenCodeSans-Bold.otf +0 -0
- package/skills/lit-pptx/pretendard-font/public/static/LitOpenCodeSans-Regular.otf +0 -0
- package/skills/lit-pptx/requirements.in +2 -0
- package/skills/lit-pptx/requirements.lock +285 -0
- package/skills/lit-pptx/run.mjs +71 -0
- package/skills/lit-pptx/scripts/check_ooxml.py +47 -0
- package/skills/lit-pptx/scripts/compile-deck.js +447 -0
- package/skills/lit-pptx/scripts/craft-thresholds.json +26 -0
- package/skills/lit-pptx/scripts/craft_extras.py +224 -0
- package/skills/lit-pptx/scripts/embed_fonts.py +204 -0
- package/skills/lit-pptx/scripts/layout_inventory.py +70 -0
- package/skills/lit-pptx/scripts/learn_template.py +433 -0
- package/skills/lit-pptx/scripts/lib/derive-palette.js +176 -0
- package/skills/lit-pptx/scripts/lib/directive-parser.js +244 -0
- package/skills/lit-pptx/scripts/lib/font-map.js +79 -0
- package/skills/lit-pptx/scripts/lib/gen-bg.js +85 -0
- package/skills/lit-pptx/scripts/lib/gen-gradient-assets.js +36 -0
- package/skills/lit-pptx/scripts/lib/layout-resolver.js +737 -0
- package/skills/lit-pptx/scripts/lib/markdown-loader.js +136 -0
- package/skills/lit-pptx/scripts/lib/render-adapter-html.js +364 -0
- package/skills/lit-pptx/scripts/lib/render-adapter-pptx.js +1848 -0
- package/skills/lit-pptx/scripts/lib/slide-ast.js +534 -0
- package/skills/lit-pptx/scripts/lib/spec-validator.js +94 -0
- package/skills/lit-pptx/scripts/lib/template-registry.js +355 -0
- package/skills/lit-pptx/scripts/qa_deck.py +773 -0
- package/skills/lit-pptx/scripts/validate_pptx.py +206 -0
- package/skills/lit-pptx/specs/markdown-slide-spec-v1.md +251 -0
- package/skills/lit-pptx/specs/markdown-slide-spec-v2.md +223 -0
- package/skills/lit-pptx/specs/slide-ast-v1.schema.json +229 -0
- package/skills/lit-pptx/specs/slide-ast-v2.schema.json +144 -0
- package/skills/lit-pptx/specs/template-enrollment-contract.md +379 -0
- package/skills/lit-pptx/templates/bauhaus_light.css +4 -0
- package/skills/lit-pptx/templates/boilerplate_professional.css +508 -0
- package/skills/lit-pptx/templates/enrolled/AZURE-A2Z/capabilities.yaml +146 -0
- package/skills/lit-pptx/templates/enrolled/AZURE-A2Z/layout-mapping.yaml +454 -0
- package/skills/lit-pptx/templates/enrolled/AZURE-A2Z/template.yaml +37 -0
- package/skills/lit-pptx/templates/enrolled/AZURE-PRO/capabilities.yaml +154 -0
- package/skills/lit-pptx/templates/enrolled/AZURE-PRO/layout-mapping.yaml +483 -0
- package/skills/lit-pptx/templates/enrolled/AZURE-PRO/template.yaml +37 -0
- package/skills/lit-pptx/templates/enrolled/BOILERPLATE-A2Z/capabilities.yaml +179 -0
- package/skills/lit-pptx/templates/enrolled/BOILERPLATE-A2Z/layout-mapping.yaml +299 -0
- package/skills/lit-pptx/templates/enrolled/BOILERPLATE-A2Z/template.yaml +45 -0
- package/skills/lit-pptx/templates/enrolled/BOILERPLATE-PRETENDARD/capabilities.yaml +181 -0
- package/skills/lit-pptx/templates/enrolled/BOILERPLATE-PRETENDARD/layout-mapping.yaml +299 -0
- package/skills/lit-pptx/templates/enrolled/BOILERPLATE-PRETENDARD/template.yaml +44 -0
- package/skills/lit-scientific-visualization/SKILL.md +2 -0
- package/skills/lit-typographic-motion/NOTICE +23 -0
- package/skills/lit-typographic-motion/SKILL.md +77 -0
- package/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +25 -0
- package/skills/lit-typographic-motion/audio-analysis.py +52 -0
- package/skills/lit-typographic-motion/engine/browser.mjs +83 -0
- package/skills/lit-typographic-motion/engine/constants.mjs +177 -0
- package/skills/lit-typographic-motion/engine/contrast.mjs +101 -0
- package/skills/lit-typographic-motion/engine/encode.mjs +46 -0
- package/skills/lit-typographic-motion/engine/flash.mjs +216 -0
- package/skills/lit-typographic-motion/engine/look.mjs +166 -0
- package/skills/lit-typographic-motion/engine/page/common.js +105 -0
- package/skills/lit-typographic-motion/engine/page/engine.js +451 -0
- package/skills/lit-typographic-motion/engine/page/gl.js +213 -0
- package/skills/lit-typographic-motion/engine/page/lines.js +88 -0
- package/skills/lit-typographic-motion/engine/page/passes.js +144 -0
- package/skills/lit-typographic-motion/engine/page/post.js +119 -0
- package/skills/lit-typographic-motion/engine/page/scenes.js +187 -0
- package/skills/lit-typographic-motion/engine/page/type.js +321 -0
- package/skills/lit-typographic-motion/engine/png.mjs +139 -0
- package/skills/lit-typographic-motion/engine/sound.mjs +291 -0
- package/skills/lit-typographic-motion/engine/stage/capture.mjs +133 -0
- package/skills/lit-typographic-motion/engine/stage/clock.js +171 -0
- package/skills/lit-typographic-motion/engine/stage/frame-worker.mjs +47 -0
- package/skills/lit-typographic-motion/engine/stage/gate.mjs +119 -0
- package/skills/lit-typographic-motion/engine/stage/qa-page.js +158 -0
- package/skills/lit-typographic-motion/engine/stage/qa.mjs +208 -0
- package/skills/lit-typographic-motion/engine/stage/render.mjs +418 -0
- package/skills/lit-typographic-motion/engine/stage/scan.mjs +162 -0
- package/skills/lit-typographic-motion/engine/stage/stage-kit.js +310 -0
- package/skills/lit-typographic-motion/engine/stills.mjs +60 -0
- package/skills/lit-typographic-motion/engine/text.mjs +126 -0
- package/skills/lit-typographic-motion/engine/timing.mjs +289 -0
- package/skills/lit-typographic-motion/engine/treatment.mjs +254 -0
- package/skills/lit-typographic-motion/fonts/Archivo-100-400.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Archivo-100-700.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Archivo-100-900.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Archivo-125-400.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Archivo-125-700.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Archivo-125-900.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Archivo-75-400.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Archivo-75-700.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Archivo-75-900.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Archivo-OFL.txt +93 -0
- package/skills/lit-typographic-motion/fonts/Silkscreen-Bold.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/Silkscreen-OFL.txt +93 -0
- package/skills/lit-typographic-motion/fonts/Silkscreen-Regular.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/VT323-OFL.txt +93 -0
- package/skills/lit-typographic-motion/fonts/VT323-Regular.ttf +0 -0
- package/skills/lit-typographic-motion/fonts/pins.json +44 -0
- package/skills/lit-typographic-motion/fonts/stroke/CREDITS +52 -0
- package/skills/lit-typographic-motion/fonts/stroke/EMSAllure.svg +216 -0
- package/skills/lit-typographic-motion/fonts/stroke/EMSFelix.svg +216 -0
- package/skills/lit-typographic-motion/fonts/stroke/EMSOsmotron.svg +216 -0
- package/skills/lit-typographic-motion/fonts/stroke/EMSReadability.svg +216 -0
- package/skills/lit-typographic-motion/fonts/stroke/EMSTech.svg +217 -0
- package/skills/lit-typographic-motion/fonts/stroke/OFL.txt +85 -0
- package/skills/lit-typographic-motion/gate.mjs +593 -0
- package/skills/lit-typographic-motion/package-lock.json +78 -0
- package/skills/lit-typographic-motion/package.json +11 -0
- package/skills/lit-typographic-motion/probe.mjs +74 -0
- package/skills/lit-typographic-motion/references/authoring.md +110 -0
- package/skills/lit-typographic-motion/references/craft-loop.md +110 -0
- package/skills/lit-typographic-motion/references/runtime.md +88 -0
- package/skills/lit-typographic-motion/references/stage.md +164 -0
- package/skills/lit-typographic-motion/references/style-bibles.md +88 -0
- package/skills/lit-typographic-motion/references/treatment.md +143 -0
- package/skills/lit-typographic-motion/references/type-craft.md +55 -0
- package/skills/lit-typographic-motion/render.mjs +654 -0
- package/skills/lit-typographic-motion/requirements-audio.in +1 -0
- package/skills/lit-typographic-motion/requirements-audio.txt +790 -0
- package/skills/lit-typographic-motion/runtime.mjs +259 -0
- package/skills/lit-typographic-motion/word-timing-models.json +25 -0
- package/skills/litresearch/SKILL.md +41 -12
- package/skills/managed-skill-manifest.json +2367 -63
- package/skills/skill-rename-aliases.json +6 -1
- package/tools/check-pack-payload.mjs +22 -1
- package/tools/check-payload-substance.mjs +2 -2
- package/tools/payload-reference-exemptions.json +4 -0
- package/tools/payload-substance-allowlist.json +2 -3
- package/tools/payload-substance-parity.json +7 -7
- package/tools/version-manifests.json +4 -4
- package/skills/lit-korean/SKILL.md +0 -231
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Honest, renderer-agnostic QA gate for generated PPTX decks.
|
|
3
|
+
|
|
4
|
+
Brand-neutral: works for any enrolled template. Combines three checks and
|
|
5
|
+
exits non-zero if ANY real defect is found:
|
|
6
|
+
|
|
7
|
+
1. Layout issues — inventory.py --issues-only (overflow / overlap / off-slide)
|
|
8
|
+
2. Anti-slop — validate_pptx.py (forbidden/placeholder terms, default fonts)
|
|
9
|
+
3. WCAG contrast — text colour vs its fill (body < 4.5:1, large < 3:1)
|
|
10
|
+
|
|
11
|
+
No LibreOffice / rendering engine required: everything is read from the OOXML
|
|
12
|
+
via python-pptx. (A renderer is only ever needed for *visual* preview, which is
|
|
13
|
+
optional and lives in thumbnail.py.)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
import tempfile
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from pptx import Presentation
|
|
28
|
+
from pptx.dml.color import RGBColor
|
|
29
|
+
from pptx.enum.shapes import MSO_SHAPE_TYPE
|
|
30
|
+
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
|
|
31
|
+
from craft_extras import analyse as analyse_craft
|
|
32
|
+
|
|
33
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
34
|
+
VALIDATE = ROOT / "scripts" / "validate_pptx.py"
|
|
35
|
+
INVENTORY = ROOT / "scripts" / "layout_inventory.py"
|
|
36
|
+
|
|
37
|
+
# WCAG 2.x contrast floors.
|
|
38
|
+
BODY_MIN = 4.5
|
|
39
|
+
LARGE_MIN = 3.0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ── WCAG contrast ──────────────────────────────────────────────────────────
|
|
43
|
+
def _rel_luminance(rgb: tuple[int, int, int]) -> float:
|
|
44
|
+
def chan(c: int) -> float:
|
|
45
|
+
s = c / 255.0
|
|
46
|
+
return s / 12.92 if s <= 0.03928 else ((s + 0.055) / 1.055) ** 2.4
|
|
47
|
+
|
|
48
|
+
r, g, b = (chan(x) for x in rgb)
|
|
49
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _ratio(fg: tuple[int, int, int], bg: tuple[int, int, int]) -> float:
|
|
53
|
+
l1, l2 = _rel_luminance(fg), _rel_luminance(bg)
|
|
54
|
+
hi, lo = max(l1, l2), min(l1, l2)
|
|
55
|
+
return (hi + 0.05) / (lo + 0.05)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _hex_to_rgb(value: Any) -> tuple[int, int, int] | None:
|
|
59
|
+
if isinstance(value, RGBColor):
|
|
60
|
+
value = str(value)
|
|
61
|
+
if not isinstance(value, str):
|
|
62
|
+
return None
|
|
63
|
+
v = value.lstrip("#")
|
|
64
|
+
if len(v) != 6:
|
|
65
|
+
return None
|
|
66
|
+
try:
|
|
67
|
+
return (int(v[0:2], 16), int(v[2:4], 16), int(v[4:6], 16))
|
|
68
|
+
except ValueError:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _shape_fill_rgb(shape) -> tuple[int, int, int] | None:
|
|
73
|
+
fill = getattr(shape, "fill", None)
|
|
74
|
+
if fill is None or getattr(fill, "type", None) is None:
|
|
75
|
+
return None
|
|
76
|
+
try:
|
|
77
|
+
rgb = fill.fore_color.rgb
|
|
78
|
+
except (TypeError, AttributeError):
|
|
79
|
+
return None
|
|
80
|
+
return _hex_to_rgb(rgb)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _is_large(size_pt: float | None, bold: bool) -> bool:
|
|
84
|
+
if size_pt is None:
|
|
85
|
+
return False
|
|
86
|
+
return size_pt >= 18 or (bold and size_pt >= 14)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _bbox(shape):
|
|
90
|
+
try:
|
|
91
|
+
l, t, w, h = int(shape.left), int(shape.top), int(shape.width), int(shape.height)
|
|
92
|
+
return (l, t, l + w, t + h)
|
|
93
|
+
except (TypeError, ValueError):
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _resolve_bg(shape, idx, filled):
|
|
98
|
+
"""Effective background for a text shape: its own fill, else the topmost
|
|
99
|
+
filled shape drawn *behind* it (lower z-order) that contains its centre,
|
|
100
|
+
else white. Prevents false positives like white text over a charcoal band."""
|
|
101
|
+
own = _shape_fill_rgb(shape)
|
|
102
|
+
if own is not None:
|
|
103
|
+
return own
|
|
104
|
+
box = _bbox(shape)
|
|
105
|
+
if box is not None:
|
|
106
|
+
cx, cy = (box[0] + box[2]) / 2, (box[1] + box[3]) / 2
|
|
107
|
+
best = None
|
|
108
|
+
for f_idx, f_rgb, f_box in filled:
|
|
109
|
+
if f_idx >= idx or f_box is None:
|
|
110
|
+
continue
|
|
111
|
+
if f_box[0] <= cx <= f_box[2] and f_box[1] <= cy <= f_box[3]:
|
|
112
|
+
best = f_rgb # later (higher idx) wins -> topmost behind text
|
|
113
|
+
if best is not None:
|
|
114
|
+
return best
|
|
115
|
+
return (255, 255, 255)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def check_contrast(path: Path) -> dict[str, Any]:
|
|
119
|
+
"""Flag explicit-colour text that falls under the WCAG floor on its fill."""
|
|
120
|
+
prs = Presentation(str(path))
|
|
121
|
+
violations: list[dict[str, Any]] = []
|
|
122
|
+
checked = 0
|
|
123
|
+
for s_idx, slide in enumerate(prs.slides, start=1):
|
|
124
|
+
shapes = list(slide.shapes)
|
|
125
|
+
filled = [(i, rgb, _bbox(sh)) for i, sh in enumerate(shapes) if (rgb := _shape_fill_rgb(sh)) is not None]
|
|
126
|
+
for idx, shape in enumerate(shapes):
|
|
127
|
+
if not getattr(shape, "has_text_frame", False):
|
|
128
|
+
continue
|
|
129
|
+
bg = _resolve_bg(shape, idx, filled)
|
|
130
|
+
for para in shape.text_frame.paragraphs:
|
|
131
|
+
for run in para.runs:
|
|
132
|
+
if not run.text.strip():
|
|
133
|
+
continue
|
|
134
|
+
try:
|
|
135
|
+
fg = _hex_to_rgb(run.font.color.rgb)
|
|
136
|
+
except (TypeError, AttributeError):
|
|
137
|
+
fg = None
|
|
138
|
+
if fg is None:
|
|
139
|
+
continue # theme/inherited colour — cannot judge, skip
|
|
140
|
+
checked += 1
|
|
141
|
+
size = float(run.font.size.pt) if run.font.size is not None else None
|
|
142
|
+
bold = bool(run.font.bold)
|
|
143
|
+
ratio = _ratio(fg, bg)
|
|
144
|
+
floor = LARGE_MIN if _is_large(size, bold) else BODY_MIN
|
|
145
|
+
if ratio < floor:
|
|
146
|
+
violations.append({
|
|
147
|
+
"slide": s_idx,
|
|
148
|
+
"text": run.text.strip()[:60],
|
|
149
|
+
"fg": "#%02X%02X%02X" % fg,
|
|
150
|
+
"bg": "#%02X%02X%02X" % bg,
|
|
151
|
+
"ratio": round(ratio, 2),
|
|
152
|
+
"floor": floor,
|
|
153
|
+
})
|
|
154
|
+
return {"pass": not violations, "checked_runs": checked, "violations": violations[:30]}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ── picture / table overlap ─────────────────────────────────────────────────
|
|
158
|
+
# inventory.py only considers text shapes, so a chart image drawn on top of a
|
|
159
|
+
# data table (or another image / body text) slips through. This check covers
|
|
160
|
+
# PICTURE and TABLE (graphicFrame) shapes against every other content-bearing
|
|
161
|
+
# shape and fails on any real overlap.
|
|
162
|
+
OVERLAP_TOL_IN = 0.06 # inches; ignore hairline touches / rounding
|
|
163
|
+
MIN_TEXT_PT = 6.0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _shape_kind(shape) -> str | None:
|
|
167
|
+
"""Classify a shape for overlap purposes: 'picture', 'table', 'text', or
|
|
168
|
+
None for decorative/empty shapes (backgrounds, rules, blank frames)."""
|
|
169
|
+
if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
|
|
170
|
+
return "picture"
|
|
171
|
+
if getattr(shape, "has_table", False):
|
|
172
|
+
return "table"
|
|
173
|
+
if getattr(shape, "has_text_frame", False) and shape.text_frame.text.strip():
|
|
174
|
+
return "text"
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _bbox_in(shape) -> tuple[float, float, float, float] | None:
|
|
179
|
+
try:
|
|
180
|
+
l = shape.left / 914400.0
|
|
181
|
+
t = shape.top / 914400.0
|
|
182
|
+
w = shape.width / 914400.0
|
|
183
|
+
h = shape.height / 914400.0
|
|
184
|
+
except (TypeError, ValueError):
|
|
185
|
+
return None
|
|
186
|
+
return (l, t, l + w, t + h)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _is_full_bleed(box, page_area: float) -> bool:
|
|
190
|
+
"""A shape covering essentially the whole canvas is a ground, not an occluder."""
|
|
191
|
+
return ((box[2] - box[0]) * (box[3] - box[1])) >= 0.92 * page_area
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _overlap_extent(box_a, box_b) -> tuple[float, float]:
|
|
195
|
+
return (min(box_a[2], box_b[2]) - max(box_a[0], box_b[0]),
|
|
196
|
+
min(box_a[3], box_b[3]) - max(box_a[1], box_b[1]))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def check_semantic_overlaps(path: Path) -> dict[str, Any]:
|
|
200
|
+
"""Text running over other text — the one occlusion nothing else catches.
|
|
201
|
+
|
|
202
|
+
Free positioning lets an author drop a box anywhere, so two text frames can
|
|
203
|
+
now land on top of each other and produce a slide where neither is readable.
|
|
204
|
+
Nothing in the gate saw that: the inventory arm meant to catch it read a key
|
|
205
|
+
that never existed, so it counted zero for the whole life of the gate.
|
|
206
|
+
|
|
207
|
+
Every other pairing already has a more careful owner, and duplicating them
|
|
208
|
+
here would only reintroduce their false positives:
|
|
209
|
+
|
|
210
|
+
picture or table drawn over content -> check_overlaps (z-order aware)
|
|
211
|
+
declared figure or table occluded -> check_figure_table_occlusion
|
|
212
|
+
text on a card, panel or backdrop -> check_contrast, on its real fill
|
|
213
|
+
|
|
214
|
+
That last one is why an undeclared picture is not treated as content. A
|
|
215
|
+
gradient circle behind a cover title is a ground exactly as a navy panel is;
|
|
216
|
+
a picture only carries content once its description declares it as evidence,
|
|
217
|
+
and check_figure_table_occlusion is what reads that declaration.
|
|
218
|
+
"""
|
|
219
|
+
prs = Presentation(str(path))
|
|
220
|
+
page_area = (prs.slide_width / 914400.0) * (prs.slide_height / 914400.0)
|
|
221
|
+
violations: list[dict[str, Any]] = []
|
|
222
|
+
|
|
223
|
+
for s_idx, slide in enumerate(prs.slides, start=1):
|
|
224
|
+
texts = [
|
|
225
|
+
(_bbox_in(sh), sh) for sh in slide.shapes
|
|
226
|
+
if _shape_kind(sh) == "text" and _bbox_in(sh) is not None
|
|
227
|
+
]
|
|
228
|
+
for i in range(len(texts)):
|
|
229
|
+
for j in range(i + 1, len(texts)):
|
|
230
|
+
box_a, sh_a = texts[i]
|
|
231
|
+
box_b, sh_b = texts[j]
|
|
232
|
+
if _is_full_bleed(box_a, page_area) or _is_full_bleed(box_b, page_area):
|
|
233
|
+
continue
|
|
234
|
+
ow, oh = _overlap_extent(box_a, box_b)
|
|
235
|
+
if ow <= OVERLAP_TOL_IN or oh <= OVERLAP_TOL_IN:
|
|
236
|
+
continue
|
|
237
|
+
violations.append({
|
|
238
|
+
"slide": s_idx,
|
|
239
|
+
"a": "text",
|
|
240
|
+
"b": "text",
|
|
241
|
+
"area": round(ow * oh, 2),
|
|
242
|
+
"a_text": _shape_text(sh_a)[:40],
|
|
243
|
+
"b_text": _shape_text(sh_b)[:40],
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
return {"pass": not violations, "violations": violations[:30]}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def check_overlaps(path: Path) -> dict[str, Any]:
|
|
250
|
+
"""A PICTURE or TABLE must never be drawn on top of (higher z-order than)
|
|
251
|
+
another content-bearing shape. Decorations are drawn first (behind), so they
|
|
252
|
+
are never the 'top' shape and never false-flag."""
|
|
253
|
+
prs = Presentation(str(path))
|
|
254
|
+
page_area = (prs.slide_width / 914400.0) * (prs.slide_height / 914400.0)
|
|
255
|
+
violations: list[dict[str, Any]] = []
|
|
256
|
+
for s_idx, slide in enumerate(prs.slides, start=1):
|
|
257
|
+
shapes = []
|
|
258
|
+
for z, sh in enumerate(slide.shapes):
|
|
259
|
+
box = _bbox_in(sh)
|
|
260
|
+
if box is None:
|
|
261
|
+
continue
|
|
262
|
+
shapes.append((z, sh, _shape_kind(sh), box))
|
|
263
|
+
|
|
264
|
+
def is_full_bleed(b):
|
|
265
|
+
return _is_full_bleed(b, page_area)
|
|
266
|
+
for z, sh, kind, box in shapes:
|
|
267
|
+
if kind not in ("picture", "table"):
|
|
268
|
+
continue
|
|
269
|
+
for z2, sh2, kind2, box2 in shapes:
|
|
270
|
+
if z2 >= z or kind2 is None:
|
|
271
|
+
continue # only shapes drawn *behind* this picture/table
|
|
272
|
+
# A full-bleed background image (mesh/gradient wash) is never an
|
|
273
|
+
# occlusion victim — anything may sit on top of it by design.
|
|
274
|
+
if kind2 == "picture" and is_full_bleed(box2):
|
|
275
|
+
continue
|
|
276
|
+
# Picture-over-picture is intentional decorative layering.
|
|
277
|
+
if kind == "picture" and kind2 == "picture":
|
|
278
|
+
continue
|
|
279
|
+
ow = min(box[2], box2[2]) - max(box[0], box2[0])
|
|
280
|
+
oh = min(box[3], box2[3]) - max(box[1], box2[1])
|
|
281
|
+
if ow > OVERLAP_TOL_IN and oh > OVERLAP_TOL_IN:
|
|
282
|
+
violations.append({
|
|
283
|
+
"slide": s_idx,
|
|
284
|
+
"top": kind,
|
|
285
|
+
"under": kind2,
|
|
286
|
+
"area": round(ow * oh, 2),
|
|
287
|
+
})
|
|
288
|
+
return {"pass": not violations, "violations": violations[:30]}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# ── objective executive/research checks ────────────────────────────────────
|
|
292
|
+
def _intersection(box_a, box_b) -> tuple[float, float]:
|
|
293
|
+
return (
|
|
294
|
+
min(box_a[2], box_b[2]) - max(box_a[0], box_b[0]),
|
|
295
|
+
min(box_a[3], box_b[3]) - max(box_a[1], box_b[1]),
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _iter_shape_runs(shape):
|
|
300
|
+
if getattr(shape, "has_text_frame", False):
|
|
301
|
+
for paragraph in shape.text_frame.paragraphs:
|
|
302
|
+
yield from paragraph.runs
|
|
303
|
+
if getattr(shape, "has_table", False):
|
|
304
|
+
for row in shape.table.rows:
|
|
305
|
+
for cell in row.cells:
|
|
306
|
+
for paragraph in cell.text_frame.paragraphs:
|
|
307
|
+
yield from paragraph.runs
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _shape_text(shape) -> str:
|
|
311
|
+
return "\n".join(run.text for run in _iter_shape_runs(shape) if run.text).strip()
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _semantic_label(shape) -> str:
|
|
315
|
+
values = [getattr(shape, "name", "") or ""]
|
|
316
|
+
try:
|
|
317
|
+
c_nv_pr = shape._element.xpath(".//p:cNvPr")[0]
|
|
318
|
+
values.extend([c_nv_pr.get("name", ""), c_nv_pr.get("descr", ""), c_nv_pr.get("title", "")])
|
|
319
|
+
except (AttributeError, IndexError):
|
|
320
|
+
pass
|
|
321
|
+
return " ".join(value for value in values if value).strip()
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def check_side_stripes(path: Path) -> dict[str, Any]:
|
|
325
|
+
"""Detect a narrow filled bar attached to the vertical edge of a larger card."""
|
|
326
|
+
prs = Presentation(str(path))
|
|
327
|
+
violations: list[dict[str, Any]] = []
|
|
328
|
+
for slide_idx, slide in enumerate(prs.slides, start=1):
|
|
329
|
+
filled = [
|
|
330
|
+
(idx, shape, _bbox_in(shape), _shape_fill_rgb(shape))
|
|
331
|
+
for idx, shape in enumerate(slide.shapes)
|
|
332
|
+
if _bbox_in(shape) is not None and _shape_fill_rgb(shape) is not None
|
|
333
|
+
]
|
|
334
|
+
for stripe_idx, stripe, stripe_box, stripe_rgb in filled:
|
|
335
|
+
sw = stripe_box[2] - stripe_box[0]
|
|
336
|
+
sh = stripe_box[3] - stripe_box[1]
|
|
337
|
+
if sw > 0.15 or sh < 0.5 or sh / max(sw, 0.001) < 5:
|
|
338
|
+
continue
|
|
339
|
+
for card_idx, card, card_box, card_rgb in filled:
|
|
340
|
+
if card_idx == stripe_idx or card_rgb == stripe_rgb:
|
|
341
|
+
continue
|
|
342
|
+
cw = card_box[2] - card_box[0]
|
|
343
|
+
ch = card_box[3] - card_box[1]
|
|
344
|
+
if cw < 1.0 or ch < 0.5 or sh < ch * 0.7:
|
|
345
|
+
continue
|
|
346
|
+
same_vertical_band = abs(stripe_box[1] - card_box[1]) <= 0.08 and abs(stripe_box[3] - card_box[3]) <= 0.08
|
|
347
|
+
on_edge = abs(stripe_box[0] - card_box[0]) <= 0.08 or abs(stripe_box[2] - card_box[2]) <= 0.08
|
|
348
|
+
if same_vertical_band and on_edge:
|
|
349
|
+
violations.append({
|
|
350
|
+
"slide": slide_idx,
|
|
351
|
+
"stripe_shape": stripe_idx + 1,
|
|
352
|
+
"card_shape": card_idx + 1,
|
|
353
|
+
"stripe_width_in": round(sw, 3),
|
|
354
|
+
})
|
|
355
|
+
break
|
|
356
|
+
return {"pass": not violations, "violations": violations[:30]}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def check_text_size_floor(path: Path) -> dict[str, Any]:
|
|
360
|
+
"""Fail only on explicit text below the absolute 6pt export floor."""
|
|
361
|
+
prs = Presentation(str(path))
|
|
362
|
+
violations: list[dict[str, Any]] = []
|
|
363
|
+
checked = 0
|
|
364
|
+
for slide_idx, slide in enumerate(prs.slides, start=1):
|
|
365
|
+
for shape_idx, shape in enumerate(slide.shapes, start=1):
|
|
366
|
+
for run in _iter_shape_runs(shape):
|
|
367
|
+
if not run.text.strip() or run.font.size is None:
|
|
368
|
+
continue
|
|
369
|
+
checked += 1
|
|
370
|
+
size = float(run.font.size.pt)
|
|
371
|
+
if size < MIN_TEXT_PT:
|
|
372
|
+
violations.append({
|
|
373
|
+
"slide": slide_idx,
|
|
374
|
+
"shape": shape_idx,
|
|
375
|
+
"text": run.text.strip()[:60],
|
|
376
|
+
"size_pt": round(size, 2),
|
|
377
|
+
"floor_pt": MIN_TEXT_PT,
|
|
378
|
+
})
|
|
379
|
+
return {"pass": not violations, "checked_runs": checked, "violations": violations[:30]}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def check_figure_table_occlusion(path: Path) -> dict[str, Any]:
|
|
383
|
+
"""Flag content shapes drawn over a non-background figure or table."""
|
|
384
|
+
prs = Presentation(str(path))
|
|
385
|
+
violations: list[dict[str, Any]] = []
|
|
386
|
+
for slide_idx, slide in enumerate(prs.slides, start=1):
|
|
387
|
+
page_area = (prs.slide_width / 914400.0) * (prs.slide_height / 914400.0)
|
|
388
|
+
shapes = [
|
|
389
|
+
(idx, shape, _shape_kind(shape), _bbox_in(shape))
|
|
390
|
+
for idx, shape in enumerate(slide.shapes)
|
|
391
|
+
if _bbox_in(shape) is not None
|
|
392
|
+
]
|
|
393
|
+
for victim_idx, victim, victim_kind, victim_box in shapes:
|
|
394
|
+
if victim_kind not in ("picture", "table"):
|
|
395
|
+
continue
|
|
396
|
+
# A generic picture may intentionally be a background or decorated
|
|
397
|
+
# asset. Only pictures explicitly declared as evidence/figures have
|
|
398
|
+
# objective occlusion semantics; tables are always data objects.
|
|
399
|
+
if victim_kind == "picture" and not _EVIDENCE_MARKER.search(_semantic_label(victim)):
|
|
400
|
+
continue
|
|
401
|
+
victim_area = (victim_box[2] - victim_box[0]) * (victim_box[3] - victim_box[1])
|
|
402
|
+
if victim_kind == "picture" and victim_area >= 0.92 * page_area:
|
|
403
|
+
continue
|
|
404
|
+
for top_idx, top, top_kind, top_box in shapes:
|
|
405
|
+
if top_idx <= victim_idx or top_kind is None:
|
|
406
|
+
continue
|
|
407
|
+
if victim_kind == "picture" and top_kind == "picture":
|
|
408
|
+
continue
|
|
409
|
+
ow, oh = _intersection(victim_box, top_box)
|
|
410
|
+
if ow <= OVERLAP_TOL_IN or oh <= OVERLAP_TOL_IN:
|
|
411
|
+
continue
|
|
412
|
+
overlap_area = ow * oh
|
|
413
|
+
if overlap_area < max(0.08, victim_area * 0.03):
|
|
414
|
+
continue
|
|
415
|
+
violations.append({
|
|
416
|
+
"slide": slide_idx,
|
|
417
|
+
"victim": victim_kind,
|
|
418
|
+
"victim_shape": victim_idx + 1,
|
|
419
|
+
"occluding": top_kind,
|
|
420
|
+
"occluding_shape": top_idx + 1,
|
|
421
|
+
"area": round(overlap_area, 2),
|
|
422
|
+
})
|
|
423
|
+
return {"pass": not violations, "violations": violations[:30]}
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
_EVIDENCE_MARKER = re.compile(
|
|
427
|
+
r"(?:^|\b)(?:figure|evidence|source[ -]?capture|그림|도표|근거|출처\s*캡처)\s*(?::|\d|\b)",
|
|
428
|
+
re.IGNORECASE,
|
|
429
|
+
)
|
|
430
|
+
_CAPTION_MARKER = re.compile(r"(?:그림|도|figure|fig\.)\s*\d+", re.IGNORECASE)
|
|
431
|
+
_SOURCE_MARKER = re.compile(r"(?:출처|source)\s*:", re.IGNORECASE)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def check_evidence_binding(path: Path) -> dict[str, Any]:
|
|
435
|
+
"""Require visible, adjacent caption and source text for declared evidence figures."""
|
|
436
|
+
prs = Presentation(str(path))
|
|
437
|
+
violations: list[dict[str, Any]] = []
|
|
438
|
+
checked = 0
|
|
439
|
+
for slide_idx, slide in enumerate(prs.slides, start=1):
|
|
440
|
+
shapes = list(slide.shapes)
|
|
441
|
+
for shape_idx, shape in enumerate(shapes, start=1):
|
|
442
|
+
if shape.shape_type != MSO_SHAPE_TYPE.PICTURE or not _EVIDENCE_MARKER.search(_semantic_label(shape)):
|
|
443
|
+
continue
|
|
444
|
+
checked += 1
|
|
445
|
+
figure_box = _bbox_in(shape)
|
|
446
|
+
nearby: list[str] = []
|
|
447
|
+
if figure_box is not None:
|
|
448
|
+
figure_width = figure_box[2] - figure_box[0]
|
|
449
|
+
for other in shapes:
|
|
450
|
+
text = _shape_text(other)
|
|
451
|
+
other_box = _bbox_in(other)
|
|
452
|
+
if not text or other_box is None:
|
|
453
|
+
continue
|
|
454
|
+
horizontal = min(figure_box[2], other_box[2]) - max(figure_box[0], other_box[0])
|
|
455
|
+
vertical_gap = other_box[1] - figure_box[3]
|
|
456
|
+
if horizontal >= figure_width * 0.2 and -0.04 <= vertical_gap <= 1.1:
|
|
457
|
+
nearby.append(text)
|
|
458
|
+
bound_text = "\n".join(nearby)
|
|
459
|
+
missing = []
|
|
460
|
+
if not _CAPTION_MARKER.search(bound_text):
|
|
461
|
+
missing.append("caption")
|
|
462
|
+
if not _SOURCE_MARKER.search(bound_text):
|
|
463
|
+
missing.append("source")
|
|
464
|
+
if missing:
|
|
465
|
+
violations.append({
|
|
466
|
+
"slide": slide_idx,
|
|
467
|
+
"shape": shape_idx,
|
|
468
|
+
"label": _semantic_label(shape)[:100],
|
|
469
|
+
"missing": missing,
|
|
470
|
+
})
|
|
471
|
+
return {"pass": not violations, "checked_figures": checked, "violations": violations[:30]}
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
_APPENDIX_MARKER = re.compile(r"\bappendix\b|부록", re.IGNORECASE)
|
|
475
|
+
_RAW_URL = re.compile(r"https?://[^\s<>\]\[)}]+", re.IGNORECASE)
|
|
476
|
+
_DOI_VALUE = re.compile(r"(?<!doi\.org/)\b10\.\d{4,9}/[^\s<>\]\[)}]+", re.IGNORECASE)
|
|
477
|
+
_LINK_FIELD = re.compile(
|
|
478
|
+
r"\bdoi\b|canonical\s+link|source\s+link|정식\s*링크|원문(?:\s*링크)?",
|
|
479
|
+
re.IGNORECASE,
|
|
480
|
+
)
|
|
481
|
+
_SOURCE_LOCATOR = re.compile(r"\bsource\b|출처", re.IGNORECASE)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _appendix_link_units(shape):
|
|
485
|
+
"""Yield visible text and run-level hyperlink targets per paragraph/cell."""
|
|
486
|
+
if getattr(shape, "has_table", False):
|
|
487
|
+
headers = [cell.text.strip() for cell in shape.table.rows[0].cells]
|
|
488
|
+
for row_idx, row in enumerate(shape.table.rows):
|
|
489
|
+
if row_idx == 0:
|
|
490
|
+
continue
|
|
491
|
+
for column_idx, cell in enumerate(row.cells):
|
|
492
|
+
for paragraph in cell.text_frame.paragraphs:
|
|
493
|
+
runs = [(run.text, run.hyperlink.address) for run in paragraph.runs if run.text]
|
|
494
|
+
text = "".join(run_text for run_text, _ in runs).strip()
|
|
495
|
+
if text:
|
|
496
|
+
yield (
|
|
497
|
+
text,
|
|
498
|
+
[address for _, address in runs if address],
|
|
499
|
+
bool(
|
|
500
|
+
_LINK_FIELD.search(headers[column_idx])
|
|
501
|
+
or (
|
|
502
|
+
_SOURCE_LOCATOR.search(headers[column_idx])
|
|
503
|
+
and any(address for _, address in runs)
|
|
504
|
+
)
|
|
505
|
+
),
|
|
506
|
+
)
|
|
507
|
+
return
|
|
508
|
+
if getattr(shape, "has_text_frame", False):
|
|
509
|
+
for paragraph in shape.text_frame.paragraphs:
|
|
510
|
+
runs = [(run.text, run.hyperlink.address) for run in paragraph.runs if run.text]
|
|
511
|
+
text = "".join(run_text for run_text, _ in runs).strip()
|
|
512
|
+
if text:
|
|
513
|
+
run_links = [address for _, address in runs if address]
|
|
514
|
+
yield text, run_links, bool(
|
|
515
|
+
_LINK_FIELD.search(text)
|
|
516
|
+
or (_SOURCE_LOCATOR.search(text) and run_links)
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _slide_hyperlink_targets(slide) -> list[str]:
|
|
521
|
+
targets = []
|
|
522
|
+
for relationship in slide.part.rels.values():
|
|
523
|
+
if relationship.reltype == RT.HYPERLINK and relationship.is_external:
|
|
524
|
+
targets.append(relationship.target_ref)
|
|
525
|
+
return targets
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def check_appendix_links(path: Path) -> dict[str, Any]:
|
|
529
|
+
"""Appendix DOI, canonical-link, and raw-URL fields must be clickable."""
|
|
530
|
+
prs = Presentation(str(path))
|
|
531
|
+
violations: list[dict[str, Any]] = []
|
|
532
|
+
declared = 0
|
|
533
|
+
relationship_targets: list[str] = []
|
|
534
|
+
for slide_idx, slide in enumerate(prs.slides, start=1):
|
|
535
|
+
slide_text = "\n".join(_shape_text(shape) for shape in slide.shapes)
|
|
536
|
+
if not _APPENDIX_MARKER.search(slide_text):
|
|
537
|
+
continue
|
|
538
|
+
slide_targets = _slide_hyperlink_targets(slide)
|
|
539
|
+
relationship_targets.extend(slide_targets)
|
|
540
|
+
for shape_idx, shape in enumerate(slide.shapes, start=1):
|
|
541
|
+
for text, run_links, is_link_field in _appendix_link_units(shape):
|
|
542
|
+
raw_urls = [url.rstrip(".,;:") for url in _RAW_URL.findall(text)]
|
|
543
|
+
doi_values = [] if raw_urls else [doi.rstrip(".,;:") for doi in _DOI_VALUE.findall(text)]
|
|
544
|
+
candidates = raw_urls or doi_values
|
|
545
|
+
if not candidates and is_link_field:
|
|
546
|
+
candidates = [run_links[0]] if run_links else [None]
|
|
547
|
+
for candidate in candidates:
|
|
548
|
+
declared += 1
|
|
549
|
+
if candidate is None:
|
|
550
|
+
linked = False
|
|
551
|
+
elif candidate.startswith(("http://", "https://")):
|
|
552
|
+
linked = candidate in run_links and candidate in slide_targets
|
|
553
|
+
else:
|
|
554
|
+
linked = any(
|
|
555
|
+
candidate.lower() in target.lower() and target in run_links
|
|
556
|
+
for target in slide_targets
|
|
557
|
+
)
|
|
558
|
+
if not linked:
|
|
559
|
+
violations.append({
|
|
560
|
+
"slide": slide_idx,
|
|
561
|
+
"shape": shape_idx,
|
|
562
|
+
"text": text[:100],
|
|
563
|
+
"locator": candidate,
|
|
564
|
+
"missing": "OOXML hyperlink relationship",
|
|
565
|
+
})
|
|
566
|
+
return {
|
|
567
|
+
"pass": not violations,
|
|
568
|
+
"declared_links": declared,
|
|
569
|
+
"hyperlink_relationships": len(relationship_targets),
|
|
570
|
+
"relationship_targets": relationship_targets[:20],
|
|
571
|
+
"violations": violations[:30],
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def check_objective_quality(path: Path) -> dict[str, Any]:
|
|
576
|
+
checks = {
|
|
577
|
+
"side_stripes": check_side_stripes(path),
|
|
578
|
+
"text_size_floor": check_text_size_floor(path),
|
|
579
|
+
"figure_table_occlusion": check_figure_table_occlusion(path),
|
|
580
|
+
"evidence_binding": check_evidence_binding(path),
|
|
581
|
+
"appendix_links": check_appendix_links(path),
|
|
582
|
+
}
|
|
583
|
+
return {"pass": all(check["pass"] for check in checks.values()), **checks}
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def check_composition(path: Path) -> dict[str, Any]:
|
|
587
|
+
"""Catch visibly unfinished body slides and furniture outside the canvas."""
|
|
588
|
+
prs = Presentation(str(path))
|
|
589
|
+
width, height = prs.slide_width / 914400, prs.slide_height / 914400
|
|
590
|
+
issues: list[dict[str, Any]] = []
|
|
591
|
+
body_kinds: list[tuple[bool, bool]] = []
|
|
592
|
+
fill_ratios: list[dict[str, Any]] = []
|
|
593
|
+
for slide_no, slide in enumerate(prs.slides, 1):
|
|
594
|
+
content = []
|
|
595
|
+
table_count = chart_count = 0
|
|
596
|
+
for shape_no, shape in enumerate(slide.shapes, 1):
|
|
597
|
+
box = _bbox_in(shape)
|
|
598
|
+
if box is None:
|
|
599
|
+
continue
|
|
600
|
+
x0, y0, x1, y1 = box
|
|
601
|
+
if x0 < -0.03 or y0 < -0.03 or x1 > width + 0.03 or y1 > height + 0.03:
|
|
602
|
+
issues.append({"slide": slide_no, "shape": shape_no, "kind": "off_slide",
|
|
603
|
+
"hint": "Move or crop the decoration inside the slide canvas."})
|
|
604
|
+
has_table = bool(getattr(shape, "has_table", False))
|
|
605
|
+
has_chart = bool(getattr(shape, "has_chart", False))
|
|
606
|
+
table_count += has_table
|
|
607
|
+
chart_count += has_chart
|
|
608
|
+
if (has_table or has_chart or shape.shape_type == MSO_SHAPE_TYPE.PICTURE or
|
|
609
|
+
(getattr(shape, "has_text_frame", False) and shape.text_frame.text.strip())):
|
|
610
|
+
if y0 >= 1.7 and y0 < 6.7:
|
|
611
|
+
content.append((max(0.7, x0), max(1.7, y0), min(width - 0.7, x1), min(6.7, y1)))
|
|
612
|
+
if (not _shape_text(shape) and not has_table and not has_chart and
|
|
613
|
+
shape.shape_type == MSO_SHAPE_TYPE.AUTO_SHAPE and
|
|
614
|
+
(x1 - x0) * (y1 - y0) > 0.3 and
|
|
615
|
+
_shape_fill_rgb(shape) is None):
|
|
616
|
+
# A large empty outlined rectangle is usually a leftover box.
|
|
617
|
+
try:
|
|
618
|
+
line_rgb = _hex_to_rgb(shape.line.color.rgb)
|
|
619
|
+
except (TypeError, AttributeError):
|
|
620
|
+
line_rgb = None
|
|
621
|
+
if line_rgb is not None and max(line_rgb) < 100:
|
|
622
|
+
issues.append({"slide": slide_no, "shape": shape_no, "kind": "empty_outline",
|
|
623
|
+
"hint": "Remove the unused outlined box or put its intended content inside it."})
|
|
624
|
+
if 1 < slide_no < len(prs.slides):
|
|
625
|
+
area = (width - 1.4) * 5.0
|
|
626
|
+
occupied = sum(max(0, x1 - x0) * max(0, y1 - y0) for x0, y0, x1, y1 in content)
|
|
627
|
+
ratio = round(min(1, occupied / area), 3)
|
|
628
|
+
fill_ratios.append({"slide": slide_no, "ratio": ratio})
|
|
629
|
+
if ratio < 0.12:
|
|
630
|
+
issues.append({"slide": slide_no, "kind": "low_content_fill", "ratio": ratio,
|
|
631
|
+
"hint": "Enlarge the chart/cards/flow or add meaningful evidence to use the content area."})
|
|
632
|
+
body_kinds.append((table_count > 0, chart_count > 0))
|
|
633
|
+
if len(body_kinds) >= 2 and all(table and not chart for table, chart in body_kinds):
|
|
634
|
+
issues.append({"kind": "table_only_deck",
|
|
635
|
+
"hint": "Use an editable chart for numeric series and KPI cards for headline numbers."})
|
|
636
|
+
return {"pass": not issues, "fill_ratios": fill_ratios, "issues": issues[:30]}
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# ── inventory issues ───────────────────────────────────────────────────────
|
|
640
|
+
def load_inventory_issues(pptx: Path) -> dict[str, Any]:
|
|
641
|
+
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as tmp:
|
|
642
|
+
tmp_path = Path(tmp.name)
|
|
643
|
+
try:
|
|
644
|
+
subprocess.run(
|
|
645
|
+
[sys.executable, str(INVENTORY), str(pptx), str(tmp_path), "--issues-only"],
|
|
646
|
+
capture_output=True, text=True, cwd=ROOT, timeout=120,
|
|
647
|
+
)
|
|
648
|
+
return json.loads(tmp_path.read_text(encoding="utf-8")) if tmp_path.exists() else {}
|
|
649
|
+
finally:
|
|
650
|
+
tmp_path.unlink(missing_ok=True)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def summarize_inventory(issues: dict[str, Any]) -> dict[str, Any]:
|
|
654
|
+
shape_count = overflow = slide_overflow = frame_overflow = overlap = 0
|
|
655
|
+
for shapes in issues.values():
|
|
656
|
+
if not isinstance(shapes, dict):
|
|
657
|
+
continue
|
|
658
|
+
for shape in shapes.values():
|
|
659
|
+
if not isinstance(shape, dict):
|
|
660
|
+
continue
|
|
661
|
+
shape_count += 1
|
|
662
|
+
ov = shape.get("overflow") or {}
|
|
663
|
+
if ov:
|
|
664
|
+
overflow += 1
|
|
665
|
+
if "slide" in ov:
|
|
666
|
+
slide_overflow += 1
|
|
667
|
+
if "frame" in ov:
|
|
668
|
+
frame_overflow += 1
|
|
669
|
+
# inventory.py nests this under "overlap"; reading it at the top
|
|
670
|
+
# level silently counted zero and left this arm dead for the whole
|
|
671
|
+
# life of the gate. It is repaired here but reports only — see qa().
|
|
672
|
+
if (shape.get("overlap") or {}).get("overlapping_shapes"):
|
|
673
|
+
overlap += 1
|
|
674
|
+
return {
|
|
675
|
+
"issue_shapes": shape_count,
|
|
676
|
+
"overflow_shapes": overflow,
|
|
677
|
+
"slide_overflow_shapes": slide_overflow,
|
|
678
|
+
"frame_overflow_shapes": frame_overflow,
|
|
679
|
+
"overlap_shapes": overlap,
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def run_validate(pptx: Path) -> dict[str, Any]:
|
|
684
|
+
result = subprocess.run(
|
|
685
|
+
[sys.executable, str(VALIDATE), str(pptx)],
|
|
686
|
+
capture_output=True, text=True, cwd=ROOT, timeout=120,
|
|
687
|
+
)
|
|
688
|
+
try:
|
|
689
|
+
return json.loads(result.stdout)
|
|
690
|
+
except json.JSONDecodeError:
|
|
691
|
+
return {"pass": False, "error": "validate_pptx produced no JSON", "stderr": result.stderr[:400]}
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
# ── gate ───────────────────────────────────────────────────────────────────
|
|
695
|
+
def qa(pptx: Path) -> dict[str, Any]:
|
|
696
|
+
validate_report = run_validate(pptx)
|
|
697
|
+
inv_summary = summarize_inventory(load_inventory_issues(pptx))
|
|
698
|
+
contrast = check_contrast(pptx)
|
|
699
|
+
overlaps = check_overlaps(pptx)
|
|
700
|
+
semantic = check_semantic_overlaps(pptx)
|
|
701
|
+
objective = check_objective_quality(pptx)
|
|
702
|
+
composition = check_composition(pptx)
|
|
703
|
+
craft = analyse_craft(pptx)
|
|
704
|
+
|
|
705
|
+
reasons: list[str] = []
|
|
706
|
+
if not validate_report.get("pass", False):
|
|
707
|
+
reasons.append("anti-slop: " + ", ".join(
|
|
708
|
+
k for k, v in validate_report.get("checks", {}).items() if isinstance(v, dict) and not v.get("pass", True)
|
|
709
|
+
) or "anti-slop: validate_pptx failed")
|
|
710
|
+
# Fail only on real layout defects (overflow / off-slide). The inventory also
|
|
711
|
+
# flags "manual bullet symbols", which is an authoring lint for the HTML path
|
|
712
|
+
# — the markdown engine legitimately renders "• " bullet runs, so that lint
|
|
713
|
+
# must not fail the gate (issue_shapes stays informational).
|
|
714
|
+
#
|
|
715
|
+
# inv_summary["overlap_shapes"] is deliberately NOT part of this sum. It is
|
|
716
|
+
# raw pairwise geometry with no notion of z-order or shape kind, so every
|
|
717
|
+
# tinted card counts its own text as an overlap. check_semantic_overlaps owns
|
|
718
|
+
# the overlap verdict; this number is carried in the report for transparency.
|
|
719
|
+
layout_defects = inv_summary["overflow_shapes"]
|
|
720
|
+
if layout_defects:
|
|
721
|
+
reasons.append(
|
|
722
|
+
f"layout defects: {layout_defects} shapes "
|
|
723
|
+
f"({inv_summary['slide_overflow_shapes']} slide-overflow, "
|
|
724
|
+
f"{inv_summary['frame_overflow_shapes']} frame-overflow)"
|
|
725
|
+
)
|
|
726
|
+
if not contrast["pass"]:
|
|
727
|
+
reasons.append(f"contrast: {len(contrast['violations'])} run(s) below WCAG floor")
|
|
728
|
+
if not overlaps["pass"]:
|
|
729
|
+
reasons.append(
|
|
730
|
+
f"overlap: {len(overlaps['violations'])} picture/table shape(s) drawn over other content"
|
|
731
|
+
)
|
|
732
|
+
if not semantic["pass"]:
|
|
733
|
+
reasons.append(
|
|
734
|
+
f"semantic overlap: {len(semantic['violations'])} content pair(s) occluding each other"
|
|
735
|
+
)
|
|
736
|
+
if not objective["pass"]:
|
|
737
|
+
failed = [name for name, check in objective.items() if isinstance(check, dict) and not check.get("pass", True)]
|
|
738
|
+
reasons.append("objective quality: " + ", ".join(failed))
|
|
739
|
+
if not composition["pass"]:
|
|
740
|
+
reasons.append("composition: " + ", ".join(sorted({item["kind"] for item in composition["issues"]})))
|
|
741
|
+
if not craft["pass"]:
|
|
742
|
+
reasons.append("craft: " + ", ".join(sorted({item["rule"] for item in craft["findings"] if item["severity"] == "HIGH"})))
|
|
743
|
+
|
|
744
|
+
return {
|
|
745
|
+
"file": str(pptx),
|
|
746
|
+
"pass": not reasons,
|
|
747
|
+
"failure_reasons": reasons,
|
|
748
|
+
"anti_slop": {
|
|
749
|
+
"pass": validate_report.get("pass"),
|
|
750
|
+
"slop_score": validate_report.get("slop_score"),
|
|
751
|
+
"review_warnings": validate_report.get("checks", {}).get("forbidden_terms", {}).get("soft_warnings", []),
|
|
752
|
+
},
|
|
753
|
+
"layout": {**inv_summary, "picture_table_overlaps": len(overlaps["violations"])},
|
|
754
|
+
"contrast": contrast,
|
|
755
|
+
"overlaps": overlaps,
|
|
756
|
+
"semantic_overlaps": semantic,
|
|
757
|
+
"objective": objective,
|
|
758
|
+
"composition": composition,
|
|
759
|
+
"craft": craft,
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def main(argv: list[str] | None = None) -> int:
|
|
764
|
+
parser = argparse.ArgumentParser(description="Honest renderer-agnostic QA gate for PPTX decks.")
|
|
765
|
+
parser.add_argument("pptx", type=Path)
|
|
766
|
+
args = parser.parse_args(argv)
|
|
767
|
+
report = qa(args.pptx.resolve())
|
|
768
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
769
|
+
return 0 if report["pass"] else 1
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
if __name__ == "__main__":
|
|
773
|
+
raise SystemExit(main())
|