name: CI on: push: branches: [ main ] pull_request: branches: [ main ] permissions: contents: read env: PYTHONIOENCODING: utf-8 PYTHONUTF8: "1" jobs: plugin-package: name: Plugin Package & Version Gate runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout repository history uses: actions/checkout@v7 env: GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: "core.autocrlf" GIT_CONFIG_VALUE_0: "false" with: fetch-depth: 0 persist-credentials: false - name: Set up Python uses: actions/setup-python@v7 with: python-version: "3.12" - name: Set up Node.js uses: actions/setup-node@v7 with: node-version: "22" - name: Test plugin package tooling run: python3 scripts/test-plugin-package.py - name: Verify maintainer policy tracks package and CI truth run: python3 scripts/test-maintainer-policy.py # Versions are bumped on main by the Auto Version Bump workflow after # each merge (ADR 0009); pull requests must leave them untouched. # A pull_request checkout is GitHub's merge of the PR into the current # base tip, so the merge's first parent is the base to compare against. # github.event.pull_request.base.sha can predate later auto-bumps and # would report their versions as edits made by the PR. - name: Forbid manifest version changes in pull requests shell: bash run: | if [ "${{ github.event_name }}" = "pull_request" ]; then if ! git rev-parse -q --verify HEAD^2 >/dev/null; then echo "expected the pull request merge commit at HEAD" >&2 exit 1 fi python3 scripts/verify-plugin-package.py --require-no-bump HEAD^1 else python3 scripts/verify-plugin-package.py --current-only fi - name: Validate Claude marketplace package without warnings run: npx --yes @anthropic-ai/claude-code@2.1.229 plugin validate . --strict python39-compat: name: Python 3.9 Compatibility runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v7 with: persist-credentials: false - name: Set up Python 3.9 uses: actions/setup-python@v7 with: python-version: "3.9" - name: Verify linter compatibility run: | python scripts/test-lint-a11y.py python scripts/lint-skin.py --all --baseline validate: name: Lint & Verify (${{ matrix.os }}, Python ${{ matrix.python-version }}) strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.11", "3.12"] runs-on: ${{ matrix.os }} timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v7 env: GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: "core.autocrlf" GIT_CONFIG_VALUE_0: "false" with: persist-credentials: false - name: Set up Python uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Verify accessible SVG contract if: always() id: a11y run: python scripts/test-lint-a11y.py - name: Verify semantic pattern documentation if: always() id: semantic_docs run: python scripts/verify-semantic-motion.py --markdown-only - name: Verify animated example structure if: always() id: animated_example run: python scripts/verify-semantic-motion.py --example-only - name: Lint animated example skin if: always() id: animated_lint run: python scripts/lint-skin.py skills/diagram-design/assets/example-policy-trace-animated.html - name: Verify every shipped motion file if: always() id: shipped_motion run: python scripts/verify-motion.py --shipped - name: Verify URL SVG normalization if: always() id: url_icons run: python scripts/test-build-icons-quoted-attributes.py - name: Verify Devicon normalization if: always() id: devicon run: python scripts/test-build-icons-devicon.py - name: Verify mojibake repair utility if: always() id: mojibake run: python scripts/test-fix-mojibake.py - name: Run skin linter if: always() id: skin run: python scripts/lint-skin.py --all --baseline - name: Verify quantitative polar chart if: always() id: polar shell: bash run: | python scripts/test-verify-polar.py python scripts/verify-polar.py - name: Verify heatmap monotone ramp and grid if: always() id: heatmap shell: bash run: | python scripts/test-verify-heatmap.py python scripts/verify-heatmap.py --all - name: Verify sequence OAuth specs if: always() id: oauth run: python scripts/verify-sequence-oauth.py - name: Verify semantic-motion verifier if: always() id: semantic_motion_tests run: python scripts/test-verify-semantic-motion.py - name: Verify sequence-oauth verifier if: always() id: oauth_tests run: python scripts/test-verify-sequence-oauth.py - name: Verify draw.io import script if: always() id: drawio run: | python scripts/verify-drawio-import.py python scripts/test-verify-drawio-import.py - name: Verify Mermaid import script if: always() id: mermaid run: python scripts/verify-mermaid-import.py - name: Verify Excalidraw import script if: always() id: excalidraw run: | python scripts/verify-excalidraw-import.py python scripts/test-verify-excalidraw-import.py - name: Verify optional motion contract if: always() id: motion run: python scripts/test-verify-motion.py - name: Verify doctor diagnostics contract if: always() id: doctor run: python scripts/verify-doctor.py - name: Verify doctor diagnostics tests if: always() id: doctor_tests run: python scripts/test-verify-doctor.py - name: Verify docs and routing sync if: always() id: docs_sync shell: bash run: | python scripts/verify-docs-sync.py python scripts/test-verify-docs-sync.py - name: Verify canonical screenshot freshness if: always() id: screenshots run: python scripts/verify-screenshot-freshness.py - name: Verify screenshot freshness checker if: always() id: screenshots_tests run: python scripts/test-verify-screenshot-freshness.py - name: Install Pillow for README thumbnail verification if: always() && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' env: PILLOW_VERSION: "12.1.1" shell: bash run: | pip install "Pillow==${PILLOW_VERSION}" python -c "import PIL; print('Pillow', PIL.__version__)" - name: Verify README thumbnails if: always() && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' id: readme_thumbs run: | python scripts/test-build-readme-thumbs.py python scripts/build-readme-thumbs.py --check - name: Verify packaged self-check if: always() id: self_check run: python scripts/test-self-check.py - name: Verify standalone SVG export (CSS carry + defs IDs) if: always() id: export_svg run: python scripts/test-export-svg-standalone.py - name: Verify label geometry if: always() id: geometry run: python scripts/verify-geometry.py --all - name: Verify label geometry checker if: always() id: geometry_tests run: python scripts/test-verify-geometry.py - name: Verify block registry if: always() id: block_registry run: python scripts/verify-block-registry.py --all - name: Verify block registry checker if: always() id: block_registry_tests run: python scripts/test-verify-block-registry.py - name: Verify treemap area fidelity and label fit if: always() id: treemap run: python scripts/verify-treemap.py --all - name: Verify treemap checker if: always() id: treemap_tests run: python scripts/test-verify-treemap.py - name: Verify marimekko width, height and area shares if: always() id: marimekko run: python scripts/verify-marimekko.py --all - name: Verify marimekko checker if: always() id: marimekko_tests run: python scripts/test-verify-marimekko.py - name: Verify dumbbell domain and contrast rules if: always() id: dumbbell run: python scripts/verify-dumbbell.py - name: Verify dumbbell checker if: always() id: dumbbell_tests run: python scripts/test-verify-dumbbell.py - name: Verify slopegraph shared scale and endpoint values if: always() id: slopegraph run: python scripts/verify-slopegraph.py --all - name: Verify slopegraph checker if: always() id: slopegraph_tests run: python scripts/test-verify-slopegraph.py - name: Verify streamgraph stack and baseline if: always() id: streamgraph run: python scripts/verify-streamgraph.py --all - name: Verify streamgraph checker if: always() id: streamgraph_tests run: python scripts/test-verify-streamgraph.py - name: Verify sankey flow conservation if: always() id: sankey run: python scripts/verify-sankey.py --all - name: Verify sankey checker if: always() id: sankey_tests run: python scripts/test-verify-sankey.py - name: Verify waterfall running-total conservation if: always() id: waterfall run: python scripts/verify-waterfall.py --all - name: Verify waterfall checker if: always() id: waterfall_tests run: python scripts/test-verify-waterfall.py - name: Verify ridgeline shared amplitude if: always() id: ridgeline run: python scripts/verify-ridgeline.py --all - name: Verify ridgeline checker if: always() id: ridgeline_tests run: python scripts/test-verify-ridgeline.py - name: Verify bubble positions and area encoding if: always() id: bubble run: python scripts/verify-bubble.py --all - name: Verify bubble checker if: always() id: bubble_tests run: python scripts/test-verify-bubble.py - name: Verify bump chart rank grid if: always() id: bump run: python scripts/verify-bump.py --all - name: Verify bump checker if: always() id: bump_tests run: python scripts/test-verify-bump.py - name: Verify beeswarm positions and packing if: always() id: beeswarm run: python scripts/verify-beeswarm.py --all - name: Verify beeswarm checker if: always() id: beeswarm_tests run: python scripts/test-verify-beeswarm.py - name: Verify tone claims against the ramps they describe if: always() id: skin_polarity run: python scripts/verify-skin-polarity.py --all - name: Verify skin-polarity checker if: always() id: skin_polarity_tests run: python scripts/test-verify-skin-polarity.py # lint-render.py's oracle is pixels, so the browser is part of the contract: # pin Playwright exactly, and let that pin choose the Chromium build. Bump # both together, deliberately, by editing PLAYWRIGHT_VERSION. # # One platform only. Antialiasing and font fallback differ per OS, so running # a pixel comparison across the whole matrix would compare six oracles rather # than one, and the thresholds are calibrated on Linux Chromium. The other # matrix legs still run every source-level gate. - name: Install Playwright Chromium if: always() && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' env: PLAYWRIGHT_VERSION: "1.62.0" shell: bash run: | pip install "playwright==${PLAYWRIGHT_VERSION}" playwright install --with-deps chromium python -c "import importlib.metadata as m; print('playwright', m.version('playwright'))" # The checks before the sweep: a broken linter must not be able to report # a clean repository. - name: Verify render linter checks if: always() && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' id: render_selftest run: python scripts/lint-render.py --self-test - name: Run render linter if: always() && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' id: render run: python scripts/lint-render.py --all - name: Verify generated icon assets if: always() id: icons shell: bash run: | python scripts/build-icons.py git diff --ignore-space-at-eol --exit-code -- \ skills/diagram-design/assets/icons.html \ skills/diagram-design/references/primitive-icons.md - name: Generate CI execution summary table if: always() shell: bash run: | echo "### 📊 CI Verification Summary (${{ matrix.os }}, Python ${{ matrix.python-version }})" >> $GITHUB_STEP_SUMMARY echo "| Verification Gate | Outcome |" >> $GITHUB_STEP_SUMMARY echo "|---|---|" >> $GITHUB_STEP_SUMMARY echo "| SVG Accessibility Contract | ${{ steps.a11y.outcome == 'success' && '✅ Passed' || (steps.a11y.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Semantic Pattern Documentation | ${{ steps.semantic_docs.outcome == 'success' && '✅ Passed' || (steps.semantic_docs.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Animated Example Structure | ${{ steps.animated_example.outcome == 'success' && '✅ Passed' || (steps.animated_example.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Animated Example Skin | ${{ steps.animated_lint.outcome == 'success' && '✅ Passed' || (steps.animated_lint.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Shipped Motion HTML | ${{ steps.shipped_motion.outcome == 'success' && '✅ Passed' || (steps.shipped_motion.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| URL SVG Normalization | ${{ steps.url_icons.outcome == 'success' && '✅ Passed' || (steps.url_icons.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Devicon Normalization | ${{ steps.devicon.outcome == 'success' && '✅ Passed' || (steps.devicon.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Mojibake Repair Utility | ${{ steps.mojibake.outcome == 'success' && '✅ Passed' || (steps.mojibake.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Color Palette & Skin Linter | ${{ steps.skin.outcome == 'success' && '✅ Passed' || (steps.skin.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Quantitative Polar Chart | ${{ steps.polar.outcome == 'success' && '✅ Passed' || (steps.polar.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Heatmap Monotone Ramp & Grid | ${{ steps.heatmap.outcome == 'success' && '✅ Passed' || (steps.heatmap.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Heatmap Checker | ${{ steps.heatmap.outcome == 'success' && '✅ Passed' || (steps.heatmap.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Sequence Diagram Grammar | ${{ steps.oauth.outcome == 'success' && '✅ Passed' || (steps.oauth.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Semantic-Motion Verifier Tests | ${{ steps.semantic_motion_tests.outcome == 'success' && '✅ Passed' || (steps.semantic_motion_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Sequence-OAuth Verifier Tests | ${{ steps.oauth_tests.outcome == 'success' && '✅ Passed' || (steps.oauth_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Draw.io Import Extractor | ${{ steps.drawio.outcome == 'success' && '✅ Passed' || (steps.drawio.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Mermaid Import Extractor | ${{ steps.mermaid.outcome == 'success' && '✅ Passed' || (steps.mermaid.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Excalidraw Import Extractor | ${{ steps.excalidraw.outcome == 'success' && '✅ Passed' || (steps.excalidraw.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Optional Motion Contract | ${{ steps.motion.outcome == 'success' && '✅ Passed' || (steps.motion.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Doctor Diagnostics Contract | ${{ steps.doctor.outcome == 'success' && '✅ Passed' || (steps.doctor.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Doctor Diagnostics Tests | ${{ steps.doctor_tests.outcome == 'success' && '✅ Passed' || (steps.doctor_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Docs & Routing Sync | ${{ steps.docs_sync.outcome == 'success' && '✅ Passed' || (steps.docs_sync.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Canonical Screenshot Freshness | ${{ steps.screenshots.outcome == 'success' && '✅ Passed' || (steps.screenshots.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Screenshot Freshness Checker | ${{ steps.screenshots_tests.outcome == 'success' && '✅ Passed' || (steps.screenshots_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| README Thumbnail Freshness | ${{ steps.readme_thumbs.outcome == 'success' && '✅ Passed' || (steps.readme_thumbs.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Packaged Self-Check | ${{ steps.self_check.outcome == 'success' && '✅ Passed' || (steps.self_check.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Standalone SVG Export | ${{ steps.export_svg.outcome == 'success' && '✅ Passed' || (steps.export_svg.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Label Geometry | ${{ steps.geometry.outcome == 'success' && '✅ Passed' || (steps.geometry.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Label Geometry Checker | ${{ steps.geometry_tests.outcome == 'success' && '✅ Passed' || (steps.geometry_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Block Registry | ${{ steps.block_registry.outcome == 'success' && '✅ Passed' || (steps.block_registry.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Block Registry Checker | ${{ steps.block_registry_tests.outcome == 'success' && '✅ Passed' || (steps.block_registry_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Treemap Area & Label Fit | ${{ steps.treemap.outcome == 'success' && '✅ Passed' || (steps.treemap.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Treemap Checker | ${{ steps.treemap_tests.outcome == 'success' && '✅ Passed' || (steps.treemap_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Marimekko Width, Height & Area | ${{ steps.marimekko.outcome == 'success' && '✅ Passed' || (steps.marimekko.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Marimekko Checker | ${{ steps.marimekko_tests.outcome == 'success' && '✅ Passed' || (steps.marimekko_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Dumbbell Domain & Contrast | ${{ steps.dumbbell.outcome == 'success' && '✅ Passed' || (steps.dumbbell.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Dumbbell Checker | ${{ steps.dumbbell_tests.outcome == 'success' && '✅ Passed' || (steps.dumbbell_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Slopegraph Shared Scale | ${{ steps.slopegraph.outcome == 'success' && '✅ Passed' || (steps.slopegraph.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Slopegraph Checker | ${{ steps.slopegraph_tests.outcome == 'success' && '✅ Passed' || (steps.slopegraph_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Streamgraph Stack & Baseline | ${{ steps.streamgraph.outcome == 'success' && '✅ Passed' || (steps.streamgraph.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Streamgraph Checker | ${{ steps.streamgraph_tests.outcome == 'success' && '✅ Passed' || (steps.streamgraph_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Sankey Flow Conservation | ${{ steps.sankey.outcome == 'success' && '✅ Passed' || (steps.sankey.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Sankey Checker | ${{ steps.sankey_tests.outcome == 'success' && '✅ Passed' || (steps.sankey_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Waterfall Running-Total Conservation | ${{ steps.waterfall.outcome == 'success' && '✅ Passed' || (steps.waterfall.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Waterfall Checker | ${{ steps.waterfall_tests.outcome == 'success' && '✅ Passed' || (steps.waterfall_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Ridgeline Shared Amplitude | ${{ steps.ridgeline.outcome == 'success' && '✅ Passed' || (steps.ridgeline.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Ridgeline Checker | ${{ steps.ridgeline_tests.outcome == 'success' && '✅ Passed' || (steps.ridgeline_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Bubble Position & Area | ${{ steps.bubble.outcome == 'success' && '✅ Passed' || (steps.bubble.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Bubble Checker | ${{ steps.bubble_tests.outcome == 'success' && '✅ Passed' || (steps.bubble_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Bump Chart Rank Grid | ${{ steps.bump.outcome == 'success' && '✅ Passed' || (steps.bump.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Bump Checker | ${{ steps.bump_tests.outcome == 'success' && '✅ Passed' || (steps.bump_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Beeswarm Positions & Packing | ${{ steps.beeswarm.outcome == 'success' && '✅ Passed' || (steps.beeswarm.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Beeswarm Checker | ${{ steps.beeswarm_tests.outcome == 'success' && '✅ Passed' || (steps.beeswarm_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Tone Claim vs Ramp Polarity | ${{ steps.skin_polarity.outcome == 'success' && '✅ Passed' || (steps.skin_polarity.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Skin Polarity Checker | ${{ steps.skin_polarity_tests.outcome == 'success' && '✅ Passed' || (steps.skin_polarity_tests.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Primitive Icons & Documentation | ${{ steps.icons.outcome == 'success' && '✅ Passed' || (steps.icons.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Render Linter Self-Test | ${{ steps.render_selftest.outcome == 'success' && '✅ Passed' || (steps.render_selftest.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY echo "| Rendered Layout (paint oracle) | ${{ steps.render.outcome == 'success' && '✅ Passed' || (steps.render.outcome == 'skipped' && '⏭️ Skipped' || '❌ Failed') }} |" >> $GITHUB_STEP_SUMMARY