# CLI commands

> Every stacks command and subcommand, with its flags, defaults, JSON result schema, exit codes and examples.

Every `stacks` command, its flags and what it does. `stacks <command> --help` prints the same flags with examples and exit codes, and `stacks schema <name>` prints the JSON Schema of any `--json` result named on this page.

| Command | What it does | `--json` schema |
| --- | --- | --- |
| [`stacks search`](#stacks-search) | Find stacks and fragments for what you want to build | `search` |
| [`stacks list`](#stacks-list) | List the stacks, fragments and apps you can build from | `list` |
| [`stacks show`](#stacks-show) | Review one stack or fragment | `show` |
| [`stacks new`](#stacks-new) | Start a project from a stack | `new` |
| [`stacks add`](#stacks-add) | Apply fragments to an existing repository | `add` |
| [`stacks verify`](#stacks-verify) | Run the project's verify steps | `verify`, `verify-event` |
| [`stacks runs`](#stacks-runs) | Show a project's verify history | `runs` |
| [`stacks validate`](#stacks-validate) | Check entries, or preview a composed brief | `validate`, `validate-brief` |
| [`stacks create`](#stacks-create) | Create or copy a stack or fragment for editing | `create` |
| [`stacks submit`](#stacks-submit) | Open a draft pull request adding your entries to the public registry | `submit` |
| [`stacks update`](#stacks-update) | Check for a newer release, or upgrade a global npm install | `update` |
| [`stacks skill install`](#stacks-skill-install) | Install an agent skill | `skill` |
| [`stacks guide`](#stacks-guide) | Print the guide for coding agents | `guide` |
| [`stacks guide author`](#stacks-guide-author) | Print the guide to writing and submitting a stack or fragment | `guide-author` |
| [`stacks doctor`](#stacks-doctor) | Inspect the runtime, paths and configuration | `doctor` |
| [`stacks schema`](#stacks-schema) | Print the JSON Schema of a `--json` result | none |
| [`stacks telemetry`](#stacks-telemetry) | Inspect consent, turn collection off, rotate identity or send pending events | `telemetry` |

A failed `--json` command writes one error envelope as its last stderr line; its schema is `error`. The envelope, the three output audiences and the full exit code table are in [Output, JSON and exit codes](https://stacks.steamfield.io/docs/reference/output.md).

## Global flags

Every command accepts these flags.

| Flag | What it does |
| --- | --- |
| `--json` | Write one JSON result to stdout. A failure writes one JSON error envelope as the last stderr line instead |
| `--offline` | Use cached and bundled entries without network requests. `STACKS_OFFLINE=1` does the same. No registry, search, stats or telemetry request is made |
| `--no-telemetry` | Skip telemetry for this invocation. Queued events stay queued |
| `--help`, `-h` | Show the command's help: flags, exit codes and examples |
| `--version`, `-v` | Print the version. With `--json`, prints `{ "version": "<version>" }` |
| `--completions <shell>` | Print a shell completion script. See [Shell completions](#shell-completions) |
| `--log-level <level>` | Minimum log level: `all`, `trace`, `debug`, `info`, `warn`, `warning`, `error`, `fatal` or `none` |
| `--wizard` | Start wizard mode for a command |

Running `stacks` with no arguments prints the agent guide, the same as [`stacks guide`](#stacks-guide). `stacks --help` prints the full help and ends with a short "Start here (agents)" footer.

On the first invocation with telemetry enabled, stacks prints a one-time privacy notice on stderr (JSON results gain an optional `notice` field instead), and that invocation sends nothing. See [Telemetry](https://stacks.steamfield.io/docs/reference/telemetry.md) for the off switches.

## Interactive pickers

Run `new` without a stack, `add` without fragments, or `create` without an entry kind or id, and stacks asks for what's missing with numbered choices. It asks only when output is for a human: no `--json`, both stdin and stdout on a terminal, no coding agent detected and `CI` unset. Everywhere else `new`, `add` and `create stack|fragment` exit 2 (`Usage`), and a bare `create` prints its help.

Type one number to pick a row. Fragment lists take several numbers separated by commas; for `new`'s optional fragments, Enter picks none. Ctrl+C quits before anything is written. After the last answer stacks prints a `Running:` line with the equivalent command, so you can rerun it without the prompts.

## stacks search

Ranks stacks and fragments against a request in plain words and explains each hit.

```sh title="Terminal"
stacks search "an iOS health app" --json
stacks search go cli --kind stack --limit 3
stacks search "a static blog" --local     # rank on this machine only
```

| Argument or flag | What it does |
| --- | --- |
| `<request...>` | What you want to build, in your own words. 1 to 500 characters; several words don't need quotes |
| `--kind stack\|fragment` | Search only stacks or only fragments |
| `--limit <n>` | Maximum hits per list, from 1 to 20. Default 5 |
| `--local` | Rank locally without sending the request to the API |

`search` sends the request text to the hosted API at `https://stacks.steamfield.io` and ranks locally at the same time. When the API is unavailable it falls back to the local ranking. `--local`, `--offline`, `STACKS_OFFLINE=1` and `DO_NOT_TRACK` keep the request on your machine.

```sh title="Terminal" group="search"
stacks search go cli --kind stack --limit 3 --local
```

```text title="Output" group="search"
stack go-cli (strong)
A Go command-line tool with subcommands, configurable settings, actionable errors and human or JSON output.
Why: cli (platforms); go (languages, description, summary, requests); go cli (id, name, tags, body)
...

stack go-api (partial)
A Go HTTP API on the standard library with JSON resources, consistent errors, request logs and graceful shutdown.
Why: go (languages, id, name, description, tags, body, summary, requests)
Penalties: platform (cli; entry: server)
Caveat: Race-enabled tests need cgo and a C compiler on every machine and CI runner.
...

Next: stacks show go-cli, then stacks new go-cli <dir>.
```

Each hit has a fit (`strong` or `partial`), the fields that matched (`Why`), anything that counted against it (`Penalties`), and caveats. [How search ranks entries](https://stacks.steamfield.io/docs/reference/search.md) covers the ranking, the result fields and the API's backoff.

Exit codes: 0 even when nothing fits strongly (the JSON result says `noStrongMatch`), 2 for invalid input (`Usage`, `BadInput`), 6 for an unreadable registry (`PermissionDenied`), 130 when interrupted.

## stacks list

Lists the stacks, fragments and apps you can build from, where each one comes from, and whether it's trusted.

```sh title="Terminal"
stacks list                 # addable fragments in a project; otherwise all groups
stacks list stacks          # one group: stacks, fragments (apps excluded) or apps
stacks list --json          # full registry, plus project context when a manifest decodes
stacks list --dir ../app    # list from another project directory
```

| Argument or flag | What it does |
| --- | --- |
| `[group]` | Show one group: `stacks`, `fragments` (apps excluded) or `apps`. Omit it to show all three |
| `--dir <path>` | Project directory to list from. Defaults to the current directory |

Inside a project (a directory with `.stacks/manifest.json`), the default text view shows only the fragments that fit and aren't applied yet, each with its `stacks add` command. An explicit group keeps the full view. `--json` always returns the full registry and adds `project: { stack, applied, addable }` when the manifest decodes. A corrupt, newer or unreadable manifest prints a warning and falls back to the full view.

```sh title="Terminal" group="list"
stacks list stacks
```

```text title="Output" group="list"
Stacks
  astro-blog               historical pass 2026-10-27
    A static Astro blog with MDX posts in a zod-validated content collection (draft/published/unlisted status), React islands, Tailwind with typography, sitemap and RSS, optional generated OG images, and vitest plus astro check.
  astro-fullstack          unproven
    A Bun workspace with a static Astro site (React islands, Tailwind), a Node API on Effect's HttpApi and a shared contract package, so islands call the API through a typed client. The API can also serve the built site.
  ...
  go-cli                   historical pass 2026-10-27
    A Go command-line tool with cobra commands, viper config (file, env, flags), structured errors with actionable suggestions, human and JSON output, testify tests with race detection and a coverage floor, and golangci-lint.
  ...

Next: stacks new astro-blog my-project --sandbox
Next: stacks list --json
```

The label beside each stack is its last recorded run: `historical pass <date>` for a pass, `<result> <date>` for anything else, or `unproven` when there is no record. Bundled and index stacks carry it as `lastRun: { date, result, agent, preset, fingerprint } | null` in JSON. It records a past run of the default composition; `list` doesn't compare it with the current content, and `stacks show <id>` does. Fragments and local entries have no run record.

### Origins and shadowing

Entries come from four origins, highest precedence first. The same id in a higher origin wins, and `list --json` marks the winner's `shadows` with the origins it hides.

| Origin | Where | Trusted |
| --- | --- | --- |
| `project` | `./.stacks/` in the working directory | No: a cloned repository can ship these |
| `user` | `$XDG_CONFIG_HOME/stacks/` (default `~/.config/stacks/`) | Yes |
| `index` | Verified bundles cached from the signed registry index | Yes for the default index; no for a custom `STACKS_INDEX_URL` |
| `bundled` | The registry shipped with the CLI | Yes, unless `STACKS_REGISTRY_DIR` replaced it |

An entry that fails to load is listed under `rejected` with a one-line error, and a lower copy never silently replaces it. An entry whose `schema_version` is newer than this CLI reads is skipped with a warning, and the next origin's copy is used. [Registries and trust](https://stacks.steamfield.io/docs/concepts/registries-and-trust.md) explains what trust changes, and [Entry file format](https://stacks.steamfield.io/docs/reference/entry-format.md) describes the directory layout inside each root.

### Published stats

For bundled and index entries, `list` and `show` can add published project stats from the hosted API. `list --json` carries a flat `stats` summary per entry; `show --json` carries the full detail, including agent breakdowns and a weekly series. Text output prints one `Stats:` line.

| Field | Meaning |
| --- | --- |
| `window`, `through`, `population` | The trailing 7 or 28 finalized UTC days, ending on `through`. `population` is real `stacks projects` or `seeded development data`, which text labels `seeded` |
| `projects` | Distinct projects in the window, each counted once however many events it sent |
| `greenRate`, `greenRateLower`, `greenRateUpper`, `sample` | Green projects over attempted projects, its 95% Wilson interval, and the attempted sample behind it. Green requires a full, unchanged verify |
| `noVerifyShare` | Share of projects with no verify event, which can include projects whose telemetry was off |
| `medianMinutesToGreen` | Median time from scaffold to first green verify |

A suppressed cell (too few projects to publish) is `null` in JSON, and text prints `unavailable`. Stats are optional: a missing or unreachable response adds no warning and doesn't change the exit code. They're cached under `$XDG_CACHE_HOME/stacks/stats/` for 24 hours, `--offline` makes no request, and a custom `STACKS_INDEX_URL` turns them off because they describe a different registry.

Exit codes: 0, 2 on an unknown group, 6 when a registry root can't be read, 130 when interrupted.

## stacks show

Reviews one stack or fragment without starting any process: metadata, every guidance branch, commands, verify steps and shipped files, including adapter files for every agent.

```sh title="Terminal"
stacks show typescript-cli          # metadata, guidance, commands, steps, shipped files
stacks show postgres --json         # one ShowReport object
stacks show effect --kind fragment  # look among fragments only
```

| Argument or flag | What it does |
| --- | --- |
| `<id>` | The stack or fragment id to review. Apps are fragments |
| `--kind stack\|fragment` | Select a kind when a stack and a fragment share an id |

```sh title="Terminal" group="show"
stacks show go-cli
```

```text title="Output" group="show"
Go CLI (go-cli), stack, origin bundled, trusted
A starter for a terminal program written in the Go language, with settings and clear error messages.
Good at: Commands that read settings from files, environment variables and flags
Good at: Output for people to read, or in a structured form scripts can parse
Good at: Error messages that say what went wrong and how to fix it
...
Tags: go, cli, make
Dependencies (cli): github.com/spf13/cobra, github.com/spf13/viper
Dependencies (test): github.com/stretchr/testify
Tools: go, make, git, golangci-lint
Decisions:
  - Module path: github.com/<owner>/<project-slug> (default), ...
  - Binary name: the project slug (default; the Makefile's BIN).
  ...
Fragments: required none; default agent-ops, git-hooks, ci-github; optional goreleaser, rr, ce-plugin
Verify:
  install: make install (phase setup, layer install)
  build: make build (layer build)
  vet: make vet (layer typecheck)
  lint: make lint (layer lint)
  test: make test (layer test)
  ...
Files:
  .claude/settings.json (claude) <- adapters/claude/.claude/settings.json
  .editorconfig <- files/.editorconfig
  .gitignore <- files/.gitignore
  Makefile <- files/Makefile.tmpl
  ...
```

When the entry's search card has plain-language copy, `show` prints its definition, strengths, first project example and limits before the technical description. Bundled and index stacks also get their recorded run, and `sameContent` in JSON says whether the current composition's fingerprint matches the one that run used. Other origins get `lastRun: null`. Published stats appear as described under [Published stats](#published-stats).

Exit codes: 0; 2 on an unknown or rejected id, an id that needs `--kind`, or a stack whose default fragments conflict (`ComposeConflict`); 3 on a broken `config.json`; 6 when the entry can't be read; 130 when interrupted.

## stacks new

Starts a project from a stack: composes it with its fragments, writes the starter files and `.stacks/BRIEF.md`, then hands the project to a coding agent. It never runs a command from a stack or fragment.

```sh title="Terminal"
stacks new typescript-cli my-cli                     # write the project, then hand it to your agent
stacks new astro-blog my-blog --sandbox --dry-run    # preview without writing anything
stacks new typescript-cli my-cli --sandbox --json    # one NewResult object, as an agent would
```

| Argument or flag | What it does |
| --- | --- |
| `[stack]` | The stack to start from (see `stacks list stacks`). Omitted, a person at a terminal gets a picker |
| `[dir]` | The project directory: missing, empty, or holding only `.git` and OS files such as `.DS_Store`. Default: the slug of `--name`, else `./<stack>`. `.` is the current directory |
| `--with <ids>` | Add optional fragments or an app. Repeatable or comma-separated |
| `--without <ids>` | Leave out default fragments. Repeatable or comma-separated |
| `--sandbox` | Local only: every default, no deploy, release or app fragments unless named with `--with`, and steps tagged `prod` skipped. Implies `--yes`. Alias for `--preset sandbox` |
| `--preset default\|sandbox` | The composition preset. Default: the `preset` setting, else `default`. Sandbox implies `--yes` |
| `--yes` | Take every decision's default. The brief tells the agent not to ask you |
| `--agents <list>` | Agents to write adapter files for: `claude`, `codex`, `opencode`, `cursor`. Repeatable or comma-separated. Default: the `agents` setting, else `claude` |
| `--name <name>` | Project name for templates. Default: the directory's base name |
| `--author <name>` | Author for templates. Default: the `author` setting, then `git config user.name`, else empty. An explicit empty value suppresses the fallback |
| `--scope <scope>` | npm scope for the `package_scope` template variable, with or without `@`. Default: the `package_scope` setting, else none |
| `--dry-run` | Compose and render the plan without writing anything or starting an agent |
| `--launch <harness>` | The harness to hand the project to: `claude`, `codex`, `opencode`, `cursor`, `copilot` or `codex-app` (macOS). Never with `--json`, `--no-launch` or `--dry-run` |
| `--no-launch` | Don't start an agent; print the prompt to give one |
| `--source <where>` | How you reached stacks: `web`, `docs`, `skill`, `cli` or `other`. Optional telemetry metadata |

```sh title="Terminal" group="new-dry"
stacks new go-cli my-cli --sandbox --dry-run
```

```text title="Output" group="new-dry"
Planned my-cli from go-cli (sandbox).
Fragments: agent-ops, ci-github, git-hooks
Agents: claude
Git: would create a new repository
Would write 20 files:
  .claude/ (10 files)
  .editorconfig
  .gitignore
  .stacks/ (4 files)
  AGENTS.md
  CLAUDE.md
  Makefile
  README.md
Brief: .stacks/BRIEF.md (3272 words)
Dry run: nothing was written.
```

A real run writes the same files plus `.stacks/` (`BRIEF.md`, `NOTES.md`, `manifest.json`, an empty `state/`, and a `.gitignore` that ignores everything there except the manifest), writes a state record outside the project, and runs `git init` when the target isn't already inside a repository and git is on PATH. [The brief and the manifest](https://stacks.steamfield.io/docs/concepts/brief-and-manifest.md) explains what each file is for.

### Presets

The preset comes from the flags, then the `preset` setting in `config.json`, then `default`. `--sandbox` and `--preset sandbox` are the same thing, and sandbox implies `--yes` however it was selected. `--sandbox --preset default` is a `Usage` error (exit 2).

### Dry run

`--dry-run` checks the target, composes and renders every file in memory (the manifest included), then stops. It creates no directory, state record or git repository and starts no agent. Its JSON result has `dryRun: true`, `stateRecord: false`, and `written` lists the planned files. `--dry-run --launch` is a `Usage` error.

### Names and template values

`--name` sets `project_name`. When you don't give a directory it also picks one through its slug; an explicit directory always stays the target. Slugs strip npm scopes, fold accents, split camelCase and digit boundaries, then join lowercase ASCII words with dashes. A name with no ASCII words becomes `project`.

Files ending in `.tmpl` are rendered and lose the suffix. Every other file is copied byte for byte, keeping its executable bit. The template variables are listed in [Entry file format](https://stacks.steamfield.io/docs/reference/entry-format.md#templates).

### Handing the project to an agent

After writing, `new` hands the project off by the first of these rules that matches, and records which one in the manifest's `handoff` field.

1. Inside an agent session (a marker such as `CLAUDECODE` or `AI_AGENT` is set): stdout ends with `NEXT STEP FOR THE AGENT: read .stacks/BRIEF.md now and carry it out, ...`, telling that agent to loop on `stacks verify`. With an untrusted entry it says `STOP` instead, and tells the agent to show you the entries and the brief's first section and continue only if you confirm.
2. `--json`, `--no-launch`, stdin or stdout not a terminal, or any untrusted entry: the prompt is printed and nothing is launched. An untrusted entry never launches an agent.
3. `--launch` picks a harness explicitly. Without it, the first available agent in the `agents` setting launches. With none configured, a single found harness launches, and several show a numbered picker. The search order is `claude`, `codex`, `opencode`, `cursor-agent`, `copilot`, then the Codex app on macOS. With none found, the prompt is printed.

A terminal harness gets your environment and terminal in the project directory, and stacks exits with the agent's own status. The Codex app is opened with the project and prompt, and stacks exits 0. Claude Code and Codex are the proven adapters; OpenCode and Cursor adapters are written but unproved.

### Refused targets and conflicts

| Condition | Error |
| --- | --- |
| The target exists and holds anything besides `.git` and OS litter (`.DS_Store`, `Thumbs.db`, `desktop.ini`, `._*`) | `TargetNotEmpty` (2) |
| A fragment with `needs_own_repo` inside another repository | `ComposeConflict` (2), with the `--without` flags that avoid it |
| An entry writing under `.git/` or `.stacks/`, in any letter case | `ComposeConflict`, reason `reserved_path` |
| Two entry files whose paths differ only in case | `ComposeConflict`, reason `file_owner` |
| A write that would land outside the project's real path | `PermissionDenied` (6), reason `escape` |
| An unknown template variable in a `.tmpl` file | `EntryInvalid` (2) |

A failure or Ctrl+C removes everything `new` wrote, the state record included. Missing tools never fail `new`: they're listed in the result and the brief, with the `--without` flag that would drop the fragment needing one. If the state directory isn't writable, `new` still succeeds with `stateRecord: false`, and verify's tamper check falls back to the manifest alone.

Exit codes: 0; 2 on bad input, an unknown entry, a non-empty target or conflicting fragments; 3 on bad configuration or a manifest schema newer than this CLI; 6 when a write is refused; 130 when interrupted. After launching a terminal agent, the exit is the agent's own status; an app launch exits 0.

## stacks add

Applies fragments to an existing repository without overwriting live files. Conflicting contributions are parked under `.stacks/incoming/` for your agent to merge.

```sh title="Terminal"
stacks add ci-github                                   # the current repository
stacks add ci-github git-hooks --dir ../my-project --json
stacks add agent-ops --agents claude,codex --no-launch
```

| Argument or flag | What it does |
| --- | --- |
| `[fragment...]` | One or more fragments to apply (see `stacks list fragments`). Omitted, a person at a terminal gets a picker |
| `--dir <path>` | Existing project directory. Defaults to the current directory |
| `--sandbox` | Use the sandbox preset when creating a manifest. Existing projects keep their preset. Implies `--yes` |
| `--yes` | Take every decision's default |
| `--agents <list>` | Agents to write adapter files for. Default: the `agents` setting, else `claude` |
| `--launch <harness>` | The harness to hand the project to, as for `new`. Never with `--json` or `--no-launch` |
| `--no-launch` | Don't start an agent; print the prompt |
| `--source <where>` | Optional telemetry attribution, as for `new` |

`add` applies only the requested fragments and what they require. Existing files keep their bytes. A contribution identical to the live file is skipped; a conflicting one is parked whole at `.stacks/incoming/<path>`. Later parked JSON contributions deep-merge into the parked copy, and other repeated contributions get numbered copies. The result and the add brief list every pending incoming file, including ones from earlier runs.

An existing manifest keeps its stack, preset, agents, accepted verify steps and existing env and command values. Without one, `add` creates a manifest with `mode: "add"` and `stack: null`, and infers the project name from `package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, then the directory name. `--sandbox` and `--agents` apply only to a newly created manifest; values that contradict an existing one are refused.

Before planning, `add` runs the same tamper check as `verify`, and changed steps refuse with `VerifyChanged` (5). When an untrusted fragment is added under an agent or without a terminal, `add` writes the new steps but keeps the previous digest (`digestRecorded: false`), so a person has to review them and run `stacks verify --accept-changes`. `add` runs no verify steps and no tools. A failed or interrupted write restores parked copies and state and removes new files.

`add --json` returns `AddResult`: `added`, `alreadyApplied`, newly `written` files, `skipped`, `mergeNeeded` with incoming paths, `missingRequired` with exact add commands, `commandsKept`, `warnings`, `missingTools`, the inferred `name`, manifest and digest status, and the brief and handoff. [Add to an existing repository](https://stacks.steamfield.io/docs/guides/existing-repo.md) walks through a full run.

Exit codes: 0; 2 on bad input or an unknown fragment; 3 on a bad manifest; 5 when the project's verify steps changed (`VerifyChanged`); 6 on permissions; 130 when interrupted. After launching a terminal agent, the agent's own status.

## stacks verify

Runs the project's verify steps from `.stacks/manifest.json` and reports whether the project is done. It finds the nearest manifest walking up from the current directory and runs only the steps listed there, printing each command before running it.

```sh title="Terminal"
stacks verify                    # every step, from the project root or any directory below it
stacks verify --only lint,test   # rerun two checks while fixing them (outcome partial)
stacks verify --all              # also run steps skipped by default
stacks verify --json             # one VerifyResult object
```

| Flag | What it does |
| --- | --- |
| `--only <names>` | Run only these check and serve steps, by name. Repeatable or comma-separated. Setup and teardown still run. The outcome is `partial`, never `green` |
| `--skip-tag <tag>` | Skip steps carrying this tag. Repeatable or comma-separated. The outcome is `partial` |
| `--all` | Also run steps skipped by default (steps tagged `prod` in a sandbox project) |
| `--accept-changes` | For a person who changed the verify steps on purpose: record them as they are now, then run them. Refused under a detected agent or without a terminal on stdin |

Phases run in this order:

| Phase | How its steps run |
| --- | --- |
| `setup` | Stops at the first failure and skips everything after it |
| `check` | Every step runs, so one run reports every failing check |
| `serve` | Each step is started, probed over HTTP, then stopped |
| `teardown` | Always runs, after a failure or Ctrl+C too |

Projects with an app run the app's steps as a second stage, only after the base project passes.

Steps run through `/bin/sh -c` in the project root (or the step's `cwd`) with stdin closed. They inherit your environment minus agent session markers, and the manifest's `env` fills any key you haven't set, so `PORT_WEB=5187 stacks verify` moves a port. Each step has a timeout (default 10m). Exit status 127 is reported as a missing command.

| Outcome | Exit | Meaning |
| --- | --- | --- |
| `green` | 0 | A full run where every step that ran passed |
| `partial` | 0 | A passing run narrowed by `--only` or `--skip-tag`. Never counts as done |
| `failed` | 5 (`VerifyFailed`) | A step failed. The full result is still on stdout, with each failed step's `failureKind` (`step`, `env`, `timeout`) and output tail |
| `changed` | 5 (`VerifyChanged`) | The steps differ from the copy recorded when the project was made. Nothing runs, not even teardown |

`--json` also writes live events to stderr while it runs, one JSON object per line (schema `verify-event`): `step-started` as a step starts and `step-finished` with its status and duration as it ends. Read the last stderr line for the error envelope and skip lines with an `event` key.

Every run that gets past the manifest appends one record to `.stacks/state/runs.jsonl`. Records hold step names, phases, statuses and timings, with no commands, output, paths or environment values. [Verify: the definition of done](https://stacks.steamfield.io/docs/concepts/verify.md) covers [outcomes](https://stacks.steamfield.io/docs/concepts/verify.md#outcomes), app stages, serve probes and [tamper evidence](https://stacks.steamfield.io/docs/concepts/verify.md#tamper-evidence) in full.

> [!IMPORTANT]
> `--accept-changes` is for a person. Under an agent (`reason: "agent"`) or without a terminal on stdin (`reason: "no-tty"`) it exits 6 and changes nothing. An agent that sees `VerifyChanged` should stop and ask you.

Exit codes: 0 when green or partial; 2 outside a project (`NotFound`) or on an `--only` name that isn't a check or serve step (`BadInput`); 3 on a manifest that doesn't decode or a state record from a newer stacks; 5 when a step fails or the steps changed; 6 when the manifest can't be read or `--accept-changes` is refused; 130 when interrupted, after teardown.

```sh title="Terminal" group="verify-outside"
stacks verify --json
```

```json title="stderr" group="verify-outside"
{"error":{"tag":"NotFound","message":"Not found.","retryable":false,"next":"run this command again from inside a project stacks new created, or create one with stacks new <stack> <dir>.","details":{"suggestions":[],"kind":"project","looked":"..."}}}
```

## stacks runs

Summarizes the nearest project's verify history, timings and interventions.

```sh title="Terminal"
stacks runs          # summary for the nearest project
stacks runs --json   # one ProjectRuns object
```

`runs` has no flags beyond the global ones. It reads `.stacks/state/runs.jsonl`, `.stacks/NOTES.md` and the project's state record outside the project. Interventions are the nonempty items under `## Asked` in `NOTES.md`. Timings come only from the state record, so a fresh clone reports them as unknown. Invalid lines, or lines written by a newer stacks, are counted and skipped.

```sh title="Terminal" group="runs"
stacks runs
```

```text title="Output" group="runs"
Stack: go-cli
Fingerprint: sha256:c1c76ffed7d10d3815c1bfa762899875465cb766f353796fd671ab6561f9f3d3
Created at: 2026-10-30T05:00:57.858Z
Handoff: inside-agent
Brief words: 3272
Verify runs: 0
Last outcome: unknown
Minutes to green: unknown
Minutes to first successful test command (run completion): unknown
Interventions: 0
State record: true
Skipped lines: 0
```

Exit codes: 0; 2 outside a project (`NotFound`); 3 on a manifest that doesn't decode, or a manifest or state record from a newer stacks; 6 when the manifest can't be read; 130 when interrupted.

## stacks validate

Checks stacks and fragments before an agent builds from them, or previews the brief a composition renders to. It reads files only and runs nothing from an entry.

```sh title="Terminal"
stacks validate                                         # the bundled registry
stacks validate ~/.config/stacks                        # a registry root (holds stacks/ or fragments/)
stacks validate ~/.config/stacks/stacks/my-api --json   # one entry, as a ValidateReport
stacks validate --brief typescript-cli --sandbox        # the brief a composition renders to
stacks validate ~/.config/stacks/stacks/my-api --submission   # ready for the public registry?
```

| Argument or flag | What it does |
| --- | --- |
| `[path]` | A registry root (holding `stacks/` or `fragments/`) or one entry directory inside one. Relative paths resolve against the working directory. Omit it to check the bundled registry |
| `--brief <stack>` | Print the brief this stack composes to, with its word counts and findings. Writes nothing |
| `--with <ids>` | With `--brief`: add an optional fragment or app. Repeatable or comma-separated |
| `--without <ids>` | With `--brief`: leave out a default fragment. Repeatable or comma-separated |
| `--sandbox` | With `--brief`: render the sandbox preset |
| `--submission` | Check one entry directory against the bar for the public registry (below) |

`--json` returns `ValidateReport` (schema `validate`), or `BriefPreview` (schema `validate-brief`) with `--brief`. [Validation checks](https://stacks.steamfield.io/docs/reference/validation.md) lists every check, the word budgets and how the brief preview works.

`--submission` adds these checks to the report, each with a file and line:

| Check | Severity | Finds |
| --- | --- | --- |
| `fill-marker` | error | A `stacks:fill` marker left in the frontmatter, the body, the search card, or a file under `files/` or `adapters/`. A shipped marker is allowed when a verify step whose `run` mentions `stacks:fill` names that file's project path (with or without `.tmpl`), as the skeleton's `readme` step names `README.md`, because the project's agent fills it |
| `secret` | error | A credential-shaped string: cloud, GitHub, Slack and AI provider keys, private keys, and long literals assigned to names such as `password` or `token`. A value that reads as a placeholder or a reference (`your-...`, `{{project_slug}}`, `${...}`, `secrets.`, `process.env`) is allowed. The report names the kind of secret, never the value |
| `env-file` | error | A `.env` file other than `.env.example`, `.env.sample` or `.env.template` (each also as `.tmpl`) |
| `absolute-path` | warning | A home directory path (`/Users/<name>/` other than `/Users/Shared/`, or `/home/<name>/`) or a drive-letter path such as `C:\work` in shipped text. The homes of container, CI and dev container accounts (`node`, `runner`, `vscode`, `ubuntu`, `codespace`, `gitpod`, `coder`, `circleci`, `app`, `user`, `ec2-user`, `linuxbrew`, `bun`, `pwuser`, `nonroot`, `jovyan`, `vagrant`, `jenkins`, `gitlab-runner`) are allowed |
| `missing-card` | warning | No search card |

The scan matches patterns, so it misses things: read the files yourself before submitting.

Exit codes: 0 when there are no errors (warnings and notes don't fail); 2 on errors (`ValidationFailed`, with the report still on stdout), a path that is neither a registry nor an entry, `--with`, `--without` or `--sandbox` without `--brief`, a `--brief` composition that is unknown or conflicts, or `--submission` with `--brief` or with a path that isn't one entry; 3 on a broken `config.json` with `--brief`; 6 when the root can't be read; 130 when interrupted.

## stacks create

Writes a new stack or fragment skeleton into your user registry, or copies an existing entry there for editing. It never runs anything from an entry.

```sh title="Terminal"
stacks create stack my-api --json
stacks create fragment my-ci --category ci
stacks create fragment my-app --category app
stacks create stack go-cli --from bundled
stacks create stack team-api --from project --yes
```

`create` has two subcommands, `stacks create stack [id]` and `stacks create fragment [id]`. The id is lowercase kebab-case. Without a subcommand or id, a person at a terminal gets a picker.

| Flag | Applies to | What it does |
| --- | --- | --- |
| `--from bundled\|index\|project\|user` | both | Copy the entry with this id from that origin instead of writing a skeleton. `user` is refused, because the user registry is the destination |
| `--category <category>` | `fragment` | Category for a new fragment: `agent-ops`, `app`, `ci`, `deploy`, `release`, `service`, `vendor` or `tooling`. Default `tooling`. An app needs `applies_to` filled before it validates |
| `--yes` | both | Skip the trust question after the full report. Still requires a person at a terminal for an untrusted copy |

Entries land in `$XDG_CONFIG_HOME/stacks/stacks/<id>/` or `.../fragments/<id>/` (default `~/.config/stacks/`). A copy keeps the same id and every file, adapter and search card, so the user copy then shadows the original. Copying from `project` or any other untrusted source prints a full trust report on stderr and asks a person at a terminal to confirm. An agent or a non-terminal stdin can't confer that trust (`PermissionDenied`, 6).

An occupied target is refused and left as it was, and so is a skeleton whose id another registry root already has: copy that entry with `--from` instead. `--json` returns `{ kind, id, path, guide }` with absolute paths. [Write your own stack or fragment](https://stacks.steamfield.io/docs/guides/authoring.md) covers filling in the new entry and validating it.

Exit codes: 0; 2 on a bad id, an id another registry already has, an occupied target, an unknown source or a declined confirmation; 6 when the user registry can't be written or an untrusted copy can't be confirmed by a person at a terminal; 130 when interrupted.

## stacks submit

Opens a draft pull request that adds your stacks and fragments to the public registry, [steamfield/stacks](https://github.com/steamfield/stacks). It runs the GitHub CLI (`gh`) and `git` with fixed arguments and holds no credentials of its own, so `gh` must be on `PATH` and signed in (`gh auth login`).

```sh title="Terminal"
stacks submit ~/.config/stacks/stacks/my-api --dry-run    # check and print, open nothing
stacks submit my-api my-lint --query "a REST API in Go"    # one pull request for both
stacks submit my-api --repo my-org/stacks --json           # another upstream; a SubmitResult
```

| Argument or flag | What it does |
| --- | --- |
| `<entry...>` | Entry directories, or ids of entries in your user registry. An argument with a `/`, or one that starts with `.` or `~`, is a path; anything else is an id (`./my-api` for a folder in the current directory). An id that isn't kebab-case is refused. Give a stack together with the fragments it needs and they go in one pull request |
| `--dry-run` | Clone and check as usual, then print the files and the pull request body. Pushes nothing and opens nothing |
| `--query <text>` | A search the entry should rank first for, proposed to maintainers in the pull request. Repeatable |
| `--repo <owner/name>` | The upstream repository. Default `steamfield/stacks` |

`submit` stops at the first step that fails:

1. It refuses offline mode, then reads your GitHub login with `gh api user`.
2. It makes a partial, shallow clone of the upstream `main` in a temporary directory, which is removed however the command ends.
3. It copies each entry to `packages/cli/registry/stacks/<id>/` or `fragments/<id>/`, its search card to `search/`, and maps `<kind>/<id>` to your login in `authors.json`. An update without a search card keeps the upstream card. An unreviewed author card gets its digest stamped from the entry as submitted. A reviewed card whose digest no longer matches is restamped and becomes an unreviewed author card again.
4. It checks the entries against the clone's registry, not your installed one, so every reference must resolve upstream or among the submitted entries. `validate --submission` must report no errors and no warnings except the ones maintainers clear after the pull request: `search-unreviewed`, `search-missing`, `missing-card`, `deps-missing` and `deps-unreviewed`. A removed id is always refused. An existing id is refused unless `authors.json` gives it to your login, which makes the pull request an update.
5. Unless `--dry-run`, it forks the repository (reusing an existing fork), commits exactly the submitted entries, cards and `authors.json` on branch `entry/<kind>-<id>`, pushes to your fork and opens a draft pull request. The body lists the entries, the validation summary and the warnings left, your proposed queries, the CLI version and the maintainer checklist.

A rerun pushes to the same branch, rewrites the open pull request's title and body, and reports it instead of opening another. It replaces the branch only when its tip is the single commit an earlier `submit` made. A branch with commits `submit` didn't make is refused (`branch_taken`): update that pull request by hand, or delete the branch on your fork to start over.

An agent may run `submit`. The authoring skill tells it to show you the `--dry-run` output and get your yes first. `--json` returns `SubmitResult` (schema `submit`): `{ repo, entries, author, branch, prUrl, reused, dryRun, findings, files }`, plus `body` with `--dry-run`.

Exit codes: 0 when submitted or dry-run; 2 for offline mode, a missing or signed-out `gh`, a bad `--repo`, an id that isn't kebab-case, an entry that fails the checks (each blocking finding in `details.blockers`), an id collision, a branch with commits `submit` didn't make, or nothing to change; 4 when GitHub or git can't be reached (`UpstreamUnavailable`, retry later); 6 when GitHub refuses the fork or push for your login; 130 when interrupted.

## stacks update

Checks the latest published version of `@steamfield/stacks` and upgrades a global npm install.

```sh title="Terminal"
stacks update                  # upgrade a global npm install
stacks update --check          # report versions and the command without installing
stacks update --check --json   # one UpdateResult object
```

| Flag | What it does |
| --- | --- |
| `--check` | Report versions and the update command without installing. Agents use this |

Each invocation makes one request to the npm registry with a 10 second timeout. Nothing checks in the background and no version is cached, so under `--offline` or `STACKS_OFFLINE=1` both forms fail with `BadInput` (exit 2) before any request.

| Status | Meaning |
| --- | --- |
| `up-to-date` | The running version is the latest |
| `ahead` | The running version is newer than the published one |
| `available` | A newer version exists. The result carries the install method and `command` to run |
| `updated` | A global npm install was upgraded |

When a newer version exists, `update` works out how stacks was installed. A global npm install upgrades with `npm install -g @steamfield/stacks@<latest>`, pinned to the version it reported, with a 5 minute timeout. npm runs from your home directory, so a project's `.npmrc` can't change it. An npx install gets `npx -y @steamfield/stacks@<latest>` as its command; other installs get the npm command without installing. The running process keeps its old code after updating.

An agent may run `--check`. An agent that tries to install an available upgrade without `--check` gets `PermissionDenied` (6).

Exit codes: 0 when up to date, ahead, available or updated; 2 in offline mode; 4 when the registry is unavailable (`UpstreamUnavailable`, retry later); 6 when an agent tries to install, or npm fails (`UpdateFailed`: run the supplied command yourself to see npm's output); 130 when interrupted.

## stacks skill install

Installs a bundled skill for Claude Code. `stacks`, the default, teaches your agent the search, show, new, verify loop. `stacks-author` teaches it to write a stack or fragment and submit it, and ships a `references/` folder beside its `SKILL.md`.

```sh title="Terminal"
stacks skill install                  # $HOME/.claude/skills/stacks/SKILL.md
stacks skill install stacks-author    # $HOME/.claude/skills/stacks-author/, every file
stacks skill install --project        # .claude/skills/ under the git toplevel
stacks skill install --force          # replace edited copies
stacks skill install --print          # print SKILL.md to copy somewhere else
stacks skill install --print --json   # { name, path: null, action: "printed", files, text }
```

| Argument or flag | What it does |
| --- | --- |
| `[name]` | `stacks` (default) or `stacks-author` |
| `--project` | Install under the git toplevel, or the working directory outside git |
| `--force` | Replace installed files whose content differs |
| `--print` | Print the skill's `SKILL.md` without looking up or writing an install target. For `stacks-author`, its links to references become `stacks guide author <topic>` commands |

Every file of the skill goes under `.claude/skills/<name>/`, and `--json` lists each one's path and action in `files`. Identical installed files are left alone (`action: "unchanged"`). Different ones are preserved with `TargetNotEmpty`, and nothing is written, unless you pass `--force`; a directory sitting at a skill file's path is always preserved. Writes refuse symlinked skill files and symlinks that escape the skills root, though a dotfiles symlink above the skills root works. Installing under an agent is allowed. For agents other than Claude Code, take the text from `--print` or `stacks guide author`. [Use stacks with your agent](https://stacks.steamfield.io/docs/agents.md) covers the other agents.

Exit codes: 0 when written, unchanged or printed; 2 on an unknown name, when a different file is already there (`TargetNotEmpty`), or when the bundled skill is missing; 6 when `HOME` is unset or the target can't be written; 130 when interrupted.

## stacks guide

Prints a short guide written for coding agents: the search, show, new, verify loop and the rules an agent must keep. `stacks` with no arguments prints the same thing, and `--json` returns `{ guide, commands }`.

```sh title="Terminal" group="guide"
stacks guide
```

```text title="Output" group="guide"
stacks starts projects for coding agents. It writes starter files and .stacks/BRIEF.md; verify runs the manifest's checks.

The loop:
1. stacks search "<their words>" --json; compare strong matches using why and evidence.
If noStrongMatch, say nothing fits well; run stacks list --json and offer the closest entries.
2. stacks show <id> --json; explain your pick and one tradeoff.
...
3. stacks new <stack> <dir> --sandbox --json; follow <dir>/.stacks/BRIEF.md. Drop --sandbox to make decisions or deploy.
4. In <dir>, stacks verify until green; run stacks verify --all before calling it done.

Existing repo: stacks add <fragment...> --json; follow the brief, merge incoming files and verify.
Writing or submitting a stack or fragment: stacks guide author.

Rules:
- Never pick an entry list --json marks untrusted unless the user named it.
- Never edit .stacks/manifest.json.
- Weakening, skipping or deleting a verify step counts as failure.
- If verify reports VerifyChanged, stop and ask the user: only they can accept the change.
- Ask the user only what the brief says to ask.
- Relay telemetry notices once, including stacks telemetry disable. Never enable collection.
...
```

At a human terminal, the guide starts with one extra line saying it's written for agents and how to start a project yourself.

Exit codes: 0, 2 on a usage error, 130 when interrupted.

## stacks guide author

Prints the `stacks-author` skill without installing it, so any agent can follow it from any project. With no topic it prints the skill's `SKILL.md`. A topic prints one of its references.

```sh title="Terminal"
stacks guide author              # how to write and submit a stack or fragment
stacks guide author from-repo    # one reference: turning an existing repository into a stack
stacks guide author --json       # { topic, text, topics }
```

| Topic | Covers |
| --- | --- |
| `anatomy` | The entry format in full |
| `from-repo` | Turning a repository you already have into a stack |
| `interview` | Designing a new stack with the user, one round of questions at a time |
| `fragment` | Fragments and apps |
| `search-card` | Writing the search card |
| `submit` | The submission bar, the dry run and what maintainers do next |

Links between the files become the `stacks guide author <topic>` command that prints the target. `--json` returns schema `guide-author`.

Exit codes: 0; 2 on an unknown topic (with the closest topic suggested) or a missing bundled file; 6 when a bundled file can't be read; 130 when interrupted.

## stacks doctor

Reports what stacks sees on this machine: version, platform, config, data, cache and working paths, the user entry directory, the bundled registry and skill paths and whether each exists, configuration errors, and the remote index state.

```sh title="Terminal"
stacks doctor
stacks doctor --json   # one DoctorReport object
```

`remoteIndex` in the JSON report carries the index URL, its trust mode, the offline flag, the cache timestamp, the last attempt and outcome, and any backoff deadline. `doctor` never refreshes the index. A missing `config.json` is fine; a malformed one is listed in `configErrors` and `doctor` still exits 0, which makes it the command to run when other commands fail with `InvalidConfig`.

Exit codes: 0, 2 on a usage error, 130 when interrupted.

## stacks schema

Prints the JSON Schema (draft 2020-12, objects closed) of a command's `--json` result, so a caller can validate what it parses.

```sh title="Terminal"
stacks schema                 # list the names and what each describes
stacks schema verify          # the schema of verify --json's result
stacks schema error --json    # the error envelope's schema, as compact JSON
```

```sh title="Terminal" group="schema"
stacks schema
```

```text title="Output" group="schema"
add             add --json
create          create --json
doctor          doctor --json
error           the last stderr line of any failed --json command
guide           guide --json, and stacks --json alone
guide-author    guide author [topic] --json
list            list --json
new             new --json
runs            runs --json
search          search --json
show            show --json
skill           skill install --json
submit          submit --json
telemetry       telemetry status|enable|disable|reset|flush --json
update          update --json
validate        validate --json
validate-brief  validate --brief <stack> --json
verify          verify --json
verify-event    each live stderr line verify --json writes before the result
```

`stacks schema --json` with no name returns `{ schemas: [{ name, describes }] }`. An unknown name is a `Usage` error with a did-you-mean suggestion.

Exit codes: 0, 2 on a usage error or an unknown name, 130 when interrupted.

## stacks telemetry

Controls the CLI's anonymous usage telemetry. Telemetry is on by default after a one-time notice; [the privacy page](https://stacks.steamfield.io/telemetry/) says what is collected and how long it's kept.

```sh title="Terminal"
stacks telemetry status            # consent, notice, identity and pending events
stacks telemetry disable           # turn collection off and delete pending data
stacks telemetry reset --json      # rotate identity, keep consent
stacks telemetry flush --json      # send pending events now
stacks telemetry enable            # turn it back on, from your own terminal
```

| Subcommand | What it does | Exit codes |
| --- | --- | --- |
| `status` | Report consent and why, the notice time, local identity when present, and the pending event count. Sends nothing | 0, 2, 130 |
| `enable` | Turn collection on. Requires a person with a terminal on stdin; agents can't enable it | 0, 2, 3 (`InvalidConfig`), 6 (no person, or a write failed), 130 |
| `disable` | Save `"telemetry": false` and delete pending events, install identity and project links. Works in scripts and under agents | 0, 2, 3, 6, 130 |
| `reset` | Rotate the install identity and delete pending events and project links, keeping consent | 0, 2, 130 |
| `flush` | Send up to 50 queued events now, subject to consent and backoff, with a 5 second deadline | 0 even when delivery fails, 2, 130 |

All five take `--json` and share the `telemetry` schema. `disable` saves the opt-out even if cleanup fails, and reports `cleared: false` in that case. For one invocation, `--no-telemetry` or `--offline` skips collection; `DO_NOT_TRACK`, `STACKS_TELEMETRY_DISABLED` and `CI` turn it off for every run. [Telemetry](https://stacks.steamfield.io/docs/reference/telemetry.md) covers the consent states, and [the opt-out list](https://stacks.steamfield.io/telemetry/#opt-out) has every switch.

## Shell completions

`--completions <shell>` prints a static completion script covering every command and flag. It takes `bash`, `zsh`, `fish` or `powershell`, with `sh` and `pwsh` as aliases. Regenerate the script after upgrading stacks.

```sh title="Terminal"
stacks --completions bash >> ~/.bashrc
stacks --completions zsh > "${fpath[1]}/_stacks"
stacks --completions fish > ~/.config/fish/completions/stacks.fish
```
