feat: full-stack MCP surface — client template, npm CI, 13 repos
- build.py: vendor templates/client into mcp/templates/client with client.json manifest (16 kinds); register drone-npm as a third CI shape (types: pypi/npm/docker); root *.py stale doc numbers fixed - registry.py: registry://client resource + list_client_components / get_client_component / get_client_template tools; build prompt gains the client scaffold step (docs://modules/ui_admin); compliance prompt covers npm packages; drone docs describe all three shapes - repos.json (+mcp): doc-weaver, react-auth, react-openapi (npm Type B, package.json version source), vet (app Type C, VERSION) - docs: 90_drone_pipelines gains Type B npm section; 91_versioning covers package.json as a version source; 94_repository_inventory gains the npm package table and services/apps Type C; lib index updated Verified: bundle regen (24 modules, client 16, drone 3, repos 13); registry client readers exercised against the vendored bundle.
This commit is contained in:
130
build.py
130
build.py
@@ -362,14 +362,125 @@ def build_templates(folder: Path, dry_run: bool = False) -> None:
|
||||
f"({len(manifest['components'])} components)")
|
||||
|
||||
|
||||
# ── Client template bundle ────────────────────────────────────────────────────
|
||||
|
||||
# Component kinds shipped in the vendored React client template
|
||||
# (``templates/client``): the Vite + MUI shell that pairs with ``templates/app``
|
||||
# and renders the admin UI from the app's openapi.yaml via
|
||||
# ``@aetoskia/react-openapi`` / ``@aetoskia/react-auth``. No seed tokens —
|
||||
# ship as-is (see standards "40_ui_admin").
|
||||
_CLIENT_COMPONENTS: list[dict[str, str]] = [
|
||||
{"kind": "client-package", "path": "package.json", "placeholder": "",
|
||||
"description": "npm manifest: @aetoskia deps, scripts (dev/build/preview)."},
|
||||
{"kind": "client-npmrc", "path": ".npmrc", "placeholder": "",
|
||||
"description": "Private registry auth pointing at npm.aetoskia.com."},
|
||||
{"kind": "client-env", "path": ".env.example", "placeholder": "",
|
||||
"description": "VITE_API_BASE_URL / VITE_AUTH_BASE_URL seed."},
|
||||
{"kind": "client-vite-config", "path": "vite.config.ts", "placeholder": "",
|
||||
"description": "Vite config (dev server, build)."},
|
||||
{"kind": "client-tsconfig", "path": "tsconfig.json", "placeholder": "",
|
||||
"description": "TypeScript config for the client."},
|
||||
{"kind": "client-index-html", "path": "index.html", "placeholder": "",
|
||||
"description": "HTML entry point."},
|
||||
{"kind": "client-entry", "path": "src/main.jsx", "placeholder": "",
|
||||
"description": "React entry: router + providers + theme."},
|
||||
{"kind": "client-openapi-config", "path": "src/openapi-config.ts", "placeholder": "",
|
||||
"description": "Points the admin renderer at the app spec + auth-server spec."},
|
||||
{"kind": "client-home", "path": "src/Home.tsx", "placeholder": "",
|
||||
"description": "Auto-listing home page (ResourceCard + LiveTable per resource)."},
|
||||
{"kind": "client-header", "path": "src/Header.tsx", "placeholder": "",
|
||||
"description": "App header bar (brand + nav)."},
|
||||
{"kind": "client-footer", "path": "src/Footer.tsx", "placeholder": "",
|
||||
"description": "App footer."},
|
||||
{"kind": "client-vite-env", "path": "src/vite-env.d.ts", "placeholder": "",
|
||||
"description": "Vite client type declarations."},
|
||||
{"kind": "client-theme", "path": "src/shared-theme/AppTheme.tsx", "placeholder": "",
|
||||
"description": "Theme provider wrapper (light/dark)."},
|
||||
{"kind": "client-theme-config", "path": "src/shared-theme/themeConfig.ts", "placeholder": "",
|
||||
"description": "Palette / typography tokens."},
|
||||
{"kind": "client-theme-primitives", "path": "src/shared-theme/themePrimitives.ts", "placeholder": "",
|
||||
"description": "MUI theme primitives (shadows, accents)."},
|
||||
{"kind": "client-theme-shim", "path": "src/shared-theme/mui.d.ts", "placeholder": "",
|
||||
"description": "MUI v5 type shims (Theme.vars, SelectClasses.focused)."},
|
||||
]
|
||||
|
||||
|
||||
def build_client_manifest(template_root: Path) -> dict[str, object]:
|
||||
"""Describe every component in the vendored client template.
|
||||
|
||||
Args:
|
||||
template_root (Path): The vendored ``templates/client`` directory.
|
||||
|
||||
Returns:
|
||||
dict[str, object]: Manifest with the component entries.
|
||||
"""
|
||||
components: list[dict[str, str]] = []
|
||||
for entry in _CLIENT_COMPONENTS:
|
||||
rel = entry["path"]
|
||||
if not (template_root / rel).exists():
|
||||
raise SystemExit(f"[ERR] client component path missing from template: {rel}")
|
||||
components.append({
|
||||
"kind": entry["kind"],
|
||||
"path": rel,
|
||||
"placeholder": entry["placeholder"],
|
||||
"description": entry["description"],
|
||||
})
|
||||
return {
|
||||
"project": "standards",
|
||||
"template": "templates/client",
|
||||
"components": components,
|
||||
}
|
||||
|
||||
|
||||
def build_client_templates(folder: Path, dry_run: bool = False) -> None:
|
||||
"""Vendor ``folder/src/templates/client`` into ``<name>/mcp/templates/client``.
|
||||
|
||||
Ships the React client skeleton (the auto-rendered admin shell) in the MCP
|
||||
bundle so an agent can pull exact, current component code, and emits
|
||||
``client.json`` describing the kinds. Excludes caches and lock files.
|
||||
|
||||
Args:
|
||||
folder (Path): The hub folder owning ``src/templates/client``.
|
||||
dry_run (bool): Print the plan without executing.
|
||||
"""
|
||||
name = folder.name
|
||||
src = folder / "src" / "templates" / "client"
|
||||
if not src.is_dir():
|
||||
return
|
||||
out_dir = folder / "mcp" / "templates" / "client"
|
||||
if dry_run:
|
||||
print(f"[CLT] {name} {src.relative_to(ROOT)}/** -> {out_dir.relative_to(ROOT)}/**")
|
||||
return
|
||||
|
||||
if out_dir.exists():
|
||||
shutil.rmtree(out_dir)
|
||||
out_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(
|
||||
src, out_dir,
|
||||
ignore=shutil.ignore_patterns(
|
||||
"node_modules", "dist", ".ruff_cache", ".pytest_cache",
|
||||
".mypy_cache", ".venv", "venv", ".git", "*.egg-info",
|
||||
),
|
||||
)
|
||||
manifest = build_client_manifest(out_dir)
|
||||
(folder / "mcp" / "client.json").write_text(
|
||||
json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8"
|
||||
)
|
||||
print(f"[CLT] {name} client template vendored to {out_dir.relative_to(ROOT)}/ "
|
||||
f"({len(manifest['components'])} components)")
|
||||
|
||||
|
||||
# ── Drone standard bundle ─────────────────────────────────────────────────────
|
||||
|
||||
# The two CI shapes shipped in ``src/templates/drone``. There are exactly two
|
||||
# types of ``.drone.yml`` in the org: a Python package (private PyPI) and a
|
||||
# Docker deployment (build / push / run). See standards "10_drone_pipelines".
|
||||
# The CI shapes shipped in ``src/templates/drone``. There are exactly three
|
||||
# types of ``.drone.yml`` in the org: a Python package (private PyPI), an npm
|
||||
# package (private npm registry), and a Docker deployment (build / push / run).
|
||||
# See standards "90_drone_pipelines".
|
||||
_DRONE_COMPONENTS: list[dict[str, str]] = [
|
||||
{"kind": "drone-pypi", "path": "python-lib.yaml", "placeholder": "",
|
||||
"description": "Drone pipeline: build + publish a Python package to the private PyPI index."},
|
||||
{"kind": "drone-npm", "path": "npm-lib.yaml", "placeholder": "",
|
||||
"description": "Drone pipeline: build + publish an npm package to the private npm registry."},
|
||||
{"kind": "drone-service", "path": "service-docker.yaml", "placeholder": "",
|
||||
"description": "Drone pipeline: build, push, and deploy a Docker service."},
|
||||
]
|
||||
@@ -398,7 +509,7 @@ def build_drone_manifest(template_root: Path) -> dict[str, object]:
|
||||
return {
|
||||
"project": "standards",
|
||||
"template": "templates/drone",
|
||||
"types": ["pypi", "docker"],
|
||||
"types": ["pypi", "npm", "docker"],
|
||||
"components": components,
|
||||
}
|
||||
|
||||
@@ -406,9 +517,9 @@ def build_drone_manifest(template_root: Path) -> dict[str, object]:
|
||||
def build_drone_templates(folder: Path, dry_run: bool = False) -> None:
|
||||
"""Vendor ``folder/src/templates/drone`` into ``<name>/mcp/templates/drone``.
|
||||
|
||||
Ships the two ``.drone.yml`` shapes (PyPI package, Docker deployment) in the
|
||||
MCP bundle so an agent can pull the exact CI file, and emits ``drone.json``
|
||||
describing them.
|
||||
Ships the three ``.drone.yml`` shapes (PyPI package, npm package, Docker
|
||||
deployment) in the MCP bundle so an agent can pull the exact CI file, and
|
||||
emits ``drone.json`` describing them.
|
||||
|
||||
Args:
|
||||
folder (Path): The hub folder owning ``src/templates/drone``.
|
||||
@@ -442,7 +553,7 @@ def build_drone_templates(folder: Path, dry_run: bool = False) -> None:
|
||||
|
||||
# The release artefacts shipped in ``src/templates/release``. One canonical
|
||||
# changelog (Keep a Changelog + SemVer) and the service version file. See
|
||||
# standards "11_versioning_and_changelog".
|
||||
# standards "91_versioning_and_changelog".
|
||||
_RELEASE_COMPONENTS: list[dict[str, str]] = [
|
||||
{"kind": "changelog", "path": "CHANGELOG.md", "placeholder": "",
|
||||
"description": "Keep a Changelog skeleton: one [Unreleased] section + compare links."},
|
||||
@@ -523,7 +634,7 @@ def build_repos(folder: Path, dry_run: bool = False) -> None:
|
||||
The repository inventory (name, type, version source, Drone shape) is a
|
||||
single hand-authored JSON file, surfaced to agents through the
|
||||
``registry://repos`` resource and the ``list_repos`` tool. See standards
|
||||
"15_repository_inventory".
|
||||
"94_repository_inventory".
|
||||
|
||||
Args:
|
||||
folder (Path): The hub folder owning ``src/repos.json``.
|
||||
@@ -566,6 +677,7 @@ def main() -> None:
|
||||
for folder in folders:
|
||||
build_bundle(folder, dry_run=args.dry_run)
|
||||
build_templates(folder, dry_run=args.dry_run)
|
||||
build_client_templates(folder, dry_run=args.dry_run)
|
||||
build_drone_templates(folder, dry_run=args.dry_run)
|
||||
build_release_templates(folder, dry_run=args.dry_run)
|
||||
build_repos(folder, dry_run=args.dry_run)
|
||||
|
||||
Reference in New Issue
Block a user