Files
Claude 61caad16cb feat: Phases 3-5 — generation v2, fleet ops, api-spec IR, MCP serve fleet-wide
Phase 3 (Generation v2):
- scaffold-cli.py migrated to Jinja2 (${} delimiters preserve existing
  templates; StrictUndefined; TEMPLATE_VERSION 2.0.0; .manifest.json
  provenance emission)
- auth.py.tpl unified (google-sso via {% if %} block; auth_google_sso.py.tpl
  deleted); anchor-based method injection replaced by template conditionals
- New scaffolds: command_group.py.tpl (--resource flag), test_e2e.py.tpl
  (canonical subprocess fixtures), README.md.tpl + SKILL.md.tpl (replace
  dead Handlebars .template files, now actually rendered)
- 21-profile golden test matrix (protocol x http_client x auth) — compile-
  clean, no placeholder leaks, profile-correct deps/files
- Skills consolidation: skills/shared/CONVENTIONS.md (single definition of
  every implementation rule) + RECOVERY.md (per-gate failure decision
  trees); boilerplate skill 829 -> 106 lines; HARNESS.md now navigational;
  reviewer agents de-overlapped; gap-analyzer mandatory first step of
  /refine; quality checklist tiered (38 Tier-1 critical checks) with
  --tier1-only/--strict in validate-checklist.py

Phase 4 (Fleet ops):
- cli-web-devkit canary: registry-declared read-only live-site smoke
  commands (12 no-auth CLIs) + daily canary.yml workflow that auto-files
  site-breakage issues
- contract.yml workflow: fleet contract suite + vendored-file drift on PR
- release-please switched to manifest/monorepo mode (root + cli-web-core +
  devkit packages, per-package changelogs); publish.yml with PyPI trusted
  publishing (setup guide in docs/PUBLISHING.md)
- cli-web-devkit docs: README fleet table + install block generated from
  registry.json between markers, --check freshness gate in CI (caught the
  install list missing linkedin)

Phase 5 (Product leaps):
- MCP SERVE FLEET-WIDE: cli_web_core/mcp_server.py adapter (vendored to all
  19 CLIs, synced by resync) exposes every Click command as a typed MCP
  tool over stdio; mcp-serve wired into all 19 entry points;
  contract-tested handshake x19
- api-spec.json IR: evidence-required endpoint schema + cli-web-devkit
  spec validate; wired into methodology Phase 2 (structural enforcement of
  never-invent-endpoints)
- cli-web-devkit gaps: deterministic captured-vs-implemented-vs-exposed
  diff (AST-based), wired into gap-analyzer skill Step 0
- Capture quality scoring (0-100) in validate-capture report

Fleet contract fixes found by the new gates:
- gh-trending emitted bare JSON arrays instead of the success envelope
  (fixed + e2e updated; stale version assertion fixed)
- notebooklm/stitch/codewiki/chatgpt lacked --version (fixed in Phase 2
  commit); booking registry commands corrected

Verified: 514 plugin/devkit/core tests, 95 fleet contract tests (incl. 19
MCP handshakes), 19/19 CLI unit suites, gh-trending e2e live, ruff+format
clean (512 files), mypy strict clean, verify-plugin 28/28, drift 39/39 ok,
registry + README freshness gates green.

https://claude.ai/code/session_017wCNXuLy6UpGJ1jcPdsD5i
2026-06-09 22:54:20 +00:00

121 lines
4.0 KiB
Python

"""Load and validate ``registry.json`` — the fleet's source of truth.
The registry drives the CI test matrix, docs generation, and contract
tests, so its accuracy is enforced both ways:
* every registry entry must point at a real ``<dir>/agent-harness`` package
* every ``*/agent-harness`` directory in the repo must have a registry entry
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
REQUIRED_FIELDS = (
"name",
"website",
"protocol",
"auth",
"directory",
"namespace",
"commands",
"install",
)
@dataclass
class RegistryEntry:
name: str
website: str
protocol: str
auth: str
directory: str
namespace: str
commands: list[str]
install: str
# Optional: read-only command invocations (arg lists) safe to run on a
# schedule against the live site to detect breakage. No-auth CLIs only.
canary: list[list[str]] = field(default_factory=list)
# Optional presentation fields (description, skill, display_website, ...).
extra: dict[str, Any] = field(default_factory=dict)
@property
def package(self) -> str:
"""Python sub-package name, e.g. ``gh_trending``."""
return self.namespace.removeprefix("cli_web.")
@property
def app_dir(self) -> str:
"""Top-level app directory, e.g. ``gh-trending``."""
return self.directory.split("/", 1)[0]
@classmethod
def from_dict(cls, raw: dict[str, Any]) -> RegistryEntry:
missing = [f for f in REQUIRED_FIELDS if f not in raw]
if missing:
raise ValueError(
f"registry entry {raw.get('name', '<unnamed>')!r} missing fields: {missing}"
)
extra = {k: v for k, v in raw.items() if k not in REQUIRED_FIELDS and k != "canary"}
return cls(
**{f: raw[f] for f in REQUIRED_FIELDS}, canary=raw.get("canary", []), extra=extra
)
@dataclass
class Registry:
version: str
clis: list[RegistryEntry] = field(default_factory=list)
@classmethod
def load(cls, path: Path) -> Registry:
raw = json.loads(path.read_text(encoding="utf-8"))
return cls(
version=raw.get("version", "0"),
clis=[RegistryEntry.from_dict(e) for e in raw.get("clis", [])],
)
def entry(self, name: str) -> RegistryEntry:
for e in self.clis:
if e.name == name or e.app_dir == name or e.package == name:
return e
raise KeyError(name)
def validate(root: Path) -> list[str]:
"""Return a list of problems (empty list == valid)."""
problems: list[str] = []
try:
registry = Registry.load(root / "registry.json")
except (OSError, ValueError, json.JSONDecodeError) as exc:
return [f"registry.json unreadable: {exc}"]
seen_dirs: set[str] = set()
for e in registry.clis:
seen_dirs.add(e.app_dir)
harness = root / e.directory
if not harness.is_dir():
problems.append(f"{e.name}: directory {e.directory!r} does not exist")
continue
pkg_dir = harness / "cli_web" / e.package
if not pkg_dir.is_dir():
problems.append(f"{e.name}: package dir {pkg_dir.relative_to(root)} does not exist")
if not e.name.startswith("cli-web-"):
problems.append(f"{e.name}: name must start with 'cli-web-'")
if not e.namespace.startswith("cli_web."):
problems.append(f"{e.name}: namespace must start with 'cli_web.'")
if e.directory not in e.install:
problems.append(f"{e.name}: install command does not reference {e.directory!r}")
if not e.commands:
problems.append(f"{e.name}: commands list is empty")
for harness in sorted(root.glob("*/agent-harness")):
app_dir = harness.parent.name
if app_dir not in seen_dirs:
problems.append(f"unregistered CLI: {app_dir}/agent-harness has no registry.json entry")
return problems