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:
co-authored by
mysoniw
thang
YinShaokun
0xDarkMatter
Rupam Pal
Yuvraj3905
parent
4b573616cb
commit
b827385a17
+132
-19
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user