feat(standards): add versioning + changelog standard, release templates, and Drone version guard
Add lib page 11 (SemVer contract, pre-1.0 rule, tag==version invariant, Keep a Changelog), the releasing wiki runbook, and canonical release templates (CHANGELOG.md, VERSION). Vendor them into the MCP bundle as templates/release + release.json and expose registry://release with list/get tools. Add a changelog component to the app template. Enforce the tag<->version contract in both Drone shapes: Type A check-version and the backfill loop verify pyproject version; Type B adds a VERSION guard step.
This commit is contained in:
72
registry.py
72
registry.py
@@ -11,6 +11,7 @@ Resources:
|
||||
registry://libraries (the structured catalog)
|
||||
registry://components (the template component index)
|
||||
registry://drone (the CI template index)
|
||||
registry://release (the release template index)
|
||||
|
||||
Tools:
|
||||
ping (from the base server)
|
||||
@@ -20,6 +21,8 @@ Tools:
|
||||
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)
|
||||
list_release_templates (the changelog + version-file artefacts)
|
||||
get_release_template (kind -> exact release template file)
|
||||
|
||||
Prompts:
|
||||
intake (the questions to gather before building)
|
||||
@@ -136,6 +139,32 @@ class StandardsMCPServer(MCPServer):
|
||||
return {"error": "not_found", "path": str(path), "components": []}
|
||||
return json.loads(path.read_bytes())
|
||||
|
||||
def _release(self) -> dict[str, Any]:
|
||||
"""Load the release (versioning + changelog) template manifest."""
|
||||
path = Path(self.mcp_root) / "release.json"
|
||||
if not path.exists():
|
||||
return {"error": "not_found", "path": str(path), "components": []}
|
||||
return json.loads(path.read_bytes())
|
||||
|
||||
def _find_release_component(self, kind: str) -> dict[str, Any] | None:
|
||||
for entry in self._release().get("components", []):
|
||||
if entry.get("kind") == kind:
|
||||
return entry
|
||||
return None
|
||||
|
||||
def _read_release_component(self, entry: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Read a vendored release standard file."""
|
||||
rel = entry["path"]
|
||||
path = Path(self.mcp_root) / "templates" / "release" / 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_drone_component(self, kind: str) -> dict[str, Any] | None:
|
||||
for entry in self._drone().get("components", []):
|
||||
if entry.get("kind") == kind:
|
||||
@@ -228,6 +257,10 @@ class StandardsMCPServer(MCPServer):
|
||||
def drone() -> dict[str, Any]:
|
||||
return self._drone()
|
||||
|
||||
@self.app.resource("registry://release")
|
||||
def release() -> dict[str, Any]:
|
||||
return self._release()
|
||||
|
||||
@self.app.resource("docs://spec")
|
||||
def spec() -> dict[str, Any]:
|
||||
"""Return the pinned auth-server OpenAPI contract.
|
||||
@@ -380,6 +413,45 @@ class StandardsMCPServer(MCPServer):
|
||||
}
|
||||
return self._read_drone_component(entry)
|
||||
|
||||
@self.app.tool()
|
||||
def list_release_templates() -> list[dict[str, Any]]:
|
||||
"""List the release/versioning template artefacts.
|
||||
|
||||
These are the files every repo carries for releases: a Keep a
|
||||
Changelog ``CHANGELOG.md`` and, for services, a root ``VERSION``
|
||||
file. See standards "11_versioning_and_changelog".
|
||||
|
||||
Returns:
|
||||
list[dict[str, Any]]:
|
||||
Entries with ``kind``, ``path``, ``placeholder`` and
|
||||
``description``. Fetch one with :func:`get_release_template`.
|
||||
"""
|
||||
return self._release().get("components", [])
|
||||
|
||||
@self.app.tool()
|
||||
def get_release_template(kind: str) -> dict[str, Any]:
|
||||
"""Return the exact release standard file for a component kind.
|
||||
|
||||
Use this to drop the canonical changelog / version file into a repo
|
||||
instead of inventing one. See :func:`list_release_templates` for
|
||||
valid kinds ("changelog", "version-file").
|
||||
|
||||
Args:
|
||||
kind (str): Component kind, e.g. "changelog" or "version-file".
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: ``{kind, path, placeholder, content}`` or an
|
||||
error entry when the kind is unknown.
|
||||
"""
|
||||
entry = self._find_release_component(kind)
|
||||
if entry is None:
|
||||
return {
|
||||
"error": "unknown_kind",
|
||||
"kind": kind,
|
||||
"available": [c["kind"] for c in self._release().get("components", [])],
|
||||
}
|
||||
return self._read_release_component(entry)
|
||||
|
||||
@self.app.prompt()
|
||||
def intake() -> str:
|
||||
"""Return the questions to gather before building an app.
|
||||
|
||||
Reference in New Issue
Block a user