Skip to content

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
}

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).

Deterministic file generation from atoms. No LLM required. Walks the atom set and emits typed output:

  • entity atoms → ORM models / TypeScript interfaces.
  • feature atoms with behavior.steps → action-chain code.
  • ui_view atoms with behavior.screens → view-tree code.
  • criterion atoms with behavior.before_after → scenario fixtures.

This is where @atomprd/codegen-senlang does most of its work — it’s almost entirely structural lowering.

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.

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:

  1. Calls scaffold(ctx).
  2. Calls structural(ctx).
  3. Calls behavioral(ctx) if ctx.llm is provided, else skips.
  4. Dedups on path (later layers win).
  5. Writes files to outDir, respecting generated: false on re-run.

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-behavior
POST /api/projects/:projectId/nodes/:nodeId/suggest-behavior

Both 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).

KindInference rule
featureWalk verifies reverse → criteria. Pull before_after.api_callscall steps. Pull state_diffsetState steps.
ui_viewWalk rendered_by → entity. Apply viewType template.
ruleWalk uses_rule reverse → features. Emit pre_call enforced_at entries.
criterionWalk verifies → feature. Pull feature’s steps into before_after.
fixtureRead 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.