Skip to main content
Use this skill
atmos ai skill install atmos-scaffold
SKILL.md8.9 KB
View on GitHub

Atmos Scaffold

Use this skill for generating boilerplate (components, configs, directory structures) from templates via atmos scaffold generate, for authoring new templates (scaffold.yaml), and for updating previously-generated output from a changed template via --update.

For bootstrapping a brand-new Atmos project from the built-in template catalog, load atmos-init instead — it shares this exact engine but has its own command surface and built-in template list.

Quick Shape

apiVersion: atmos/v1
kind: AtmosScaffoldConfig
metadata:
name: terraform-component
description: Standard Terraform component structure
spec:
fields:
- name: component_name
label: Name of the component
type: input
required: true
atmos scaffold generate terraform-component ./components/terraform/vpc
atmos scaffold list
atmos scaffold validate ./components/terraform/vpc/scaffold.yaml

atmos scaffold ships experimental — behavior may change between releases.

Creating a Template

A template is a directory containing scaffold.yaml (the questionnaire and optional conditional-generation/hooks config) plus the files to generate. Files are auto-discovered by walking the template directory — there is no files: manifest listing every file (spec.files: exists only for the optional conditional-generation overlay, see below).

Mark a file as a Go template (rendered with the collected answers) either by:

  • Naming it with a .tmpl extension, or
  • Adding an atmos:template magic comment in the first 10 lines, in the comment style matching the file type: # atmos:template (shell/YAML/Python), // atmos:template (Go/JS/C++), /* atmos:template */ (C-style block), <!-- atmos:template --> (HTML/XML/Markdown)

Template sources: embedded (built into the Atmos binary), custom (declared under scaffold.templates in atmos.yaml), or catalog/remote (git/https/s3 — advertised as stubs, fetched on selection).

Form Fields

spec.fields is an ordered questionnaire; fields prompt in the order declared.

TypePrompt widget
input / text / stringFree-form text (huh Input)
selectSingle choice from options:
multiselectMultiple choices from options: (filterable)
confirm / bool / booleanYes/no

Common field keys: name (required, used as the template variable — access via {{ .Config.<name> }}), label, description, required, default, options (select/multiselect), placeholder (input), validation.pattern/message (regex, input fields only).

Conditional prompts (when:)

A field can declare when: to be shown only if a condition on earlier-declared fields' answers holds true:

spec:
fields:
- name: enable_monitoring
type: confirm
default: false
- name: alert_email
type: input
when: "answers.enable_monitoring == true" # only asked if confirmed above

when: accepts a predicate keyword (always, never, ci, local), a CEL string, or a list (implicit all). Reference collected answers via the answers map — e.g. "'dev' in answers.environments" for a multiselect, "answers.x == true" for a confirm (a bare answers.x is not valid CEL here — it's typed dyn, not bool; compare explicitly). Use CEL's &&/||/! for compound conditions — the {all:/any:/not:} map form is not accepted for scaffold when: (see references/scaffold-yaml-schema.md for why). A when: can only see fields declared before it in the list.

Full field/validation reference: references/scaffold-yaml-schema.md.

Conditional File Generation

spec.files: is an optional overlay gating specific auto-discovered files, keyed by their path in the template tree. Files not listed always generate.

spec:
files:
- path: stacks/deploy/dev.yaml
when: "'dev' in answers.environments"
- path: stacks/deploy/staging.yaml
when: "'staging' in answers.environments"

This is static gating over a fixed, enumerable set of files the template author already created — not a loop generating an unbounded number of files per answer (no for_each:; that's a future enhancement, see the PRD).

This is distinct from the older path-templating trick: if a file's path itself is a Go template that renders to "", "false", "null", or "<no value>", the engine skips it too (ShouldSkipFile). Prefer declarative when: for new templates — it's evaluated before any rendering and doesn't require crafting a path template.

Hooks

spec.hooks: runs step-backed actions before/after generation, keyed by hook name, reusing the exact vocabulary stack-level lifecycle hooks use — load atmos-hooks for the full events/kind/when/type/with reference and atmos-steps for the step types available in with:. Events are before.scaffold.generate and after.scaffold.generate; a hook with no events: matches both.

spec:
hooks:
git-add:
events:
- after.scaffold.generate
kind: step
type: shell
when: "size(answers.environments) > 0"
with:
command: "git add ."

Only kind: step/kind: steps are supported today. Stack-level command, scanner, store, git, and CI kinds require stack/component context that scaffold generation does not have. kind: step takes one registered step type in type: and its payload in with:; kind: steps takes an ordered with: list. Answers reach when: through the answers CEL variable and reach step bodies through {{ .Answers.<field> }} Go-template syntax.

Security: use --skip-hooks (skip all) or --skip-hooks=name1,name2 (skip specific hooks) to bypass hooks for a diagnostic or untrusted-template run — the same flag semantics terraform already has. ATMOS_SCAFFOLD_SKIP_HOOKS is the matching env var.

Updating Existing Projects (3-Way Merge)

atmos scaffold generate my-template ./target --update
atmos scaffold generate my-template ./target --update --base-ref=v1.2.0
atmos scaffold generate my-template ./target --update --merge-strategy=theirs
atmos scaffold generate my-template ./target --update --dry-run

--update performs a real 3-way merge (base = the git ref the target was generated from, defaulting to HEAD) instead of failing on a non-empty target directory. --merge-strategy controls conflict resolution: manual (surface conflicts, default), ours (keep your version), theirs (use the template's version). Full mechanics (base storage, conflict markers, the "offer to update instead of failing" interactive prompt): references/merge-strategy.md.

Commands and Flags

atmos scaffold generate [template] [target]: --force, --update, --base-ref, --dry-run, --interactive/-i (default true), --defaults (use defaults/--set without prompting), --set key=value (repeatable), --scaffold-source-override, --ref (git ref for a template source), --git/--no-git (default false — see atmos-init for the opposite default), --merge-strategy, --skip-hooks.

atmos scaffold list: templates from scaffold.templates in atmos.yaml (plus embedded/catalog). atmos scaffold validate [path]: validates scaffold.yaml against the JSON Schema.

Routing

NeedSkill
Stack hook kinds, lifecycle events, envelope (events/when/retry/on_failure)atmos-hooks
Every registered step type and aliases usable in a hook's with:atmos-steps
Go-template/Gomplate/Sprig functions available in file contentatmos-templates
Project bootstrap from the built-in template catalogatmos-init
Generated JSON Schema for IDE validationatmos-schemas
when:/CEL syntax referenceatmos-workflows

Guardrails

  • Prefer declarative spec.fields[].when:/spec.files[].when: over hand-rolled path templates or post-generation sed/shell cleanup.
  • Keep destructive post_generate hooks (deleting files, force-pushing, etc.) opt-in and visible in scaffold.yaml, mirroring atmos-hooks' guidance for stack hooks.
  • A when: can only reference fields/files declared earlier — referencing a not-yet-declared field silently sees its zero value, not an error; order fields deliberately.
  • Don't confuse the path-sentinel skip trick with declarative when: — use when: for new templates; the sentinel trick remains for backward compatibility.