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>
This commit is contained in:
Cathryn Lavery
2026-08-20 12:21:43 -07:00
committed by GitHub
co-authored by mysoniw thang YinShaokun 0xDarkMatter Rupam Pal Yuvraj3905
parent 4b573616cb
commit b827385a17
42 changed files with 7090 additions and 130 deletions
+132 -19
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Verify that routing and browsing surfaces stay in sync with the skill.
Eight drift classes, each of which has shipped before:
Ten drift classes, each of which has shipped before:
1. The SKILL.md frontmatter description is the only text an agent sees before
deciding to load the skill — every visual type in the selection table must
@@ -10,7 +10,7 @@ Eight drift classes, each of which has shipped before:
gallery tab must point at a file that exists.
3. Every concrete file named in README.md's architecture tree must exist.
4. Every relative references/*.md link in SKILL.md must resolve.
5. Claude and Pi profile surfaces must both route to the profile reference.
5. Claude and Pi command/prompt surfaces must route to the matching reference.
6. The plugin manifests repeat the SKILL.md description verbatim. They are the
text a user reads *before installing*, so by ADR 0004's own argument they
need every type's lexical hook too - and nothing else notices when they
@@ -19,6 +19,10 @@ Eight drift classes, each of which has shipped before:
with the package metadata instead of becoming a second hand-maintained API.
8. Every support path a strict skill bundler can extract from SKILL.md must be
a literal file shipped inside the skill package.
9. Import command surfaces must route to the visual-type taxonomy instead of
hardcoding a count that becomes stale when a type is added.
10. The High-Level reproducibility checklist must agree with its canvas formula
and retain sequential numbering.
"""
from __future__ import annotations
@@ -34,17 +38,26 @@ SKILL = ROOT / "skills/diagram-design/SKILL.md"
GALLERY = ROOT / "skills/diagram-design/assets/index.html"
ASSET_DIR = ROOT / "skills/diagram-design/assets"
README = ROOT / "README.md"
HIGH_LEVEL_REFERENCE = ROOT / "skills/diagram-design/references/type-high-level.md"
VARIANTS = ("", "-dark", "-full")
VISUAL_TYPE_COUNT = 39
# Types whose selection-table name differs from its description vocabulary.
DESCRIPTION_ALIASES = {
"bar chart": "bar",
"line chart": "line",
"scatter plot": "scatter",
}
PROFILE_SURFACES = (
Path("commands/profile.md"),
Path("prompts/profile.md"),
)
ROUTING_SURFACES = {
Path("commands/export-diagram.md"): "references/export.md",
Path("commands/import-drawio.md"): "references/import-drawio.md",
Path("commands/import-mermaid.md"): "references/import-mermaid.md",
Path("commands/profile.md"): "references/profiles.md",
Path("commands/doctor.md"): "references/doctor.md",
Path("prompts/export-diagram.md"): "references/export.md",
Path("prompts/import-mermaid.md"): "references/import-mermaid.md",
Path("prompts/profile.md"): "references/profiles.md",
Path("prompts/doctor.md"): "references/doctor.md",
}
FACTORY_MANIFEST = Path(".factory-plugin/plugin.json")
FACTORY_MARKETPLACE = Path(".factory-plugin/marketplace.json")
SUPPORT_DIRECTORIES = frozenset(
@@ -102,8 +115,10 @@ def check_description(errors: list[str]) -> None:
errors.append("SKILL.md frontmatter description is missing")
return
types = selection_table_types(markdown)
if len(types) != 38:
errors.append(f"expected 38 visual types in the selection table; found {len(types)}")
if len(types) != VISUAL_TYPE_COUNT:
errors.append(
f"expected {VISUAL_TYPE_COUNT} visual types in the selection table; found {len(types)}"
)
for name in types:
key = normalized(name)
key = DESCRIPTION_ALIASES.get(key, key)
@@ -210,18 +225,18 @@ def check_packaged_support_references(
)
def check_profile_surfaces(errors: list[str], root: Path) -> None:
reference = root / "skills/diagram-design/references/profiles.md"
if not reference.is_file():
errors.append("profile source of truth is missing: skills/diagram-design/references/profiles.md")
for relative in PROFILE_SURFACES:
def check_routing_surfaces(errors: list[str], root: Path) -> None:
for relative, reference_link in ROUTING_SURFACES.items():
reference = root / "skills/diagram-design" / reference_link
if not reference.is_file():
errors.append(f"routing source of truth is missing: skills/diagram-design/{reference_link}")
path = root / relative
if not path.is_file():
errors.append(f"profile surface is missing: {relative.as_posix()}")
errors.append(f"routing surface is missing: {relative.as_posix()}")
continue
if "references/profiles.md" not in path.read_text(encoding="utf-8"):
if reference_link not in path.read_text(encoding="utf-8"):
errors.append(
f"profile surface does not route to references/profiles.md: {relative.as_posix()}"
f"routing surface does not route to {reference_link}: {relative.as_posix()}"
)
@@ -270,6 +285,102 @@ def check_factory_install_surface(errors: list[str], root: Path) -> None:
)
# A command that spells the type count out has to be edited by every PR that
# adds a type, and is the one file such a PR has no reason to open. Both import
# commands were left at 27 while the selection table moved on.
# The phrasing varies, so match the count rather than the one sentence it went
# stale in. Two forms carry it: the bare count standing in for the table
# (`one of the 27`), and a count attached to the taxonomy noun with room for
# adjectives between, in either order (`28 visual types`, `28 supported visual
# diagram types`, `28 types of visual diagrams`). Those clauses insist on that
# noun so an unrelated quantity — `accepts 2 file types` — is not rejected by a
# gate about the visual taxonomy.
#
# Every gap is whitespace-tolerant because both commands already wrap the
# sentence that carried the stale count, so a count can land just after the
# wrap. That is why the whole file is searched at once and the line is derived
# from the match offset rather than iterating lines.
#
# Word-form numerals (`Twenty-eight visual types`) are out of scope; README and
# the docstring say "numeral" so the gate does not claim more than it checks.
HARDCODED_COUNT_RE = re.compile(
r"one\s+of\s+(?:the\s+)?\d+\b"
r"|\b\d+\s+(?:[\w-]+\s+){0,2}?(?:visual|diagram)[\s-]+types?\b"
r"|\b\d+\s+types?\s+of\s+(?:[\w-]+\s+){0,2}?diagrams?\b",
re.IGNORECASE,
)
COUNT_SURFACES = (
Path("commands/import-drawio.md"),
Path("commands/import-mermaid.md"),
)
def check_type_counts(errors: list[str], root: Path) -> None:
"""No routing surface may write the visual-type count as a numeral."""
for relative in COUNT_SURFACES:
path = root / relative
if not path.is_file():
errors.append(f"type-count surface is missing: {relative.as_posix()}")
continue
text = path.read_text(encoding="utf-8")
for match in HARDCODED_COUNT_RE.finditer(text):
number = text.count("\n", 0, match.start()) + 1
phrase = " ".join(match.group(0).split())
errors.append(
f"{relative.as_posix()}:{number} hardcodes the visual-type count "
f"({phrase!r}); point at SKILL.md \u00a73 instead so adding a type "
f"cannot leave it stale"
)
def check_high_level_reference(errors: list[str], markdown: str) -> None:
width_formula = re.search(
r"^effective_w\s*=\s*(\d+)\s*-\s*right_strip_w\s*-\s*strip_margin",
markdown,
re.MULTILINE,
)
right_strip = re.search(r"^right_strip_w\s*=\s*(\d+)\s+if", markdown, re.MULTILINE)
strip_margin = re.search(r"^strip_margin\s*=\s*(\d+)\s+if", markdown, re.MULTILINE)
if not all((width_formula, right_strip, strip_margin)):
errors.append("High-Level canvas is missing the effective-width formula")
return
checklist = re.search(
r"^## 7\. Reproducibility checklist[^\n]*\n(.*?)(?=^## |\Z)",
markdown,
re.MULTILINE | re.DOTALL,
)
if checklist is None:
errors.append("High-Level reproducibility checklist is missing")
return
items = re.findall(r"^(\d+)\.\s+(.+)$", checklist.group(1), re.MULTILINE)
numbers = [int(number) for number, _ in items]
expected_numbers = list(range(1, len(numbers) + 1))
if numbers != expected_numbers:
rendered = ",".join(str(number) for number in numbers)
errors.append(
"High-Level reproducibility checklist numbering is not sequential: "
f"expected 1..{len(numbers)}, found {rendered}"
)
item_three = next((text for number, text in items if number == "3"), "")
checklist_width = re.search(r"effective_w\s*=\s*(\d+)", item_three)
expected_width = (
int(width_formula.group(1))
- int(right_strip.group(1))
- int(strip_margin.group(1))
)
if checklist_width is None:
errors.append("High-Level checklist item 3 is missing effective_w")
elif int(checklist_width.group(1)) != expected_width:
errors.append(
f"High-Level checklist item 3 has effective_w={checklist_width.group(1)}; "
f"expected {width_formula.group(1)} - {right_strip.group(1)} - "
f"{strip_margin.group(1)} = {expected_width}"
)
MANIFEST_DESCRIPTIONS = (
(Path(".claude-plugin/plugin.json"), ("description",)),
(Path(".claude-plugin/marketplace.json"), ("description",)),
@@ -340,7 +451,9 @@ def main() -> int:
SKILL.read_text(encoding="utf-8"),
SKILL.parent,
)
check_profile_surfaces(errors, ROOT)
check_type_counts(errors, ROOT)
check_high_level_reference(errors, HIGH_LEVEL_REFERENCE.read_text(encoding="utf-8"))
check_routing_surfaces(errors, ROOT)
if errors:
print("FAIL docs sync")
for error in errors:
@@ -348,8 +461,8 @@ def main() -> int:
return 1
print(
"OK docs sync: description hooks, gallery reachability, README tree, "
"reference links, packaged support files, profile surfaces, manifest descriptions, "
"Factory install contract"
"reference links, packaged support files, routing surfaces, manifest descriptions, "
"Factory install contract, type-count routing, High-Level invariants"
)
return 0