@research-copilot/plugin 1.1.16 → 1.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.claude-plugin/plugin.json +1 -1
- package/dist/.codex-plugin/plugin.toml +1 -1
- package/dist/.cursor-plugin/plugin.json +1 -1
- package/dist/.gemini-plugin/plugin.json +1 -1
- package/dist/.opencode-plugin/plugin.json +1 -1
- package/dist/.windsurf-plugin/plugin.json +1 -1
- package/dist/skills/ablation-planner/SKILL.md +126 -0
- package/dist/skills/alphaxiv/SKILL.md +186 -0
- package/dist/skills/analyze-results/SKILL.md +45 -0
- package/dist/skills/arxiv/SKILL.md +210 -0
- package/dist/skills/auto-paper-improvement-loop/SKILL.md +574 -0
- package/dist/skills/auto-review-loop/SKILL.md +370 -0
- package/dist/skills/auto-review-loop-llm/SKILL.md +247 -0
- package/dist/skills/auto-review-loop-minimax/SKILL.md +290 -0
- package/dist/skills/benchmark-paper-template/SKILL.md +142 -0
- package/dist/skills/benchmark-paper-template/references/benchmark-design.md +180 -0
- package/dist/skills/benchmark-paper-template/references/checklist.md +113 -0
- package/dist/skills/benchmark-paper-template/references/construction-pipeline.md +127 -0
- package/dist/skills/benchmark-paper-template/references/experiments.md +159 -0
- package/dist/skills/benchmark-paper-template/references/gap-analysis.md +86 -0
- package/dist/skills/benchmark-paper-template/references/instantiation-template.md +194 -0
- package/dist/skills/benchmark-paper-template/references/orchestrator-notes.md +96 -0
- package/dist/skills/benchmark-paper-template/references/paper-structure.md +209 -0
- package/dist/skills/citation-audit/SKILL.md +494 -0
- package/dist/skills/claims-drafting/SKILL.md +239 -0
- package/dist/skills/comm-lit-review/SKILL.md +299 -0
- package/dist/skills/comm-lit-review/references/domain-taxonomy.md +57 -0
- package/dist/skills/comm-lit-review/references/output-template.md +37 -0
- package/dist/skills/comm-lit-review/references/source-policy.md +99 -0
- package/dist/skills/comm-lit-review/references/venue-tiering.md +112 -0
- package/dist/skills/composer/SKILL.md +840 -0
- package/dist/skills/composer/references/section_guides.md +675 -0
- package/dist/skills/composer/references/writing_standards.md +629 -0
- package/dist/skills/composer/scripts/chapter_quality_check.py +470 -0
- package/dist/skills/composer/scripts/final_evaluation.py +550 -0
- package/dist/skills/deepxiv/SKILL.md +142 -0
- package/dist/skills/dse-loop/SKILL.md +277 -0
- package/dist/skills/embodiment-description/SKILL.md +129 -0
- package/dist/skills/exa-search/SKILL.md +192 -0
- package/dist/skills/experiment-audit/SKILL.md +262 -0
- package/dist/skills/experiment-bridge/SKILL.md +316 -0
- package/dist/skills/experiment-plan/SKILL.md +249 -0
- package/dist/skills/experiment-queue/SKILL.md +380 -0
- package/dist/skills/experiment-queue/scripts/build_manifest.py +142 -0
- package/dist/skills/experiment-queue/scripts/queue_manager.py +426 -0
- package/dist/skills/feishu-notify/SKILL.md +155 -0
- package/dist/skills/figure-description/SKILL.md +138 -0
- package/dist/skills/figure-designer/SKILL.md +207 -0
- package/dist/skills/figure-designer/references/design-rules.md +99 -0
- package/dist/skills/figure-designer/references/experimental-results.md +127 -0
- package/dist/skills/figure-designer/references/motivated-example.md +138 -0
- package/dist/skills/figure-designer/references/solution-overview.md +145 -0
- package/dist/skills/figure-designer/references/tools.md +124 -0
- package/dist/skills/figure-spec/SKILL.md +252 -0
- package/dist/skills/figure-spec/scripts/figure_renderer.py +799 -0
- package/dist/skills/formula-derivation/SKILL.md +280 -0
- package/dist/skills/gemini-search/SKILL.md +205 -0
- package/dist/skills/grant-proposal/SKILL.md +625 -0
- package/dist/skills/humanizer/AGENTS.md +25 -0
- package/dist/skills/humanizer/SKILL.md +621 -0
- package/dist/skills/idea-creator/SKILL.md +311 -0
- package/dist/skills/idea-discovery/SKILL.md +340 -0
- package/dist/skills/idea-discovery-robot/SKILL.md +362 -0
- package/dist/skills/idea-evaluator/SKILL.md +265 -0
- package/dist/skills/idea-evaluator/references/fatal-flaws.md +192 -0
- package/dist/skills/idea-evaluator/references/five-dimensions.md +251 -0
- package/dist/skills/idea-evaluator/references/lifecycle-capability-matching.md +177 -0
- package/dist/skills/idea-evaluator/references/paradigm-elephant.md +112 -0
- package/dist/skills/idea-evaluator/references/paradigm-examples.md +107 -0
- package/dist/skills/idea-evaluator/references/paradigm-first-principles.md +115 -0
- package/dist/skills/idea-evaluator/references/paradigm-hamming.md +137 -0
- package/dist/skills/idea-evaluator/references/paradigm-shift-probe.md +160 -0
- package/dist/skills/idea-evaluator/references/paradigm-technology-cycle.md +116 -0
- package/dist/skills/idea-evaluator/references/worked-examples.md +206 -0
- package/dist/skills/interview-cheatsheet/SKILL.md +245 -0
- package/dist/skills/intro-drafter/SKILL.md +250 -0
- package/dist/skills/intro-drafter/references/contribution-patterns.md +116 -0
- package/dist/skills/intro-drafter/references/flowchart.md +204 -0
- package/dist/skills/intro-drafter/references/paper-types.md +155 -0
- package/dist/skills/intro-drafter/references/running-example.md +133 -0
- package/dist/skills/intro-drafter/references/worked-examples.md +161 -0
- package/dist/skills/invention-structuring/SKILL.md +188 -0
- package/dist/skills/jurisdiction-format/SKILL.md +192 -0
- package/dist/skills/kill-argument/SKILL.md +384 -0
- package/dist/skills/llm-wiki/.mise.toml +2 -0
- package/dist/skills/llm-wiki/.nvmrc +1 -0
- package/dist/skills/llm-wiki/AGENTS.md +132 -0
- package/dist/skills/llm-wiki/CHANGELOG.md +459 -0
- package/dist/skills/llm-wiki/CLAUDE.md +109 -0
- package/dist/skills/llm-wiki/HERMES.md +44 -0
- package/dist/skills/llm-wiki/SKILL.md +1133 -0
- package/dist/skills/llm-wiki/assets/graph-demo.gif +0 -0
- package/dist/skills/llm-wiki/assets/graph-preview.png +0 -0
- package/dist/skills/llm-wiki/deps/LICENSE-d3.txt +13 -0
- package/dist/skills/llm-wiki/deps/LICENSE-marked.txt +44 -0
- package/dist/skills/llm-wiki/deps/LICENSE-purify.txt +568 -0
- package/{LICENSE → dist/skills/llm-wiki/deps/LICENSE-roughjs.txt} +1 -1
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/SKILL.md +261 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/references/config/first-time-setup.md +106 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/cdp.ts +179 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/constants.ts +13 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/defuddle-converter.ts +58 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/html-to-markdown.ts +135 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/legacy-converter.ts +629 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/main.ts +314 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/markdown-conversion-shared.ts +305 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/media-localizer.ts +317 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/package.json +14 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/paths.ts +29 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/vendor/baoyu-chrome-cdp/package.json +9 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/vendor/baoyu-chrome-cdp/src/index.test.ts +307 -0
- package/dist/skills/llm-wiki/deps/baoyu-url-to-markdown/scripts/vendor/baoyu-chrome-cdp/src/index.ts +523 -0
- package/dist/skills/llm-wiki/deps/d3.min.js +2 -0
- package/dist/skills/llm-wiki/deps/marked.min.js +6 -0
- package/dist/skills/llm-wiki/deps/purify.min.js +3 -0
- package/dist/skills/llm-wiki/deps/rough.min.js +7 -0
- package/dist/skills/llm-wiki/deps/youtube-transcript/SKILL.md +47 -0
- package/dist/skills/llm-wiki/deps/youtube-transcript/scripts/get_transcript.py +72 -0
- package/dist/skills/llm-wiki/docs/design/oriental-atlas/DESIGN.md +183 -0
- package/dist/skills/llm-wiki/docs/design/oriental-atlas/design-brief.md +170 -0
- package/dist/skills/llm-wiki/docs/design/oriental-atlas/oriental-editorial-atlas.html +1908 -0
- package/dist/skills/llm-wiki/docs/design/oriental-atlas/reviews/result-review.md +68 -0
- package/dist/skills/llm-wiki/docs/plans/2026-06-15-omp-tool-status-events-phased-plan.md +655 -0
- package/dist/skills/llm-wiki/docs/plans/2026-06-15-omp-tool-status-events-progress.json +913 -0
- package/dist/skills/llm-wiki/docs/spark/2026-06-15-omp-tool-status-events-design.md +174 -0
- package/dist/skills/llm-wiki/install.ps1 +87 -0
- package/dist/skills/llm-wiki/install.sh +722 -0
- package/dist/skills/llm-wiki/package-lock.json +10309 -0
- package/dist/skills/llm-wiki/package.json +24 -0
- package/dist/skills/llm-wiki/packages/graph-engine/package.json +33 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/anim/.gitkeep +1 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/anim/index.ts +179 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/diff.ts +121 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/graph-node.ts +37 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/index.ts +140 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/model/atlas.ts +18 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/model/index.ts +6 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/model/labels.ts +11 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/model/learning.ts +8 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/model/legacy-helpers.ts +1373 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/model/queue.ts +7 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/model/storage.ts +4 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/model/visibility.ts +9 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/render/.gitkeep +1 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/render/index.ts +59 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/render/legend.ts +25 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/render/model.ts +593 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/render/preview.ts +65 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/render/search.ts +48 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/render/static-renderer.ts +2783 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/render/toolbar.ts +40 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/render/viewport.ts +272 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/select/.gitkeep +1 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/select/index.ts +189 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/sim/index.ts +298 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/sim/pins.ts +96 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/themes/.gitkeep +1 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/themes/index.ts +8 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/themes/tokens.ts +107 -0
- package/dist/skills/llm-wiki/packages/graph-engine/src/types.ts +313 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/anim.test.ts +100 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/diff.test.ts +133 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/helpers.test.ts +275 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/learning.test.ts +259 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/preview.test.ts +56 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/queue.test.ts +114 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/render-model.test.ts +322 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/runtime-state.test.ts +281 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/search-and-legend.test.ts +111 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/select.test.ts +168 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/sim.test.ts +123 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/themes.test.ts +62 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/toolbar.test.ts +64 -0
- package/dist/skills/llm-wiki/packages/graph-engine/test/viewport.test.ts +167 -0
- package/dist/skills/llm-wiki/packages/graph-engine/tsconfig.json +17 -0
- package/dist/skills/llm-wiki/packages/graph-engine/vite.config.ts +15 -0
- package/dist/skills/llm-wiki/platforms/claude/CLAUDE.md +37 -0
- package/dist/skills/llm-wiki/platforms/claude/companions/llm-wiki-upgrade/SKILL.md +95 -0
- package/dist/skills/llm-wiki/platforms/codex/AGENTS.md +23 -0
- package/dist/skills/llm-wiki/scripts/adapter-state.sh +424 -0
- package/dist/skills/llm-wiki/scripts/build-graph-data.sh +395 -0
- package/dist/skills/llm-wiki/scripts/build-graph-html.sh +453 -0
- package/dist/skills/llm-wiki/scripts/cache.sh +352 -0
- package/dist/skills/llm-wiki/scripts/create-source-page.sh +100 -0
- package/dist/skills/llm-wiki/scripts/delete-helper.sh +68 -0
- package/dist/skills/llm-wiki/scripts/graph-analysis.js +732 -0
- package/dist/skills/llm-wiki/scripts/hook-session-start.sh +46 -0
- package/dist/skills/llm-wiki/scripts/init-wiki.sh +95 -0
- package/dist/skills/llm-wiki/scripts/lib/source-signal-eligibility.js +158 -0
- package/dist/skills/llm-wiki/scripts/lint-fix.sh +114 -0
- package/dist/skills/llm-wiki/scripts/lint-runner.sh +217 -0
- package/dist/skills/llm-wiki/scripts/runtime-context.sh +77 -0
- package/dist/skills/llm-wiki/scripts/shared-config.sh +83 -0
- package/dist/skills/llm-wiki/scripts/source-record-contract.tsv +10 -0
- package/dist/skills/llm-wiki/scripts/source-registry.sh +349 -0
- package/dist/skills/llm-wiki/scripts/source-registry.tsv +10 -0
- package/dist/skills/llm-wiki/scripts/source-signal-coverage.js +83 -0
- package/dist/skills/llm-wiki/scripts/validate-step1.sh +144 -0
- package/dist/skills/llm-wiki/scripts/wiki-compat.sh +267 -0
- package/dist/skills/llm-wiki/setup.sh +8 -0
- package/dist/skills/llm-wiki/templates/entity-template.md +30 -0
- package/dist/skills/llm-wiki/templates/graph-styles/wash/footer.html +27 -0
- package/dist/skills/llm-wiki/templates/graph-styles/wash/graph-wash-helpers.js +1326 -0
- package/dist/skills/llm-wiki/templates/graph-styles/wash/graph-wash.js +1009 -0
- package/dist/skills/llm-wiki/templates/graph-styles/wash/header.html +2002 -0
- package/dist/skills/llm-wiki/templates/index-en-template.md +51 -0
- package/dist/skills/llm-wiki/templates/index-template.md +51 -0
- package/dist/skills/llm-wiki/templates/log-en-template.md +11 -0
- package/dist/skills/llm-wiki/templates/log-template.md +11 -0
- package/dist/skills/llm-wiki/templates/overview-en-template.md +41 -0
- package/dist/skills/llm-wiki/templates/overview-template.md +41 -0
- package/dist/skills/llm-wiki/templates/purpose-en-template.md +16 -0
- package/dist/skills/llm-wiki/templates/purpose-template.md +16 -0
- package/dist/skills/llm-wiki/templates/query-template.md +19 -0
- package/dist/skills/llm-wiki/templates/schema-template.md +185 -0
- package/dist/skills/llm-wiki/templates/source-template.md +51 -0
- package/dist/skills/llm-wiki/templates/synthesis-template.md +25 -0
- package/dist/skills/llm-wiki/templates/topic-template.md +35 -0
- package/dist/skills/llm-wiki/tests/adapter-state.sh +338 -0
- package/dist/skills/llm-wiki/tests/browser/graph-density-preview.mjs +53 -0
- package/dist/skills/llm-wiki/tests/browser/graph-html-insights.mjs +41 -0
- package/dist/skills/llm-wiki/tests/browser/graph-node-slim.mjs +71 -0
- package/dist/skills/llm-wiki/tests/browser/graph-stage-4-5.mjs +717 -0
- package/dist/skills/llm-wiki/tests/expected/graph-data-empty.json +60 -0
- package/dist/skills/llm-wiki/tests/expected/graph-data-sample.json +396 -0
- package/dist/skills/llm-wiki/tests/expected/graph-interactive-basic.html +1736 -0
- package/dist/skills/llm-wiki/tests/expected/lint-output.txt +44 -0
- package/dist/skills/llm-wiki/tests/fixtures/coverage-sample-wiki/index.md +1 -0
- package/dist/skills/llm-wiki/tests/fixtures/coverage-sample-wiki/wiki/entities/Alpha.md +10 -0
- package/dist/skills/llm-wiki/tests/fixtures/coverage-sample-wiki/wiki/entities/Beta.md +7 -0
- package/dist/skills/llm-wiki/tests/fixtures/coverage-sample-wiki/wiki/entities/Delta.md +8 -0
- package/dist/skills/llm-wiki/tests/fixtures/coverage-sample-wiki/wiki/entities/Gamma.md +8 -0
- package/dist/skills/llm-wiki/tests/fixtures/coverage-sample-wiki/wiki/queries/20260422-abc.md +10 -0
- package/dist/skills/llm-wiki/tests/fixtures/coverage-sample-wiki/wiki/synthesis/Crystal.md +8 -0
- package/dist/skills/llm-wiki/tests/fixtures/coverage-sample-wiki/wiki/topics/Overview.md +9 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-empty-wiki/wiki/entities/.gitkeep +0 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-sample-wiki/purpose.md +3 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-sample-wiki/wiki/entities/Attention.md +7 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-sample-wiki/wiki/entities/Decoder.md +7 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-sample-wiki/wiki/entities/Encoder.md +7 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-sample-wiki/wiki/entities/GPT.md +7 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-sample-wiki/wiki/entities/Transformer.md +13 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-sample-wiki/wiki/sources/paper.md +9 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-sample-wiki/wiki/topics/arch.md +12 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-data-sample-wiki/wiki/topics/finetune.md +9 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-interactive-basic/wiki/graph-data.json +40 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-interactive-dense/wiki/graph-data.json +3965 -0
- package/dist/skills/llm-wiki/tests/fixtures/graph-interactive-multicomm/wiki/graph-data.json +212 -0
- package/dist/skills/llm-wiki/tests/fixtures/lint-sample-wiki/index.md +14 -0
- package/dist/skills/llm-wiki/tests/fixtures/lint-sample-wiki/wiki/entities/C++.md +6 -0
- package/dist/skills/llm-wiki/tests/fixtures/lint-sample-wiki/wiki/entities/Lonely.md +4 -0
- package/dist/skills/llm-wiki/tests/fixtures/lint-sample-wiki/wiki/entities/Other.md +8 -0
- package/dist/skills/llm-wiki/tests/fixtures/lint-sample-wiki/wiki/entities/Real.md +7 -0
- package/dist/skills/llm-wiki/tests/fixtures/lint-sample-wiki/wiki/sources/2026-01-01-test-source.md +15 -0
- package/dist/skills/llm-wiki/tests/fixtures/lint-sample-wiki/wiki/sources/2026-02-01-inline-img.md +12 -0
- package/dist/skills/llm-wiki/tests/fixtures/lint-sample-wiki/wiki/topics/AIbasic.md +3 -0
- package/dist/skills/llm-wiki/tests/graph-analysis-helper.regression-1.sh +237 -0
- package/dist/skills/llm-wiki/tests/graph-browser-stage-4-5.regression-1.sh +157 -0
- package/dist/skills/llm-wiki/tests/graph-build-failures.regression-1.sh +91 -0
- package/dist/skills/llm-wiki/tests/graph-data-confidence-merge.regression-1.sh +309 -0
- package/dist/skills/llm-wiki/tests/graph-data-source-paths.regression-1.sh +49 -0
- package/dist/skills/llm-wiki/tests/graph-html-a11y.regression-1.sh +47 -0
- package/dist/skills/llm-wiki/tests/graph-html-brand-link.regression-1.sh +47 -0
- package/dist/skills/llm-wiki/tests/graph-html-density.regression-1.sh +228 -0
- package/dist/skills/llm-wiki/tests/graph-html-drawer-neighbors.regression-1.sh +47 -0
- package/dist/skills/llm-wiki/tests/graph-html-insights.regression-1.sh +80 -0
- package/dist/skills/llm-wiki/tests/graph-html-long-label.regression-1.sh +92 -0
- package/dist/skills/llm-wiki/tests/graph-html-minimap.regression-1.sh +51 -0
- package/dist/skills/llm-wiki/tests/graph-html-mobile.regression-1.sh +48 -0
- package/dist/skills/llm-wiki/tests/graph-html-oriental-atlas-contract.regression-1.sh +94 -0
- package/dist/skills/llm-wiki/tests/graph-html-oriental-design-contract.regression-1.sh +82 -0
- package/dist/skills/llm-wiki/tests/graph-html-search.regression-1.sh +52 -0
- package/dist/skills/llm-wiki/tests/graph-html-styles.regression-1.sh +66 -0
- package/dist/skills/llm-wiki/tests/graph-html-toolbar.regression-1.sh +62 -0
- package/dist/skills/llm-wiki/tests/js/graph-wash-bootstrap.test.js +110 -0
- package/dist/skills/llm-wiki/tests/js/graph-wash-helpers.test.js +296 -0
- package/dist/skills/llm-wiki/tests/js/graph-wash-learning.test.js +260 -0
- package/dist/skills/llm-wiki/tests/js/graph-wash-queue.test.js +115 -0
- package/dist/skills/llm-wiki/tests/js/graph-wash-runtime-state.test.js +282 -0
- package/dist/skills/llm-wiki/tests/js/source-signal-coverage.test.js +48 -0
- package/dist/skills/llm-wiki/tests/js/source-signal-eligibility.test.js +202 -0
- package/dist/skills/llm-wiki/tests/lib/graph-html-engine-helpers.sh +110 -0
- package/dist/skills/llm-wiki/tests/lint-output.regression-1.sh +32 -0
- package/dist/skills/llm-wiki/tests/regression.sh +1754 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/.gitkeep +0 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/SKILL.md +590 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/__init__.py +1 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/accept_changes.py +135 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/comment.py +318 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/helpers/__init__.py +0 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/helpers/merge_runs.py +199 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/pack.py +159 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/soffice.py +183 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/unpack.py +132 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/validate.py +111 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/validators/__init__.py +15 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/validators/base.py +847 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/validators/docx.py +446 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/validators/pptx.py +275 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/office/validators/redlining.py +247 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/templates/comments.xml +3 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/docx/scripts/templates/people.xml +3 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/SKILL.md +314 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/forms.md +294 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/reference.md +612 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/scripts/check_bounding_boxes.py +65 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/scripts/check_fillable_fields.py +11 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/scripts/create_validation_image.py +37 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/scripts/extract_form_field_info.py +122 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/scripts/extract_form_structure.py +115 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/scripts/fill_fillable_fields.py +98 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/SKILL.md +232 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/editing.md +205 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/pptxgenjs.md +420 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/__init__.py +0 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/add_slide.py +195 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/clean.py +286 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/helpers/__init__.py +0 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/pack.py +159 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/soffice.py +183 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/unpack.py +132 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/validate.py +111 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/validators/__init__.py +15 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/validators/base.py +847 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/validators/docx.py +446 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/validators/pptx.py +275 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/office/validators/redlining.py +247 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/pptx/scripts/thumbnail.py +289 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/SKILL.md +292 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/pack.py +159 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/soffice.py +183 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/unpack.py +132 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/validate.py +111 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/validators/__init__.py +15 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/validators/base.py +847 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/validators/docx.py +446 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/validators/pptx.py +275 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/office/validators/redlining.py +247 -0
- package/dist/skills/llm-wiki/workbench/.claude/skills/xlsx/scripts/recalc.py +184 -0
- package/dist/skills/llm-wiki/workbench/AGENTS.md +110 -0
- package/dist/skills/llm-wiki/workbench/CLAUDE.md +54 -0
- package/dist/skills/llm-wiki/workbench/PRODUCT.md +1268 -0
- package/dist/skills/llm-wiki/workbench/docs/current-kb-retrieval-design.md +587 -0
- package/dist/skills/llm-wiki/workbench/docs/graph-evolution-1-design.md +210 -0
- package/dist/skills/llm-wiki/workbench/docs/stage-2-design.md +1139 -0
- package/dist/skills/llm-wiki/workbench/docs/stage-3-design.md +1269 -0
- package/dist/skills/llm-wiki/workbench/docs/stage-3.5-design.md +1164 -0
- package/dist/skills/llm-wiki/workbench/docs/stage-4-design.md +504 -0
- package/dist/skills/llm-wiki/workbench/docs/stage-4.5-design.md +195 -0
- package/dist/skills/llm-wiki/workbench/docs/superpowers/specs/2026-05-28-resizable-preview-layout-design.md +165 -0
- package/dist/skills/llm-wiki/workbench/docs/superpowers/specs/2026-05-28-settings-panel-scroll-fix.md +50 -0
- package/dist/skills/llm-wiki/workbench/server/package.json +29 -0
- package/dist/skills/llm-wiki/workbench/server/src/agent.ts +457 -0
- package/dist/skills/llm-wiki/workbench/server/src/artifacts.ts +248 -0
- package/dist/skills/llm-wiki/workbench/server/src/auth.ts +164 -0
- package/dist/skills/llm-wiki/workbench/server/src/config.ts +130 -0
- package/dist/skills/llm-wiki/workbench/server/src/conversations.test.ts +121 -0
- package/dist/skills/llm-wiki/workbench/server/src/conversations.ts +255 -0
- package/dist/skills/llm-wiki/workbench/server/src/digest/batch.ts +189 -0
- package/dist/skills/llm-wiki/workbench/server/src/digest/concurrency.test.ts +36 -0
- package/dist/skills/llm-wiki/workbench/server/src/digest/concurrency.ts +31 -0
- package/dist/skills/llm-wiki/workbench/server/src/digest/subagent.ts +91 -0
- package/dist/skills/llm-wiki/workbench/server/src/extensions/artifacts.ts +81 -0
- package/dist/skills/llm-wiki/workbench/server/src/extensions/knowledge-base.ts +263 -0
- package/dist/skills/llm-wiki/workbench/server/src/extensions/new-wiki.ts +42 -0
- package/dist/skills/llm-wiki/workbench/server/src/extensions/synthesis.ts +172 -0
- package/dist/skills/llm-wiki/workbench/server/src/graph-watcher.test.ts +196 -0
- package/dist/skills/llm-wiki/workbench/server/src/graph.test.ts +84 -0
- package/dist/skills/llm-wiki/workbench/server/src/graph.ts +379 -0
- package/dist/skills/llm-wiki/workbench/server/src/index.ts +1148 -0
- package/dist/skills/llm-wiki/workbench/server/src/knowledge-bases.test.ts +30 -0
- package/dist/skills/llm-wiki/workbench/server/src/knowledge-bases.ts +321 -0
- package/dist/skills/llm-wiki/workbench/server/src/pages.test.ts +16 -0
- package/dist/skills/llm-wiki/workbench/server/src/pages.ts +158 -0
- package/dist/skills/llm-wiki/workbench/server/src/retrieval.test.ts +151 -0
- package/dist/skills/llm-wiki/workbench/server/src/retrieval.ts +427 -0
- package/dist/skills/llm-wiki/workbench/server/src/tool-status-events.test.ts +363 -0
- package/dist/skills/llm-wiki/workbench/server/src/tool-status-events.ts +616 -0
- package/dist/skills/llm-wiki/workbench/server/src/wiki-init.test.ts +19 -0
- package/dist/skills/llm-wiki/workbench/server/src/wiki-init.ts +188 -0
- package/dist/skills/llm-wiki/workbench/server/tsconfig.json +10 -0
- package/dist/skills/llm-wiki/workbench/tsconfig.json +20 -0
- package/dist/skills/llm-wiki/workbench/web/components.json +21 -0
- package/dist/skills/llm-wiki/workbench/web/eslint.config.js +22 -0
- package/dist/skills/llm-wiki/workbench/web/index.html +13 -0
- package/dist/skills/llm-wiki/workbench/web/package.json +48 -0
- package/dist/skills/llm-wiki/workbench/web/src/App.tsx +749 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/AddExternalDialog.tsx +373 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ArtifactView.tsx +15 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/BatchDigestPanel.tsx +106 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ChatPanel.tsx +821 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/CommandMenu.tsx +65 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ExportButtons.tsx +54 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/GraphPanel.tsx +516 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/GraphReader.tsx +48 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/GraphSelection.tsx +100 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/MarkdownView.tsx +56 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/NewWikiDialog.tsx +101 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/RefMenu.tsx +40 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/RightDrawer.tsx +227 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/SettingsPanel.tsx +331 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/Sidebar.tsx +436 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ToolHistorySummary.tsx +123 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ToolStatusRunway.tsx +90 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/renderers/DownloadOnlyRenderer.tsx +88 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/renderers/HtmlRenderer.tsx +92 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ui/button.tsx +52 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ui/command.tsx +50 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ui/dialog.tsx +156 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ui/input.tsx +21 -0
- package/dist/skills/llm-wiki/workbench/web/src/components/ui/tooltip.tsx +57 -0
- package/dist/skills/llm-wiki/workbench/web/src/index.css +1737 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/api.ts +648 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/drawer-state.ts +79 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/graph-reader.ts +25 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/graph-selection-drawer.ts +35 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/graph-selection.ts +96 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/legacy-tool-status.ts +113 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/sse.ts +72 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/tool-status-format.ts +170 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/tool-status-model.ts +392 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/utils.ts +10 -0
- package/dist/skills/llm-wiki/workbench/web/src/lib/wiki-links.ts +32 -0
- package/dist/skills/llm-wiki/workbench/web/src/main.tsx +10 -0
- package/dist/skills/llm-wiki/workbench/web/test/api.test.ts +61 -0
- package/dist/skills/llm-wiki/workbench/web/test/chat-panel-tool-status.test.tsx +26 -0
- package/dist/skills/llm-wiki/workbench/web/test/graph-drawer-state.test.ts +78 -0
- package/dist/skills/llm-wiki/workbench/web/test/graph-reader-request.test.ts +32 -0
- package/dist/skills/llm-wiki/workbench/web/test/graph-reader.test.ts +62 -0
- package/dist/skills/llm-wiki/workbench/web/test/graph-selection-drawer.test.ts +56 -0
- package/dist/skills/llm-wiki/workbench/web/test/graph-selection.test.ts +83 -0
- package/dist/skills/llm-wiki/workbench/web/test/tool-history-summary.test.tsx +124 -0
- package/dist/skills/llm-wiki/workbench/web/test/tool-status-format.test.ts +107 -0
- package/dist/skills/llm-wiki/workbench/web/test/tool-status-model.test.ts +324 -0
- package/dist/skills/llm-wiki/workbench/web/test/tool-status-runway.test.ts +134 -0
- package/dist/skills/llm-wiki/workbench/web/test/wiki-links.test.ts +23 -0
- package/dist/skills/llm-wiki/workbench/web/tsconfig.app.json +30 -0
- package/dist/skills/llm-wiki/workbench/web/tsconfig.json +7 -0
- package/dist/skills/llm-wiki/workbench/web/tsconfig.node.json +24 -0
- package/dist/skills/llm-wiki/workbench/web/vite.config.ts +26 -0
- package/dist/skills/mean-reviewer/SECURITY.md +23 -0
- package/dist/skills/mean-reviewer/SKILL.md +203 -0
- package/dist/skills/mean-reviewer/examples/13446_1000_Layer_Networks_for_.pdf +0 -0
- package/dist/skills/mean-reviewer/examples/13446_mean_reviewer.md +116 -0
- package/dist/skills/mean-reviewer/examples/13446_mean_reviewer_author_rebuttal_simulated.md +282 -0
- package/dist/skills/mean-reviewer/examples/13446_mean_reviewer_reply.md +91 -0
- package/dist/skills/mean-reviewer/examples/13446_official_review.json +973 -0
- package/dist/skills/mean-reviewer/examples/ChatGPT_AC_Roleplay.jpeg +0 -0
- package/dist/skills/medical-imaging-review/SKILL.md +295 -0
- package/dist/skills/medical-imaging-review/references/CITATION_INTEGRITY.md +233 -0
- package/dist/skills/medical-imaging-review/references/DOMAINS.md +302 -0
- package/dist/skills/medical-imaging-review/references/HALLUCINATION_PATTERNS.md +211 -0
- package/dist/skills/medical-imaging-review/references/MCP_SETUP.md +216 -0
- package/dist/skills/medical-imaging-review/references/PARADIGM.md +221 -0
- package/dist/skills/medical-imaging-review/references/QUALITY_CHECKLIST.md +169 -0
- package/dist/skills/medical-imaging-review/references/TEMPLATES.md +291 -0
- package/dist/skills/medical-imaging-review/references/WORKFLOW.md +289 -0
- package/dist/skills/mermaid-diagram/SKILL.md +379 -0
- package/dist/skills/meta-apply/SKILL.md +137 -0
- package/dist/skills/meta-optimize/SKILL.md +253 -0
- package/dist/skills/monitor-experiment/SKILL.md +98 -0
- package/dist/skills/novelty-check/SKILL.md +89 -0
- package/dist/skills/openalex/SKILL.md +228 -0
- package/dist/skills/overleaf-sync/SKILL.md +220 -0
- package/dist/skills/paper-claim-audit/SKILL.md +340 -0
- package/dist/skills/paper-compile/SKILL.md +253 -0
- package/dist/skills/paper-figure/SKILL.md +279 -0
- package/dist/skills/paper-illustration/SKILL.md +690 -0
- package/dist/skills/paper-illustration-image2/SKILL.md +383 -0
- package/dist/skills/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
- package/dist/skills/paper-plan/SKILL.md +278 -0
- package/dist/skills/paper-poster/SKILL.md +19 -0
- package/dist/skills/paper-poster-html/DESIGN_FINAL.md +176 -0
- package/dist/skills/paper-poster-html/IMPLEMENTATION_CONVENTIONS.md +161 -0
- package/dist/skills/paper-poster-html/LICENSES/posterly-MIT.txt +21 -0
- package/dist/skills/paper-poster-html/NOTICE.md +57 -0
- package/dist/skills/paper-poster-html/SKILL.md +357 -0
- package/dist/skills/paper-poster-html/scripts/_posterly/__init__.py +0 -0
- package/dist/skills/paper-poster-html/scripts/_posterly/canvas.py +200 -0
- package/dist/skills/paper-poster-html/scripts/_posterly/measure.py +588 -0
- package/dist/skills/paper-poster-html/scripts/_posterly/polish.py +498 -0
- package/dist/skills/paper-poster-html/scripts/_posterly/preflight.py +489 -0
- package/dist/skills/paper-poster-html/scripts/_posterly/render.py +215 -0
- package/dist/skills/paper-poster-html/scripts/_posterly/textutil.py +16 -0
- package/dist/skills/paper-poster-html/scripts/_posterly/verify_final.py +171 -0
- package/dist/skills/paper-poster-html/scripts/asset_check.py +897 -0
- package/dist/skills/paper-poster-html/scripts/extract_pdf_figures.py +666 -0
- package/dist/skills/paper-poster-html/scripts/poster_check.py +251 -0
- package/dist/skills/paper-poster-html/scripts/preprocess_figures.py +238 -0
- package/dist/skills/paper-poster-html/scripts/render_preview.py +217 -0
- package/dist/skills/paper-poster-html/scripts/run_gates.py +556 -0
- package/dist/skills/paper-poster-html/scripts/style_check.py +1324 -0
- package/dist/skills/paper-poster-html/templates/COMPONENTS.md +462 -0
- package/dist/skills/paper-poster-html/templates/landscape_4col.html +1032 -0
- package/dist/skills/paper-poster-html/templates/landscape_hero.html +1046 -0
- package/dist/skills/paper-poster-html/templates/portrait_2col.html +947 -0
- package/dist/skills/paper-poster-html/templates/tokens/acl.json +9 -0
- package/dist/skills/paper-poster-html/templates/tokens/cvpr.json +9 -0
- package/dist/skills/paper-poster-html/templates/tokens/generic.json +9 -0
- package/dist/skills/paper-poster-html/templates/tokens/iclr.json +9 -0
- package/dist/skills/paper-poster-html/templates/tokens/icml.json +9 -0
- package/dist/skills/paper-poster-html/templates/tokens/neurips.json +9 -0
- package/dist/skills/paper-slide-deck/SKILL.md +464 -0
- package/dist/skills/paper-slide-deck/references/analysis-framework.md +392 -0
- package/dist/skills/paper-slide-deck/references/base-prompt.md +70 -0
- package/dist/skills/paper-slide-deck/references/content-rules.md +95 -0
- package/dist/skills/paper-slide-deck/references/figure-container-template.md +177 -0
- package/dist/skills/paper-slide-deck/references/modification-guide.md +85 -0
- package/dist/skills/paper-slide-deck/references/outline-template.md +437 -0
- package/dist/skills/paper-slide-deck/references/styles/academic-paper.md +109 -0
- package/dist/skills/paper-slide-deck/references/styles/blueprint.md +67 -0
- package/dist/skills/paper-slide-deck/references/styles/bold-editorial.md +70 -0
- package/dist/skills/paper-slide-deck/references/styles/chalkboard.md +68 -0
- package/dist/skills/paper-slide-deck/references/styles/corporate.md +69 -0
- package/dist/skills/paper-slide-deck/references/styles/dark-atmospheric.md +69 -0
- package/dist/skills/paper-slide-deck/references/styles/editorial-infographic.md +73 -0
- package/dist/skills/paper-slide-deck/references/styles/fantasy-animation.md +69 -0
- package/dist/skills/paper-slide-deck/references/styles/intuition-machine.md +72 -0
- package/dist/skills/paper-slide-deck/references/styles/minimal.md +64 -0
- package/dist/skills/paper-slide-deck/references/styles/notion.md +69 -0
- package/dist/skills/paper-slide-deck/references/styles/pixel-art.md +67 -0
- package/dist/skills/paper-slide-deck/references/styles/scientific.md +73 -0
- package/dist/skills/paper-slide-deck/references/styles/sketch-notes.md +66 -0
- package/dist/skills/paper-slide-deck/references/styles/vector-illustration.md +72 -0
- package/dist/skills/paper-slide-deck/references/styles/vintage.md +73 -0
- package/dist/skills/paper-slide-deck/references/styles/watercolor.md +68 -0
- package/dist/skills/paper-slide-deck/scripts/apply-template.ts +282 -0
- package/dist/skills/paper-slide-deck/scripts/detect-figures.ts +283 -0
- package/dist/skills/paper-slide-deck/scripts/extract-figure.ts +140 -0
- package/dist/skills/paper-slide-deck/scripts/generate-slides.py +182 -0
- package/dist/skills/paper-slide-deck/scripts/merge-to-pdf.ts +116 -0
- package/dist/skills/paper-slide-deck/scripts/merge-to-pptx.ts +137 -0
- package/dist/skills/paper-slide-deck/scripts/package-lock.json +899 -0
- package/dist/skills/paper-slide-deck/scripts/package.json +8 -0
- package/dist/skills/paper-slides/SKILL.md +571 -0
- package/dist/skills/paper-talk/SKILL.md +381 -0
- package/dist/skills/paper-write/SKILL.md +411 -0
- package/dist/skills/paper-write/templates/IEEEtran.bst +2409 -0
- package/dist/skills/paper-write/templates/IEEEtran.cls +6347 -0
- package/dist/skills/paper-write/templates/aaai2026.bst +1493 -0
- package/dist/skills/paper-write/templates/aaai2026.sty +315 -0
- package/dist/skills/paper-write/templates/aaai2026.tex +952 -0
- package/dist/skills/paper-write/templates/acl.sty +312 -0
- package/dist/skills/paper-write/templates/acl2026.tex +377 -0
- package/dist/skills/paper-write/templates/acl_natbib.bst +1940 -0
- package/dist/skills/paper-write/templates/acm.bst +3081 -0
- package/dist/skills/paper-write/templates/acm_mm2026.tex +204 -0
- package/dist/skills/paper-write/templates/acmart.cls +3520 -0
- package/dist/skills/paper-write/templates/cvpr.bst +1448 -0
- package/dist/skills/paper-write/templates/cvpr.sty +508 -0
- package/dist/skills/paper-write/templates/cvpr2026.tex +63 -0
- package/dist/skills/paper-write/templates/iclr2026.tex +84 -0
- package/dist/skills/paper-write/templates/iclr2026_conference.bst +1440 -0
- package/dist/skills/paper-write/templates/iclr2026_conference.sty +246 -0
- package/dist/skills/paper-write/templates/icml2025.tex +87 -0
- package/dist/skills/paper-write/templates/icml2026.sty +767 -0
- package/dist/skills/paper-write/templates/icml2026.tex +662 -0
- package/dist/skills/paper-write/templates/ieee_conference.tex +89 -0
- package/dist/skills/paper-write/templates/ieee_journal.tex +93 -0
- package/dist/skills/paper-write/templates/math_commands.tex +48 -0
- package/dist/skills/paper-write/templates/neurips2025.tex +80 -0
- package/dist/skills/paper-write/templates/neurips2026.tex +493 -0
- package/dist/skills/paper-write/templates/neurips_2026.sty +437 -0
- package/dist/skills/paper-writing/SKILL.md +632 -0
- package/dist/skills/patent-novelty-check/SKILL.md +153 -0
- package/dist/skills/patent-pipeline/SKILL.md +344 -0
- package/dist/skills/patent-review/SKILL.md +202 -0
- package/dist/skills/pixel-art/SKILL.md +139 -0
- package/dist/skills/pre-submission-reviewer/SKILL.md +247 -0
- package/dist/skills/pre-submission-reviewer/references/forbidden-patterns.md +163 -0
- package/dist/skills/pre-submission-reviewer/references/grammar-rules.md +135 -0
- package/dist/skills/pre-submission-reviewer/references/latex-rules.md +158 -0
- package/dist/skills/pre-submission-reviewer/references/logic-and-structure.md +125 -0
- package/dist/skills/pre-submission-reviewer/references/section-guides.md +169 -0
- package/dist/skills/prior-art-search/SKILL.md +146 -0
- package/dist/skills/proof-checker/SKILL.md +499 -0
- package/dist/skills/proof-writer/SKILL.md +222 -0
- package/dist/skills/qzcli/SKILL.md +316 -0
- package/dist/skills/rebuttal/SKILL.md +304 -0
- package/dist/skills/render-html/SKILL.md +299 -0
- package/dist/skills/render-html/scripts/render_html.py +905 -0
- package/dist/skills/render-html/scripts/templates/academic.html +342 -0
- package/dist/skills/render-html/scripts/templates/dashboard.html +333 -0
- package/dist/skills/research-lit/SKILL.md +353 -0
- package/dist/skills/research-paper-writing/SKILL.md +99 -0
- package/dist/skills/research-paper-writing/agents/openai.yaml +4 -0
- package/dist/skills/research-paper-writing/references/abstract.md +102 -0
- package/dist/skills/research-paper-writing/references/conclusion.md +35 -0
- package/dist/skills/research-paper-writing/references/does-my-writing-flow-source.md +45 -0
- package/dist/skills/research-paper-writing/references/examples/abstract/template-a.md +21 -0
- package/dist/skills/research-paper-writing/references/examples/abstract/template-b.md +34 -0
- package/dist/skills/research-paper-writing/references/examples/abstract/template-c.md +28 -0
- package/dist/skills/research-paper-writing/references/examples/abstract-examples.md +13 -0
- package/dist/skills/research-paper-writing/references/examples/index.md +21 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/novel-task-challenge-decomposition.md +18 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/pipeline-not-recommended-abstract-only.md +30 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/pipeline-version-1-one-contribution-multi-advantages.md +30 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/pipeline-version-2-two-contributions.md +34 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/pipeline-version-3-new-module-on-existing-pipeline.md +18 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/pipeline-version-4-observation-driven.md +16 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/technical-challenge-version-1-existing-task.md +32 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/technical-challenge-version-2-existing-task-insight-backed-by-traditional.md +33 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/technical-challenge-version-3-novel-task.md +21 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/version-1-task-then-application.md +14 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/version-2-application-first.md +10 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/version-3-general-to-specific-setting.md +14 -0
- package/dist/skills/research-paper-writing/references/examples/introduction/version-4-open-with-challenge.md +20 -0
- package/dist/skills/research-paper-writing/references/examples/introduction-examples.md +25 -0
- package/dist/skills/research-paper-writing/references/examples/method/example-of-the-three-elements.md +67 -0
- package/dist/skills/research-paper-writing/references/examples/method/method-writing-common-issues-note.md +10 -0
- package/dist/skills/research-paper-writing/references/examples/method/module-design-instant-ngp.md +55 -0
- package/dist/skills/research-paper-writing/references/examples/method/module-motivation-patterns.md +15 -0
- package/dist/skills/research-paper-writing/references/examples/method/module-triad-neural-body.md +19 -0
- package/dist/skills/research-paper-writing/references/examples/method/neural-body-annotated-figure-text.md +66 -0
- package/dist/skills/research-paper-writing/references/examples/method/overview-template.md +30 -0
- package/dist/skills/research-paper-writing/references/examples/method/pre-writing-questions.md +17 -0
- package/dist/skills/research-paper-writing/references/examples/method/section-skeleton.md +9 -0
- package/dist/skills/research-paper-writing/references/examples/method-examples.md +24 -0
- package/dist/skills/research-paper-writing/references/experiments.md +102 -0
- package/dist/skills/research-paper-writing/references/introduction.md +408 -0
- package/dist/skills/research-paper-writing/references/method.md +196 -0
- package/dist/skills/research-paper-writing/references/paper-review.md +86 -0
- package/dist/skills/research-paper-writing/references/related-work.md +41 -0
- package/dist/skills/research-pipeline/SKILL.md +257 -0
- package/dist/skills/research-proposal/SKILL.md +573 -0
- package/dist/skills/research-proposal/assets/proposal_scaffold_en.md +323 -0
- package/dist/skills/research-proposal/assets/proposal_scaffold_zh.md +346 -0
- package/dist/skills/research-proposal/references/DOMAIN_TEMPLATES.md +518 -0
- package/dist/skills/research-proposal/references/LITERATURE_WORKFLOW.md +529 -0
- package/dist/skills/research-proposal/references/QUALITY_CHECKLIST.md +325 -0
- package/dist/skills/research-proposal/references/STRUCTURE_GUIDE.md +398 -0
- package/dist/skills/research-proposal/references/WRITING_STYLE_GUIDE.md +661 -0
- package/dist/skills/research-refine/SKILL.md +716 -0
- package/dist/skills/research-refine-pipeline/SKILL.md +186 -0
- package/dist/skills/research-review/SKILL.md +119 -0
- package/dist/skills/research-wiki/SKILL.md +373 -0
- package/dist/skills/resubmit-pipeline/SKILL.md +427 -0
- package/dist/skills/result-to-claim/SKILL.md +190 -0
- package/dist/skills/run-experiment/SKILL.md +227 -0
- package/dist/skills/scientific-figure-making/SKILL.md +38 -0
- package/dist/skills/scientific-figure-making/references/api.md +152 -0
- package/dist/skills/scientific-figure-making/references/common-patterns.md +74 -0
- package/dist/skills/scientific-figure-making/references/demos.md +30 -0
- package/dist/skills/scientific-figure-making/references/design-theory.md +138 -0
- package/dist/skills/scientific-figure-making/references/tutorials.md +135 -0
- package/dist/skills/semantic-scholar/SKILL.md +219 -0
- package/dist/skills/serverless-modal/SKILL.md +324 -0
- package/dist/skills/slides-polish/SKILL.md +563 -0
- package/dist/skills/specification-writing/SKILL.md +211 -0
- package/dist/skills/strategist/SKILL.md +670 -0
- package/dist/skills/strategist/references/quality_standards.md +336 -0
- package/dist/skills/strategist/references/search_strategy.md +459 -0
- package/dist/skills/strategist/scripts/evaluate_samples.py +300 -0
- package/dist/skills/strategist/scripts/gap_analysis.py +399 -0
- package/dist/skills/system-profile/SKILL.md +103 -0
- package/dist/skills/tech-paper-template/SKILL.md +192 -0
- package/dist/skills/tech-paper-template/references/consistency-checks.md +166 -0
- package/dist/skills/tech-paper-template/references/paper-types.md +127 -0
- package/dist/skills/tech-paper-template/references/thinking-template.md +194 -0
- package/dist/skills/tech-paper-template/references/worked-examples.md +124 -0
- package/dist/skills/training-check/SKILL.md +83 -0
- package/dist/skills/vast-gpu/SKILL.md +380 -0
- package/dist/skills/vibe-research-workflow/SKILL.md +189 -0
- package/dist/skills/vibe-research-workflow/references/behavior-guidelines.md +149 -0
- package/dist/skills/vibe-research-workflow/references/tool-selection.md +144 -0
- package/dist/skills/vibe-research-workflow/references/vibe-coding.md +154 -0
- package/dist/skills/vibe-research-workflow/references/vibe-figure.md +131 -0
- package/dist/skills/vibe-research-workflow/references/vibe-writing.md +181 -0
- package/dist/skills/wiki-enrich/SKILL.md +254 -0
- package/dist/skills/writing-systems-papers/SKILL.md +184 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1324 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""style_check — the style HARD gate for HTML academic posters.
|
|
3
|
+
|
|
4
|
+
Implements the 12 style rules of DESIGN_FINAL.md §3 (plus the §12.5 nit-1
|
|
5
|
+
refinement on rule 8) split across two gates:
|
|
6
|
+
|
|
7
|
+
Source gate (rules 1,2,3,5,6,7,8,9,10,11) — pure static analysis. We
|
|
8
|
+
parse the HTML with ``html.parser`` to walk tags+attributes, extract
|
|
9
|
+
the ``<style>`` blocks, strip CSS comments, locate the design-token
|
|
10
|
+
block via the canonical comment pair, and then scan for: color
|
|
11
|
+
literals outside the token block, forbidden ``style=`` attributes,
|
|
12
|
+
gradients, font-family stacks against the whitelist, font-size values
|
|
13
|
+
against the ``--fs-*`` scale, font-size token count, and the
|
|
14
|
+
data-attribute / inline-SVG contracts.
|
|
15
|
+
|
|
16
|
+
Render gate (rules 4 and 12) — needs computed style, so it lazy-imports
|
|
17
|
+
Playwright, prints-emulates the poster at the @page viewport (reusing
|
|
18
|
+
the vendored ``_posterly`` canvas+render helpers so the viewport basis
|
|
19
|
+
matches ``measure``/``polish`` exactly), reads every element's
|
|
20
|
+
computed colors, and runs the non-neutral hue-clustering check (rule
|
|
21
|
+
4) and the large-dark-area check (rule 12).
|
|
22
|
+
|
|
23
|
+
WHY two gates: the source gate is cheap, deterministic, and runnable with
|
|
24
|
+
no browser — it is what Phase 3 scaffolding relies on. The render gate
|
|
25
|
+
catches what static analysis cannot (a token resolving to an off-palette
|
|
26
|
+
hue, or a dark slab assembled from many small boxes). ``--no-render``
|
|
27
|
+
keeps the source gate live while the heavier render gate is SKIPPED — the
|
|
28
|
+
overall status can still PASS from the source gate alone, but we print a
|
|
29
|
+
notice so the user knows two rules were not evaluated.
|
|
30
|
+
|
|
31
|
+
CLI (IMPLEMENTATION_CONVENTIONS.md §C):
|
|
32
|
+
python3 style_check.py POSTER.html [--tokens TOKENS.json] [--json OUT.json]
|
|
33
|
+
[--no-render]
|
|
34
|
+
|
|
35
|
+
Exit codes: 0 = PASS (no hard failures), 1 = at least one HARD rule
|
|
36
|
+
failed, 2 = usage / environment error.
|
|
37
|
+
"""
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import argparse
|
|
41
|
+
import colorsys
|
|
42
|
+
import json
|
|
43
|
+
import math
|
|
44
|
+
import os
|
|
45
|
+
import re
|
|
46
|
+
import sys
|
|
47
|
+
from html.parser import HTMLParser
|
|
48
|
+
from pathlib import Path
|
|
49
|
+
from typing import Any
|
|
50
|
+
|
|
51
|
+
# Make `_posterly` importable when this file is run directly via
|
|
52
|
+
# `python style_check.py …` (it lives in the same scripts/ dir).
|
|
53
|
+
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
54
|
+
if _THIS_DIR not in sys.path:
|
|
55
|
+
sys.path.insert(0, _THIS_DIR)
|
|
56
|
+
|
|
57
|
+
from _posterly.textutil import ascii_safe # noqa: E402
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _eprint(*args: Any, **kw: Any) -> None:
|
|
61
|
+
print(*args, file=sys.stderr, **kw)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Whitelists (DESIGN_FINAL §2 / IMPLEMENTATION_CONVENTIONS §A).
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
#: Serif families allowed for body text (and the `--font-serif` token).
|
|
69
|
+
SERIF_WHITELIST = {
|
|
70
|
+
"charter", "source serif pro", "georgia", "times new roman", "serif",
|
|
71
|
+
}
|
|
72
|
+
#: Sans families allowed for headings / headers / table labels (and
|
|
73
|
+
#: `--font-sans`). "aptos" and "helvetica neue" / "arial" per §2.
|
|
74
|
+
SANS_WHITELIST = {
|
|
75
|
+
"inter", "aptos", "helvetica neue", "arial", "sans-serif",
|
|
76
|
+
}
|
|
77
|
+
#: Mono families allowed for code only.
|
|
78
|
+
MONO_WHITELIST = {"menlo", "consolas", "monospace"}
|
|
79
|
+
|
|
80
|
+
#: The canonical font-size scale tokens. font-size declarations must use
|
|
81
|
+
#: one of these via `var(--fs-N)` (rule 8); the count gate (rule 9) warns
|
|
82
|
+
#: when MORE than 9 distinct --fs tokens are *defined* in the token block.
|
|
83
|
+
FS_TOKEN_RE = re.compile(r"--fs-(\d+)\b")
|
|
84
|
+
|
|
85
|
+
#: A `var(--fs-N)` reference inside a font-size value.
|
|
86
|
+
FS_VAR_REF_RE = re.compile(r"var\(\s*--fs-\d+\s*\)")
|
|
87
|
+
|
|
88
|
+
#: A predefined component variant suffix on a selector: BEM-ish `--<word>`
|
|
89
|
+
#: appended to a class, e.g. `.eqn--large`, `.card--compact`,
|
|
90
|
+
#: `.figure--wide`. §12.5 nit 1: `calc(var(--fs-*) * k)` is allowed ONLY
|
|
91
|
+
#: in a rule whose selector carries such a variant suffix; everywhere else
|
|
92
|
+
#: it is a HARD fail (an arbitrary per-element font-size override sneaking
|
|
93
|
+
#: past the token scale).
|
|
94
|
+
VARIANT_SUFFIX_RE = re.compile(r"\.[A-Za-z][\w-]*--[A-Za-z][\w-]*")
|
|
95
|
+
|
|
96
|
+
#: A `calc(... var(--fs-N) ...)` expression inside a font-size value — the
|
|
97
|
+
#: only legal non-bare-token form, and only inside a variant rule.
|
|
98
|
+
CALC_FS_RE = re.compile(r"calc\([^)]*var\(\s*--fs-\d+\s*\)[^)]*\)")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Color-literal detection (rules 1, 2, 3).
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
#: Matches any CSS color literal we forbid outside the token block:
|
|
106
|
+
#: #hex (3/4/6/8 digit), rgb()/rgba(), hsl()/hsla(). Named colors are NOT
|
|
107
|
+
#: matched — they are caught structurally where it matters (the templates
|
|
108
|
+
#: ship `transparent` / `currentColor` which are intentionally allowed),
|
|
109
|
+
#: and chasing the full CSS named-color list would create false positives
|
|
110
|
+
#: on words like "red" appearing in prose. The literals above are the
|
|
111
|
+
#: forms a hand-edited off-palette color actually takes.
|
|
112
|
+
COLOR_LITERAL_RE = re.compile(
|
|
113
|
+
r"""
|
|
114
|
+
(?:\#[0-9a-fA-F]{3,8}\b) # #hex
|
|
115
|
+
| (?:\brgba?\s*\([^)]*\)) # rgb( ) / rgba( )
|
|
116
|
+
| (?:\bhsla?\s*\([^)]*\)) # hsl( ) / hsla( )
|
|
117
|
+
""",
|
|
118
|
+
re.VERBOSE,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
#: A single rgb/rgba/hsl/hsla literal we need to PARSE (for the
|
|
122
|
+
#: radial-gradient alpha check in rule 5). Captures the function name and
|
|
123
|
+
#: the comma/space separated args.
|
|
124
|
+
_FUNC_COLOR_RE = re.compile(
|
|
125
|
+
r"\b(rgb|rgba|hsl|hsla)\s*\(([^)]*)\)", re.IGNORECASE
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _parse_alpha(func: str, args: str) -> float | None:
|
|
130
|
+
"""Extract the alpha channel from an rgba()/hsla() literal.
|
|
131
|
+
|
|
132
|
+
Returns 1.0 for the opaque rgb()/hsl() forms (no alpha), the parsed
|
|
133
|
+
alpha for rgba()/hsla(), or ``None`` if the literal can't be parsed.
|
|
134
|
+
We only need alpha here (rule 5's radial-gradient stop check), so we
|
|
135
|
+
don't bother converting the color channels.
|
|
136
|
+
"""
|
|
137
|
+
parts = [p.strip() for p in re.split(r"[,/]", args) if p.strip()]
|
|
138
|
+
func = func.lower()
|
|
139
|
+
if func in ("rgb", "hsl"):
|
|
140
|
+
return 1.0
|
|
141
|
+
if func in ("rgba", "hsla"):
|
|
142
|
+
if len(parts) < 4:
|
|
143
|
+
return None
|
|
144
|
+
a = parts[3]
|
|
145
|
+
try:
|
|
146
|
+
if a.endswith("%"):
|
|
147
|
+
return float(a[:-1]) / 100.0
|
|
148
|
+
return float(a)
|
|
149
|
+
except ValueError:
|
|
150
|
+
return None
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
# HTML parsing: collect <style> CSS and every element's attributes.
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class _PosterParser(HTMLParser):
|
|
160
|
+
"""Walk the poster HTML, recording two things the source gate needs:
|
|
161
|
+
|
|
162
|
+
1. The concatenated text of all ``<style>`` blocks (so we can run
|
|
163
|
+
CSS-level checks against the same content the browser sees).
|
|
164
|
+
2. A flat list of ``(tag, attrs_dict, exempt_path)`` for every
|
|
165
|
+
start/startend tag, where ``exempt_path`` flags whether the
|
|
166
|
+
element sits inside a ``data-color-exempt="logo"`` subtree — so
|
|
167
|
+
rule 1/2/11 exemptions can be applied without a full DOM.
|
|
168
|
+
|
|
169
|
+
We track the exempt-subtree depth with a small stack: when we enter an
|
|
170
|
+
element carrying ``data-color-exempt="logo"`` (or a ``data-source``
|
|
171
|
+
"paper" subtree, for the SVG/style exemptions), every descendant
|
|
172
|
+
inherits the exemption until the matching close tag.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
#: Void elements never get a matching end tag, so they must not push
|
|
176
|
+
#: onto the exempt stack (otherwise the stack desyncs).
|
|
177
|
+
_VOID = {
|
|
178
|
+
"area", "base", "br", "col", "embed", "hr", "img", "input",
|
|
179
|
+
"link", "meta", "param", "source", "track", "wbr",
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
def __init__(self) -> None:
|
|
183
|
+
super().__init__(convert_charrefs=True)
|
|
184
|
+
self.style_css_parts: list[str] = []
|
|
185
|
+
self._in_style = False
|
|
186
|
+
# Each element record: tag, attrs(dict), inside_logo, inside_paper.
|
|
187
|
+
self.elements: list[dict[str, Any]] = []
|
|
188
|
+
# Stack of (tag, opened_logo, opened_paper) for open non-void
|
|
189
|
+
# elements, used to inherit exemption to descendants.
|
|
190
|
+
self._stack: list[tuple[str, bool, bool]] = []
|
|
191
|
+
|
|
192
|
+
# -- exemption bookkeeping ------------------------------------------
|
|
193
|
+
def _inside_logo(self) -> bool:
|
|
194
|
+
return any(s[1] for s in self._stack)
|
|
195
|
+
|
|
196
|
+
def _inside_paper(self) -> bool:
|
|
197
|
+
return any(s[2] for s in self._stack)
|
|
198
|
+
|
|
199
|
+
def _record(self, tag: str, attrs: list[tuple[str, str | None]],
|
|
200
|
+
self_closing: bool) -> tuple[bool, bool]:
|
|
201
|
+
d = {k.lower(): (v if v is not None else "") for k, v in attrs}
|
|
202
|
+
opens_logo = d.get("data-color-exempt", "").lower() == "logo"
|
|
203
|
+
opens_paper = d.get("data-source", "").lower() == "paper"
|
|
204
|
+
inside_logo = self._inside_logo() or opens_logo
|
|
205
|
+
inside_paper = self._inside_paper() or opens_paper
|
|
206
|
+
self.elements.append({
|
|
207
|
+
"tag": tag,
|
|
208
|
+
"attrs": d,
|
|
209
|
+
"inside_logo": inside_logo,
|
|
210
|
+
"inside_paper": inside_paper,
|
|
211
|
+
"self_closing": self_closing,
|
|
212
|
+
})
|
|
213
|
+
return opens_logo, opens_paper
|
|
214
|
+
|
|
215
|
+
# -- HTMLParser hooks ----------------------------------------------
|
|
216
|
+
def handle_starttag(self, tag, attrs):
|
|
217
|
+
tag = tag.lower()
|
|
218
|
+
if tag == "style":
|
|
219
|
+
self._in_style = True
|
|
220
|
+
opens_logo, opens_paper = self._record(tag, attrs, self_closing=False)
|
|
221
|
+
if tag not in self._VOID:
|
|
222
|
+
self._stack.append((tag, opens_logo, opens_paper))
|
|
223
|
+
|
|
224
|
+
def handle_startendtag(self, tag, attrs):
|
|
225
|
+
# `<svg .../>`, `<img .../>` — self-closing; record but never push.
|
|
226
|
+
self._record(tag.lower(), attrs, self_closing=True)
|
|
227
|
+
|
|
228
|
+
def handle_endtag(self, tag):
|
|
229
|
+
tag = tag.lower()
|
|
230
|
+
if tag == "style":
|
|
231
|
+
self._in_style = False
|
|
232
|
+
# Pop back to (and including) the matching open tag. Tolerant of
|
|
233
|
+
# minor mismatches so a stray close tag can't desync the stack.
|
|
234
|
+
for i in range(len(self._stack) - 1, -1, -1):
|
|
235
|
+
if self._stack[i][0] == tag:
|
|
236
|
+
del self._stack[i:]
|
|
237
|
+
break
|
|
238
|
+
|
|
239
|
+
def handle_data(self, data):
|
|
240
|
+
if self._in_style:
|
|
241
|
+
self.style_css_parts.append(data)
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def style_css(self) -> str:
|
|
245
|
+
return "\n".join(self.style_css_parts)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _strip_css_comments(css: str) -> str:
|
|
249
|
+
"""Strip ``/* … */`` comments. We must NOT strip before locating the
|
|
250
|
+
token block (the markers ARE comments), so callers strip on a copy.
|
|
251
|
+
"""
|
|
252
|
+
return re.sub(r"/\*.*?\*/", "", css, flags=re.DOTALL)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _blank_allowed_radial_stops(css: str) -> str:
|
|
256
|
+
"""Blank (space-fill) the contents of every ``radial-gradient(…)`` so
|
|
257
|
+
its color stops don't trip the rule-1 literal scan.
|
|
258
|
+
|
|
259
|
+
WHY: the ARIS fork keeps a faint radial tint on ``.poster``'s
|
|
260
|
+
background with ``rgba(…)`` stops whose alpha is <= 0.06
|
|
261
|
+
(IMPLEMENTATION_CONVENTIONS §E.2). Those rgba literals are the one
|
|
262
|
+
documented place a color literal legitimately appears OUTSIDE the token
|
|
263
|
+
block — rule 5 validates them (right selector, alpha bound), so rule 1
|
|
264
|
+
must not double-flag them. We blank (not delete) to preserve char
|
|
265
|
+
offsets. Selector/alpha legality is rule 5's job, not rule 1's.
|
|
266
|
+
"""
|
|
267
|
+
out = []
|
|
268
|
+
for pre, body, _close in _iter_radial_gradients(css):
|
|
269
|
+
out.append(pre)
|
|
270
|
+
out.append(" " * len(body)) # blank the (…) body (incl. parens)
|
|
271
|
+
return "".join(out)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _iter_radial_gradients(css: str):
|
|
275
|
+
"""Yield ``(text_before, gradient_text, end_index)`` for each
|
|
276
|
+
``radial-gradient(…)`` with BALANCED parentheses, so a nested
|
|
277
|
+
``rgba(…)`` stop is captured whole.
|
|
278
|
+
|
|
279
|
+
``gradient_text`` spans from ``radial-gradient`` through its matching
|
|
280
|
+
``)``. The naive ``radial-gradient\\((.*?)\\)`` regex stops at the
|
|
281
|
+
first inner ``)`` (the rgba close), splitting the literal — so rule 5's
|
|
282
|
+
alpha check missed stops and rule 1's blanker under-blanked. Balanced
|
|
283
|
+
walking fixes both. Pure + stdlib.
|
|
284
|
+
"""
|
|
285
|
+
low = css.lower()
|
|
286
|
+
i = 0
|
|
287
|
+
while i < len(css):
|
|
288
|
+
j = low.find("radial-gradient(", i)
|
|
289
|
+
if j == -1:
|
|
290
|
+
yield css[i:], "", len(css)
|
|
291
|
+
break
|
|
292
|
+
pre = css[i:j]
|
|
293
|
+
k = j + len("radial-gradient")
|
|
294
|
+
depth = 0
|
|
295
|
+
while k < len(css):
|
|
296
|
+
if css[k] == "(":
|
|
297
|
+
depth += 1
|
|
298
|
+
elif css[k] == ")":
|
|
299
|
+
depth -= 1
|
|
300
|
+
if depth == 0:
|
|
301
|
+
k += 1
|
|
302
|
+
break
|
|
303
|
+
k += 1
|
|
304
|
+
yield pre, css[j:k], k
|
|
305
|
+
i = k
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# Canonical token-block markers (IMPLEMENTATION_CONVENTIONS §A). The
|
|
309
|
+
# token block is the ONLY place color literals are allowed (rule 1).
|
|
310
|
+
_TOKEN_START_RE = re.compile(
|
|
311
|
+
r"/\*\s*=+\s*DESIGN TOKENS\s*=+\s*\*/", re.IGNORECASE
|
|
312
|
+
)
|
|
313
|
+
_TOKEN_END_RE = re.compile(
|
|
314
|
+
r"/\*\s*=+\s*END DESIGN TOKENS\s*=+\s*\*/", re.IGNORECASE
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _locate_token_block(css: str) -> tuple[int, int] | None:
|
|
319
|
+
"""Return ``(start, end)`` char offsets of the token block content
|
|
320
|
+
(between the two marker comments) in the *raw* (comment-bearing) CSS,
|
|
321
|
+
or ``None`` if the canonical marker pair isn't present.
|
|
322
|
+
"""
|
|
323
|
+
m_start = _TOKEN_START_RE.search(css)
|
|
324
|
+
if not m_start:
|
|
325
|
+
return None
|
|
326
|
+
m_end = _TOKEN_END_RE.search(css, m_start.end())
|
|
327
|
+
if not m_end:
|
|
328
|
+
return None
|
|
329
|
+
return m_start.end(), m_end.start()
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# ---------------------------------------------------------------------------
|
|
333
|
+
# CSS rule splitting (for selector-scoped checks: rules 3, 5, 8).
|
|
334
|
+
# ---------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _iter_css_rules(css_no_comments: str):
|
|
338
|
+
"""Yield ``(selector, body)`` for each top-level ``sel { … }`` rule.
|
|
339
|
+
|
|
340
|
+
Naive but adequate for the templates: it does not recurse into nested
|
|
341
|
+
at-rules' inner braces beyond one level, which is fine because the
|
|
342
|
+
declarations we inspect (color, font-size, background) live in flat
|
|
343
|
+
rule bodies. ``@page``/``@media`` wrappers are skipped as selectors
|
|
344
|
+
but their inner rules are still seen on the next pass via a second
|
|
345
|
+
scan of the wrapper body. WHY a hand split instead of a CSS parser:
|
|
346
|
+
stdlib has none, and the contract is "small functions, stdlib only".
|
|
347
|
+
"""
|
|
348
|
+
# First, pull out @media/@supports wrapper bodies and re-scan them so
|
|
349
|
+
# rules inside print/screen media are inspected too (the templates put
|
|
350
|
+
# `@media print { :root { --u: 1mm } }` and component overrides there).
|
|
351
|
+
def _scan(block: str):
|
|
352
|
+
i = 0
|
|
353
|
+
n = len(block)
|
|
354
|
+
while i < n:
|
|
355
|
+
brace = block.find("{", i)
|
|
356
|
+
if brace == -1:
|
|
357
|
+
break
|
|
358
|
+
selector = block[i:brace].strip()
|
|
359
|
+
depth = 1
|
|
360
|
+
j = brace + 1
|
|
361
|
+
while j < n and depth:
|
|
362
|
+
if block[j] == "{":
|
|
363
|
+
depth += 1
|
|
364
|
+
elif block[j] == "}":
|
|
365
|
+
depth -= 1
|
|
366
|
+
j += 1
|
|
367
|
+
body = block[brace + 1:j - 1]
|
|
368
|
+
if selector.startswith("@") and "{" in body:
|
|
369
|
+
# Nested at-rule: recurse one level into its body.
|
|
370
|
+
yield from _scan(body)
|
|
371
|
+
else:
|
|
372
|
+
yield selector, body
|
|
373
|
+
i = j
|
|
374
|
+
|
|
375
|
+
yield from _scan(css_no_comments)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
# Rule result container.
|
|
380
|
+
# ---------------------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class RuleResult:
|
|
384
|
+
"""One row in the JSON ``rules`` array.
|
|
385
|
+
|
|
386
|
+
``status`` is PASS / FAIL / WARN / SKIPPED. ``detail`` is a single
|
|
387
|
+
human-readable line (ascii-safe at output). A HARD rule with status
|
|
388
|
+
FAIL drives the overall gate to FAIL; a WARN rule never does.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
def __init__(self, rid: int, severity: str, title: str) -> None:
|
|
392
|
+
self.id = rid
|
|
393
|
+
self.severity = severity # "hard" | "warn"
|
|
394
|
+
self.title = title
|
|
395
|
+
self.status = "PASS"
|
|
396
|
+
self.detail = "ok"
|
|
397
|
+
|
|
398
|
+
def fail(self, detail: str) -> "RuleResult":
|
|
399
|
+
self.status = "FAIL"
|
|
400
|
+
self.detail = detail
|
|
401
|
+
return self
|
|
402
|
+
|
|
403
|
+
def warn(self, detail: str) -> "RuleResult":
|
|
404
|
+
self.status = "WARN"
|
|
405
|
+
self.detail = detail
|
|
406
|
+
return self
|
|
407
|
+
|
|
408
|
+
def skip(self, detail: str) -> "RuleResult":
|
|
409
|
+
self.status = "SKIPPED"
|
|
410
|
+
self.detail = detail
|
|
411
|
+
return self
|
|
412
|
+
|
|
413
|
+
def to_dict(self) -> dict[str, Any]:
|
|
414
|
+
return {
|
|
415
|
+
"id": self.id,
|
|
416
|
+
"severity": self.severity,
|
|
417
|
+
"status": self.status,
|
|
418
|
+
"detail": ascii_safe(self.detail),
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
# ---------------------------------------------------------------------------
|
|
423
|
+
# Source gate.
|
|
424
|
+
# ---------------------------------------------------------------------------
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def run_source_gate(
|
|
428
|
+
html_text: str, html_path: Path
|
|
429
|
+
) -> tuple[list[RuleResult], _PosterParser, str | None]:
|
|
430
|
+
"""Run rules 1,2,3,5,6,7,8,9,10,11 (the static rules).
|
|
431
|
+
|
|
432
|
+
Returns ``(results, parser, token_block_or_None)``. The parser and
|
|
433
|
+
located token block are returned so the render gate can reuse them
|
|
434
|
+
(token block holds the :root --accent/--gold for hue centers fallback).
|
|
435
|
+
"""
|
|
436
|
+
parser = _PosterParser()
|
|
437
|
+
parser.feed(html_text)
|
|
438
|
+
parser.close()
|
|
439
|
+
|
|
440
|
+
raw_css = parser.style_css
|
|
441
|
+
token_span = _locate_token_block(raw_css)
|
|
442
|
+
token_block_text: str | None = None
|
|
443
|
+
if token_span is not None:
|
|
444
|
+
token_block_text = raw_css[token_span[0]:token_span[1]]
|
|
445
|
+
|
|
446
|
+
results: list[RuleResult] = []
|
|
447
|
+
|
|
448
|
+
# --- Rule 1: color literals only inside the token block / logo SVG ---
|
|
449
|
+
r1 = RuleResult(1, "hard", "color literals only in token block")
|
|
450
|
+
if token_span is None:
|
|
451
|
+
r1.fail(
|
|
452
|
+
"design-token block not found: expected the comment pair "
|
|
453
|
+
"/* ===== DESIGN TOKENS ===== */ ... "
|
|
454
|
+
"/* ===== END DESIGN TOKENS ===== */ inside a <style> block. "
|
|
455
|
+
"Cannot locate where color literals are allowed."
|
|
456
|
+
)
|
|
457
|
+
else:
|
|
458
|
+
# Build a comment-stripped copy where the token-block region is
|
|
459
|
+
# blanked out (so literals inside it are exempt), then scan the
|
|
460
|
+
# REMAINING CSS for color literals. We blank-not-delete to keep
|
|
461
|
+
# char offsets meaningful for the error message.
|
|
462
|
+
css_scan = (
|
|
463
|
+
raw_css[:token_span[0]]
|
|
464
|
+
+ (" " * (token_span[1] - token_span[0]))
|
|
465
|
+
+ raw_css[token_span[1]:]
|
|
466
|
+
)
|
|
467
|
+
css_scan = _strip_css_comments(css_scan)
|
|
468
|
+
# The documented radial-tint exemption: alpha<=0.06 rgba stops on
|
|
469
|
+
# the .poster background are validated by rule 5, not rule 1.
|
|
470
|
+
css_scan = _blank_allowed_radial_stops(css_scan)
|
|
471
|
+
offenders = sorted(set(
|
|
472
|
+
m.group(0) for m in COLOR_LITERAL_RE.finditer(css_scan)
|
|
473
|
+
))
|
|
474
|
+
if offenders:
|
|
475
|
+
r1.fail(
|
|
476
|
+
"color literal(s) outside the token block: "
|
|
477
|
+
+ ", ".join(offenders[:8])
|
|
478
|
+
+ (" …" if len(offenders) > 8 else "")
|
|
479
|
+
+ ". Move them to a --token in the DESIGN TOKENS block and "
|
|
480
|
+
"reference via var(--…) (rule 3)."
|
|
481
|
+
)
|
|
482
|
+
results.append(r1)
|
|
483
|
+
|
|
484
|
+
# --- Rule 2: forbidden inline `style=` attributes --------------------
|
|
485
|
+
# Zero tolerance EXCEPT the two documented exemptions:
|
|
486
|
+
# (a) any element inside a data-color-exempt="logo" subtree;
|
|
487
|
+
# (b) `style="width: NN%"` on an <img data-source="paper">.
|
|
488
|
+
r2 = RuleResult(2, "hard", "no inline style= (two documented exemptions)")
|
|
489
|
+
style_offenders: list[str] = []
|
|
490
|
+
for el in parser.elements:
|
|
491
|
+
style = el["attrs"].get("style")
|
|
492
|
+
if style is None or style.strip() == "":
|
|
493
|
+
continue
|
|
494
|
+
if el["inside_logo"]:
|
|
495
|
+
continue # exemption (a)
|
|
496
|
+
# exemption (b): img[data-source=paper] with only `width: NN%`.
|
|
497
|
+
is_paper_img = (
|
|
498
|
+
el["tag"] == "img"
|
|
499
|
+
and el["attrs"].get("data-source", "").lower() == "paper"
|
|
500
|
+
)
|
|
501
|
+
if is_paper_img and re.fullmatch(
|
|
502
|
+
r"\s*width\s*:\s*\d+(?:\.\d+)?%\s*;?\s*", style
|
|
503
|
+
):
|
|
504
|
+
continue
|
|
505
|
+
style_offenders.append(f"<{el['tag']} style=\"{style.strip()}\">")
|
|
506
|
+
if style_offenders:
|
|
507
|
+
r2.fail(
|
|
508
|
+
f"{len(style_offenders)} inline style= attribute(s) "
|
|
509
|
+
"(zero-tolerance; use utility classes). First: "
|
|
510
|
+
+ style_offenders[0]
|
|
511
|
+
+ ". Exemptions: inside data-color-exempt=\"logo\", or "
|
|
512
|
+
"style=\"width: NN%\" on img[data-source=\"paper\"]."
|
|
513
|
+
)
|
|
514
|
+
results.append(r2)
|
|
515
|
+
|
|
516
|
+
# --- Rule 3: component CSS colors must be var(--…) --------------------
|
|
517
|
+
# Any color/background/border-color declaration in a NON-:root rule
|
|
518
|
+
# whose value contains a color literal fails (literals belong only in
|
|
519
|
+
# the token block, which is inside :root). This overlaps rule 1 but
|
|
520
|
+
# targets the *property* level for a clearer message.
|
|
521
|
+
r3 = RuleResult(3, "hard", "component colors use var(--…)")
|
|
522
|
+
css_nc = _strip_css_comments(raw_css)
|
|
523
|
+
color_prop_offenders: list[str] = []
|
|
524
|
+
color_prop_re = re.compile(
|
|
525
|
+
r"(?<![\w-])(color|background|background-color|border|border-color|"
|
|
526
|
+
r"border-top-color|border-bottom-color|border-left-color|"
|
|
527
|
+
r"border-right-color|fill|stroke|box-shadow|outline|outline-color)"
|
|
528
|
+
r"\s*:\s*([^;{}]+)",
|
|
529
|
+
re.IGNORECASE,
|
|
530
|
+
)
|
|
531
|
+
for selector, body in _iter_css_rules(css_nc):
|
|
532
|
+
sel_l = selector.lower()
|
|
533
|
+
# :root is where tokens live — literals there are expected.
|
|
534
|
+
if ":root" in sel_l:
|
|
535
|
+
continue
|
|
536
|
+
# Same radial-tint exemption as rule 1: the alpha<=0.06 stops on
|
|
537
|
+
# the .poster background are rule 5's responsibility, not rule 3's.
|
|
538
|
+
body_scan = _blank_allowed_radial_stops(body)
|
|
539
|
+
for m in color_prop_re.finditer(body_scan):
|
|
540
|
+
value = m.group(2)
|
|
541
|
+
if COLOR_LITERAL_RE.search(value):
|
|
542
|
+
color_prop_offenders.append(
|
|
543
|
+
f"{selector.strip()[:40]} {{ {m.group(1)}: "
|
|
544
|
+
f"{value.strip()[:40]} }}"
|
|
545
|
+
)
|
|
546
|
+
if color_prop_offenders:
|
|
547
|
+
r3.fail(
|
|
548
|
+
f"{len(color_prop_offenders)} component color declaration(s) "
|
|
549
|
+
"with a literal instead of var(--…). First: "
|
|
550
|
+
+ color_prop_offenders[0]
|
|
551
|
+
)
|
|
552
|
+
results.append(r3)
|
|
553
|
+
|
|
554
|
+
# --- Rule 5: no linear-gradient; radial only on .poster, alpha<=0.06 -
|
|
555
|
+
r5 = RuleResult(5, "hard", "no linear-gradient; radial only .poster bg")
|
|
556
|
+
if re.search(r"\blinear-gradient\s*\(", css_nc, re.IGNORECASE):
|
|
557
|
+
r5.fail(
|
|
558
|
+
"linear-gradient is forbidden (flat, de-gradient design). "
|
|
559
|
+
"Replace with a solid var(--…) fill."
|
|
560
|
+
)
|
|
561
|
+
else:
|
|
562
|
+
radial_problems: list[str] = []
|
|
563
|
+
for selector, body in _iter_css_rules(css_nc):
|
|
564
|
+
if "radial-gradient" not in body.lower():
|
|
565
|
+
continue
|
|
566
|
+
sel_l = selector.lower()
|
|
567
|
+
# Allowed ONLY on the .poster background.
|
|
568
|
+
if ".poster" not in sel_l:
|
|
569
|
+
radial_problems.append(
|
|
570
|
+
f"radial-gradient on '{selector.strip()[:50]}' "
|
|
571
|
+
"(only .poster background may use it)"
|
|
572
|
+
)
|
|
573
|
+
continue
|
|
574
|
+
# Every color stop's alpha must be <= 0.06. Use balanced
|
|
575
|
+
# extraction so a nested rgba() stop is captured whole.
|
|
576
|
+
for _pre, grad_text, _end in _iter_radial_gradients(body):
|
|
577
|
+
if not grad_text:
|
|
578
|
+
continue
|
|
579
|
+
for fm in _FUNC_COLOR_RE.finditer(grad_text):
|
|
580
|
+
a = _parse_alpha(fm.group(1), fm.group(2))
|
|
581
|
+
if a is None:
|
|
582
|
+
continue
|
|
583
|
+
if a > 0.06 + 1e-9:
|
|
584
|
+
radial_problems.append(
|
|
585
|
+
f"radial-gradient stop alpha={a:g} > 0.06 on "
|
|
586
|
+
f"'{selector.strip()[:40]}'"
|
|
587
|
+
)
|
|
588
|
+
if radial_problems:
|
|
589
|
+
r5.fail("; ".join(radial_problems[:4]))
|
|
590
|
+
results.append(r5)
|
|
591
|
+
|
|
592
|
+
# --- Rules 6 + 7: font pairing + whitelist ---------------------------
|
|
593
|
+
# We inspect every `font-family:` declaration. The token convention is
|
|
594
|
+
# that body text uses `--font-serif` and headings/headers/table labels
|
|
595
|
+
# use `--font-sans`; the actual stacks are DEFINED once on those two
|
|
596
|
+
# tokens. So we (a) whitelist-check the families in --font-serif /
|
|
597
|
+
# --font-sans / --font-mono token definitions (rule 7), and (b) verify
|
|
598
|
+
# the serif token is a serif stack, the sans token a sans stack, and
|
|
599
|
+
# that components reference the *right* token for their role (rule 6).
|
|
600
|
+
r6 = RuleResult(6, "hard", "font pairing: serif body / sans heading")
|
|
601
|
+
r7 = RuleResult(7, "hard", "font-family whitelist")
|
|
602
|
+
|
|
603
|
+
# Pull token stack definitions from the token block (if present),
|
|
604
|
+
# else from any :root.
|
|
605
|
+
def _families(value: str) -> list[str]:
|
|
606
|
+
out = []
|
|
607
|
+
for raw in value.split(","):
|
|
608
|
+
fam = raw.strip().strip('"').strip("'").lower()
|
|
609
|
+
if fam:
|
|
610
|
+
out.append(fam)
|
|
611
|
+
return out
|
|
612
|
+
|
|
613
|
+
serif_def = sans_def = mono_def = None
|
|
614
|
+
scope = token_block_text if token_block_text is not None else css_nc
|
|
615
|
+
m = re.search(r"--font-serif\s*:\s*([^;{}]+)", scope, re.IGNORECASE)
|
|
616
|
+
if m:
|
|
617
|
+
serif_def = _families(m.group(1))
|
|
618
|
+
m = re.search(r"--font-sans\s*:\s*([^;{}]+)", scope, re.IGNORECASE)
|
|
619
|
+
if m:
|
|
620
|
+
sans_def = _families(m.group(1))
|
|
621
|
+
m = re.search(r"--font-mono\s*:\s*([^;{}]+)", scope, re.IGNORECASE)
|
|
622
|
+
if m:
|
|
623
|
+
mono_def = _families(m.group(1))
|
|
624
|
+
|
|
625
|
+
wl_problems: list[str] = []
|
|
626
|
+
if serif_def is not None:
|
|
627
|
+
bad = [f for f in serif_def if f not in SERIF_WHITELIST]
|
|
628
|
+
if bad:
|
|
629
|
+
wl_problems.append(
|
|
630
|
+
f"--font-serif has non-whitelisted family/ies: "
|
|
631
|
+
f"{', '.join(bad)}"
|
|
632
|
+
)
|
|
633
|
+
if sans_def is not None:
|
|
634
|
+
bad = [f for f in sans_def if f not in SANS_WHITELIST]
|
|
635
|
+
if bad:
|
|
636
|
+
wl_problems.append(
|
|
637
|
+
f"--font-sans has non-whitelisted family/ies: "
|
|
638
|
+
f"{', '.join(bad)}"
|
|
639
|
+
)
|
|
640
|
+
if mono_def is not None:
|
|
641
|
+
bad = [f for f in mono_def if f not in MONO_WHITELIST]
|
|
642
|
+
if bad:
|
|
643
|
+
wl_problems.append(
|
|
644
|
+
f"--font-mono has non-whitelisted family/ies: "
|
|
645
|
+
f"{', '.join(bad)}"
|
|
646
|
+
)
|
|
647
|
+
# Also catch literal font stacks used directly in component rules
|
|
648
|
+
# (bypassing the tokens) and whitelist-check them.
|
|
649
|
+
for selector, body in _iter_css_rules(css_nc):
|
|
650
|
+
for fm in re.finditer(
|
|
651
|
+
r"font-family\s*:\s*([^;{}]+)", body, re.IGNORECASE
|
|
652
|
+
):
|
|
653
|
+
val = fm.group(1)
|
|
654
|
+
if "var(" in val:
|
|
655
|
+
continue # token reference — checked via the token def
|
|
656
|
+
fams = _families(val)
|
|
657
|
+
allowed = SERIF_WHITELIST | SANS_WHITELIST | MONO_WHITELIST
|
|
658
|
+
bad = [f for f in fams if f not in allowed]
|
|
659
|
+
if bad:
|
|
660
|
+
wl_problems.append(
|
|
661
|
+
f"literal font stack on '{selector.strip()[:30]}' has "
|
|
662
|
+
f"non-whitelisted: {', '.join(bad)}"
|
|
663
|
+
)
|
|
664
|
+
if wl_problems:
|
|
665
|
+
r7.fail("; ".join(wl_problems[:4]))
|
|
666
|
+
results.append(r7)
|
|
667
|
+
|
|
668
|
+
# Rule 6 pairing: the --font-serif token must resolve to a serif stack
|
|
669
|
+
# (last family `serif`) and --font-sans to a sans stack (last family
|
|
670
|
+
# `sans-serif`). This is the structural guarantee that body=serif,
|
|
671
|
+
# headings=sans hold once components reference the right token.
|
|
672
|
+
pairing_problems: list[str] = []
|
|
673
|
+
if serif_def is not None and (not serif_def or serif_def[-1] != "serif"):
|
|
674
|
+
pairing_problems.append(
|
|
675
|
+
"--font-serif must end in the generic `serif` family"
|
|
676
|
+
)
|
|
677
|
+
if sans_def is not None and (
|
|
678
|
+
not sans_def or sans_def[-1] != "sans-serif"
|
|
679
|
+
):
|
|
680
|
+
pairing_problems.append(
|
|
681
|
+
"--font-sans must end in the generic `sans-serif` family"
|
|
682
|
+
)
|
|
683
|
+
if serif_def is None and sans_def is None:
|
|
684
|
+
pairing_problems.append(
|
|
685
|
+
"neither --font-serif nor --font-sans token is defined; cannot "
|
|
686
|
+
"verify the serif-body / sans-heading pairing"
|
|
687
|
+
)
|
|
688
|
+
if pairing_problems:
|
|
689
|
+
r6.fail("; ".join(pairing_problems))
|
|
690
|
+
results.append(r6)
|
|
691
|
+
|
|
692
|
+
# --- Rule 8 (+ §12.5 nit 1): font-size must use --fs-* token ---------
|
|
693
|
+
# Every `font-size:` value must be either a bare `var(--fs-N)` ref OR a
|
|
694
|
+
# `calc(... var(--fs-N) ...)` AND in that calc case the rule's selector
|
|
695
|
+
# must carry a predefined `--variant` suffix (e.g. `.eqn--large`).
|
|
696
|
+
# Anything else (raw px, raw calc(N*var(--u)), bare numbers) is a HARD
|
|
697
|
+
# fail: it is an off-scale font-size drift.
|
|
698
|
+
r8 = RuleResult(8, "hard", "font-size via --fs-* token / variant calc")
|
|
699
|
+
fs_problems: list[str] = []
|
|
700
|
+
for selector, body in _iter_css_rules(css_nc):
|
|
701
|
+
sel_has_variant = bool(VARIANT_SUFFIX_RE.search(selector))
|
|
702
|
+
for fm in re.finditer(
|
|
703
|
+
r"font-size\s*:\s*([^;{}]+)", body, re.IGNORECASE
|
|
704
|
+
):
|
|
705
|
+
value = fm.group(1).strip()
|
|
706
|
+
if FS_VAR_REF_RE.fullmatch(value):
|
|
707
|
+
continue # bare var(--fs-N) — always allowed
|
|
708
|
+
# Relative `em` <= 1.0 is allowed: it can only SHRINK a parent
|
|
709
|
+
# whose size is already on the token scale (the standard
|
|
710
|
+
# typographic pattern for sup/sub), so it cannot introduce a
|
|
711
|
+
# new absolute size the way raw px / raw calc drift can.
|
|
712
|
+
m_em = re.fullmatch(r"(0?\.\d+|1(?:\.0+)?)\s*em", value)
|
|
713
|
+
if m_em and float(m_em.group(1)) <= 1.0:
|
|
714
|
+
continue
|
|
715
|
+
if CALC_FS_RE.search(value):
|
|
716
|
+
# calc with an --fs token: allowed ONLY on a variant rule.
|
|
717
|
+
if sel_has_variant:
|
|
718
|
+
continue
|
|
719
|
+
fs_problems.append(
|
|
720
|
+
f"calc() font-size with --fs token on non-variant "
|
|
721
|
+
f"selector '{selector.strip()[:40]}' "
|
|
722
|
+
f"(value '{value[:40]}'): calc(var(--fs-*) * k) is only "
|
|
723
|
+
"allowed on a COMPONENTS.md variant like .eqn--large"
|
|
724
|
+
)
|
|
725
|
+
continue
|
|
726
|
+
# No --fs token at all → off-scale drift.
|
|
727
|
+
fs_problems.append(
|
|
728
|
+
f"off-scale font-size on '{selector.strip()[:40]}': "
|
|
729
|
+
f"'{value[:40]}' (use var(--fs-N))"
|
|
730
|
+
)
|
|
731
|
+
if fs_problems:
|
|
732
|
+
r8.fail("; ".join(fs_problems[:4]) + (
|
|
733
|
+
f" (+{len(fs_problems) - 4} more)" if len(fs_problems) > 4 else ""
|
|
734
|
+
))
|
|
735
|
+
results.append(r8)
|
|
736
|
+
|
|
737
|
+
# --- Rule 9 (WARN): > 9 distinct --fs tokens defined -----------------
|
|
738
|
+
r9 = RuleResult(9, "warn", "<= 9 font-size tokens")
|
|
739
|
+
defined_fs = sorted(set(
|
|
740
|
+
int(m.group(1)) for m in FS_TOKEN_RE.finditer(
|
|
741
|
+
token_block_text if token_block_text is not None else css_nc
|
|
742
|
+
)
|
|
743
|
+
))
|
|
744
|
+
if len(defined_fs) > 9:
|
|
745
|
+
r9.warn(
|
|
746
|
+
f"{len(defined_fs)} --fs-* tokens defined "
|
|
747
|
+
f"({', '.join('--fs-%d' % n for n in defined_fs)}); the scale "
|
|
748
|
+
"should stay <= 9 steps to keep the typographic hierarchy tight."
|
|
749
|
+
)
|
|
750
|
+
else:
|
|
751
|
+
r9.detail = f"{len(defined_fs)} --fs-* token(s) defined"
|
|
752
|
+
results.append(r9)
|
|
753
|
+
|
|
754
|
+
# --- Rule 10: data-attribute contracts -------------------------------
|
|
755
|
+
# img[data-source="paper"] MUST also carry data-asset-id; logo
|
|
756
|
+
# exemptions MUST be explicitly marked data-color-exempt="logo".
|
|
757
|
+
r10 = RuleResult(10, "hard", "data-source/asset-id + logo-exempt marks")
|
|
758
|
+
contract_problems: list[str] = []
|
|
759
|
+
for el in parser.elements:
|
|
760
|
+
a = el["attrs"]
|
|
761
|
+
if (el["tag"] == "img"
|
|
762
|
+
and a.get("data-source", "").lower() == "paper"):
|
|
763
|
+
if not a.get("data-asset-id", "").strip():
|
|
764
|
+
src = a.get("src", "?")
|
|
765
|
+
contract_problems.append(
|
|
766
|
+
f"<img data-source=\"paper\" src=\"{src[:40]}\"> is "
|
|
767
|
+
"missing data-asset-id"
|
|
768
|
+
)
|
|
769
|
+
if contract_problems:
|
|
770
|
+
r10.fail("; ".join(contract_problems[:4]))
|
|
771
|
+
results.append(r10)
|
|
772
|
+
|
|
773
|
+
# --- Rule 11: no hand-rolled decorative SVG --------------------------
|
|
774
|
+
# inline <svg> is allowed ONLY when it is (a) inside a logo-exempt
|
|
775
|
+
# subtree, OR (b) itself marked as a catalogued structural diagram
|
|
776
|
+
# (data-component="diagram"), OR (c) a QR fallback (data-component=
|
|
777
|
+
# "qr"). Everything else is a forbidden decorative SVG.
|
|
778
|
+
r11 = RuleResult(11, "hard", "no decorative inline SVG")
|
|
779
|
+
svg_problems: list[str] = []
|
|
780
|
+
for el in parser.elements:
|
|
781
|
+
if el["tag"] != "svg":
|
|
782
|
+
continue
|
|
783
|
+
a = el["attrs"]
|
|
784
|
+
comp = a.get("data-component", "").lower()
|
|
785
|
+
if el["inside_logo"]:
|
|
786
|
+
continue
|
|
787
|
+
if comp in ("diagram", "qr"):
|
|
788
|
+
continue
|
|
789
|
+
svg_problems.append(
|
|
790
|
+
"inline <svg> that is not inside a data-color-exempt=\"logo\" "
|
|
791
|
+
"subtree and is not marked data-component=\"diagram\"/\"qr\""
|
|
792
|
+
)
|
|
793
|
+
if svg_problems:
|
|
794
|
+
r11.fail(
|
|
795
|
+
f"{len(svg_problems)} disallowed inline <svg>: "
|
|
796
|
+
+ svg_problems[0]
|
|
797
|
+
+ ". Decorative SVG is banned; only logos, QR fallbacks, and "
|
|
798
|
+
"COMPONENTS.md-catalogued structural diagrams are allowed."
|
|
799
|
+
)
|
|
800
|
+
results.append(r11)
|
|
801
|
+
|
|
802
|
+
return results, parser, token_block_text
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
# ---------------------------------------------------------------------------
|
|
806
|
+
# Hue-cluster helpers (rule 4) — pure functions, unit-testable.
|
|
807
|
+
# ---------------------------------------------------------------------------
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def _hue_of_rgb(r: int, g: int, b: int) -> tuple[float, float, float]:
|
|
811
|
+
"""Return (hue_deg, saturation, lightness) using HSL (colorsys.rgb_to_hls
|
|
812
|
+
returns H,L,S — note the order). Hue in [0,360), S/L in [0,1].
|
|
813
|
+
"""
|
|
814
|
+
h, l, s = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0)
|
|
815
|
+
return h * 360.0, s, l
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def _hue_dist(a: float, b: float) -> float:
|
|
819
|
+
"""Circular distance between two hue angles in degrees (0..180)."""
|
|
820
|
+
d = abs(a - b) % 360.0
|
|
821
|
+
return min(d, 360.0 - d)
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def cluster_hues(hues: list[float], radius_deg: float = 18.0) -> list[float]:
|
|
825
|
+
"""Greedy 1-D circular clustering of hue angles.
|
|
826
|
+
|
|
827
|
+
Sort the hues, sweep once, and start a new cluster whenever the next
|
|
828
|
+
hue is more than ``radius_deg`` (circular) from the current cluster's
|
|
829
|
+
*seed*. Returns the list of cluster center hues (circular mean of each
|
|
830
|
+
cluster's members). WHY greedy + seed-anchored: it is deterministic,
|
|
831
|
+
matches the spec's "cluster radius 18 degrees", and is cheap. Pure so
|
|
832
|
+
it can be unit-tested without a browser.
|
|
833
|
+
"""
|
|
834
|
+
if not hues:
|
|
835
|
+
return []
|
|
836
|
+
ordered = sorted(hues)
|
|
837
|
+
clusters: list[list[float]] = [[ordered[0]]]
|
|
838
|
+
for h in ordered[1:]:
|
|
839
|
+
# Anchor on the cluster seed (its first/min member) so a long run
|
|
840
|
+
# of slowly drifting hues doesn't merge into one giant cluster.
|
|
841
|
+
seed = clusters[-1][0]
|
|
842
|
+
if _hue_dist(h, seed) <= radius_deg:
|
|
843
|
+
clusters[-1].append(h)
|
|
844
|
+
else:
|
|
845
|
+
clusters.append([h])
|
|
846
|
+
# The circular wrap-around case: if the last cluster's members are
|
|
847
|
+
# within radius of the first cluster's seed, merge them (e.g. reds at
|
|
848
|
+
# 350 deg and 5 deg are one hue family).
|
|
849
|
+
if len(clusters) > 1:
|
|
850
|
+
if _hue_dist(clusters[-1][-1], clusters[0][0]) <= radius_deg:
|
|
851
|
+
clusters[0] = clusters[-1] + clusters[0]
|
|
852
|
+
clusters.pop()
|
|
853
|
+
return [_circular_mean(c) for c in clusters]
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
def _circular_mean(angles: list[float]) -> float:
|
|
857
|
+
"""Mean of angles on the unit circle, returned in [0,360)."""
|
|
858
|
+
x = sum(math.cos(math.radians(a)) for a in angles)
|
|
859
|
+
y = sum(math.sin(math.radians(a)) for a in angles)
|
|
860
|
+
return math.degrees(math.atan2(y, x)) % 360.0
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
# JS that collects computed colors for every element NOT inside an exempt
|
|
864
|
+
# subtree, plus its on-screen area + background lightness (for rule 12).
|
|
865
|
+
_RENDER_JS = r"""
|
|
866
|
+
() => {
|
|
867
|
+
// An element is exempt (rule 4) if it (or an ancestor) is an <img>, a
|
|
868
|
+
// [data-color-exempt] subtree, a [data-source="paper"] subtree, or a
|
|
869
|
+
// QR block ([data-component="qr"] / .qr-block). We walk up once per
|
|
870
|
+
// element; cheap enough for a single-page poster.
|
|
871
|
+
const isExempt = (el) => {
|
|
872
|
+
let n = el;
|
|
873
|
+
while (n && n.nodeType === 1) {
|
|
874
|
+
const tag = n.tagName ? n.tagName.toLowerCase() : '';
|
|
875
|
+
if (tag === 'img') return true;
|
|
876
|
+
if (n.hasAttribute && n.hasAttribute('data-color-exempt')) return true;
|
|
877
|
+
if (n.getAttribute && n.getAttribute('data-source') === 'paper')
|
|
878
|
+
return true;
|
|
879
|
+
const comp = n.getAttribute && n.getAttribute('data-component');
|
|
880
|
+
if (comp === 'qr') return true;
|
|
881
|
+
if (n.classList && n.classList.contains('qr-block')) return true;
|
|
882
|
+
n = n.parentElement;
|
|
883
|
+
}
|
|
884
|
+
return false;
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
const poster = document.querySelector('[data-measure-role="poster"]')
|
|
888
|
+
|| document.querySelector('.poster')
|
|
889
|
+
|| document.body;
|
|
890
|
+
const pr = poster.getBoundingClientRect();
|
|
891
|
+
const posterArea = Math.max(1, pr.width * pr.height);
|
|
892
|
+
|
|
893
|
+
const colors = []; // {prop, rgba} for rule 4
|
|
894
|
+
let darkArea = 0; // sum of on-screen area with bg L < 0.18 (rule 12)
|
|
895
|
+
|
|
896
|
+
const all = Array.from(document.querySelectorAll('*'));
|
|
897
|
+
for (const el of all) {
|
|
898
|
+
const cs = window.getComputedStyle(el);
|
|
899
|
+
const r = el.getBoundingClientRect();
|
|
900
|
+
const onscreen = r.width > 0 && r.height > 0;
|
|
901
|
+
|
|
902
|
+
// Rule 12: large dark background area. Measure regardless of color
|
|
903
|
+
// exemption (a dark slab is a dark slab even behind a figure), but
|
|
904
|
+
// only count visible boxes inside the poster bbox.
|
|
905
|
+
if (onscreen) {
|
|
906
|
+
const bg = cs.backgroundColor;
|
|
907
|
+
const L = bgLightness(bg);
|
|
908
|
+
if (L !== null && L < 0.18) {
|
|
909
|
+
// Clip to poster bounds so off-canvas overflow doesn't inflate.
|
|
910
|
+
const w = Math.max(0, Math.min(r.right, pr.right)
|
|
911
|
+
- Math.max(r.left, pr.left));
|
|
912
|
+
const h = Math.max(0, Math.min(r.bottom, pr.bottom)
|
|
913
|
+
- Math.max(r.top, pr.top));
|
|
914
|
+
darkArea += w * h;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (isExempt(el)) continue;
|
|
919
|
+
// Rule 4: collect every color-bearing computed property.
|
|
920
|
+
colors.push({prop: 'color', rgba: cs.color});
|
|
921
|
+
colors.push({prop: 'background-color', rgba: cs.backgroundColor});
|
|
922
|
+
colors.push({prop: 'border-top-color', rgba: cs.borderTopColor});
|
|
923
|
+
colors.push({prop: 'border-right-color', rgba: cs.borderRightColor});
|
|
924
|
+
colors.push({prop: 'border-bottom-color', rgba: cs.borderBottomColor});
|
|
925
|
+
colors.push({prop: 'border-left-color', rgba: cs.borderLeftColor});
|
|
926
|
+
colors.push({prop: 'fill', rgba: cs.fill});
|
|
927
|
+
colors.push({prop: 'stroke', rgba: cs.stroke});
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function bgLightness(s) {
|
|
931
|
+
const m = parseRGBA(s);
|
|
932
|
+
if (!m || m.a < 0.5) return null; // transparent-ish: not a dark slab
|
|
933
|
+
const r = m.r / 255, g = m.g / 255, b = m.b / 255;
|
|
934
|
+
return (Math.max(r, g, b) + Math.min(r, g, b)) / 2;
|
|
935
|
+
}
|
|
936
|
+
function parseRGBA(s) {
|
|
937
|
+
const mm = /rgba?\(([^)]+)\)/i.exec(s || '');
|
|
938
|
+
if (!mm) return null;
|
|
939
|
+
const p = mm[1].split(/[ ,\/]+/).filter(Boolean);
|
|
940
|
+
if (p.length < 3) return null;
|
|
941
|
+
return {
|
|
942
|
+
r: parseFloat(p[0]), g: parseFloat(p[1]), b: parseFloat(p[2]),
|
|
943
|
+
a: p.length >= 4 ? parseFloat(p[3]) : 1,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
return {colors, posterArea, darkArea};
|
|
948
|
+
}
|
|
949
|
+
"""
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def _parse_rgba_str(s: str) -> tuple[int, int, int, float] | None:
|
|
953
|
+
"""Parse a computed ``rgb()/rgba()`` string into (r,g,b,a). Returns
|
|
954
|
+
None for non-rgb forms (e.g. ``transparent`` resolves to
|
|
955
|
+
rgba(0,0,0,0), which DOES parse, so this only fails on unexpected
|
|
956
|
+
formats).
|
|
957
|
+
"""
|
|
958
|
+
m = re.search(
|
|
959
|
+
r"rgba?\(\s*([\d.]+)[ ,]+([\d.]+)[ ,]+([\d.]+)"
|
|
960
|
+
r"(?:[ ,/]+([\d.]+%?))?\s*\)",
|
|
961
|
+
s or "", re.IGNORECASE,
|
|
962
|
+
)
|
|
963
|
+
if not m:
|
|
964
|
+
return None
|
|
965
|
+
r, g, b = int(float(m.group(1))), int(float(m.group(2))), int(float(m.group(3)))
|
|
966
|
+
a_raw = m.group(4)
|
|
967
|
+
if a_raw is None:
|
|
968
|
+
a = 1.0
|
|
969
|
+
elif a_raw.endswith("%"):
|
|
970
|
+
a = float(a_raw[:-1]) / 100.0
|
|
971
|
+
else:
|
|
972
|
+
a = float(a_raw)
|
|
973
|
+
return r, g, b, a
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def run_render_gate(
|
|
977
|
+
html_path: Path,
|
|
978
|
+
hue_centers: dict[str, float],
|
|
979
|
+
*,
|
|
980
|
+
cluster_radius_deg: float = 18.0,
|
|
981
|
+
center_tol_deg: float = 22.0,
|
|
982
|
+
nonneutral_alpha: float = 0.10,
|
|
983
|
+
nonneutral_sat: float = 0.18,
|
|
984
|
+
dark_area_frac: float = 0.08,
|
|
985
|
+
mathjax_timeout_ms: int = 15000,
|
|
986
|
+
settle_ms: int = 500,
|
|
987
|
+
) -> tuple[list[RuleResult], int | None]:
|
|
988
|
+
"""Run rules 4 and 12 via Playwright print-emulation.
|
|
989
|
+
|
|
990
|
+
Returns ``(results, env_exit)``. ``env_exit`` is non-None (==2) only
|
|
991
|
+
when the environment is unusable (no playwright, no @page canvas, nav
|
|
992
|
+
failure) — the caller surfaces it as a usage/env error. Otherwise the
|
|
993
|
+
two rule results carry PASS/FAIL/WARN as normal.
|
|
994
|
+
|
|
995
|
+
WHY lazy import: playwright may still be installing; importing at
|
|
996
|
+
module top would break the source-gate-only ``--no-render`` path.
|
|
997
|
+
"""
|
|
998
|
+
try:
|
|
999
|
+
from playwright.sync_api import sync_playwright
|
|
1000
|
+
from playwright.sync_api import TimeoutError as PWTimeoutError
|
|
1001
|
+
except ImportError:
|
|
1002
|
+
_eprint(
|
|
1003
|
+
"ERROR: playwright is not available, so the render gate "
|
|
1004
|
+
"(rules 4 and 12) cannot run. Install it with:\n"
|
|
1005
|
+
" python -m pip install playwright\n"
|
|
1006
|
+
" python -m playwright install chromium\n"
|
|
1007
|
+
"or re-run with --no-render to skip rules 4 and 12 (the source "
|
|
1008
|
+
"gate still runs and can PASS on its own)."
|
|
1009
|
+
)
|
|
1010
|
+
return [], 2
|
|
1011
|
+
|
|
1012
|
+
# Reuse the vendored canvas+render helpers so the viewport basis and
|
|
1013
|
+
# MathJax settling exactly match measure/polish.
|
|
1014
|
+
from _posterly import canvas as _canvas
|
|
1015
|
+
from _posterly import render as _render
|
|
1016
|
+
|
|
1017
|
+
resolved = _canvas.resolve_canvas(html_path, None, label="[style]")
|
|
1018
|
+
if resolved is None:
|
|
1019
|
+
_eprint(
|
|
1020
|
+
"ERROR: could not find `@page { size: <W> <H> }` in HTML for "
|
|
1021
|
+
"the render gate. Add an @page rule or re-run with --no-render."
|
|
1022
|
+
)
|
|
1023
|
+
return [], 2
|
|
1024
|
+
_canvas_in, viewport = resolved
|
|
1025
|
+
|
|
1026
|
+
with sync_playwright() as p:
|
|
1027
|
+
browser, _ctx, page = _render.open_print_emulated_page(p, viewport)
|
|
1028
|
+
nav_timed_out = False
|
|
1029
|
+
try:
|
|
1030
|
+
page.goto(html_path.as_uri(), wait_until="networkidle",
|
|
1031
|
+
timeout=mathjax_timeout_ms)
|
|
1032
|
+
except PWTimeoutError:
|
|
1033
|
+
nav_timed_out = True
|
|
1034
|
+
|
|
1035
|
+
settle = _render.settle_page(
|
|
1036
|
+
page, mathjax_timeout_ms=mathjax_timeout_ms, settle_ms=settle_ms,
|
|
1037
|
+
)
|
|
1038
|
+
fail = _render.hard_fail_on_settle_problems(
|
|
1039
|
+
settle, mathjax_timeout_ms=mathjax_timeout_ms,
|
|
1040
|
+
)
|
|
1041
|
+
if fail is not None:
|
|
1042
|
+
browser.close()
|
|
1043
|
+
_eprint(f"ERROR (render gate): {fail}")
|
|
1044
|
+
return [], 2
|
|
1045
|
+
if nav_timed_out:
|
|
1046
|
+
browser.close()
|
|
1047
|
+
_eprint(
|
|
1048
|
+
"ERROR (render gate): page did not reach network-idle "
|
|
1049
|
+
f"within {mathjax_timeout_ms} ms; a blocked remote resource "
|
|
1050
|
+
"is the usual cause. Inline assets or re-run --no-render."
|
|
1051
|
+
)
|
|
1052
|
+
return [], 2
|
|
1053
|
+
|
|
1054
|
+
data = page.evaluate(_RENDER_JS)
|
|
1055
|
+
browser.close()
|
|
1056
|
+
|
|
1057
|
+
results: list[RuleResult] = []
|
|
1058
|
+
|
|
1059
|
+
# --- Rule 4: non-neutral hue clustering ------------------------------
|
|
1060
|
+
r4 = RuleResult(4, "hard", "<=2 non-neutral hue clusters on palette")
|
|
1061
|
+
hues: list[float] = []
|
|
1062
|
+
for c in data["colors"]:
|
|
1063
|
+
parsed = _parse_rgba_str(c["rgba"])
|
|
1064
|
+
if parsed is None:
|
|
1065
|
+
continue
|
|
1066
|
+
r, g, b, a = parsed
|
|
1067
|
+
if a < nonneutral_alpha:
|
|
1068
|
+
continue
|
|
1069
|
+
hue, sat, _l = _hue_of_rgb(r, g, b)
|
|
1070
|
+
# Perceptual chroma, not raw saturation: HSL saturation blows up
|
|
1071
|
+
# near white/black (a warm paper tone like #F6F2F0 reads S=0.25 at
|
|
1072
|
+
# L=0.95 yet is visually neutral). Chroma = S * (1 - |2L - 1|)
|
|
1073
|
+
# discounts by lightness extremity, so near-white tints and
|
|
1074
|
+
# near-black shadows stay neutral while real accents still count.
|
|
1075
|
+
chroma = sat * (1.0 - abs(2.0 * _l - 1.0))
|
|
1076
|
+
if chroma < nonneutral_sat * 0.56: # 0.18 * 0.56 ~= 0.10 chroma floor
|
|
1077
|
+
continue # neutral (gray / near-white tint) — not a hue family
|
|
1078
|
+
hues.append(hue)
|
|
1079
|
+
|
|
1080
|
+
centers = cluster_hues(hues, radius_deg=cluster_radius_deg)
|
|
1081
|
+
target_hues = [h for h in (
|
|
1082
|
+
hue_centers.get("accent"), hue_centers.get("gold")
|
|
1083
|
+
) if h is not None]
|
|
1084
|
+
problems: list[str] = []
|
|
1085
|
+
if len(centers) > 2:
|
|
1086
|
+
problems.append(
|
|
1087
|
+
f"{len(centers)} non-neutral hue clusters "
|
|
1088
|
+
f"({', '.join('%.0f deg' % c for c in centers)}); at most 2 "
|
|
1089
|
+
"(accent + gold) are allowed."
|
|
1090
|
+
)
|
|
1091
|
+
if target_hues:
|
|
1092
|
+
for c in centers:
|
|
1093
|
+
nearest = min(_hue_dist(c, t) for t in target_hues)
|
|
1094
|
+
if nearest > center_tol_deg:
|
|
1095
|
+
problems.append(
|
|
1096
|
+
f"hue cluster at {c:.0f} deg is {nearest:.0f} deg from "
|
|
1097
|
+
f"the nearest palette center "
|
|
1098
|
+
f"({'/'.join('%.0f' % t for t in target_hues)} deg); "
|
|
1099
|
+
f"tolerance is {center_tol_deg:.0f} deg"
|
|
1100
|
+
)
|
|
1101
|
+
else:
|
|
1102
|
+
problems.append(
|
|
1103
|
+
"no accent/gold hue centers available (pass --tokens or define "
|
|
1104
|
+
"--accent/--gold in :root) — cannot verify cluster proximity"
|
|
1105
|
+
)
|
|
1106
|
+
if problems:
|
|
1107
|
+
r4.fail("; ".join(problems[:4]))
|
|
1108
|
+
else:
|
|
1109
|
+
r4.detail = (
|
|
1110
|
+
f"{len(centers)} hue cluster(s), all within {center_tol_deg:.0f}"
|
|
1111
|
+
f" deg of the palette"
|
|
1112
|
+
)
|
|
1113
|
+
results.append(r4)
|
|
1114
|
+
|
|
1115
|
+
# --- Rule 12 (WARN): large dark area ---------------------------------
|
|
1116
|
+
r12 = RuleResult(12, "warn", "large dark area (kitsch warning)")
|
|
1117
|
+
frac = data["darkArea"] / data["posterArea"] if data["posterArea"] else 0.0
|
|
1118
|
+
if frac > dark_area_frac:
|
|
1119
|
+
r12.warn(
|
|
1120
|
+
f"dark (L<0.18) backgrounds cover {frac * 100:.1f}% of the "
|
|
1121
|
+
f"poster (> {dark_area_frac * 100:.0f}% threshold); large dark "
|
|
1122
|
+
"slabs read as 'kitsch'. Lighten or shrink them."
|
|
1123
|
+
)
|
|
1124
|
+
else:
|
|
1125
|
+
r12.detail = f"dark area = {frac * 100:.1f}% of poster (<= 8%)"
|
|
1126
|
+
results.append(r12)
|
|
1127
|
+
|
|
1128
|
+
return results, None
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
# ---------------------------------------------------------------------------
|
|
1132
|
+
# Hue-center resolution (--tokens JSON, else :root --accent/--gold).
|
|
1133
|
+
# ---------------------------------------------------------------------------
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
def resolve_hue_centers(
|
|
1137
|
+
tokens_path: Path | None, token_block_text: str | None
|
|
1138
|
+
) -> dict[str, float]:
|
|
1139
|
+
"""Resolve accent/gold hue centers for rule 4.
|
|
1140
|
+
|
|
1141
|
+
Priority: ``--tokens`` JSON ``hue_centers`` block (authoritative,
|
|
1142
|
+
IMPLEMENTATION_CONVENTIONS §F), then derive from the JSON's accent/gold
|
|
1143
|
+
base hexes, then fall back to parsing ``--accent`` / ``--gold`` literals
|
|
1144
|
+
out of the :root token block.
|
|
1145
|
+
"""
|
|
1146
|
+
centers: dict[str, float] = {}
|
|
1147
|
+
if tokens_path is not None:
|
|
1148
|
+
try:
|
|
1149
|
+
doc = json.loads(tokens_path.read_text(encoding="utf-8"))
|
|
1150
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
1151
|
+
_eprint(f"WARNING: could not read --tokens {ascii_safe(tokens_path)}"
|
|
1152
|
+
f": {ascii_safe(e)}; falling back to :root.")
|
|
1153
|
+
doc = {}
|
|
1154
|
+
hc = doc.get("hue_centers") or {}
|
|
1155
|
+
for key in ("accent", "gold"):
|
|
1156
|
+
if key in hc:
|
|
1157
|
+
try:
|
|
1158
|
+
centers[key] = float(hc[key]) % 360.0
|
|
1159
|
+
except (TypeError, ValueError):
|
|
1160
|
+
pass
|
|
1161
|
+
# Derive from base hexes if hue_centers omitted a key.
|
|
1162
|
+
for key, sub in (("accent", "accent"), ("gold", "gold")):
|
|
1163
|
+
if key in centers:
|
|
1164
|
+
continue
|
|
1165
|
+
base = (doc.get(sub) or {}).get("base")
|
|
1166
|
+
h = _hue_from_hex(base) if base else None
|
|
1167
|
+
if h is not None:
|
|
1168
|
+
centers[key] = h
|
|
1169
|
+
# Fallback: parse --accent / --gold from the :root token block.
|
|
1170
|
+
if token_block_text:
|
|
1171
|
+
for key, var in (("accent", "--accent"), ("gold", "--gold")):
|
|
1172
|
+
if key in centers:
|
|
1173
|
+
continue
|
|
1174
|
+
m = re.search(
|
|
1175
|
+
re.escape(var) + r"\s*:\s*(#[0-9a-fA-F]{3,8})",
|
|
1176
|
+
token_block_text,
|
|
1177
|
+
)
|
|
1178
|
+
if m:
|
|
1179
|
+
h = _hue_from_hex(m.group(1))
|
|
1180
|
+
if h is not None:
|
|
1181
|
+
centers[key] = h
|
|
1182
|
+
return centers
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
def _hue_from_hex(hex_str: str | None) -> float | None:
|
|
1186
|
+
"""Hue (degrees) of a #RGB / #RRGGBB literal, or None if unparsable."""
|
|
1187
|
+
if not hex_str or not hex_str.startswith("#"):
|
|
1188
|
+
return None
|
|
1189
|
+
h = hex_str[1:]
|
|
1190
|
+
if len(h) == 3:
|
|
1191
|
+
h = "".join(c * 2 for c in h)
|
|
1192
|
+
if len(h) not in (6, 8):
|
|
1193
|
+
return None
|
|
1194
|
+
try:
|
|
1195
|
+
r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
|
|
1196
|
+
except ValueError:
|
|
1197
|
+
return None
|
|
1198
|
+
hue, _s, _l = _hue_of_rgb(r, g, b)
|
|
1199
|
+
return hue
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
# ---------------------------------------------------------------------------
|
|
1203
|
+
# Orchestration + CLI.
|
|
1204
|
+
# ---------------------------------------------------------------------------
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
def _overall_status(results: list[RuleResult]) -> str:
|
|
1208
|
+
"""PASS unless a HARD rule FAILED; WARN if no hard fail but a warning
|
|
1209
|
+
fired; else PASS. (SKIPPED rules never change the verdict.)
|
|
1210
|
+
"""
|
|
1211
|
+
if any(r.severity == "hard" and r.status == "FAIL" for r in results):
|
|
1212
|
+
return "FAIL"
|
|
1213
|
+
if any(r.status == "WARN" for r in results):
|
|
1214
|
+
return "WARN"
|
|
1215
|
+
return "PASS"
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
def cmd_style_check(args: argparse.Namespace) -> int:
|
|
1219
|
+
html_path = Path(args.html).resolve()
|
|
1220
|
+
if not html_path.exists():
|
|
1221
|
+
_eprint(f"ERROR: HTML not found: {ascii_safe(html_path)}")
|
|
1222
|
+
return 2
|
|
1223
|
+
try:
|
|
1224
|
+
html_text = html_path.read_text(encoding="utf-8", errors="ignore")
|
|
1225
|
+
except OSError as e:
|
|
1226
|
+
_eprint(f"ERROR: cannot read HTML: {ascii_safe(e)}")
|
|
1227
|
+
return 2
|
|
1228
|
+
|
|
1229
|
+
tokens_path = Path(args.tokens).resolve() if args.tokens else None
|
|
1230
|
+
if tokens_path is not None and not tokens_path.exists():
|
|
1231
|
+
_eprint(f"ERROR: --tokens not found: {ascii_safe(tokens_path)}")
|
|
1232
|
+
return 2
|
|
1233
|
+
|
|
1234
|
+
# --- Source gate (always runs) --------------------------------------
|
|
1235
|
+
source_results, _parser, token_block_text = run_source_gate(
|
|
1236
|
+
html_text, html_path
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
# --- Render gate (rules 4, 12) --------------------------------------
|
|
1240
|
+
render_results: list[RuleResult]
|
|
1241
|
+
if args.no_render:
|
|
1242
|
+
render_results = [
|
|
1243
|
+
RuleResult(4, "hard", "<=2 non-neutral hue clusters").skip(
|
|
1244
|
+
"render gate skipped (--no-render)"
|
|
1245
|
+
),
|
|
1246
|
+
RuleResult(12, "warn", "large dark area").skip(
|
|
1247
|
+
"render gate skipped (--no-render)"
|
|
1248
|
+
),
|
|
1249
|
+
]
|
|
1250
|
+
print("[style] NOTICE: --no-render set; rules 4 and 12 are SKIPPED. "
|
|
1251
|
+
"Overall status reflects the source gate only.")
|
|
1252
|
+
else:
|
|
1253
|
+
hue_centers = resolve_hue_centers(tokens_path, token_block_text)
|
|
1254
|
+
render_results, env_exit = run_render_gate(html_path, hue_centers)
|
|
1255
|
+
if env_exit is not None:
|
|
1256
|
+
# Environment unusable for the render gate. Per the CLI
|
|
1257
|
+
# contract an env error is exit 2 — don't masquerade as a PASS.
|
|
1258
|
+
return env_exit
|
|
1259
|
+
|
|
1260
|
+
# Merge + order by rule id for stable output.
|
|
1261
|
+
all_results = sorted(
|
|
1262
|
+
source_results + render_results, key=lambda r: r.id
|
|
1263
|
+
)
|
|
1264
|
+
status = _overall_status(all_results)
|
|
1265
|
+
|
|
1266
|
+
report = {
|
|
1267
|
+
"gate": "style",
|
|
1268
|
+
"status": status,
|
|
1269
|
+
"rules": [r.to_dict() for r in all_results],
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
if args.json:
|
|
1273
|
+
Path(args.json).write_text(
|
|
1274
|
+
json.dumps(report, indent=2), encoding="utf-8"
|
|
1275
|
+
)
|
|
1276
|
+
print(f"[style] report -> {ascii_safe(args.json)}")
|
|
1277
|
+
|
|
1278
|
+
# Human-readable summary to stdout.
|
|
1279
|
+
print(f"[style] overall = {status}")
|
|
1280
|
+
for r in all_results:
|
|
1281
|
+
print(f" rule {r.id:>2} [{r.severity:>4}] {r.status:<7} "
|
|
1282
|
+
f"{ascii_safe(r.detail)}")
|
|
1283
|
+
|
|
1284
|
+
return 1 if status == "FAIL" else 0
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
1288
|
+
p = argparse.ArgumentParser(
|
|
1289
|
+
prog="style_check",
|
|
1290
|
+
description="Style HARD gate for HTML academic posters: 12 rules "
|
|
1291
|
+
"(DESIGN_FINAL.md §3 + §12.5 nit 1). Source gate "
|
|
1292
|
+
"(rules 1-3,5-11) is pure static analysis; render gate "
|
|
1293
|
+
"(rules 4,12) print-emulates via Playwright.",
|
|
1294
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1295
|
+
epilog="exit codes: 0=PASS (no hard fail), 1=hard fail, "
|
|
1296
|
+
"2=usage/environment error.",
|
|
1297
|
+
)
|
|
1298
|
+
p.add_argument("html", help="path to poster.html")
|
|
1299
|
+
p.add_argument(
|
|
1300
|
+
"--tokens", default=None,
|
|
1301
|
+
help="tokens JSON (IMPLEMENTATION_CONVENTIONS §F); its "
|
|
1302
|
+
"hue_centers/accent/gold drive rule 4. Default: derive accent/"
|
|
1303
|
+
"gold hue from the :root token block.",
|
|
1304
|
+
)
|
|
1305
|
+
p.add_argument(
|
|
1306
|
+
"--json", default=None,
|
|
1307
|
+
help="write the JSON report ({gate,status,rules}) to this path",
|
|
1308
|
+
)
|
|
1309
|
+
p.add_argument(
|
|
1310
|
+
"--no-render", action="store_true",
|
|
1311
|
+
help="skip the render gate; rules 4 and 12 are marked SKIPPED. The "
|
|
1312
|
+
"source gate still runs and can PASS on its own (a notice is "
|
|
1313
|
+
"printed).",
|
|
1314
|
+
)
|
|
1315
|
+
return p
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
def main(argv: list[str] | None = None) -> int:
|
|
1319
|
+
args = build_parser().parse_args(argv)
|
|
1320
|
+
return cmd_style_check(args)
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
if __name__ == "__main__":
|
|
1324
|
+
sys.exit(main())
|