Why use it: only read this file when you modify Diagram Design itself — add a diagram type, change the extractor, change the icon set. Regular users never need to come here; 01–06 is enough.
What this file generates: a branch that passes the entire CI gate locally before pushing.
This entire file is shell commands. Does not depend on
01–06, but you should read04first, because the extractor's exit code contract is something you are not allowed to break.
1. Preparation
git clone git@github.com:cathrynlavery/diagram-design.git
cd diagram-design
python3 --version # phải >= 3.10; CI chạy 3.11 và 3.12 trên Linux/Windows/macOS
There is no npm install, no mandatory venv. Only two optional things:
python3 -m pip install playwright && python3 -m playwright install chromium # cho lint-render + render screenshot
python3 -m pip install "Pillow==12.1.1" # cho verify thumbnail README
Both versions are hard-pinned in CI (PLAYWRIGHT_VERSION: "1.62.0", PILLOW_VERSION: "12.1.1"). The reason is written plainly in ci.yml: the oracle of lint-render.py is pixel, so the browser is part of the contract — upgrading Playwright and Chromium must be done together, deliberately.
Process rules from CONTRIBUTING.md §"Before you start":
- Create an issue first for anything nontrivial (new type, behavior change, touching the import grammar). Small fixes and docs can go straight to a PR.
- Work on a branch — never commit directly to
main. - One PR = one concern.
2. RULE NUMBER ONE: do not bump the version in a PR
This is the thing that turns PRs red the most, and it is counterintuitive.
Three manifests — .claude-plugin/plugin.json, .codex-plugin/plugin.json, .factory-plugin/plugin.json — are bumped automatically on main after each merge by the workflow auto-bump.yml (ADR 0009). PRs must leave untouched all three. CI rejects any change to them:
python3 scripts/verify-plugin-package.py --require-no-bump origin/main
Need a release larger than a patch? Write it in the PR description, and the maintainer will attach the label release:minor or release:major before merging; the label decides the bump level. scripts/bump-plugin-version.py still exists but is for workflows and maintainers — contributors never run it.
Design reason: keep open PRs from conflicting with each other on every merge.
3. The 40,000-byte budget of SKILL.md — only 4 bytes left
The semantic-pattern gate limits skills/diagram-design/SKILL.md to 40,000 bytes so the installed skill can still load (ADR 0004; the constant MAX_SKILL_BYTES = 40_000 at scripts/verify-semantic-motion.py:23).
Current size, measured at commit 4451ead:
$ wc -c < skills/diagram-design/SKILL.md
39996
Exactly 4 bytes left. Adding one word to SKILL.md is the CI break. When this gate turns red: reduce duplication or move details into a file that references/ is routed. Absolutely do not remove the routing vocabulary from frontmatter — that is what makes the skill trigger at the right time.
4. Run all gates before pushing
CONTRIBUTING.md provides a chain of && about 48 commands long. Paste it as is:
python3 scripts/test-plugin-package.py \
&& python3 scripts/test-maintainer-policy.py \
&& python3 scripts/verify-plugin-package.py --require-no-bump origin/main \
&& claude plugin validate . --strict \
&& python3 scripts/test-lint-a11y.py \
&& python3 scripts/verify-semantic-motion.py --markdown-only \
&& python3 scripts/verify-semantic-motion.py --example-only \
&& python3 scripts/verify-motion.py --shipped \
&& python3 scripts/lint-skin.py --all --baseline \
&& python3 scripts/lint-render.py --self-test \
&& python3 scripts/lint-render.py --all \
&& python3 scripts/test-verify-polar.py && python3 scripts/verify-polar.py \
&& python3 scripts/verify-sequence-oauth.py \
&& python3 scripts/test-verify-semantic-motion.py \
&& python3 scripts/test-verify-sequence-oauth.py \
&& python3 scripts/verify-drawio-import.py && python3 scripts/test-verify-drawio-import.py \
&& python3 scripts/verify-mermaid-import.py \
&& python3 scripts/test-verify-motion.py \
&& python3 scripts/verify-doctor.py && python3 scripts/test-verify-doctor.py \
&& python3 scripts/verify-docs-sync.py && python3 scripts/test-verify-docs-sync.py \
&& python3 scripts/verify-screenshot-freshness.py \
&& python3 scripts/test-build-readme-thumbs.py && python3 scripts/build-readme-thumbs.py --check \
&& python3 scripts/test-self-check.py \
&& python3 scripts/verify-geometry.py --all && python3 scripts/test-verify-geometry.py \
&& python3 scripts/verify-treemap.py --all && python3 scripts/test-verify-treemap.py \
&& python3 scripts/verify-dumbbell.py && python3 scripts/test-verify-dumbbell.py \
&& python3 scripts/verify-slopegraph.py --all && python3 scripts/test-verify-slopegraph.py \
&& python3 scripts/verify-ridgeline.py --all && python3 scripts/test-verify-ridgeline.py \
&& python3 scripts/verify-sankey.py --all && python3 scripts/test-verify-sankey.py \
&& python3 scripts/verify-bubble.py --all && python3 scripts/test-verify-bubble.py \
&& python3 scripts/verify-bump.py --all && python3 scripts/test-verify-bump.py \
&& python3 scripts/verify-beeswarm.py --all && python3 scripts/test-verify-beeswarm.py \
&& python3 scripts/verify-skin-polarity.py --all && python3 scripts/test-verify-skin-polarity.py
A notable pattern: each verifier has an accompanying test. verify-sankey.py that checks the real file; test-verify-sankey.py checks that that very verifier catches both the passing case and the adversarial case. A broken linter must not be allowed to report the repo as clean. If you fix a verifier, you must fix its test too.
Actual measurement results (macOS, Python 3.9.6, clean clone at 4451ead)
| Gate | rc | Note |
|---|---|---|
lint-skin.py --all --baseline |
0 |
153 file(s) checked, 20 skipped, 0 finding(s) |
verify-geometry.py --all |
0 |
155 file(s) checked, 0 finding(s) |
verify-semantic-motion.py --markdown-only |
0 |
mode, primitive, static fallback, a11y |
verify-motion.py --shipped |
0 |
every shipped motion file |
verify-drawio-import.py |
0 |
All draw.io import gates passed. |
verify-mermaid-import.py |
0 |
All Mermaid import gates passed. |
verify-docs-sync.py |
0 |
description hook, gallery, README tree, reference link, routing |
test-self-check.py |
0 |
All self-check tests passed |
verify-plugin-package.py --current-only |
0 |
Claude/Codex/Factory all 2.6.12 |
verify-doctor.py |
1 |
red because of the machine environment, not because of the code |
Warning about verify-doctor.py: it is not a unit test — it actually runs doctor and returns rc according to your machine's condition. On the Python 3.9.6 machine it prints:
Doctor summary: FAIL (3 pass, 1 warn, 1 fail)
[FAIL] Python runtime: Python 3.9.6 found via python3 at /usr/bin/python3; Diagram Design requires Python >= 3.10.
[WARN] Playwright PNG export readiness: Playwright package is not available in the active Python interpreter.
This gate is red because of the environment, not because of your changes. Upgrade python3 to 3.10+ and run again. The Playwright warning is a genuine false negative — see 01 §4 trap 2: the probe in scripts/verify-doctor.py:158 uses playwright.__version__, a package attribute that does not exist.
Many gates run on 3.9 (CI also has a separate job python39-compat for test-lint-a11y.py and lint-skin.py --all --baseline), but do not rely on that — dev needs 3.10+.
5. How to read gate errors
| Red gate | Meaning and how to fix |
|---|---|
verify-plugin-package.py |
Reports version change → drop the manifest change from the branch. Reports packaging → keep every marketplace pointing to the repo root and shared skills at skills/diagram-design/SKILL.md. |
lint-skin.py |
The message states the file, line, category: color, font-family, a11y, external-asset, pure-black, script. Colors must come from the palette in style-guide.md; fonts from the allowed list. The linter also requires controllers pinned by SHA from template-motion.html verbatim, and rejects remote resources, CSS @import, CSS url() that is not a fragment, event handlers, srcdoc, executable URLs, and leftover scripts. |
verify-*.py |
The extractor's real behavior no longer matches the fixture/docs, or the reference/command/prompt wiring is off. Fix the source of truth — do not loosen the test to make it pass. |
verify-geometry.py |
A label mask overlapping a node declared after it → the fill node will clip the label at render. Move the label to the empty stretch of the connector, keep a 6–10px gap with the stroke per SKILL.md §6, and do not shrink the mask to dodge it. |
verify-slopegraph.py |
The two axes disagree on scale/origin, or the endpoint is drawn in the wrong place relative to the value it declares. Fix the coordinates, not the label — and do not move points to keep two labels from touching, because crowded labels mean the two values are actually close. |
verify-ridgeline.py |
A ridge drawn to its own amplitude, a baseline off-pitch, a ridge sampled on its own x-axis, or a range printed wider than the bin it declares. Do not renormalize a ridge just to make it readable. |
verify-bubble.py |
A bubble drawn off the shared scale, a radius disagreeing with the area constant r = K·√size, the second bubble wearing an accent, a small bubble drawn under a large one when they overlap. Fix the geometry, not the constraint. |
verify-bump.py |
A peak sits off the rank grid, one mark's rank is not a permutation 1..N, the connecting segment is curved. A rank between two ranks is not a rank. |
verify-beeswarm.py |
A dot off the value scale, two dots with the same value in two positions, a pair of dots overlapping instead of dodging, a second radius appearing, or positioning a mark with CSS (transform, translate/rotate/scale, geometry property, motion path) through any carrier — attribute, style inline, or a rule <style>. Crowding is data; dodging is the only honest way to handle it. |
verify-skin-polarity.py |
A legend/caption calling the ramp direction by brightness (darker is larger) in a file where the ramp runs the other way. ink is role, not color — it comes out near-black on light paper and near-white on dark paper. Call the direction by contrast against the paper (stronger contrast is larger), that sentence survives a skin change, and ship the same sentence in every variant. |
verify-treemap.py |
A cell does not match the value it carries as a label, or the label does not fit the cell. |
verify-screenshot-freshness.py |
A minimalist light example or its PNG changes but the catalog has not been refreshed in sync. Install Playwright, run python3 scripts/render-canonical-screenshots.py, review all 39 renders, then commit the new PNG + docs/screenshots/manifest.json. |
build-readme-thumbs.py --check |
A README preview is missing/stale/broken/wrong-sized/orphaned/no longer linking to the full PNG. Install Pillow==12.1.1, run build-readme-thumbs.py, commit the WebP + docs/screenshots/thumbs/manifest.json. |
| Icon assets | You changed scripts/vendor/icons/ or scripts/build-icons.py but the generated file is stale. Re-run python3 scripts/build-icons.py and commit icons.html + references/primitive-icons.md. |
The most important prohibition
Do not add files to
scripts/lint-skin-baseline.txtto bypass.
Baseline (currently has 20 lines) is only for legacy pre-2.0 examples that inherently predate the current skin — and they still are subject to a11y checks. Adding new files there is gate fraud.
6. Gates for each type of change
| You touch | Run at minimum |
|---|---|
| A new example | python3 scripts/lint-skin.py skills/diagram-design/assets/example-my-type.html then --all --baseline |
| A new diagram type | add references/type-*.md + example + table entry SKILL.md §3 → verify-docs-sync.py + lint-skin.py --all + 40,000-byte budget check |
| Import path | verify-drawio-import.py + test-verify-drawio-import.py + verify-mermaid-import.py |
| Icon set | build-icons.py then git diff --exit-code on icons.html and primitive-icons.md |
| Motion | verify-motion.py --shipped + test-verify-motion.py + verify-semantic-motion.py (both modes) |
| Doctor | verify-doctor.py + test-verify-doctor.py |
| Any docs file | verify-docs-sync.py — it checks both reference links and the command/prompt surface |
7. ADR — read before debating design
docs/adr/ has 9 decisions. The four most often stumbled on:
- 0001 — static by default, a single pinned controller.
- 0003 —
revealis where autoplay is allowed only. - 0004 — the byte threshold of
SKILL.md+ trigger-rich description. - 0009 — version bump on
mainafter merge, not in the PR.
Along with docs/cookbook.md (operating formula) and docs/superpowers/ (template plan + spec for a new type, using polar chart as an example).
8. This step is done when
- The entire chain
&&runs green locally (verify-doctor.pyrequires Python 3.10+ to be green). - No manifest version is touched in the diff.
-
wc -c < skills/diagram-design/SKILL.mdstays under 40,000. - No new lines in
scripts/lint-skin-baseline.txt.