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>
This commit is contained in:
co-authored by
0xDarkMatter
Claude Fable 5
parent
91fb4f243b
commit
e011b04af8
@@ -280,6 +280,83 @@ def verify_codex_skill_path(root: Path, codex_manifest: dict[str, Any], errors:
|
||||
errors.append(f"Codex skills path does not contain {PLUGIN_NAME}/SKILL.md")
|
||||
|
||||
|
||||
def parse_frontmatter_metadata_version(text: str) -> str | None:
|
||||
"""Return exactly one direct ``metadata.version`` from YAML frontmatter.
|
||||
|
||||
This intentionally does not search the Markdown body: examples and prose
|
||||
are allowed to mention ``version:``, but they cannot validate package
|
||||
metadata. The small parser is limited to the mapping shape this skill owns
|
||||
and rejects duplicate or nested version keys instead of guessing.
|
||||
"""
|
||||
lines = text.splitlines()
|
||||
if not lines or lines[0].strip() != "---":
|
||||
return None
|
||||
try:
|
||||
closing = next(
|
||||
index for index in range(1, len(lines)) if lines[index].strip() == "---"
|
||||
)
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
frontmatter = lines[1:closing]
|
||||
metadata_rows = [
|
||||
index
|
||||
for index, line in enumerate(frontmatter)
|
||||
if re.fullmatch(r"metadata:\s*(?:#.*)?", line)
|
||||
]
|
||||
if len(metadata_rows) != 1:
|
||||
return None
|
||||
|
||||
start = metadata_rows[0] + 1
|
||||
block: list[str] = []
|
||||
for line in frontmatter[start:]:
|
||||
if line and not line[0].isspace():
|
||||
break
|
||||
block.append(line)
|
||||
|
||||
content = [line for line in block if line.strip() and not line.lstrip().startswith("#")]
|
||||
if not content:
|
||||
return None
|
||||
direct_indent = min(len(line) - len(line.lstrip()) for line in content)
|
||||
versions: list[str] = []
|
||||
for line in content:
|
||||
indent = len(line) - len(line.lstrip())
|
||||
if indent != direct_indent:
|
||||
continue
|
||||
match = re.fullmatch(
|
||||
r"\s*version:\s*(?:\"([^\"]+)\"|'([^']+)'|([0-9]+(?:\.[0-9]+)*))\s*(?:#.*)?",
|
||||
line,
|
||||
)
|
||||
if match is not None:
|
||||
versions.append(next(value for value in match.groups() if value is not None))
|
||||
return versions[0] if len(versions) == 1 else None
|
||||
|
||||
|
||||
def verify_skill_metadata_version(root: Path, manifests: dict[str, dict[str, Any]], errors: list[str]) -> None:
|
||||
"""SKILL.md's metadata version must track the manifests' MAJOR.MINOR.
|
||||
|
||||
The manifests are bumped by a script; SKILL.md's `metadata.version` is
|
||||
hand-maintained, so a minor release drifts silently - 2.4 sat against
|
||||
2.5.0 manifests until a post-merge sweep caught it. Nothing else reads
|
||||
both numbers, so nothing else can notice.
|
||||
"""
|
||||
skill = root / "skills" / PLUGIN_NAME / "SKILL.md"
|
||||
if not skill.is_file():
|
||||
errors.append(f"missing {skill.relative_to(root).as_posix()}")
|
||||
return
|
||||
declared = parse_frontmatter_metadata_version(skill.read_text(encoding="utf-8"))
|
||||
if declared is None:
|
||||
errors.append("SKILL.md frontmatter has no metadata.version")
|
||||
return
|
||||
manifest_version = str(manifests["Claude"].get("version", ""))
|
||||
expected = ".".join(manifest_version.split(".")[:2])
|
||||
if declared != expected:
|
||||
errors.append(
|
||||
f"SKILL.md metadata.version {declared!r} must track the manifest "
|
||||
f"MAJOR.MINOR {expected!r} (manifests are at {manifest_version})"
|
||||
)
|
||||
|
||||
|
||||
def verify_package(root: Path, base_ref: str) -> list[str]:
|
||||
errors: list[str] = []
|
||||
manifests: dict[str, dict[str, Any]] = {}
|
||||
@@ -291,6 +368,7 @@ def verify_package(root: Path, base_ref: str) -> list[str]:
|
||||
if len(manifests) == len(MANIFEST_PATHS):
|
||||
verify_versions(root, base_ref, manifests, errors)
|
||||
verify_manifest_identity(manifests, errors)
|
||||
verify_skill_metadata_version(root, manifests, errors)
|
||||
verify_codex_skill_path(root, manifests["Codex"], errors)
|
||||
verify_marketplaces(root, errors)
|
||||
return errors
|
||||
|
||||
Reference in New Issue
Block a user