From 21972a63784588e49a1d19b27434f23ff48804d2 Mon Sep 17 00:00:00 2001 From: notque Date: Sun, 12 Apr 2026 03:11:20 -0700 Subject: [PATCH] feat(ci): add routing-drift check to catch skills absent from routing-tables.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Routing table drift has been caught in 4+ nightly evolution cycles and has never been caught at PR time. Any PR that adds a skill to skills/INDEX.json without updating routing-tables.md silently breaks routing for that skill. Adds scripts/check-routing-drift.py and a new routing-drift CI job in test.yml, matching the existing routing-benchmark job pattern. Fails with an actionable list of missing skills so authors know exactly what to add. Evolution cycle proposal: CI routing-tables drift check (STRONG 3.0/3.0 unanimous consensus — Pragmatist, Purist, User Advocate). --- .github/workflows/test.yml | 9 +++++ scripts/check-routing-drift.py | 70 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 scripts/check-routing-drift.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32a485a9..7b4d2990 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,3 +39,12 @@ jobs: with: python-version: "3.12" - run: python scripts/routing-benchmark.py --verbose + + routing-drift: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: python scripts/check-routing-drift.py --verbose diff --git a/scripts/check-routing-drift.py b/scripts/check-routing-drift.py new file mode 100644 index 00000000..d2833691 --- /dev/null +++ b/scripts/check-routing-drift.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Routing-table drift check. + +Verifies that every skill present in skills/INDEX.json is also mentioned +in skills/do/references/routing-tables.md. A skill absent from the routing +table is invisible to any process that consults the reference docs, which +means users and the router's documentation are silently out of sync. + +Usage: + python3 scripts/check-routing-drift.py + python3 scripts/check-routing-drift.py --verbose + +Exit codes: + 0 - All skills in INDEX.json are present in routing-tables.md + 1 - One or more skills are absent from routing-tables.md +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +SKILLS_INDEX = REPO_ROOT / "skills" / "INDEX.json" +ROUTING_TABLES = REPO_ROOT / "skills" / "do" / "references" / "routing-tables.md" + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--verbose", action="store_true", help="Show all skills checked") + args = parser.parse_args() + + if not SKILLS_INDEX.exists(): + print(f"ERROR: {SKILLS_INDEX} not found", file=sys.stderr) + return 1 + + if not ROUTING_TABLES.exists(): + print(f"ERROR: {ROUTING_TABLES} not found", file=sys.stderr) + return 1 + + with open(SKILLS_INDEX) as f: + idx = json.load(f) + + index_skills = sorted(idx.get("skills", {}).keys()) + routing_text = ROUTING_TABLES.read_text() + + missing = [s for s in index_skills if s not in routing_text] + present = [s for s in index_skills if s in routing_text] + + if args.verbose: + print(f"Checked {len(index_skills)} skills against routing-tables.md") + print(f" Present: {len(present)}") + print(f" Missing: {len(missing)}") + + if missing: + print(f"\nFAIL: {len(missing)} skill(s) in INDEX.json absent from routing-tables.md:") + for skill in missing: + print(f" {skill}") + print("\nAdd each missing skill to skills/do/references/routing-tables.md before merging.") + return 1 + + if args.verbose: + print("\nPASS: routing-tables.md is in sync with INDEX.json") + return 0 + + +if __name__ == "__main__": + sys.exit(main())