Add design conformance counter, score TODO

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWXTUM9dpnCS2wtFkx4
This commit is contained in:
Krzysztof Rudnicki 2026-11-01 19:05:04 +01:00
parent 5be9a22970
commit 254c8ce240
7 changed files with 508 additions and 2 deletions

View File

@ -11,8 +11,8 @@ overwrite it with `repo_contract/templates/check_repo_contract.shim.sh`.
## Commands
- run: n/a: collection of gates and libraries, no single entry point
- test: `PYTHONPATH=. python -m pytest -q agent_ready/tests coverage_gaps/tests dep_freshness/tests file_length/tests flutter_web_drive/tests home_paths/tests md_naming/tests repo_contract/tests scripts/tests`
- test: `PYTHONPATH=. python -m pytest -q agent_ready/tests coverage_gaps/tests dep_freshness/tests file_length/tests flutter_web_drive/tests home_paths/tests md_naming/tests repo_contract/tests scripts/tests unified-design-system/tests`
- test-changed: `scripts/test_changed.sh`
- lint: `ruff check agent_ready coverage_gaps flutter_web_drive repo_contract`
- coverage: `PYTHONPATH=. python -m pytest -q agent_ready/tests flutter_web_drive/tests repo_contract/tests --cov=agent_ready --cov=flutter_web_drive --cov=repo_contract --cov-branch --cov-report=xml --cov-fail-under=100`
- coverage: `PYTHONPATH=. python -m pytest -q agent_ready/tests flutter_web_drive/tests repo_contract/tests unified-design-system/tests --cov=agent_ready --cov=flutter_web_drive --cov=repo_contract --cov=conformance --cov=conformance_diff --cov-branch --cov-report=xml --cov-fail-under=100`
- coverage-gaps: `coverage-gaps coverage.xml`

View File

@ -0,0 +1,31 @@
# Add a plain-text multi-voice score format
## what
Add a plain-text score format that `compose.py` and `sequencer.py` can
render: named voices, a noise-drum voice, a `meter` field (3/4, 6/8) and
A/B sections. Today `sequencer.BEATS_PER_BAR` is fixed at 4 and a `Recipe`
has one bass, one arpeggio and one melody.
Source: simonwillison.net/2026/Oct/6/scrimshaw-jukebox.
## done
```
cd ~/src/utils/music_theory && PYTHONPATH=. python -m pytest music_theory/tests/test_score.py music_theory/tests/test_sequencer.py music_theory/tests/test_compose.py --cov=music_theory.score --cov=music_theory.sequencer --cov=music_theory.compose --cov-branch --cov-fail-under=100
```
exits 0. It covers: a 3/4 and a 6/8 score render bar-exact loops, two named
voices stack, the noise-drum voice is deterministic for a fixed seed, and an
A/B-section score renders A then B in order.
## verify
Desktop. Render one 3/4 and one 6/8 example score with
`PYTHONPATH=. python -m music_theory render --score examples/waltz.txt --out /tmp/waltz.wav`,
check `analysis.dominant_frequency` and the frame count equal
`sequencer.loop_frames` for the bar count, and re-render for byte-identical
output. The final listen is kuhy's.
## read first
- `music_theory/compose.py` (`Recipe`), `music_theory/sequencer.py`
(`NoteEvent`, `BEATS_PER_BAR`), `music_theory/noise.py` (drum source).
- `DOCS-article-walkthrough.md` (article section 10, notation as format).
- `~/.claude/skills/procedural-music/SKILL.md`.
REMOVE ME AFTER FINISH

View File

