* feat(line): add ridgeline as a Line variant One distribution per series, stacked at a fixed pitch on a single shared amplitude. Ships three static examples (light, dark, full editorial), the type-line.md spec, and a geometric gate. verify-ridgeline.py derives the figure's one amplitude and its baseline pitch from the figure itself, then holds every vertex to both. It also checks the drawn baseline rules, the shared bin scale, absolute M/L/Z only, the overlap ceiling, the focus pairing and every label binding. test-verify-ridgeline.py proves each check in both polarities and pins the scope treaty with the sibling Line gates. The contract binds data-bins on a <path>, which no other chart in the repo declares, so verify-slopegraph and this gate never claim the same file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(verify-ridgeline): read exponent coordinates as numbers, not commands parse_d scanned for [A-Za-z] independently of the numbers, so the `e` in a valid scientific-notation coordinate such as 4.4e2 was counted as a path command. The explicit-command rule then rejected a correct ridgeline with a message about command counts. Tokenise the d attribute left to right with the number branch tried first, so an exponent is consumed as part of its number. A letter that survives that branch is a real command and still faces the M/L/Z vocabulary check; anything else leaves the path unreadable, which stays a finding. Adds both polarities: a shipped coordinate rewritten as 4.4e2 must verify, and a falsified one written the same way must not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: 0xDarkMatter <0xDarkMatter@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
343 lines
16 KiB
YAML
343 lines
16 KiB
YAML
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: Require a synchronized plugin version bump
|
|
env:
|
|
BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
|
|
run: python3 scripts/verify-plugin-package.py "$BASE_REF"
|
|
|
|
- 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 sequence OAuth specs
|
|
if: always()
|
|
id: oauth
|
|
run: python scripts/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 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 packaged self-check
|
|
if: always()
|
|
id: self_check
|
|
run: python scripts/test-self-check.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 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 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 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 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
|
|
|
|
# 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 "| Sequence Diagram Grammar | ${{ steps.oauth.outcome == 'success' && '✅ Passed' || (steps.oauth.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 "| 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 "| Packaged Self-Check | ${{ steps.self_check.outcome == 'success' && '✅ Passed' || (steps.self_check.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 "| 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 "| 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 "| 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 "| 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 "| 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
|