Scaffold Generator — From Specs to Code
Starter implementation files generated from your YAML specs.
What Scaffold Does
After sysmara init creates your project structure and sysmara build compiles route handlers into app/generated/, you're left with a gap: the app/ directories for entities, capabilities, policies, invariants, and services are empty. You (or an AI agent) must write everything from scratch.
The scaffold generator closes this gap. It reads your YAML specs and produces starter TypeScript files with typed interfaces, handler functions, enforcement logic, and validation stubs — all wired to the correct types and imports.
What Gets Generated
For each spec type, scaffold produces a corresponding TypeScript file:
Entity Interfaces (app/entities/)
Each entity in system/entities.yaml gets a TypeScript interface with typed fields (required fields are non-optional, optional fields use ?) and a runtime validation helper that checks required fields.
// app/entities/user.ts (generated by scaffold)
export interface User {
/** Unique user identifier */
id: string;
/** User email address */
email: string;
name: string;
role: string;
created_at: Date;
}
export function validateUser(data: unknown): data is User {
if (typeof data !== 'object' || data === null) return false;
const obj = data as Record<string, unknown>;
if (obj.id === undefined) return false;
if (obj.email === undefined) return false;
if (obj.name === undefined) return false;
if (obj.role === undefined) return false;
if (obj.created_at === undefined) return false;
return true;
} Capability Handlers (app/capabilities/)
Each capability gets a handler function that imports typed Input/Output interfaces from the generated route stub. The function body includes TODO comments for each policy to enforce, entity to load, and invariant to validate.
// app/capabilities/create_user.ts (generated by scaffold)
import type { HandlerContext } from '@sysmara/core';
import type { CreateUserInput, CreateUserOutput }
from '../generated/routes/create_user.js';
export async function handleCreateUser(
ctx: HandlerContext
): Promise<CreateUserOutput> {
const input = ctx.body as CreateUserInput;
// TODO: Enforce policy: user_creation_policy
// TODO: Load entity: user
// TODO: Implement business logic
// TODO: Validate invariant: email_must_be_unique
void input;
throw new Error('Not implemented: create_user');
} Policy Enforcers (app/policies/)
Each policy gets an enforcement function with the actor type, effect, and condition comments extracted from the YAML spec.
// app/policies/user_creation_policy.ts (generated by scaffold)
import type { ActorContext } from '@sysmara/core';
export function enforceUserCreationPolicy(
actor: ActorContext
): boolean {
// Condition: actor.role in [admin]
// TODO: Implement policy logic
void actor;
return false; // default deny
} Invariant Validators (app/invariants/)
Each invariant gets a validation function that imports the entity type and includes the rule description as a TODO.
// app/invariants/email_must_be_unique.ts (generated by scaffold)
import type { User } from '../entities/user.js';
export interface InvariantViolation {
invariant: string;
message: string;
severity: 'error' | 'warning';
}
export function validateEmailMustBeUnique(
entity: User
): InvariantViolation | null {
// TODO: Implement rule: "No two users may share
// the same email address"
void entity;
return null; // assume valid until implemented
} Module Services (app/services/)
Each module gets a service class with one method stub per capability the module owns. Method names are derived from capability names using camelCase.
// app/services/users.ts (generated by scaffold)
export class UsersService {
constructor() {
// TODO: inject repository, logger, external adapters
}
async createUser(input: unknown): Promise<unknown> {
void input;
throw new Error('Not implemented: create_user');
}
async getUser(input: unknown): Promise<unknown> {
void input;
throw new Error('Not implemented: get_user');
}
} How to Use
Standalone Command
npx sysmara scaffold Parses specs and generates all scaffold files. Skips any that already exist.
Integrated into Build
npx sysmara build Scaffold runs automatically as step 5 of the build pipeline, after capability compilation. No extra command needed.
Programmatic API
import { scaffoldSpecs, parseSpecDirectory } from '@sysmara/core';
const result = await parseSpecDirectory('./system');
const scaffold = scaffoldSpecs(result.specs!);
for (const file of scaffold.files) {
console.log(file.path); // "entities/user.ts"
console.log(file.source); // "entity:user"
// file.content contains the TypeScript source
} Key Design Decisions
Never Overwrites
Scaffold files live in the editable safe edit zone. Once generated, they are yours to modify freely. Re-running scaffold or build will skip any file that already exists. This means you can safely re-run the build pipeline after adding new entities or capabilities — only the new stubs will be generated.
Pure Generation
The scaffold engine is a pure function: it takes SystemSpecs and returns an array of ScaffoldFile objects. No file I/O happens inside the generator. The CLI layer handles existence checks and file writes. This makes the generator trivially testable and composable.
Spec-Driven, Not Template-Driven
Unlike template-based scaffolding tools that produce generic boilerplate, SysMARA's scaffold reads the actual field definitions, policy conditions, invariant rules, and capability bindings from your YAML specs. The generated code is specific to your system.
Relationship to Generated Code
Scaffold files and generated files serve different purposes:
Generated (app/generated/) | Scaffold (app/entities/, etc.) | |
|---|---|---|
| Edit zone | generated — do not edit | editable — your code |
| Regenerated on build | Yes, always overwritten | No, skipped if exists |
| Contains | Route stubs, test scaffolds, metadata | Entity types, handlers, policies, invariants, services |
| Purpose | Boilerplate wiring | Starting point for business logic |