File size: 4,693 Bytes
1bd64c1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | #!/usr/bin/env python3
"""Fail if the docs have drifted from the real corpus in commands.json.
The command count, per-role breakdown, and vendor count are repeated by hand
across README.md and docs/index.html. This checker recomputes them from the
single source of truth (commands.json) and asserts every derived figure still
appears verbatim in those files, so a stale number fails CI instead of shipping.
The README vendor table is also checked row-by-row (docs/index.html has no
per-vendor counts).
Design notes:
- Hard failures are the raw numeric facts (total count, per-role counts, vendor
count, and README per-vendor table cells) — totals/roles/vendor-count are
matched as a substring so surrounding wording can change freely.
- The rounded marketing form (e.g. "69,000+") and the ~MB size are advisory
warnings only, since those are phrasing/rounding choices that flip on small
changes and shouldn't gate a merge.
Stdlib only. Run from anywhere: `python3 scripts/check_consistency.py`.
Exit 0 = consistent, 1 = drift found (details printed), 2 = usage/IO error.
"""
from __future__ import annotations
import json
import re
import sys
from collections import Counter
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
CORPUS = ROOT / "commands.json"
# Files that restate corpus figures. All get the same hard numeric checks.
TRACKED = ("README.md", "docs/index.html")
def fmt(n: int) -> str:
return f"{n:,}"
def rounded_thousands(n: int) -> str:
return f"{(n // 1000) * 1000:,}+"
def main() -> int:
if not CORPUS.exists():
print(f"ERROR: {CORPUS} not found", file=sys.stderr)
return 2
try:
data = json.loads(CORPUS.read_text())
except (ValueError, OSError) as exc:
print(f"ERROR: cannot read {CORPUS}: {exc}", file=sys.stderr)
return 2
total = len(data)
roles = Counter(row.get("role", "?") for row in data)
vendors = sorted({row.get("vendor", "?") for row in data})
size_mb = round(CORPUS.stat().st_size / (1024 * 1024))
print("Corpus (source of truth: commands.json)")
print(f" total commands : {fmt(total)}")
print(f" rounded form : {rounded_thousands(total)}")
print(" roles : " + " · ".join(f"{r} {fmt(c)}" for r, c in roles.most_common()))
print(f" vendors : {len(vendors)}")
print(f" size : ~{size_mb} MB")
print()
# Read each tracked file exactly once.
contents: dict[str, str | None] = {}
for rel in TRACKED:
path = ROOT / rel
contents[rel] = path.read_text() if path.exists() else None
# Hard requirements: the raw numeric facts must appear verbatim.
required = [fmt(total)] + [fmt(c) for c in roles.values()] + [f"{len(vendors)} vendor"]
failures: list[str] = []
for rel, text in contents.items():
if text is None:
failures.append(f"{rel}: file missing")
continue
for value in required:
if value not in text:
failures.append(f"{rel}: missing {value!r}")
# README vendor table is the only public per-vendor count surface.
# docs/index.html does not list per-vendor figures, so this is README-only.
# Match the markdown row: | **Vendor** | ... | N |
vendor_counts = Counter(row.get("vendor", "?") for row in data)
readme = contents.get("README.md")
if readme is not None:
for vendor, n in sorted(vendor_counts.items(), key=lambda kv: -kv[1]):
pat = rf"\| \*\*{re.escape(vendor)}\*\* \|[^|\n]+\|[^|\n]+\| {fmt(n)} \|"
if not re.search(pat, readme):
failures.append(
f"README.md: vendor table missing {vendor} {fmt(n)}"
)
# Advisory only — phrasing/rounding, not raw facts.
for rel, text in contents.items():
if text is None:
continue
if rounded_thousands(total) not in text:
print(f"WARNING: {rel} does not contain rounded form "
f"{rounded_thousands(total)!r}")
docs = contents.get("docs/index.html")
if docs is not None and f"~{size_mb} MB" not in docs:
print(f"WARNING: docs/index.html does not mention '~{size_mb} MB' "
f"(commands.json is now ~{size_mb} MB)")
if failures:
print("\nDRIFT DETECTED — docs are out of sync with commands.json:\n")
for f in failures:
print(f" ✗ {f}")
print("\nUpdate the file(s) above (and the profile hub) to the corpus figures.")
return 1
print("OK — README.md and docs/index.html match commands.json.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
|