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