@ -0,0 +1,206 @@
"""Design-token conformance per UI surface: tokens used vs raw literals.
Install counts and component counts are vanity metrics. What shows whether a
design system is working is how often a surface reaches for a token versus a
hard-coded value (learn.thedesignsystem.guide, "Half my design system metrics
don't work anymore"). This script counts, per UI surface of one repo:
* token usages: ``var(--x)`` in CSS/TS/TSX/Astro, ``AppPalette.x`` style
constants and ``Theme.of`` / ``colorScheme`` / ``textTheme`` in Dart
* raw literals: hex colours (``#rrggbb``, ``0xAARRGGBB``) and non-zero
``px`` / ``pt`` numbers, outside the token files
A surface is the nearest ancestor directory holding a manifest
(``pubspec.yaml``, ``package.json``, ``astro.config.*``), else the repo root.
Token files (tokens/theme/palette/colors), custom-property definitions,
comments, tests, ``@media`` lines and generated/build dirs are skipped.
The counts are a heuristic trend signal, not an audit.
Run: python3 conformance.py <repo> [--json] [--previous <earlier.json>]
Exit 0 = report printed; 2 = bad arguments or unreadable previous file.
"""
from __future__ import annotations
import argparse
import json
import os
import re
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from conformance_diff import delta, load_previous, table
Json = dict[str, Any]
EXTENSIONS = frozenset({".dart", ".css", ".ts", ".tsx", ".astro"})
SKIP_DIRS = frozenset({
".git", "node_modules", "build", "dist", ".dart_tool", ".astro", ".next",
"coverage", "generated", "__pycache__", ".venv", "test", "tests",
"__tests__", ".fvm", ".pub-cache",
})
SKIP_NAME = re.compile(
r"(\.g\.dart|\.freezed\.dart|\.d\.ts|\.test\.[jt]sx?|_test\.dart)$"
)
TOKEN_FILE = re.compile(r"^_?(tokens?|theme|palette|colou?rs?)\b", re.IGNORECASE)
MANIFESTS = ("pubspec.yaml", "package.json")
MAX_BYTES = 1_000_000
BLOCK_COMMENT = re.compile(r"/\*.*?\*/", re.DOTALL)
LINE_COMMENT = re.compile(r"(?<![:\w])//.*")
TOKEN_DEFINITION = re.compile(r"--[\w-]+\s*:[^;}]*;?")
MEDIA_LINE = re.compile(r"@(media|container)\b")
HEX = re.compile(
r"#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})\b|\b0x[0-9A-Fa-f]{8}\b"
)
UNIT = re.compile(r"(?<![\w.#-])(?!0+(?:\.0+)?(?:px|pt)\b)\d+(?:\.\d+)?(?:px|pt)\b")
TOKEN_USE = re.compile(
r"var\(--|\bApp(?:Palette|Spacing|Radius|TextSize|Duration|Curve)\.\w+"
r"|\bTheme\.of\(|\bcolorScheme\.|\btextTheme\."
)
@dataclass
class Surface:
"""Counts for one UI surface."""
name: str
files: int = 0
token_usages: int = 0
raw_literals: int = 0
@property
def conformance(self) -> float | None:
"""Percent of style values that are tokens, or None with no values."""
total = self.token_usages + self.raw_literals
if total == 0:
return None
return round(100.0 * self.token_usages / total, 1)
def as_dict(self) -> Json:
"""JSON-ready form."""
return {
"name": self.name,
"files": self.files,
"token_usages": self.token_usages,
"raw_literals": self.raw_literals,
"conformance": self.conformance,
}
def strip_comments(text: str, suffix: str) -> str:
"""Remove comments so a hex in prose is not counted as a literal."""
text = BLOCK_COMMENT.sub("", text)
if suffix == ".css":
return text
return LINE_COMMENT.sub("", text)
def count_text(text: str, suffix: str) -> tuple[int, int]:
"""Return (token usages, raw literals) for one file's text."""
tokens = 0
literals = 0
for line in strip_comments(text, suffix).splitlines():
tokens += len(TOKEN_USE.findall(line))
if MEDIA_LINE.search(line):
continue
line = TOKEN_DEFINITION.sub("", line)
literals += len(HEX.findall(line)) + len(UNIT.findall(line))
return tokens, literals
def _has_manifest(directory: Path) -> bool:
"""True when ``directory`` holds a package/app manifest."""
return any((directory / m).exists() for m in MANIFESTS) or any(
directory.glob("astro.config.*")
)
def surface_root(path: Path, repo: Path) -> Path:
"""Nearest ancestor of ``path`` (within ``repo``) holding a manifest."""
parent = path.parent
while parent != repo and not _has_manifest(parent):
parent = parent.parent
return parent
def ui_files(repo: Path) -> list[Path]:
"""UI source files, skipping build/generated dirs and token files."""
found: list[Path] = []
for root, dirs, names in os.walk(repo):
dirs[:] = sorted(d for d in dirs if d not in SKIP_DIRS)
for name in sorted(names):
path = Path(root) / name
if path.suffix not in EXTENSIONS or SKIP_NAME.search(name):
continue
if TOKEN_FILE.match(path.stem):
continue
if path.is_file() and path.stat().st_size <= MAX_BYTES:
found.append(path)
return found
def scan(repo: Path) -> list[Surface]:
"""Count every UI surface under ``repo``, sorted by name."""
surfaces: dict[Path, Surface] = {}
for path in ui_files(repo):
root = surface_root(path, repo)
name = str(root.relative_to(repo))
surface = surfaces.setdefault(root, Surface(name))
text = path.read_text(encoding="utf-8", errors="replace")
tokens, literals = count_text(text, path.suffix)
surface.files += 1
surface.token_usages += tokens
surface.raw_literals += literals
return sorted(surfaces.values(), key=lambda s: s.name)
def totals(surfaces: list[Surface]) -> Surface:
"""Sum of all surfaces."""
return Surface(
"TOTAL",
sum(s.files for s in surfaces),
sum(s.token_usages for s in surfaces),
sum(s.raw_literals for s in surfaces),
)
def report(repo: Path) -> Json:
"""Whole-repo report as a JSON-ready dict."""
surfaces = scan(repo)
return {
"repo": str(repo),
"surfaces": [s.as_dict() for s in surfaces],
"totals": totals(surfaces).as_dict(),
}
def main(argv: list[str] | None = None) -> int:
"""CLI entry point."""
parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
parser.add_argument("repo", type=Path)
parser.add_argument("--json", action="store_true", help="print JSON")
parser.add_argument("--previous", type=Path, help="earlier --json output")
args = parser.parse_args(argv)
if not args.repo.is_dir():
print(f"not a directory: {args.repo}", file=sys.stderr)
return 2
rep = report(args.repo.resolve())
diff = None
if args.previous:
try:
diff = delta(rep, load_previous(args.previous))
except (OSError, ValueError) as exc:
print(f"cannot read previous report: {exc}", file=sys.stderr)
return 2
if args.json:
print(json.dumps({**rep, **({"delta": diff} if diff else {})}, indent=2))
else:
print(table(rep, diff))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -0,0 +1,74 @@
"""Delta and table rendering for conformance reports (see conformance.py)."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
Json = dict[str, Any]
def _num(value: object) -> float:
"""Numeric value from loosely typed JSON, 0 when absent."""
return float(value) if isinstance(value, (int, float)) else 0.0
def load_previous(path: Path) -> Json:
"""Read an earlier ``--json`` report; ValueError if its shape is wrong."""
data = json.loads(path.read_text(encoding="utf-8"))
surfaces = data.get("surfaces") if isinstance(data, dict) else None
ok = isinstance(surfaces, list) and isinstance(data.get("totals"), dict)
if not ok or not all(isinstance(s, dict) and "name" in s for s in surfaces):
msg = "not a conformance report"
raise ValueError(msg)
return data
def delta(current: Json, previous: Json) -> Json:
"""Per-surface and total change versus a previous report."""
before = {str(s["name"]): s for s in previous["surfaces"]}
rows: list[Json] = []
entries = [*current["surfaces"], current["totals"]]
prev_total = previous["totals"]
for row in entries:
old = prev_total if row["name"] == "TOTAL" else before.get(str(row["name"]), {})
rows.append({
"name": row["name"],
"token_usages": _num(row["token_usages"]) - _num(old.get("token_usages")),
"raw_literals": _num(row["raw_literals"]) - _num(old.get("raw_literals")),
"conformance": None
if row["conformance"] is None or old.get("conformance") is None
else round(_num(row["conformance"]) - _num(old["conformance"]), 1),
})
return {"rows": rows}
def _fmt(value: object, signed: bool = False) -> str:
"""Table cell text for a count, percent or missing value."""
if value is None:
return "-"
return f"{_num(value):+g}" if signed else f"{_num(value):g}"
def table(rep: Json, diff: Json | None) -> str:
"""Plain-text table, with delta columns when ``diff`` is given."""
head = ["surface", "files", "tokens", "raw", "conf%"]
if diff:
head += ["d.tokens", "d.raw", "d.conf"]
rows = [head]
deltas = {str(r["name"]): r for r in diff["rows"]} if diff else {}
for row in [*rep["surfaces"], rep["totals"]]:
cells = [str(row["name"]), _fmt(row["files"]), _fmt(row["token_usages"]),
_fmt(row["raw_literals"]), _fmt(row["conformance"])]
if diff:
d = deltas.get(str(row["name"]), {})
cells += [_fmt(d.get("token_usages", 0), True),
_fmt(d.get("raw_literals", 0), True),
_fmt(d.get("conformance"), True)]
rows.append(cells)
widths = [max(len(r[i]) for r in rows) for i in range(len(head))]
return "\n".join(
" ".join(c.ljust(w) for c, w in zip(r, widths, strict=True)).rstrip()
for r in rows
)

View File

@ -0,0 +1,6 @@
"""Make ``scripts/`` importable: the folder name has a hyphen, so no package."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))

