Files
4faae6696c
CI / Plugin Package & Version Gate (push) Failing after 52s
CI / Python 3.9 Compatibility (push) Successful in 13s
CI / Lint & Verify (ubuntu-latest, Python 3.11) (push) Successful in 29s
Deploy gallery to GitHub Pages / build (push) Canceled after 0s
Deploy gallery to GitHub Pages / deploy (push) Canceled after 0s
CI / Lint & Verify (ubuntu-latest, Python 3.12) (push) Successful in 2m56s
CI / Lint & Verify (macos-latest, Python 3.11) (push) Canceled after 0s
CI / Lint & Verify (macos-latest, Python 3.12) (push) Canceled after 0s
CI / Lint & Verify (windows-latest, Python 3.11) (push) Canceled after 0s
CI / Lint & Verify (windows-latest, Python 3.12) (push) Canceled after 0s
Add adversarial tests for verify-semantic-motion.py and verify-sequence-oauth.py (#69)
* Add adversarial tests for verify-semantic-motion.py and verify-sequence-oauth.py

Every other verify-*.py in scripts/ has a matching test-verify-*.py
(verify-docs-sync, verify-geometry, verify-motion, verify-plugin-package),
and CONTRIBUTING.md lists both verify-semantic-motion.py and
verify-sequence-oauth.py as required PR gates, but neither had a dedicated
test file exercising its own pass/fail behavior.

- test-verify-semantic-motion.py: asserts the shipped SKILL.md /
  semantic-patterns.md / animation.md and the policy-trace animated example
  pass verify_markdown()/verify_example(), plus adversarial cases for a
  missing semantic-pattern router link, a missing pattern name, and a
  duplicate HTML/SVG id in the example.
- test-verify-sequence-oauth.py: CLI smoke-tests the shipped repo (expects
  ALL GATES PASSED) and an adversarial case that removes the dashed-return
  marker from the OAuth 401 response in a temporary copy of the repo tree
  (verify-sequence-oauth.py resolves paths from its own __file__, so the
  adversarial run executes the copied script against the copied tree).

Also wires both new tests into CONTRIBUTING.md's gate table and combined
validation command, following the exact pattern used for the existing
test-verify-*.py gates. (CI workflow wiring left out of this PR since it
touches .github/workflows/ci.yml — happy to add in a follow-up if useful.)

* Address review feedback on PR #69

- Wire test-verify-semantic-motion.py and test-verify-sequence-oauth.py
  into .github/workflows/ci.yml so CONTRIBUTING's new gates actually run
  in CI, with matching step-summary rows.
- Bump .claude-plugin/plugin.json and .codex-plugin/plugin.json in sync
  to 2.5.7 so scripts/verify-plugin-package.py origin/main passes again.
- Simplify test-verify-sequence-oauth.py's adversarial fixture to copy
  only the files verify-sequence-oauth.py's main() reads before the
  dashed-return check (type-sequence.md + cold-cache/oauth example
  trios), instead of shutil.copytree of the whole repository.

* Re-bump plugin manifests above current main

The prior 2.5.7 bump was superseded by subsequent merges of main into
this branch (main itself advanced past it to 2.6.5). Re-run
bump-plugin-version.py so the branch is strictly above upstream main
again (2.6.6), per @philyuchkoff's review: the diff had settled back
to no version delta at all.

---------

Co-authored-by: alvinhui <alvinhui@users.noreply.github.com>
Co-authored-by: Cathryn Lavery <50469282+cathrynlavery@users.noreply.github.com>
2026-08-25 01:57:59 -05:00

367 lines
18 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 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 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
- 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
# 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 "| 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 "| 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 "| 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 "| 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