feat(standards): add Drone CI standard (PyPI package + Docker deployment)

Document the two `.drone.yml` shapes used across the org, derived from the
repos:

- Type A `python-lib.yaml`: build-and-publish-pypi + backfill-pypi-from-tags
- Type B `service-docker.yaml`: build-and-deploy-service + build-and-deploy-site

- add reference page `10_drone_pipelines`
- vendor `src/templates/drone/` into the MCP bundle (`drone.json`)
- expose `registry://drone` + `list_drone_templates` / `get_drone_template`
- wire `build.py` to vendor drone templates; rebuild nav/index (14 modules)
This commit is contained in:
2026-10-15 00:52:38 +05:30
parent 18d52bdb7c
commit a754366901
14 changed files with 1133 additions and 2 deletions

View File

@@ -10,6 +10,7 @@ Resources:
docs://spec (the pinned auth-server OpenAPI contract)
registry://libraries (the structured catalog)
registry://components (the template component index)
registry://drone (the CI template index)
Tools:
ping (from the base server)
@@ -17,6 +18,8 @@ Tools:
list_app_components (the template component kinds)
get_app_component (kind -> exact template file, seed tokens renamed)
get_app_template (the whole app skeleton as a path -> content map)
list_drone_templates (the two CI shapes: PyPI package / Docker deployment)
get_drone_template (kind -> exact .drone.yml template)
Prompts:
intake (the questions to gather before building)
@@ -126,6 +129,32 @@ class StandardsMCPServer(MCPServer):
return {"error": "not_found", "path": str(path)}
return json.loads(path.read_bytes())
def _drone(self) -> dict[str, Any]:
"""Load the CI (Drone) template manifest."""
path = Path(self.mcp_root) / "drone.json"
if not path.exists():
return {"error": "not_found", "path": str(path), "components": []}
return json.loads(path.read_bytes())
def _find_drone_component(self, kind: str) -> dict[str, Any] | None:
for entry in self._drone().get("components", []):
if entry.get("kind") == kind:
return entry
return None
def _read_drone_component(self, entry: dict[str, Any]) -> dict[str, Any]:
"""Read a vendored drone template file."""
rel = entry["path"]
path = Path(self.mcp_root) / "templates" / "drone" / rel
if not path.exists():
return {"error": "not_found", "path": str(path)}
return {
"kind": entry["kind"],
"path": rel,
"placeholder": entry.get("placeholder"),
"content": path.read_text(encoding="utf-8"),
}
def _find_component(self, kind: str) -> dict[str, Any] | None:
for entry in self._components().get("components", []):
if entry.get("kind") == kind:
@@ -195,6 +224,10 @@ class StandardsMCPServer(MCPServer):
def components() -> dict[str, Any]:
return self._components()
@self.app.resource("registry://drone")
def drone() -> dict[str, Any]:
return self._drone()
@self.app.resource("docs://spec")
def spec() -> dict[str, Any]:
"""Return the pinned auth-server OpenAPI contract.
@@ -307,6 +340,46 @@ class StandardsMCPServer(MCPServer):
"files": files,
}
@self.app.tool()
def list_drone_templates() -> list[dict[str, Any]]:
"""List the CI (Drone) template shapes.
There are exactly two types of ``.drone.yml`` in the org: a Python
package published to the private PyPI index, and a Docker
deployment that builds, pushes, and runs a container.
Returns:
list[dict[str, Any]]:
Entries with ``kind``, ``path``, ``placeholder`` and
``description``. Fetch one with :func:`get_drone_template`.
"""
return self._drone().get("components", [])
@self.app.tool()
def get_drone_template(kind: str) -> dict[str, Any]:
"""Return the exact CI template for a component kind.
Use this to copy the current ``.drone.yml`` shape instead of
inventing one. See :func:`list_drone_templates` for valid kinds
("drone-pypi", "drone-service").
Args:
kind (str): Component kind, e.g. "drone-pypi" or
"drone-service".
Returns:
dict[str, Any]: ``{kind, path, placeholder, content}`` or an
error entry when the kind is unknown.
"""
entry = self._find_drone_component(kind)
if entry is None:
return {
"error": "unknown_kind",
"kind": kind,
"available": [c["kind"] for c in self._drone().get("components", [])],
}
return self._read_drone_component(entry)
@self.app.prompt()
def intake() -> str:
"""Return the questions to gather before building an app.