feat(standards): codify codegen, ship generic Tier-B infra, add get_app_template + prompts; fix docs:// regressions
P0: fix doc:// -> docs:// across standards src/server/registry and regenerate stale mongo-ops bundle. P1: build recipe + spec-first routing now document the openapi-first codegen fast path (models/routes with --inject-request/--package). P4: CRUD template documents the model-vs-document id rule (BaseDocument owns id). P5: authless guidance (omit security keys). P6: template gains generic infra (http_cache, keyed SSE bus, KV port+in-memory+Redis), interfaces/openapi.py + .dockerignore + generic build test registered as components, and a get_app_template tool; genericity matrix marks LLM pool + human-in-loop as reference-app patterns. P7: intake/build/start_app/trial prompts. P8: template ships tests/integration/test_openapi_app_builds.py. Rebuilt standards + openapi-first bundles and re-collected.
This commit is contained in:
194
registry.py
194
registry.py
@@ -6,7 +6,7 @@ helpers so an agent can find the right internal library — and pull exact
|
||||
template code — before writing any infrastructure.
|
||||
|
||||
Resources:
|
||||
doc://index, doc://nav, doc://modules/{module} (from the base server)
|
||||
docs://index, docs://nav, docs://modules/{module} (from the base server)
|
||||
registry://libraries (the structured catalog)
|
||||
registry://components (the template component index)
|
||||
|
||||
@@ -15,9 +15,13 @@ Tools:
|
||||
find_library (capability -> matching libraries + MCP endpoints)
|
||||
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)
|
||||
|
||||
Prompts:
|
||||
start_app (collect the two axes, then emit the build prompt)
|
||||
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)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -35,6 +39,50 @@ def _pascal(name: str) -> str:
|
||||
return "".join(part.capitalize() for part in re.split(r"[_\-\s]+", name) if part)
|
||||
|
||||
|
||||
_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; auth is deferred — `py-jwt` only).\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, resource=..., family=...)`.\n"
|
||||
"5. Author only Tier C; keep Tier B as shipped.\n"
|
||||
)
|
||||
|
||||
|
||||
class StandardsMCPServer(MCPServer):
|
||||
"""MCP server for the `standards` bundle with discovery/scaffolding tools.
|
||||
|
||||
@@ -72,6 +120,32 @@ class StandardsMCPServer(MCPServer):
|
||||
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],
|
||||
@@ -186,6 +260,66 @@ class StandardsMCPServer(MCPServer):
|
||||
}
|
||||
return self._read_component(entry, resource, family)
|
||||
|
||||
@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
|
||||
placeholder metadata. Seed tokens (``item`` / ``example``) are left
|
||||
as-is here — fetch a renamed file with :func:`get_app_component`.
|
||||
|
||||
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.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 = "",
|
||||
@@ -209,30 +343,36 @@ class StandardsMCPServer(MCPServer):
|
||||
str: The intake questions, or the build prompt when complete.
|
||||
"""
|
||||
if not app_name or not resources:
|
||||
return (
|
||||
"# Start a new app — intake\n\n"
|
||||
"Gather the following, then call `start_app` again 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; auth is deferred — `py-jwt` only).\n"
|
||||
"5. Integrations: Mongo / ingestion formats / email / LLM provider.\n\n"
|
||||
"Then: `start_app(app_name=..., resources=..., pipelines=..., auth=...)`.\n"
|
||||
)
|
||||
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"# 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 `doc://modules/build_recipe` and follow it step by step.\n"
|
||||
"2. Read `doc://modules/genericity_matrix` (Tier A/B/C) and "
|
||||
"`doc://modules/layering_rules`.\n"
|
||||
"3. Call `find_library(capability)` for db, di, routing, ingestion, "
|
||||
"and pipeline; never reimplement a Tier A library.\n"
|
||||
"4. Pull exact files with `list_app_components()` and "
|
||||
"`get_app_component(kind, resource=..., family=...)`.\n"
|
||||
"5. Author only Tier C; keep Tier B as shipped.\n"
|
||||
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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user