Project Builder Documentation

website·Indexed 32 days ago

https://schematics.pbuilder.dev

Deterministic code generation designed to complement AI. Project Builder allows developers to write typed, testable, and idempotent schematics to ensure consistent software mutation. The platform includes a CLI, an SDK for authoring mutation verbs, AST-aware editing for TypeScript and React, and a template language using Go text/template syntax.

Tokens
26.5K
Snippets
105
Records
164
Agent score
57%

What's inside Project Builder

  1. Overview of pbuilder mutation verbs

    Mutation verbs are used within a factory to schedule changes to a file tree. These verbs do not touch the disk immediately; they schedule directives that are flushed during the engine's apply phase after the factory returns. If an error is thrown before the factory returns, no changes are written to disk.

    Most verbs that write to a new path are 'fail-closed' on collision (throwing an AuthoringError with reason: "path-collision") unless { force: true } is provided.

  2. Overview of Project Builder and Schematics

    Project Builder provides deterministic code generation to ensure software consistency and repeatability. Unlike AI prompts, which are variable, Project Builder uses 'schematics'—typed, testable file-mutation programs—to produce the exact same files every time. This allows developers and AI agents to encode a change once and execute it as a program rather than reviewing variable AI output.
  3. Overview of pbuilder template syntax

    The template language used in create() and scaffold() is based on Go's text/template syntax, not JavaScript. It uses {= =} delimiters. Rendering occurs at builder execute time in the engine, not in the SDK. Both the path and the template (or templateFile) are rendered against a single shared options object.
    import { create } from "@pbuilder/sdk/commons";
    
    create("src/{= .name | dasherize =}.ts", {
      template: "export const {= .name | classify =} = 1;\n",
      options: { name: "myThing" },
    });
    // Renders to: src/my-thing.ts
    // Content: export const MyThing = 1;
  4. Understand the Project Builder development roadmap

    Project Builder is developed in four progressive levels to ensure a foundation of determinism before introducing composition and orchestration. The core guarantee across all levels is that the same input always produces the same bytes.

    • Level 1: Local Schematics (Current Phase): Focuses on authoring typed, testable, and deterministic schematics within a single repository. This includes the use of seven mutation verbs, templates, AST-aware dialects, and an in-memory testing harness. Effectiveness is validated via 'workbenches' (real-world project implementations).
    • Level 2: Interconnected Schematics: Introduces composition, allowing schematics to invoke other schematics and chain generation into larger flows while maintaining all-or-nothing reproducibility.
    • Level 3: External Schematics: Enables the consumption of schematics from outside the local repository via private organizational registries or public ecosystem packages.
    • Level 4: Project Builder (Full Vision): The final stage where Project Builder acts as a full orchestrator of schematics, managing generation pipelines that span multiple projects and teams.
  5. Project Builder Architecture Overview

    Project Builder splits file mutation into three distinct components to ensure reliability and determinism:

    1. SDK (@pbuilder/sdk): Produces Instruction Records (IR)—JSON directives describing intended changes (e.g., create, move). It handles the developer experience, including mutation verbs, templates, and AST-based dialects.
    2. Engine: A Go-based component that receives IRs and applies them to a staging tree. It is 'AST-blind' and only writes to disk after a factory returns successfully, ensuring that failed runs leave no partial changes.
    3. CLI (builder): The agent-facing entry point (init, new, execute). It validates input, resolves schematics, and starts the engine. It outputs structured JSON for AI agent consumption.
  6. Project Builder CLI Command Overview

    The builder binary is the entry point for Project Builder, used to parse commands, validate input, and orchestrate schematic engines. Use builder --help for the top-level listing and builder <command> --help for specific command usage.
    builder --help
    builder --version
  7. Install Bun runtime for Project Builder schematics

    Project Builder schematics execute through a Bun sidecar, so Bun must be installed on the machine. Note that Bun is only used as the runtime for the schematics; your project can continue using its own package manager (npm, pnpm, yarn, etc.).
    brew install oven-sh/bun/bun
    # OR
    curl -fsSL https://bun.sh/install | bash
  8. Perform custom AST edits using .modify()

    When the built-in named operations (like addImport or addFunction) are insufficient, use the .modify(fn) method available on every dialect handle. This provides a direct escape hatch to the underlying AST library (ts-morph for TypeScript and React dialects) to perform complex or specific mutations.
  9. Configure SDK version requirements

    The builder CLI requires a numeric floor for the SDK version rather than a semver range. Leading operators like ^, ~, or >= are ignored; the installed version must be greater than or equal to the remaining number. For example, >=0.1.0 establishes a 0.1.0 floor. To declare a version requirement without adding a package dependency, set it in project-builder.json.
    {
      "sdk": {
        "version": "0.2.4"
      }
    }
  10. Trial Project Builder without adding SDK to package.json

    You can initialize Project Builder in an existing repository without modifying package.json by using the --no-sdk-dependency flag. Note that while package.json remains untouched, builder init will still create or modify other files: project-builder.json, schematics/.gitkeep, .claude/skills/pbuilder/ files, and marker blocks in AGENTS.md or CLAUDE.md.
    # Preview the initialization plan
    builder init . --no-sdk-dependency --dry-run --non-interactive
    
    # Initialize the workspace
    builder init . --no-sdk-dependency --non-interactive
  11. Iterate over arrays and objects with range

    Use the range keyword to loop over arrays or objects. Blocks must be closed with {= end =}. Inside a range block, the dot . represents the current element. To access the index and value, use the := assignment syntax. Ranging over objects iterates keys in sorted order; arrays preserve their original order.
    // Simple loop (item is .)
    {= range .methods =}  {= .name =}() {}
    {= end =}
    
    // Loop with index and value
    {= range $i, $v := .items =}{= $i =}:{= $v =} {= end =}
    
    // Loop over object keys/values
    {= range $k, $v := .obj =} … {= end =}
  12. Mirror a template folder using scaffold()

    Use the scaffold() function to mirror an entire folder of template files into the project tree. This is the recommended approach for multi-file generators instead of calling create() for every individual file. The from path is package-local, resolved relative to the schematic's folder. Files are automatically classified: text files within budget are rendered as templates, while binary or over-budget files are copied verbatim.
    import { scaffold } from "@pbuilder/sdk/commons";
    import type { Input } from "./schema.generated.ts";
    
    export default (input: Input) => {
      scaffold({
        from: "files", // folder relative to schematic package
        to: "src/services/__name@dasherize__",
        options: { name: input.name },
      });
    };