View File

@ -0,0 +1,118 @@
"""Tests for scripts/conformance.py (100 % branch coverage)."""
from __future__ import annotations
import json
import runpy
import sys
from typing import TYPE_CHECKING
import conformance
import pytest
if TYPE_CHECKING:
from pathlib import Path
def write(root: Path, rel: str, text: str) -> None:
path = root / rel
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def test_count_text_css_tokens_literals_and_skips() -> None:
css = (
"/* #123456 in a comment */\n"
":root { --brand: #b8862e; }\n"
"@media (min-width: 600px) { a { color: red; } }\n"
".a { color: var(--brand); margin: 12px 0px; border: 1.5pt solid #fff; }\n"
)
assert conformance.count_text(css, ".css") == (1, 3)
def test_count_text_line_comments_and_urls_in_ts() -> None:
ts = 'const u = "http://x.test"; // #abcdef\nconst c = "#a1b2c3";\n'
assert conformance.count_text(ts, ".ts") == (0, 1)
def test_count_text_dart_tokens_and_literals() -> None:
dart = (
"final c = Color(0xFF112233);\n"
"final d = AppPalette.accent;\n"
"final e = Theme.of(context).colorScheme.primary;\n"
)
assert conformance.count_text(dart, ".dart") == (3, 1)
def test_conformance_percent_and_none() -> None:
assert conformance.Surface("x").conformance is None
assert conformance.Surface("x", 1, 3, 1).conformance == 75.0
def test_scan_groups_by_manifest_and_skips_generated(tmp_path: Path) -> None:
write(tmp_path, "pubspec.yaml", "name: a")
write(tmp_path, "lib/ui.dart", "final c = AppPalette.bg; final d = '#fff';")
write(tmp_path, "lib/tokens.dart", "const c = Color(0xFF000000);")
write(tmp_path, "lib/a.g.dart", "const c = '#ffffff';")
write(tmp_path, "build/out.css", "a { color: #fff }")
write(tmp_path, "test/x_test.dart", "const c = '#fff';")
write(tmp_path, "web/package.json", "{}")
write(tmp_path, "web/s.css", "a { margin: 8px; color: var(--x); }")
write(tmp_path, "site/astro.config.mjs", "export default {}")
write(tmp_path, "site/p.astro", "<p style='top: 4pt'>hi</p>")
write(tmp_path, "loose/readme.txt", "#fff 3px")
write(tmp_path, "root.tsx", "const a = 'var(--r)';")
write(tmp_path, "big.css", "a{}" + " " * conformance.MAX_BYTES)
surfaces = {s.name: s for s in conformance.scan(tmp_path)}
assert sorted(surfaces) == [".", "site", "web"]
assert (surfaces["."].token_usages, surfaces["."].raw_literals) == (2, 1)
assert surfaces["."].files == 2
assert (surfaces["web"].token_usages, surfaces["web"].raw_literals) == (1, 1)
assert (surfaces["site"].token_usages, surfaces["site"].raw_literals) == (0, 1)
def test_scan_ignores_non_regular_file(tmp_path: Path) -> None:
(tmp_path / "dangling.css").symlink_to(tmp_path / "missing.css")
assert conformance.scan(tmp_path) == []
def test_totals_and_report(tmp_path: Path) -> None:
write(tmp_path, "a.css", "a { margin: 8px; }")
write(tmp_path, "web/package.json", "{}")
write(tmp_path, "web/b.css", "b { color: var(--x); }")
rep = conformance.report(tmp_path)
assert rep["totals"]["token_usages"] == 1
assert rep["totals"]["raw_literals"] == 1
assert rep["totals"]["conformance"] == 50.0
assert [s["name"] for s in rep["surfaces"]] == [".", "web"]
def test_main_table_json_and_delta(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
repo = tmp_path / "repo"
write(repo, "a.css", "a { margin: 8px; color: var(--x); }")
assert conformance.main([str(repo)]) == 0
assert "TOTAL" in capsys.readouterr().out
assert conformance.main([str(repo), "--json"]) == 0
first = capsys.readouterr().out
assert "delta" not in json.loads(first)
prev = tmp_path / "prev.json"
prev.write_text(first, encoding="utf-8")
write(repo, "b.css", "b { color: var(--y); }")
assert conformance.main([str(repo), "--json", "--previous", str(prev)]) == 0
assert json.loads(capsys.readouterr().out)["delta"]["rows"][-1]["token_usages"] == 1.0
assert conformance.main([str(repo), "--previous", str(prev)]) == 0
assert "d.tokens" in capsys.readouterr().out
def test_main_errors(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
assert conformance.main([str(tmp_path / "nope")]) == 2
assert "not a directory" in capsys.readouterr().err
assert conformance.main([str(tmp_path), "--previous", str(tmp_path / "none.json")]) == 2
assert "cannot read previous" in capsys.readouterr().err
def test_script_entry_point(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(sys, "argv", ["conformance.py", str(tmp_path)])
with pytest.raises(SystemExit) as exit_info:
runpy.run_path(conformance.__file__, run_name="__main__")
assert exit_info.value.code == 0

View File

@ -0,0 +1,71 @@
"""Tests for scripts/conformance_diff.py (100 % branch coverage)."""
from __future__ import annotations
from typing import TYPE_CHECKING
import conformance_diff
import pytest
if TYPE_CHECKING:
from pathlib import Path
def previous_report(conf: float | None = 50.0) -> dict:
return {
"surfaces": [{"name": ".", "token_usages": 1, "raw_literals": 1, "conformance": conf}],
"totals": {"name": "TOTAL", "token_usages": 1, "raw_literals": 1, "conformance": conf},
}
def current_report() -> dict:
return {
"surfaces": [
{"name": ".", "files": 1, "token_usages": 3, "raw_literals": 1, "conformance": 75.0},
{"name": "new", "files": 1, "token_usages": 2, "raw_literals": 0, "conformance": None},
],
"totals": {"name": "TOTAL", "files": 2, "token_usages": 5, "raw_literals": 1, "conformance": 83.3},
}
def test_delta_rows() -> None:
rows = {r["name"]: r for r in conformance_diff.delta(current_report(), previous_report())["rows"]}
assert rows["."] == {"name": ".", "token_usages": 2.0, "raw_literals": 0.0, "conformance": 25.0}
assert rows["new"]["token_usages"] == 2.0
assert rows["new"]["conformance"] is None
assert rows["TOTAL"]["conformance"] == 33.3
def test_delta_missing_previous_conformance() -> None:
rows = conformance_diff.delta(current_report(), previous_report(None))["rows"]
assert rows[0]["conformance"] is None
def test_num_and_fmt() -> None:
assert conformance_diff._num("x") == 0.0
assert conformance_diff._num(2) == 2.0
assert conformance_diff._fmt(None) == "-"
assert conformance_diff._fmt(2.5) == "2.5"
assert conformance_diff._fmt(2, signed=True) == "+2"
def test_table_without_and_with_delta() -> None:
plain = conformance_diff.table(current_report(), None)
assert plain.splitlines()[0].split() == ["surface", "files", "tokens", "raw", "conf%"]
assert "TOTAL" in plain
diff = conformance_diff.delta(current_report(), previous_report())
text = conformance_diff.table(current_report(), diff)
assert "d.conf" in text
assert "+25" in text
@pytest.mark.parametrize(
"payload",
["[]", "{}", '{"surfaces": 1, "totals": {}}', '{"surfaces": [1], "totals": {}}',
'{"surfaces": [], "totals": 1}'],
)
def test_load_previous_rejects_bad_shape(tmp_path: Path, payload: str) -> None:
path = tmp_path / "p.json"
path.write_text(payload, encoding="utf-8")
with pytest.raises(ValueError, match="not a conformance report"):
conformance_diff.load_previous(path)