159 Commits

Author SHA1 Message Date
github-actions[bot] 8d8b2993ee chore(release): bump plugin manifests to 2.6.22 2026-09-10 22:33:53 +00:00
Yang Fan 5fb8a197f2 fix: keep plugin descriptions within the Cowork limit (#216)
Co-authored-by: Yang Fan <266078689+yangfan-yf-yf@users.noreply.github.com>
2026-09-10 17:33:23 -05:00
Brady Gaster fc9c0be3ad docs: add GitHub Copilot marketplace install (#218)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-09-10 17:33:07 -05:00
github-actions[bot] 562dbdf93f chore(release): bump plugin manifests to 2.6.21 2026-09-10 04:26:29 +00:00
Leo Camus 3a1a7e09e8 fix(doctor): fall through a python3 that cannot report its version (#201)
* 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>
2026-09-09 23:26:05 -05:00
github-actions[bot] 3392191f69 chore(release): bump plugin manifests to 2.6.20 2026-09-10 04:15:02 +00:00
manojp99 8bb6fff4ea fix(self-check): reject CSS asset loading (#205)
* fix(self-check): reject CSS asset loading

* fix(self-check): normalize CSS continuations

---------

Co-authored-by: Manoj Prabhakar Paidiparthy <mpaidiparthy@microsoft.com>
Co-authored-by: Cathryn Lavery <50469282+cathrynlavery@users.noreply.github.com>
2026-09-09 23:14:38 -05:00
github-actions[bot] 5d59b9b6bd chore(release): bump plugin manifests to 2.6.19 2026-09-10 00:32:24 +00:00
Cathryn Lavery 5f993e008c fix(ci): detect real plugin version bumps (#207)
* Fix auto-bump version history detection

* Test desynchronized version history
2026-09-09 19:31:54 -05:00
Matt Van Horn 899579b459 feat: import Excalidraw diagrams for editorial redraw (#192)
* 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>
2026-09-09 19:03:15 -05:00
Matt Van Horn 48fecccda7 feat: add a waterfall chart visual type (#191)
* 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>
2026-09-09 18:43:12 -05:00
github-actions[bot] dcd9317ed9 chore(release): bump plugin manifests to 2.6.18 2026-09-08 19:45:12 +00:00
Jenil Ghevariya b119771a7b fix(export): normalize rgba()/transparent for PowerPoint's SVG importer (#151)
* 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>
2026-09-08 14:44:31 -05:00
github-actions[bot] 2724fd2efd chore(release): bump plugin manifests to 2.6.17 2026-09-07 04:39:10 +00:00
Zachary Halberd d0a0f46424 feat(patterns): add traceable block decomposition semantic pattern (#169)
* 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>
2026-09-06 23:38:49 -05:00
github-actions[bot] 2eb09193a9 chore(release): bump plugin manifests to 2.6.16 2026-09-07 04:22:12 +00:00
HunterKK1424 f8c7523fbf Request the CJK faces in the exported SVG @import, and gate the parity (#196)
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 &amp; 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>
2026-09-06 23:21:48 -05:00
github-actions[bot] 3b446333f1 chore(release): bump plugin manifests to 2.6.15 2026-09-06 21:40:42 +00:00
Oscar Madera 9324402056 fix(docs): drop non-existent extended asset citations and enforce asset link integrity (#185) (#193)
* 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>
2026-09-06 16:40:19 -05:00
github-actions[bot] 013c1d52e6 chore(release): bump plugin manifests to 2.6.14 2026-09-06 21:26:10 +00:00
HunterKK1424 8ed7724dbe Ship Noto TC faces and add a Traditional Chinese labels section (#186)
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>
2026-09-06 16:25:47 -05:00
github-actions[bot] 42a907b068 chore(release): bump plugin manifests to 2.6.13 2026-09-06 21:13:23 +00:00
Cathryn Lavery 66ab67ecc2 fix(ci): allow patch bumps without skill metadata changes (#194) 2026-09-06 15:59:13 -05:00
Wu Shuwen 55fbd0b27d docs(readme): correct shipped icon count (#180) 2026-09-06 15:34:23 -05:00
Cathryn Lavery 4451eadc48 fix: consolidate reviewed repository corrections (#174) 2026-09-02 19:53:29 -07:00
Cathryn Lavery 8d55bd42ad ci(release): bump plugin versions on main after merge, not in PRs (#172)
* 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
2026-09-01 20:56:55 -05:00
chck 75f2d5e1d6 fix(doctor): quote argument-hint so frontmatter is valid YAML (#155)
Co-authored-by: Claude <noreply@anthropic.com>
2026-09-01 07:57:21 -07:00
Cathryn Lavery b52a33bfee feat(charts): consolidate reviewed variants and hardening (#160) 2026-08-29 17:21:26 -07:00
Rupam Pal ac490fd1ac fix(gallery): deduplicate slopegraph eyebrow number and strengthen sync verifier (#136)
* 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.
2026-08-27 11:58:04 -05:00
许文涛 4faae6696c 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
0xDarkMatter 648c2a5978 feat(scatter): add bubble variant for three-value comparisons (#141)
* 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>
2026-08-21 17:26:23 -05:00
CHACON MONDRAGON c79585edb4 fix(mermaid): parse compact multidirectional labels (#131)
* fix(mermaid): parse compact multidirectional labels

* fix(mermaid): preserve source ids ending in markers

* chore: bump plugin version to 2.6.2

* chore: bump plugin version to 2.6.4

* fix: preserve single-character Mermaid source IDs

* fix: preserve chained Mermaid marker IDs

---------

Co-authored-by: Cathryn Lavery <cathryn@bestself.co>
2026-08-21 14:31:30 -05:00
0xDarkMatter ddaeae936e feat(line): add ridgeline as a Line variant (#143)
* 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>
2026-08-21 12:23:44 -05:00
Cathryn Lavery 5538b35116 fix(gallery): preserve mobile preview height (#135)
* fix(gallery): preserve mobile preview height

* test(gallery): report routing failures directly
2026-08-20 12:42:26 -07:00
Cathryn Lavery b827385a17 feat: consolidate reviewed Diagram Design 2.6.0 release (#134)
* 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>
2026-08-20 12:21:43 -07:00
Cathryn Lavery 4b573616cb ci: upgrade actions to Node 24 runtimes (#128) 2026-08-19 20:45:54 -07:00
Cathryn Lavery b132fa5d7a fix(packaging): make skill references bundle-safe (#125)
* fix(packaging): make skill references bundle-safe

* test(packaging): normalize scanner paths portably

* fix(packaging): bundle required runtime helpers
2026-08-19 20:31:56 -07:00
Cathryn Lavery 0487290d5a fix(treemap): close post-merge fail-open gaps (#127)
* fix(treemap): close fail-open metadata gaps

* chore: bump plugin version to 2.5.18

* fix(treemap): validate declared cell geometry

* fix(package): validate YAML escape sequences

* fix(package): bound Unicode YAML escapes
2026-08-19 20:25:28 -07:00
Cathryn Lavery e011b04af8 fix(treemap): verify every cell share (#124)
* 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>
2026-08-19 22:02:02 -05:00
Cathryn Lavery 91fb4f243b fix(lint): enforce accessible SVG viewBox and title rules (#123)
* 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>
2026-08-19 19:53:43 -07:00
Cathryn Lavery cc2f51f3fd fix(import): emit extractor output as UTF-8 on Windows (#122)
* fix(import): emit extractor digests as UTF-8

* fix(import): limit UTF-8 setup to CLI entrypoints

* test(import): normalize cross-platform newline parity
2026-08-19 17:49:34 -07:00
Cathryn Lavery 8154ec97bf feat(factory): add native Droid plugin packaging (#120)
* feat(factory): add native Droid plugin packaging

* test(factory): guard documented install contract

* fix(factory): enforce install command order
2026-08-19 17:46:46 -07:00
Cathryn Lavery bf49fb408a fix(treemap): fail closed for CJK label widths (#121)
* Fix CJK treemap label width verification

* Strengthen CJK treemap width regressions
2026-08-19 19:35:06 -05:00
Cathryn Lavery e8b7d7ae62 docs(gallery): refresh canonical 38-type visual catalog (#119)
* docs(gallery): refresh canonical visual catalog

* docs: document screenshot renderer setup
2026-08-19 18:47:20 -05:00
Cathryn Lavery 4691a2f40c feat(types): ship ten editorial diagram types in one release (#118)
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>
2026-08-19 15:49:27 -07:00
0xDarkMatter 5f1b6ddc66 feat(line): add slopegraph variant for change between two states (#106) 2026-08-18 22:31:48 -07:00
0xDarkMatter 29917724f8 feat(bar): add dumbbell as a Bar variant (#107)
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>
2026-08-18 18:49:39 -05:00
Cathryn Lavery 0947fdb6e5 fix(drawio): keep import command reference in sync (#109) 2026-08-18 12:38:57 -05:00
Vitalii Filiuchkov 7942fa0227 docs(high-level): register datalake examples as unclustered High-Level variants (fixes #55) (#100)
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.
2026-08-18 08:49:01 -07:00
Cathryn Lavery 9ec2497b85 fix(drawio): escape untrusted Markdown digest labels (#91)
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.
2026-08-18 01:12:04 -07:00