* fix(doctor): fall through a python3 that cannot report its version
* fix(doctor): report a command that cannot be launched instead of raising
The python3 fallback only survived an interpreter that exits non-zero. A
name on PATH that cannot be spawned at all -- an App Execution Alias for an
uninstalled app, a dangling symlink, a file without the exec bit -- raised
out of run_command and ended the run with a traceback, on the very
candidate the fallback exists to survive.
Catch OSError in run_command and return it as a non-zero result. Every
caller already asks only whether the command answered, so the probe loop
falls through as it does for any other dud, and check_playwright, which is
handed the command name even when the runtime check failed, warns instead
of dying.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018yueXNv7ew2s8sHhv43HG4
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cathryn Lavery <50469282+cathrynlavery@users.noreply.github.com>
* feat(import): add Excalidraw extractor and scene fixtures
excalidraw_extract.py parses .excalidraw/.excalidraw.json scenes into the
same digest shape as the draw.io and Mermaid extractors: nodes, edges,
frames, groups, budget flags, and a discard ledger. Source content is
treated as untrusted data throughout; links, embeds, images, freedraw,
and unknown element types are inventoried, never rendered or fetched.
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
* docs(import): Excalidraw redraw reference, command, prompt, worked example
Mirrors the Mermaid import surfaces: references/import-excalidraw.md
carries the four dials and fidelity-ledger contract, the plugin command
and Pi prompt route to it, and the gallery ships a worked example tab.
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
* ci: verify Excalidraw imports
verify-excalidraw-import.py drives the real extractor against both
fixtures (clean whiteboard + adversarial scene), checks trust-boundary
behavior, resource caps, named exit-2 failures, encoding safety, and
reference/command wiring. test-verify-excalidraw-import.py proves the
verifier rejects intentional breakage.
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
* docs: route Excalidraw import across README, SKILL.md, doctor, and manifests
Adds .excalidraw routing to SKILL.md §11, README import section and file
tree, CONTRIBUTING gates, cookbook R6, doctor script/routing inventories,
docs-sync routing surfaces, and the four manifest descriptions. No
plugin version bumps.
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
* fix(excalidraw_extract): reject non-finite geometry with the promised exit 2
A scene is untrusted input, and three shapes reached the digest's integer
formatting and crashed there instead of being diagnosed: the JSON tokens
`Infinity` and `NaN` (OverflowError / ValueError) and an out-of-range integer
coordinate ("int too large to convert to float"). All three exited 1 with a
traceback rather than the documented exit 2.
`_num` now rejects non-finite values and out-of-range ints, `json.loads`
refuses the non-finite constant tokens via parse_constant, and scene_bounds
guards the derived width/height arithmetic. Absent or non-numeric fields keep
reading as 0, so no valid scene changes: both fixtures produce byte-identical
output to before this commit.
* Run mobile render checks in the Playwright gate
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Cathryn Lavery <cathryn@bestself.co>
* feat(waterfall): add reference, example variants, and executable verifier
Waterfall is a running-total grammar: totals anchor at the domain floor,
signed bridge bars float between running levels, and a carry connector
conserves the total across every gap. Each bar declares data-role,
data-value, and data-name; scripts/verify-waterfall.py recomputes the
walk and fails on conservation, scale, bridge-geometry, carry, printed-
label, or sign-treatment drift, with adversarial coverage in
scripts/test-verify-waterfall.py per ADR 0005.
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
* feat(waterfall): wire routing, gallery, docs, CI, and screenshot catalog
Move the verifiable type count 39 -> 40 across SKILL.md, the frontmatter
description and all four plugin manifest descriptions, the gallery tab
(eyebrow 53), README, CONTRIBUTING, cookbook, and the counters in
verify-docs-sync.py, verify-semantic-motion.py, the screenshot catalog
scripts, and the motion-verifier fixture. Add the waterfall CI gates to
ci.yml and .maintainer-policy.json, the canonical waterfall.png with its
manifest entry, and the README thumbnail. Pay the ADR 0004 byte cap by
trimming SKILL.md body prose the ADR 0007 way (an illustrative §6 rule-5
example clause and the §10 consultant-row detail); SKILL.md lands at
39,971 bytes. Plugin manifest versions are untouched (ADR 0009).
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
* docs(adr): record the waterfall running-total grammar decision
ADR 0010 argues the grammar against bar, dumbbell, pyramid, Sankey, and
stacked bars, and scopes the two dumbbell-precedent departures (sign by
fill weight, data coordinates exempt from the 4px grid). ADR 0002 gains
the 39 -> 40 amendment its enforcement procedure requires.
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
* docs(pr-previews): add waterfall light/dark/full visual proof renders
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
* fix(verify-waterfall): close the three review findings, with regressions
Non-finite carries: parse_number now rejects nan/inf, and the bar geometry,
running total and fitted scale each require finite values, so a
data-carry="nan" can no longer sail past a tolerance comparison that is false
either way.
Labels were not bar-bound: printed_values now takes the bar and only accepts a
<text> centred on that bar's column at its expected label height. A bar that
loses its own label while an unrelated tick carries the same number now fails
instead of borrowing it.
Polarity fills were under-validated: the tint-for-increase and
paper-for-decrease mapping is asserted absolutely per bar against the theme's
documented pair, which the previous set-vs-set comparison could not see for a
single-polarity walk or a swapped chart. An unrecognised paper falls through to
the relative checks rather than failing the file.
Each finding gets a mutation in test-verify-waterfall.py: 17 now, all caught,
and the three shipped examples still pass.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Cathryn Lavery <cathryn@bestself.co>
* fix(export): normalize rgba()/transparent for PowerPoint's SVG importer
PowerPoint's SVG 1.1 importer doesn't understand CSS Color Module
notation: rgba(...) and transparent render as opaque black instead of
the intended tint, so an exported diagram with tinted cards or hairline
borders comes out with solid dark blocks that swallow the labels
inside them. The design system's tokens are authored as rgba(...) (see
style-guide.md), so this affects nearly every shipped example — 2850
rgba() occurrences across all 149 example files, plus transparent in
49 of them.
Add a color-normalization step to the SVG export procedure in
export.md: rgba(r,g,b,a) becomes a hex fill/stroke plus a
fill-opacity/stroke-opacity attribute, and transparent becomes none.
Both are lossless — every browser that already rendered the diagram
correctly renders the transformed attributes identically. The source
HTML and design tokens are untouched; only the extracted standalone
SVG is normalized, matching how the export procedure already handles
XML-escaping the font @import (see #38).
Doc-only change, following the shape of the export procedure itself
(no shipped script backs this path). Verified the documented regex
transform against every rgba()/transparent occurrence in
example-architecture.html and confirmed round-trip color equivalence.
Fixes#148
* fix(export): fully specify the color-normalization regexes
Addresses review feedback on #151: the procedure said "two
attribute-scoped regexes cover this" without giving them, leaving the
exact matching/capture behavior to be inferred. Replace the prose with
literal, tested Python regexes: tolerant of optional whitespace after
each rgba() channel and of alpha values with or without a leading zero
(both 0.03 and .03 appear in shipped tokens).
Verified against every <svg>...</svg> block in skills/diagram-design/assets/
(330 blocks, 2218 rgba() and 188 transparent occurrences): zero left
over after the documented transform, output stays well-formed. The
four whitespace-separated rgba() values that exist in the repo turned
out to be CSS custom properties in <head><style> blocks (outside the
extracted <svg> node, so already out of scope) rather than fill/stroke
attributes — the whitespace tolerance is forward-looking robustness,
not a fix for a currently-reachable miss.
* chore(plugin): re-bump to 2.6.8 after main reached 2.6.6
Main advanced to 2.6.6 in #69, the version this branch had claimed, so the
delta against upstream settled back to zero and the package gate fails:
"Claude manifest version must increase relative to origin/main: 2.6.6 ->
2.6.6".
Re-run bump-plugin-version.py to 2.6.8 rather than 2.6.7, leaving 2.6.7 to
my open #153 so the two branches do not collide on the same number
whichever lands first.
---------
Co-authored-by: Cathryn Lavery <cathryn@bestself.co>
* feat(patterns): add traceable block decomposition semantic pattern
Hierarchical, ID-addressable block diagrams on Tree, with per-block
input/output/constraint/assumption metadata carried in data-block-*
attributes and a --registry export mode that projects it into a JSON
sidecar. See docs/adr/0009 for the design rationale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: registry-only export no longer implies image export; parser no longer truncates on quoted '>'
Two defects from automated review (Greptile) on PR #169, both verified
independently before fixing:
- commands/export-diagram.md and prompts/export-diagram.md: --registry
used alone still fell through to the unconditional "produce both
.svg and .png" default, pulling in a Playwright dependency a
metadata-only call should never have. --registry alone is now
explicitly registry-only; combine with --svg-only/--png-only to
also get an image.
- scripts/verify-block-registry.py: the tag matcher used [^>]* to find
a tag's end, so a literal '>' inside a quoted attribute value (valid
HTML, e.g. data-block-constraint="output > input") truncated the
match. If that attribute sat before data-block-id in the tag, the
whole block vanished from the scan silently -- a file with a real
block passed CI as if it had none. The matcher now treats a quoted
span (either quote style) as one unit, and the attribute matcher
accepts single quotes too, which it previously didn't. Four new
regression cases in scripts/test-verify-block-registry.py cover
both orderings of the truncation hazard, single quotes, and mixed
quoting in one tag.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* chore: rebase onto main and re-bump version to 2.6.13
PR #155 merged and moved main to 2.6.12 while this branch was open,
colliding with this PR's own 2.6.11 -> 2.6.12 bump. Rebased and
re-ran scripts/bump-plugin-version.py; verify-plugin-package.py
origin/main now passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(skill): restore two load-bearing sentences trimmed for byte budget
The verify-geometry.py pointer under rule 6 and "the connector rules
never relax" (referring to section 6) in section 11 were both cut to
make room for the new routing-table row; both turned out to be
load-bearing (the only in-context link from the label-mask rule to
its verifier, and a sentence that forecloses an argument agents have
actually made).
Restored both and found the bytes elsewhere instead: tightened the
output-dials intro, dropped a redundant clause after the decorative-
SVG aria-hidden rule, and tightened the duplicate-ID rationale. Net
+5 bytes; SKILL.md is 39,989 of 40,000.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs(adr): reframe ADR 0009 around the general need, not a named downstream project
ADRs here are settled repo policy future contributors read as
precedent, not a record of one customer's motivation. Dropped the
named project and its patent-filing detail from the Context section
and replaced it with the general need the pattern actually serves
(stable, citable block identity plus a machine-readable sidecar, for
compliance/IP-style documentation generally). Also genericized the
illustrative example names in the Decision and Consequences sections
for the same reason.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* ci: register the block-registry gates as required maintainer-policy checks
verify-block-registry.py --all and test-verify-block-registry.py were
wired into .github/workflows/ci.yml but never added to
.maintainer-policy.json, so the policy manifest wasn't the source of
truth for them. Added both next to the geometry pair, and documented
them in CONTRIBUTING.md's gate table, combined run-them-all script,
and "if a gate fails" troubleshooting section, matching every other
gate's treatment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs: attach PR #169 visual-proof screenshots
Temporary review artifact for the PR template's required visual-proof
section (light/dark/full editorial renders of the new Traceable block
decomposition example). Not part of the shipped skill package; safe
to delete once the PR merges.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* feat(examples): ship a Traceable block decomposition example, gallery-registered
Light/dark/full editorial variants (example-tree-block-decomposition*.html),
a generic payment-gateway decomposition chosen so the example reads clearly
as illustrative rather than tied to any real product. Registered in the
gallery as "Tree - block decomposition" (eyebrow 53).
Without this, verify-block-registry.py --all passed vacuously -- no shipped
asset carried data-block-id, so the gate had nothing to guard. It now
covers 158 files instead of 155.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(examples): show each block's data-block-id in its badge chip
The three Traceable block decomposition variants rendered category tags
(SYS, AUTH, RISK, LDG) in the chip while data-block-id carried PAY-001,
PAY-001-01, PAY-001-02 and PAY-001-03. semantic-patterns.md § 8 puts the
ID in the chip and names "an ID badge with no matching data-block-id" as
an anti-pattern, and the full variant's headline card already tells the
reader the badge says PAY-001-02. The chips now carry the IDs. Each chip
widens (28 to 46 for the root, 32 to 62 for the children) and its text
recentres; the 160-wide node boxes, name slot and port labels are
unchanged, so the density budget does not move.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(verify): reject blank data-block-id values and whitespace-only names
verify-block-registry.py accepted data-block-id="" as a valid block, and
because that blank id then sat in the known-id set, a blank
data-block-parent resolved against it: two blocks joined by an empty
string passed as a coherent tree with zero findings. A whitespace-only
data-block-name passed too, while an empty one was caught. Both
asymmetries are gone: a blank or whitespace-only id is a finding, a
blank id is never a resolvable parent target, and names are stripped
before the presence check. export-registry.md promises id and name are
always present, so export would otherwise have emitted "id": "". Five
adversarial cases cover both polarities, and the CONTRIBUTING and README
gate descriptions name the new findings.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: ground the pattern's docs and example in the shipped payment-gateway case
Review point 5 asked ADR 0009 to stop grounding itself in a named
downstream project and a future patent filing. That framing survived in
four more places: ADR 0002's amendment line, the § 8 selection trigger
and its canonical ID and name examples (FC3-001-02, "Timeline Engine"),
the entire worked example in export-registry.md (fc3-timeline-
architecture, FC3-001, "Command Queue"), and the full variant's headline
card ("a future filing"). The two reference files are the surface agents
load at runtime, and the repo was teaching two different canonical
examples. Everything now uses the payment-gateway example this PR ships,
and the registry worked example is a literal excerpt of running
--registry on it. The verifier's test fixtures drop the project
initialism as well.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(export-registry): define document order once
The schema note said blocks are emitted in document order "(depth-first,
matching the tree's own root-to-leaf visual order)" while procedure
step 5 said only "document order". Those agree only when the SVG happens
to be authored depth-first: a three-level tree drawn row by row makes
document order breadth-first and the parenthetical false, and the
shipped two-level example cannot tell the two apart. Document order now
has one definition, source order with no re-sorting, stated in the
schema note and referenced from the step.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(adr): make the SKILL.md headroom remark non-numeric
The sentence said SKILL.md "had 16 bytes of headroom" when this landed;
after the review-point-4 restoration it is 39,989 of 40,000, so 11. The
argument does not depend on the exact figure and any figure goes stale
on the next byte-neutral edit, so the remark now says what it means:
within a few dozen bytes of the cap when this landed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: re-render the PR #169 visual-proof screenshots with the ID badges
Same capture as the originals (Playwright Chromium, 2x, first-<svg> for
the light and dark variants, the full editorial page for the full
variant) so the linked previews show the badges reading PAY-001,
PAY-001-01, PAY-001-02 and PAY-001-03 rather than the old category tags.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* chore: drop the manifest version bump per upstream ADR 0009
main now bumps the Claude, Codex and Factory manifest versions after
each merge (Auto Version Bump workflow, ADR 0009), and CI rejects any
version change in a pull request through verify-plugin-package.py
--require-no-bump. The 2.6.12 to 2.6.13 bump this PR carried from the
earlier rebase is reverted so all three manifests match main again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(adr): renumber the block-registry ADR to 0010
Upstream took ADR 0009 for the versions-bumped-on-main policy while this
PR was in review, so the block-registry metadata contract moves to 0010.
Its status line now records the manifest version at the time of writing,
v2.6.12, since the PR no longer carries a bump.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(verify): parse block registries the way a browser parses HTML
verify-block-registry.py used a hand-rolled regex tag/attribute scanner.
On exact head ca983b2 it returned zero blocks for unquoted attribute
values, whitespace around "=", and case-insensitive DATA-BLOCK-* names,
treated a commented-out block as live, and silently dropped a
boolean/blank data-block-id instead of reporting it -- the same
fail-open shape as the quoted-">" bug fixed earlier: a real block
vanishes from the scan and CI reports clean as if the file had none.
The regex scanner is replaced with the stdlib html.parser.HTMLParser,
which already lowercases attribute names, tolerates unquoted values and
spacing, and never calls handle_starttag for tag-like text inside a
comment or a <script>/<style> element. A present-but-valueless
data-block-id now registers as a block with a blank id (a finding),
rather than making the whole element invisible to the scan, and a
repeated attribute name keeps its first value, matching how a browser
resolves a duplicate attribute in one tag. Source order and line
reporting are unchanged. Six adversarial cases cover unquoted values,
whitespace around "=", case-insensitive names, a boolean data-block-id,
a commented-out block, and duplicate-attribute precedence -- each
checked to confirm it would fail under the wrong implementation, not
just pass vacuously.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Hank-tha-Cowdog <zachalberd@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cathryn Lavery <50469282+cathrynlavery@users.noreply.github.com>
Closes#195.
export.md's @import requested three Latin faces while assets/template.html
requested seven. The four missing ones were all the CJK faces, so a Korean or
Chinese diagram exported to .svg lost its type — quietly, and only on a machine
other than the author's, which is the case those faces are in the link to
prevent.
- references/export.md: the @import now requests the same families as the
shipped link, with the & separators the surrounding prose requires.
- scripts/verify-docs-sync.py: check_export_font_parity asserts that the
families in the @import are a superset of those in the template's <link>.
Superset rather than equality, since an export may legitimately request more
but must never request less.
The gate belongs in verify-docs-sync.py rather than a new script because a new
verify-*.py needs matching entries in ci.yml, the CONTRIBUTING table, and
.maintainer-policy.json, and test-maintainer-policy.py asserts those agree.
Verified adversarially: reverting only export.md to its state on main makes the
gate exit 1 and name all four missing families; with the fix it exits 0.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(docs): drop non-existent extended asset citations and enforce asset link integrity (#185)
* docs(references): clean up stale prose references to extended examples
---------
Co-authored-by: Cathryn Lavery <50469282+cathrynlavery@users.noreply.github.com>
Closes the two gaps in #175: the Traditional Chinese faces are now in the
shipped Google Fonts link, and style-guide.md carries a register/floor/title
section for Han that mirrors #korean-labels.
- Font link: add Noto Sans TC and Noto Serif TC to all six files that carry
it (SKILL.md, style-guide.md, and the four assets/template*.html), which
were byte-identical before and remain so.
- style-guide.md: new "### Traditional Chinese labels" section, parallel to
the Korean one — stack, per-character width budget, 12px floor, sublabels
stay Latin, register switch for the 7-8px mono slots.
- output-spec.md: the Traditional stack now leads with Noto Sans TC (the face
ships, so the rationale in #korean-labels applies), points at the new
anchor, and states 12px rather than 10px as the CJK floor.
- lint-skin.py: ALLOWED_FONTS was missing "noto serif tc" while Korean had
both faces, so the documented serif stack would have failed the linter.
- SKILL.md: the Korean summary becomes a CJK summary covering both scripts.
The width-budget clause moves out to references/, which is what ADR 0004
prescribes when the file is at the cap; SKILL.md stays at 39,996 bytes.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cathryn Lavery <50469282+cathrynlavery@users.noreply.github.com>
* ci(release): bump plugin versions on main after merge, not in PRs
Every PR previously edited the same three manifest version lines, so each
merge conflicted every other open PR. Pull requests now leave the versions
untouched (verify-plugin-package.py --require-no-bump enforces this), and
the Auto Version Bump workflow bumps them on main after each merge, with
release:minor / release:major labels selecting the bump size. The bump tool
now also syncs SKILL.md metadata.version so minor and major bumps cannot
drift. Records the policy as ADR 0009.
Refs #171
* fix(ci): harden automatic version publishing
* fix(gallery): deduplicate slopegraph eyebrow number and strengthen sync check
- index.html: slopegraph eyebrow was 30 (duplicate of treemap); corrected to 31
- verify-docs-sync.py: extend check_gallery with two new guards:
1. Duplicate eyebrow number detection -- catches the exact regression just fixed
2. Dark/full variant coverage for non-single tabs -- catches incomplete type additions
* chore: bump plugin version 2.6.1 → 2.6.2
* test(gallery): add focused adversarial tests for gallery guards
Six cases added to test-verify-docs-sync.py:
- duplicate eyebrow number is caught
- unique eyebrow numbers produce no error
- non-single tab missing -dark variant is caught
- non-single tab missing -full variant is caught
- data-single tab with only light file passes (no false positive)
- complete non-single tab (all three variants) passes
Addresses greptile-apps review comment on the gallery regression
coverage gap.
* refactor(verify-docs-sync): replace dead-store dict with set for seen_eyebrows
* fix(gallery): encode parent/variant eyebrow model with data-parent-type
Replace global eyebrow uniqueness with a parent/variant-aware rule:
- Add data-parent-type="line" to slopegraph and ridgeline tab buttons
- Add data-parent-type="scatter" to bubble tab button
- Revert slopegraph eyebrow 31 → 20 (Line variant shares its parent's number)
- Update verify-docs-sync.py check_gallery to enforce uniqueness only among
independent types and require each variant to match its parent's eyebrow
- Update test-verify-docs-sync.py: extend make_tab with parent param and add
three new cases (allowed reuse, wrong variant number, missing parent)
* chore: merge upstream/main and bump plugin manifests to 2.6.7
Merge upstream/main (commit 4faae66 — adversarial tests for
verify-semantic-motion.py and verify-sequence-oauth.py, CI wiring,
CONTRIBUTING gate table update) cleanly into this branch.
Upstream had already bumped manifests to 2.6.6; re-bump to 2.6.7 so
this branch remains strictly above main after merging the
parent/variant eyebrow changes added in the previous commit.
* 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>
* feat(scatter): add bubble variant for three-value comparisons
Bubble chart as a Scatter variant per the issue #62 triage: x and y on
shared zero-based linear scales, bubble AREA proportional to the third
value (r = K*sqrt(size)), one accent bubble, ink-opacity ramp as ink-mass
compensation, largest-first paint order.
scripts/verify-bubble.py derives both axis scales and the area constant
from the set itself (Theil-Sen, leave-one-out) and holds every bound
label and tick against the mark it describes; test-verify-bubble.py
exercises both polarities. Bindings are bubble-specific (data-size,
data-name, data-tick) rather than data-series, so verify-slopegraph.py
never claims a bubble file and neither checker needs scope carve-outs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(bubble): report a bubble whose peers cannot describe a scale
A bubble holding the only distinct value on an axis was skipped by the
leave-one-out residual test — its own fit is degenerate, and the full-set
fit passes through wherever it was drawn, so a wrong coordinate shipped
clean. Report it as unverifiable instead of skipping it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: bump to 2.6.3 and register bubble gates
Ridgeline (#143) landed on 2.6.2, colliding with this branch's bump, so
re-bump past it. Also add the bubble gate commands to the maintainer
policy's local_commands, matching what ridgeline registered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: bump to 2.6.5
The mermaid fix (#131) landed on 2.6.4, colliding with this branch's
bump again. Re-bump past it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: 0xDarkMatter <0xDarkMatter@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* 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>
* fix(commands): stop hardcoding the visual-type count
Integrates the final reviewed diff from PR #112.
* docs(high-level): correct effective width checklist
Integrates the final reviewed diff from PR #105.
* fix(commands): quote profile argument hint
Integrates the final reviewed diff from PR #126.
* test(sankey): gate conservation and ribbon geometry
Integrates the final reviewed diff from PR #103.
* feat(tooling): add doctor diagnostics gate
Integrates the final reviewed diff from PR #132.
* feat(scripts): lint diagrams as rendered
Integrates the final reviewed diff from PR #18.
* feat(types): add quantitative polar chart
Integrates the final reviewed diff from PR #101.
* chore(release): consolidate reviewed changes in 2.6.0
Documents every combined gate and synchronizes Claude, Codex, and Factory metadata for the 39-type release.
* fix(doctor): support installed-skill projects
---------
Co-authored-by: mysoniw <soniw1@gmail.com>
Co-authored-by: thang <76550342+thangldw@users.noreply.github.com>
Co-authored-by: YinShaokun <yinshaokun@didiglobal.com>
Co-authored-by: 0xDarkMatter <0xDarkMatter@users.noreply.github.com>
Co-authored-by: Rupam Pal <rupam.pal@euromonitor.com>
Co-authored-by: Yuvraj3905 <yuvraj202001@gmail.com>
* fix(treemap): verify every cell's area, including cells too small to label
Closes#89.
The area check derived its basis from in-cell labels, so a cell with no
label was excluded from it. That is precisely the sliver -- the cell a
4px grid distorts most, and the one that shipped 24% undersized during
review of #87. On current main, widening Oceania from 16px to 24px, a
50% area distortion, still reports OK.
Every cell now declares `data-share`, its percentage of the whole, and
the verifier checks all of them against it. Cells without it are
reported rather than skipped: a treemap whose cells cannot be parsed is
not a passing treemap, it is an unchecked one, and answering OK to it is
the defect. The label and the metadata are also cross-checked, because
two statements of one fact is a new way for the picture to lie while the
gate stays green.
Adversarial tests gain the oversized and undersized unlabelled sliver
(both pass on current main and fail here), a fail-closed case for
missing metadata, and a label/metadata divergence case.
SKILL.md `metadata.version` said 2.4 against 2.5.0 manifests. The
manifests are bumped by a script and that field is hand-maintained, so
nothing read both numbers and nothing could notice. verify-plugin-package
now asserts it tracks the manifest MAJOR.MINOR. The package test's
fixture gains the field with it -- a SKILL.md without one is not a valid
package, and a fixture missing it would only prove the gate can be
tripped by its own test data.
The examples regained the `PARTS ROUNDED, MAY NOT SUM` disclosure the
type reference requires: displayed parts round up to 8.10B against a
stated 8.09B. It was dropped when the examples were rebuilt around the
legend block. The anti-pattern list, which still forbade outright what
the honest-data rule permits once disclosed, now agrees with it.
Also nudges the info mark onto the label row of its neighbours rather
than the vertical centre of its own cell, so the bottom row reads as one
row.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(treemap): reject ambiguous share metadata
---------
Co-authored-by: 0xDarkMatter <0xDarkMatter@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(lint): enforce viewBox presence and title length cap
- Add a11y check: non-decorative <svg> must carry a viewBox attribute
with exactly four numeric tokens and positive width/height dimensions.
- Add a11y check: <title> text must be 60 characters or fewer, closing
the gap between the CONTRIBUTING.md spec and linter enforcement.
- Update VALID_SVG in test-lint-a11y.py to include viewBox so all
existing passing cases remain valid after the new checks land.
- Add three adversarial cases: missing-viewbox, invalid-viewbox,
title-too-long — all verified against the gate suite (47/47 OK).
* fix(lint): accept SVG comma separators and reject non-finite viewBox values
- Re-split viewBox on [\s,]+ so comma-separated values (e.g. 0,0,800,600)
are accepted as valid SVG
- Guard each component with math.isfinite() so values like inf and nan are
rejected rather than passing the > 0 size check
- Add adversarial test cases: viewbox-comma-separated (pass),
viewbox-nonfinite-width, viewbox-nonfinite-height (both fail)
- Bump plugin manifests to 2.4.1
* fix(lint): reject viewBox numbers with Python-only underscore separators
* test(lint): cover remaining viewBox cases
---------
Co-authored-by: Rupam Pal <rupam.pal@euromonitor.com>
Adds ten editorial diagram types with light, dark, and full-editorial examples; updates the taxonomy, gallery, descriptions, references, and synchronized plugin version; and adds maintainer/visual-proof contribution gates.
Co-authored-by: Jacopo Vendramin <vendraminjacopo@gmail.com>
Documents the dumbbell in type-bar.md's existing Variants section,
alongside grouped and stacked bars. Reference-only, matching the
precedent those two set and the one #100 landed for datalake: no
example assets, no gallery entries.
Not a new visual type. The count stays at 28 and SKILL.md is untouched
-- no selection-table row, no frontmatter hook, no manifest description
change.
Covers layout on the shared 1000x500 composition, an element pattern,
and five honesty rules: no truncated axis, one scale with both endpoints
labelled, the connector is a gap and not a trajectory, a stated row
order, and no imputed or silently dropped endpoints.
Two of those rules live in a formula rather than a drawing, so they ship
as a checker with adversarial fixtures per ADR 0005:
- Domain resolution is exhaustive over sign. A rule phrased as
"all-positive / all-negative" silently drops data that only touches
zero, and drops all-zero data entirely -- where floor == ceil == 0 and
the position formula divides by zero. resolve_domain() partitions the
space, guarantees ceil > floor, and returns a finite fallback span for
all-zero input so every dot lands on the floor.
- Non-text contrast is carried by the boundary, not the fill. Accent on
paper is 2.86:1 skin-wide, and shape redundancy does not waive WCAG
1.4.11, so the solid endpoint takes a 1px ink stroke (11.82:1) and the
connector 55% ink on light / 40% on dark (3.19:1 and 3.24:1). The
checker reads these tokens out of the reference, so prose and
thresholds cannot drift apart.
scripts/test-verify-dumbbell.py covers both polarities: all-zero,
zero-touching, mixed-sign, empty and non-finite input, observed-extremes
truncation, and the sub-3:1 treatments this replaces. Wired into CI and
CONTRIBUTING alongside the treemap and geometry gates.
Other things the geometry and house style forced: the focal bar pattern
(12% tint plus accent stroke) does not survive at a 6px dot, so the pair
separates by fill weight; a 64px row pitch from y=96 overflows the y=420
plot floor at seven rows, so pitch and origin are tabulated per row
count; rgb(45,49,66) IS the dark paper, so light-theme hairlines
composite to 1.000:1 and must invert; label offsets keyed to start/end
put both labels inside the pair on a decreasing row; a value on the
domain floor collided with its category label; and the value axis was
missing the Geist Mono 7px title every other cartesian type carries.
Co-authored-by: 0xDarkMatter <0xDarkMatter@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Added descriptions for the shipped datalake examples (example-datalake.html,
-datalake-dark.html, -datalake-full.html) to the High-Level type reference,
categorising the five-phase data stack as an unclustered High-Level variant.
Fold all line-boundary forms and escape Markdown/HTML syntax in draw.io digest headings, lists, and tables; add adversarial injection coverage; publish synchronized plugin version 2.5.1. Fixes#81.