Skip to content

ADR-015: Shape registry as code (`shapes.yml`)

Status: Accepted Date: 2026-05-22

PRD §4 lists the supported shapes as a Markdown table. That’s fine for human reading but inadequate as a runtime source — agents need to validate plan shape: fields, the orchestrator needs to map shape→plugin, the shape-detection helper (ADR-008: Shape detection — .copier-answers.yml with sniffing fallback) needs the canonical list of valid identifiers, and /shared:new-service needs the template path per shape. Today all of this is implicit in §4’s prose.

Add shapes.yml at the platform repo root as the canonical machine-readable shape registry. PRD §4’s table is generated from shapes.yml (or maintained alongside with a CI consistency check; see Consequences). Agents read shapes.yml directly.

# shapes.yml — canonical shape registry for ika100-claude
shapes:
- id: service-python
plugin: svc
template: service-python
deployable: true
library: false
detection:
copier_src: templates/service-python
sniff:
- "pyproject.toml + Dockerfile"
default_stack:
language: python
version: "3.12"
framework: fastapi
status: stable
adr: null # null for shapes that pre-date the ADR log
- id: library-python
plugin: svc
template: library-python
deployable: false
library: true
detection:
copier_src: templates/library-python
sniff:
- "pyproject.toml without Dockerfile"
default_stack:
language: python
version: "3.12"
status: stable
adr: null
- id: web-nextjs
plugin: web
template: web-nextjs
deployable: true
library: false
detection:
copier_src: templates/web-nextjs
sniff:
- "next.config.*"
- "package.json contains \"next\""
default_stack:
language: typescript
runtime: node-lts
framework: nextjs-app-router
status: planned
adr: [002, 003, 004, 005]
- id: gitops-app
plugin: gitops
template: gitops-app
deployable: false # gitops-app is the GitOps source, not a deployable
library: false
detection:
copier_src: templates/gitops-app
sniff:
- "applications/*/applicationset.yaml"
default_stack:
kind: kustomize+argocd
status: planned
adr: [006, 014]
- id: service-java
plugin: svc-java
template: service-java
deployable: true
library: false
detection:
copier_src: templates/service-java
sniff:
- "pom.xml"
- "build.gradle*"
default_stack:
language: java
version: "21"
build_tool: maven
framework: spring-boot-3
status: planned
adr: [009]
- id: service-go
plugin: svc-go
template: service-go
deployable: true
library: false
detection:
copier_src: templates/service-go
sniff:
- "go.mod"
default_stack:
language: go
version: "1.22"
router: chi
status: planned
adr: [010]
Field Meaning
id Shape identifier — the value used in plan shape: fields, in /shared:new-service --type, and in shape-detection results
plugin Marketplace plugin that owns the shape’s agents
template Copier template directory under templates/
deployable If true, /shared:new-service tags the repo deployable-service for ArgoCD discovery
library If true, no Docker, no k8s, publishable artifact
detection.copier_src The _src_path tail in .copier-answers.yml that identifies this shape (primary detection)
detection.sniff Ordered sniffing-fallback markers (per ADR-008 fallback rules)
default_stack Free-form per-shape stack metadata (language, framework, version) — used for documentation and template defaults
status stable / planned / deprecated
adr List of ADR numbers governing this shape’s design decisions

The §4.1 extensibility contract now reads:

  1. Add a row to shapes.yml at the top.
  2. Copier template under templates/<id>/.
  3. Plugin at plugins/<plugin>/ (or extend an existing one).
  4. Update ADR-008: Shape detection — .copier-answers.yml with sniffing fallback sniffing table (the shapes.yml detection.sniff field is the source; ADR-008’s table is rendered from it OR maintained alongside with a CI check).

PRD §4’s Markdown table is then either regenerated by a scripts/render-shapes.py script, or kept in sync by a CI check that compares the two.

  • Agents need a machine-readable list to validate shape: fields. Today the architect could emit shape: web-nextjz (typo) and no orchestrator step would catch it before routing. shapes.yml plus a simple validator closes that hole.
  • Orchestrator routing becomes data, not code. shape: service-java → plugin: svc-java is a lookup, not an if/else chain that gets edited each time a shape is added.
  • /shared:new-service becomes uniform. It reads shapes.yml to find the template path, deployability flag, and plugin to enable (ADR-013: Plugin auto-enable on /shared:new-service). No per-shape special-casing in the command.
  • Single source of truth. Without shapes.yml, the same shape list appears in PRD §4, ADR-008’s tables, /shared:new-service logic, and individual agent prompts — guaranteed to drift.
  • A small platform script (scripts/validate-shapes.py) runs in CI to validate shapes.yml against a JSON Schema and to check that PRD §4 + ADR-008 tables match.
  • Adding a shape becomes mostly file edits — shapes.yml, then template, then plugin. The platform docs that reference shapes can be generated.
  • The shape registry is versioned via git like everything else; bumping a status from planned to stable is a one-line PR.
  • Existing prose in PRD §4 stays for human readability but is marked as derived from shapes.yml.
  • Discoverability: /shared:new-service rejects an unknown --type with the list of valid shapes read from shapes.yml. A separate /shared:shapes command was proposed and dropped (2026-10-07) as not worth its always-on prompt cost.