#!/usr/bin/env python3 """Render the SDK conformance matrix (GL #395). Reads the per-SDK scorecards written by the conformance suites (``conformance-{python,typescript,rust}.json``) and renders the publishable matrix ``docs/reference/sdk-conformance-matrix.md``: SDK version x engine version x per-check status. Run by ``scripts/sdk-conformance.sh`` and the ``sdk-conformance`` CI job on every release candidate. """ from __future__ import annotations import argparse import datetime as dt import json import pathlib import re import sys SDK_VERSIONS = { "python": ("clients/python/leanctx/__init__.py", r'__version__ = "([^"]+)"'), "typescript": ("cookbook/sdk/package.json", r'"version":\s*"([^"]+)"'), "rust": ("clients/rust/lean-ctx-client/Cargo.toml", r'^version = "([^"]+)"'), } def sdk_version(root: pathlib.Path, sdk: str) -> str: rel, pattern = SDK_VERSIONS[sdk] text = (root / rel).read_text(encoding="utf-8") m = re.search(pattern, text, re.MULTILINE) return m.group(1) if m else "unknown" def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("matrix_dir", type=pathlib.Path) parser.add_argument("--engine-version", required=True) parser.add_argument("--out", type=pathlib.Path, required=True) args = parser.parse_args() root = pathlib.Path(__file__).resolve().parent.parent cards = {} for sdk in ("python", "typescript", "rust"): path = args.matrix_dir / f"conformance-{sdk}.json" if path.is_file(): cards[sdk] = json.loads(path.read_text(encoding="utf-8")) if not cards: print("no scorecards found โ€” did the suites run?", file=sys.stderr) return 1 check_names: list[str] = [] for card in cards.values(): for check in card["checks"]: if check["name"] not in check_names: check_names.append(check["name"]) lines = [ "# SDK Conformance Matrix", "", "Status of every first-party SDK against the frozen `/v1` contract", "(`run_conformance`, GL #395). Generated by `scripts/sdk-conformance.sh`", "against a real `lean-ctx serve` instance โ€” regenerate, never edit.", "", f"- **Engine:** `{args.engine_version}`", f"- **Generated:** {dt.datetime.now(dt.timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}", "", "| SDK | Package | Version | Conformance |", "|---|---|---|---|", ] packages = { "python": "`lean-ctx-client` (PyPI)", "typescript": "`lean-ctx-client` (npm)", "rust": "`lean-ctx-client` (crates.io)", } for sdk, card in cards.items(): verdict = "PASS" if card["all_passed"] else "FAIL" lines.append( f"| {sdk} | {packages[sdk]} | {sdk_version(root, sdk)} " f"| {verdict} ({card['passed']}/{card['total']}) |" ) lines += ["", "## Checks", "", "| Check | " + " | ".join(cards) + " |"] lines.append("|---|" + "---|" * len(cards)) for name in check_names: row = [name] for card in cards.values(): check = next((c for c in card["checks"] if c["name"] == name), None) if check is None: row.append("โ€”") elif check["passed"]: row.append("pass") else: detail = check.get("detail", "") row.append(f"FAIL {detail}".strip()) lines.append("| " + " | ".join(row) + " |") lines += [ "", "## SemVer coupling", "", "Every SDK declares the `http_mcp` contract versions it speaks", "(`SUPPORTED_HTTP_CONTRACT_VERSIONS`); the `engine_compat` check fails when", "a server speaks a contract the SDK release does not support. SDK majors", "follow the engine contract major (CONTRACTS.md ยง Versioning rules).", "", ] args.out.parent.mkdir(parents=True, exist_ok=True) args.out.write_text("\n".join(lines), encoding="utf-8") print(f"wrote {args.out}") return 0 if all(c["all_passed"] for c in cards.values()) else 1 if __name__ == "__main__": raise SystemExit(main())