Interview → Manifest → Render
The heart of ai-core-kit is a small, frozen pipeline: an archetype-first interview
writes a project.manifest.yaml, which a renderer consumes to stamp out your project.
Everything here is the P3 contract — the stable interface several downstream
consumers build against.
templates/interview/questions.yaml is the deterministic question bank; the /ack-init interview is the sole writer of managed: (machine-owned), walking the bank in order with no LLM and validating before render. Render (P4) substitutes ${VAR}, the gate (P5) reads contract_gate.* + contracts[], and telemetry (P6) buckets OFFLINE cost via telemetry.* — all read-only, so the single source of truth never drifts mid-run.
questions.yaml ──(interview, no LLM)──▶ project.manifest.yaml ──(validate)──▶ RENDER
(the question bank) (single source of truth) (P4)One producer, many consumers
PRODUCER
/ack-init interview → the ONLY writer of the manifest
• writes managed: wholesale from templates/interview/questions.yaml
• seeds user: once on first run, then never touches it
• validates against the JSON-Schema BEFORE any render
CONSUMERS (read-only; none of them writes the manifest)
render engine → substitutes ${VAR} into the archetype scaffold
contract gate hook → enforces contract_gate.* + contracts[]
telemetry aggregator → buckets OFFLINE cost via telemetry.*The single-writer rule is what makes the manifest a trustworthy single source of truth: no consumer ever writes it back, so it cannot drift mid-run.
The four frozen artifacts
| Artifact | File | Role |
|---|---|---|
| Manifest schema (human) | templates/manifest/project.manifest.schema.yaml | Authoritative, annotated contract (schema_version: 3); doubles as a worked instance. |
| Manifest schema (machine) | templates/manifest/project.manifest.schema.json | JSON-Schema draft 2020-12 — the validator the interview runs. |
| Question bank | templates/interview/questions.yaml | The deterministic questions the interview walks in order. |
| Render contract | docs/RENDER-ENGINE.md | ${VAR} syntax, _when.* conditional dirs, render.map.yaml, idempotency. |
The question bank is deterministic
/ack-init walks questions.yaml in order, with no LLM in the loop: the same
answers in always produce a byte-identical manifest out (invariant I2). Each question
declares:
| Field | Meaning |
|---|---|
id | unique, stable, kebab-case; referenced by ask_if/skip_if. |
prompt | the human-facing question text. |
type | select | multiselect | text | bool. |
options | for select/multiselect: list of {value, label?}. |
default | pre-filled value; for select it must be one of options.value. |
applies_to | the archetypes this question is asked for, or all. |
ask_if / skip_if | optional gating expression over earlier answers. |
writes_to | dotted path under managed: where the answer is written. |
Archetype is the first, mandatory question (the branch axis)
The very first question is archetype (invariant I3). Its answer narrows every
subsequent question through applies_to gating — evaluated before ask_if/skip_if.
This is how, for example, persistence (DB/ORM/migration) questions fire only for the deep
archetypes, and the design-system question fires only for fullstack and saas.
The bank ships six archetypes. Three are deep — backend-api, fullstack, and
saas (the opinionated Vercel + Next.js App Router + shadcn/ui + Supabase + Clerk +
Drizzle + Stripe stack added in schema v3) — and three are minimal core: monorepo,
library-sdk, and infra-iac. The minimal-core archetypes answer only the universal
questions (identity, features, contract_gate.mode + protected_paths,
telemetry.enabled, ci_cd); the DB and design questions never fire for them — a
deterministic outcome of applies_to, not an LLM judgment call.
Infrastructure-as-code is orthogonal to the archetype axis: the feat_iac toggle and
the iac_provider/iac_tool questions let any archetype add an iac block (with
derived is_aws/is_gcp booleans), so IaC is a feature, not a seventh branch.
The tiny expression grammar
ask_if/skip_if use an intentionally small, deterministic grammar — operators ==,
!=, in, not_in, no parentheses or boolean chains — and may reference only
earlier questions. A referenced id that was itself skipped evaluates to an unknown
sentinel, which makes ==/in false and !=/not_in true (fail-safe: skip rather
than ask). Example: migrations_tool carries ask_if: "migrations_enabled == true", so
it never fires when migrations are disabled.
The full bank — all 45 questions and the branch matrix — is enumerated in the reference docs; the contract above is the part you need to reason about the flow.
The manifest envelope
schema_version: 3 # const 3; the FORMAT major version
generator: # provenance only; OUTSIDE the hash (re-runs stay byte-stable)
tool: ai-core-kit
tool_version: "0.4.0"
rendered_at: "..." # the only clock value; informational
managed: # MACHINE-OWNED. Regenerated wholesale every run.
manifest_hash: "sha256:..." # written LAST, over the canonical managed: subtree
project: { name, language, ... }
archetype: backend-api
features: { hooks, mcp, agent_teams, sdd_gate, iac } # iac is the v3 orthogonal toggle
persistence: { ... } # db now includes supabase (v3)
auth: { provider } # v3; saas defaults clerk
hosting: { target } # v3; saas defaults vercel
billing: { provider } # v3; saas defaults stripe
iac: { provider, tool, is_aws, is_gcp } # v3; present only when features.iac
contract_gate: { mode, protected_paths, scope, exempt }
contracts: [ ... ]
telemetry: { ... }
ci_cd: { target }
rendered_files: [ ... ] # ownership ledger; written by the renderer
user: # HUMAN-OWNED. Seeded once, then never overwritten.
notes: ""
overrides: {}Schema v3 is the current FORMAT major. It adds the saas archetype, makes
design_system required for both fullstack and saas, introduces the auth,
hosting, and billing objects, adds supabase to the persistence.db enum, and adds
the orthogonal features.iac toggle plus the iac block (whose is_aws/is_gcp are
derived, not asked). Every new key defaults to “off”, so a v2 → v3 migration is
render-neutral — but it is one-way and opt-in: /ack-init --migrate rewrites managed:
to the v3 shape and recomputes the hash, and a v2 child is refused (not auto-upgraded)
without it. Consumers refuse a mismatched major: the gate degrades to off + stderr,
the aggregator errors.
The split is the whole idempotency story: managed: is machine-owned and
regenerated every run; user: is human-owned and seeded exactly once. No consumer
reads user: behaviorally — it is the user’s scratch space (the renderer may consult
user.overrides.* for render vars but never requires it).
The global invariants
These are the frozen guarantees the whole pipeline rests on:
- I1 —
writes_tocross-check. Every question’swrites_tomust resolve to a real property declared in the schema./ack-initcross-checks this at startup and hard-refuses on any orphan key. The interview and the manifest can never silently diverge. - I2 — structural idempotency.
managed:/user:split plus a canonicalmanifest_hashplus therendered_files[]ledger give byte-identical output on identical answers. There is no 3-way merge engine. - I3 — archetype branching.
archetypeis the first mandatory question and narrows all subsequent questions. - I4 — the gate is never vacuous.
contract_gate.protected_pathsis required and non-empty per archetype, so eveninfra-iac(nosrc/**) can never ship an empty gate. - I5 —
additionalProperties: false. A typo’d key is a hard validation error, not a silent drop. - I6 — fail-closed at author time, fail-open at runtime. Invalid manifest at write
time aborts
/ack-init; corrupt/missing manifest at consumption time degrades the gate tooffplus stderr, never wedging a session. - I7 — forkability. The META discovery engine and
.claude/tooling are never copied into a child; rendered child paths use${CLAUDE_PROJECT_DIR}and${dotted.path}.
Validation gates the render
The order is load-bearing: answers → write manifest → validate → render. The
renderer may assume a schema-valid managed: subtree because an invalid manifest aborts
before any file is touched (author-time fail-closed). What happens next is the
Render engine contract.
See also: Render engine contract · The META vs CHILD boundary · the full question reference and branch matrix in the reference docs.