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
This commit is contained in:
114
build.py
114
build.py
@@ -194,43 +194,61 @@ def build_bundle(folder: Path, dry_run: bool = False) -> None:
|
||||
|
||||
# ── Template bundle ──────────────────────────────────────────────────────────
|
||||
|
||||
# Component kinds shipped in the vendored template. ``placeholder`` names the
|
||||
# seed token an agent renames: "resource" -> item/Item, "family" -> example/
|
||||
# Example; "" means ship as-is.
|
||||
# Component kinds shipped in the vendored template. The template is the vet
|
||||
# sample: a concrete veterinary-clinic domain (parents, pets, treatments,
|
||||
# vets, appointments + a jobs queue for the worker). Agents copy slices and
|
||||
# adapt them to their own domain — no seed-token renaming. ``placeholder`` is
|
||||
# always "" and kept only for manifest-shape compatibility with the client /
|
||||
# drone / release bundles.
|
||||
_TEMPLATE_COMPONENTS: list[dict[str, str]] = [
|
||||
# Per-resource slice (seed: item/Item).
|
||||
{"kind": "model", "path": "domain/item/models.py", "placeholder": "resource",
|
||||
"description": "Business Pydantic model for a resource."},
|
||||
{"kind": "ports-db", "path": "domain/item/ports/db.py", "placeholder": "resource",
|
||||
"description": "Document model + repository factory for a resource."},
|
||||
{"kind": "ports-service", "path": "domain/item/ports/service.py", "placeholder": "resource",
|
||||
"description": "CRUD service wired through the DI library."},
|
||||
{"kind": "converters", "path": "domain/item/ports/converters.py", "placeholder": "resource",
|
||||
"description": "Optional pure converters between domain types."},
|
||||
{"kind": "routes", "path": "interfaces/item/routes.py", "placeholder": "resource",
|
||||
"description": "Thin HTTP handlers bound to operationIds."},
|
||||
# Per-resource slices — copy one as the pattern for a new aggregate.
|
||||
# parents: simplest slice (no foreign keys).
|
||||
{"kind": "parent-model", "path": "domain/parents/models.py", "placeholder": "",
|
||||
"description": "Example domain model (parents — the simplest slice)."},
|
||||
{"kind": "parent-ports-db", "path": "domain/parents/ports/db.py", "placeholder": "",
|
||||
"description": "Example document model + repository factory."},
|
||||
{"kind": "parent-ports-service", "path": "domain/parents/ports/service.py", "placeholder": "",
|
||||
"description": "Example CRUD service wired through the DI library."},
|
||||
{"kind": "parent-routes", "path": "interfaces/parents/routes.py", "placeholder": "",
|
||||
"description": "Example thin HTTP handlers bound to operationIds."},
|
||||
# pets: embedded list-of-refs + file upload.
|
||||
{"kind": "pet-model", "path": "domain/pets/models.py", "placeholder": "",
|
||||
"description": "Domain model with an embedded parents list."},
|
||||
{"kind": "pet-ports-db", "path": "domain/pets/ports/db.py", "placeholder": "",
|
||||
"description": "Populating document + repository factory (FK wiring)."},
|
||||
{"kind": "pet-routes", "path": "interfaces/pets/routes.py", "placeholder": "",
|
||||
"description": "Routes including the file-upload endpoint."},
|
||||
# appointments: single FKs expanded on read, filtered list.
|
||||
{"kind": "appointment-model", "path": "domain/appointments/models.py", "placeholder": "",
|
||||
"description": "Domain model with single-id FKs + nested value objects."},
|
||||
{"kind": "appointment-routes", "path": "interfaces/appointments/routes.py", "placeholder": "",
|
||||
"description": "Routes with expand_ref FK handling and list filters."},
|
||||
# jobs: the worker's queue (second resource shipped end to end).
|
||||
{"kind": "job-model", "path": "domain/jobs/models.py", "placeholder": "",
|
||||
"description": "Job queue model (the worker's unit of work)."},
|
||||
{"kind": "job-ports-db", "path": "domain/jobs/ports/db.py", "placeholder": "",
|
||||
"description": "Job repository factory."},
|
||||
{"kind": "job-ports-service", "path": "domain/jobs/ports/service.py", "placeholder": "",
|
||||
"description": "Job CRUD service."},
|
||||
{"kind": "job-routes", "path": "interfaces/jobs/routes.py", "placeholder": "",
|
||||
"description": "Job routes (enqueue/inspect via the API)."},
|
||||
# Interface-layer shared files.
|
||||
{"kind": "interface-common", "path": "interfaces/_common.py", "placeholder": "",
|
||||
"description": "Shared helpers: svc resolution, dump, pagination, expand_ref."},
|
||||
{"kind": "openapi-routes", "path": "interfaces/openapi.py", "placeholder": "",
|
||||
"description": "Route registry: flat re-export of every handler namespace."},
|
||||
{"kind": "auth", "path": "interfaces/auth/security.yaml", "placeholder": "",
|
||||
"description": "Mergeable bearerAuth security scheme + global security for openapi.yaml."},
|
||||
{"kind": "test-model", "path": "tests/unit/test_item_models.py", "placeholder": "resource",
|
||||
"description": "Unit test for the resource model."},
|
||||
{"kind": "test-routes", "path": "tests/integration/test_item_routes.py", "placeholder": "resource",
|
||||
"description": "Integration test at the HTTP edge."},
|
||||
{"kind": "test-openapi-build", "path": "tests/integration/test_openapi_app_builds.py", "placeholder": "",
|
||||
"description": "Generic test: every spec operationId resolves and the app builds."},
|
||||
{"kind": "test-pipeline", "path": "tests/unit/test_item_sync.py", "placeholder": "",
|
||||
"description": "Unit test for the smallest pipeline form."},
|
||||
# Per-family pipeline scaffold (seed: example/Example).
|
||||
{"kind": "pipeline-abc", "path": "pipelines/example/abc.py", "placeholder": "family",
|
||||
# Worked pipeline family (appointment_reminder) — the copy-me pattern.
|
||||
{"kind": "pipeline-abc", "path": "pipelines/appointment_reminder/abc.py", "placeholder": "",
|
||||
"description": "Port contracts for a pipeline family."},
|
||||
{"kind": "pipeline-impl", "path": "pipelines/example/impl.py", "placeholder": "family",
|
||||
{"kind": "pipeline-impl", "path": "pipelines/appointment_reminder/impl.py", "placeholder": "",
|
||||
"description": "Concrete stages, wired root, and family registration."},
|
||||
{"kind": "pipeline-yaml", "path": "pipelines/example/pipeline.yaml", "placeholder": "family",
|
||||
{"kind": "pipeline-yaml", "path": "pipelines/appointment_reminder/pipeline.yaml", "placeholder": "",
|
||||
"description": "Graph spec (nodes, edges, roots, schema)."},
|
||||
{"kind": "pipeline-resources", "path": "pipelines/example/resources/pipeline.yaml", "placeholder": "family",
|
||||
{"kind": "pipeline-resources", "path": "pipelines/appointment_reminder/resources/pipeline.yaml", "placeholder": "",
|
||||
"description": "Runtime spec consumed by the shared loader."},
|
||||
{"kind": "pipeline-pattern", "path": "pipelines/example/resources/patterns/example.yaml", "placeholder": "family",
|
||||
{"kind": "pipeline-pattern", "path": "pipelines/appointment_reminder/resources/patterns/reminder.yaml", "placeholder": "",
|
||||
"description": "Pattern/rule fragment merged by the loader."},
|
||||
# Shared pipeline scaffolding (ship as-is).
|
||||
{"kind": "pipeline-ports", "path": "pipelines/shared/ports.py", "placeholder": "",
|
||||
@@ -239,32 +257,23 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [
|
||||
"description": "Shared loader: DI wiring + pattern merge."},
|
||||
{"kind": "pipeline-registry", "path": "pipelines/shared/registry.py", "placeholder": "",
|
||||
"description": "Shared registry: (kind, variant, mode) -> root class."},
|
||||
{"kind": "pipeline-simple", "path": "pipelines/item_sync.py", "placeholder": "",
|
||||
"description": "Smallest pipeline form (plain async steps)."},
|
||||
# Second resource slice (seed: job).
|
||||
{"kind": "job-model", "path": "domain/job/models.py", "placeholder": "",
|
||||
"description": "Seeded second resource model (job)."},
|
||||
{"kind": "job-ports-db", "path": "domain/job/ports/db.py", "placeholder": "",
|
||||
"description": "Seeded second resource repository (job)."},
|
||||
{"kind": "job-ports-service", "path": "domain/job/ports/service.py", "placeholder": "",
|
||||
"description": "Seeded second resource service (job)."},
|
||||
{"kind": "job-routes", "path": "interfaces/job/routes.py", "placeholder": "",
|
||||
"description": "Seeded second resource routes (job)."},
|
||||
# Root / shared files (ship as-is).
|
||||
{"kind": "config", "path": "config.py", "placeholder": "",
|
||||
"description": "Settings dataclass + singleton."},
|
||||
"description": "Settings dataclass + singleton (incl. worker knobs)."},
|
||||
{"kind": "init", "path": "init.py", "placeholder": "",
|
||||
"description": "Composition root (init/shutdown, get_svc)."},
|
||||
"description": "Composition root (init/shutdown, resources dict)."},
|
||||
{"kind": "main", "path": "main.py", "placeholder": "",
|
||||
"description": "Spec-first app bootstrap + lifespan."},
|
||||
{"kind": "worker", "path": "worker.py", "placeholder": "",
|
||||
"description": "Poll loop + job status machine."},
|
||||
"description": "Poll loop + job dispatch by kind + status machine."},
|
||||
{"kind": "openapi", "path": "openapi.yaml", "placeholder": "",
|
||||
"description": "OpenAPI contract skeleton."},
|
||||
"description": "OpenAPI contract (x- extensions drive the admin client)."},
|
||||
{"kind": "pyproject", "path": "pyproject.toml", "placeholder": "",
|
||||
"description": "Packaging + dependency pins."},
|
||||
{"kind": "changelog", "path": "CHANGELOG.md", "placeholder": "",
|
||||
"description": "Keep a Changelog file with an [Unreleased] section (versioning standard)."},
|
||||
{"kind": "version-file", "path": "VERSION", "placeholder": "",
|
||||
"description": "Single-line version file (service-docker CI checks tag == VERSION)."},
|
||||
{"kind": "dockerfile", "path": "Dockerfile", "placeholder": "",
|
||||
"description": "API + worker image with private-index wiring."},
|
||||
{"kind": "dockerignore", "path": ".dockerignore", "placeholder": "",
|
||||
@@ -279,12 +288,18 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [
|
||||
"description": "Mongo connection lifecycle."},
|
||||
{"kind": "repo", "path": "infra/db/repos/mongo.py", "placeholder": "",
|
||||
"description": "Repository factory (base/populating/cached)."},
|
||||
{"kind": "repo-int", "path": "infra/db/repos/mongo_int.py", "placeholder": "",
|
||||
"description": "Integer-id repository variants (create_repo factory)."},
|
||||
{"kind": "repo-memory", "path": "infra/db/repos/memory.py", "placeholder": "",
|
||||
"description": "In-memory repository flavour (zero-dependency demo store)."},
|
||||
{"kind": "service-adapter", "path": "infra/adapters/service.py", "placeholder": "",
|
||||
"description": "Generic CRUD service adaptor."},
|
||||
{"kind": "http-cache", "path": "infra/http_cache.py", "placeholder": "",
|
||||
"description": "Response cache decorator + Redis store (generation invalidation)."},
|
||||
{"kind": "progress-bus", "path": "infra/adapters/sse.py", "placeholder": "",
|
||||
"description": "In-memory SSE progress bus (keyed by an event attribute)."},
|
||||
{"kind": "actions-stream", "path": "infra/adapters/actions_stream.py", "placeholder": "",
|
||||
"description": "Per-resource action stream (SSE) adaptor."},
|
||||
{"kind": "kv-store", "path": "infra/adapters/kv.py", "placeholder": "",
|
||||
"description": "KV port + in-memory fallback + optional Redis adaptor."},
|
||||
{"kind": "package-init", "path": "domain/__init__.py", "placeholder": "",
|
||||
@@ -293,6 +308,15 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [
|
||||
"description": "Operator seed script."},
|
||||
{"kind": "script-drop", "path": "scripts/drop.py", "placeholder": "",
|
||||
"description": "Operator drop script."},
|
||||
# Tests shipped with the sample.
|
||||
{"kind": "test-openapi-build", "path": "tests/integration/test_openapi_app_builds.py", "placeholder": "",
|
||||
"description": "Generic test: every spec operationId resolves and the app builds."},
|
||||
{"kind": "test-jobs-routes", "path": "tests/unit/test_jobs_routes.py", "placeholder": "",
|
||||
"description": "Unit test pattern for a resource's routes with service fakes."},
|
||||
{"kind": "test-pipeline", "path": "tests/unit/test_appointment_reminder_pipeline.py", "placeholder": "",
|
||||
"description": "Unit test pattern for a pipeline family."},
|
||||
{"kind": "test-worker", "path": "tests/unit/test_worker.py", "placeholder": "",
|
||||
"description": "Unit test pattern for worker dispatch and status transitions."},
|
||||
]
|
||||
|
||||
|
||||
@@ -303,7 +327,7 @@ def build_components_manifest(template_root: Path) -> dict[str, object]:
|
||||
template_root (Path): The vendored ``templates/app`` directory.
|
||||
|
||||
Returns:
|
||||
dict[str, object]: Manifest with the seed tokens and component entries.
|
||||
dict[str, object]: Manifest with the component entries.
|
||||
"""
|
||||
components: list[dict[str, str]] = []
|
||||
for entry in _TEMPLATE_COMPONENTS:
|
||||
@@ -319,7 +343,7 @@ def build_components_manifest(template_root: Path) -> dict[str, object]:
|
||||
return {
|
||||
"project": "standards",
|
||||
"template": "templates/app",
|
||||
"seed": {"resource": "item", "family": "example"},
|
||||
"sample": "vet",
|
||||
"components": components,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user