Architecture
@atomprd/codegen defines a 3-layer template interface that every codegen target implements.
export interface CodegenTemplate { name: string; scaffold(ctx: CodegenContext): Promise<OutputFile[]>; // Layer 1: static files structural(ctx: CodegenContext): Promise<OutputFile[]>; // Layer 2: deterministic behavioral(ctx: CodegenContext): Promise<OutputFile[]>; // Layer 3: LLM-heavy}export interface CodegenContext { doc: PrdDocument; // wrapped manifest + helpers outDir: string; // where to write llm?: LlmClient; // optional, for Layer 3 flags?: Record<string, unknown>;}
export interface OutputFile { path: string; contents: string | Uint8Array; generated: boolean; // true = safe to overwrite on re-run}The 3 layers
Section titled “The 3 layers”Layer 1 — Scaffold
Section titled “Layer 1 — Scaffold”Static files that don’t depend on atoms. package.json, tsconfig.json, project README, the entry point.
Idempotent. Writes once, leaves alone on re-run if generated: false (user-edited).
Layer 2 — Structural
Section titled “Layer 2 — Structural”Deterministic file generation from atoms. No LLM required. Walks the atom set and emits typed output:
entityatoms → ORM models / TypeScript interfaces.featureatoms withbehavior.steps→ action-chain code.ui_viewatoms withbehavior.screens→ view-tree code.criterionatoms withbehavior.before_after→ scenario fixtures.
This is where @atomprd/codegen-senlang does most of its work — it’s almost entirely structural lowering.
Layer 3 — Behavioral (optional)
Section titled “Layer 3 — Behavioral (optional)”LLM-heavy passes for things that can’t be lowered deterministically. Style polish, comment generation, error message wording, README copy.
A target can leave behavioral empty (async behavioral(ctx) { return [] }) — many do. The llm client is optional in CodegenContext.
The driver — runTemplate
Section titled “The driver — runTemplate”import { runTemplate } from "@atomprd/codegen";
const result = await runTemplate(myTemplate, { doc: prdDocument, outDir: "./out", llm: optionalLlmClient,});
// result.files = OutputFile[] across all 3 layers, deduped on path// result.errors = CodegenError[] (e.g. invalid atom shapes encountered)The driver:
- Calls
scaffold(ctx). - Calls
structural(ctx). - Calls
behavioral(ctx)ifctx.llmis provided, else skips. - Dedups on path (later layers win).
- Writes files to
outDir, respectinggenerated: falseon re-run.
Pattern C cloud-side service
Section titled “Pattern C cloud-side service”Codegen runs at build time. Pattern C inference (filling Layer 2 from related atoms) runs at authoring time, in the cloud authoring API:
POST /api/projects/:projectId/nodes/:nodeId/infer-behaviorPOST /api/projects/:projectId/nodes/:nodeId/suggest-behaviorBoth return:
{ behavior: Record<string, unknown>; provenance: Record<string, ProvenanceEntry>; pattern: "C" | "D"; notes?: string;}Pattern C logic per atom kind lives in behaviors.service.ts (private repo).
| Kind | Inference rule |
|---|---|
feature | Walk verifies reverse → criteria. Pull before_after.api_calls → call steps. Pull state_diff → setState steps. |
ui_view | Walk rendered_by → entity. Apply viewType template. |
rule | Walk uses_rule reverse → features. Emit pre_call enforced_at entries. |
criterion | Walk verifies → feature. Pull feature’s steps into before_after. |
fixture | Read metadata.linked_criteria. Pull each criterion’s before_after into the fixture. |
Pattern D wraps AiService.callForJson with a kind-specific prompt and the atom’s Layer 1 + current metadata. Cached at sha256(prompt + system + model + version) by AiCache.
See also
Section titled “See also”- SenLang bridge — the reference target’s lowering rules.
- Custom targets — adding your own template.
- Spec → Packages — package status.