feat(standards): MR-based tag strategy + repository inventory (registry://repos)

Versioning standard now documents the MR flow: bump pyproject.toml/VERSION and promote CHANGELOG in the MR (referencing the version/tag, not a commit id), merge/squash, then tag the resulting commit. Adds the agent-assisted tagging procedure (wait for the go-ahead, find the version-bump commit, tag only it, ask the user to push) to both the reference page and the releasing wiki runbook. Adds the repo inventory (src/repos.json -> mcp/repos.json), the registry://repos resource + list_repos tool, the 15_repository_inventory lib page, and two MCP prompts: compliance (remediate repos) and release (agent tagging).
This commit is contained in:
2026-10-15 14:39:21 +05:30
parent df03f41771
commit 5971f26c08
36 changed files with 2107 additions and 94 deletions

View File

@@ -12,6 +12,7 @@ Resources:
registry://components (the template component index)
registry://drone (the CI template index)
registry://release (the release template index)
registry://repos (the repository inventory)
Tools:
ping (from the base server)
@@ -23,12 +24,15 @@ Tools:
get_drone_template (kind -> exact .drone.yml template)
list_release_templates (the changelog + version-file artefacts)
get_release_template (kind -> exact release template file)
list_repos (the covered repositories: type, path, version source)
Prompts:
intake (the questions to gather before building)
build (the two axes -> deterministic build prompt)
start_app (combined entry: intake, then build)
trial (self-contained trial build into a scratch dir)
compliance (remediate repos against the CI + versioning standard)
release (agent-assisted tagging after the release MR is merged)
"""
from __future__ import annotations
@@ -94,6 +98,45 @@ def _build_text(app_name: str, resources: str, pipelines: str, auth: str) -> str
)
_COMPLIANCE_TEXT = (
"# Compliance — align repositories with the CI + versioning standard\n\n"
"Read `docs://modules/drone_pipelines` and `docs://modules/versioning_and_changelog`,\n"
"then enumerate the working set with `registry://repos` (or `list_repos()`).\n\n"
"For each repository, bring it to the standard for its `type`:\n\n"
"- **package** — `version` in `pyproject.toml`; root `CHANGELOG.md`; Drone file\n"
" from `get_drone_template(\"drone-pypi\")` (`python-lib.yaml`, tag-triggered).\n"
"- **service / site** — root `VERSION`; root `CHANGELOG.md`; Drone file from\n"
" `get_drone_template(\"drone-service\")` (`service-docker.yaml`; tag-triggered\n"
" service variant, push-triggered site variant).\n\n"
"Steps:\n"
"1. Inspect each repo (`git -C <path> tag`, version file, `CHANGELOG.md`,\n"
" `.drone.yml`) and list the gaps — never assume.\n"
"2. Add missing files from `get_release_template(\"changelog\"/\"version-file\")`.\n"
"3. Replace `.drone.yml` with the guarded template for the repo's type.\n"
"4. Align the version file with the intended tag.\n"
"5. Commit per repo (one focused commit); **do not push or tag** without the\n"
" user's explicit go-ahead.\n"
)
_RELEASE_TEXT = (
"# Release — tag the merged commit\n\n"
"The version bump and changelog promotion live **in the release merge\n"
"request**; the tag is created on the commit that results from merging it.\n"
"See `docs://modules/versioning_and_changelog`.\n\n"
"Do this only after the user confirms the MR is merged/squashed and tells you\n"
"to proceed — never tag before the merge.\n\n"
"1. Wait for that explicit go-ahead.\n"
"2. Find the commit that introduced the version bump `x.y.z`:\n"
" - package: `git log -S 'version = \"x.y.z\"' -n 1 --format='%H %s' -- pyproject.toml`\n"
" - service / site: `git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`\n"
"3. Verify that commit's version file equals `x.y.z` (the Drone guard enforces\n"
" `tag == version`).\n"
"4. Tag **only** that commit, lightweight and bare: `git tag x.y.z <sha>`.\n"
"5. **Do not push.** Ask the user to run `git push origin x.y.z`, then Drone\n"
" builds the release.\n"
)
class StandardsMCPServer(MCPServer):
"""MCP server for the `standards` bundle with discovery/scaffolding tools.
@@ -146,6 +189,13 @@ class StandardsMCPServer(MCPServer):
return {"error": "not_found", "path": str(path), "components": []}
return json.loads(path.read_bytes())
def _repos(self) -> dict[str, Any]:
"""Load the repository inventory."""
path = Path(self.mcp_root) / "repos.json"
if not path.exists():
return {"error": "not_found", "path": str(path), "repos": []}
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:
@@ -258,9 +308,19 @@ class StandardsMCPServer(MCPServer):
return self._drone()
@self.app.resource("registry://release")
def release() -> dict[str, Any]:
def release_templates() -> dict[str, Any]:
return self._release()
@self.app.resource("registry://repos")
def repos() -> dict[str, Any]:
"""Return the repository inventory.
Use this to enumerate the repositories covered by the CI and
versioning standards, each with its type, path, version source, and
Drone shape. Paths are relative to the aetos/ workspace root.
"""
return self._repos()
@self.app.resource("docs://spec")
def spec() -> dict[str, Any]:
"""Return the pinned auth-server OpenAPI contract.
@@ -452,6 +512,27 @@ class StandardsMCPServer(MCPServer):
}
return self._read_release_component(entry)
@self.app.tool()
def list_repos(type: str | None = None) -> list[dict[str, Any]]:
"""List the repositories covered by the CI and versioning standards.
Each entry has: ``name``, ``type`` ("package" | "service" | "site"),
``path`` (relative to the aetos/ workspace root), ``version_source``
("pyproject.toml" | "VERSION"), ``drone`` (the template file),
``trigger`` ("tag" | "push"), ``docs`` kinds, and ``mcp``.
Args:
type (str | None): Optional filter, e.g. "package" or "site".
Returns:
list[dict[str, Any]]: Matching repository entries.
"""
repos = self._repos().get("repos", [])
if type:
needle = type.strip().lower()
return [r for r in repos if str(r.get("type", "")).lower() == needle]
return repos
@self.app.prompt()
def intake() -> str:
"""Return the questions to gather before building an app.
@@ -544,3 +625,28 @@ class StandardsMCPServer(MCPServer):
"back so the standard can be amended.\n\n"
f"Delete `{target}` once you are satisfied.\n"
)
@self.app.prompt()
def compliance() -> str:
"""Return the procedure to align repositories with the standard.
Use this to audit and remediate the repos listed in
`registry://repos` against the Drone and versioning standards.
Returns:
str: The compliance/remediation prompt.
"""
return _COMPLIANCE_TEXT
@self.app.prompt()
def release() -> str:
"""Return the agent-assisted tagging procedure.
Use after the user confirms the release MR is merged/squashed: find
the commit that bumped the version file, tag only that commit, and
ask the user to push the tag.
Returns:
str: The release/tagging prompt.
"""
return _RELEASE_TEXT