# atmos scaffold

Give your team reusable templates for components, configuration, and projects. Scaffolds generate
consistent files from validated answers and can merge later template improvements into previously
generated output.

## Commands

| Command | Purpose |
| --- | --- |
| [`atmos scaffold list`](/cli/commands/scaffold/list) | List embedded, configured, and catalog templates. |
| [`atmos scaffold generate`](/cli/commands/scaffold/generate) | Generate or update output from a template. |
| [`atmos scaffold validate`](/cli/commands/scaffold/validate) | Validate a template's `scaffold.yaml` manifest. |

## Template Contract

Each template directory contains a versioned `scaffold.yaml` plus files to generate. Files are
discovered automatically; use `spec.files` only to conditionally gate discovered files.

```yaml title="scaffold.yaml"
apiVersion: atmos/v1
kind: AtmosScaffoldConfig
metadata:
  name: terraform-component
  description: Standard Terraform component
spec:
  fields:
    - name: component_name
      label: Component name
      type: input
      required: true
      validation:
        pattern: "^[a-z0-9-]+$"
        message: Use lowercase letters, digits, and hyphens.
    - name: environments
      label: Environments
      type: multiselect
      options: [dev, staging, prod]
      default: [dev]
```

`spec.fields` replaces the retired `prompts:` shape. Fields are available to rendered files as
`{{ .Config.component_name }}`. Atmos validates declared fields after it merges interactive input,
defaults, persisted `spec.values`, and repeated `--set key=value` flags, so automation follows the
same rules as an interactive run.

Supported field types are `input`, `text`, `string`, `select`, `multiselect`, `confirm`, `bool`,
`boolean`, and `computed`. Required fields reject missing or blank text and empty multiselect
values; `false` is a valid boolean answer. Select values must come from `options`, and text fields
can use `validation.pattern` with an optional `validation.message`. A `computed` field is never
prompted for — see [Computed Fields](/cli/commands/scaffold/generate#computed-fields) in the
`generate` reference.

## Conditions and Files

Use `when:` to make one template adapt to its answers. A condition can be `always`, `never`,
`ci`, or `local`; a CEL expression; or a list treated as an implicit logical `all`. A field can
only read answers from fields declared before it.

```yaml title="scaffold.yaml"
spec:
  fields:
    - name: enable_monitoring
      type: confirm
      default: false
    - name: alert_email
      type: input
      when: "answers.enable_monitoring == true"
  files:
    - path: monitoring/alerts.tf
      when: "answers.enable_monitoring == true"
    - path: stacks/staging.yaml
      when: "'staging' in answers.environments"
```

Use CEL operators such as `&&`, `||`, and `!` for compound conditions. The map form
`{all: ...}`, `{any: ...}`, or `{not: ...}` is not accepted in scaffold manifests.

## Generation Hooks

Scaffold hooks run around generation at `before.scaffold.generate` and
`after.scaffold.generate`. They use the shared hook envelope but intentionally support only
`kind: step` and `kind: steps` because generation has no stack/component context.

```yaml title="scaffold.yaml"
spec:
  hooks:
    format:
      events: [after.scaffold.generate]
      kind: step
      type: shell
      with:
        command: terraform fmt -recursive
    verify:
      events: [after.scaffold.generate]
      kind: steps
      when: "answers.enable_monitoring == true"
      with:
        - type: require
          tools: [terraform]
        - type: shell
          command: terraform validate
```

For `kind: step`, `type:` selects one registered [step type](/steps/type) and `with:`
is its configuration. For `kind: steps`, `with:` is an ordered step list. The envelope owns
`events`, `when`, `env`, `retry`, and `on_failure`; answer values are available in conditions as
`answers` and in step templates as `{{ .Answers.<field> }}`. Hooks run in stable name order.

A step's `working_directory:` defaults to (or, if bare-relative, resolves under) the scaffold's
target directory, also exposed as `{{ .TargetPath }}`, so the `format`/`verify` hooks above run
against the generated project. Set `working_directory: "."` to run a step in the directory
`atmos` was launched from instead. A `type: atmos` step is exempt from this default -- it keeps
running in the directory `atmos` was launched from when `working_directory:` is unset, since the
nested `atmos` invocation must resolve its own config there, but an explicit `working_directory:`
on that step is still honored.

Use `--skip-hooks` or `--skip-hooks=name1,name2` when inspecting an untrusted template or
diagnosing generation. See [lifecycle hooks](/stacks/hooks) for the broader stack-hook kinds and
the shared step bridge.

## Update Existing Output

```shell
atmos scaffold generate terraform-component ./components/terraform/vpc
atmos scaffold generate terraform-component ./components/terraform/vpc --update
atmos scaffold generate terraform-component ./components/terraform/vpc --update --merge-strategy=theirs
atmos scaffold generate terraform-component ./components/terraform/vpc --update --update-strategy=rendered
```

`--update` uses the recorded base revision to perform an optimistic three-way merge. The default
`manual` strategy surfaces real conflicts; `ours` keeps local changes and `theirs` applies the
template side of a conflict.

`--update-strategy` controls where the merge's _base_ comes from, independently of `--merge-strategy`.
`tracked` (the default) reads it from the target's own Git history. `rendered` instead re-renders
the template at the ref that produced what's currently on disk, using that generation's recorded
answers, with no Git history dependency at all. This needs two separate things, not one: the
template itself must define a `scaffold.yaml` manifest (so the old ref's fields can be resolved
when re-rendering), and the target must already carry a prior generation's `.atmos/scaffold.yaml`
record (a different file, at a different path — it stores that generation's recorded answers, not
the template's field definitions).

`--max-changes` (default `50`) caps the percentage of changed lines a three-way merge is allowed
to touch before `--update` fails outright instead of applying it. `0` disables this check
entirely (guaranteed to never fail); any other value is compared against a computed percentage
with no upper bound, so raising it only makes a hard failure less likely, never impossible.

## Project Initialization

[`atmos init`](/cli/commands/init) consumes the same manifest and generation engine for complete
project templates. Use `init` to select a built-in or catalog project starting point; use
`scaffold generate` to distribute an organization-specific component, configuration, or other
golden path.

## Learn More

- [Generate from a scaffold](/cli/commands/scaffold/generate)
- [Validate a scaffold](/cli/commands/scaffold/validate)
- [Workflow and hook step types](/steps/type)
- [Manage lifecycle events with hooks](/stacks/hooks)
