Files
docs/registry.py
Vishesh 'ironeagle' Bangotra 0cb35d10a6 Phase 7: serve the vet sample from the template tools and docs
- build.py: _TEMPLATE_COMPONENTS rewritten around the vet sample (53 kinds —
  per-resource slices, jobs, worked appointment_reminder family, tests);
  seed tokens dropped from the manifest ('sample: vet' instead)
- registry.py: get_app_component(kind) no longer renames item/example seeds;
  prompts and tool docs updated to 'copy a slice and adapt'
- templates/README.md, 01_build_recipe, 40_ui_admin, wiki/03 updated off the
  old seed scheme; repo inventory/config now exclude vet (docs move under
  standards; react/vet itself is deleted in a later phase)
- regenerated MCP bundle, vendored templates, and site output
2026-10-20 14:58:17 +05:30

771 lines
32 KiB
Python

"""Standards MCP server.
Serves the hand-authored `standards` bundle (the application blueprint plus the
library registry and the runnable template) and adds discovery/scaffolding
helpers so an agent can find the right internal library — and pull exact
template code — before writing any infrastructure.
Resources:
docs://index, docs://nav, docs://modules/{module} (from the base server)
docs://spec (the pinned auth-server OpenAPI contract)
registry://libraries (the structured catalog)
registry://components (the app template component index)
registry://client (the client 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)
find_library (capability -> matching libraries + MCP endpoints)
list_app_components (the template component kinds)
get_app_component (kind -> exact template file)
get_app_template (the whole app skeleton as a path -> content map)
list_client_components (the client template component kinds)
get_client_component (kind -> exact client template file)
get_client_template (the whole client skeleton as a path -> content map)
list_drone_templates (the three CI shapes: PyPI package / npm 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)
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
import json
from pathlib import Path
from typing import Any
from config import MCPBundle
from mcp_server import MCPServer
_INTAKE_TEXT = (
"# Start a new app — intake\n\n"
"Gather the following, then call `build` (or `start_app`) with the values:\n\n"
"1. App name + one-line purpose.\n"
"2. Axis 1 — resources: each resource, its fields, and relationships\n"
" (or paste `openapi.yaml`).\n"
"3. Axis 2 — pipelines: multi-step inputs? families + formats (or `none`).\n"
"4. Auth needed now? (yes/no). If yes, the auth-server service provides it —\n"
" read `docs://spec` + `docs://modules/auth_integration` (no auth code to write).\n"
"5. Integrations: Mongo / ingestion formats / email / LLM provider.\n\n"
"Then: `build(app_name=..., resources=..., pipelines=..., auth=...)`.\n"
)
def _build_text(app_name: str, resources: str, pipelines: str, auth: str) -> str:
"""Render the deterministic build prompt from the two axes.
Args:
app_name (str): App name + one-line purpose.
resources (str): Axis 1 — resources, fields, relationships.
pipelines (str): Axis 2 — pipeline families + input formats.
auth (str): Whether auth is needed now ("yes"/"no").
Returns:
str: The build prompt.
"""
return (
f"# Build: {app_name}\n\n"
"## Spec\n"
f"- resources: {resources}\n"
f"- pipelines: {pipelines or 'none'}\n"
f"- auth: {auth}\n\n"
"## Method (deterministic)\n"
"1. Read `docs://modules/build_recipe` and follow it step by step.\n"
"2. Read `docs://modules/genericity_matrix` (Tier A/B/C) and "
"`docs://modules/layering_rules`.\n"
"3. Call `find_library(capability)` for db, di, routing, ingestion, "
"and pipeline; never reimplement a Tier A library.\n"
"4. Scaffold with `get_app_template()` (whole tree) or pull single "
"files with `list_app_components()` and `get_app_component(kind)` — "
"the template is the vet sample; copy a slice and adapt it to your "
"domain.\n"
"5. If a UI is needed, scaffold the client with `get_client_template()` "
"(or pull files via `list_client_components()` / "
"`get_client_component(kind)`) — the admin renders from your spec's x- "
"extensions; see `docs://modules/ui_admin`.\n"
"6. If auth is needed, read `docs://spec` (the auth-server contract) and "
"`docs://modules/auth_integration`; declare `securitySchemes.bearerAuth` "
"and `security` in your spec — do not write auth code.\n"
"7. Author only Tier C; keep Tier B as shipped.\n"
)
_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** (Python) — `version` in `pyproject.toml`; root `CHANGELOG.md`;\n"
" Drone file from `get_drone_template(\"drone-pypi\")` (`python-lib.yaml`,\n"
" tag-triggered).\n"
"- **package** (npm) — `version` in `package.json`; root `CHANGELOG.md`;\n"
" Drone file from `get_drone_template(\"drone-npm\")` (`npm-lib.yaml`,\n"
" tag-triggered, publishes to the private npm registry).\n"
"- **service** — root `VERSION`; root `CHANGELOG.md`; Drone file from\n"
" `get_drone_template(\"drone-service\")` (`service-docker.yaml`, tag-triggered\n"
" with a `tag == VERSION` guard). The service must read its version from\n"
" `VERSION` (e.g. a build arg) — never hardcode it.\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: `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.
Attributes:
mcp_root (Path):
Directory containing the generated bundle (index.json, nav.json,
modules/, libraries.json, components.json, and templates/).
"""
def __init__(self, bundle: MCPBundle) -> None:
super().__init__(bundle)
self._register_standards()
# ------------------------------------------------------------------
# Bundle readers
# ------------------------------------------------------------------
def _catalog(self) -> dict[str, Any]:
"""Load the machine-readable library registry."""
path = Path(self.mcp_root) / "libraries.json"
if not path.exists():
return {"error": "not_found", "path": str(path), "libraries": []}
return json.loads(path.read_bytes())
def _components(self) -> dict[str, Any]:
"""Load the template component manifest."""
path = Path(self.mcp_root) / "components.json"
if not path.exists():
return {"error": "not_found", "path": str(path), "components": []}
return json.loads(path.read_bytes())
def _spec(self) -> dict[str, Any]:
"""Load the pinned OpenAPI contract (auth-server)."""
path = Path(self.mcp_root) / "specs" / "auth-server.json"
if not path.exists():
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 _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 _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:
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:
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:
return entry
return None
def _template_files(self, prefix: str | None = None) -> dict[str, str]:
"""Read the vendored template tree as a path -> content map.
Args:
prefix (str | None): Optional path prefix to filter by (POSIX
separators, for example ``"infra/"``).
Returns:
dict[str, str]: Relative POSIX path -> UTF-8 file content, excluding
caches and bytecode.
"""
root = Path(self.mcp_root) / "templates" / "app"
files: dict[str, str] = {}
if not root.is_dir():
return files
for path in sorted(root.rglob("*")):
if not path.is_file():
continue
if "__pycache__" in path.parts or path.suffix == ".pyc":
continue
rel = path.relative_to(root).as_posix()
if prefix and not rel.startswith(prefix):
continue
files[rel] = path.read_text(encoding="utf-8")
return files
def _read_component(self, entry: dict[str, Any]) -> dict[str, Any]:
"""Read a vendored template file as-is (no token rewriting)."""
rel = entry["path"]
path = Path(self.mcp_root) / "templates" / "app" / 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 _client(self) -> dict[str, Any]:
"""Load the client template component manifest."""
path = Path(self.mcp_root) / "client.json"
if not path.exists():
return {"error": "not_found", "path": str(path), "components": []}
return json.loads(path.read_bytes())
def _find_client_component(self, kind: str) -> dict[str, Any] | None:
for entry in self._client().get("components", []):
if entry.get("kind") == kind:
return entry
return None
def _read_client_component(self, entry: dict[str, Any]) -> dict[str, Any]:
"""Read a vendored client template file."""
rel = entry["path"]
path = Path(self.mcp_root) / "templates" / "client" / 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 _client_files(self, prefix: str | None = None) -> dict[str, str]:
"""Read the vendored client template tree as a path -> content map.
Args:
prefix (str | None): Optional path prefix filter with POSIX
separators, for example ``"src/shared-theme/"``.
Returns:
dict[str, str]: Relative POSIX path -> UTF-8 file content,
excluding caches and build output.
"""
root = Path(self.mcp_root) / "templates" / "client"
files: dict[str, str] = {}
if not root.is_dir():
return files
for path in sorted(root.rglob("*")):
if not path.is_file():
continue
if "__pycache__" in path.parts or path.suffix == ".pyc":
continue
rel = path.relative_to(root).as_posix()
if prefix and not rel.startswith(prefix):
continue
files[rel] = path.read_text(encoding="utf-8")
return files
# ------------------------------------------------------------------
# Standards surface
# ------------------------------------------------------------------
def _register_standards(self) -> None:
@self.app.resource("registry://libraries")
def libraries() -> dict[str, Any]:
return self._catalog()
@self.app.resource("registry://components")
def components() -> dict[str, Any]:
return self._components()
@self.app.resource("registry://client")
def client() -> dict[str, Any]:
"""Return the client template component manifest.
The React client skeleton (Vite + MUI) that pairs with the app
template; the admin UI renders from the app's openapi.yaml x-
extensions via @aetoskia/react-openapi.
"""
return self._client()
@self.app.resource("registry://drone")
def drone() -> dict[str, Any]:
return self._drone()
@self.app.resource("registry://release")
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.
Use this (instead of a live fetch) to learn the auth-server client
contract when wiring authentication into an app.
"""
return self._spec()
@self.app.tool()
def find_library(capability: str) -> list[dict[str, Any]]:
"""Find internal libraries matching a capability keyword.
Use this before writing infrastructure: it returns the library to
use, its import name, what it replaces, and its MCP endpoint.
Args:
capability (str):
A keyword such as "database", "pdf", "auth", "pipeline",
"openapi", or "email".
Returns:
list[dict[str, Any]]:
Matching library entries (empty when nothing matches).
"""
needle = (capability or "").strip().lower()
if not needle:
return self._catalog().get("libraries", [])
matches: list[dict[str, Any]] = []
for lib in self._catalog().get("libraries", []):
haystack = " ".join(
[
str(lib.get("name", "")),
str(lib.get("import", "")),
str(lib.get("purpose", "")),
str(lib.get("use_instead_of", "")),
str(lib.get("spec", "")),
str(lib.get("recipe", "")),
" ".join(lib.get("capabilities", [])),
]
).lower()
if needle in haystack:
matches.append(lib)
return matches
@self.app.tool()
def list_app_components() -> list[dict[str, Any]]:
"""List the reusable component kinds shipped in the app template.
Returns:
list[dict[str, Any]]:
Entries with ``kind``, ``path``, ``placeholder`` and
``description``. Fetch one with :func:`get_app_component`.
"""
return self._components().get("components", [])
@self.app.tool()
def get_app_component(kind: str) -> dict[str, Any]:
"""Return the exact template file for a component kind.
Use this to copy current sample code instead of inventing it. The
template is the vet sample (parents/pets/treatments/vets/
appointments + jobs + the appointment_reminder pipeline family);
copy a slice and adapt it to your domain. See
:func:`list_app_components` for valid kinds.
Args:
kind (str): Component kind, e.g. "parent-routes", "pipeline-impl".
Returns:
dict[str, Any]: ``{kind, path, placeholder, content}`` or an
error entry when the kind is unknown.
"""
entry = self._find_component(kind)
if entry is None:
return {
"error": "unknown_kind",
"kind": kind,
"available": [c["kind"] for c in self._components().get("components", [])],
}
return self._read_component(entry)
@self.app.tool()
def get_app_template(prefix: str = "") -> dict[str, Any]:
"""Return the whole app skeleton as a path -> content map.
Use this to scaffold in one shot instead of pulling components one
by one; combine with :func:`list_app_components` for the kind and
description metadata. The tree is the vet sample — replace the
domain with your own, keeping the layer structure.
Args:
prefix (str): Optional path prefix filter with POSIX
separators, for example ``"infra/"`` or ``"pipelines/"``.
Empty returns every file.
Returns:
dict[str, Any]: ``{root, file_count, files: {relpath: content}}``.
"""
files = self._template_files(prefix or None)
return {
"root": "templates/app",
"file_count": len(files),
"files": files,
}
@self.app.tool()
def list_client_components() -> list[dict[str, Any]]:
"""List the reusable component kinds shipped in the client template.
The React client (Vite + MUI) that pairs with the app template:
the admin shell rendering the app's resources from its
``openapi.yaml``. See :func:`get_client_template` for the whole
tree and :func:`list_drone_templates` ("drone-npm") for its CI.
Returns:
list[dict[str, Any]]:
Entries with ``kind``, ``path``, ``placeholder`` and
``description``. Fetch one with :func:`get_client_component`.
"""
return self._client().get("components", [])
@self.app.tool()
def get_client_component(kind: str) -> dict[str, Any]:
"""Return the exact client template file for a component kind.
Use this to copy current, app-neutral client code instead of
inventing it. See :func:`list_client_components` for valid kinds
(e.g. "client-home", "client-header", "client-openapi-config").
Args:
kind (str): Component kind, e.g. "client-openapi-config".
Returns:
dict[str, Any]: ``{kind, path, placeholder, content}`` or an
error entry when the kind is unknown.
"""
entry = self._find_client_component(kind)
if entry is None:
return {
"error": "unknown_kind",
"kind": kind,
"available": [c["kind"] for c in self._client().get("components", [])],
}
return self._read_client_component(entry)
@self.app.tool()
def get_client_template(prefix: str = "") -> dict[str, Any]:
"""Return the whole client skeleton as a path -> content map.
Use this to scaffold the UI in one shot instead of pulling
components one by one; combine with :func:`list_client_components`
for the kind and description metadata.
Args:
prefix (str): Optional path prefix filter with POSIX
separators, for example ``"src/shared-theme/"``. Empty
returns every file.
Returns:
dict[str, Any]: ``{root, file_count, files: {relpath: content}}``.
"""
files = self._client_files(prefix or None)
return {
"root": "templates/client",
"file_count": len(files),
"files": files,
}
@self.app.tool()
def list_drone_templates() -> list[dict[str, Any]]:
"""List the CI (Drone) template shapes.
There are exactly three types of ``.drone.yml`` in the org: a
Python package published to the private PyPI index, an npm package
published to the private npm registry, 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-npm", "drone-service").
Args:
kind (str): Component kind, e.g. "drone-pypi", "drone-npm" 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.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 "91_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.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"),
``path`` (relative to the aetos/ workspace root), ``version_source``
("pyproject.toml" | "package.json" | "VERSION"), ``drone`` (the
template file), ``trigger`` ("tag"), ``docs`` kinds, and ``mcp``.
Args:
type (str | None): Optional filter, e.g. "package" or "service".
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.
Use this (or `start_app` with no arguments) to collect the two axes
and the integration list. Answer them, then call `build`.
Returns:
str: The intake questions.
"""
return _INTAKE_TEXT
@self.app.prompt()
def build(
app_name: str = "",
resources: str = "",
pipelines: str = "",
auth: str = "no",
) -> str:
"""Emit the deterministic build prompt from the two axes.
Args:
app_name (str): App name + one-line purpose.
resources (str): Axis 1 — resources, fields, relationships
(or a pasted openapi.yaml).
pipelines (str): Axis 2 — pipeline families + input formats.
auth (str): Whether auth is needed now ("yes"/"no").
Returns:
str: The build prompt, or the intake questions when the axes
are missing.
"""
if not app_name or not resources:
return _INTAKE_TEXT
return _build_text(app_name, resources, pipelines, auth)
@self.app.prompt()
def start_app(
app_name: str = "",
resources: str = "",
pipelines: str = "",
auth: str = "no",
) -> str:
"""Entry point to build a new app from the standards blueprint.
Call with no arguments to get the intake questions; call again with
the answers to get the deterministic build prompt.
Args:
app_name (str): App name + one-line purpose.
resources (str): Axis 1 — resources, fields, relationships
(or a pasted openapi.yaml).
pipelines (str): Axis 2 — pipeline families + input formats.
auth (str): Whether auth is needed now ("yes"/"no").
Returns:
str: The intake questions, or the build prompt when complete.
"""
if not app_name or not resources:
return _INTAKE_TEXT
return _build_text(app_name, resources, pipelines, auth)
@self.app.prompt()
def trial(app_name: str = "", build_path: str = "") -> str:
"""Prompt a self-contained trial build into a scratch directory.
Use this to validate the standard end-to-end without touching a real
app: generate into a throwaway path, prove it builds, and record
findings.
Args:
app_name (str): App to build for the trial.
build_path (str): Where to write the trial app.
Returns:
str: The trial prompt.
"""
target = build_path or f"apps/{app_name or '<app>'}-trial"
return (
f"# Trial: {app_name or '<app>'}\n\n"
f"Build into `{target}` (a throwaway path) and validate the "
"standard end-to-end.\n\n"
"## Steps\n"
"1. Run the `build` prompt to get the deterministic build prompt.\n"
f"2. Scaffold into `{target}` only — never an existing app.\n"
"3. Prove it: `python -c \"import main\"` and `pytest`.\n"
"4. Record every friction point as a finding: missing component, "
"wrong codegen shape, ambiguous doc, or missing prompt.\n"
"5. Do not fix the standard from inside the trial; report findings "
"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